move routes installation into component

This commit is contained in:
2021-01-21 21:55:24 +01:00
parent 3ba4a195ba
commit 667339979b
27 changed files with 492 additions and 420 deletions

View File

@@ -14,28 +14,28 @@ import io.ktor.response.respond
import io.ktor.routing.Route
@KtorExperimentalLocationsAPI
@Location("/articles/{article}/versions")
class ArticleVersionsRequest(
val article: ArticleForView,
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
}
object FindArticleVersions {
@Location("/articles/{article}/versions")
class ArticleVersionsRequest(
val article: ArticleForView,
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
private fun ArticleRepository.findVersions(request: ArticleVersionsRequest) =
findVersionsByVersionId(request.page, request.limit, request.article.versionId)
private fun ArticleRepository.findVersions(request: ArticleVersionsRequest) =
findVersionsByVersionId(request.page, request.limit, request.article.versionId)
@KtorExperimentalLocationsAPI
fun Route.findArticleVersions(repo: ArticleRepository, voter: ArticleVoter) {
get<ArticleVersionsRequest> {
repo.findVersions(it)
.apply { voter.assert { canView(it.article, citizenOrNull) } }
.let { call.respond(it) }
fun Route.findArticleVersions(repo: ArticleRepository, voter: ArticleVoter) {
get<ArticleVersionsRequest> {
repo.findVersions(it)
.apply { voter.assert { canView(it.article, citizenOrNull) } }
.let { call.respond(it) }
}
}
}

View File

@@ -8,40 +8,44 @@ import fr.dcproject.voter.assert
import fr.postgresjson.connexion.Paginated
import fr.postgresjson.repository.RepositoryI
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
@Location("/articles")
class ArticlesRequest(
page: Int = 1,
limit: Int = 50,
val sort: String? = null,
val direction: RepositoryI.Direction? = null,
val search: String? = null,
val createdBy: String? = null,
val workgroup: 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
object FindArticles {
@Location("/articles")
class ArticlesRequest(
page: Int = 1,
limit: Int = 50,
val sort: String? = null,
val direction: RepositoryI.Direction? = null,
val search: String? = null,
val createdBy: String? = null,
val workgroup: 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
}
private fun ArticleRepository.findArticles(request: ArticlesRequest): Paginated<ArticleForListing> {
return find(
request.page,
request.limit,
request.sort,
request.direction,
request.search,
ArticleRepository.Filter(createdById = request.createdBy, workgroupId = request.workgroup)
)
}
private fun ArticleRepository.findArticles(request: ArticlesRequest): Paginated<ArticleForListing> {
return find(
request.page,
request.limit,
request.sort,
request.direction,
request.search,
ArticleRepository.Filter(createdById = request.createdBy, workgroupId = request.workgroup)
)
}
fun Route.findArticles(repo: ArticleRepository, voter: ArticleVoter) {
get<ArticlesRequest> {
repo.findArticles(it)
.apply { voter.assert { canView(result, citizenOrNull) } }
.let { call.respond(it) }
fun Route.findArticles(repo: ArticleRepository, voter: ArticleVoter) {
get<ArticlesRequest> {
repo.findArticles(it)
.apply { voter.assert { canView(result, citizenOrNull) } }
.let { call.respond(it) }
}
}
}

View File

@@ -4,7 +4,7 @@ import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleRepository
import fr.dcproject.component.article.ArticleViewManager
import fr.dcproject.component.article.ArticleVoter
import fr.dcproject.component.article.routes.ArticleRequest.Output
import fr.dcproject.component.article.routes.GetOneArticle.ArticleRequest.Output
import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.dto.CreatedAt
import fr.dcproject.dto.Opinionable
@@ -19,56 +19,54 @@ import io.ktor.locations.Location
import io.ktor.locations.get
import io.ktor.response.respond
import io.ktor.routing.Route
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.launch
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.UUID
@KtorExperimentalLocationsAPI
@Location("/articles/{articleId}")
class ArticleRequest(val articleId: UUID) : KoinComponent {
val repo: ArticleRepository by inject()
object GetOneArticle {
@Location("/articles/{articleId}")
class ArticleRequest(val articleId: UUID) : KoinComponent {
val repo: ArticleRepository by inject()
@KtorExperimentalAPI
val article: ArticleForView = repo.findById(articleId) ?: throw NotFoundException("Article $articleId not found")
val article: ArticleForView = repo.findById(articleId) ?: throw NotFoundException("Article $articleId not found")
class Output(
article: ArticleForView,
views: fr.dcproject.entity.ViewAggregation = fr.dcproject.entity.ViewAggregation()
) : CreatedAt by CreatedAt.Imp(article),
Opinionable by Opinionable.Imp(article),
Votable by Votable.Imp(article),
Versionable by Versionable.Imp(article),
Viewable by Viewable.Imp(views) {
val id = article.id
val title = article.title
val anonymous = article.anonymous
val content = article.content
val description = article.description
val tags = article.tags
val draft = article.draft
val lastVersion = article.lastVersion
val createdBy = article.createdBy
val workgroup = article.workgroup // TODO change to workgroup DTO
}
}
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
fun Route.getOneArticle(viewManager: ArticleViewManager, voter: ArticleVoter) {
get<ArticleRequest> {
voter.assert { canView(it.article, citizenOrNull) }
Output(
it.article,
viewManager.getViewsCount(it.article)
).also { out ->
call.respond(out)
class Output(
article: ArticleForView,
views: fr.dcproject.entity.ViewAggregation = fr.dcproject.entity.ViewAggregation()
) : CreatedAt by CreatedAt.Imp(article),
Opinionable by Opinionable.Imp(article),
Votable by Votable.Imp(article),
Versionable by Versionable.Imp(article),
Viewable by Viewable.Imp(views) {
val id = article.id
val title = article.title
val anonymous = article.anonymous
val content = article.content
val description = article.description
val tags = article.tags
val draft = article.draft
val lastVersion = article.lastVersion
val createdBy = article.createdBy
val workgroup = article.workgroup // TODO change to workgroup DTO
}
}
launch {
viewManager.addView(call.request.local.remoteHost, it.article, citizenOrNull)
fun Route.getOneArticle(viewManager: ArticleViewManager, voter: ArticleVoter) {
get<ArticleRequest> {
voter.assert { canView(it.article, citizenOrNull) }
Output(
it.article,
viewManager.getViewsCount(it.article)
).also { out ->
call.respond(out)
}
launch {
viewManager.addView(call.request.local.remoteHost, it.article, citizenOrNull)
}
}
}
}

View File

@@ -4,7 +4,7 @@ import fr.dcproject.component.article.ArticleForUpdate
import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleRepository
import fr.dcproject.component.article.ArticleVoter
import fr.dcproject.component.article.routes.PostArticleRequest.Input
import fr.dcproject.component.article.routes.UpsertArticle.UpsertArticleRequest.Input
import fr.dcproject.component.auth.citizen
import fr.dcproject.component.auth.citizenOrNull
import fr.dcproject.component.workgroup.WorkgroupRef
@@ -23,47 +23,44 @@ import io.ktor.routing.Route
import java.util.UUID
@KtorExperimentalLocationsAPI
@Location("/articles")
class PostArticleRequest {
class Input(
val id: UUID?,
val title: String,
val anonymous: Boolean = true,
val content: String,
val description: String,
val tags: List<String> = emptyList(),
val draft: Boolean = false,
val versionId: UUID?,
val workgroup: WorkgroupRef? = null
)
}
@KtorExperimentalLocationsAPI
fun Route.upsertArticle(repo: ArticleRepository, workgroupRepository: WorkgroupRepository, voter: ArticleVoter) {
suspend fun ApplicationCall.convertRequestToEntity(): ArticleForUpdate = receive<Input>().run {
ArticleForUpdate(
id = id ?: UUID.randomUUID(),
title = title,
anonymous = anonymous,
content = content,
description = description,
tags = tags,
draft = draft,
createdBy = citizen,
workgroup = if (workgroup != null) workgroupRepository.findById(workgroup.id) else null,
versionId = versionId
object UpsertArticle {
@Location("/articles")
class UpsertArticleRequest {
class Input(
val id: UUID?,
val title: String,
val anonymous: Boolean = true,
val content: String,
val description: String,
val tags: List<String> = emptyList(),
val draft: Boolean = false,
val versionId: UUID?,
val workgroup: WorkgroupRef? = null
)
}
post<PostArticleRequest> {
val article = call.convertRequestToEntity()
fun Route.upsertArticle(repo: ArticleRepository, workgroupRepository: WorkgroupRepository, voter: ArticleVoter) {
suspend fun ApplicationCall.convertRequestToEntity(): ArticleForUpdate = receive<Input>().run {
ArticleForUpdate(
id = id ?: UUID.randomUUID(),
title = title,
anonymous = anonymous,
content = content,
description = description,
tags = tags,
draft = draft,
createdBy = citizen,
workgroup = if (workgroup != null) workgroupRepository.findById(workgroup.id) else null,
versionId = versionId
)
}
voter.assert { canUpsert(article, citizenOrNull) }
val newArticle: ArticleForView = repo.upsert(article) ?: error("Article not updated")
call.respond(newArticle)
raiseEvent(ArticleUpdate.event, ArticleUpdate(newArticle))
post<UpsertArticleRequest> {
val article = call.convertRequestToEntity()
voter.assert { canUpsert(article, citizenOrNull) }
val newArticle: ArticleForView = repo.upsert(article) ?: error("Article not updated")
call.respond(newArticle)
raiseEvent(ArticleUpdate.event, ArticleUpdate(newArticle))
}
}
}

View File

@@ -0,0 +1,21 @@
package fr.dcproject.component.article.routes
import fr.dcproject.component.article.routes.FindArticleVersions.findArticleVersions
import fr.dcproject.component.article.routes.FindArticles.findArticles
import fr.dcproject.component.article.routes.GetOneArticle.getOneArticle
import fr.dcproject.component.article.routes.UpsertArticle.upsertArticle
import io.ktor.auth.authenticate
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.routing.Routing
import io.ktor.util.KtorExperimentalAPI
import org.koin.ktor.ext.get
@KtorExperimentalLocationsAPI
fun Routing.installArticleRoutes() {
authenticate(optional = true) {
findArticles(get(), get())
findArticleVersions(get(), get())
getOneArticle(get(), get())
upsertArticle(get(), get(), get())
}
}

View File

@@ -6,7 +6,7 @@ import com.sendgrid.helpers.mail.objects.Email
import fr.dcproject.component.citizen.CitizenRepository
import fr.dcproject.component.citizen.CitizenWithEmail
import fr.dcproject.component.citizen.CitizenWithUserI
import fr.dcproject.makeToken
import fr.dcproject.component.auth.jwt.makeToken
import fr.dcproject.messages.Mailer
import io.ktor.http.URLBuilder

View File

@@ -1,4 +1,4 @@
package fr.dcproject
package fr.dcproject.component.auth.jwt
import com.auth0.jwt.JWT
import fr.dcproject.component.auth.UserI

View File

@@ -1,8 +1,7 @@
package component.auth.jwt
package fr.dcproject.component.auth.jwt
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserRepository
import fr.dcproject.component.auth.jwt.JwtConfig
import io.ktor.application.ApplicationCall
import io.ktor.auth.Authentication
import io.ktor.auth.jwt.jwt

View File

@@ -2,7 +2,7 @@ package fr.dcproject.component.auth.routes
import com.fasterxml.jackson.databind.exc.MismatchedInputException
import fr.dcproject.component.auth.UserRepository
import fr.dcproject.makeToken
import fr.dcproject.component.auth.jwt.makeToken
import io.ktor.application.call
import io.ktor.auth.UserPasswordCredential
import io.ktor.http.HttpStatusCode
@@ -13,23 +13,22 @@ import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.Route
import io.ktor.util.KtorExperimentalAPI
@KtorExperimentalLocationsAPI
@Location("/login")
class LoginRequest
object Login {
@Location("/login")
class LoginRequest
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
fun Route.authLogin(userRepo: UserRepository) {
post<LoginRequest> {
try {
val credentials = call.receive<UserPasswordCredential>()
userRepo.findByCredentials(credentials)?.let { user ->
call.respondText(user.makeToken())
} ?: call.respond(HttpStatusCode.BadRequest, "Username not exist or password is wrong")
} catch (e: MismatchedInputException) {
call.respond(HttpStatusCode.BadRequest, "You must be send name and password to the request")
fun Route.authLogin(userRepo: UserRepository) {
post<LoginRequest> {
try {
val credentials = call.receive<UserPasswordCredential>()
userRepo.findByCredentials(credentials)?.let { user ->
call.respondText(user.makeToken())
} ?: call.respond(HttpStatusCode.BadRequest, "Username not exist or password is wrong")
} catch (e: MismatchedInputException) {
call.respond(HttpStatusCode.BadRequest, "You must be send name and password to the request")
}
}
}
}

View File

@@ -3,11 +3,11 @@ package fr.dcproject.component.auth.routes
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI
import fr.dcproject.component.auth.routes.RegisterRequest.Input
import fr.dcproject.component.auth.routes.Register.RegisterRequest.Input
import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.citizen.CitizenRepository
import fr.dcproject.makeToken
import fr.dcproject.component.auth.jwt.makeToken
import io.ktor.application.call
import io.ktor.features.BadRequestException
import io.ktor.http.HttpStatusCode
@@ -18,55 +18,54 @@ import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.Route
import io.ktor.util.KtorExperimentalAPI
import org.joda.time.DateTime
@KtorExperimentalLocationsAPI
@Location("/register")
class RegisterRequest {
data class Input(
val name: Name,
val email: String,
val birthday: DateTime,
val voteAnonymous: Boolean = true,
val followAnonymous: Boolean = true,
val user: User
) {
data class Name(
val firstName: String,
val lastName: String,
val civility: String? = null
)
data class User(
val username: String,
val plainPassword: String? = null
)
object Register {
@Location("/register")
class RegisterRequest {
data class Input(
val name: Name,
val email: String,
val birthday: DateTime,
val voteAnonymous: Boolean = true,
val followAnonymous: Boolean = true,
val user: User
) {
data class Name(
val firstName: String,
val lastName: String,
val civility: String? = null
)
data class User(
val username: String,
val plainPassword: String? = null
)
}
}
}
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
fun Route.authRegister(citizenRepo: CitizenRepository) {
fun Input.toCitizen(): Citizen = Citizen(
name = CitizenI.Name(name.firstName, name.lastName, name.civility),
birthday = birthday,
email = email,
followAnonymous = followAnonymous,
voteAnonymous = voteAnonymous,
user = User(
username = user.username,
plainPassword = user.plainPassword,
roles = listOf(UserI.Roles.ROLE_USER)
fun Route.authRegister(citizenRepo: CitizenRepository) {
fun Input.toCitizen(): Citizen = Citizen(
name = CitizenI.Name(name.firstName, name.lastName, name.civility),
birthday = birthday,
email = email,
followAnonymous = followAnonymous,
voteAnonymous = voteAnonymous,
user = User(
username = user.username,
plainPassword = user.plainPassword,
roles = listOf(UserI.Roles.ROLE_USER)
)
)
)
post<RegisterRequest> {
try {
val citizen = call.receive<Input>().toCitizen()
val createdCitizen = citizenRepo.insertWithUser(citizen)?.user ?: throw BadRequestException("Bad request")
call.respondText(createdCitizen.makeToken())
} catch (e: MissingKotlinParameterException) {
call.respond(HttpStatusCode.BadRequest)
post<RegisterRequest> {
try {
val citizen = call.receive<Input>().toCitizen()
val createdCitizen = citizenRepo.insertWithUser(citizen)?.user ?: throw BadRequestException("Bad request")
call.respondText(createdCitizen.makeToken())
} catch (e: MissingKotlinParameterException) {
call.respond(HttpStatusCode.BadRequest)
}
}
}
}

View File

@@ -1,7 +1,7 @@
package fr.dcproject.component.auth.routes
import fr.dcproject.component.auth.PasswordlessAuth
import fr.dcproject.component.auth.routes.PasswordlessRequest.Input
import fr.dcproject.component.auth.routes.Sso.PasswordlessRequest.Input
import io.ktor.application.call
import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -10,28 +10,27 @@ import io.ktor.locations.post
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.routing.Route
import io.ktor.util.KtorExperimentalAPI
@KtorExperimentalLocationsAPI
@Location("/auth/passwordless")
class PasswordlessRequest {
data class Input(val email: String, val url: String)
}
object Sso {
@Location("/auth/passwordless")
class PasswordlessRequest {
data class Input(val email: String, val url: String)
}
/**
* Send an email to the citizen with a link to automatically connect
*/
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
fun Route.authPasswordless(passwordlessAuth: PasswordlessAuth) {
post<PasswordlessRequest> {
call.receive<Input>().run {
try {
passwordlessAuth.sendEmail(email, url)
} catch (e: PasswordlessAuth.EmailNotFound) {
call.respond(HttpStatusCode.NotFound)
/**
* Send an email to the citizen with a link to automatically connect
*/
fun Route.authPasswordless(passwordlessAuth: PasswordlessAuth) {
post<PasswordlessRequest> {
call.receive<Input>().run {
try {
passwordlessAuth.sendEmail(email, url)
} catch (e: PasswordlessAuth.EmailNotFound) {
call.respond(HttpStatusCode.NotFound)
}
call.respond(HttpStatusCode.NoContent)
}
call.respond(HttpStatusCode.NoContent)
}
}
}

View File

@@ -0,0 +1,19 @@
package fr.dcproject.component.auth.routes
import fr.dcproject.component.auth.routes.Login.authLogin
import fr.dcproject.component.auth.routes.Register.authRegister
import fr.dcproject.component.auth.routes.Sso.authPasswordless
import io.ktor.auth.authenticate
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.routing.Routing
import io.ktor.util.KtorExperimentalAPI
import org.koin.ktor.ext.get
@KtorExperimentalLocationsAPI
fun Routing.installAuthRoutes() {
authenticate(optional = true) {
authLogin(get())
authRegister(get())
authPasswordless(get())
}
}

View File

@@ -18,30 +18,30 @@ import io.ktor.response.respond
import io.ktor.routing.Route
@KtorExperimentalLocationsAPI
object ChangeMyPassword {
@Location("/citizens/{citizen}/password/change")
class ChangePasswordCitizenRequest(val citizen: Citizen) {
data class Input(val oldPassword: String, val newPassword: String)
}
@Location("/citizens/{citizen}/password/change")
class ChangePasswordCitizenRequest(val citizen: Citizen) {
data class Input(val oldPassword: String, val newPassword: String)
}
fun Route.changeMyPassword(voter: CitizenVoter, userRepository: UserRepository) {
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)
@KtorExperimentalLocationsAPI
fun Route.changeMyPassword(voter: CitizenVoter, userRepository: UserRepository) {
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)
call.respond(HttpStatusCode.Created)
}
} catch (e: MissingKotlinParameterException) {
call.respond(HttpStatusCode.BadRequest, "Request format is not correct")
}
} catch (e: MissingKotlinParameterException) {
call.respond(HttpStatusCode.BadRequest, "Request format is not correct")
}
}
}

View File

@@ -13,23 +13,24 @@ import io.ktor.response.respond
import io.ktor.routing.Route
@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
}
object FindCitizens {
@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)
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)
}
}
}

View File

@@ -13,18 +13,19 @@ import io.ktor.response.respond
import io.ktor.routing.Route
@KtorExperimentalLocationsAPI
@Location("/citizens/current")
class CurrentCitizenRequest
object GetCurrentCitizen {
@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)
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)
}
}
}
}

View File

@@ -12,14 +12,15 @@ import io.ktor.response.respond
import io.ktor.routing.Route
@KtorExperimentalLocationsAPI
@Location("/citizens/{citizen}")
class CitizenRequest(val citizen: Citizen)
object GetOneCitizen {
@Location("/citizens/{citizen}")
class CitizenRequest(val citizen: Citizen)
@KtorExperimentalLocationsAPI
fun Route.getOneCitizen(voter: CitizenVoter) {
get<CitizenRequest> {
voter.assert { canView(it.citizen, citizenOrNull) }
fun Route.getOneCitizen(voter: CitizenVoter) {
get<CitizenRequest> {
voter.assert { canView(it.citizen, citizenOrNull) }
call.respond(it.citizen)
call.respond(it.citizen)
}
}
}

View File

@@ -0,0 +1,24 @@
package fr.dcproject.component.citizen.routes
import fr.dcproject.component.auth.routes.Login.authLogin
import fr.dcproject.component.auth.routes.Register.authRegister
import fr.dcproject.component.auth.routes.Sso.authPasswordless
import fr.dcproject.component.citizen.routes.ChangeMyPassword.changeMyPassword
import fr.dcproject.component.citizen.routes.FindCitizens.findCitizen
import fr.dcproject.component.citizen.routes.GetCurrentCitizen.getCurrentCitizen
import fr.dcproject.component.citizen.routes.GetOneCitizen.getOneCitizen
import io.ktor.auth.authenticate
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.routing.Routing
import io.ktor.util.KtorExperimentalAPI
import org.koin.ktor.ext.get
@KtorExperimentalLocationsAPI
fun Routing.installCitizenRoutes() {
authenticate(optional = true) {
findCitizen(get(), get())
getOneCitizen(get())
getCurrentCitizen(get())
changeMyPassword(get(), get())
}
}

View File

@@ -18,30 +18,31 @@ import io.ktor.response.respond
import io.ktor.routing.Route
@KtorExperimentalLocationsAPI
@Location("/articles/{article}/comments")
class PostArticleCommentRequest(
val article: ArticleForView
) {
class Comment(
val content: String
)
suspend fun getComment(call: ApplicationCall) = call.receive<Comment>().run {
CommentForUpdate(
target = article,
createdBy = call.citizen,
content = content
object CreateCommentArticle {
@Location("/articles/{article}/comments")
class PostArticleCommentRequest(
val article: ArticleForView
) {
class Comment(
val content: String
)
}
}
@KtorExperimentalLocationsAPI
fun Route.createCommentArticle(repo: CommentArticleRepository, voter: CommentVoter) {
post<PostArticleCommentRequest> {
it.getComment(call).let { comment ->
voter.assert { canCreate(comment, citizenOrNull) }
repo.comment(comment)
call.respond(HttpStatusCode.Created, comment)
suspend fun getComment(call: ApplicationCall) = call.receive<Comment>().run {
CommentForUpdate(
target = article,
createdBy = call.citizen,
content = content
)
}
}
fun Route.createCommentArticle(repo: CommentArticleRepository, voter: CommentVoter) {
post<PostArticleCommentRequest> {
it.getComment(call).let { comment ->
voter.assert { canCreate(comment, citizenOrNull) }
repo.comment(comment)
call.respond(HttpStatusCode.Created, comment)
}
}
}
}

View File

@@ -14,26 +14,27 @@ import io.ktor.response.respond
import io.ktor.routing.Route
@KtorExperimentalLocationsAPI
@Location("/articles/{article}/comments")
class ArticleCommentsRequest(
val article: ArticleRef,
page: Int = 1,
limit: Int = 50,
val search: String? = null,
sort: String = CommentArticleRepository.Sort.CREATED_AT.sql
) {
val page: Int = if (page < 1) 1 else page
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
val sort: CommentArticleRepository.Sort = CommentArticleRepository.Sort.fromString(sort) ?: CommentArticleRepository.Sort.CREATED_AT
}
object GetArticleComments {
@Location("/articles/{article}/comments")
class ArticleCommentsRequest(
val article: ArticleRef,
page: Int = 1,
limit: Int = 50,
val search: String? = null,
sort: String = CommentArticleRepository.Sort.CREATED_AT.sql
) {
val page: Int = if (page < 1) 1 else page
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
val sort: CommentArticleRepository.Sort = CommentArticleRepository.Sort.fromString(sort) ?: CommentArticleRepository.Sort.CREATED_AT
}
@KtorExperimentalLocationsAPI
fun Route.getArticleComments(repo: CommentArticleRepository, voter: CommentVoter) {
get<ArticleCommentsRequest> {
val comment = repo.findByTarget(it.article, it.page, it.limit, it.sort)
if (comment.result.isNotEmpty()) {
voter.assert { canView(comment.result, citizenOrNull) }
fun Route.getArticleComments(repo: CommentArticleRepository, voter: CommentVoter) {
get<ArticleCommentsRequest> {
val comment = repo.findByTarget(it.article, it.page, it.limit, it.sort)
if (comment.result.isNotEmpty()) {
voter.assert { canView(comment.result, citizenOrNull) }
}
call.respond(HttpStatusCode.OK, comment)
}
call.respond(HttpStatusCode.OK, comment)
}
}

View File

@@ -13,15 +13,16 @@ import io.ktor.response.respond
import io.ktor.routing.Route
@KtorExperimentalLocationsAPI
@Location("/citizens/{citizen}/comments/articles")
class CitizenCommentArticleRequest(val citizen: Citizen)
object GetCitizenArticleComments {
@Location("/citizens/{citizen}/comments/articles")
class CitizenCommentArticleRequest(val citizen: Citizen)
@KtorExperimentalLocationsAPI
fun Route.getCitizenArticleComments(repo: CommentArticleRepository, voter: CommentVoter) {
get<CitizenCommentArticleRequest> {
repo.findByCitizen(it.citizen).let { comments ->
voter.assert { canView(comments.result, citizenOrNull) }
call.respond(comments)
fun Route.getCitizenArticleComments(repo: CommentArticleRepository, voter: CommentVoter) {
get<CitizenCommentArticleRequest> {
repo.findByCitizen(it.citizen).let { comments ->
voter.assert { canView(comments.result, citizenOrNull) }
call.respond(comments)
}
}
}
}

View File

@@ -0,0 +1,18 @@
package fr.dcproject.component.comment.article.routes
import fr.dcproject.component.comment.article.routes.CreateCommentArticle.createCommentArticle
import fr.dcproject.component.comment.article.routes.GetArticleComments.getArticleComments
import fr.dcproject.component.comment.article.routes.GetCitizenArticleComments.getCitizenArticleComments
import io.ktor.auth.authenticate
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.routing.Routing
import org.koin.ktor.ext.get
@KtorExperimentalLocationsAPI
fun Routing.installCommentArticleRoutes() {
authenticate(optional = true) {
getArticleComments(get(), get())
createCommentArticle(get(), get())
getCitizenArticleComments(get(), get())
}
}

View File

@@ -19,25 +19,25 @@ import io.ktor.routing.Route
import io.ktor.util.KtorExperimentalAPI
@KtorExperimentalLocationsAPI
@Location("/comments/{comment}/children")
class CreateCommentChildrenRequest(val comment: CommentRef) {
class Input(val content: String)
}
object CreateCommentChildren {
@Location("/comments/{comment}/children")
class CreateCommentChildrenRequest(val comment: CommentRef) {
class Input(val content: String)
}
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
fun Route.createCommentChildren(repo: CommentRepository, voter: CommentVoter) {
post<CreateCommentChildrenRequest> {
val parent = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
val newComment = CommentForUpdate(
content = call.receive<CreateCommentChildrenRequest.Input>().content,
createdBy = citizen,
parent = parent
)
fun Route.createCommentChildren(repo: CommentRepository, voter: CommentVoter) {
post<CreateCommentChildrenRequest> {
val parent = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
val newComment = CommentForUpdate(
content = call.receive<CreateCommentChildrenRequest.Input>().content,
createdBy = citizen,
parent = parent
)
voter.assert { canCreate(newComment, citizenOrNull) }
repo.comment(newComment)
voter.assert { canCreate(newComment, citizenOrNull) }
repo.comment(newComment)
call.respond(HttpStatusCode.Created, newComment)
call.respond(HttpStatusCode.Created, newComment)
}
}
}

View File

@@ -14,22 +14,21 @@ import io.ktor.locations.put
import io.ktor.request.receiveText
import io.ktor.response.respond
import io.ktor.routing.Route
import io.ktor.util.KtorExperimentalAPI
@KtorExperimentalLocationsAPI
@Location("/comments/{comment}")
class EditCommentRequest(val comment: CommentRef)
object EditComment {
@Location("/comments/{comment}")
class EditCommentRequest(val comment: CommentRef)
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
fun Route.editComment(repo: CommentRepository, voter: CommentVoter) {
put<EditCommentRequest> {
val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
voter.assert { canUpdate(comment, citizenOrNull) }
fun Route.editComment(repo: CommentRepository, voter: CommentVoter) {
put<EditCommentRequest> {
val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment not found")
voter.assert { canUpdate(comment, citizenOrNull) }
comment.content = call.receiveText()
repo.edit(comment)
comment.content = call.receiveText()
repo.edit(comment)
call.respond(HttpStatusCode.OK, comment)
call.respond(HttpStatusCode.OK, comment)
}
}
}

View File

@@ -15,30 +15,30 @@ import io.ktor.util.KtorExperimentalAPI
import java.util.UUID
@KtorExperimentalLocationsAPI
@Location("/comments/{comment}/children")
class CommentChildrenRequest(
val comment: UUID,
page: Int = 1,
limit: Int = 50,
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
}
object GetCommentChildren {
@Location("/comments/{comment}/children")
class CommentChildrenRequest(
val comment: UUID,
page: Int = 1,
limit: Int = 50,
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
}
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
fun Route.getChildrenComments(repo: CommentRepository, voter: CommentVoter) {
get<CommentChildrenRequest> {
val comments =
repo.findByParent(
it.comment,
it.page,
it.limit
)
fun Route.getChildrenComments(repo: CommentRepository, voter: CommentVoter) {
get<CommentChildrenRequest> {
val comments =
repo.findByParent(
it.comment,
it.page,
it.limit
)
voter.assert { canView(comments.result, citizenOrNull) }
voter.assert { canView(comments.result, citizenOrNull) }
call.respond(HttpStatusCode.OK, comments)
call.respond(HttpStatusCode.OK, comments)
}
}
}

View File

@@ -16,16 +16,16 @@ import io.ktor.routing.Route
import io.ktor.util.KtorExperimentalAPI
@KtorExperimentalLocationsAPI
@Location("/comments/{comment}")
class CommentRequest(val comment: CommentRef)
object GetOneComment {
@Location("/comments/{comment}")
class CommentRequest(val comment: CommentRef)
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
fun Route.getOneComment(repo: CommentRepository, voter: CommentVoter) {
get<CommentRequest> {
val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment ${it.comment.id} not found")
voter.assert { canView(comment, citizenOrNull) }
fun Route.getOneComment(repo: CommentRepository, voter: CommentVoter) {
get<CommentRequest> {
val comment = repo.findById(it.comment.id) ?: throw NotFoundException("Comment ${it.comment.id} not found")
voter.assert { canView(comment, citizenOrNull) }
call.respond(HttpStatusCode.OK, comment)
call.respond(HttpStatusCode.OK, comment)
}
}
}

View File

@@ -0,0 +1,20 @@
package fr.dcproject.component.comment.generic.routes
import fr.dcproject.component.comment.generic.routes.CreateCommentChildren.createCommentChildren
import fr.dcproject.component.comment.generic.routes.EditComment.editComment
import fr.dcproject.component.comment.generic.routes.GetCommentChildren.getChildrenComments
import fr.dcproject.component.comment.generic.routes.GetOneComment.getOneComment
import io.ktor.auth.authenticate
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.routing.Routing
import org.koin.ktor.ext.get
@KtorExperimentalLocationsAPI
fun Routing.installCommentRoutes() {
authenticate(optional = true) {
editComment(get(), get())
getOneComment(get(), get())
createCommentChildren(get(), get())
getChildrenComments(get(), get())
}
}