diff --git a/src/main/kotlin/fr/dcproject/Module.kt b/src/main/kotlin/fr/dcproject/Module.kt index 06cc485..02dae72 100644 --- a/src/main/kotlin/fr/dcproject/Module.kt +++ b/src/main/kotlin/fr/dcproject/Module.kt @@ -1,7 +1,5 @@ package fr.dcproject -import fr.dcproject.repository.FollowArticleRepository -import fr.dcproject.repository.FollowConstitutionRepository import fr.postgresjson.connexion.Connection import fr.postgresjson.connexion.Requester 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.Citizen as CitizenRepository import fr.dcproject.repository.Constitution as ConstitutionRepository +import fr.dcproject.repository.FollowArticle as FollowArticleRepository +import fr.dcproject.repository.FollowConstitution as FollowConstitutionRepository val config = Config() diff --git a/src/main/kotlin/fr/dcproject/entity/Follow.kt b/src/main/kotlin/fr/dcproject/entity/Follow.kt index 3829aa6..0f674a4 100644 --- a/src/main/kotlin/fr/dcproject/entity/Follow.kt +++ b/src/main/kotlin/fr/dcproject/entity/Follow.kt @@ -1,12 +1,9 @@ package fr.dcproject.entity -import fr.postgresjson.entity.EntityI +import fr.postgresjson.entity.UuidEntity import java.util.* -class Follow > ( +class Follow ( id: UUID = UUID.randomUUID(), citizen: Citizen, override var target: T ): Extra(id, citizen) - -typealias FollowArticleEntity = Follow
-typealias FollowConstitutionEntity = Follow \ No newline at end of file diff --git a/src/main/kotlin/fr/dcproject/repository/Follow.kt b/src/main/kotlin/fr/dcproject/repository/Follow.kt index e02ac1f..3224a8b 100644 --- a/src/main/kotlin/fr/dcproject/repository/Follow.kt +++ b/src/main/kotlin/fr/dcproject/repository/Follow.kt @@ -1,16 +1,37 @@ package fr.dcproject.repository +import fr.postgresjson.connexion.Paginated import fr.postgresjson.connexion.Requester -import fr.postgresjson.entity.EntityI +import fr.postgresjson.entity.UuidEntity import fr.postgresjson.repository.RepositoryI import java.util.* import kotlin.reflect.KClass 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.Follow as FollowEntity -open class Follow >(override var requester: Requester): RepositoryI> { +open class Follow (override var requester: Requester): RepositoryI> { override val entityName = FollowEntity::class as KClass> + open fun findByCitizenId( + citizen: CitizenEntity, + page: Int = 1, + limit: Int = 50 + ): Paginated> = + findByCitizenId(citizen.id ?: error("The citizen must have an id"), page, limit) + + open fun findByCitizenId( + citizenId: UUID, + page: Int = 1, + limit: Int = 50 + ): Paginated> { + return requester.run { + getFunction("find_follows_by_citizen") + .select(page, limit, + "citizen_id" to citizenId + ) + } + } fun follow(follow: FollowEntity) { val reference = follow.target::class.simpleName!!.toLowerCase() @@ -34,5 +55,33 @@ open class Follow >(override var requester: Requester): Reposit ) } } -class FollowArticleRepository(override var requester: Requester): Follow(requester) -class FollowConstitutionRepository(override var requester: Requester): Follow(requester) + +class FollowArticle (requester: Requester): Follow(requester) { + override fun findByCitizenId( + citizenId: UUID, + page: Int, + limit: Int + ): Paginated> { + return requester.run { + getFunction("find_follows_article_by_citizen") + .select(page, limit, + "citizen_id" to citizenId + ) + } + } +} + +class FollowConstitution (requester: Requester): Follow(requester) { + override fun findByCitizenId( + citizenId: UUID, + page: Int, + limit: Int + ): Paginated> { + return requester.run { + getFunction("find_follows_constitution_by_citizen") + .select(page, limit, + "citizen_id" to citizenId + ) + } + } +} diff --git a/src/main/kotlin/fr/dcproject/routes/Follow.kt b/src/main/kotlin/fr/dcproject/routes/FollowArticle.kt similarity index 82% rename from src/main/kotlin/fr/dcproject/routes/Follow.kt rename to src/main/kotlin/fr/dcproject/routes/FollowArticle.kt index 7ae2b92..8fcc00a 100644 --- a/src/main/kotlin/fr/dcproject/routes/Follow.kt +++ b/src/main/kotlin/fr/dcproject/routes/FollowArticle.kt @@ -3,17 +3,18 @@ package fr.dcproject.routes import Paths import fr.dcproject.entity.Citizen import fr.dcproject.entity.User -import fr.dcproject.repository.FollowArticleRepository import io.ktor.application.call import io.ktor.http.HttpStatusCode import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.delete +import io.ktor.locations.get import io.ktor.locations.post import io.ktor.response.respond import io.ktor.routing.Route import org.joda.time.DateTime import java.util.* import fr.dcproject.entity.Follow as FollowEntity +import fr.dcproject.repository.FollowArticle as FollowArticleRepository // TODO get current citizen val currentCitizen = Citizen( @@ -34,4 +35,9 @@ fun Route.followArticle(repo: FollowArticleRepository) { repo.unfollow(FollowEntity(target = it.article, citizen = currentCitizen)) call.respond(HttpStatusCode.NoContent) } + + get { + val follows = repo.findByCitizenId(it.citizen) + call.respond(follows) + } } \ No newline at end of file diff --git a/src/main/kotlin/fr/dcproject/routes/FollowConstitution.kt b/src/main/kotlin/fr/dcproject/routes/FollowConstitution.kt index 89a844a..c3b95a2 100644 --- a/src/main/kotlin/fr/dcproject/routes/FollowConstitution.kt +++ b/src/main/kotlin/fr/dcproject/routes/FollowConstitution.kt @@ -3,17 +3,18 @@ package fr.dcproject.routes import Paths import fr.dcproject.entity.Citizen import fr.dcproject.entity.User -import fr.dcproject.repository.FollowConstitutionRepository import io.ktor.application.call import io.ktor.http.HttpStatusCode import io.ktor.locations.KtorExperimentalLocationsAPI import io.ktor.locations.delete +import io.ktor.locations.get import io.ktor.locations.post import io.ktor.response.respond import io.ktor.routing.Route import org.joda.time.DateTime import java.util.* import fr.dcproject.entity.Follow as FollowEntity +import fr.dcproject.repository.FollowConstitution as FollowConstitutionRepository // TODO get current citizen val currentCitizen2 = Citizen( @@ -34,4 +35,9 @@ fun Route.followConstitution(repo: FollowConstitutionRepository) { repo.unfollow(FollowEntity(target = it.constitution, citizen = currentCitizen2)) call.respond(HttpStatusCode.NoContent) } + + get { + val follows = repo.findByCitizenId(it.citizen) + call.respond(follows) + } } diff --git a/src/main/kotlin/fr/dcproject/routes/Paths.kt b/src/main/kotlin/fr/dcproject/routes/Paths.kt index da898ec..f4bf6b4 100644 --- a/src/main/kotlin/fr/dcproject/routes/Paths.kt +++ b/src/main/kotlin/fr/dcproject/routes/Paths.kt @@ -1,7 +1,6 @@ import fr.dcproject.entity.Article import fr.dcproject.entity.Citizen import fr.dcproject.entity.Constitution -import fr.dcproject.entity.Follow import fr.postgresjson.repository.RepositoryI.Direction import io.ktor.locations.KtorExperimentalLocationsAPI 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 } @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) } \ No newline at end of file diff --git a/src/main/resources/sql/functions/follow/find_follows_article_by_citizen.sql b/src/main/resources/sql/functions/follow/find_follows_article_by_citizen.sql new file mode 100644 index 0000000..36bab21 --- /dev/null +++ b/src/main/resources/sql/functions/follow/find_follows_article_by_citizen.sql @@ -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); diff --git a/src/main/resources/sql/functions/follow/find_follows_by_citizen.sql b/src/main/resources/sql/functions/follow/find_follows_by_citizen.sql new file mode 100644 index 0000000..f9d1896 --- /dev/null +++ b/src/main/resources/sql/functions/follow/find_follows_by_citizen.sql @@ -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); diff --git a/src/main/resources/sql/functions/follow/find_follows_constitution_by_citizen.sql b/src/main/resources/sql/functions/follow/find_follows_constitution_by_citizen.sql new file mode 100644 index 0000000..75c507f --- /dev/null +++ b/src/main/resources/sql/functions/follow/find_follows_constitution_by_citizen.sql @@ -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); diff --git a/src/test/kotlin/feature/Request.kt b/src/test/kotlin/feature/Request.kt index 9a13725..03c18e3 100644 --- a/src/test/kotlin/feature/Request.kt +++ b/src/test/kotlin/feature/Request.kt @@ -1,6 +1,7 @@ package feature import com.google.gson.Gson +import com.google.gson.JsonParser import cucumber.api.java8.En import fr.dcproject.entity.Citizen import fr.dcproject.entity.User @@ -21,6 +22,7 @@ import org.koin.test.inject import org.opentest4j.AssertionFailedError import java.util.* import kotlin.random.Random +import kotlin.test.assertTrue import kotlin.test.asserter import feature.Context.Companion.current as currentContext @@ -103,14 +105,11 @@ class Request: En, KoinTest { And("the response should contain object:") { expected: DataTable -> val call: TestApplicationCall = currentContext.call ?: throw AssertionFailedError("No call") val p = call.response - val response = Gson().fromJson>(p.content, Map::class.java) + val response = JsonParser().parse(p.content).getAsJsonObject() - expected.asMap(String::class.java, String::class.java).forEach { (key, value) -> - if (response.containsKey(key)) { - assertEquals(value, response[key]) - return@And - } - asserter.fail("The response not contain $key field") + expected.asMap(String::class.java, String::class.java).forEach { (key, valueExpected) -> + assertTrue(response.has(key)) + assertEquals(valueExpected, response.get(key).asString) } } } diff --git a/src/test/resources/feature/follow.feature b/src/test/resources/feature/follow.feature index 5f2dcd9..f985381 100644 --- a/src/test/resources/feature/follow.feature +++ b/src/test/resources/feature/follow.feature @@ -9,6 +9,17 @@ Feature: follow Article and Constitution When I send a "POST" request to "/articles/9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b/follow" 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 Given I have citizen: | 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" 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 Given I have citizen: | id | 64b7b379-2298-43ec-b428-ba134930cabd |