Move Follow to a component
This commit is contained in:
46
src/main/kotlin/component/follow/Follow.kt
Normal file
46
src/main/kotlin/component/follow/Follow.kt
Normal file
@@ -0,0 +1,46 @@
|
||||
package fr.dcproject.component.follow
|
||||
|
||||
import fr.dcproject.component.citizen.CitizenBasic
|
||||
import fr.dcproject.component.citizen.CitizenBasicI
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.entity.ExtraI
|
||||
import fr.dcproject.entity.HasTarget
|
||||
import fr.dcproject.entity.TargetI
|
||||
import fr.postgresjson.entity.EntityCreatedAt
|
||||
import fr.postgresjson.entity.EntityCreatedAtImp
|
||||
import fr.postgresjson.entity.EntityCreatedBy
|
||||
import fr.postgresjson.entity.EntityCreatedByImp
|
||||
import fr.postgresjson.entity.UuidEntityI
|
||||
import java.util.UUID
|
||||
|
||||
@Deprecated("")
|
||||
class Follow<T : TargetI>(
|
||||
id: UUID = UUID.randomUUID(),
|
||||
override val createdBy: CitizenBasic,
|
||||
override var target: T
|
||||
) : ExtraI<T, CitizenBasicI>,
|
||||
FollowSimple<T, CitizenBasicI>(id, createdBy, target)
|
||||
|
||||
@Deprecated("")
|
||||
open class FollowSimple<T : TargetI, C : CitizenI>(
|
||||
id: UUID = UUID.randomUUID(),
|
||||
override val createdBy: C,
|
||||
override var target: T
|
||||
) : ExtraI<T, C>,
|
||||
FollowRef(id),
|
||||
EntityCreatedAt by EntityCreatedAtImp(),
|
||||
EntityCreatedBy<C> by EntityCreatedByImp(createdBy)
|
||||
|
||||
class FollowForUpdate<T : TargetI, C : CitizenI>(
|
||||
id: UUID = UUID.randomUUID(),
|
||||
override val target: T,
|
||||
override val createdBy: C
|
||||
) : FollowRef(id),
|
||||
HasTarget<T>,
|
||||
EntityCreatedBy<C> by EntityCreatedByImp<C>(createdBy)
|
||||
|
||||
open class FollowRef(
|
||||
override val id: UUID
|
||||
) : FollowI
|
||||
|
||||
interface FollowI : UuidEntityI
|
||||
148
src/main/kotlin/component/follow/FollowRepository.kt
Normal file
148
src/main/kotlin/component/follow/FollowRepository.kt
Normal file
@@ -0,0 +1,148 @@
|
||||
package fr.dcproject.component.follow
|
||||
|
||||
import fr.dcproject.component.article.ArticleForView
|
||||
import fr.dcproject.component.article.ArticleRef
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.component.citizen.CitizenRef
|
||||
import fr.dcproject.entity.ConstitutionRef
|
||||
import fr.dcproject.entity.TargetRef
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
import fr.postgresjson.connexion.Requester
|
||||
import fr.postgresjson.entity.UuidEntity
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import java.util.UUID
|
||||
import fr.dcproject.component.follow.Follow as FollowEntity
|
||||
import fr.dcproject.entity.Constitution as ConstitutionEntity
|
||||
|
||||
sealed class FollowRepository<IN : TargetRef, OUT : TargetRef>(override var requester: Requester) : RepositoryI {
|
||||
open fun findByCitizen(
|
||||
citizen: CitizenI,
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
): Paginated<FollowEntity<OUT>> =
|
||||
findByCitizen(citizen.id, page, limit)
|
||||
|
||||
open fun findByCitizen(
|
||||
citizenId: UUID,
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
): Paginated<FollowEntity<OUT>> {
|
||||
return requester
|
||||
.getFunction("find_follows_by_citizen")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"created_by_id" to citizenId
|
||||
)
|
||||
}
|
||||
|
||||
fun follow(follow: FollowForUpdate<IN, *>) {
|
||||
requester
|
||||
.getFunction("follow")
|
||||
.sendQuery(
|
||||
"reference" to follow.target.reference,
|
||||
"target_id" to follow.target.id,
|
||||
"created_by_id" to follow.createdBy.id
|
||||
)
|
||||
}
|
||||
|
||||
fun unfollow(follow: FollowForUpdate<IN, *>) {
|
||||
requester
|
||||
.getFunction("unfollow")
|
||||
.sendQuery(
|
||||
"reference" to follow.target.reference,
|
||||
"target_id" to follow.target.id,
|
||||
"created_by_id" to follow.createdBy.id
|
||||
)
|
||||
}
|
||||
|
||||
open fun findFollow(
|
||||
citizen: CitizenI,
|
||||
target: TargetRef
|
||||
): FollowEntity<OUT>? =
|
||||
requester
|
||||
.getFunction("find_follow")
|
||||
.selectOne(
|
||||
"citizen_id" to citizen.id,
|
||||
"target_id" to target.id,
|
||||
"target_reference" to target.reference
|
||||
)
|
||||
|
||||
fun findFollowsByTarget(
|
||||
target: UuidEntity,
|
||||
bulkSize: Int = 300
|
||||
): Flow<FollowSimple<IN, CitizenRef>> = flow {
|
||||
var nextPage = 1
|
||||
do {
|
||||
val paginate = findFollowsByTarget(target, nextPage, bulkSize)
|
||||
paginate.result.forEach {
|
||||
emit(it)
|
||||
}
|
||||
nextPage = paginate.currentPage + 1
|
||||
} while (!paginate.isLastPage())
|
||||
}
|
||||
|
||||
abstract fun findFollowsByTarget(
|
||||
target: UuidEntity,
|
||||
page: Int = 1,
|
||||
limit: Int = 300
|
||||
): Paginated<FollowSimple<IN, CitizenRef>>
|
||||
}
|
||||
|
||||
class FollowArticleRepository(requester: Requester) : FollowRepository<ArticleRef, ArticleForView>(requester) {
|
||||
override fun findByCitizen(
|
||||
citizenId: UUID,
|
||||
page: Int,
|
||||
limit: Int
|
||||
): Paginated<FollowEntity<ArticleForView>> {
|
||||
return requester.run {
|
||||
getFunction("find_follows_article_by_citizen")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"created_by_id" to citizenId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun findFollowsByTarget(
|
||||
target: UuidEntity,
|
||||
page: Int,
|
||||
limit: Int
|
||||
): Paginated<FollowSimple<ArticleRef, CitizenRef>> {
|
||||
return requester
|
||||
.getFunction("find_follows_article_by_target")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"target_id" to target.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class FollowConstitutionRepository(requester: Requester) : FollowRepository<ConstitutionRef, ConstitutionEntity>(requester) {
|
||||
override fun findByCitizen(
|
||||
citizenId: UUID,
|
||||
page: Int,
|
||||
limit: Int
|
||||
): Paginated<FollowEntity<ConstitutionEntity>> {
|
||||
return requester.run {
|
||||
getFunction("find_follows_constitution_by_citizen")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"created_by_id" to citizenId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun findFollowsByTarget(
|
||||
target: UuidEntity,
|
||||
page: Int,
|
||||
limit: Int
|
||||
): Paginated<FollowSimple<ConstitutionRef, CitizenRef>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
26
src/main/kotlin/component/follow/FollowVoter.kt
Normal file
26
src/main/kotlin/component/follow/FollowVoter.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
package fr.dcproject.component.follow
|
||||
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.voter.Voter
|
||||
import fr.dcproject.voter.VoterResponse
|
||||
import fr.dcproject.component.follow.Follow as FollowEntity
|
||||
|
||||
class FollowVoter : Voter() {
|
||||
fun canCreate(subject: FollowI, citizen: CitizenI?): VoterResponse {
|
||||
return if (citizen == null) denied("You must be connected to follow", "follow.create.notConnected")
|
||||
else granted()
|
||||
}
|
||||
|
||||
fun canDelete(subject: FollowI, citizen: CitizenI?): VoterResponse {
|
||||
return if (citizen == null) denied("You must be connected to unfollow", "follow.delete.notConnected")
|
||||
else granted()
|
||||
}
|
||||
|
||||
fun <S : FollowEntity<*>> canView(subjects: List<S>, citizen: CitizenI?): VoterResponse =
|
||||
canAll(subjects) { canView(it, citizen) }
|
||||
|
||||
fun canView(subject: FollowEntity<*>, citizen: CitizenI?): VoterResponse {
|
||||
return if ((citizen != null && subject.createdBy.id == citizen.id) || !subject.createdBy.followAnonymous) granted()
|
||||
else denied("You cannot view an anonymous follow", "follow.view.anonymous")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package fr.dcproject.component.follow.routes.article
|
||||
|
||||
import fr.dcproject.component.article.ArticleRef
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.follow.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.FollowForUpdate
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.voter.assert
|
||||
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
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object FollowArticle {
|
||||
@Location("/articles/{article}/follows")
|
||||
class ArticleFollowRequest(val article: ArticleRef)
|
||||
|
||||
fun Route.followArticle(repo: FollowArticleRepository, voter: FollowVoter) {
|
||||
post<ArticleFollowRequest> {
|
||||
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
|
||||
voter.assert { canCreate(follow, citizenOrNull) }
|
||||
repo.follow(follow)
|
||||
call.respond(HttpStatusCode.Created)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package fr.dcproject.component.follow.routes.article
|
||||
|
||||
import fr.dcproject.component.article.ArticleRef
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.follow.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.voter.assert
|
||||
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
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object GetFollowArticle {
|
||||
@Location("/articles/{article}/follows")
|
||||
class ArticleFollowRequest(val article: ArticleRef)
|
||||
|
||||
fun Route.getFollowArticle(repo: FollowArticleRepository, voter: FollowVoter) {
|
||||
get<ArticleFollowRequest> {
|
||||
repo.findFollow(citizen, it.article)?.let { follow ->
|
||||
voter.assert { canView(follow, citizenOrNull) }
|
||||
call.respond(follow)
|
||||
} ?: call.respond(HttpStatusCode.NoContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package fr.dcproject.component.follow.routes.article
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.Citizen
|
||||
import fr.dcproject.component.follow.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import io.ktor.application.call
|
||||
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
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object GetMyFollowsArticle {
|
||||
@Location("/citizens/{citizen}/follows/articles")
|
||||
class CitizenFollowArticleRequest(val citizen: Citizen)
|
||||
|
||||
fun Route.getMyFollowsArticle(repo: FollowArticleRepository, voter: FollowVoter) {
|
||||
get<CitizenFollowArticleRequest> {
|
||||
val follows = repo.findByCitizen(it.citizen)
|
||||
voter.assert { canView(follows.result, citizenOrNull) }
|
||||
call.respond(follows)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package fr.dcproject.component.follow.routes.article
|
||||
|
||||
import fr.dcproject.component.article.ArticleRef
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.follow.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.FollowForUpdate
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.voter.assert
|
||||
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
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object UnfollowArticle {
|
||||
@Location("/articles/{article}/follows")
|
||||
class ArticleFollowRequest(val article: ArticleRef)
|
||||
|
||||
fun Route.unfollowArticle(repo: FollowArticleRepository, voter: FollowVoter) {
|
||||
delete<ArticleFollowRequest> {
|
||||
val follow = FollowForUpdate(target = it.article, createdBy = this.citizen)
|
||||
voter.assert { canDelete(follow, citizenOrNull) }
|
||||
repo.unfollow(follow)
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package fr.dcproject.component.follow.routes.constitution
|
||||
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.follow.FollowConstitutionRepository
|
||||
import fr.dcproject.component.follow.FollowForUpdate
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.entity.ConstitutionRef
|
||||
import fr.dcproject.voter.assert
|
||||
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
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object FollowConstitution {
|
||||
@Location("/constitutions/{constitution}/follows")
|
||||
class ConstitutionFollowRequest(val constitution: ConstitutionRef)
|
||||
|
||||
fun Route.followConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) {
|
||||
post<ConstitutionFollowRequest> {
|
||||
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
|
||||
voter.assert { canCreate(follow, citizenOrNull) }
|
||||
repo.follow(follow)
|
||||
call.respond(HttpStatusCode.Created)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package fr.dcproject.component.follow.routes.constitution
|
||||
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.follow.FollowConstitutionRepository
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.entity.ConstitutionRef
|
||||
import fr.dcproject.voter.assert
|
||||
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
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object GetFollowConstitution {
|
||||
@Location("/constitutions/{constitution}/follows")
|
||||
class ConstitutionFollowRequest(val constitution: ConstitutionRef)
|
||||
|
||||
fun Route.getFollowConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) {
|
||||
get<ConstitutionFollowRequest> {
|
||||
repo.findFollow(citizen, it.constitution)?.let { follow ->
|
||||
voter.assert { canView(follow, citizenOrNull) }
|
||||
call.respond(follow)
|
||||
} ?: call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package fr.dcproject.component.follow.routes.constitution
|
||||
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.CitizenRef
|
||||
import fr.dcproject.component.follow.FollowConstitutionRepository
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import io.ktor.application.call
|
||||
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
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object GetMyFollowsConstitution {
|
||||
@Location("/citizens/{citizen}/follows/constitutions")
|
||||
class CitizenFollowConstitutionRequest(val citizen: CitizenRef)
|
||||
|
||||
fun Route.getMyFollowsConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) {
|
||||
get<CitizenFollowConstitutionRequest> {
|
||||
val follows = repo.findByCitizen(it.citizen)
|
||||
voter.assert { canView(follows.result, citizenOrNull) }
|
||||
call.respond(follows)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package fr.dcproject.component.follow.routes.constitution
|
||||
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.follow.FollowConstitutionRepository
|
||||
import fr.dcproject.component.follow.FollowForUpdate
|
||||
import fr.dcproject.component.follow.FollowVoter
|
||||
import fr.dcproject.entity.ConstitutionRef
|
||||
import fr.dcproject.voter.assert
|
||||
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
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object UnfollowConstitution {
|
||||
@Location("/constitutions/{constitution}/follows")
|
||||
class ConstitutionUnfollowRequest(val constitution: ConstitutionRef)
|
||||
|
||||
fun Route.unfollowConstitution(repo: FollowConstitutionRepository, voter: FollowVoter) {
|
||||
delete<ConstitutionUnfollowRequest> {
|
||||
val follow = FollowForUpdate(target = it.constitution, createdBy = this.citizen)
|
||||
voter.assert { canDelete(follow, citizenOrNull) }
|
||||
repo.unfollow(follow)
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user