feature #23: create route for citizen

This commit is contained in:
2019-08-15 15:59:46 +02:00
parent 9821833dd8
commit 7ae30bd3cd
7 changed files with 141 additions and 1 deletions

View File

@@ -7,8 +7,10 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.databind.SerializationFeature import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.joda.JodaModule import com.fasterxml.jackson.datatype.joda.JodaModule
import fr.dcproject.entity.Article import fr.dcproject.entity.Article
import fr.dcproject.entity.Citizen
import fr.dcproject.entity.Constitution import fr.dcproject.entity.Constitution
import fr.dcproject.routes.article import fr.dcproject.routes.article
import fr.dcproject.routes.citizen
import fr.dcproject.routes.constitution import fr.dcproject.routes.constitution
import fr.dcproject.routes.followArticle import fr.dcproject.routes.followArticle
import fr.postgresjson.migration.Migrations import fr.postgresjson.migration.Migrations
@@ -30,6 +32,7 @@ import org.koin.ktor.ext.get
import org.slf4j.event.Level import org.slf4j.event.Level
import java.util.* import java.util.*
import fr.dcproject.repository.Article as RepositoryArticle import fr.dcproject.repository.Article as RepositoryArticle
import fr.dcproject.repository.Citizen as RepositoryCitizen
import fr.dcproject.repository.Constitution as RepositoryConstitution import fr.dcproject.repository.Constitution as RepositoryConstitution
fun main(args: Array<String>): Unit = io.ktor.server.jetty.EngineMain.main(args) fun main(args: Array<String>): Unit = io.ktor.server.jetty.EngineMain.main(args)
@@ -63,7 +66,7 @@ fun Application.module() {
} }
} }
// create generic convert for entityI // TODO: create generic convert for entityI
convert<Article> { convert<Article> {
decode { values, _ -> decode { values, _ ->
val id = values.singleOrNull()?.let { UUID.fromString(it) } val id = values.singleOrNull()?.let { UUID.fromString(it) }
@@ -71,6 +74,7 @@ fun Application.module() {
get<RepositoryArticle>().findById(id) ?: throw InternalError("Article $values not found") get<RepositoryArticle>().findById(id) ?: throw InternalError("Article $values not found")
} }
} }
convert<Constitution> { convert<Constitution> {
decode { values, _ -> decode { values, _ ->
val id = values.singleOrNull()?.let { UUID.fromString(it) } val id = values.singleOrNull()?.let { UUID.fromString(it) }
@@ -78,6 +82,14 @@ fun Application.module() {
get<RepositoryConstitution>().findById(id) ?: throw InternalError("Constitution $values not found") get<RepositoryConstitution>().findById(id) ?: throw InternalError("Constitution $values not found")
} }
} }
convert<Citizen> {
decode { values, _ ->
val id = values.singleOrNull()?.let { UUID.fromString(it) }
?: throw InternalError("Cannot convert $values to UUID")
get<RepositoryCitizen>().findById(id) ?: throw InternalError("Citizen $values not found")
}
}
} }
install(Locations) { install(Locations) {
@@ -106,6 +118,7 @@ fun Application.module() {
install(Routing) { install(Routing) {
article(get()) article(get())
citizen(get())
constitution(get()) constitution(get())
followArticle(get()) followArticle(get())
} }

View File

@@ -1,12 +1,14 @@
package fr.dcproject package fr.dcproject
import fr.dcproject.repository.FollowArticleRepository import fr.dcproject.repository.FollowArticleRepository
import fr.dcproject.repository.FollowConstitutionRepository
import fr.postgresjson.connexion.Connection import fr.postgresjson.connexion.Connection
import fr.postgresjson.connexion.Requester import fr.postgresjson.connexion.Requester
import fr.postgresjson.migration.Migrations import fr.postgresjson.migration.Migrations
import io.ktor.util.KtorExperimentalAPI import io.ktor.util.KtorExperimentalAPI
import org.koin.dsl.module import org.koin.dsl.module
import fr.dcproject.repository.Article as ArticleRepository import fr.dcproject.repository.Article as ArticleRepository
import fr.dcproject.repository.Citizen as CitizenRepository
import fr.dcproject.repository.Constitution as ConstitutionRepository import fr.dcproject.repository.Constitution as ConstitutionRepository
val config = Config() val config = Config()
@@ -25,6 +27,7 @@ val Module = module {
// TODO: create generic declaration // TODO: create generic declaration
single { ArticleRepository(get()) } single { ArticleRepository(get()) }
single { CitizenRepository(get()) }
single { ConstitutionRepository(get()) } single { ConstitutionRepository(get()) }
single { FollowArticleRepository(get()) } single { FollowArticleRepository(get()) }

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.Citizen as CitizenEntity
class Citizen(override var requester: Requester) : RepositoryI<CitizenEntity> {
override val entityName = CitizenEntity::class
fun findById(id: UUID): CitizenEntity? {
val function = requester.getFunction("find_citizen_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<CitizenEntity> {
return requester
.getFunction("find_citizens")
.select(
page, limit,
"sort" to sort?.toSnakeCase(),
"direction" to direction,
"search" to search
)
}
fun upsert(citizen: CitizenEntity): CitizenEntity? {
return requester
.getFunction("upsert_citizen")
.selectOne("resource" to citizen)
}
}

View File

@@ -0,0 +1,21 @@
package fr.dcproject.routes
import Paths
import io.ktor.application.call
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.get
import io.ktor.response.respond
import io.ktor.routing.Route
import fr.dcproject.repository.Citizen as CitizenRepository
@KtorExperimentalLocationsAPI
fun Route.citizen(repo: CitizenRepository) {
get<Paths.CitizensRequest> {
val citizens = repo.find(it.page, it.limit, it.sort, it.direction, it.search)
call.respond(citizens)
}
get<Paths.CitizenRequest> {
call.respond(it.citizen)
}
}

View File

@@ -1,4 +1,5 @@
import fr.dcproject.entity.Article import fr.dcproject.entity.Article
import fr.dcproject.entity.Citizen
import fr.dcproject.entity.Constitution import fr.dcproject.entity.Constitution
import fr.dcproject.entity.Follow import fr.dcproject.entity.Follow
import fr.postgresjson.repository.RepositoryI.Direction import fr.postgresjson.repository.RepositoryI.Direction
@@ -22,4 +23,11 @@ object Paths {
} }
@Location("/constitutions/{constitution}") class ConstitutionRequest(val constitution: Constitution) @Location("/constitutions/{constitution}") class ConstitutionRequest(val constitution: Constitution)
@Location("/constitutions") class PostConstitutionRequest @Location("/constitutions") class PostConstitutionRequest
@Location("/citizens") class CitizensRequest(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("/citizens/{citizen}") class CitizenRequest(val citizen: Citizen)
} }

View File

@@ -0,0 +1,43 @@
create or replace function find_citizens(
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 citizen)
into resource, total
from (
select
z.*
from citizen as z
where "search" is null or (
(name->'first_name')::text ilike '%'||"search"||'%' or
(name->'last_name')::text ilike '%'||"search"||'%'
)
order by
case direction when 'asc' then
case sort
when 'name' then (z.name->'first_name')::text
when 'created_at' then z.created_at::text
else null
end
end,
case direction when 'desc' then
case sort
when 'name' then (z.name->'first_name')::text
when 'created_at' then z.created_at::text
end
end
desc,
z.created_at desc
limit "limit" offset "offset"
) as t;
end;
$$;
-- drop function if exists find_citizens(text, text, text, int, int);

View File

@@ -0,0 +1,11 @@
Feature: citizens routes
Scenario: The route for get citizens must response a 200
When I send a "GET" request to "/citizens"
Then the response status code should be 200
Scenario: The route for get one citizen must response a 200 and return citizen
When I send a "GET" request to "/citizens/6434f4f9-f570-f22a-c134-8668350651ff"
Then the response status code should be 200
And the response should contain object:
| id | 6434f4f9-f570-f22a-c134-8668350651ff |