#60 Can follow citizen
This commit is contained in:
@@ -25,6 +25,7 @@ import fr.dcproject.component.constitution.routes.installConstitutionRoutes
|
||||
import fr.dcproject.component.doc.routes.installDocRoutes
|
||||
import fr.dcproject.component.follow.followKoinModule
|
||||
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.notification.NotificationConsumer
|
||||
import fr.dcproject.component.notification.routes.installNotificationsRoutes
|
||||
@@ -154,6 +155,7 @@ fun Application.module(env: Env = PROD) {
|
||||
installCommentRoutes()
|
||||
installFollowArticleRoutes()
|
||||
installFollowConstitutionRoutes()
|
||||
installFollowCitizenRoutes()
|
||||
installWorkgroupRoutes()
|
||||
installOpinionRoutes()
|
||||
installVoteRoutes()
|
||||
|
||||
@@ -69,7 +69,7 @@ val KoinModule = module {
|
||||
|
||||
single {
|
||||
val config: Configuration = get()
|
||||
NotificationConsumer(get(), get(), get(), get(), get(), config.exchangeNotificationName)
|
||||
NotificationConsumer(get(), get(), get(), get(), get(), get(), config.exchangeNotificationName)
|
||||
}
|
||||
|
||||
// RabbitMQ
|
||||
|
||||
@@ -2,6 +2,7 @@ package fr.dcproject.common.entity
|
||||
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
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.constitution.database.ConstitutionRef
|
||||
import fr.dcproject.component.opinion.database.OpinionRef
|
||||
@@ -34,7 +35,8 @@ interface TargetI : EntityI {
|
||||
Article("article"),
|
||||
Constitution("constitution"),
|
||||
Comment("comment"),
|
||||
Opinion("opinion")
|
||||
Opinion("opinion"),
|
||||
Citizen("citizen"),
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -44,6 +46,7 @@ interface TargetI : EntityI {
|
||||
t.isSubclassOf(ConstitutionRef::class) -> TargetName.Constitution.targetReference
|
||||
t.isSubclassOf(CommentRef::class) -> TargetName.Comment.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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ package fr.dcproject.component.citizen.database
|
||||
|
||||
import fr.dcproject.common.entity.CreatedAt
|
||||
import fr.dcproject.common.entity.DeletedAt
|
||||
import fr.dcproject.common.entity.Entity
|
||||
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.UserCreator
|
||||
import fr.dcproject.component.auth.database.UserForCreate
|
||||
@@ -95,10 +96,10 @@ open class CitizenRefWithUser(
|
||||
|
||||
open class CitizenRef(
|
||||
id: UUID = UUID.randomUUID()
|
||||
) : Entity(id),
|
||||
) : TargetRef(id),
|
||||
CitizenI
|
||||
|
||||
interface CitizenI : EntityI {
|
||||
interface CitizenI : EntityI, TargetI {
|
||||
data class Name(
|
||||
override val firstName: String,
|
||||
override val lastName: String,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package fr.dcproject.component.follow
|
||||
|
||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||
import org.koin.dsl.module
|
||||
|
||||
val followKoinModule = module {
|
||||
single { FollowArticleRepository(get()) }
|
||||
single { FollowConstitutionRepository(get()) }
|
||||
single { FollowCitizenRepository(get()) }
|
||||
single { FollowAccessControl() }
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import fr.dcproject.common.entity.Entity
|
||||
import fr.dcproject.common.entity.TargetRef
|
||||
import fr.dcproject.component.article.database.ArticleForView
|
||||
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.CitizenRef
|
||||
import fr.dcproject.component.constitution.database.ConstitutionForView
|
||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
@@ -144,3 +146,28 @@ class FollowConstitutionRepository(requester: Requester) : FollowRepository<Cons
|
||||
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 fr.dcproject.common.entity.TargetRef
|
||||
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.FollowForView
|
||||
import io.ktor.utils.io.errors.IOException
|
||||
@@ -23,6 +24,7 @@ class NotificationConsumer(
|
||||
private val redisClient: RedisClient,
|
||||
private val followConstitutionRepo: FollowConstitutionRepository,
|
||||
private val followArticleRepo: FollowArticleRepository,
|
||||
private val followCitizenRepo: FollowCitizenRepository,
|
||||
private val notificationEmailSender: NotificationEmailSender,
|
||||
private val exchangeName: String,
|
||||
) {
|
||||
@@ -98,6 +100,7 @@ class NotificationConsumer(
|
||||
val follows = when (notification.type) {
|
||||
"article" -> followArticleRepo.findFollowsByTarget(notification.target)
|
||||
"constitution" -> followConstitutionRepo.findFollowsByTarget(notification.target)
|
||||
"citizen" -> followCitizenRepo.findFollowsByTarget(notification.target)
|
||||
else -> error("event '${notification.type}' not implemented")
|
||||
}
|
||||
|
||||
|
||||
@@ -955,13 +955,105 @@ paths:
|
||||
description: Return only http status 204 on success
|
||||
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:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/citizen'
|
||||
get:
|
||||
security:
|
||||
- JWTAuth: [ ]
|
||||
summary: Return Follow or nothing if you not follow
|
||||
summary: Return article Follow of citizen
|
||||
tags:
|
||||
- follow
|
||||
- article
|
||||
@@ -1036,7 +1128,7 @@ paths:
|
||||
- citizen
|
||||
responses:
|
||||
200:
|
||||
description: Return your follows
|
||||
description: Return constitution Follow of citizen
|
||||
content:
|
||||
application/json:
|
||||
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;
|
||||
$$;
|
||||
Reference in New Issue
Block a user