feature #12: Add constitution Entity, repository and route

This commit is contained in:
2019-08-06 18:20:21 +02:00
parent 6131935036
commit 86d699c9c0
11 changed files with 288 additions and 20 deletions

View File

@@ -7,7 +7,10 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.joda.JodaModule
import fr.dcproject.entity.Article
import fr.dcproject.entity.Constitution
import fr.dcproject.routes.article
import fr.dcproject.routes.constitution
import fr.postgresjson.migration.Migrations
import io.ktor.application.Application
import io.ktor.application.install
import io.ktor.auth.Authentication
@@ -23,6 +26,7 @@ import org.koin.ktor.ext.Koin
import org.koin.ktor.ext.get
import java.util.*
import fr.dcproject.repository.Article as RepositoryArticle
import fr.dcproject.repository.Constitution as RepositoryConstitution
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
@@ -36,6 +40,7 @@ fun Application.module() {
}
install(DataConversion) {
// TODO move to postgresJson lib
convert<UUID> {
decode { values, _ ->
values.singleOrNull()?.let { UUID.fromString(it) }
@@ -49,6 +54,8 @@ fun Application.module() {
}
}
}
// create generic convert for entityI
convert<Article> {
decode { values, _ ->
val id = values.singleOrNull()?.let { UUID.fromString(it) }
@@ -56,6 +63,13 @@ fun Application.module() {
get<RepositoryArticle>().findById(id) ?: throw InternalError("Article $values not found")
}
}
convert<Constitution> {
decode { values, _ ->
val id = values.singleOrNull()?.let { UUID.fromString(it) }
?: throw InternalError("Cannot convert $values to UUID")
get<RepositoryConstitution>().findById(id) ?: throw InternalError("Constitution $values not found")
}
}
}
install(Locations) {
@@ -84,5 +98,9 @@ fun Application.module() {
install(Routing) {
article(get())
constitution(get())
}
// TODO move to postgresJson lib
get<Migrations>().run()
}

View File

@@ -6,6 +6,7 @@ import fr.postgresjson.migration.Migrations
import io.ktor.util.KtorExperimentalAPI
import org.koin.dsl.module
import fr.dcproject.repository.Article as ArticleRepository
import fr.dcproject.repository.Constitution as ConstitutionRepository
val config = Config()
@@ -21,6 +22,9 @@ val Module = module {
functionsDirectory = config.sqlFiles.resolve("functions")
).createRequester() }
// create generic declaration
single { ArticleRepository(get()) }
single { ConstitutionRepository(get()) }
single { Migrations(connection = get(), directory = config.sqlFiles) }
}

View File

@@ -5,15 +5,14 @@ import java.util.*
class Article(
id: UUID = UUID.randomUUID(),
var versionId: UUID = UUID.randomUUID(),
var versionNumber: Int? = null,
var title: String?,
var annonymous: Boolean? = true,
var content: String?,
var description: String?,
var tags: List<String> = emptyList(),
override var createdBy: Citizen?
createdBy: Citizen?
):
UuidEntity(id),
EntityVersioning<UUID, Int> by UuidEntityVersioning(),
EntityCreatedAt by EntityCreatedAtImp(),
CreatedBy<Citizen> by EntityCreatedByImp()
CreatedBy<Citizen> by EntityCreatedByImp(createdBy)

View File

@@ -0,0 +1,32 @@
package fr.dcproject.entity
import fr.postgresjson.entity.*
import java.util.*
class Constitution(
id: UUID = UUID.randomUUID(),
var title: String?,
var annonymous: Boolean?,
var titles: List<Title>,
createdBy: Citizen?
): UuidEntity(id),
EntityVersioning<UUID, Int> by UuidEntityVersioning(),
EntityCreatedAt by EntityCreatedAtImp(),
CreatedBy<Citizen> by EntityCreatedByImp(createdBy) {
init{
titles.forEachIndexed { index, title ->
title.createdBy = this.createdBy
title.rank = index
}
}
class Title(
id: UUID = UUID.randomUUID(),
var name: String?,
var rank: Int? = null,
createdBy: Citizen? = null
): UuidEntity(id),
EntityCreatedAt by EntityCreatedAtImp(),
CreatedBy<Citizen> by EntityCreatedByImp(createdBy)
}

View File

@@ -2,8 +2,8 @@ package fr.dcproject.repository
import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester
import fr.postgresjson.entity.EntitiesCollections
import fr.postgresjson.repository.RepositoryI
import fr.postgresjson.repository.RepositoryI.Direction
import net.pearx.kasechange.toSnakeCase
import java.util.*
import fr.dcproject.entity.Article as ArticleEntity
@@ -13,12 +13,7 @@ class Article(override var requester: Requester) : RepositoryI<ArticleEntity> {
fun findById(id: UUID): ArticleEntity? {
val function = requester.getFunction("find_article_by_id")
return when (val e = EntitiesCollections().get(id) as ArticleEntity?) {
null -> {
function.selectOne("id" to id)
}
else -> e
}
return function.selectOne("id" to id)
}
fun find(
@@ -41,13 +36,6 @@ class Article(override var requester: Requester) : RepositoryI<ArticleEntity> {
fun upsert(article: ArticleEntity): ArticleEntity? {
return requester
.getFunction("upsert_article")
.selectOne<ArticleEntity>("resource" to article)?.also {
EntitiesCollections().set(it)
}
}
enum class Direction {
asc,
desc
.selectOne("resource" to article)
}
}

View File

@@ -0,0 +1,41 @@
package fr.dcproject.repository
import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester
import fr.postgresjson.repository.RepositoryI
import fr.postgresjson.repository.RepositoryI.Direction
import net.pearx.kasechange.toSnakeCase
import java.util.*
import fr.dcproject.entity.Constitution as ConstitutionEntity
class Constitution(override var requester: Requester) : RepositoryI<ConstitutionEntity> {
override val entityName = ConstitutionEntity::class
fun findById(id: UUID): ConstitutionEntity? {
val function = requester.getFunction("find_constitution_by_id")
return function.selectOne("id" to id)
}
fun find(
page: Int = 1,
limit: Int = 50,
sort: String? = null,
direction: Direction? = null,
search: String? = null
): Paginated<ConstitutionEntity> {
return requester
.getFunction("find_constitutions")
.select(
page, limit,
"sort" to sort?.toSnakeCase(),
"direction" to direction,
"search" to search
)
}
fun upsert(article: ConstitutionEntity): ConstitutionEntity? {
return requester
.getFunction("upsert_constitution")
.selectOne("resource" to article)
}
}

View File

@@ -0,0 +1,30 @@
package fr.dcproject.routes
import Paths
import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.get
import io.ktor.locations.post
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.Route
import fr.dcproject.entity.Constitution as ConstitutionEntity
import fr.dcproject.repository.Constitution as ConstitutionRepository
@KtorExperimentalLocationsAPI
fun Route.constitution(repo: ConstitutionRepository) {
get<Paths.ConstitutionsRequest> {
val constitutions = repo.find(it.page, it.limit, it.sort, it.direction, it.search)
call.respond(constitutions)
}
get<Paths.ConstitutionRequest> {
call.respond(it.constitution)
}
post<Paths.PostConstitutionRequest>() {
val constitution = call.receive<ConstitutionEntity>()
repo.upsert(constitution)
call.respond(constitution)
}
}

View File

@@ -1,5 +1,6 @@
import fr.dcproject.entity.Article
import fr.dcproject.repository.Article.Direction
import fr.dcproject.entity.Constitution
import fr.postgresjson.repository.RepositoryI.Direction
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location
@@ -11,4 +12,12 @@ object Paths {
}
@Location("/articles/{article}") class ArticleRequest(val article: Article)
@Location("/articles") class PostArticleRequest
@Location("/constitutions") class ConstitutionsRequest(page: Int = 1, limit: Int = 50, val sort: String? = null, val direction: Direction? = null, val search: String? = null) {
val page: Int = if (page < 1) 1 else page
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
}
@Location("/constitutions/{constitution}") class ConstitutionRequest(val constitution: Constitution)
@Location("/constitutions") class PostConstitutionRequest
}

View File

@@ -17,6 +17,9 @@ begin
returning id into new_id;
if resource->>'relations' is not null then
delete from article_relations
where source_id = (resource->>'id')::uuid;
insert into article_relations (source_id, target_id, created_by_id)
select
(resource->>'id')::uuid,

View File

@@ -0,0 +1,42 @@
create or replace function find_constitutions(
search text default null,
direction text default 'desc',
sort text default 'created_at',
"limit" int default 50,
"offset" int default 0,
out resource json,
out total int
) language plpgsql as
$$
begin
select json_agg(t), (select count(id) from constitution)
into resource, total
from (
select
c.*,
find_citizen_by_id(c.created_by_id) as created_by,
find_constitution_titles_by_id(c.id) as titles
from constitution as c
where "search" is null or title ilike '%'||"search"||'%'
order by
case direction when 'asc' then
case sort
when 'title' then c.title
when 'created_at' then c.created_at::text
else null
end
end,
case direction when 'desc' then
case sort
when 'title' then c.title
when 'created_at' then c.created_at::text
end
end
desc,
c.created_at desc
limit "limit" offset "offset"
) as t;
end;
$$;
-- drop function if exists find_constitutions(json, int, int);