Refactors Citizen into component
Refactor CitizenVoter Split citizens routes
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.article
|
||||
|
||||
import fr.dcproject.component.citizen.*
|
||||
import fr.dcproject.entity.*
|
||||
import fr.postgresjson.entity.*
|
||||
import org.joda.time.DateTime
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package fr.dcproject.component.article
|
||||
|
||||
import fr.dcproject.entity.CitizenI
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.entity.ViewAggregation
|
||||
import fr.dcproject.utils.contentToString
|
||||
import fr.dcproject.utils.getJsonField
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package fr.dcproject.component.article
|
||||
|
||||
import fr.dcproject.entity.CitizenI
|
||||
import fr.dcproject.component.citizen.CitizenI
|
||||
import fr.dcproject.entity.CreatedBy
|
||||
import fr.dcproject.entity.VersionableRef
|
||||
import fr.dcproject.voter.Voter
|
||||
|
||||
110
src/main/kotlin/component/citizen/Citizen.kt
Normal file
110
src/main/kotlin/component/citizen/Citizen.kt
Normal file
@@ -0,0 +1,110 @@
|
||||
package fr.dcproject.component.citizen
|
||||
|
||||
import fr.dcproject.component.citizen.CitizenI.Name
|
||||
import fr.dcproject.entity.User
|
||||
import fr.dcproject.entity.UserI
|
||||
import fr.dcproject.entity.UserRef
|
||||
import fr.dcproject.entity.WorkgroupSimple
|
||||
import fr.postgresjson.entity.*
|
||||
import org.joda.time.DateTime
|
||||
import java.util.*
|
||||
|
||||
@Deprecated("")
|
||||
class Citizen(
|
||||
override val id: UUID = UUID.randomUUID(),
|
||||
override val name: Name,
|
||||
override val email: String,
|
||||
override val birthday: DateTime,
|
||||
override val voteAnonymous: Boolean = true,
|
||||
override val followAnonymous: Boolean = true,
|
||||
override val user: User,
|
||||
deletedAt: DateTime? = null
|
||||
) : CitizenFull,
|
||||
CitizenBasicI,
|
||||
CitizenRef(id),
|
||||
CitizenCartI,
|
||||
EntityCreatedAt by EntityCreatedAtImp(),
|
||||
EntityDeletedAt by EntityDeletedAtImp(deletedAt) {
|
||||
var workgroups: List<WorkgroupAndRoles> = emptyList()
|
||||
|
||||
class WorkgroupAndRoles(
|
||||
val roles: List<String>,
|
||||
val workgroup: WorkgroupSimple<CitizenRef>
|
||||
)
|
||||
}
|
||||
|
||||
@Deprecated("")
|
||||
data class CitizenBasic(
|
||||
override var id: UUID = UUID.randomUUID(),
|
||||
override var name: Name,
|
||||
override var email: String,
|
||||
override var birthday: DateTime,
|
||||
override var voteAnonymous: Boolean = true,
|
||||
override var followAnonymous: Boolean = true,
|
||||
override val user: User,
|
||||
override val deletedAt: DateTime? = null
|
||||
) : CitizenBasicI,
|
||||
CitizenRefWithUser(id, user),
|
||||
EntityDeletedAt by EntityDeletedAtImp(deletedAt)
|
||||
|
||||
@Deprecated("")
|
||||
open class CitizenSimple(
|
||||
id: UUID = UUID.randomUUID(),
|
||||
var name: Name,
|
||||
user: UserRef
|
||||
) : CitizenRefWithUser(id, user)
|
||||
|
||||
class CitizenCart(
|
||||
id: UUID = UUID.randomUUID(),
|
||||
override val name: Name,
|
||||
override val user: UserRef
|
||||
) : CitizenRef(id),
|
||||
CitizenCartI
|
||||
|
||||
interface CitizenCartI : CitizenI, CitizenWithUserI {
|
||||
val name: Name
|
||||
}
|
||||
|
||||
open class CitizenRefWithUser(
|
||||
id: UUID = UUID.randomUUID(),
|
||||
override val user: UserRef
|
||||
) : CitizenWithUserI,
|
||||
CitizenRef(id)
|
||||
|
||||
open class CitizenRef(
|
||||
id: UUID = UUID.randomUUID()
|
||||
) : UuidEntity(id),
|
||||
CitizenI
|
||||
|
||||
interface CitizenI : UuidEntityI {
|
||||
data class Name(
|
||||
override val firstName: String,
|
||||
override val lastName: String,
|
||||
override val civility: String? = null
|
||||
) : NameI
|
||||
|
||||
interface NameI {
|
||||
val firstName: String
|
||||
val lastName: String
|
||||
val civility: String?
|
||||
fun getFullName(): String = "${civility ?: ""} $firstName $lastName".trim()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("")
|
||||
interface CitizenBasicI : CitizenWithUserI, EntityDeletedAt {
|
||||
val name: Name
|
||||
val email: String
|
||||
val birthday: DateTime
|
||||
val voteAnonymous: Boolean
|
||||
val followAnonymous: Boolean
|
||||
}
|
||||
|
||||
@Deprecated("")
|
||||
interface CitizenFull : CitizenBasicI {
|
||||
override val user: User
|
||||
}
|
||||
|
||||
interface CitizenWithUserI : CitizenI {
|
||||
val user: UserI
|
||||
}
|
||||
49
src/main/kotlin/component/citizen/CitizenRepository.kt
Normal file
49
src/main/kotlin/component/citizen/CitizenRepository.kt
Normal file
@@ -0,0 +1,49 @@
|
||||
package fr.dcproject.component.citizen
|
||||
|
||||
import fr.dcproject.entity.UserI
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
import fr.postgresjson.connexion.Requester
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
import net.pearx.kasechange.toSnakeCase
|
||||
import java.util.*
|
||||
|
||||
class CitizenRepository(override var requester: Requester) : RepositoryI {
|
||||
fun findById(id: UUID): Citizen? = requester
|
||||
.getFunction("find_citizen_by_id_with_user_and_workgroups")
|
||||
.selectOne("id" to id)
|
||||
|
||||
fun findByUser(user: UserI): Citizen? = requester
|
||||
.getFunction("find_citizen_by_user_id")
|
||||
.selectOne("user_id" to user.id)
|
||||
|
||||
fun findByUsername(unsername: String): Citizen? = requester
|
||||
.getFunction("find_citizen_by_username")
|
||||
.selectOne("username" to unsername)
|
||||
|
||||
fun findByEmail(email: String): Citizen? = requester
|
||||
.getFunction("find_citizen_by_email")
|
||||
.selectOne("email" to email)
|
||||
|
||||
fun find(
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
sort: String? = null,
|
||||
direction: RepositoryI.Direction? = null,
|
||||
search: String? = null
|
||||
): Paginated<CitizenBasic> = requester
|
||||
.getFunction("find_citizens")
|
||||
.select(
|
||||
page, limit,
|
||||
"sort" to sort?.toSnakeCase(),
|
||||
"direction" to direction,
|
||||
"search" to search
|
||||
)
|
||||
|
||||
fun upsert(citizen: CitizenFull): Citizen? = requester
|
||||
.getFunction("upsert_citizen")
|
||||
.selectOne("resource" to citizen)
|
||||
|
||||
fun insertWithUser(citizen: CitizenFull): Citizen? = requester
|
||||
.getFunction("insert_citizen_with_user")
|
||||
.selectOne("resource" to citizen)
|
||||
}
|
||||
26
src/main/kotlin/component/citizen/CitizenVoter.kt
Normal file
26
src/main/kotlin/component/citizen/CitizenVoter.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
package fr.dcproject.component.citizen
|
||||
|
||||
import fr.dcproject.voter.Voter
|
||||
import fr.dcproject.voter.VoterResponse
|
||||
import fr.postgresjson.entity.EntityDeletedAt
|
||||
|
||||
class CitizenVoter : Voter() {
|
||||
fun <S> canView(subjects: List<S>, connectedCitizen: CitizenI?): VoterResponse where S : CitizenI, S: EntityDeletedAt =
|
||||
canAll(subjects) { canView(it, connectedCitizen) }
|
||||
|
||||
fun <S> canView(subject: S, connectedCitizen: CitizenI?): VoterResponse where S : CitizenI, S: EntityDeletedAt {
|
||||
if (connectedCitizen == null) return denied("You must be connected to view citizen", "citizen.view.connected")
|
||||
return if (subject.isDeleted()) denied("You cannot view a deleted citizen", "citizen.view.deleted")
|
||||
else granted()
|
||||
}
|
||||
|
||||
fun <S: CitizenI> canUpdate(subject: S, connectedCitizen: CitizenI?): VoterResponse {
|
||||
if (connectedCitizen == null) return denied("You must be connected to update Citizen", "citizen.update.notConnected")
|
||||
return if (subject.id == connectedCitizen.id) granted() else denied("You can only update your citizen", "citizen.update.notYours")
|
||||
}
|
||||
|
||||
fun <S: CitizenI> canChangePassword(subject: S, connectedCitizen: CitizenI?): VoterResponse {
|
||||
if (connectedCitizen == null) return denied("You must be connected to change your password", "citizen.changePassword.notConnected")
|
||||
return if (subject.id == connectedCitizen.id) granted() else denied("You can only change your password", "citizen.password.notYours")
|
||||
}
|
||||
}
|
||||
45
src/main/kotlin/component/citizen/routes/ChangeMyPassword.kt
Normal file
45
src/main/kotlin/component/citizen/routes/ChangeMyPassword.kt
Normal file
@@ -0,0 +1,45 @@
|
||||
package fr.dcproject.component.citizen.routes
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
|
||||
import fr.dcproject.citizen
|
||||
import fr.dcproject.citizenOrNull
|
||||
import fr.dcproject.component.citizen.Citizen
|
||||
import fr.dcproject.component.citizen.CitizenVoter
|
||||
import fr.dcproject.repository.User
|
||||
import fr.dcproject.voter.assert
|
||||
import io.ktor.application.*
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.locations.*
|
||||
import io.ktor.request.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
|
||||
@Location("/citizens/{citizen}/password/change")
|
||||
class ChangePasswordCitizenRequest(val citizen: Citizen) {
|
||||
data class Input(val oldPassword: String, val newPassword: String)
|
||||
}
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Route.changeMyPassword(voter: CitizenVoter, userRepository: User) {
|
||||
put<ChangePasswordCitizenRequest> {
|
||||
voter.assert { canChangePassword(it.citizen, citizenOrNull) }
|
||||
try {
|
||||
val content = call.receive<ChangePasswordCitizenRequest.Input>()
|
||||
val currentUser = userRepository.findByCredentials(UserPasswordCredential(citizen.user.username, content.oldPassword))
|
||||
val user = it.citizen.user
|
||||
if (currentUser == null || currentUser.id != user.id) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Bad password")
|
||||
} else {
|
||||
user.plainPassword = content.newPassword
|
||||
userRepository.changePassword(user)
|
||||
|
||||
call.respond(HttpStatusCode.Created)
|
||||
}
|
||||
} catch (e: MissingKotlinParameterException) {
|
||||
call.respond(HttpStatusCode.BadRequest, "Request format is not correct")
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/main/kotlin/component/citizen/routes/FindCitizens.kt
Normal file
33
src/main/kotlin/component/citizen/routes/FindCitizens.kt
Normal file
@@ -0,0 +1,33 @@
|
||||
package fr.dcproject.component.citizen.routes
|
||||
|
||||
import fr.dcproject.citizenOrNull
|
||||
import fr.dcproject.component.citizen.CitizenRepository
|
||||
import fr.dcproject.component.citizen.CitizenVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
import io.ktor.application.*
|
||||
import io.ktor.locations.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
@Location("/citizens")
|
||||
class CitizensRequest(
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
val sort: String? = null,
|
||||
val direction: RepositoryI.Direction? = null,
|
||||
val search: String? = null
|
||||
) {
|
||||
val page: Int = if (page < 1) 1 else page
|
||||
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
|
||||
}
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Route.findCitizen(voter: CitizenVoter, repo: CitizenRepository) {
|
||||
get<CitizensRequest> {
|
||||
val citizens = repo.find(it.page, it.limit, it.sort, it.direction, it.search)
|
||||
voter.assert { canView(citizens.result, citizenOrNull) }
|
||||
call.respond(citizens)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package fr.dcproject.component.citizen.routes
|
||||
|
||||
import fr.dcproject.citizen
|
||||
import fr.dcproject.citizenOrNull
|
||||
import fr.dcproject.component.citizen.CitizenVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import io.ktor.application.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.locations.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
@Location("/citizens/current")
|
||||
class CurrentCitizenRequest
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Route.getCurrentCitizen(voter: CitizenVoter) {
|
||||
get<CurrentCitizenRequest> {
|
||||
val currentUser = citizenOrNull
|
||||
if (currentUser === null) {
|
||||
call.respond(HttpStatusCode.Unauthorized)
|
||||
} else {
|
||||
voter.assert { canView(currentUser, citizenOrNull) }
|
||||
call.respond(citizen)
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/main/kotlin/component/citizen/routes/GetOneCitizen.kt
Normal file
23
src/main/kotlin/component/citizen/routes/GetOneCitizen.kt
Normal file
@@ -0,0 +1,23 @@
|
||||
package fr.dcproject.component.citizen.routes
|
||||
|
||||
import fr.dcproject.citizenOrNull
|
||||
import fr.dcproject.component.citizen.Citizen
|
||||
import fr.dcproject.component.citizen.CitizenVoter
|
||||
import fr.dcproject.voter.assert
|
||||
import io.ktor.application.*
|
||||
import io.ktor.locations.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
@Location("/citizens/{citizen}")
|
||||
class CitizenRequest(val citizen: Citizen)
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Route.getOneCitizen(voter: CitizenVoter) {
|
||||
get<CitizenRequest> {
|
||||
voter.assert { canView(it.citizen, citizenOrNull) }
|
||||
|
||||
call.respond(it.citizen)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user