feature #23: create route for citizen
This commit is contained in:
@@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()) }
|
||||||
|
|
||||||
|
|||||||
41
src/main/kotlin/fr/dcproject/repository/Citizen.kt
Normal file
41
src/main/kotlin/fr/dcproject/repository/Citizen.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/main/kotlin/fr/dcproject/routes/Citizen.kt
Normal file
21
src/main/kotlin/fr/dcproject/routes/Citizen.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
43
src/main/resources/sql/functions/citizen/find_citizens.sql
Normal file
43
src/main/resources/sql/functions/citizen/find_citizens.sql
Normal 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);
|
||||||
11
src/test/resources/feature/citizen.feature
Normal file
11
src/test/resources/feature/citizen.feature
Normal 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 |
|
||||||
Reference in New Issue
Block a user