Split Entities for remove nullable variables
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="All Tests" type="JUnit" factoryName="JUnit" show_console_on_std_err="true">
|
<configuration default="false" name="All Tests + Lint" type="JUnit" factoryName="JUnit" show_console_on_std_err="true">
|
||||||
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
<module name="dcproject.test" />
|
<module name="dcproject.test" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
<dir value="$PROJECT_DIR$" />
|
<dir value="$PROJECT_DIR$" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
|
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Lint" run_configuration_type="GradleRunConfiguration" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
||||||
4
.idea/runConfigurations/Cucumber_Tests.xml
generated
4
.idea/runConfigurations/Cucumber_Tests.xml
generated
@@ -1,10 +1,10 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Cucumber Tests" type="JUnit" factoryName="JUnit">
|
<configuration default="false" name="Cucumber Tests" type="JUnit" factoryName="JUnit">
|
||||||
<output_file path="$PROJECT_DIR$/var/log/test/cucumber.out.log" is_save="true" />
|
<output_file path="$PROJECT_DIR$/var/log/test/cucumber.out.log" />
|
||||||
<module name="dcproject.test" />
|
<module name="dcproject.test" />
|
||||||
<option name="PACKAGE_NAME" value="" />
|
<option name="PACKAGE_NAME" value="" />
|
||||||
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
||||||
<option name="METHOD_NAME" value="testArticle" />
|
<option name="METHOD_NAME" value="" />
|
||||||
<option name="TEST_OBJECT" value="class" />
|
<option name="TEST_OBJECT" value="class" />
|
||||||
<option name="PARAMETERS" value="" />
|
<option name="PARAMETERS" value="" />
|
||||||
<envs>
|
<envs>
|
||||||
|
|||||||
31
.idea/runConfigurations/Lint.xml
generated
Normal file
31
.idea/runConfigurations/Lint.xml
generated
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Lint" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="executionName" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="externalSystemIdString" value="GRADLE" />
|
||||||
|
<option name="scriptParameters" value="" />
|
||||||
|
<option name="taskDescriptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="taskNames">
|
||||||
|
<list>
|
||||||
|
<option value="ktlintCheck" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<extension name="net.ashald.envfile">
|
||||||
|
<option name="IS_ENABLED" value="false" />
|
||||||
|
<option name="IS_SUBST" value="false" />
|
||||||
|
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||||
|
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||||
|
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||||
|
<ENTRIES>
|
||||||
|
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
|
||||||
|
</ENTRIES>
|
||||||
|
</extension>
|
||||||
|
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -8,10 +8,7 @@ import com.fasterxml.jackson.databind.SerializationFeature
|
|||||||
import com.fasterxml.jackson.datatype.joda.JodaModule
|
import com.fasterxml.jackson.datatype.joda.JodaModule
|
||||||
import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException
|
import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException
|
||||||
import fr.dcproject.Env.PROD
|
import fr.dcproject.Env.PROD
|
||||||
import fr.dcproject.entity.Article
|
import fr.dcproject.entity.*
|
||||||
import fr.dcproject.entity.Citizen
|
|
||||||
import fr.dcproject.entity.Constitution
|
|
||||||
import fr.dcproject.entity.User
|
|
||||||
import fr.dcproject.routes.*
|
import fr.dcproject.routes.*
|
||||||
import fr.dcproject.security.voter.*
|
import fr.dcproject.security.voter.*
|
||||||
import fr.postgresjson.migration.Migrations
|
import fr.postgresjson.migration.Migrations
|
||||||
@@ -80,9 +77,16 @@ fun Application.module(env: Env = PROD) {
|
|||||||
// TODO: create generic convert for entityI
|
// TODO: create generic convert for entityI
|
||||||
convert<Article> {
|
convert<Article> {
|
||||||
decode { values, _ ->
|
decode { values, _ ->
|
||||||
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
values.singleOrNull()?.let {
|
||||||
?: throw InternalError("Cannot convert $values to UUID")
|
get<RepositoryArticle>().findById(UUID.fromString(it)) ?: throw InternalError("Article $values not found")
|
||||||
get<RepositoryArticle>().findById(id) ?: throw InternalError("Article $values not found")
|
} ?: throw NotFoundException("Article $values not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
convert<ArticleRef> {
|
||||||
|
decode { values, _ ->
|
||||||
|
values.singleOrNull()?.let {
|
||||||
|
ArticleRef(UUID.fromString(it))
|
||||||
|
} ?: throw NotFoundException("Article $values not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +94,7 @@ fun Application.module(env: Env = PROD) {
|
|||||||
decode { values, _ ->
|
decode { values, _ ->
|
||||||
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
||||||
?: throw InternalError("Cannot convert $values to UUID")
|
?: throw InternalError("Cannot convert $values to UUID")
|
||||||
get<RepositoryConstitution>().findById(id) ?: throw InternalError("Constitution $values not found")
|
get<RepositoryConstitution>().findById(id) ?: throw NotFoundException("Constitution $values not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +102,7 @@ fun Application.module(env: Env = PROD) {
|
|||||||
decode { values, _ ->
|
decode { values, _ ->
|
||||||
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
||||||
?: throw InternalError("Cannot convert $values to UUID")
|
?: throw InternalError("Cannot convert $values to UUID")
|
||||||
get<RepositoryCitizen>().findById(id, true) ?: throw InternalError("Citizen $values not found")
|
get<RepositoryCitizen>().findById(id, true) ?: throw NotFoundException("Citizen $values not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,6 +183,9 @@ fun Application.module(env: Env = PROD) {
|
|||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
exception<NotFoundException> { e ->
|
||||||
|
call.respond(HttpStatusCode.BadRequest, e.message!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
install(CORS) {
|
install(CORS) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package fr.dcproject
|
package fr.dcproject
|
||||||
|
|
||||||
|
import fr.dcproject.entity.UserI
|
||||||
import fr.dcproject.security.voter.ForbiddenException
|
import fr.dcproject.security.voter.ForbiddenException
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.auth.authentication
|
import io.ktor.auth.authentication
|
||||||
@@ -8,7 +9,6 @@ import io.ktor.util.pipeline.PipelineContext
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.koin.core.context.GlobalContext
|
import org.koin.core.context.GlobalContext
|
||||||
import fr.dcproject.entity.Citizen as CitizenEntity
|
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||||
import fr.dcproject.entity.User as UserEntity
|
|
||||||
import fr.dcproject.repository.Citizen as CitizenRepository
|
import fr.dcproject.repository.Citizen as CitizenRepository
|
||||||
|
|
||||||
private val citizenAttributeKey = AttributeKey<CitizenEntity>("CitizenContext")
|
private val citizenAttributeKey = AttributeKey<CitizenEntity>("CitizenContext")
|
||||||
@@ -16,7 +16,7 @@ private val citizenAttributeKey = AttributeKey<CitizenEntity>("CitizenContext")
|
|||||||
val ApplicationCall.citizen: CitizenEntity
|
val ApplicationCall.citizen: CitizenEntity
|
||||||
get() = attributes.computeIfAbsent(citizenAttributeKey) {
|
get() = attributes.computeIfAbsent(citizenAttributeKey) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val user = authentication.principal<UserEntity>() ?: throw ForbiddenException()
|
val user = authentication.principal<UserI>() ?: throw ForbiddenException()
|
||||||
GlobalContext.get().koin.get<CitizenRepository>().findByUser(user) ?: throw ForbiddenException("Citizen not found for this user id \"${user.id}\"")
|
GlobalContext.get().koin.get<CitizenRepository>().findByUser(user) ?: throw ForbiddenException("Citizen not found for this user id \"${user.id}\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import com.auth0.jwt.JWT
|
|||||||
import com.auth0.jwt.JWTVerifier
|
import com.auth0.jwt.JWTVerifier
|
||||||
import com.auth0.jwt.algorithms.Algorithm
|
import com.auth0.jwt.algorithms.Algorithm
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.UserI
|
||||||
import org.eclipse.jetty.util.resource.JarResource
|
import org.eclipse.jetty.util.resource.JarResource
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -46,7 +46,7 @@ object JwtConfig {
|
|||||||
/**
|
/**
|
||||||
* Produce a token for this combination of User and Account
|
* Produce a token for this combination of User and Account
|
||||||
*/
|
*/
|
||||||
fun makeToken(user: User): String = JWT.create()
|
fun makeToken(user: UserI): String = JWT.create()
|
||||||
.withSubject("Authentication")
|
.withSubject("Authentication")
|
||||||
.withIssuer(issuer)
|
.withIssuer(issuer)
|
||||||
.withClaim("id", user.id.toString())
|
.withClaim("id", user.id.toString())
|
||||||
|
|||||||
@@ -1,26 +1,77 @@
|
|||||||
package fr.dcproject.entity
|
package fr.dcproject.entity
|
||||||
import fr.postgresjson.entity.mutable.*
|
import fr.postgresjson.entity.immutable.*
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAt
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAtImp
|
||||||
|
import fr.postgresjson.entity.mutable.EntityVersioning
|
||||||
|
import fr.postgresjson.entity.mutable.UuidEntityVersioning
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class Article(
|
class Article(
|
||||||
id: UUID = UUID.randomUUID(),
|
id: UUID = UUID.randomUUID(),
|
||||||
var title: String?,
|
title: String,
|
||||||
var anonymous: Boolean? = true,
|
anonymous: Boolean = true,
|
||||||
var content: String?,
|
content: String,
|
||||||
var description: String?,
|
description: String,
|
||||||
var tags: List<String> = emptyList(),
|
tags: List<String> = emptyList(),
|
||||||
var draft: Boolean = false,
|
override var draft: Boolean = false,
|
||||||
var lastVersion: Boolean = false,
|
override var lastVersion: Boolean = false,
|
||||||
createdBy: Citizen?
|
createdBy: CitizenBasic
|
||||||
) :
|
) : ArticleFull,
|
||||||
UuidEntity(id),
|
ArticleBasic(id, title, anonymous, content, description, tags, createdBy)
|
||||||
EntityVersioning<UUID, Int> by UuidEntityVersioning(),
|
|
||||||
EntityCreatedAt by EntityCreatedAtImp(),
|
open class ArticleBasic(
|
||||||
EntityCreatedBy<Citizen> by EntityCreatedByImp(createdBy),
|
id: UUID = UUID.randomUUID(),
|
||||||
EntityDeletedAt by EntityDeletedAtImp(),
|
title: String,
|
||||||
Votable by VotableImp() {
|
override var anonymous: Boolean = true,
|
||||||
|
override var content: String,
|
||||||
|
override var description: String,
|
||||||
|
override var tags: List<String> = emptyList(),
|
||||||
|
override val createdBy: CitizenBasic
|
||||||
|
) : ArticleBasicI,
|
||||||
|
ArticleSimple(id, title, createdBy) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
tags = tags.distinct()
|
tags = tags.distinct()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open class ArticleSimple(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
override var title: String,
|
||||||
|
override val createdBy: CitizenBasic
|
||||||
|
) : ArticleSimpleI,
|
||||||
|
ArticleRef(id),
|
||||||
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
|
EntityCreatedBy<CitizenBasicI> by EntityCreatedByImp(createdBy),
|
||||||
|
EntityDeletedAt by EntityDeletedAtImp(),
|
||||||
|
EntityVersioning<UUID, Int> by UuidEntityVersioning(),
|
||||||
|
Votable by VotableImp()
|
||||||
|
|
||||||
|
open class ArticleRef(
|
||||||
|
id: UUID = UUID.randomUUID()
|
||||||
|
) : ArticleI, TargetRef(id)
|
||||||
|
|
||||||
|
interface ArticleI : UuidEntityI, TargetI
|
||||||
|
|
||||||
|
interface ArticleSimpleI :
|
||||||
|
ArticleI,
|
||||||
|
EntityVersioning<UUID, Int>,
|
||||||
|
EntityCreatedBy<CitizenBasicI>,
|
||||||
|
EntityCreatedAt,
|
||||||
|
EntityDeletedAt,
|
||||||
|
Votable {
|
||||||
|
var title: String
|
||||||
|
}
|
||||||
|
interface ArticleBasicI :
|
||||||
|
ArticleSimpleI {
|
||||||
|
var anonymous: Boolean
|
||||||
|
var content: String
|
||||||
|
var description: String
|
||||||
|
var tags: List<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArticleFull :
|
||||||
|
ArticleBasicI {
|
||||||
|
var draft: Boolean
|
||||||
|
var lastVersion: Boolean
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,24 +1,68 @@
|
|||||||
package fr.dcproject.entity
|
package fr.dcproject.entity
|
||||||
|
|
||||||
import fr.postgresjson.entity.mutable.*
|
import fr.dcproject.entity.CitizenI.Name
|
||||||
|
import fr.postgresjson.entity.immutable.EntityCreatedAt
|
||||||
|
import fr.postgresjson.entity.immutable.EntityCreatedAtImp
|
||||||
|
import fr.postgresjson.entity.immutable.UuidEntity
|
||||||
|
import fr.postgresjson.entity.immutable.UuidEntityI
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAt
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAtImp
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class Citizen(
|
class Citizen(
|
||||||
id: UUID = UUID.randomUUID(),
|
id: UUID = UUID.randomUUID(),
|
||||||
var name: Name?,
|
name: Name,
|
||||||
var email: String?,
|
email: String,
|
||||||
var birthday: DateTime?,
|
birthday: DateTime,
|
||||||
var userId: UUID? = null,
|
voteAnonymous: Boolean = true,
|
||||||
var voteAnonymous: Boolean = true,
|
followAnonymous: Boolean = true,
|
||||||
var followAnonymous: Boolean = true,
|
override val user: User
|
||||||
var user: User?
|
) : CitizenFull,
|
||||||
|
CitizenBasic(id, name, email, birthday, voteAnonymous, followAnonymous, user),
|
||||||
|
EntityCreatedAt by EntityCreatedAtImp()
|
||||||
|
|
||||||
|
open class CitizenBasic(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
name: Name,
|
||||||
|
override var email: String,
|
||||||
|
override var birthday: DateTime,
|
||||||
|
override var voteAnonymous: Boolean = true,
|
||||||
|
override var followAnonymous: Boolean = true,
|
||||||
|
user: UserRef
|
||||||
|
) : CitizenBasicI,
|
||||||
|
CitizenSimple(id, name, user)
|
||||||
|
|
||||||
|
open class CitizenSimple(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
var name: Name,
|
||||||
|
user: UserRef
|
||||||
|
) : CitizenRef(id, user)
|
||||||
|
|
||||||
|
open class CitizenRef(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
open val user: UserRef
|
||||||
) : UuidEntity(id),
|
) : UuidEntity(id),
|
||||||
EntityCreatedAt by EntityCreatedAtImp(),
|
CitizenI,
|
||||||
EntityDeletedAt by EntityDeletedAtImp() {
|
EntityDeletedAt by EntityDeletedAtImp()
|
||||||
|
|
||||||
|
interface CitizenI : UuidEntityI {
|
||||||
data class Name(
|
data class Name(
|
||||||
var firstName: String?,
|
var firstName: String,
|
||||||
var lastName: String?,
|
var lastName: String,
|
||||||
var civility: String? = null
|
var civility: String? = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CitizenBasicI : CitizenI, EntityDeletedAt {
|
||||||
|
var name: Name
|
||||||
|
var email: String
|
||||||
|
var birthday: DateTime
|
||||||
|
var voteAnonymous: Boolean
|
||||||
|
var followAnonymous: Boolean
|
||||||
|
val user: UserI
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CitizenFull : CitizenBasicI {
|
||||||
|
override val user: User
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
package fr.dcproject.entity
|
package fr.dcproject.entity
|
||||||
|
|
||||||
import fr.postgresjson.entity.mutable.*
|
import fr.postgresjson.entity.immutable.EntityUpdatedAt
|
||||||
|
import fr.postgresjson.entity.immutable.EntityUpdatedAtImp
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAt
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAtImp
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
open class Comment <T : UuidEntity> (
|
open class Comment<T : TargetI>(
|
||||||
id: UUID = UUID.randomUUID(),
|
id: UUID = UUID.randomUUID(),
|
||||||
createdBy: Citizen,
|
override val createdBy: CitizenBasic,
|
||||||
target: T,
|
target: T,
|
||||||
override var targetReference: String = target::class.simpleName!!.toLowerCase(),
|
|
||||||
var content: String,
|
var content: String,
|
||||||
val responses: List<Comment<T>>? = null,
|
val responses: List<Comment<T>>? = null,
|
||||||
var parent: Comment<T>? = null,
|
var parent: Comment<T>? = null,
|
||||||
@@ -16,17 +18,18 @@ open class Comment <T : UuidEntity> (
|
|||||||
) : Extra<T>(id, createdBy, target),
|
) : Extra<T>(id, createdBy, target),
|
||||||
EntityUpdatedAt by EntityUpdatedAtImp(),
|
EntityUpdatedAt by EntityUpdatedAtImp(),
|
||||||
EntityDeletedAt by EntityDeletedAtImp(),
|
EntityDeletedAt by EntityDeletedAtImp(),
|
||||||
Votable by VotableImp()
|
Votable by VotableImp(),
|
||||||
{
|
TargetI {
|
||||||
constructor(
|
constructor(
|
||||||
createdBy: Citizen,
|
createdBy: CitizenBasic,
|
||||||
parent: Comment<T>,
|
parent: Comment<T>,
|
||||||
content: String
|
content: String
|
||||||
) : this(
|
) : this(
|
||||||
createdBy = createdBy,
|
createdBy = createdBy,
|
||||||
parent = parent,
|
parent = parent,
|
||||||
target = parent.target,
|
target = parent.target,
|
||||||
targetReference = parent.targetReference,
|
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override val reference get() = TargetI.getReference(this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,69 @@
|
|||||||
package fr.dcproject.entity
|
package fr.dcproject.entity
|
||||||
|
|
||||||
import fr.postgresjson.entity.mutable.*
|
import fr.postgresjson.entity.immutable.*
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAt
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAtImp
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class Constitution(
|
class Constitution(
|
||||||
id: UUID = UUID.randomUUID(),
|
id: UUID = UUID.randomUUID(),
|
||||||
var title: String?,
|
title: String,
|
||||||
var anonymous: Boolean? = true,
|
anonymous: Boolean = true,
|
||||||
var titles: List<Title> = listOf(),
|
titles: MutableList<TitleSimple<ArticleSimple>> = mutableListOf(),
|
||||||
|
draft: Boolean = false,
|
||||||
|
lastVersion: Boolean = false,
|
||||||
|
override val createdBy: CitizenSimple
|
||||||
|
) : ConstitutionSimple<CitizenSimple, ConstitutionSimple.TitleSimple<ArticleSimple>>(
|
||||||
|
id,
|
||||||
|
title = title,
|
||||||
|
anonymous = anonymous,
|
||||||
|
titles = titles,
|
||||||
|
draft = draft,
|
||||||
|
lastVersion = lastVersion,
|
||||||
|
createdBy = createdBy
|
||||||
|
) {
|
||||||
|
|
||||||
|
class Title(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
name: String,
|
||||||
|
rank: Int? = null,
|
||||||
|
override val articles: MutableList<ArticleSimple> = mutableListOf()
|
||||||
|
) : ConstitutionSimple.TitleSimple<ArticleSimple>(id, name, rank)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class ConstitutionSimple<Cr : CitizenRef, T : ConstitutionSimple.TitleSimple<*>>(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
var title: String,
|
||||||
|
var anonymous: Boolean = true,
|
||||||
|
open var titles: MutableList<T> = mutableListOf(),
|
||||||
var draft: Boolean = false,
|
var draft: Boolean = false,
|
||||||
var lastVersion: Boolean = false,
|
var lastVersion: Boolean = false,
|
||||||
createdBy: Citizen?
|
override val createdBy: Cr,
|
||||||
) : UuidEntity(id),
|
versionId: UUID = UUID.randomUUID()
|
||||||
EntityVersioning<UUID, Int> by UuidEntityVersioning(),
|
) : ConstitutionRef(id),
|
||||||
|
EntityVersioning<UUID, Int?> by UuidEntityVersioning(versionId = versionId),
|
||||||
EntityCreatedAt by EntityCreatedAtImp(),
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
EntityCreatedBy<Citizen> by EntityCreatedByImp(createdBy),
|
EntityCreatedBy<Cr> by EntityCreatedByImp(createdBy),
|
||||||
EntityDeletedAt by EntityDeletedAtImp() {
|
EntityDeletedAt by EntityDeletedAtImp() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
titles.forEachIndexed { index, title ->
|
titles.forEachIndexed { index, title ->
|
||||||
title.createdBy = this.createdBy
|
|
||||||
title.rank = index
|
title.rank = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Title(
|
open class TitleSimple<A : ArticleI>(
|
||||||
id: UUID = UUID.randomUUID(),
|
id: UUID = UUID.randomUUID(),
|
||||||
var name: String?,
|
var name: String,
|
||||||
var rank: Int? = null,
|
var rank: Int? = null,
|
||||||
var articles: List<Article> = listOf(),
|
open val articles: MutableList<A> = mutableListOf()
|
||||||
createdBy: Citizen? = null
|
) : TitleRef(id)
|
||||||
) : UuidEntity(id),
|
|
||||||
EntityCreatedAt by EntityCreatedAtImp(),
|
|
||||||
EntityCreatedBy<Citizen> by EntityCreatedByImp(createdBy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open class ConstitutionRef(id: UUID = UUID.randomUUID()) : ConstitutionS(id) {
|
||||||
|
open class TitleRef(
|
||||||
|
id: UUID = UUID.randomUUID()
|
||||||
|
) : UuidEntity(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ConstitutionS(id: UUID = UUID.randomUUID()) : TargetRef(id), TargetI
|
||||||
@@ -1,24 +1,62 @@
|
|||||||
package fr.dcproject.entity
|
package fr.dcproject.entity
|
||||||
|
|
||||||
import fr.postgresjson.entity.EntityI
|
import fr.postgresjson.entity.immutable.*
|
||||||
import fr.postgresjson.entity.mutable.*
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.full.isSuperclassOf
|
||||||
|
|
||||||
interface ExtraI <T : EntityI> :
|
interface ExtraI<T : TargetI> :
|
||||||
EntityI,
|
UuidEntityI,
|
||||||
EntityCreatedAt,
|
EntityCreatedAt,
|
||||||
EntityCreatedBy<Citizen> {
|
EntityCreatedBy<CitizenBasicI> {
|
||||||
var target: T
|
var target: T
|
||||||
var targetReference: String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class Extra<T : UuidEntity>(
|
abstract class Extra<T : TargetI>(
|
||||||
id: UUID? = UUID.randomUUID(),
|
id: UUID = UUID.randomUUID(),
|
||||||
createdBy: Citizen,
|
override val createdBy: CitizenBasic,
|
||||||
override var target: T,
|
override var target: T
|
||||||
override var targetReference: String = target::class.simpleName!!.toLowerCase()
|
|
||||||
) :
|
) :
|
||||||
ExtraI<T>,
|
ExtraI<T>,
|
||||||
UuidEntity(id),
|
UuidEntity(id),
|
||||||
EntityCreatedAt by EntityCreatedAtImp(),
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
EntityCreatedBy<Citizen> by EntityCreatedByImp(createdBy)
|
EntityCreatedBy<CitizenBasicI> by EntityCreatedByImp(createdBy)
|
||||||
|
|
||||||
|
open class TargetRef(id: UUID = UUID.randomUUID()) : TargetI, UuidEntity(id) {
|
||||||
|
override val reference: String = ""
|
||||||
|
get() {
|
||||||
|
return if (field != "") field else TargetI.getReference(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TargetI : UuidEntityI {
|
||||||
|
enum class TargetName(val targetReference: String) {
|
||||||
|
Article("article"),
|
||||||
|
Constitution("constitution"),
|
||||||
|
Comment("comment")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun <T : TargetI> getReference(t: KClass<T>): String {
|
||||||
|
return when {
|
||||||
|
t.isSuperclassOf(Article::class) -> TargetName.Article.targetReference
|
||||||
|
t.isSuperclassOf(Constitution::class) -> TargetName.Constitution.targetReference
|
||||||
|
t.isSuperclassOf(Comment::class) -> TargetName.Comment.targetReference
|
||||||
|
else -> throw error("target not implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getReference(t: TargetI): String {
|
||||||
|
val ref = this.getReference(t::class)
|
||||||
|
return if (t is ExtraI<*>) {
|
||||||
|
ref +
|
||||||
|
"_on_" +
|
||||||
|
t.target.reference
|
||||||
|
} else {
|
||||||
|
ref
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val reference: String
|
||||||
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
package fr.dcproject.entity
|
package fr.dcproject.entity
|
||||||
import fr.postgresjson.entity.mutable.UuidEntity
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class Follow <T : UuidEntity> (
|
class Follow <T : TargetI> (
|
||||||
id: UUID = UUID.randomUUID(),
|
id: UUID = UUID.randomUUID(),
|
||||||
createdBy: Citizen,
|
override val createdBy: CitizenBasic,
|
||||||
target: T
|
target: T
|
||||||
) : Extra<T>(id, createdBy, target)
|
) : Extra<T>(id, createdBy, target)
|
||||||
|
|||||||
@@ -1,19 +1,41 @@
|
|||||||
package fr.dcproject.entity
|
package fr.dcproject.entity
|
||||||
|
|
||||||
import fr.postgresjson.entity.mutable.*
|
import fr.dcproject.entity.UserI.Roles
|
||||||
|
import fr.postgresjson.entity.immutable.*
|
||||||
import io.ktor.auth.Principal
|
import io.ktor.auth.Principal
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class User(
|
class User(
|
||||||
id: UUID? = UUID.randomUUID(),
|
id: UUID = UUID.randomUUID(),
|
||||||
var username: String?,
|
username: String,
|
||||||
var blockedAt: DateTime? = null,
|
blockedAt: DateTime? = null,
|
||||||
var plainPassword: String?,
|
override var plainPassword: String?,
|
||||||
var roles: List<Roles> = emptyList()
|
override var roles: List<Roles> = emptyList()
|
||||||
) : UuidEntity(id),
|
) : UserFull, UserBasic(id, username, blockedAt),
|
||||||
EntityCreatedAt by EntityCreatedAtImp(),
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
EntityUpdatedAt by EntityUpdatedAtImp(),
|
EntityUpdatedAt by EntityUpdatedAtImp()
|
||||||
Principal {
|
|
||||||
|
open class UserBasic(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
override var username: String,
|
||||||
|
override var blockedAt: DateTime? = null
|
||||||
|
) : UserBasicI, UserRef(id)
|
||||||
|
|
||||||
|
open class UserRef(
|
||||||
|
id: UUID = UUID.randomUUID()
|
||||||
|
) : UserI, UuidEntity(id)
|
||||||
|
|
||||||
|
interface UserI : UuidEntityI, Principal {
|
||||||
enum class Roles { ROLE_USER, ROLE_ADMIN }
|
enum class Roles { ROLE_USER, ROLE_ADMIN }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UserBasicI : UserI {
|
||||||
|
var username: String
|
||||||
|
var blockedAt: DateTime?
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserFull : UserBasicI, EntityCreatedAt, EntityUpdatedAt {
|
||||||
|
var plainPassword: String?
|
||||||
|
var roles: List<Roles>
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
package fr.dcproject.entity
|
package fr.dcproject.entity
|
||||||
|
|
||||||
import fr.postgresjson.entity.mutable.EntityUpdatedAt
|
import fr.postgresjson.entity.immutable.EntityUpdatedAt
|
||||||
import fr.postgresjson.entity.mutable.EntityUpdatedAtImp
|
import fr.postgresjson.entity.immutable.EntityUpdatedAtImp
|
||||||
import fr.postgresjson.entity.mutable.UuidEntity
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
open class Vote <T : UuidEntity> (
|
open class Vote <T : TargetI> (
|
||||||
id: UUID = UUID.randomUUID(),
|
id: UUID = UUID.randomUUID(),
|
||||||
createdBy: Citizen,
|
override val createdBy: CitizenBasic,
|
||||||
target: T,
|
target: T,
|
||||||
var note: Int,
|
var note: Int,
|
||||||
var anonymous: Boolean = true
|
var anonymous: Boolean = true
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package fr.dcproject.entity.request
|
package fr.dcproject.entity.request
|
||||||
|
|
||||||
|
import fr.dcproject.entity.ArticleFull
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.Citizen
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import fr.dcproject.entity.Article as ArticleEntity
|
import fr.dcproject.entity.Article as ArticleEntity
|
||||||
@@ -7,7 +8,7 @@ import fr.dcproject.entity.Article as ArticleEntity
|
|||||||
class Article(
|
class Article(
|
||||||
val id: UUID?,
|
val id: UUID?,
|
||||||
val title: String,
|
val title: String,
|
||||||
val anonymous: Boolean? = true,
|
val anonymous: Boolean = true,
|
||||||
val content: String,
|
val content: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
val tags: List<String> = emptyList(),
|
val tags: List<String> = emptyList(),
|
||||||
@@ -16,7 +17,7 @@ class Article(
|
|||||||
) :
|
) :
|
||||||
Request {
|
Request {
|
||||||
|
|
||||||
fun merge(article: ArticleEntity) {
|
fun merge(article: ArticleFull) {
|
||||||
article.title = this.title
|
article.title = this.title
|
||||||
article.content = this.content
|
article.content = this.content
|
||||||
article.description = this.description
|
article.description = this.description
|
||||||
|
|||||||
48
src/main/kotlin/fr/dcproject/entity/request/Constitution.kt
Normal file
48
src/main/kotlin/fr/dcproject/entity/request/Constitution.kt
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package fr.dcproject.entity.request
|
||||||
|
|
||||||
|
import fr.dcproject.entity.ArticleRef
|
||||||
|
import fr.dcproject.entity.Citizen
|
||||||
|
import fr.dcproject.entity.CitizenSimple
|
||||||
|
import fr.dcproject.entity.ConstitutionSimple
|
||||||
|
import fr.postgresjson.entity.immutable.UuidEntity
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class Constitution(
|
||||||
|
var title: String,
|
||||||
|
var anonymous: Boolean = true,
|
||||||
|
var titles: MutableList<Title> = mutableListOf(),
|
||||||
|
var draft: Boolean = false,
|
||||||
|
var lastVersion: Boolean = false,
|
||||||
|
var versionId: UUID = UUID.randomUUID()
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
titles.forEachIndexed { index, title ->
|
||||||
|
title.rank = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Title(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
var name: String,
|
||||||
|
var rank: Int? = null,
|
||||||
|
var articles: MutableList<ArticleRef> = mutableListOf()
|
||||||
|
) : UuidEntity(id) {
|
||||||
|
fun create(): ConstitutionSimple.TitleSimple<ArticleRef> {
|
||||||
|
return ConstitutionSimple.TitleSimple(
|
||||||
|
id, name, rank, articles
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun create(createdBy: Citizen): ConstitutionSimple<CitizenSimple, ConstitutionSimple.TitleSimple<ArticleRef>> {
|
||||||
|
return ConstitutionSimple(
|
||||||
|
title = title,
|
||||||
|
titles = titles.create(),
|
||||||
|
createdBy = createdBy,
|
||||||
|
versionId = versionId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun List<Constitution.Title>.create(): MutableList<ConstitutionSimple.TitleSimple<ArticleRef>> =
|
||||||
|
map { it.create() }.toMutableList()
|
||||||
@@ -4,8 +4,8 @@ import com.sendgrid.helpers.mail.Mail
|
|||||||
import com.sendgrid.helpers.mail.objects.Content
|
import com.sendgrid.helpers.mail.objects.Content
|
||||||
import com.sendgrid.helpers.mail.objects.Email
|
import com.sendgrid.helpers.mail.objects.Email
|
||||||
import fr.dcproject.JwtConfig
|
import fr.dcproject.JwtConfig
|
||||||
|
import fr.dcproject.entity.CitizenBasicI
|
||||||
import io.ktor.http.URLBuilder
|
import io.ktor.http.URLBuilder
|
||||||
import fr.dcproject.entity.Citizen as CitizenEntity
|
|
||||||
import fr.dcproject.repository.Citizen as CitizenRepository
|
import fr.dcproject.repository.Citizen as CitizenRepository
|
||||||
|
|
||||||
class SsoManager(
|
class SsoManager(
|
||||||
@@ -15,11 +15,15 @@ class SsoManager(
|
|||||||
) {
|
) {
|
||||||
fun sendMail(email: String, url: String) {
|
fun sendMail(email: String, url: String) {
|
||||||
val citizen = citizenRepo.findByEmail(email) ?: noEmail(email)
|
val citizen = citizenRepo.findByEmail(email) ?: noEmail(email)
|
||||||
|
sendMail(citizen, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendMail(citizen: CitizenBasicI, url: String) {
|
||||||
mailer.sendEmail {
|
mailer.sendEmail {
|
||||||
Mail(
|
Mail(
|
||||||
Email("sso@$domain"),
|
Email("sso@$domain"),
|
||||||
"Connection",
|
"Connection",
|
||||||
Email(email),
|
Email(citizen.email),
|
||||||
Content("text/plain", generateContent(citizen, url))
|
Content("text/plain", generateContent(citizen, url))
|
||||||
).apply {
|
).apply {
|
||||||
addContent(Content("text/html", generateHtmlContent(citizen, url)))
|
addContent(Content("text/html", generateHtmlContent(citizen, url)))
|
||||||
@@ -27,15 +31,15 @@ class SsoManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateHtmlContent(citizen: CitizenEntity, url: String): String? {
|
private fun generateHtmlContent(citizen: CitizenBasicI, url: String): String? {
|
||||||
val urlObject = URLBuilder(url)
|
val urlObject = URLBuilder(url)
|
||||||
urlObject.parameters.append("token", JwtConfig.makeToken(citizen.user ?: error("Citizen must have User")))
|
urlObject.parameters.append("token", JwtConfig.makeToken(citizen.user))
|
||||||
return "Click <a href=\"${urlObject.buildString()}\">here</a> for connect to $domain"
|
return "Click <a href=\"${urlObject.buildString()}\">here</a> for connect to $domain"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateContent(citizen: CitizenEntity, url: String): String {
|
private fun generateContent(citizen: CitizenBasicI, url: String): String {
|
||||||
val urlObject = URLBuilder(url)
|
val urlObject = URLBuilder(url)
|
||||||
urlObject.parameters.append("token", JwtConfig.makeToken(citizen.user ?: error("Citizen must have User")))
|
urlObject.parameters.append("token", JwtConfig.makeToken(citizen.user))
|
||||||
return "Copy this link into your browser for connect to $domain: \n${urlObject.buildString()}"
|
return "Copy this link into your browser for connect to $domain: \n${urlObject.buildString()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package fr.dcproject.repository
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.dcproject.entity.ArticleFull
|
||||||
|
import fr.dcproject.entity.ArticleSimple
|
||||||
import fr.postgresjson.connexion.Paginated
|
import fr.postgresjson.connexion.Paginated
|
||||||
import fr.postgresjson.connexion.Requester
|
import fr.postgresjson.connexion.Requester
|
||||||
import fr.postgresjson.entity.Parameter
|
import fr.postgresjson.entity.Parameter
|
||||||
@@ -28,7 +30,7 @@ class Article(override var requester: Requester) : RepositoryI {
|
|||||||
direction: Direction? = null,
|
direction: Direction? = null,
|
||||||
search: String? = null,
|
search: String? = null,
|
||||||
filter: Filter = Filter()
|
filter: Filter = Filter()
|
||||||
): Paginated<ArticleEntity> {
|
): Paginated<ArticleSimple> {
|
||||||
return requester
|
return requester
|
||||||
.getFunction("find_articles")
|
.getFunction("find_articles")
|
||||||
.select(
|
.select(
|
||||||
@@ -40,7 +42,7 @@ class Article(override var requester: Requester) : RepositoryI {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upsert(article: ArticleEntity): ArticleEntity? {
|
fun upsert(article: ArticleFull): ArticleEntity? {
|
||||||
return requester
|
return requester
|
||||||
.getFunction("upsert_article")
|
.getFunction("upsert_article")
|
||||||
.selectOne("resource" to article)
|
.selectOne("resource" to article)
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package fr.dcproject.repository
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.dcproject.entity.CitizenBasic
|
||||||
|
import fr.dcproject.entity.CitizenFull
|
||||||
|
import fr.dcproject.entity.UserI
|
||||||
import fr.postgresjson.connexion.Paginated
|
import fr.postgresjson.connexion.Paginated
|
||||||
import fr.postgresjson.connexion.Requester
|
import fr.postgresjson.connexion.Requester
|
||||||
import fr.postgresjson.repository.RepositoryI
|
import fr.postgresjson.repository.RepositoryI
|
||||||
@@ -7,7 +10,6 @@ import fr.postgresjson.repository.RepositoryI.Direction
|
|||||||
import net.pearx.kasechange.toSnakeCase
|
import net.pearx.kasechange.toSnakeCase
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import fr.dcproject.entity.Citizen as CitizenEntity
|
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||||
import fr.dcproject.entity.User as UserEntity
|
|
||||||
|
|
||||||
class Citizen(override var requester: Requester) : RepositoryI {
|
class Citizen(override var requester: Requester) : RepositoryI {
|
||||||
fun findById(id: UUID, withUser: Boolean = false): CitizenEntity? {
|
fun findById(id: UUID, withUser: Boolean = false): CitizenEntity? {
|
||||||
@@ -16,7 +18,7 @@ class Citizen(override var requester: Requester) : RepositoryI {
|
|||||||
.selectOne("id" to id)
|
.selectOne("id" to id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findByUser(user: UserEntity): CitizenEntity? {
|
fun findByUser(user: UserI): CitizenEntity? {
|
||||||
return requester
|
return requester
|
||||||
.getFunction("find_citizen_by_user_id")
|
.getFunction("find_citizen_by_user_id")
|
||||||
.selectOne("user_id" to user.id)
|
.selectOne("user_id" to user.id)
|
||||||
@@ -40,7 +42,7 @@ class Citizen(override var requester: Requester) : RepositoryI {
|
|||||||
sort: String? = null,
|
sort: String? = null,
|
||||||
direction: Direction? = null,
|
direction: Direction? = null,
|
||||||
search: String? = null
|
search: String? = null
|
||||||
): Paginated<CitizenEntity> {
|
): Paginated<CitizenBasic> {
|
||||||
return requester
|
return requester
|
||||||
.getFunction("find_citizens")
|
.getFunction("find_citizens")
|
||||||
.select(
|
.select(
|
||||||
@@ -51,13 +53,13 @@ class Citizen(override var requester: Requester) : RepositoryI {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upsert(citizen: CitizenEntity): CitizenEntity? {
|
fun upsert(citizen: CitizenFull): CitizenEntity? {
|
||||||
return requester
|
return requester
|
||||||
.getFunction("upsert_citizen")
|
.getFunction("upsert_citizen")
|
||||||
.selectOne("resource" to citizen)
|
.selectOne("resource" to citizen)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun insertWithUser(citizen: CitizenEntity): CitizenEntity? {
|
fun insertWithUser(citizen: CitizenFull): CitizenEntity? {
|
||||||
return requester
|
return requester
|
||||||
.getFunction("insert_citizen_with_user")
|
.getFunction("insert_citizen_with_user")
|
||||||
.selectOne("resource" to citizen)
|
.selectOne("resource" to citizen)
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
package fr.dcproject.repository
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.dcproject.entity.ArticleRef
|
||||||
|
import fr.dcproject.entity.TargetI
|
||||||
|
import fr.dcproject.entity.TargetRef
|
||||||
import fr.postgresjson.connexion.Paginated
|
import fr.postgresjson.connexion.Paginated
|
||||||
import fr.postgresjson.connexion.Requester
|
import fr.postgresjson.connexion.Requester
|
||||||
import fr.postgresjson.entity.mutable.UuidEntity
|
import fr.postgresjson.entity.immutable.UuidEntityI
|
||||||
import fr.postgresjson.repository.RepositoryI
|
import fr.postgresjson.repository.RepositoryI
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import fr.dcproject.entity.Article as ArticleEntity
|
|
||||||
import fr.dcproject.entity.Citizen as CitizenEntity
|
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||||
import fr.dcproject.entity.Comment as CommentEntity
|
import fr.dcproject.entity.Comment as CommentEntity
|
||||||
import fr.dcproject.entity.Constitution as ConstitutionEntity
|
import fr.dcproject.entity.Constitution as ConstitutionEntity
|
||||||
|
|
||||||
abstract class Comment <T : UuidEntity>(override var requester: Requester) : RepositoryI {
|
abstract class Comment <T : TargetI>(override var requester: Requester) : RepositoryI {
|
||||||
abstract fun findById(id: UUID): CommentEntity<T>?
|
abstract fun findById(id: UUID): CommentEntity<T>?
|
||||||
|
|
||||||
abstract fun findByCitizen(
|
abstract fun findByCitizen(
|
||||||
@@ -24,7 +26,7 @@ abstract class Comment <T : UuidEntity>(override var requester: Requester) : Rep
|
|||||||
page: Int = 1,
|
page: Int = 1,
|
||||||
limit: Int = 50
|
limit: Int = 50
|
||||||
): Paginated<CommentEntity<T>> {
|
): Paginated<CommentEntity<T>> {
|
||||||
return findByParent(parent.id ?: error("comment must have an ID"), page, limit)
|
return findByParent(parent.id, page, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun findByParent(
|
open fun findByParent(
|
||||||
@@ -41,11 +43,11 @@ abstract class Comment <T : UuidEntity>(override var requester: Requester) : Rep
|
|||||||
}
|
}
|
||||||
|
|
||||||
open fun findByTarget(
|
open fun findByTarget(
|
||||||
target: UuidEntity,
|
target: UuidEntityI,
|
||||||
page: Int = 1,
|
page: Int = 1,
|
||||||
limit: Int = 50
|
limit: Int = 50
|
||||||
): Paginated<CommentEntity<T>> {
|
): Paginated<CommentEntity<T>> {
|
||||||
return findByTarget(target.id ?: error("comment must have an ID"), page, limit)
|
return findByTarget(target.id, page, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun findByTarget(
|
open fun findByTarget(
|
||||||
@@ -65,7 +67,7 @@ abstract class Comment <T : UuidEntity>(override var requester: Requester) : Rep
|
|||||||
requester
|
requester
|
||||||
.getFunction("comment")
|
.getFunction("comment")
|
||||||
.sendQuery(
|
.sendQuery(
|
||||||
"reference" to comment.targetReference,
|
"reference" to comment.target.reference,
|
||||||
"resource" to comment
|
"resource" to comment
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -80,8 +82,8 @@ abstract class Comment <T : UuidEntity>(override var requester: Requester) : Rep
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CommentGeneric(requester: Requester) : Comment<UuidEntity>(requester) {
|
class CommentGeneric(requester: Requester) : Comment<TargetRef>(requester) {
|
||||||
override fun findById(id: UUID): CommentEntity<UuidEntity>? {
|
override fun findById(id: UUID): CommentEntity<TargetRef>? {
|
||||||
return requester
|
return requester
|
||||||
.getFunction("find_comment_by_id")
|
.getFunction("find_comment_by_id")
|
||||||
.selectOne(mapOf("id" to id))
|
.selectOne(mapOf("id" to id))
|
||||||
@@ -91,7 +93,7 @@ class CommentGeneric(requester: Requester) : Comment<UuidEntity>(requester) {
|
|||||||
citizen: CitizenEntity,
|
citizen: CitizenEntity,
|
||||||
page: Int,
|
page: Int,
|
||||||
limit: Int
|
limit: Int
|
||||||
): Paginated<CommentEntity<UuidEntity>> {
|
): Paginated<CommentEntity<TargetRef>> {
|
||||||
return requester.run {
|
return requester.run {
|
||||||
getFunction("find_comments_by_citizen")
|
getFunction("find_comments_by_citizen")
|
||||||
.select(page, limit,
|
.select(page, limit,
|
||||||
@@ -101,8 +103,8 @@ class CommentGeneric(requester: Requester) : Comment<UuidEntity>(requester) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CommentArticle(requester: Requester) : Comment<ArticleEntity>(requester) {
|
class CommentArticle(requester: Requester) : Comment<ArticleRef>(requester) {
|
||||||
override fun findById(id: UUID): CommentEntity<ArticleEntity>? {
|
override fun findById(id: UUID): CommentEntity<ArticleRef>? {
|
||||||
return requester
|
return requester
|
||||||
.getFunction("find_comment_by_id")
|
.getFunction("find_comment_by_id")
|
||||||
.selectOne(mapOf("id" to id))
|
.selectOne(mapOf("id" to id))
|
||||||
@@ -112,13 +114,12 @@ class CommentArticle(requester: Requester) : Comment<ArticleEntity>(requester) {
|
|||||||
citizen: CitizenEntity,
|
citizen: CitizenEntity,
|
||||||
page: Int,
|
page: Int,
|
||||||
limit: Int
|
limit: Int
|
||||||
): Paginated<CommentEntity<ArticleEntity>> {
|
): Paginated<CommentEntity<ArticleRef>> {
|
||||||
val reference = ArticleEntity::class.simpleName!!.toLowerCase()
|
|
||||||
return requester.run {
|
return requester.run {
|
||||||
getFunction("find_comments_by_citizen")
|
getFunction("find_comments_by_citizen")
|
||||||
.select(page, limit,
|
.select(page, limit,
|
||||||
"created_by_id" to citizen.id,
|
"created_by_id" to citizen.id,
|
||||||
"reference" to reference
|
"reference" to TargetI.getReference(ArticleRef::class)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,12 +137,11 @@ class CommentConstitution(requester: Requester) : Comment<ConstitutionEntity>(re
|
|||||||
page: Int,
|
page: Int,
|
||||||
limit: Int
|
limit: Int
|
||||||
): Paginated<CommentEntity<ConstitutionEntity>> {
|
): Paginated<CommentEntity<ConstitutionEntity>> {
|
||||||
val reference = ConstitutionEntity::class.simpleName!!.toLowerCase()
|
|
||||||
return requester.run {
|
return requester.run {
|
||||||
getFunction("find_comments_by_citizen")
|
getFunction("find_comments_by_citizen")
|
||||||
.select(page, limit,
|
.select(page, limit,
|
||||||
"created_by_id" to citizen.id,
|
"created_by_id" to citizen.id,
|
||||||
"reference" to reference
|
"reference" to TargetI.getReference(ConstitutionEntity::class)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package fr.dcproject.repository
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.dcproject.entity.ArticleRef
|
||||||
|
import fr.dcproject.entity.CitizenSimple
|
||||||
|
import fr.dcproject.entity.ConstitutionSimple
|
||||||
import fr.postgresjson.connexion.Paginated
|
import fr.postgresjson.connexion.Paginated
|
||||||
import fr.postgresjson.connexion.Requester
|
import fr.postgresjson.connexion.Requester
|
||||||
import fr.postgresjson.repository.RepositoryI
|
import fr.postgresjson.repository.RepositoryI
|
||||||
@@ -31,7 +34,7 @@ class Constitution(override var requester: Requester) : RepositoryI {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upsert(constitution: ConstitutionEntity): ConstitutionEntity? {
|
fun upsert(constitution: ConstitutionSimple<CitizenSimple, ConstitutionSimple.TitleSimple<ArticleRef>>): ConstitutionEntity? {
|
||||||
return requester
|
return requester
|
||||||
.getFunction("upsert_constitution")
|
.getFunction("upsert_constitution")
|
||||||
.selectOne("resource" to constitution)
|
.selectOne("resource" to constitution)
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
package fr.dcproject.repository
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.dcproject.entity.CitizenI
|
||||||
|
import fr.dcproject.entity.TargetI
|
||||||
import fr.postgresjson.connexion.Paginated
|
import fr.postgresjson.connexion.Paginated
|
||||||
import fr.postgresjson.connexion.Requester
|
import fr.postgresjson.connexion.Requester
|
||||||
import fr.postgresjson.entity.mutable.UuidEntity
|
|
||||||
import fr.postgresjson.repository.RepositoryI
|
import fr.postgresjson.repository.RepositoryI
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import fr.dcproject.entity.Article as ArticleEntity
|
import fr.dcproject.entity.Article as ArticleEntity
|
||||||
import fr.dcproject.entity.Citizen as CitizenEntity
|
|
||||||
import fr.dcproject.entity.Constitution as ConstitutionEntity
|
import fr.dcproject.entity.Constitution as ConstitutionEntity
|
||||||
import fr.dcproject.entity.Follow as FollowEntity
|
import fr.dcproject.entity.Follow as FollowEntity
|
||||||
|
|
||||||
open class Follow <T : UuidEntity>(override var requester: Requester) : RepositoryI {
|
open class Follow <T : TargetI>(override var requester: Requester) : RepositoryI {
|
||||||
open fun findByCitizen(
|
open fun findByCitizen(
|
||||||
citizen: CitizenEntity,
|
citizen: CitizenI,
|
||||||
page: Int = 1,
|
page: Int = 1,
|
||||||
limit: Int = 50
|
limit: Int = 50
|
||||||
): Paginated<FollowEntity<T>> =
|
): Paginated<FollowEntity<T>> =
|
||||||
findByCitizen(citizen.id ?: error("The citizen must have an id"), page, limit)
|
findByCitizen(citizen.id, page, limit)
|
||||||
|
|
||||||
open fun findByCitizen(
|
open fun findByCitizen(
|
||||||
citizenId: UUID,
|
citizenId: UUID,
|
||||||
@@ -32,24 +32,22 @@ open class Follow <T : UuidEntity>(override var requester: Requester) : Reposito
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun follow(follow: FollowEntity<T>) {
|
fun follow(follow: FollowEntity<T>) {
|
||||||
val reference = follow.target::class.simpleName!!.toLowerCase()
|
|
||||||
requester
|
requester
|
||||||
.getFunction("follow")
|
.getFunction("follow")
|
||||||
.sendQuery(
|
.sendQuery(
|
||||||
"reference" to reference,
|
"reference" to follow.target.reference,
|
||||||
"target_id" to follow.target.id,
|
"target_id" to follow.target.id,
|
||||||
"created_by_id" to follow.createdBy?.id
|
"created_by_id" to follow.createdBy.id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unfollow(follow: FollowEntity<T>) {
|
fun unfollow(follow: FollowEntity<T>) {
|
||||||
val reference = follow.target::class.simpleName!!.toLowerCase()
|
|
||||||
requester
|
requester
|
||||||
.getFunction("unfollow")
|
.getFunction("unfollow")
|
||||||
.sendQuery(
|
.sendQuery(
|
||||||
"reference" to reference,
|
"reference" to follow.target.reference,
|
||||||
"target_id" to follow.target.id,
|
"target_id" to follow.target.id,
|
||||||
"created_by_id" to follow.createdBy?.id
|
"created_by_id" to follow.createdBy.id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package fr.dcproject.repository
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.dcproject.entity.UserFull
|
||||||
import fr.postgresjson.connexion.Requester
|
import fr.postgresjson.connexion.Requester
|
||||||
import fr.postgresjson.repository.RepositoryI
|
import fr.postgresjson.repository.RepositoryI
|
||||||
import io.ktor.auth.UserPasswordCredential
|
import io.ktor.auth.UserPasswordCredential
|
||||||
@@ -30,7 +31,7 @@ class User(override var requester: Requester) : RepositoryI {
|
|||||||
.selectOne("resource" to user)
|
.selectOne("resource" to user)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changePassword(user: UserEntity) {
|
fun changePassword(user: UserFull) {
|
||||||
requester
|
requester
|
||||||
.getFunction("change_user_password")
|
.getFunction("change_user_password")
|
||||||
.sendQuery("resource" to user)
|
.sendQuery("resource" to user)
|
||||||
|
|||||||
@@ -1,34 +1,25 @@
|
|||||||
package fr.dcproject.repository
|
package fr.dcproject.repository
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference
|
import com.fasterxml.jackson.core.type.TypeReference
|
||||||
|
import fr.dcproject.entity.*
|
||||||
import fr.dcproject.entity.Article
|
import fr.dcproject.entity.Article
|
||||||
import fr.dcproject.entity.Comment
|
import fr.dcproject.entity.Comment
|
||||||
import fr.dcproject.entity.Constitution
|
import fr.dcproject.entity.Constitution
|
||||||
import fr.dcproject.entity.VoteAggregation
|
|
||||||
import fr.postgresjson.connexion.Paginated
|
import fr.postgresjson.connexion.Paginated
|
||||||
import fr.postgresjson.connexion.Requester
|
import fr.postgresjson.connexion.Requester
|
||||||
import fr.postgresjson.entity.mutable.UuidEntity
|
|
||||||
import fr.postgresjson.repository.RepositoryI
|
import fr.postgresjson.repository.RepositoryI
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import fr.dcproject.entity.Citizen as CitizenEntity
|
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||||
import fr.dcproject.entity.Vote as VoteEntity
|
import fr.dcproject.entity.Vote as VoteEntity
|
||||||
|
|
||||||
open class Vote <T : UuidEntity>(override var requester: Requester) : RepositoryI {
|
open class Vote <T : TargetI>(override var requester: Requester) : RepositoryI {
|
||||||
fun vote(vote: VoteEntity<T>): VoteAggregation {
|
fun vote(vote: VoteEntity<T>): VoteAggregation {
|
||||||
val target = vote.target
|
val author = vote.createdBy
|
||||||
val reference = if (target is Comment<*>) {
|
|
||||||
target::class.simpleName!!.toLowerCase() +
|
|
||||||
"_on_" +
|
|
||||||
target.targetReference
|
|
||||||
} else {
|
|
||||||
target::class.simpleName!!.toLowerCase()
|
|
||||||
}
|
|
||||||
val author = vote.createdBy ?: error("vote must be contain an author")
|
|
||||||
val anonymous = author.voteAnonymous
|
val anonymous = author.voteAnonymous
|
||||||
return requester
|
return requester
|
||||||
.getFunction("vote")
|
.getFunction("vote")
|
||||||
.selectOne(
|
.selectOne(
|
||||||
"reference" to reference,
|
"reference" to vote.target.reference,
|
||||||
"target_id" to vote.target.id,
|
"target_id" to vote.target.id,
|
||||||
"note" to vote.note,
|
"note" to vote.note,
|
||||||
"created_by_id" to author.id,
|
"created_by_id" to author.id,
|
||||||
@@ -56,12 +47,11 @@ open class Vote <T : UuidEntity>(override var requester: Requester) : Repository
|
|||||||
citizen: CitizenEntity,
|
citizen: CitizenEntity,
|
||||||
targets: List<UUID>
|
targets: List<UUID>
|
||||||
): List<VoteEntity<*>> {
|
): List<VoteEntity<*>> {
|
||||||
val typeReference = object : TypeReference<List<VoteEntity<UuidEntity>>>() {}
|
val typeReference = object : TypeReference<List<VoteEntity<TargetRef>>>() {}
|
||||||
return requester.run {
|
return requester.run {
|
||||||
val citizenId = citizen.id ?: error("The citizen must have an id")
|
|
||||||
getFunction("find_citizen_votes_by_target_ids")
|
getFunction("find_citizen_votes_by_target_ids")
|
||||||
.select(typeReference, mapOf(
|
.select(typeReference, mapOf(
|
||||||
"citizen_id" to citizenId,
|
"citizen_id" to citizen.id,
|
||||||
"ids" to targets
|
"ids" to targets
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -75,7 +65,7 @@ class VoteArticle(requester: Requester) : Vote<Article>(requester) {
|
|||||||
limit: Int = 50
|
limit: Int = 50
|
||||||
): Paginated<VoteEntity<Article>> =
|
): Paginated<VoteEntity<Article>> =
|
||||||
findByCitizen(
|
findByCitizen(
|
||||||
citizen.id ?: error("The citizen must have an id"),
|
citizen.id,
|
||||||
"article",
|
"article",
|
||||||
object : TypeReference<List<VoteEntity<Article>>>() {},
|
object : TypeReference<List<VoteEntity<Article>>>() {},
|
||||||
page,
|
page,
|
||||||
@@ -90,7 +80,7 @@ class VoteArticleComment(requester: Requester) : Vote<Comment<Article>>(requeste
|
|||||||
limit: Int = 50
|
limit: Int = 50
|
||||||
): Paginated<VoteEntity<Comment<Article>>> =
|
): Paginated<VoteEntity<Comment<Article>>> =
|
||||||
findByCitizen(
|
findByCitizen(
|
||||||
citizen.id ?: error("The citizen must have an id"),
|
citizen.id,
|
||||||
"article",
|
"article",
|
||||||
object : TypeReference<List<VoteEntity<Comment<Article>>>>() {},
|
object : TypeReference<List<VoteEntity<Comment<Article>>>>() {},
|
||||||
page,
|
page,
|
||||||
@@ -98,16 +88,16 @@ class VoteArticleComment(requester: Requester) : Vote<Comment<Article>>(requeste
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class VoteComment(requester: Requester) : Vote<Comment<UuidEntity>>(requester) {
|
class VoteComment(requester: Requester) : Vote<Comment<TargetRef>>(requester) {
|
||||||
fun findByCitizen(
|
fun findByCitizen(
|
||||||
citizen: CitizenEntity,
|
citizen: CitizenEntity,
|
||||||
page: Int = 1,
|
page: Int = 1,
|
||||||
limit: Int = 50
|
limit: Int = 50
|
||||||
): Paginated<VoteEntity<Comment<UuidEntity>>> =
|
): Paginated<VoteEntity<Comment<TargetRef>>> =
|
||||||
findByCitizen(
|
findByCitizen(
|
||||||
citizen.id ?: error("The citizen must have an id"),
|
citizen.id,
|
||||||
"article",
|
"article",
|
||||||
object : TypeReference<List<VoteEntity<Comment<UuidEntity>>>>() {},
|
object : TypeReference<List<VoteEntity<Comment<TargetRef>>>>() {},
|
||||||
page,
|
page,
|
||||||
limit
|
limit
|
||||||
)
|
)
|
||||||
@@ -120,7 +110,7 @@ class VoteConstitution(requester: Requester) : Vote<Constitution>(requester) {
|
|||||||
limit: Int = 50
|
limit: Int = 50
|
||||||
): Paginated<VoteEntity<Constitution>> =
|
): Paginated<VoteEntity<Constitution>> =
|
||||||
findByCitizen(
|
findByCitizen(
|
||||||
citizen.id ?: error("The citizen must have an id"),
|
citizen.id,
|
||||||
"constitution",
|
"constitution",
|
||||||
object : TypeReference<List<VoteEntity<Constitution>>>() {},
|
object : TypeReference<List<VoteEntity<Constitution>>>() {},
|
||||||
page,
|
page,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package fr.dcproject.routes
|
package fr.dcproject.routes
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.exc.MismatchedInputException
|
import com.fasterxml.jackson.databind.exc.MismatchedInputException
|
||||||
|
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
|
||||||
import fr.dcproject.JwtConfig
|
import fr.dcproject.JwtConfig
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.UserI.Roles.ROLE_USER
|
||||||
import fr.dcproject.messages.SsoManager
|
import fr.dcproject.messages.SsoManager
|
||||||
import fr.dcproject.routes.AuthPaths.LoginRequest
|
import fr.dcproject.routes.AuthPaths.LoginRequest
|
||||||
import fr.dcproject.routes.AuthPaths.RegisterRequest
|
import fr.dcproject.routes.AuthPaths.RegisterRequest
|
||||||
@@ -52,12 +53,14 @@ fun Route.auth(
|
|||||||
}
|
}
|
||||||
|
|
||||||
post <RegisterRequest> {
|
post <RegisterRequest> {
|
||||||
|
try {
|
||||||
val citizen = call.receive<CitizenEntity>()
|
val citizen = call.receive<CitizenEntity>()
|
||||||
citizen.user?.roles = listOf(User.Roles.ROLE_USER)
|
citizen.user.roles = listOf(ROLE_USER)
|
||||||
// TODO implement with validator
|
|
||||||
citizen.email ?: throw BadRequestException("Bad request")
|
|
||||||
val created = citizenRepo.insertWithUser(citizen)?.user ?: throw BadRequestException("Bad request")
|
val created = citizenRepo.insertWithUser(citizen)?.user ?: throw BadRequestException("Bad request")
|
||||||
call.respondText(JwtConfig.makeToken(created))
|
call.respondText(JwtConfig.makeToken(created))
|
||||||
|
} catch (e: MissingKotlinParameterException) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post<SsoRequest> {
|
post<SsoRequest> {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ fun Route.citizen(
|
|||||||
assertCan(CHANGE_PASSWORD, it.citizen)
|
assertCan(CHANGE_PASSWORD, it.citizen)
|
||||||
val content = call.receive<ChangePasswordCitizenRequest.Content>()
|
val content = call.receive<ChangePasswordCitizenRequest.Content>()
|
||||||
|
|
||||||
val user = it.citizen.user ?: error("Citizen must have User")
|
val user = it.citizen.user
|
||||||
|
|
||||||
user.plainPassword = content.password
|
user.plainPassword = content.password
|
||||||
userRepository.changePassword(user)
|
userRepository.changePassword(user)
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ fun Route.comment(repo: CommentRepository) {
|
|||||||
assertCan(CREATE, newComment)
|
assertCan(CREATE, newComment)
|
||||||
repo.comment(newComment)
|
repo.comment(newComment)
|
||||||
|
|
||||||
|
|
||||||
call.respond(HttpStatusCode.Created, newComment)
|
call.respond(HttpStatusCode.Created, newComment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package fr.dcproject.routes
|
package fr.dcproject.routes
|
||||||
|
|
||||||
import fr.dcproject.citizen
|
import fr.dcproject.citizen
|
||||||
|
import fr.dcproject.entity.ArticleRef
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.Citizen
|
||||||
import fr.dcproject.security.voter.CommentVoter.Action.CREATE
|
import fr.dcproject.security.voter.CommentVoter.Action.CREATE
|
||||||
import fr.dcproject.security.voter.CommentVoter.Action.VIEW
|
import fr.dcproject.security.voter.CommentVoter.Action.VIEW
|
||||||
@@ -14,7 +15,6 @@ import io.ktor.locations.post
|
|||||||
import io.ktor.request.receive
|
import io.ktor.request.receive
|
||||||
import io.ktor.response.respond
|
import io.ktor.response.respond
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import fr.dcproject.entity.Article as ArticleEntity
|
|
||||||
import fr.dcproject.entity.Comment as CommentEntity
|
import fr.dcproject.entity.Comment as CommentEntity
|
||||||
import fr.dcproject.entity.request.Comment as CommentEntityRequest
|
import fr.dcproject.entity.request.Comment as CommentEntityRequest
|
||||||
import fr.dcproject.repository.CommentArticle as CommentArticleRepository
|
import fr.dcproject.repository.CommentArticle as CommentArticleRepository
|
||||||
@@ -23,7 +23,7 @@ import fr.dcproject.repository.CommentArticle as CommentArticleRepository
|
|||||||
object CommentArticlePaths {
|
object CommentArticlePaths {
|
||||||
@Location("/articles/{article}/comments")
|
@Location("/articles/{article}/comments")
|
||||||
class ArticleCommentRequest(
|
class ArticleCommentRequest(
|
||||||
val article: ArticleEntity,
|
val article: ArticleRef,
|
||||||
page: Int = 1,
|
page: Int = 1,
|
||||||
limit: Int = 50,
|
limit: Int = 50,
|
||||||
val search: String? = null
|
val search: String? = null
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package fr.dcproject.routes
|
package fr.dcproject.routes
|
||||||
|
|
||||||
import fr.dcproject.citizen
|
import fr.dcproject.citizen
|
||||||
|
import fr.dcproject.entity.request.Constitution
|
||||||
import fr.dcproject.security.voter.ConstitutionVoter.Action.CREATE
|
import fr.dcproject.security.voter.ConstitutionVoter.Action.CREATE
|
||||||
import fr.dcproject.security.voter.ConstitutionVoter.Action.VIEW
|
import fr.dcproject.security.voter.ConstitutionVoter.Action.VIEW
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.dcproject.security.voter.assertCan
|
||||||
@@ -41,8 +42,7 @@ fun Route.constitution(repo: ConstitutionRepository) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
post<ConstitutionPaths.PostConstitutionRequest> {
|
post<ConstitutionPaths.PostConstitutionRequest> {
|
||||||
val constitution = call.receive<ConstitutionEntity>()
|
val constitution = call.receive<Constitution>().create(citizen)
|
||||||
constitution.createdBy = citizen
|
|
||||||
assertCan(CREATE, constitution)
|
assertCan(CREATE, constitution)
|
||||||
|
|
||||||
repo.upsert(constitution)
|
repo.upsert(constitution)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.ArticleSimpleI
|
||||||
|
import fr.dcproject.entity.UserI
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import fr.dcproject.entity.Article as ArticleEntity
|
|
||||||
import fr.dcproject.entity.Comment as CommentEntity
|
import fr.dcproject.entity.Comment as CommentEntity
|
||||||
import fr.dcproject.entity.Vote as VoteEntity
|
import fr.dcproject.entity.Vote as VoteEntity
|
||||||
|
|
||||||
@@ -16,23 +16,23 @@ class ArticleVoter : Voter {
|
|||||||
|
|
||||||
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
|
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
|
||||||
return (action is Action || action is CommentVoter.Action || action is VoteVoter.Action)
|
return (action is Action || action is CommentVoter.Action || action is VoteVoter.Action)
|
||||||
.and(subject is List<*> || subject is ArticleEntity? || subject is VoteEntity<*> || subject is CommentEntity<*>)
|
.and(subject is List<*> || subject is ArticleSimpleI? || subject is VoteEntity<*> || subject is CommentEntity<*>)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote {
|
override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote {
|
||||||
val user = call.user
|
val user = call.user
|
||||||
if (action == Action.CREATE && user is User) {
|
if (action == Action.CREATE && user is UserI) {
|
||||||
return Vote.GRANTED
|
return Vote.GRANTED
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == Action.VIEW) {
|
if (action == Action.VIEW) {
|
||||||
if (subject is ArticleEntity) {
|
if (subject is ArticleSimpleI) {
|
||||||
return if (subject.isDeleted()) Vote.DENIED
|
return if (subject.isDeleted()) Vote.DENIED
|
||||||
else Vote.GRANTED
|
else Vote.GRANTED
|
||||||
}
|
}
|
||||||
if (subject is List<*>) {
|
if (subject is List<*>) {
|
||||||
subject.forEach {
|
subject.forEach {
|
||||||
if (it !is ArticleEntity || it.isDeleted()) {
|
if (it !is ArticleSimpleI || it.isDeleted()) {
|
||||||
return Vote.DENIED
|
return Vote.DENIED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,12 +44,12 @@ class ArticleVoter : Voter {
|
|||||||
if (action is CommentVoter.Action) return voteForComment(action)
|
if (action is CommentVoter.Action) return voteForComment(action)
|
||||||
if (action is VoteVoter.Action) return voteForVote(action, subject)
|
if (action is VoteVoter.Action) return voteForVote(action, subject)
|
||||||
|
|
||||||
if (subject is ArticleEntity) {
|
if (subject is ArticleSimpleI) {
|
||||||
if (action == Action.DELETE && user is User && subject.createdBy?.userId == user.id) {
|
if (action == Action.DELETE && user is UserI && subject.createdBy.user.id == user.id) {
|
||||||
return Vote.GRANTED
|
return Vote.GRANTED
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == Action.UPDATE && user is User && subject.createdBy?.userId == user.id) {
|
if (action == Action.UPDATE && user is UserI && subject.createdBy.user.id == user.id) {
|
||||||
return Vote.GRANTED
|
return Vote.GRANTED
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ class ArticleVoter : Voter {
|
|||||||
private fun voteForVote(action: VoteVoter.Action, subject: Any?): Vote {
|
private fun voteForVote(action: VoteVoter.Action, subject: Any?): Vote {
|
||||||
if (action == VoteVoter.Action.CREATE && subject is VoteEntity<*>) {
|
if (action == VoteVoter.Action.CREATE && subject is VoteEntity<*>) {
|
||||||
val target = subject.target
|
val target = subject.target
|
||||||
if (target !is ArticleEntity) {
|
if (target !is ArticleSimpleI) {
|
||||||
return Vote.ABSTAIN
|
return Vote.ABSTAIN
|
||||||
}
|
}
|
||||||
if (target.isDeleted()) {
|
if (target.isDeleted()) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.CitizenBasicI
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.UserI
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ class CitizenVoter : Voter {
|
|||||||
|
|
||||||
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
|
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
|
||||||
return (action is Action)
|
return (action is Action)
|
||||||
.and(subject is List<*> || subject is Citizen?)
|
.and(subject is List<*> || subject is CitizenBasicI?)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote {
|
override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote {
|
||||||
@@ -28,13 +28,13 @@ class CitizenVoter : Voter {
|
|||||||
|
|
||||||
if (action == Action.VIEW) {
|
if (action == Action.VIEW) {
|
||||||
if (user == null) return Vote.DENIED
|
if (user == null) return Vote.DENIED
|
||||||
if (subject is Citizen) {
|
if (subject is CitizenBasicI) {
|
||||||
return if (subject.isDeleted()) Vote.DENIED
|
return if (subject.isDeleted()) Vote.DENIED
|
||||||
else Vote.GRANTED
|
else Vote.GRANTED
|
||||||
}
|
}
|
||||||
if (subject is List<*>) {
|
if (subject is List<*>) {
|
||||||
subject.forEach {
|
subject.forEach {
|
||||||
if (it !is Citizen || it.isDeleted()) {
|
if (it !is CitizenBasicI || it.isDeleted()) {
|
||||||
return Vote.DENIED
|
return Vote.DENIED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,14 +48,14 @@ class CitizenVoter : Voter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (action == Action.UPDATE &&
|
if (action == Action.UPDATE &&
|
||||||
user is User &&
|
user is UserI &&
|
||||||
subject is Citizen &&
|
subject is CitizenBasicI &&
|
||||||
subject.user?.id == user.id) {
|
subject.user.id == user.id) {
|
||||||
return Vote.GRANTED
|
return Vote.GRANTED
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == Action.CHANGE_PASSWORD && user != null && subject is Citizen) {
|
if (action == Action.CHANGE_PASSWORD && user != null && subject is CitizenBasicI) {
|
||||||
val userToChange = subject.user ?: error("Citizen must have User")
|
val userToChange = subject.user
|
||||||
return if (user.id == userToChange.id) {
|
return if (user.id == userToChange.id) {
|
||||||
Vote.GRANTED
|
Vote.GRANTED
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class CommentVoter : Voter {
|
|||||||
return Vote.DENIED
|
return Vote.DENIED
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == Action.UPDATE && user != null && subject is Comment<*> && user.id == subject.createdBy?.userId) {
|
if (action == Action.UPDATE && user != null && subject is Comment<*> && user.id == subject.createdBy.user.id) {
|
||||||
return Vote.GRANTED
|
return Vote.GRANTED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package fr.dcproject.security.voter
|
package fr.dcproject.security.voter
|
||||||
|
|
||||||
import fr.dcproject.entity.Comment
|
import fr.dcproject.entity.Comment
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.ConstitutionSimple
|
||||||
|
import fr.dcproject.entity.UserI
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
import fr.dcproject.entity.Constitution as ConstitutionEntity
|
|
||||||
import fr.dcproject.entity.Vote as VoteEntity
|
import fr.dcproject.entity.Vote as VoteEntity
|
||||||
|
|
||||||
class ConstitutionVoter : Voter {
|
class ConstitutionVoter : Voter {
|
||||||
@@ -16,7 +16,7 @@ class ConstitutionVoter : Voter {
|
|||||||
|
|
||||||
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
|
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
|
||||||
return (action is Action || action is CommentVoter.Action || action is VoteVoter.Action)
|
return (action is Action || action is CommentVoter.Action || action is VoteVoter.Action)
|
||||||
.and(subject is List<*> || subject is ConstitutionEntity? || subject is VoteEntity<*> || subject is Comment<*>)
|
.and(subject is List<*> || subject is ConstitutionSimple<*, *>? || subject is VoteEntity<*> || subject is Comment<*>)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote {
|
override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote {
|
||||||
@@ -26,13 +26,13 @@ class ConstitutionVoter : Voter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (action == Action.VIEW) {
|
if (action == Action.VIEW) {
|
||||||
if (subject is ConstitutionEntity) {
|
if (subject is ConstitutionSimple<*, *>) {
|
||||||
return if (subject.isDeleted()) Vote.DENIED
|
return if (subject.isDeleted()) Vote.DENIED
|
||||||
else Vote.GRANTED
|
else Vote.GRANTED
|
||||||
}
|
}
|
||||||
if (subject is List<*>) {
|
if (subject is List<*>) {
|
||||||
subject.forEach {
|
subject.forEach {
|
||||||
if (it !is ConstitutionEntity || it.isDeleted()) {
|
if (it !is ConstitutionSimple<*, *> || it.isDeleted()) {
|
||||||
return Vote.DENIED
|
return Vote.DENIED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,11 +41,11 @@ class ConstitutionVoter : Voter {
|
|||||||
return Vote.DENIED
|
return Vote.DENIED
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == Action.DELETE && user is User && subject is ConstitutionEntity && subject.createdBy?.userId == user.id) {
|
if (action == Action.DELETE && user is UserI && subject is ConstitutionSimple<*, *> && subject.createdBy.user.id == user.id) {
|
||||||
return Vote.GRANTED
|
return Vote.GRANTED
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == Action.UPDATE && user is User && subject is ConstitutionEntity && subject.createdBy?.userId == user.id) {
|
if (action == Action.UPDATE && user is UserI && subject is ConstitutionSimple<*, *> && subject.createdBy.user.id == user.id) {
|
||||||
return Vote.GRANTED
|
return Vote.GRANTED
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ class ConstitutionVoter : Voter {
|
|||||||
private fun voteForVote(action: VoteVoter.Action, subject: Any?): Vote {
|
private fun voteForVote(action: VoteVoter.Action, subject: Any?): Vote {
|
||||||
if (action == VoteVoter.Action.CREATE && subject is VoteEntity<*>) {
|
if (action == VoteVoter.Action.CREATE && subject is VoteEntity<*>) {
|
||||||
val target = subject.target
|
val target = subject.target
|
||||||
if (target !is ConstitutionEntity) {
|
if (target !is ConstitutionSimple<*, *>) {
|
||||||
return Vote.ABSTAIN
|
return Vote.ABSTAIN
|
||||||
}
|
}
|
||||||
if (target.isDeleted()) {
|
if (target.isDeleted()) {
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class FollowVoter : Voter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun voteView(user: UserEntity?, subject: FollowEntity<*>): Vote {
|
private fun voteView(user: UserEntity?, subject: FollowEntity<*>): Vote {
|
||||||
return if ((user != null && subject.createdBy?.user?.id == user.id) || subject.createdBy?.followAnonymous == false) Vote.GRANTED
|
return if ((user != null && subject.createdBy.user.id == user.id) || !subject.createdBy.followAnonymous) Vote.GRANTED
|
||||||
else Vote.DENIED
|
else Vote.DENIED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class VoteVoter : Voter {
|
|||||||
|
|
||||||
if (action == Action.VIEW && user != null) {
|
if (action == Action.VIEW && user != null) {
|
||||||
if (subject is VoteEntity<*>) {
|
if (subject is VoteEntity<*>) {
|
||||||
return if (subject.createdBy?.userId != user.id) {
|
return if (subject.createdBy.user.id != user.id) {
|
||||||
Vote.DENIED
|
Vote.DENIED
|
||||||
} else {
|
} else {
|
||||||
Vote.GRANTED
|
Vote.GRANTED
|
||||||
@@ -33,7 +33,7 @@ class VoteVoter : Voter {
|
|||||||
|
|
||||||
if (subject is List<*>) {
|
if (subject is List<*>) {
|
||||||
subject.forEach {
|
subject.forEach {
|
||||||
if (it !is VoteEntity<*> || it.createdBy?.userId != user.id) {
|
if (it !is VoteEntity<*> || it.createdBy.user.id != user.id) {
|
||||||
return Vote.DENIED
|
return Vote.DENIED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,20 +17,18 @@ begin
|
|||||||
now() + (row_number() over () * interval '7 minute 3 second')
|
now() + (row_number() over () * interval '7 minute 3 second')
|
||||||
from citizen z;
|
from citizen z;
|
||||||
|
|
||||||
insert into title (id, created_by_id, name, rank, constitution_id)
|
insert into title (id, name, rank, constitution_id)
|
||||||
select
|
select
|
||||||
uuid_in(md5('constitution_title'||row_number() over ())::cstring),
|
uuid_in(md5('constitution_title'||row_number() over ())::cstring),
|
||||||
c.created_by_id,
|
|
||||||
'name' || row_number() over (),
|
'name' || row_number() over (),
|
||||||
row_number() over (),
|
row_number() over (),
|
||||||
c.id
|
c.id
|
||||||
from constitution c,
|
from constitution c,
|
||||||
lateral generate_series(1, 5) g;
|
lateral generate_series(1, 5) g;
|
||||||
|
|
||||||
insert into article_in_title (id, created_by_id, rank, title_id, article_id, constitution_id)
|
insert into article_in_title (id, rank, title_id, article_id, constitution_id)
|
||||||
select
|
select
|
||||||
uuid_in(md5('article_in_title'||row_number() over ())::cstring),
|
uuid_in(md5('article_in_title'||row_number() over ())::cstring),
|
||||||
ti.created_by_id,
|
|
||||||
row_number() over (),
|
row_number() over (),
|
||||||
ti.id,
|
ti.id,
|
||||||
a.id,
|
a.id,
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ begin
|
|||||||
select to_json(t) into resource
|
select to_json(t) into resource
|
||||||
from (
|
from (
|
||||||
select
|
select
|
||||||
z.*
|
z.*,
|
||||||
|
find_user_by_id(z.user_id) as "user"
|
||||||
from citizen as z
|
from citizen as z
|
||||||
where z.id = _id
|
where z.id = _id
|
||||||
) as t;
|
) as t;
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ begin
|
|||||||
from (
|
from (
|
||||||
select
|
select
|
||||||
z.*,
|
z.*,
|
||||||
u as "user"
|
find_user_by_id(z.user_id) as "user"
|
||||||
from citizen as z
|
from citizen as z
|
||||||
join "user" u on z.user_id = u.id
|
|
||||||
where z.id = _id
|
where z.id = _id
|
||||||
) as t;
|
) as t;
|
||||||
end;
|
end;
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ begin
|
|||||||
into resource, total
|
into resource, total
|
||||||
from (
|
from (
|
||||||
select
|
select
|
||||||
z.*
|
z.*,
|
||||||
|
json_build_object('id', z.user_id) as "user"
|
||||||
from citizen as z
|
from citizen as z
|
||||||
where "search" is null or (
|
where "search" is null or (
|
||||||
(name->'first_name')::text ilike '%'||"search"||'%' or
|
(name->'first_name')::text ilike '%'||"search"||'%' or
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ $$
|
|||||||
declare
|
declare
|
||||||
_title alias for title;
|
_title alias for title;
|
||||||
_constitution_id uuid = coalesce(constitution_id, (title#>>'{constitution_id}')::uuid);
|
_constitution_id uuid = coalesce(constitution_id, (title#>>'{constitution_id}')::uuid);
|
||||||
_author_id uuid = (title#>>'{created_by, id}')::uuid;
|
|
||||||
new_id uuid;
|
new_id uuid;
|
||||||
begin
|
begin
|
||||||
insert into title (created_by_id, name, rank, constitution_id)
|
insert into title (name, rank, constitution_id)
|
||||||
select
|
select
|
||||||
_author_id,
|
|
||||||
ti.name,
|
ti.name,
|
||||||
row_number() OVER (),
|
row_number() OVER (),
|
||||||
_constitution_id
|
_constitution_id
|
||||||
@@ -17,9 +15,8 @@ begin
|
|||||||
returning id into new_id;
|
returning id into new_id;
|
||||||
|
|
||||||
if (_title->'articles' is not null) then
|
if (_title->'articles' is not null) then
|
||||||
insert into article_in_title (created_by_id, rank, title_id, article_id, constitution_id)
|
insert into article_in_title (rank, title_id, article_id, constitution_id)
|
||||||
select
|
select
|
||||||
_author_id,
|
|
||||||
row_number() over (),
|
row_number() over (),
|
||||||
new_id,
|
new_id,
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -46,10 +46,6 @@ begin
|
|||||||
titles := (resource->>'titles');
|
titles := (resource->>'titles');
|
||||||
|
|
||||||
for _title in select json_array_elements(titles) loop
|
for _title in select json_array_elements(titles) loop
|
||||||
if _title#>>'{created_by, id}' is null then
|
|
||||||
_title := jsonb_set(_title::jsonb, '{created_by}'::text[], jsonb_build_object('id', _citizen_id::text), true)::json;
|
|
||||||
end if;
|
|
||||||
|
|
||||||
perform create_title_in_constitution(_title, new_id);
|
perform create_title_in_constitution(_title, new_id);
|
||||||
end loop;
|
end loop;
|
||||||
|
|
||||||
|
|||||||
@@ -274,7 +274,6 @@ create table title
|
|||||||
(
|
(
|
||||||
id uuid default uuid_generate_v4() not null primary key,
|
id uuid default uuid_generate_v4() not null primary key,
|
||||||
created_at timestamptz default now() not null,
|
created_at timestamptz default now() not null,
|
||||||
created_by_id uuid not null references citizen (id),
|
|
||||||
name text not null check ( name != '' ),
|
name text not null check ( name != '' ),
|
||||||
rank int not null check ( rank >= 0 ),
|
rank int not null check ( rank >= 0 ),
|
||||||
constitution_id uuid not null references constitution (id)
|
constitution_id uuid not null references constitution (id)
|
||||||
@@ -284,7 +283,6 @@ create table article_in_title
|
|||||||
(
|
(
|
||||||
id uuid default uuid_generate_v4() not null primary key,
|
id uuid default uuid_generate_v4() not null primary key,
|
||||||
created_at timestamptz default now() not null,
|
created_at timestamptz default now() not null,
|
||||||
created_by_id uuid not null references citizen (id),
|
|
||||||
rank int not null check ( rank >= 0 ),
|
rank int not null check ( rank >= 0 ),
|
||||||
title_id uuid not null references title (id),
|
title_id uuid not null references title (id),
|
||||||
article_id uuid not null references article (id),
|
article_id uuid not null references article (id),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import fr.dcproject.entity.Article
|
import fr.dcproject.entity.Article
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.CitizenBasic
|
||||||
|
import fr.dcproject.entity.CitizenI
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.User
|
||||||
import fr.postgresjson.serializer.deserialize
|
import fr.postgresjson.serializer.deserialize
|
||||||
import fr.postgresjson.serializer.serialize
|
import fr.postgresjson.serializer.serialize
|
||||||
@@ -57,8 +58,8 @@ class ArticleTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `test Article serialize`() {
|
fun `test Article serialize`() {
|
||||||
val user = User(username = "jaque", plainPassword = "azerty")
|
val user = User(username = "jaque", plainPassword = "azerty")
|
||||||
val citizen = Citizen(
|
val citizen = CitizenBasic(
|
||||||
name = Citizen.Name("Jaque", "Bono"),
|
name = CitizenI.Name("Jaque", "Bono"),
|
||||||
birthday = DateTime.now(),
|
birthday = DateTime.now(),
|
||||||
email = "jaque.bono@gmail.com",
|
email = "jaque.bono@gmail.com",
|
||||||
user = user
|
user = user
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.CitizenBasic
|
||||||
|
import fr.dcproject.entity.CitizenI
|
||||||
import fr.dcproject.entity.Constitution
|
import fr.dcproject.entity.Constitution
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.User
|
||||||
import fr.postgresjson.serializer.deserialize
|
import fr.postgresjson.serializer.deserialize
|
||||||
@@ -78,8 +79,8 @@ class ConstitutionTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `test Constitution serialize`() {
|
fun `test Constitution serialize`() {
|
||||||
val user = User(username = "jaque", plainPassword = "azerty")
|
val user = User(username = "jaque", plainPassword = "azerty")
|
||||||
val citizen = Citizen(
|
val citizen = CitizenBasic(
|
||||||
name = Citizen.Name("Jaque", "Bono"),
|
name = CitizenI.Name("Jaque", "Bono"),
|
||||||
email = "jaque.bono@gmail.com",
|
email = "jaque.bono@gmail.com",
|
||||||
birthday = DateTime.now(),
|
birthday = DateTime.now(),
|
||||||
user = user
|
user = user
|
||||||
@@ -90,7 +91,7 @@ class ConstitutionTest {
|
|||||||
val constitution = Constitution(
|
val constitution = Constitution(
|
||||||
title = "Hello world!",
|
title = "Hello world!",
|
||||||
anonymous = true,
|
anonymous = true,
|
||||||
titles = listOf(title1),
|
titles = mutableListOf(title1),
|
||||||
createdBy = citizen
|
createdBy = citizen
|
||||||
)
|
)
|
||||||
println(constitution.serialize())
|
println(constitution.serialize())
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import fr.dcproject.entity.Article
|
import fr.dcproject.entity.*
|
||||||
import fr.dcproject.entity.Citizen
|
|
||||||
import fr.dcproject.entity.Follow
|
|
||||||
import fr.dcproject.entity.User
|
|
||||||
import fr.postgresjson.serializer.deserialize
|
import fr.postgresjson.serializer.deserialize
|
||||||
import fr.postgresjson.serializer.serialize
|
import fr.postgresjson.serializer.serialize
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
@@ -53,7 +50,25 @@ class FollowTest {
|
|||||||
|
|
||||||
],
|
],
|
||||||
"created_by":{
|
"created_by":{
|
||||||
"id":"4a87ad24-187a-46a8-97ab-00b30a24e561"
|
"id":"4a87ad24-187a-46a8-97ab-00b30a24e561",
|
||||||
|
"name":{
|
||||||
|
"first_name":"Jaque",
|
||||||
|
"last_name":"Bono",
|
||||||
|
"civility":null
|
||||||
|
},
|
||||||
|
"email": "jaque.bono@gmail.com",
|
||||||
|
"birthday":"2019-08-09T11:42:47.168Z",
|
||||||
|
"user_id":null,
|
||||||
|
"vote_anonymous":null,
|
||||||
|
"follow_anonymous":null,
|
||||||
|
"user":{
|
||||||
|
"id":"721db690-d050-46e6-92b0-056f2e8ba993",
|
||||||
|
"username":"jaque",
|
||||||
|
"blocked_at":null,
|
||||||
|
"plain_password":"azerty",
|
||||||
|
"created_at":null,
|
||||||
|
"updated_at":null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"version_id":"a4aa7dd4-d174-42d2-9ba5-ae6f1129ffce",
|
"version_id":"a4aa7dd4-d174-42d2-9ba5-ae6f1129ffce",
|
||||||
"version_number":null,
|
"version_number":null,
|
||||||
@@ -65,8 +80,8 @@ class FollowTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `test Follow Article serialize`() {
|
fun `test Follow Article serialize`() {
|
||||||
val user = User(username = "jaque", plainPassword = "azerty")
|
val user = User(username = "jaque", plainPassword = "azerty")
|
||||||
val citizen = Citizen(
|
val citizen = CitizenBasic(
|
||||||
name = Citizen.Name("Jaque", "Bono"),
|
name = CitizenI.Name("Jaque", "Bono"),
|
||||||
email = "jaque.bono@gmail.com",
|
email = "jaque.bono@gmail.com",
|
||||||
birthday = DateTime.now(),
|
birthday = DateTime.now(),
|
||||||
user = user
|
user = user
|
||||||
@@ -87,7 +102,7 @@ class FollowTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test Follow Article Deserialize`() {
|
fun `test Follow Article Deserialize`() {
|
||||||
val follow: Follow<Article> = followJson.deserialize()!!
|
val follow: Follow<ArticleSimple> = followJson.deserialize()!!
|
||||||
follow.id.toString() `should equal` "bae81585-d985-4d7a-9b58-3a13e911688a"
|
follow.id.toString() `should equal` "bae81585-d985-4d7a-9b58-3a13e911688a"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import fr.dcproject.entity.Article
|
import fr.dcproject.entity.*
|
||||||
import fr.dcproject.entity.Citizen
|
|
||||||
import fr.dcproject.entity.User
|
|
||||||
import fr.dcproject.entity.Vote
|
|
||||||
import fr.postgresjson.serializer.deserialize
|
import fr.postgresjson.serializer.deserialize
|
||||||
import fr.postgresjson.serializer.serialize
|
import fr.postgresjson.serializer.serialize
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
@@ -40,11 +37,12 @@ class VoteTest {
|
|||||||
"blocked_at": null,
|
"blocked_at": null,
|
||||||
"plain_password": "azerty",
|
"plain_password": "azerty",
|
||||||
"roles": [],
|
"roles": [],
|
||||||
"created_at": null,
|
"created_at": "2019-10-01T10:59:40.570Z",
|
||||||
"updated_at": null
|
"updated_at": "2019-10-01T10:59:40.570Z"
|
||||||
},
|
},
|
||||||
"deleted": false,
|
"deleted": false,
|
||||||
"created_at": null,
|
"created_at": "2019-10-01T10:59:40.570Z",
|
||||||
|
"updated_at": "2019-10-01T10:59:40.570Z",
|
||||||
"deleted_at": null
|
"deleted_at": null
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
@@ -90,20 +88,20 @@ class VoteTest {
|
|||||||
"version_id": "48dad61e-c54b-4f4c-9f66-428f90b94045",
|
"version_id": "48dad61e-c54b-4f4c-9f66-428f90b94045",
|
||||||
"version_number": null,
|
"version_number": null,
|
||||||
"deleted": false,
|
"deleted": false,
|
||||||
"created_at": null,
|
"created_at": "2019-10-01T10:59:40.570Z",
|
||||||
"deleted_at": null
|
"deleted_at": "2019-10-01T10:59:40.570Z"
|
||||||
},
|
},
|
||||||
"note": -1,
|
"note": -1,
|
||||||
"anonymous": true,
|
"anonymous": true,
|
||||||
"updated_at": null,
|
"updated_at": "2019-10-01T10:59:40.570Z",
|
||||||
"created_at": null
|
"created_at": "2019-10-01T10:59:40.570Z"
|
||||||
}""".trimIndent()
|
}""".trimIndent()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test Vote Article serialize`() {
|
fun `test Vote Article serialize`() {
|
||||||
val user = User(username = "jaque", plainPassword = "azerty")
|
val user = User(username = "jaque", plainPassword = "azerty")
|
||||||
val citizen = Citizen(
|
val citizen = CitizenBasic(
|
||||||
name = Citizen.Name("Jaque", "Bono"),
|
name = CitizenI.Name("Jaque", "Bono"),
|
||||||
email = "jaque.bono@gmail.com",
|
email = "jaque.bono@gmail.com",
|
||||||
birthday = DateTime.now(),
|
birthday = DateTime.now(),
|
||||||
user = user
|
user = user
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package feature
|
package feature
|
||||||
|
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.*
|
||||||
import fr.dcproject.repository.CommentArticle
|
import fr.dcproject.repository.CommentArticle
|
||||||
import io.cucumber.java8.En
|
import io.cucumber.java8.En
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
@@ -18,7 +18,7 @@ class ArticleSteps : En, KoinTest {
|
|||||||
init {
|
init {
|
||||||
Given("I have article with id {string}") { id: String ->
|
Given("I have article with id {string}") { id: String ->
|
||||||
var citizen = Citizen(
|
var citizen = Citizen(
|
||||||
name = Citizen.Name("John", "Doe"),
|
name = CitizenI.Name("John", "Doe"),
|
||||||
email = "john.doe@gmail.com",
|
email = "john.doe@gmail.com",
|
||||||
birthday = DateTime.now(),
|
birthday = DateTime.now(),
|
||||||
user = UserEntity(username = "john-doe", plainPassword = "azerty")
|
user = UserEntity(username = "john-doe", plainPassword = "azerty")
|
||||||
@@ -55,7 +55,7 @@ class ArticleSteps : En, KoinTest {
|
|||||||
|
|
||||||
Given("I have comment {string} on article {string}") { commentId: String, articleId: String ->
|
Given("I have comment {string} on article {string}") { commentId: String, articleId: String ->
|
||||||
var citizen = Citizen(
|
var citizen = Citizen(
|
||||||
name = Citizen.Name("John", "Doe"),
|
name = CitizenI.Name("John", "Doe"),
|
||||||
email = "john.doe@gmail.com",
|
email = "john.doe@gmail.com",
|
||||||
birthday = DateTime.now(),
|
birthday = DateTime.now(),
|
||||||
user = UserEntity(username = "john-doe", plainPassword = "azerty")
|
user = UserEntity(username = "john-doe", plainPassword = "azerty")
|
||||||
@@ -76,7 +76,7 @@ class ArticleSteps : En, KoinTest {
|
|||||||
)
|
)
|
||||||
get<ArticleRepository>().upsert(article)
|
get<ArticleRepository>().upsert(article)
|
||||||
|
|
||||||
val comment = CommentEntity(
|
val comment: CommentEntity<ArticleRef> = CommentEntity(
|
||||||
id = UUID.fromString(commentId),
|
id = UUID.fromString(commentId),
|
||||||
createdBy = citizen,
|
createdBy = citizen,
|
||||||
target = article,
|
target = article,
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package feature
|
package feature
|
||||||
|
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.*
|
||||||
|
import fr.dcproject.entity.ConstitutionSimple.TitleSimple
|
||||||
|
import fr.dcproject.entity.request.Constitution
|
||||||
import io.cucumber.java8.En
|
import io.cucumber.java8.En
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import org.koin.test.KoinTest
|
import org.koin.test.KoinTest
|
||||||
import org.koin.test.get
|
import org.koin.test.get
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.CompletionException
|
import java.util.concurrent.CompletionException
|
||||||
import fr.dcproject.entity.Constitution as ConstitutionEntity
|
|
||||||
import fr.dcproject.entity.Constitution.Title as TitleEntity
|
|
||||||
import fr.dcproject.entity.User as UserEntity
|
import fr.dcproject.entity.User as UserEntity
|
||||||
import fr.dcproject.repository.Citizen as CitizenRepository
|
import fr.dcproject.repository.Citizen as CitizenRepository
|
||||||
import fr.dcproject.repository.Constitution as ConstitutionRepository
|
import fr.dcproject.repository.Constitution as ConstitutionRepository
|
||||||
@@ -17,7 +17,7 @@ class ConstitutionSteps : En, KoinTest {
|
|||||||
init {
|
init {
|
||||||
Given("I have constitution with id {string}") { id: String ->
|
Given("I have constitution with id {string}") { id: String ->
|
||||||
var citizen = Citizen(
|
var citizen = Citizen(
|
||||||
name = Citizen.Name("John", "Doe"),
|
name = CitizenI.Name("John", "Doe"),
|
||||||
email = "john.doe@gmail.com",
|
email = "john.doe@gmail.com",
|
||||||
birthday = DateTime.now(),
|
birthday = DateTime.now(),
|
||||||
user = UserEntity(username = "john-doe", plainPassword = "azerty")
|
user = UserEntity(username = "john-doe", plainPassword = "azerty")
|
||||||
@@ -29,33 +29,31 @@ class ConstitutionSteps : En, KoinTest {
|
|||||||
citizen = get<CitizenRepository>().findByUsername("john-doe")!!
|
citizen = get<CitizenRepository>().findByUsername("john-doe")!!
|
||||||
}
|
}
|
||||||
|
|
||||||
val title1 = TitleEntity(
|
val title1 = Constitution.Title(
|
||||||
name = "My Title"
|
name = "My Title"
|
||||||
)
|
)
|
||||||
|
|
||||||
val constitution = ConstitutionEntity(
|
val constitution = Constitution(
|
||||||
id = UUID.fromString(id),
|
|
||||||
title = "hello",
|
title = "hello",
|
||||||
titles = listOf(title1),
|
titles = mutableListOf(title1),
|
||||||
createdBy = citizen,
|
|
||||||
anonymous = false
|
anonymous = false
|
||||||
)
|
)
|
||||||
get<ConstitutionRepository>().upsert(constitution)
|
get<ConstitutionRepository>().upsert(constitution.create(citizen))
|
||||||
}
|
}
|
||||||
|
|
||||||
Given("I have constitution with id {string} created by {string}") { id: String, username: String ->
|
Given("I have constitution with id {string} created by {string}") { id: String, username: String ->
|
||||||
val citizen = get<CitizenRepository>().findByUsername(username)!!
|
val citizen = get<CitizenRepository>().findByUsername(username)!!
|
||||||
|
|
||||||
val title1 = TitleEntity(
|
val title1 = TitleSimple<ArticleRef>(
|
||||||
name = "My Title"
|
name = "My Title"
|
||||||
)
|
)
|
||||||
|
|
||||||
val constitution = ConstitutionEntity(
|
val constitution = ConstitutionSimple<CitizenSimple, TitleSimple<ArticleRef>>(
|
||||||
id = UUID.fromString(id),
|
id = UUID.fromString(id),
|
||||||
title = "hello",
|
title = "hello",
|
||||||
titles = listOf(title1),
|
titles = mutableListOf(title1),
|
||||||
createdBy = citizen,
|
anonymous = false,
|
||||||
anonymous = false
|
createdBy = citizen
|
||||||
)
|
)
|
||||||
get<ConstitutionRepository>().upsert(constitution)
|
get<ConstitutionRepository>().upsert(constitution)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package feature
|
|||||||
import com.auth0.jwt.JWT
|
import com.auth0.jwt.JWT
|
||||||
import fr.dcproject.JwtConfig
|
import fr.dcproject.JwtConfig
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.Citizen
|
||||||
|
import fr.dcproject.entity.CitizenI
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.User
|
||||||
import fr.postgresjson.connexion.Requester
|
import fr.postgresjson.connexion.Requester
|
||||||
import io.cucumber.datatable.DataTable
|
import io.cucumber.datatable.DataTable
|
||||||
@@ -29,7 +30,7 @@ class KtorServerAuthSteps : En, KoinTest {
|
|||||||
val data = body.asMap<String, String>(String::class.java, String::class.java)
|
val data = body.asMap<String, String>(String::class.java, String::class.java)
|
||||||
val citizen = Citizen(
|
val citizen = Citizen(
|
||||||
id = UUID.fromString(data["id"]),
|
id = UUID.fromString(data["id"]),
|
||||||
name = Citizen.Name(data["firstName"], data["lastName"]),
|
name = CitizenI.Name(data["firstName"]!!, data["lastName"]!!),
|
||||||
email = data["email"] ?: ((data["firstName"] + "-" + data["lastName"]).toLowerCase()) + "@dc-project.com",
|
email = data["email"] ?: ((data["firstName"] + "-" + data["lastName"]).toLowerCase()) + "@dc-project.com",
|
||||||
birthday = DateTime.now(),
|
birthday = DateTime.now(),
|
||||||
user = user
|
user = user
|
||||||
@@ -52,7 +53,7 @@ class KtorServerAuthSteps : En, KoinTest {
|
|||||||
)
|
)
|
||||||
val citizen = Citizen(
|
val citizen = Citizen(
|
||||||
id = UUID.fromString(id),
|
id = UUID.fromString(id),
|
||||||
name = Citizen.Name(firstName, lastName),
|
name = CitizenI.Name(firstName, lastName),
|
||||||
email = ("$firstName-$lastName".toLowerCase()) + "@dc-project.fr",
|
email = ("$firstName-$lastName".toLowerCase()) + "@dc-project.fr",
|
||||||
birthday = DateTime.now(),
|
birthday = DateTime.now(),
|
||||||
user = user
|
user = user
|
||||||
@@ -77,7 +78,7 @@ class KtorServerAuthSteps : En, KoinTest {
|
|||||||
)
|
)
|
||||||
val citizen = Citizen(
|
val citizen = Citizen(
|
||||||
id = UUID.fromString(id),
|
id = UUID.fromString(id),
|
||||||
name = Citizen.Name(firstName, lastName),
|
name = CitizenI.Name(firstName, lastName),
|
||||||
email = "$firstName-$lastName".toLowerCase() + "@gmail.com",
|
email = "$firstName-$lastName".toLowerCase() + "@gmail.com",
|
||||||
birthday = DateTime.now(),
|
birthday = DateTime.now(),
|
||||||
user = user,
|
user = user,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
Feature: comment Constitution
|
Feature: comment Constitution
|
||||||
|
|
||||||
Scenario: Can comment an constitution
|
Scenario: Can comment an constitution
|
||||||
Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd"
|
Given I am authenticated as John Toe with id "a6eb1f5a-8c02-42f4-8e8e-a722f26841ef"
|
||||||
And I have constitution with id "9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b"
|
And I have constitution with id "d7e20f0b-3fdd-4638-817a-bbd87054eb82" created by "john-toe"
|
||||||
When I send a POST request to "/constitutions/9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b/comments" with body:
|
When I send a POST request to "/constitutions/d7e20f0b-3fdd-4638-817a-bbd87054eb82/comments" with body:
|
||||||
"""
|
"""
|
||||||
Hello mister
|
Hello mister
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ Feature: constitution routes
|
|||||||
"id":"64b7b379-2298-43ec-b428-ba134930cabd"
|
"id":"64b7b379-2298-43ec-b428-ba134930cabd"
|
||||||
},
|
},
|
||||||
"created_at":null,
|
"created_at":null,
|
||||||
"version_id":"3311a7af-2a62-4e31-b4cd-889f8ead9737",
|
|
||||||
"version_number":null
|
"version_number":null
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ Feature: vote Article
|
|||||||
And the response should contain object:
|
And the response should contain object:
|
||||||
| [0].note | 1 |
|
| [0].note | 1 |
|
||||||
|
|
||||||
Scenario: Can vote a comment on article
|
Scenario: Can vote a comment
|
||||||
Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd"
|
Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd"
|
||||||
And I have comment "ea5c9e87-c99e-4646-a381-2910219e077f" on article "cc9c624e-a27e-42de-af78-ae821c657a68"
|
And I have comment "ea5c9e87-c99e-4646-a381-2910219e077f" on article "cc9c624e-a27e-42de-af78-ae821c657a68"
|
||||||
When I send a PUT request to "/comments/ea5c9e87-c99e-4646-a381-2910219e077f/vote" with body:
|
When I send a PUT request to "/comments/ea5c9e87-c99e-4646-a381-2910219e077f/vote" with body:
|
||||||
|
|||||||
Reference in New Issue
Block a user