Move Entity.Request to Routes

This commit is contained in:
2020-04-15 00:43:59 +02:00
parent 9e50e3af58
commit 7dc1772708
15 changed files with 239 additions and 216 deletions

View File

@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build and start Docker" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
<configuration default="false" name="Build and start all Docker" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
<deployment type="docker-compose.yml">
<settings>
<option name="envVars">

View File

@@ -0,0 +1,27 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run dependencies" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
<deployment type="docker-compose.yml">
<settings>
<option name="envVars">
<list>
<DockerEnvVarImpl>
<option name="name" value="SEND_GRID_KEY" />
<option name="value" value="$SEND_GRID_KEY$" />
</DockerEnvVarImpl>
</list>
</option>
<option name="commandLineOptions" value="--build" />
<option name="services">
<list>
<option value="db" />
<option value="elasticsearch" />
<option value="rabbitmq" />
<option value="redis" />
</list>
</option>
<option name="sourceFilePath" value="docker-compose.yml" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@@ -2,6 +2,14 @@
<configuration default="false" name="Start Db" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
<deployment type="docker-compose.yml">
<settings>
<option name="envVars">
<list>
<DockerEnvVarImpl>
<option name="name" value="SEND_GRID_KEY" />
<option name="value" value="$SEND_GRID_KEY$" />
</DockerEnvVarImpl>
</list>
</option>
<option name="commandLineOptions" value="--build" />
<option name="services">
<list>

View File

@@ -2,6 +2,14 @@
<configuration default="false" name="Start OpenAPI" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
<deployment type="docker-compose.yml">
<settings>
<option name="envVars">
<list>
<DockerEnvVarImpl>
<option name="name" value="SEND_GRID_KEY" />
<option name="value" value="$SEND_GRID_KEY$" />
</DockerEnvVarImpl>
</list>
</option>
<option name="commandLineOptions" value="--build" />
<option name="services">
<list>

View File

@@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Start RabbitMQ" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
<deployment type="docker-compose.yml">
<settings>
<option name="envVars">
<list>
<DockerEnvVarImpl>
<option name="name" value="SEND_GRID_KEY" />
<option name="value" value="$SEND_GRID_KEY$" />
</DockerEnvVarImpl>
</list>
</option>
<option name="commandLineOptions" value="--build" />
<option name="services">
<list>
<option value="rabbitmq" />
</list>
</option>
<option name="sourceFilePath" value="docker-compose.yml" />
</settings>
</deployment>
<method v="2" />
</configuration>
</component>

View File

@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Docker" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
<configuration default="false" name="Start Redis" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
<deployment type="docker-compose.yml">
<settings>
<option name="envVars">
@@ -11,6 +11,11 @@
</list>
</option>
<option name="commandLineOptions" value="--build" />
<option name="services">
<list>
<option value="redis" />
</list>
</option>
<option name="sourceFilePath" value="docker-compose.yml" />
</settings>
</deployment>

View File

@@ -1,42 +0,0 @@
package fr.dcproject.entity.request
import fr.dcproject.entity.ArticleFull
import fr.dcproject.entity.Citizen
import java.util.*
import fr.dcproject.entity.Article as ArticleEntity
class Article(
val id: UUID?,
val title: String,
val anonymous: Boolean = true,
val content: String,
val description: String,
val tags: List<String> = emptyList(),
val draft: Boolean = false,
val versionId: UUID?
) :
Request {
fun merge(article: ArticleFull) {
article.title = this.title
article.content = this.content
article.description = this.description
article.tags = this.tags.distinct()
article.anonymous = this.anonymous
article.draft = this.draft
article.versionId = this.versionId ?: UUID.randomUUID()
}
fun create(createdBy: Citizen): ArticleEntity {
return ArticleEntity(
id ?: UUID.randomUUID(),
title,
anonymous,
content,
description,
tags,
draft,
createdBy = createdBy
).apply { this.versionId = this@Article.versionId ?: UUID.randomUUID() }
}
}

View File

@@ -1,5 +0,0 @@
package fr.dcproject.entity.request
class Comment(
val content: String
) : Request

View File

@@ -1,48 +0,0 @@
package fr.dcproject.entity.request
import fr.dcproject.entity.ArticleRef
import fr.dcproject.entity.Citizen
import fr.dcproject.entity.CitizenSimple
import fr.dcproject.entity.ConstitutionSimple
import fr.postgresjson.entity.immutable.UuidEntity
import java.util.*
class Constitution(
var title: String,
var anonymous: Boolean = true,
var titles: MutableList<Title> = mutableListOf(),
var draft: Boolean = false,
var lastVersion: Boolean = false,
var versionId: UUID = UUID.randomUUID()
) {
init {
titles.forEachIndexed { index, title ->
title.rank = index
}
}
class Title(
id: UUID = UUID.randomUUID(),
var name: String,
var rank: Int? = null,
var articles: MutableList<ArticleRef> = mutableListOf()
) : UuidEntity(id) {
fun create(): ConstitutionSimple.TitleSimple<ArticleRef> {
return ConstitutionSimple.TitleSimple(
id, name, rank, articles
)
}
}
fun create(createdBy: Citizen): ConstitutionSimple<CitizenSimple, ConstitutionSimple.TitleSimple<ArticleRef>> {
return ConstitutionSimple(
title = title,
titles = titles.create(),
createdBy = createdBy,
versionId = versionId
)
}
}
fun List<Constitution.Title>.create(): MutableList<ConstitutionSimple.TitleSimple<ArticleRef>> =
map { it.create() }.toMutableList()

View File

@@ -1,11 +0,0 @@
package fr.dcproject.entity.request
import io.ktor.application.ApplicationCall
interface Request
interface RequestBuilder<E> {
suspend fun getContent(call: ApplicationCall): E
}
suspend fun <E> ApplicationCall.getContent(builder: RequestBuilder<E>) = builder.getContent(this)

View File

@@ -6,9 +6,10 @@ import fr.dcproject.event.ArticleUpdate
import fr.dcproject.repository.Article.Filter
import fr.dcproject.security.voter.ArticleVoter.Action.CREATE
import fr.dcproject.security.voter.ArticleVoter.Action.VIEW
import fr.ktorVoter.assertCan
import fr.dcproject.views.ArticleViewManager
import fr.ktorVoter.assertCan
import fr.postgresjson.repository.RepositoryI
import io.ktor.application.ApplicationCall
import io.ktor.application.application
import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -19,8 +20,8 @@ import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.Route
import kotlinx.coroutines.launch
import java.util.*
import fr.dcproject.entity.Article as ArticleEntity
import fr.dcproject.entity.request.Article as ArticleEntityRequest
import fr.dcproject.repository.Article as ArticleRepository
@KtorExperimentalLocationsAPI
@@ -55,7 +56,33 @@ object ArticlesPaths {
}
@Location("/articles")
class PostArticleRequest
class PostArticleRequest {
class Article(
val id: UUID?,
val title: String,
val anonymous: Boolean = true,
val content: String,
val description: String,
val tags: List<String> = emptyList(),
val draft: Boolean = false,
val versionId: UUID?
)
suspend fun getNewArticle(call: ApplicationCall): ArticleEntity = call.receive<Article>().run {
ArticleEntity(
id ?: UUID.randomUUID(),
title,
anonymous,
content,
description,
tags,
draft,
createdBy = call.citizen
).also {
it.versionId = versionId ?: UUID.randomUUID()
}
}
}
}
@KtorExperimentalLocationsAPI
@@ -82,20 +109,17 @@ fun Route.article(repo: ArticleRepository, viewManager: ArticleViewManager) {
get<ArticlesPaths.ArticleVersionsRequest> {
assertCan(VIEW, it.article)
val versions = repo.findVerionsByVersionsId(it.page, it.limit, it.article.versionId)
call.respond(versions)
repo.findVerionsByVersionsId(it.page, it.limit, it.article.versionId).let {
call.respond(it)
}
}
post<ArticlesPaths.PostArticleRequest> {
val request = call.receive<ArticleEntityRequest>()
val article = request.create(citizen)
assertCan(CREATE, article)
repo.upsert(article)
application.environment.monitor.raise(ArticleUpdate.event, ArticleUpdate(article))
call.respond(article)
it.getNewArticle(call).also { article ->
assertCan(CREATE, article)
repo.upsert(article)
call.respond(article)
application.environment.monitor.raise(ArticleUpdate.event, ArticleUpdate(article))
}
}
}

View File

@@ -7,6 +7,7 @@ import fr.dcproject.repository.CommentArticle.Sort
import fr.dcproject.security.voter.CommentVoter.Action.CREATE
import fr.dcproject.security.voter.CommentVoter.Action.VIEW
import fr.ktorVoter.assertCan
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -17,7 +18,6 @@ import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.Route
import fr.dcproject.entity.Comment as CommentEntity
import fr.dcproject.entity.request.Comment as CommentEntityRequest
import fr.dcproject.repository.CommentArticle as CommentArticleRepository
@KtorExperimentalLocationsAPI
@@ -35,6 +35,23 @@ object CommentArticlePaths {
val sort: Sort = Sort.fromString(sort) ?: Sort.CREATED_AT
}
@Location("/articles/{article}/comments")
class PostArticleCommentRequest(
val article: Article
) {
class Comment(
val content: String
)
suspend fun getComment(call: ApplicationCall) = call.receive<Comment>().run {
CommentEntity(
target = article,
createdBy = call.citizen,
content = content
)
}
}
@Location("/citizens/{citizen}/comments/articles")
class CitizenCommentArticleRequest(val citizen: Citizen)
}
@@ -49,23 +66,18 @@ fun Route.commentArticle(repo: CommentArticleRepository) {
call.respond(HttpStatusCode.OK, comment)
}
post<CommentArticlePaths.ArticleCommentRequest> {
val content = call.receive<CommentEntityRequest>().content
val comment = CommentEntity(
target = it.article,
createdBy = citizen,
content = content
)
assertCan(CREATE, comment)
repo.comment(comment)
call.respond(HttpStatusCode.Created, comment)
post<CommentArticlePaths.PostArticleCommentRequest> {
it.getComment(call).let { comment ->
assertCan(CREATE, comment)
repo.comment(comment)
call.respond(HttpStatusCode.Created, comment)
}
}
get<CommentArticlePaths.CitizenCommentArticleRequest> {
val comments = repo.findByCitizen(it.citizen)
assertCan(VIEW, comments.result)
call.respond(comments)
repo.findByCitizen(it.citizen).let { comments ->
assertCan(VIEW, comments.result)
call.respond(comments)
}
}
}

View File

@@ -1,11 +1,15 @@
package fr.dcproject.routes
import fr.dcproject.citizen
import fr.dcproject.entity.request.Constitution
import fr.dcproject.entity.ArticleRef
import fr.dcproject.entity.CitizenSimple
import fr.dcproject.entity.ConstitutionSimple
import fr.dcproject.security.voter.ConstitutionVoter.Action.CREATE
import fr.dcproject.security.voter.ConstitutionVoter.Action.VIEW
import fr.ktorVoter.assertCan
import fr.postgresjson.entity.immutable.UuidEntity
import fr.postgresjson.repository.RepositoryI
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location
@@ -14,6 +18,7 @@ import io.ktor.locations.post
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.Route
import java.util.*
import fr.dcproject.entity.Constitution as ConstitutionEntity
import fr.dcproject.repository.Constitution as ConstitutionRepository
@@ -35,7 +40,46 @@ object ConstitutionPaths {
class ConstitutionRequest(val constitution: ConstitutionEntity)
@Location("/constitutions")
class PostConstitutionRequest
class PostConstitutionRequest {
class Constitution(
var title: String,
var anonymous: Boolean = true,
var titles: MutableList<Title> = mutableListOf(),
var draft: Boolean = false,
var lastVersion: Boolean = false,
var versionId: UUID = UUID.randomUUID()
) {
init {
titles.forEachIndexed { index, title ->
title.rank = index
}
}
class Title(
id: UUID = UUID.randomUUID(),
var name: String,
var rank: Int? = null,
var articles: MutableList<ArticleRef> = mutableListOf()
) : UuidEntity(id) {
fun create(): ConstitutionSimple.TitleSimple<ArticleRef> =
ConstitutionSimple.TitleSimple(
id, name, rank, articles
)
}
fun List<Title>.create(): MutableList<ConstitutionSimple.TitleSimple<ArticleRef>> =
map { it.create() }.toMutableList()
}
suspend fun getNewConstitution(call: ApplicationCall): ConstitutionSimple<CitizenSimple, ConstitutionSimple.TitleSimple<ArticleRef>> = call.receive<Constitution>().run {
ConstitutionSimple(
title = title,
titles = titles.create(),
createdBy = call.citizen,
versionId = versionId
)
}
}
}
@KtorExperimentalLocationsAPI
@@ -52,11 +96,10 @@ fun Route.constitution(repo: ConstitutionRepository) {
}
post<ConstitutionPaths.PostConstitutionRequest> {
val constitution = call.receive<Constitution>().create(citizen)
assertCan(CREATE, constitution)
repo.upsert(constitution)
call.respond(constitution)
it.getNewConstitution(call).let { constitution ->
assertCan(CREATE, constitution)
repo.upsert(constitution)
call.respond(constitution)
}
}
}

View File

@@ -3,13 +3,10 @@ package fr.dcproject.routes
import fr.dcproject.citizen
import fr.dcproject.entity.CitizenRef
import fr.dcproject.entity.OpinionChoiceRef
import fr.dcproject.entity.request.RequestBuilder
import fr.dcproject.entity.request.getContent
import fr.dcproject.security.voter.OpinionVoter.Action.CREATE
import fr.dcproject.security.voter.OpinionVoter.Action.VIEW
import fr.ktorVoter.assertCan
import fr.dcproject.utils.toUUID
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -44,14 +41,10 @@ object OpinionArticlePaths {
*/
@Location("/articles/{article}/opinions")
@KtorExperimentalAPI
class ArticleOpinion(val article: ArticleEntity) : RequestBuilder<List<OpinionChoiceRef>> {
private class Content(ids: List<String>) : KoinComponent {
val ids = ids.map { it.toUUID() }
class ArticleOpinion(val article: ArticleEntity) {
class Body(ids: List<String>) {
val ids = ids.map { OpinionChoiceRef(it.toUUID()) }
}
override suspend fun getContent(call: ApplicationCall): List<OpinionChoiceRef> =
call.receive<Content>().ids.map { OpinionChoiceRef(it) }
}
/**
@@ -80,12 +73,11 @@ fun Route.opinionArticle(repo: OpinionArticleRepository) {
}
put<OpinionArticlePaths.ArticleOpinion> {
call.getContent(it)
.let { choices ->
assertCan(CREATE, it.article)
repo.updateOpinions(choices, citizen, it.article)
}.let {
call.respond(HttpStatusCode.Created, it)
}
call.receive<OpinionArticlePaths.ArticleOpinion.Body>().ids.let { choices ->
assertCan(CREATE, it.article)
repo.updateOpinions(choices, citizen, it.article)
}.let {
call.respond(HttpStatusCode.Created, it)
}
}
}

View File

@@ -3,8 +3,6 @@ package fr.dcproject.routes
import fr.dcproject.citizen
import fr.dcproject.entity.CitizenRef
import fr.dcproject.entity.WorkgroupSimple
import fr.dcproject.entity.request.RequestBuilder
import fr.dcproject.entity.request.getContent
import fr.dcproject.repository.Workgroup.Filter
import fr.dcproject.security.voter.WorkgroupVoter.Action.VIEW
import fr.dcproject.security.voter.WorkgroupVoter.Action.CREATE
@@ -27,7 +25,6 @@ import io.ktor.locations.delete
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.Route
import org.koin.core.KoinComponent
import java.util.*
import fr.dcproject.entity.Workgroup as WorkgroupEntity
import fr.dcproject.repository.Workgroup as WorkgroupRepository
@@ -51,54 +48,44 @@ object WorkgroupsPaths {
class WorkgroupRequest(val workgroup: WorkgroupEntity)
@Location("/workgroups")
open class PostWorkgroupRequest : RequestBuilder<WorkgroupSimple<CitizenRef>> {
class Content(
open class PostWorkgroupRequest {
class Body(
val id: UUID?,
val name: String,
val description: String,
val logo: String?,
val anonymous: Boolean?,
val owner: UUID?
) : KoinComponent {
fun create(creator: CitizenRef): WorkgroupSimple<CitizenRef> {
return WorkgroupSimple(
id ?: UUID.randomUUID(),
name,
description,
logo,
anonymous ?: true,
owner?.let { CitizenRef(it) } ?: creator,
creator
)
}
}
)
override suspend fun getContent(call: ApplicationCall): WorkgroupSimple<CitizenRef> {
return call.receive<Content>().create(call.citizen)
suspend fun getNewWorkgroup(call: ApplicationCall): WorkgroupSimple<CitizenRef> = call.receive<Body>().run {
WorkgroupSimple(
id ?: UUID.randomUUID(),
name,
description,
logo,
anonymous ?: true,
owner?.let { CitizenRef(it) } ?: call.citizen,
call.citizen
)
}
}
@Location("/workgroups/{workgroup}")
class PutWorkgroupRequest(val workgroup: WorkgroupEntity) : RequestBuilder<WorkgroupEntity> {
class Content(
class PutWorkgroupRequest(val workgroup: WorkgroupEntity) {
class Body(
val name: String?,
val description: String?,
val logo: String?,
val anonymous: Boolean?,
val owner: UUID?
) : KoinComponent {
fun update(workgroup: WorkgroupEntity): WorkgroupEntity {
name?.let { workgroup.name = it }
description?.let { workgroup.description = it }
logo?.let { workgroup.logo = it }
anonymous?.let { workgroup.anonymous = it }
)
return workgroup
}
}
override suspend fun getContent(call: ApplicationCall): WorkgroupEntity {
return call.receive<Content>().update(workgroup)
suspend fun updateWorkgroup(call: ApplicationCall): Unit = call.receive<Body>().run {
name?.let { workgroup.name = it }
description?.let { workgroup.description = it }
logo?.let { workgroup.logo = it }
anonymous?.let { workgroup.anonymous = it }
}
}
@@ -109,15 +96,15 @@ object WorkgroupsPaths {
@KtorExperimentalLocationsAPI
object WorkgroupsMembersPaths {
@Location("/workgroups/{workgroup}/members")
class WorkgroupsMembersRequest(val workgroup: WorkgroupEntity) : RequestBuilder<List<CitizenRef>> {
class Content : MutableList<Content.Item> by mutableListOf() {
class Item(val id: String)
class WorkgroupsMembersRequest(val workgroup: WorkgroupEntity) {
class Body : MutableList<Body.Item> by mutableListOf() {
class Item(id: String) {
val id = id.toUUID()
}
}
override suspend fun getContent(call: ApplicationCall): List<CitizenRef> {
return call.receive<Content>().map {
CitizenRef(it.id.toUUID())
}
suspend fun getMembers(call: ApplicationCall): List<CitizenRef> = call.receive<Body>().map {
CitizenRef(it.id)
}
}
}
@@ -138,7 +125,7 @@ fun Route.workgroup(repo: WorkgroupRepository) {
}
post<WorkgroupsPaths.PostWorkgroupRequest> {
call.getContent(it)
it.getNewWorkgroup(call)
.let { workgroup ->
assertCan(CREATE, workgroup)
repo.upsert(workgroup)
@@ -148,13 +135,12 @@ fun Route.workgroup(repo: WorkgroupRepository) {
}
put<WorkgroupsPaths.PutWorkgroupRequest> {
call.getContent(it)
.let { workgroup ->
assertCan(UPDATE, workgroup)
repo.upsert(workgroup as WorkgroupSimple<CitizenRef>)
}.let {
call.respond(HttpStatusCode.OK, it)
}
it.updateWorkgroup(call).let { workgroup ->
assertCan(UPDATE, workgroup)
repo.upsert(workgroup as WorkgroupSimple<CitizenRef>)
}.let {
call.respond(HttpStatusCode.OK, it)
}
}
delete<WorkgroupsPaths.DeleteWorkgroupRequest> {
@@ -165,7 +151,7 @@ fun Route.workgroup(repo: WorkgroupRepository) {
/* Add members to workgroup */
post<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
call.getContent(it)
it.getMembers(call)
.let { members ->
assertCan(ADD_MEMBERS, it.workgroup)
repo.addMembers(it.workgroup, members)
@@ -176,7 +162,7 @@ fun Route.workgroup(repo: WorkgroupRepository) {
/* Delete members of workgroup */
delete<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
call.getContent(it)
it.getMembers(call)
.let { members ->
assertCan(REMOVE_MEMBERS, it.workgroup)
repo.removeMembers(it.workgroup, members)
@@ -187,7 +173,7 @@ fun Route.workgroup(repo: WorkgroupRepository) {
/* Update members of workgroup */
put<WorkgroupsMembersPaths.WorkgroupsMembersRequest> {
call.getContent(it)
it.getMembers(call)
.let { members ->
assertCan(UPDATE_MEMBERS, it.workgroup)
repo.updateMembers(it.workgroup, members)