#60 Can follow citizen #96
@@ -25,6 +25,7 @@ import fr.dcproject.component.constitution.routes.installConstitutionRoutes
|
|||||||
import fr.dcproject.component.doc.routes.installDocRoutes
|
import fr.dcproject.component.doc.routes.installDocRoutes
|
||||||
import fr.dcproject.component.follow.followKoinModule
|
import fr.dcproject.component.follow.followKoinModule
|
||||||
import fr.dcproject.component.follow.routes.article.installFollowArticleRoutes
|
import fr.dcproject.component.follow.routes.article.installFollowArticleRoutes
|
||||||
|
import fr.dcproject.component.follow.routes.citizen.installFollowCitizenRoutes
|
||||||
import fr.dcproject.component.follow.routes.constitution.installFollowConstitutionRoutes
|
import fr.dcproject.component.follow.routes.constitution.installFollowConstitutionRoutes
|
||||||
import fr.dcproject.component.notification.NotificationConsumer
|
import fr.dcproject.component.notification.NotificationConsumer
|
||||||
import fr.dcproject.component.notification.routes.installNotificationsRoutes
|
import fr.dcproject.component.notification.routes.installNotificationsRoutes
|
||||||
@@ -154,6 +155,7 @@ fun Application.module(env: Env = PROD) {
|
|||||||
installCommentRoutes()
|
installCommentRoutes()
|
||||||
installFollowArticleRoutes()
|
installFollowArticleRoutes()
|
||||||
installFollowConstitutionRoutes()
|
installFollowConstitutionRoutes()
|
||||||
|
installFollowCitizenRoutes()
|
||||||
installWorkgroupRoutes()
|
installWorkgroupRoutes()
|
||||||
installOpinionRoutes()
|
installOpinionRoutes()
|
||||||
installVoteRoutes()
|
installVoteRoutes()
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ val KoinModule = module {
|
|||||||
|
|
||||||
single {
|
single {
|
||||||
val config: Configuration = get()
|
val config: Configuration = get()
|
||||||
NotificationConsumer(get(), get(), get(), get(), get(), config.exchangeNotificationName)
|
NotificationConsumer(get(), get(), get(), get(), get(), get(), config.exchangeNotificationName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RabbitMQ
|
// RabbitMQ
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package fr.dcproject.common.entity
|
|||||||
|
|
||||||
import fr.dcproject.component.article.database.ArticleRef
|
import fr.dcproject.component.article.database.ArticleRef
|
||||||
import fr.dcproject.component.citizen.database.CitizenI
|
import fr.dcproject.component.citizen.database.CitizenI
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenRef
|
||||||
import fr.dcproject.component.comment.generic.database.CommentRef
|
import fr.dcproject.component.comment.generic.database.CommentRef
|
||||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||||
import fr.dcproject.component.opinion.database.OpinionRef
|
import fr.dcproject.component.opinion.database.OpinionRef
|
||||||
@@ -34,7 +35,8 @@ interface TargetI : EntityI {
|
|||||||
Article("article"),
|
Article("article"),
|
||||||
Constitution("constitution"),
|
Constitution("constitution"),
|
||||||
Comment("comment"),
|
Comment("comment"),
|
||||||
Opinion("opinion")
|
Opinion("opinion"),
|
||||||
|
Citizen("citizen"),
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -44,6 +46,7 @@ interface TargetI : EntityI {
|
|||||||
t.isSubclassOf(ConstitutionRef::class) -> TargetName.Constitution.targetReference
|
t.isSubclassOf(ConstitutionRef::class) -> TargetName.Constitution.targetReference
|
||||||
t.isSubclassOf(CommentRef::class) -> TargetName.Comment.targetReference
|
t.isSubclassOf(CommentRef::class) -> TargetName.Comment.targetReference
|
||||||
t.isSubclassOf(OpinionRef::class) -> TargetName.Opinion.targetReference
|
t.isSubclassOf(OpinionRef::class) -> TargetName.Opinion.targetReference
|
||||||
|
t.isSubclassOf(CitizenRef::class) -> TargetName.Citizen.targetReference
|
||||||
else -> throw error("target not implemented: ${t.qualifiedName} \nImplement it or return 'reference' from SQL")
|
else -> throw error("target not implemented: ${t.qualifiedName} \nImplement it or return 'reference' from SQL")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package fr.dcproject.component.citizen.database
|
|||||||
|
|
||||||
import fr.dcproject.common.entity.CreatedAt
|
import fr.dcproject.common.entity.CreatedAt
|
||||||
import fr.dcproject.common.entity.DeletedAt
|
import fr.dcproject.common.entity.DeletedAt
|
||||||
import fr.dcproject.common.entity.Entity
|
|
||||||
import fr.dcproject.common.entity.EntityI
|
import fr.dcproject.common.entity.EntityI
|
||||||
|
import fr.dcproject.common.entity.TargetI
|
||||||
|
import fr.dcproject.common.entity.TargetRef
|
||||||
import fr.dcproject.component.auth.database.User
|
import fr.dcproject.component.auth.database.User
|
||||||
import fr.dcproject.component.auth.database.UserCreator
|
import fr.dcproject.component.auth.database.UserCreator
|
||||||
import fr.dcproject.component.auth.database.UserForCreate
|
import fr.dcproject.component.auth.database.UserForCreate
|
||||||
@@ -95,10 +96,10 @@ open class CitizenRefWithUser(
|
|||||||
|
|
||||||
open class CitizenRef(
|
open class CitizenRef(
|
||||||
id: UUID = UUID.randomUUID()
|
id: UUID = UUID.randomUUID()
|
||||||
) : Entity(id),
|
) : TargetRef(id),
|
||||||
CitizenI
|
CitizenI
|
||||||
|
|
||||||
interface CitizenI : EntityI {
|
interface CitizenI : EntityI, TargetI {
|
||||||
data class Name(
|
data class Name(
|
||||||
override val firstName: String,
|
override val firstName: String,
|
||||||
override val lastName: String,
|
override val lastName: String,
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package fr.dcproject.component.follow
|
package fr.dcproject.component.follow
|
||||||
|
|
||||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||||
|
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val followKoinModule = module {
|
val followKoinModule = module {
|
||||||
single { FollowArticleRepository(get()) }
|
single { FollowArticleRepository(get()) }
|
||||||
single { FollowConstitutionRepository(get()) }
|
single { FollowConstitutionRepository(get()) }
|
||||||
|
single { FollowCitizenRepository(get()) }
|
||||||
single { FollowAccessControl() }
|
single { FollowAccessControl() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import fr.dcproject.common.entity.Entity
|
|||||||
import fr.dcproject.common.entity.TargetRef
|
import fr.dcproject.common.entity.TargetRef
|
||||||
import fr.dcproject.component.article.database.ArticleForView
|
import fr.dcproject.component.article.database.ArticleForView
|
||||||
import fr.dcproject.component.article.database.ArticleRef
|
import fr.dcproject.component.article.database.ArticleRef
|
||||||
|
import fr.dcproject.component.citizen.database.Citizen
|
||||||
import fr.dcproject.component.citizen.database.CitizenI
|
import fr.dcproject.component.citizen.database.CitizenI
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenRef
|
||||||
import fr.dcproject.component.constitution.database.ConstitutionForView
|
import fr.dcproject.component.constitution.database.ConstitutionForView
|
||||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||||
import fr.postgresjson.connexion.Paginated
|
import fr.postgresjson.connexion.Paginated
|
||||||
@@ -144,3 +146,28 @@ class FollowConstitutionRepository(requester: Requester) : FollowRepository<Cons
|
|||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FollowCitizenRepository(requester: Requester) : FollowRepository<CitizenRef, Citizen>(requester) {
|
||||||
|
override fun findByCitizen(
|
||||||
|
citizenId: UUID,
|
||||||
|
page: Int,
|
||||||
|
limit: Int
|
||||||
|
): Paginated<FollowForView<Citizen>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_follows_citizen_by_citizen")
|
||||||
|
.select(
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
"created_by_id" to citizenId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findFollowsByTarget(
|
||||||
|
target: Entity,
|
||||||
|
page: Int,
|
||||||
|
limit: Int
|
||||||
|
): Paginated<FollowForView<CitizenRef>> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package fr.dcproject.component.follow.routes.citizen
|
||||||
|
|
||||||
|
import fr.dcproject.common.security.assert
|
||||||
|
import fr.dcproject.component.auth.citizen
|
||||||
|
import fr.dcproject.component.auth.citizenOrNull
|
||||||
|
import fr.dcproject.component.auth.mustBeAuth
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenRef
|
||||||
|
import fr.dcproject.component.follow.FollowAccessControl
|
||||||
|
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||||
|
import fr.dcproject.component.follow.database.FollowForUpdate
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.locations.Location
|
||||||
|
import io.ktor.locations.post
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
|
object FollowCitizen {
|
||||||
|
@Location("/citizens/{citizen}/follows")
|
||||||
|
class CitizenFollowRequest(citizen: UUID) {
|
||||||
|
val citizen = CitizenRef(citizen)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Route.followCitizen(repo: FollowCitizenRepository, ac: FollowAccessControl) {
|
||||||
|
post<CitizenFollowRequest> {
|
||||||
|
mustBeAuth()
|
||||||
|
val follow = FollowForUpdate(target = it.citizen, createdBy = this.citizen)
|
||||||
|
ac.assert { canCreate(follow, citizenOrNull) }
|
||||||
|
repo.follow(follow)
|
||||||
|
call.respond(HttpStatusCode.Created)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package fr.dcproject.component.follow.routes.citizen
|
||||||
|
|
||||||
|
import fr.dcproject.common.response.toOutput
|
||||||
|
import fr.dcproject.common.security.assert
|
||||||
|
import fr.dcproject.component.auth.citizen
|
||||||
|
import fr.dcproject.component.auth.citizenOrNull
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenRef
|
||||||
|
import fr.dcproject.component.follow.FollowAccessControl
|
||||||
|
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.locations.Location
|
||||||
|
import io.ktor.locations.get
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
|
object GetFollowCitizen {
|
||||||
|
@Location("/citizens/{citizen}/follows")
|
||||||
|
class CitizenFollowRequest(citizen: UUID) {
|
||||||
|
val citizen = CitizenRef(citizen)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Route.getFollowCitizen(repo: FollowCitizenRepository, ac: FollowAccessControl) {
|
||||||
|
get<CitizenFollowRequest> {
|
||||||
|
repo.findFollow(citizen, it.citizen)?.let { follow ->
|
||||||
|
ac.assert { canView(follow, citizenOrNull) }
|
||||||
|
call.respond(
|
||||||
|
HttpStatusCode.OK,
|
||||||
|
follow.let { f ->
|
||||||
|
object {
|
||||||
|
val id: UUID = f.id
|
||||||
|
val createdBy: Any = f.createdBy.toOutput()
|
||||||
|
val target: Any = f.target.let { t ->
|
||||||
|
object {
|
||||||
|
val id: UUID = t.id
|
||||||
|
val reference: String = f.target.reference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val createdAt: DateTime = f.createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} ?: call.respond(HttpStatusCode.NoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package fr.dcproject.component.follow.routes.citizen
|
||||||
|
|
||||||
|
import fr.dcproject.common.response.toOutput
|
||||||
|
import fr.dcproject.common.security.assert
|
||||||
|
import fr.dcproject.component.auth.citizenOrNull
|
||||||
|
import fr.dcproject.component.auth.mustBeAuth
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenRef
|
||||||
|
import fr.dcproject.component.follow.FollowAccessControl
|
||||||
|
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.locations.Location
|
||||||
|
import io.ktor.locations.get
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
|
object GetMyFollowsCitizen {
|
||||||
|
@Location("/citizens/{citizen}/follows/citizens")
|
||||||
|
class CitizenFollowCitizenRequest(citizen: UUID) {
|
||||||
|
val citizen = CitizenRef(citizen)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Route.getMyFollowsCitizen(repo: FollowCitizenRepository, ac: FollowAccessControl) {
|
||||||
|
get<CitizenFollowCitizenRequest> {
|
||||||
|
mustBeAuth()
|
||||||
|
val follows = repo.findByCitizen(it.citizen)
|
||||||
|
ac.assert { canView(follows.result, citizenOrNull) }
|
||||||
|
call.respond(
|
||||||
|
HttpStatusCode.OK,
|
||||||
|
follows.toOutput { f ->
|
||||||
|
object {
|
||||||
|
val id: UUID = f.id
|
||||||
|
val createdBy: Any = f.createdBy.toOutput()
|
||||||
|
val target: Any = f.target.let { t ->
|
||||||
|
object {
|
||||||
|
val id: UUID = t.id
|
||||||
|
val reference: String = f.target.reference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val createdAt: DateTime = f.createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package fr.dcproject.component.follow.routes.citizen
|
||||||
|
|
||||||
|
import fr.dcproject.common.security.assert
|
||||||
|
import fr.dcproject.component.auth.citizen
|
||||||
|
import fr.dcproject.component.auth.citizenOrNull
|
||||||
|
import fr.dcproject.component.auth.mustBeAuth
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenRef
|
||||||
|
import fr.dcproject.component.follow.FollowAccessControl
|
||||||
|
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||||
|
import fr.dcproject.component.follow.database.FollowForUpdate
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.locations.Location
|
||||||
|
import io.ktor.locations.delete
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
|
object UnfollowCitizen {
|
||||||
|
@Location("/citizens/{citizen}/follows")
|
||||||
|
class CitizenFollowRequest(citizen: UUID) {
|
||||||
|
val citizen = CitizenRef(citizen)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Route.unfollowCitizen(repo: FollowCitizenRepository, ac: FollowAccessControl) {
|
||||||
|
delete<CitizenFollowRequest> {
|
||||||
|
mustBeAuth()
|
||||||
|
val follow = FollowForUpdate(target = it.citizen, createdBy = this.citizen)
|
||||||
|
ac.assert { canDelete(follow, citizenOrNull) }
|
||||||
|
repo.unfollow(follow)
|
||||||
|
call.respond(HttpStatusCode.NoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package fr.dcproject.component.follow.routes.citizen
|
||||||
|
|
||||||
|
import fr.dcproject.component.follow.routes.citizen.FollowCitizen.followCitizen
|
||||||
|
import fr.dcproject.component.follow.routes.citizen.GetFollowCitizen.getFollowCitizen
|
||||||
|
import fr.dcproject.component.follow.routes.citizen.GetMyFollowsCitizen.getMyFollowsCitizen
|
||||||
|
import fr.dcproject.component.follow.routes.citizen.UnfollowCitizen.unfollowCitizen
|
||||||
|
import io.ktor.auth.authenticate
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.routing.Routing
|
||||||
|
import org.koin.ktor.ext.get
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
|
fun Routing.installFollowCitizenRoutes() {
|
||||||
|
authenticate(optional = true) {
|
||||||
|
followCitizen(get(), get())
|
||||||
|
unfollowCitizen(get(), get())
|
||||||
|
getFollowCitizen(get(), get())
|
||||||
|
getMyFollowsCitizen(get(), get())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import com.rabbitmq.client.DefaultConsumer
|
|||||||
import com.rabbitmq.client.Envelope
|
import com.rabbitmq.client.Envelope
|
||||||
import fr.dcproject.common.entity.TargetRef
|
import fr.dcproject.common.entity.TargetRef
|
||||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||||
|
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||||
import fr.dcproject.component.follow.database.FollowForView
|
import fr.dcproject.component.follow.database.FollowForView
|
||||||
import io.ktor.utils.io.errors.IOException
|
import io.ktor.utils.io.errors.IOException
|
||||||
@@ -23,6 +24,7 @@ class NotificationConsumer(
|
|||||||
private val redisClient: RedisClient,
|
private val redisClient: RedisClient,
|
||||||
private val followConstitutionRepo: FollowConstitutionRepository,
|
private val followConstitutionRepo: FollowConstitutionRepository,
|
||||||
private val followArticleRepo: FollowArticleRepository,
|
private val followArticleRepo: FollowArticleRepository,
|
||||||
|
private val followCitizenRepo: FollowCitizenRepository,
|
||||||
private val notificationEmailSender: NotificationEmailSender,
|
private val notificationEmailSender: NotificationEmailSender,
|
||||||
private val exchangeName: String,
|
private val exchangeName: String,
|
||||||
) {
|
) {
|
||||||
@@ -98,6 +100,7 @@ class NotificationConsumer(
|
|||||||
val follows = when (notification.type) {
|
val follows = when (notification.type) {
|
||||||
"article" -> followArticleRepo.findFollowsByTarget(notification.target)
|
"article" -> followArticleRepo.findFollowsByTarget(notification.target)
|
||||||
"constitution" -> followConstitutionRepo.findFollowsByTarget(notification.target)
|
"constitution" -> followConstitutionRepo.findFollowsByTarget(notification.target)
|
||||||
|
"citizen" -> followCitizenRepo.findFollowsByTarget(notification.target)
|
||||||
else -> error("event '${notification.type}' not implemented")
|
else -> error("event '${notification.type}' not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -955,13 +955,105 @@ paths:
|
|||||||
description: Return only http status 204 on success
|
description: Return only http status 204 on success
|
||||||
401:
|
401:
|
||||||
$ref: '#/components/responses/401'
|
$ref: '#/components/responses/401'
|
||||||
|
|
||||||
|
/citizens/{citizen}/follows:
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/citizen'
|
||||||
|
get:
|
||||||
|
security:
|
||||||
|
- JWTAuth: [ ]
|
||||||
|
summary: Return Follows of citizen
|
||||||
|
tags:
|
||||||
|
- follow
|
||||||
|
- citizen
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Return follows
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/FollowResponse'
|
||||||
|
404:
|
||||||
|
description: Citizen not exist
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/404'
|
||||||
|
post:
|
||||||
|
security:
|
||||||
|
- JWTAuth: []
|
||||||
|
summary: Follow citizen
|
||||||
|
description: Follow a citizen to receive notifications of his activity
|
||||||
|
tags:
|
||||||
|
- follow
|
||||||
|
- citizen
|
||||||
|
responses:
|
||||||
|
201:
|
||||||
|
description: Return only http status 201 on success
|
||||||
|
401:
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
404:
|
||||||
|
description: Citizen not exist
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/404'
|
||||||
|
delete:
|
||||||
|
security:
|
||||||
|
- JWTAuth: [ ]
|
||||||
|
summary: Unfollow one citizen
|
||||||
|
tags:
|
||||||
|
- follow
|
||||||
|
- citizen
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: Return only http status 204 on success
|
||||||
|
401:
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
404:
|
||||||
|
description: Citizen not exist
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/404'
|
||||||
|
/citizens/{citizen}/follows/citizens:
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/citizen'
|
||||||
|
get:
|
||||||
|
security:
|
||||||
|
- JWTAuth: [ ]
|
||||||
|
summary: Return citizen Follow of citizen
|
||||||
|
tags:
|
||||||
|
- follow
|
||||||
|
- citizen
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Return citizen Follow of citizen
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Paginated'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
result:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/FollowResponse'
|
||||||
|
404:
|
||||||
|
description: Citizen not exist
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/404'
|
||||||
|
|
||||||
/citizens/{citizen}/follows/articles:
|
/citizens/{citizen}/follows/articles:
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/components/parameters/citizen'
|
- $ref: '#/components/parameters/citizen'
|
||||||
get:
|
get:
|
||||||
security:
|
security:
|
||||||
- JWTAuth: [ ]
|
- JWTAuth: [ ]
|
||||||
summary: Return Follow or nothing if you not follow
|
summary: Return article Follow of citizen
|
||||||
tags:
|
tags:
|
||||||
- follow
|
- follow
|
||||||
- article
|
- article
|
||||||
@@ -1036,7 +1128,7 @@ paths:
|
|||||||
- citizen
|
- citizen
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: Return your follows
|
description: Return constitution Follow of citizen
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
create or replace function find_follows_citizen_by_citizen(
|
||||||
|
_created_by_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_citizen_by_id_with_user(f.target_id) as target,
|
||||||
|
find_citizen_by_id_with_user(f.created_by_id) as created_by
|
||||||
|
from follow as f
|
||||||
|
where created_by_id = _created_by_id
|
||||||
|
order by created_at desc,
|
||||||
|
f.created_at desc
|
||||||
|
limit "limit" offset "offset"
|
||||||
|
) as t;
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
@@ -92,7 +92,8 @@ class NotificationConsumerTest {
|
|||||||
rabbitFactory = rabbitFactory,
|
rabbitFactory = rabbitFactory,
|
||||||
redisClient = redisClient,
|
redisClient = redisClient,
|
||||||
followArticleRepo = followArticleRepo,
|
followArticleRepo = followArticleRepo,
|
||||||
followConstitutionRepo = mockk(),
|
followConstitutionRepo = mockk(), // TODO test followConstitution
|
||||||
|
followCitizenRepo = mockk(), // TODO test followCitizen
|
||||||
notificationEmailSender = emailSender,
|
notificationEmailSender = emailSender,
|
||||||
exchangeName = "notification",
|
exchangeName = "notification",
|
||||||
).apply { start() }
|
).apply { start() }
|
||||||
|
|||||||
96
src/test/kotlin/integration/Follow citizen routes.kt
Normal file
96
src/test/kotlin/integration/Follow citizen routes.kt
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import integration.steps.`when`.`When I send a DELETE request`
|
||||||
|
import integration.steps.`when`.`When I send a GET request`
|
||||||
|
import integration.steps.`when`.`When I send a POST request`
|
||||||
|
import integration.steps.given.`And follow citizen`
|
||||||
|
import integration.steps.given.`Given I have citizen`
|
||||||
|
import integration.steps.given.`authenticated as`
|
||||||
|
import integration.steps.given.`with no content`
|
||||||
|
import integration.steps.then.`And the response should be null`
|
||||||
|
import integration.steps.then.`And the response should contain`
|
||||||
|
import integration.steps.then.`And the response should not be null`
|
||||||
|
import integration.steps.then.`Then the response should be`
|
||||||
|
import integration.steps.then.and
|
||||||
|
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||||
|
import io.ktor.http.HttpStatusCode.Companion.NoContent
|
||||||
|
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||||
|
import org.junit.jupiter.api.Tag
|
||||||
|
import org.junit.jupiter.api.Tags
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestInstance
|
||||||
|
|
||||||
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
|
@Tags(Tag("integration"), Tag("article"), Tag("follow"))
|
||||||
|
class `Follow citizen routes` : BaseTest() {
|
||||||
|
@Test
|
||||||
|
fun `I can follow citizen`() {
|
||||||
|
withIntegrationApplication {
|
||||||
|
/* Followed user */
|
||||||
|
`Given I have citizen`("John", "Glenn", id = "7e1580c5-05b7-4557-84f4-faac9f0a9441")
|
||||||
|
/* Current user */
|
||||||
|
`Given I have citizen`("Valentina", "Terechkova")
|
||||||
|
`When I send a POST request`("/citizens/7e1580c5-05b7-4557-84f4-faac9f0a9441/follows") {
|
||||||
|
`authenticated as`("Valentina", "Terechkova")
|
||||||
|
`with no content`()
|
||||||
|
} `Then the response should be` Created
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `I can get my follow citizen`() {
|
||||||
|
withIntegrationApplication {
|
||||||
|
/* Followed user */
|
||||||
|
`Given I have citizen`("Jean-Loup", "Chrétien", id = "c2432b94-a509-4116-a8b6-9774bc963372")
|
||||||
|
/* Current user */
|
||||||
|
`Given I have citizen`("John", "Young", id = "6d41ce65-9df7-47e0-af46-8da4a909490b") {
|
||||||
|
`And follow citizen`("c2432b94-a509-4116-a8b6-9774bc963372")
|
||||||
|
}
|
||||||
|
/* Get my all follows */
|
||||||
|
`When I send a GET request`("/citizens/6d41ce65-9df7-47e0-af46-8da4a909490b/follows/citizens") {
|
||||||
|
`authenticated as`("John", "Young")
|
||||||
|
} `Then the response should be` OK and {
|
||||||
|
`And the response should not be null`()
|
||||||
|
`And the response should contain`("$.currentPage", 1)
|
||||||
|
`And the response should contain`("$.limit", 50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `I can unfollow citizen`() {
|
||||||
|
withIntegrationApplication {
|
||||||
|
/* Followed user */
|
||||||
|
`Given I have citizen`("Bruce", "McCandless", id = "680c7af7-d2de-4249-bfcb-47007ef546fe")
|
||||||
|
/* Current user */
|
||||||
|
`Given I have citizen`("Jean-François", "Clervoy", id = "a12455ae-1047-43ff-826d-0d826dbe90f7") {
|
||||||
|
`And follow citizen`("680c7af7-d2de-4249-bfcb-47007ef546fe")
|
||||||
|
}
|
||||||
|
`When I send a DELETE request`("/citizens/680c7af7-d2de-4249-bfcb-47007ef546fe/follows") {
|
||||||
|
`authenticated as`("Jean-François", "Clervoy")
|
||||||
|
`with no content`()
|
||||||
|
} `Then the response should be` NoContent and {
|
||||||
|
`And the response should be null`()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `I can know if I follow an citizen`() {
|
||||||
|
withIntegrationApplication {
|
||||||
|
/* Followed user */
|
||||||
|
`Given I have citizen`("Eugene", "Cernan", id = "c755788f-7f48-4cde-8ff0-e75bcffdafc2")
|
||||||
|
/* Current user */
|
||||||
|
`Given I have citizen`("Buzz", "Aldrin", id = "39e2915a-e96f-43ea-babd-bd339d8bf197") {
|
||||||
|
`And follow citizen`("c755788f-7f48-4cde-8ff0-e75bcffdafc2")
|
||||||
|
}
|
||||||
|
`When I send a GET request`("/citizens/c755788f-7f48-4cde-8ff0-e75bcffdafc2/follows") {
|
||||||
|
`authenticated as`("Buzz", "Aldrin")
|
||||||
|
`with no content`()
|
||||||
|
} `Then the response should be` OK and {
|
||||||
|
`And the response should not be null`()
|
||||||
|
`And the response should contain`("$.target.id", "c755788f-7f48-4cde-8ff0-e75bcffdafc2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import fr.dcproject.component.citizen.database.CitizenRef
|
|||||||
import fr.dcproject.component.citizen.database.CitizenRepository
|
import fr.dcproject.component.citizen.database.CitizenRepository
|
||||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||||
|
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||||
import fr.dcproject.component.follow.database.FollowForUpdate
|
import fr.dcproject.component.follow.database.FollowForUpdate
|
||||||
import io.ktor.server.testing.TestApplicationEngine
|
import io.ktor.server.testing.TestApplicationEngine
|
||||||
@@ -24,6 +25,11 @@ fun Citizen.`And follow constitution`(
|
|||||||
) {
|
) {
|
||||||
createFollow(this, ConstitutionRef(constitution.toUUID()))
|
createFollow(this, ConstitutionRef(constitution.toUUID()))
|
||||||
}
|
}
|
||||||
|
fun Citizen.`And follow citizen`(
|
||||||
|
citizen: String,
|
||||||
|
) {
|
||||||
|
createFollow(this, CitizenRef(citizen.toUUID()))
|
||||||
|
}
|
||||||
|
|
||||||
fun TestApplicationEngine.`Given I have follow on article`(
|
fun TestApplicationEngine.`Given I have follow on article`(
|
||||||
firstName: String,
|
firstName: String,
|
||||||
@@ -56,3 +62,9 @@ fun createFollow(citizen: CitizenRef, constitution: ConstitutionRef) {
|
|||||||
val follow = FollowForUpdate(createdBy = citizen, target = constitution)
|
val follow = FollowForUpdate(createdBy = citizen, target = constitution)
|
||||||
followConstitutionRepository.follow(follow)
|
followConstitutionRepository.follow(follow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createFollow(createdBy: CitizenRef, target: CitizenRef) {
|
||||||
|
val followCitizenRepository: FollowCitizenRepository by lazy { GlobalContext.get().get() }
|
||||||
|
val follow = FollowForUpdate(createdBy = createdBy, target = target)
|
||||||
|
followCitizenRepository.follow(follow)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user