feature #14: Add routes for get follows of one citizen
This commit is contained in:
@@ -1,7 +1,5 @@
|
|||||||
package fr.dcproject
|
package fr.dcproject
|
||||||
|
|
||||||
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
|
||||||
@@ -10,6 +8,8 @@ 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.Citizen as CitizenRepository
|
||||||
import fr.dcproject.repository.Constitution as ConstitutionRepository
|
import fr.dcproject.repository.Constitution as ConstitutionRepository
|
||||||
|
import fr.dcproject.repository.FollowArticle as FollowArticleRepository
|
||||||
|
import fr.dcproject.repository.FollowConstitution as FollowConstitutionRepository
|
||||||
|
|
||||||
val config = Config()
|
val config = Config()
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
package fr.dcproject.entity
|
package fr.dcproject.entity
|
||||||
import fr.postgresjson.entity.EntityI
|
import fr.postgresjson.entity.UuidEntity
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class Follow <T: EntityI<UUID>> (
|
class Follow <T: UuidEntity> (
|
||||||
id: UUID = UUID.randomUUID(),
|
id: UUID = UUID.randomUUID(),
|
||||||
citizen: Citizen,
|
citizen: Citizen,
|
||||||
override var target: T
|
override var target: T
|
||||||
): Extra<T>(id, citizen)
|
): Extra<T>(id, citizen)
|
||||||
|
|
||||||
typealias FollowArticleEntity = Follow<Article>
|
|
||||||
typealias FollowConstitutionEntity = Follow<Constitution>
|
|
||||||
@@ -1,16 +1,37 @@
|
|||||||
package fr.dcproject.repository
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.postgresjson.connexion.Paginated
|
||||||
import fr.postgresjson.connexion.Requester
|
import fr.postgresjson.connexion.Requester
|
||||||
import fr.postgresjson.entity.EntityI
|
import fr.postgresjson.entity.UuidEntity
|
||||||
import fr.postgresjson.repository.RepositoryI
|
import fr.postgresjson.repository.RepositoryI
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import fr.dcproject.entity.Article as ArticleEntity
|
import fr.dcproject.entity.Article as ArticleEntity
|
||||||
|
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||||
import fr.dcproject.entity.Constitution as ConstitutionEntity
|
import fr.dcproject.entity.Constitution as ConstitutionEntity
|
||||||
import fr.dcproject.entity.Follow as FollowEntity
|
import fr.dcproject.entity.Follow as FollowEntity
|
||||||
|
|
||||||
open class Follow <T: EntityI<UUID>>(override var requester: Requester): RepositoryI<FollowEntity<T>> {
|
open class Follow <T: UuidEntity>(override var requester: Requester): RepositoryI<FollowEntity<T>> {
|
||||||
override val entityName = FollowEntity::class as KClass<FollowEntity<T>>
|
override val entityName = FollowEntity::class as KClass<FollowEntity<T>>
|
||||||
|
open fun findByCitizenId(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50
|
||||||
|
): Paginated<FollowEntity<T>> =
|
||||||
|
findByCitizenId(citizen.id ?: error("The citizen must have an id"), page, limit)
|
||||||
|
|
||||||
|
open fun findByCitizenId(
|
||||||
|
citizenId: UUID,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50
|
||||||
|
): Paginated<FollowEntity<T>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_follows_by_citizen")
|
||||||
|
.select(page, limit,
|
||||||
|
"citizen_id" to citizenId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun follow(follow: FollowEntity<T>) {
|
fun follow(follow: FollowEntity<T>) {
|
||||||
val reference = follow.target::class.simpleName!!.toLowerCase()
|
val reference = follow.target::class.simpleName!!.toLowerCase()
|
||||||
@@ -34,5 +55,33 @@ open class Follow <T: EntityI<UUID>>(override var requester: Requester): Reposit
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class FollowArticleRepository(override var requester: Requester): Follow<ArticleEntity>(requester)
|
|
||||||
class FollowConstitutionRepository(override var requester: Requester): Follow<ConstitutionEntity>(requester)
|
class FollowArticle (requester: Requester): Follow<ArticleEntity>(requester) {
|
||||||
|
override fun findByCitizenId(
|
||||||
|
citizenId: UUID,
|
||||||
|
page: Int,
|
||||||
|
limit: Int
|
||||||
|
): Paginated<FollowEntity<ArticleEntity>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_follows_article_by_citizen")
|
||||||
|
.select(page, limit,
|
||||||
|
"citizen_id" to citizenId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FollowConstitution (requester: Requester): Follow<ConstitutionEntity>(requester) {
|
||||||
|
override fun findByCitizenId(
|
||||||
|
citizenId: UUID,
|
||||||
|
page: Int,
|
||||||
|
limit: Int
|
||||||
|
): Paginated<FollowEntity<ConstitutionEntity>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_follows_constitution_by_citizen")
|
||||||
|
.select(page, limit,
|
||||||
|
"citizen_id" to citizenId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,17 +3,18 @@ package fr.dcproject.routes
|
|||||||
import Paths
|
import Paths
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.Citizen
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.User
|
||||||
import fr.dcproject.repository.FollowArticleRepository
|
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.ktor.locations.delete
|
import io.ktor.locations.delete
|
||||||
|
import io.ktor.locations.get
|
||||||
import io.ktor.locations.post
|
import io.ktor.locations.post
|
||||||
import io.ktor.response.respond
|
import io.ktor.response.respond
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import fr.dcproject.entity.Follow as FollowEntity
|
import fr.dcproject.entity.Follow as FollowEntity
|
||||||
|
import fr.dcproject.repository.FollowArticle as FollowArticleRepository
|
||||||
|
|
||||||
// TODO get current citizen
|
// TODO get current citizen
|
||||||
val currentCitizen = Citizen(
|
val currentCitizen = Citizen(
|
||||||
@@ -34,4 +35,9 @@ fun Route.followArticle(repo: FollowArticleRepository) {
|
|||||||
repo.unfollow(FollowEntity(target = it.article, citizen = currentCitizen))
|
repo.unfollow(FollowEntity(target = it.article, citizen = currentCitizen))
|
||||||
call.respond(HttpStatusCode.NoContent)
|
call.respond(HttpStatusCode.NoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get<Paths.CitizenFollowArticleRequest> {
|
||||||
|
val follows = repo.findByCitizenId(it.citizen)
|
||||||
|
call.respond(follows)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,17 +3,18 @@ package fr.dcproject.routes
|
|||||||
import Paths
|
import Paths
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.Citizen
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.User
|
||||||
import fr.dcproject.repository.FollowConstitutionRepository
|
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.http.HttpStatusCode
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.ktor.locations.delete
|
import io.ktor.locations.delete
|
||||||
|
import io.ktor.locations.get
|
||||||
import io.ktor.locations.post
|
import io.ktor.locations.post
|
||||||
import io.ktor.response.respond
|
import io.ktor.response.respond
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import fr.dcproject.entity.Follow as FollowEntity
|
import fr.dcproject.entity.Follow as FollowEntity
|
||||||
|
import fr.dcproject.repository.FollowConstitution as FollowConstitutionRepository
|
||||||
|
|
||||||
// TODO get current citizen
|
// TODO get current citizen
|
||||||
val currentCitizen2 = Citizen(
|
val currentCitizen2 = Citizen(
|
||||||
@@ -34,4 +35,9 @@ fun Route.followConstitution(repo: FollowConstitutionRepository) {
|
|||||||
repo.unfollow(FollowEntity(target = it.constitution, citizen = currentCitizen2))
|
repo.unfollow(FollowEntity(target = it.constitution, citizen = currentCitizen2))
|
||||||
call.respond(HttpStatusCode.NoContent)
|
call.respond(HttpStatusCode.NoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get<Paths.CitizenFollowConstitutionRequest> {
|
||||||
|
val follows = repo.findByCitizenId(it.citizen)
|
||||||
|
call.respond(follows)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import fr.dcproject.entity.Article
|
import fr.dcproject.entity.Article
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.Citizen
|
||||||
import fr.dcproject.entity.Constitution
|
import fr.dcproject.entity.Constitution
|
||||||
import fr.dcproject.entity.Follow
|
|
||||||
import fr.postgresjson.repository.RepositoryI.Direction
|
import fr.postgresjson.repository.RepositoryI.Direction
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.ktor.locations.Location
|
import io.ktor.locations.Location
|
||||||
@@ -31,4 +30,6 @@ object Paths {
|
|||||||
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
|
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
|
||||||
}
|
}
|
||||||
@Location("/citizens/{citizen}") class CitizenRequest(val citizen: Citizen)
|
@Location("/citizens/{citizen}") class CitizenRequest(val citizen: Citizen)
|
||||||
|
@Location("/citizens/{citizen}/follows/articles") class CitizenFollowArticleRequest(val citizen: Citizen)
|
||||||
|
@Location("/citizens/{citizen}/follows/constitutions") class CitizenFollowConstitutionRequest(val citizen: Citizen)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
create or replace function find_follows_article_by_citizen(
|
||||||
|
_citizen_id uuid,
|
||||||
|
"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 follow)
|
||||||
|
into resource, total
|
||||||
|
from (
|
||||||
|
select
|
||||||
|
f.*,
|
||||||
|
find_article_by_id(f.target_id) as target,
|
||||||
|
find_citizen_by_id(f.citizen_id) as citizen
|
||||||
|
from follow as f
|
||||||
|
where citizen_id = _citizen_id
|
||||||
|
order by created_at desc,
|
||||||
|
f.created_at desc
|
||||||
|
limit "limit" offset "offset"
|
||||||
|
) as t;
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- drop function if exists find_follows_article_by_citizen(uuid, int, int);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
create or replace function find_follows_by_citizen(
|
||||||
|
_citizen_id uuid,
|
||||||
|
"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 follow)
|
||||||
|
into resource, total
|
||||||
|
from (
|
||||||
|
select
|
||||||
|
f.*,
|
||||||
|
json_build_object('id', f.target_id) as target,
|
||||||
|
find_citizen_by_id(f.citizen_id) as citizen
|
||||||
|
from follow as f
|
||||||
|
where citizen_id = _citizen_id
|
||||||
|
order by created_at desc,
|
||||||
|
f.created_at desc
|
||||||
|
limit "limit" offset "offset"
|
||||||
|
) as t;
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- drop function if exists find_follows_by_citizen(uuid, int, int);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
create or replace function find_follows_constitution_by_citizen(
|
||||||
|
_citizen_id uuid,
|
||||||
|
"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 follow)
|
||||||
|
into resource, total
|
||||||
|
from (
|
||||||
|
select
|
||||||
|
f.*,
|
||||||
|
find_constitution_by_id(f.target_id) as target,
|
||||||
|
find_citizen_by_id(f.citizen_id) as citizen
|
||||||
|
from follow as f
|
||||||
|
where citizen_id = _citizen_id
|
||||||
|
order by created_at desc,
|
||||||
|
f.created_at desc
|
||||||
|
limit "limit" offset "offset"
|
||||||
|
) as t;
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- drop function if exists find_follows_constitution_by_citizen(uuid, int, int);
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package feature
|
package feature
|
||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.JsonParser
|
||||||
import cucumber.api.java8.En
|
import cucumber.api.java8.En
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.Citizen
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.User
|
||||||
@@ -21,6 +22,7 @@ import org.koin.test.inject
|
|||||||
import org.opentest4j.AssertionFailedError
|
import org.opentest4j.AssertionFailedError
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
import kotlin.test.assertTrue
|
||||||
import kotlin.test.asserter
|
import kotlin.test.asserter
|
||||||
import feature.Context.Companion.current as currentContext
|
import feature.Context.Companion.current as currentContext
|
||||||
|
|
||||||
@@ -103,14 +105,11 @@ class Request: En, KoinTest {
|
|||||||
And("the response should contain object:") { expected: DataTable ->
|
And("the response should contain object:") { expected: DataTable ->
|
||||||
val call: TestApplicationCall = currentContext.call ?: throw AssertionFailedError("No call")
|
val call: TestApplicationCall = currentContext.call ?: throw AssertionFailedError("No call")
|
||||||
val p = call.response
|
val p = call.response
|
||||||
val response = Gson().fromJson<Map<String, String>>(p.content, Map::class.java)
|
val response = JsonParser().parse(p.content).getAsJsonObject()
|
||||||
|
|
||||||
expected.asMap<String, String>(String::class.java, String::class.java).forEach { (key, value) ->
|
expected.asMap<String, String>(String::class.java, String::class.java).forEach { (key, valueExpected) ->
|
||||||
if (response.containsKey(key)) {
|
assertTrue(response.has(key))
|
||||||
assertEquals(value, response[key])
|
assertEquals(valueExpected, response.get(key).asString)
|
||||||
return@And
|
|
||||||
}
|
|
||||||
asserter.fail("The response not contain $key field")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,17 @@ Feature: follow Article and Constitution
|
|||||||
When I send a "POST" request to "/articles/9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b/follow"
|
When I send a "POST" request to "/articles/9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b/follow"
|
||||||
Then the response status code should be 201
|
Then the response status code should be 201
|
||||||
|
|
||||||
|
Scenario: The route for get follows of articles must response a 200 and return objects
|
||||||
|
Given I have citizen:
|
||||||
|
| id | 64b7b379-2298-43ec-b428-ba134930cabd |
|
||||||
|
| firstName | Jaque |
|
||||||
|
| lastName | Dupuis |
|
||||||
|
When I send a "GET" request to "/citizens/64b7b379-2298-43ec-b428-ba134930cabd/follows/articles"
|
||||||
|
Then the response status code should be 200
|
||||||
|
And the response should contain object:
|
||||||
|
| current_page | 1 |
|
||||||
|
| limit | 50 |
|
||||||
|
|
||||||
Scenario: The route for unfollow article must response a 204
|
Scenario: The route for unfollow article must response a 204
|
||||||
Given I have citizen:
|
Given I have citizen:
|
||||||
| id | 64b7b379-2298-43ec-b428-ba134930cabd |
|
| id | 64b7b379-2298-43ec-b428-ba134930cabd |
|
||||||
@@ -26,6 +37,17 @@ Feature: follow Article and Constitution
|
|||||||
When I send a "POST" request to "/constitutions/72aa1ee1-4963-eb44-c9e0-5ce6e0f18f00/follow"
|
When I send a "POST" request to "/constitutions/72aa1ee1-4963-eb44-c9e0-5ce6e0f18f00/follow"
|
||||||
Then the response status code should be 201
|
Then the response status code should be 201
|
||||||
|
|
||||||
|
Scenario: The route for get follows of constitutions must response a 200 and return objects
|
||||||
|
Given I have citizen:
|
||||||
|
| id | 64b7b379-2298-43ec-b428-ba134930cabd |
|
||||||
|
| firstName | Jaque |
|
||||||
|
| lastName | Dupuis |
|
||||||
|
When I send a "GET" request to "/citizens/64b7b379-2298-43ec-b428-ba134930cabd/follows/constitutions"
|
||||||
|
Then the response status code should be 200
|
||||||
|
And the response should contain object:
|
||||||
|
| current_page | 1 |
|
||||||
|
| limit | 50 |
|
||||||
|
|
||||||
Scenario: The route for unfollow constitution must response a 204
|
Scenario: The route for unfollow constitution must response a 204
|
||||||
Given I have citizen:
|
Given I have citizen:
|
||||||
| id | 64b7b379-2298-43ec-b428-ba134930cabd |
|
| id | 64b7b379-2298-43ec-b428-ba134930cabd |
|
||||||
|
|||||||
Reference in New Issue
Block a user