Split Entities for remove nullable variables

This commit is contained in:
2020-01-28 11:08:43 +01:00
parent 3cdd1f3a46
commit 813d6857e9
52 changed files with 569 additions and 287 deletions

View File

@@ -1,5 +1,5 @@
<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" />
<module name="dcproject.test" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
@@ -19,6 +19,7 @@
<dir value="$PROJECT_DIR$" />
<method v="2">
<option name="Make" enabled="true" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Lint" run_configuration_type="GradleRunConfiguration" />
</method>
</configuration>
</component>

View File

@@ -1,10 +1,10 @@
<component name="ProjectRunConfigurationManager">
<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" />
<option name="PACKAGE_NAME" value="" />
<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="PARAMETERS" value="" />
<envs>

31
.idea/runConfigurations/Lint.xml generated Normal file
View 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>

View File

@@ -8,10 +8,7 @@ import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.joda.JodaModule
import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException
import fr.dcproject.Env.PROD
import fr.dcproject.entity.Article
import fr.dcproject.entity.Citizen
import fr.dcproject.entity.Constitution
import fr.dcproject.entity.User
import fr.dcproject.entity.*
import fr.dcproject.routes.*
import fr.dcproject.security.voter.*
import fr.postgresjson.migration.Migrations
@@ -80,9 +77,16 @@ fun Application.module(env: Env = PROD) {
// TODO: create generic convert for entityI
convert<Article> {
decode { values, _ ->
val id = values.singleOrNull()?.let { UUID.fromString(it) }
?: throw InternalError("Cannot convert $values to UUID")
get<RepositoryArticle>().findById(id) ?: throw InternalError("Article $values not found")
values.singleOrNull()?.let {
get<RepositoryArticle>().findById(UUID.fromString(it)) ?: 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, _ ->
val id = values.singleOrNull()?.let { UUID.fromString(it) }
?: 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, _ ->
val id = values.singleOrNull()?.let { UUID.fromString(it) }
?: 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
}
}
exception<NotFoundException> { e ->
call.respond(HttpStatusCode.BadRequest, e.message!!)
}
}
install(CORS) {

View File

@@ -1,5 +1,6 @@
package fr.dcproject
import fr.dcproject.entity.UserI
import fr.dcproject.security.voter.ForbiddenException
import io.ktor.application.ApplicationCall
import io.ktor.auth.authentication
@@ -8,7 +9,6 @@ import io.ktor.util.pipeline.PipelineContext
import kotlinx.coroutines.runBlocking
import org.koin.core.context.GlobalContext
import fr.dcproject.entity.Citizen as CitizenEntity
import fr.dcproject.entity.User as UserEntity
import fr.dcproject.repository.Citizen as CitizenRepository
private val citizenAttributeKey = AttributeKey<CitizenEntity>("CitizenContext")
@@ -16,7 +16,7 @@ private val citizenAttributeKey = AttributeKey<CitizenEntity>("CitizenContext")
val ApplicationCall.citizen: CitizenEntity
get() = attributes.computeIfAbsent(citizenAttributeKey) {
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}\"")
}
}

View File

@@ -4,7 +4,7 @@ import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import com.typesafe.config.ConfigFactory
import fr.dcproject.entity.User
import fr.dcproject.entity.UserI
import org.eclipse.jetty.util.resource.JarResource
import java.io.File
import java.util.*
@@ -46,7 +46,7 @@ object JwtConfig {
/**
* 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")
.withIssuer(issuer)
.withClaim("id", user.id.toString())

View File

@@ -1,26 +1,77 @@
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.*
class Article(
id: UUID = UUID.randomUUID(),
var title: String?,
var anonymous: Boolean? = true,
var content: String?,
var description: String?,
var tags: List<String> = emptyList(),
var draft: Boolean = false,
var lastVersion: Boolean = false,
createdBy: Citizen?
) :
UuidEntity(id),
EntityVersioning<UUID, Int> by UuidEntityVersioning(),
EntityCreatedAt by EntityCreatedAtImp(),
EntityCreatedBy<Citizen> by EntityCreatedByImp(createdBy),
EntityDeletedAt by EntityDeletedAtImp(),
Votable by VotableImp() {
title: String,
anonymous: Boolean = true,
content: String,
description: String,
tags: List<String> = emptyList(),
override var draft: Boolean = false,
override var lastVersion: Boolean = false,
createdBy: CitizenBasic
) : ArticleFull,
ArticleBasic(id, title, anonymous, content, description, tags, createdBy)
open class ArticleBasic(
id: UUID = UUID.randomUUID(),
title: String,
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 {
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
}

View File

@@ -1,24 +1,68 @@
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 java.util.*
class Citizen(
id: UUID = UUID.randomUUID(),
var name: Name?,
var email: String?,
var birthday: DateTime?,
var userId: UUID? = null,
var voteAnonymous: Boolean = true,
var followAnonymous: Boolean = true,
var user: User?
name: Name,
email: String,
birthday: DateTime,
voteAnonymous: Boolean = true,
followAnonymous: Boolean = true,
override val 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),
EntityCreatedAt by EntityCreatedAtImp(),
EntityDeletedAt by EntityDeletedAtImp() {
CitizenI,
EntityDeletedAt by EntityDeletedAtImp()
interface CitizenI : UuidEntityI {
data class Name(
var firstName: String?,
var lastName: String?,
var firstName: String,
var lastName: String,
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
}

View File

@@ -1,13 +1,15 @@
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.*
open class Comment <T : UuidEntity> (
open class Comment<T : TargetI>(
id: UUID = UUID.randomUUID(),
createdBy: Citizen,
override val createdBy: CitizenBasic,
target: T,
override var targetReference: String = target::class.simpleName!!.toLowerCase(),
var content: String,
val responses: List<Comment<T>>? = null,
var parent: Comment<T>? = null,
@@ -16,17 +18,18 @@ open class Comment <T : UuidEntity> (
) : Extra<T>(id, createdBy, target),
EntityUpdatedAt by EntityUpdatedAtImp(),
EntityDeletedAt by EntityDeletedAtImp(),
Votable by VotableImp()
{
Votable by VotableImp(),
TargetI {
constructor(
createdBy: Citizen,
createdBy: CitizenBasic,
parent: Comment<T>,
content: String
) : this(
createdBy = createdBy,
parent = parent,
target = parent.target,
targetReference = parent.targetReference,
content = content
)
override val reference get() = TargetI.getReference(this)
}

View File

@@ -1,36 +1,69 @@
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.*
class Constitution(
id: UUID = UUID.randomUUID(),
var title: String?,
var anonymous: Boolean? = true,
var titles: List<Title> = listOf(),
title: String,
anonymous: Boolean = true,
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 lastVersion: Boolean = false,
createdBy: Citizen?
) : UuidEntity(id),
EntityVersioning<UUID, Int> by UuidEntityVersioning(),
override val createdBy: Cr,
versionId: UUID = UUID.randomUUID()
) : ConstitutionRef(id),
EntityVersioning<UUID, Int?> by UuidEntityVersioning(versionId = versionId),
EntityCreatedAt by EntityCreatedAtImp(),
EntityCreatedBy<Citizen> by EntityCreatedByImp(createdBy),
EntityCreatedBy<Cr> by EntityCreatedByImp(createdBy),
EntityDeletedAt by EntityDeletedAtImp() {
init {
titles.forEachIndexed { index, title ->
title.createdBy = this.createdBy
title.rank = index
}
}
class Title(
open class TitleSimple<A : ArticleI>(
id: UUID = UUID.randomUUID(),
var name: String?,
var name: String,
var rank: Int? = null,
var articles: List<Article> = listOf(),
createdBy: Citizen? = null
) : UuidEntity(id),
EntityCreatedAt by EntityCreatedAtImp(),
EntityCreatedBy<Citizen> by EntityCreatedByImp(createdBy)
open val articles: MutableList<A> = mutableListOf()
) : TitleRef(id)
}
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

View File

@@ -1,24 +1,62 @@
package fr.dcproject.entity
import fr.postgresjson.entity.EntityI
import fr.postgresjson.entity.mutable.*
import fr.postgresjson.entity.immutable.*
import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.full.isSuperclassOf
interface ExtraI <T : EntityI> :
EntityI,
interface ExtraI<T : TargetI> :
UuidEntityI,
EntityCreatedAt,
EntityCreatedBy<Citizen> {
EntityCreatedBy<CitizenBasicI> {
var target: T
var targetReference: String
}
abstract class Extra<T : UuidEntity>(
id: UUID? = UUID.randomUUID(),
createdBy: Citizen,
override var target: T,
override var targetReference: String = target::class.simpleName!!.toLowerCase()
abstract class Extra<T : TargetI>(
id: UUID = UUID.randomUUID(),
override val createdBy: CitizenBasic,
override var target: T
) :
ExtraI<T>,
UuidEntity(id),
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
}

View File

@@ -1,9 +1,8 @@
package fr.dcproject.entity
import fr.postgresjson.entity.mutable.UuidEntity
import java.util.*
class Follow <T : UuidEntity> (
class Follow <T : TargetI> (
id: UUID = UUID.randomUUID(),
createdBy: Citizen,
override val createdBy: CitizenBasic,
target: T
) : Extra<T>(id, createdBy, target)

View File

@@ -1,19 +1,41 @@
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 org.joda.time.DateTime
import java.util.*
class User(
id: UUID? = UUID.randomUUID(),
var username: String?,
var blockedAt: DateTime? = null,
var plainPassword: String?,
var roles: List<Roles> = emptyList()
) : UuidEntity(id),
id: UUID = UUID.randomUUID(),
username: String,
blockedAt: DateTime? = null,
override var plainPassword: String?,
override var roles: List<Roles> = emptyList()
) : UserFull, UserBasic(id, username, blockedAt),
EntityCreatedAt by EntityCreatedAtImp(),
EntityUpdatedAt by EntityUpdatedAtImp(),
Principal {
EntityUpdatedAt by EntityUpdatedAtImp()
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 }
}
interface UserBasicI : UserI {
var username: String
var blockedAt: DateTime?
}
interface UserFull : UserBasicI, EntityCreatedAt, EntityUpdatedAt {
var plainPassword: String?
var roles: List<Roles>
}

View File

@@ -1,13 +1,12 @@
package fr.dcproject.entity
import fr.postgresjson.entity.mutable.EntityUpdatedAt
import fr.postgresjson.entity.mutable.EntityUpdatedAtImp
import fr.postgresjson.entity.mutable.UuidEntity
import fr.postgresjson.entity.immutable.EntityUpdatedAt
import fr.postgresjson.entity.immutable.EntityUpdatedAtImp
import java.util.*
open class Vote <T : UuidEntity> (
open class Vote <T : TargetI> (
id: UUID = UUID.randomUUID(),
createdBy: Citizen,
override val createdBy: CitizenBasic,
target: T,
var note: Int,
var anonymous: Boolean = true

View File

@@ -1,5 +1,6 @@
package fr.dcproject.entity.request
import fr.dcproject.entity.ArticleFull
import fr.dcproject.entity.Citizen
import java.util.*
import fr.dcproject.entity.Article as ArticleEntity
@@ -7,7 +8,7 @@ import fr.dcproject.entity.Article as ArticleEntity
class Article(
val id: UUID?,
val title: String,
val anonymous: Boolean? = true,
val anonymous: Boolean = true,
val content: String,
val description: String,
val tags: List<String> = emptyList(),
@@ -16,7 +17,7 @@ class Article(
) :
Request {
fun merge(article: ArticleEntity) {
fun merge(article: ArticleFull) {
article.title = this.title
article.content = this.content
article.description = this.description

View 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()

View File

@@ -4,8 +4,8 @@ import com.sendgrid.helpers.mail.Mail
import com.sendgrid.helpers.mail.objects.Content
import com.sendgrid.helpers.mail.objects.Email
import fr.dcproject.JwtConfig
import fr.dcproject.entity.CitizenBasicI
import io.ktor.http.URLBuilder
import fr.dcproject.entity.Citizen as CitizenEntity
import fr.dcproject.repository.Citizen as CitizenRepository
class SsoManager(
@@ -15,11 +15,15 @@ class SsoManager(
) {
fun sendMail(email: String, url: String) {
val citizen = citizenRepo.findByEmail(email) ?: noEmail(email)
sendMail(citizen, url)
}
fun sendMail(citizen: CitizenBasicI, url: String) {
mailer.sendEmail {
Mail(
Email("sso@$domain"),
"Connection",
Email(email),
Email(citizen.email),
Content("text/plain", generateContent(citizen, url))
).apply {
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)
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"
}
private fun generateContent(citizen: CitizenEntity, url: String): String {
private fun generateContent(citizen: CitizenBasicI, url: String): String {
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()}"
}

View File

@@ -1,5 +1,7 @@
package fr.dcproject.repository
import fr.dcproject.entity.ArticleFull
import fr.dcproject.entity.ArticleSimple
import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester
import fr.postgresjson.entity.Parameter
@@ -28,7 +30,7 @@ class Article(override var requester: Requester) : RepositoryI {
direction: Direction? = null,
search: String? = null,
filter: Filter = Filter()
): Paginated<ArticleEntity> {
): Paginated<ArticleSimple> {
return requester
.getFunction("find_articles")
.select(
@@ -40,7 +42,7 @@ class Article(override var requester: Requester) : RepositoryI {
)
}
fun upsert(article: ArticleEntity): ArticleEntity? {
fun upsert(article: ArticleFull): ArticleEntity? {
return requester
.getFunction("upsert_article")
.selectOne("resource" to article)

View File

@@ -1,5 +1,8 @@
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.Requester
import fr.postgresjson.repository.RepositoryI
@@ -7,7 +10,6 @@ import fr.postgresjson.repository.RepositoryI.Direction
import net.pearx.kasechange.toSnakeCase
import java.util.*
import fr.dcproject.entity.Citizen as CitizenEntity
import fr.dcproject.entity.User as UserEntity
class Citizen(override var requester: Requester) : RepositoryI {
fun findById(id: UUID, withUser: Boolean = false): CitizenEntity? {
@@ -16,7 +18,7 @@ class Citizen(override var requester: Requester) : RepositoryI {
.selectOne("id" to id)
}
fun findByUser(user: UserEntity): CitizenEntity? {
fun findByUser(user: UserI): CitizenEntity? {
return requester
.getFunction("find_citizen_by_user_id")
.selectOne("user_id" to user.id)
@@ -40,7 +42,7 @@ class Citizen(override var requester: Requester) : RepositoryI {
sort: String? = null,
direction: Direction? = null,
search: String? = null
): Paginated<CitizenEntity> {
): Paginated<CitizenBasic> {
return requester
.getFunction("find_citizens")
.select(
@@ -51,13 +53,13 @@ class Citizen(override var requester: Requester) : RepositoryI {
)
}
fun upsert(citizen: CitizenEntity): CitizenEntity? {
fun upsert(citizen: CitizenFull): CitizenEntity? {
return requester
.getFunction("upsert_citizen")
.selectOne("resource" to citizen)
}
fun insertWithUser(citizen: CitizenEntity): CitizenEntity? {
fun insertWithUser(citizen: CitizenFull): CitizenEntity? {
return requester
.getFunction("insert_citizen_with_user")
.selectOne("resource" to citizen)

View File

@@ -1,16 +1,18 @@
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.Requester
import fr.postgresjson.entity.mutable.UuidEntity
import fr.postgresjson.entity.immutable.UuidEntityI
import fr.postgresjson.repository.RepositoryI
import java.util.*
import fr.dcproject.entity.Article as ArticleEntity
import fr.dcproject.entity.Citizen as CitizenEntity
import fr.dcproject.entity.Comment as CommentEntity
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 findByCitizen(
@@ -24,7 +26,7 @@ abstract class Comment <T : UuidEntity>(override var requester: Requester) : Rep
page: Int = 1,
limit: Int = 50
): Paginated<CommentEntity<T>> {
return findByParent(parent.id ?: error("comment must have an ID"), page, limit)
return findByParent(parent.id, page, limit)
}
open fun findByParent(
@@ -41,11 +43,11 @@ abstract class Comment <T : UuidEntity>(override var requester: Requester) : Rep
}
open fun findByTarget(
target: UuidEntity,
target: UuidEntityI,
page: Int = 1,
limit: Int = 50
): Paginated<CommentEntity<T>> {
return findByTarget(target.id ?: error("comment must have an ID"), page, limit)
return findByTarget(target.id, page, limit)
}
open fun findByTarget(
@@ -65,7 +67,7 @@ abstract class Comment <T : UuidEntity>(override var requester: Requester) : Rep
requester
.getFunction("comment")
.sendQuery(
"reference" to comment.targetReference,
"reference" to comment.target.reference,
"resource" to comment
)
}
@@ -80,8 +82,8 @@ abstract class Comment <T : UuidEntity>(override var requester: Requester) : Rep
}
}
class CommentGeneric(requester: Requester) : Comment<UuidEntity>(requester) {
override fun findById(id: UUID): CommentEntity<UuidEntity>? {
class CommentGeneric(requester: Requester) : Comment<TargetRef>(requester) {
override fun findById(id: UUID): CommentEntity<TargetRef>? {
return requester
.getFunction("find_comment_by_id")
.selectOne(mapOf("id" to id))
@@ -91,7 +93,7 @@ class CommentGeneric(requester: Requester) : Comment<UuidEntity>(requester) {
citizen: CitizenEntity,
page: Int,
limit: Int
): Paginated<CommentEntity<UuidEntity>> {
): Paginated<CommentEntity<TargetRef>> {
return requester.run {
getFunction("find_comments_by_citizen")
.select(page, limit,
@@ -101,8 +103,8 @@ class CommentGeneric(requester: Requester) : Comment<UuidEntity>(requester) {
}
}
class CommentArticle(requester: Requester) : Comment<ArticleEntity>(requester) {
override fun findById(id: UUID): CommentEntity<ArticleEntity>? {
class CommentArticle(requester: Requester) : Comment<ArticleRef>(requester) {
override fun findById(id: UUID): CommentEntity<ArticleRef>? {
return requester
.getFunction("find_comment_by_id")
.selectOne(mapOf("id" to id))
@@ -112,13 +114,12 @@ class CommentArticle(requester: Requester) : Comment<ArticleEntity>(requester) {
citizen: CitizenEntity,
page: Int,
limit: Int
): Paginated<CommentEntity<ArticleEntity>> {
val reference = ArticleEntity::class.simpleName!!.toLowerCase()
): Paginated<CommentEntity<ArticleRef>> {
return requester.run {
getFunction("find_comments_by_citizen")
.select(page, limit,
"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,
limit: Int
): Paginated<CommentEntity<ConstitutionEntity>> {
val reference = ConstitutionEntity::class.simpleName!!.toLowerCase()
return requester.run {
getFunction("find_comments_by_citizen")
.select(page, limit,
"created_by_id" to citizen.id,
"reference" to reference
"reference" to TargetI.getReference(ConstitutionEntity::class)
)
}
}

View File

@@ -1,5 +1,8 @@
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.Requester
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
.getFunction("upsert_constitution")
.selectOne("resource" to constitution)

View File

@@ -1,22 +1,22 @@
package fr.dcproject.repository
import fr.dcproject.entity.CitizenI
import fr.dcproject.entity.TargetI
import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester
import fr.postgresjson.entity.mutable.UuidEntity
import fr.postgresjson.repository.RepositoryI
import java.util.*
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.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(
citizen: CitizenEntity,
citizen: CitizenI,
page: Int = 1,
limit: Int = 50
): Paginated<FollowEntity<T>> =
findByCitizen(citizen.id ?: error("The citizen must have an id"), page, limit)
findByCitizen(citizen.id, page, limit)
open fun findByCitizen(
citizenId: UUID,
@@ -32,24 +32,22 @@ open class Follow <T : UuidEntity>(override var requester: Requester) : Reposito
}
fun follow(follow: FollowEntity<T>) {
val reference = follow.target::class.simpleName!!.toLowerCase()
requester
.getFunction("follow")
.sendQuery(
"reference" to reference,
"reference" to follow.target.reference,
"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>) {
val reference = follow.target::class.simpleName!!.toLowerCase()
requester
.getFunction("unfollow")
.sendQuery(
"reference" to reference,
"reference" to follow.target.reference,
"target_id" to follow.target.id,
"created_by_id" to follow.createdBy?.id
"created_by_id" to follow.createdBy.id
)
}
}

View File

@@ -1,5 +1,6 @@
package fr.dcproject.repository
import fr.dcproject.entity.UserFull
import fr.postgresjson.connexion.Requester
import fr.postgresjson.repository.RepositoryI
import io.ktor.auth.UserPasswordCredential
@@ -30,7 +31,7 @@ class User(override var requester: Requester) : RepositoryI {
.selectOne("resource" to user)
}
fun changePassword(user: UserEntity) {
fun changePassword(user: UserFull) {
requester
.getFunction("change_user_password")
.sendQuery("resource" to user)

View File

@@ -1,34 +1,25 @@
package fr.dcproject.repository
import com.fasterxml.jackson.core.type.TypeReference
import fr.dcproject.entity.*
import fr.dcproject.entity.Article
import fr.dcproject.entity.Comment
import fr.dcproject.entity.Constitution
import fr.dcproject.entity.VoteAggregation
import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester
import fr.postgresjson.entity.mutable.UuidEntity
import fr.postgresjson.repository.RepositoryI
import java.util.*
import fr.dcproject.entity.Citizen as CitizenEntity
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 {
val target = vote.target
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 author = vote.createdBy
val anonymous = author.voteAnonymous
return requester
.getFunction("vote")
.selectOne(
"reference" to reference,
"reference" to vote.target.reference,
"target_id" to vote.target.id,
"note" to vote.note,
"created_by_id" to author.id,
@@ -56,12 +47,11 @@ open class Vote <T : UuidEntity>(override var requester: Requester) : Repository
citizen: CitizenEntity,
targets: List<UUID>
): List<VoteEntity<*>> {
val typeReference = object : TypeReference<List<VoteEntity<UuidEntity>>>() {}
val typeReference = object : TypeReference<List<VoteEntity<TargetRef>>>() {}
return requester.run {
val citizenId = citizen.id ?: error("The citizen must have an id")
getFunction("find_citizen_votes_by_target_ids")
.select(typeReference, mapOf(
"citizen_id" to citizenId,
"citizen_id" to citizen.id,
"ids" to targets
))
}
@@ -75,7 +65,7 @@ class VoteArticle(requester: Requester) : Vote<Article>(requester) {
limit: Int = 50
): Paginated<VoteEntity<Article>> =
findByCitizen(
citizen.id ?: error("The citizen must have an id"),
citizen.id,
"article",
object : TypeReference<List<VoteEntity<Article>>>() {},
page,
@@ -90,7 +80,7 @@ class VoteArticleComment(requester: Requester) : Vote<Comment<Article>>(requeste
limit: Int = 50
): Paginated<VoteEntity<Comment<Article>>> =
findByCitizen(
citizen.id ?: error("The citizen must have an id"),
citizen.id,
"article",
object : TypeReference<List<VoteEntity<Comment<Article>>>>() {},
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(
citizen: CitizenEntity,
page: Int = 1,
limit: Int = 50
): Paginated<VoteEntity<Comment<UuidEntity>>> =
): Paginated<VoteEntity<Comment<TargetRef>>> =
findByCitizen(
citizen.id ?: error("The citizen must have an id"),
citizen.id,
"article",
object : TypeReference<List<VoteEntity<Comment<UuidEntity>>>>() {},
object : TypeReference<List<VoteEntity<Comment<TargetRef>>>>() {},
page,
limit
)
@@ -120,7 +110,7 @@ class VoteConstitution(requester: Requester) : Vote<Constitution>(requester) {
limit: Int = 50
): Paginated<VoteEntity<Constitution>> =
findByCitizen(
citizen.id ?: error("The citizen must have an id"),
citizen.id,
"constitution",
object : TypeReference<List<VoteEntity<Constitution>>>() {},
page,

View File

@@ -1,8 +1,9 @@
package fr.dcproject.routes
import com.fasterxml.jackson.databind.exc.MismatchedInputException
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
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.routes.AuthPaths.LoginRequest
import fr.dcproject.routes.AuthPaths.RegisterRequest
@@ -52,12 +53,14 @@ fun Route.auth(
}
post <RegisterRequest> {
try {
val citizen = call.receive<CitizenEntity>()
citizen.user?.roles = listOf(User.Roles.ROLE_USER)
// TODO implement with validator
citizen.email ?: throw BadRequestException("Bad request")
citizen.user.roles = listOf(ROLE_USER)
val created = citizenRepo.insertWithUser(citizen)?.user ?: throw BadRequestException("Bad request")
call.respondText(JwtConfig.makeToken(created))
} catch (e: MissingKotlinParameterException) {
call.respond(HttpStatusCode.BadRequest)
}
}
post<SsoRequest> {

View File

@@ -62,7 +62,7 @@ fun Route.citizen(
assertCan(CHANGE_PASSWORD, it.citizen)
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
userRepository.changePassword(user)

View File

@@ -73,7 +73,6 @@ fun Route.comment(repo: CommentRepository) {
assertCan(CREATE, newComment)
repo.comment(newComment)
call.respond(HttpStatusCode.Created, newComment)
}

View File

@@ -1,6 +1,7 @@
package fr.dcproject.routes
import fr.dcproject.citizen
import fr.dcproject.entity.ArticleRef
import fr.dcproject.entity.Citizen
import fr.dcproject.security.voter.CommentVoter.Action.CREATE
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.response.respond
import io.ktor.routing.Route
import fr.dcproject.entity.Article as ArticleEntity
import fr.dcproject.entity.Comment as CommentEntity
import fr.dcproject.entity.request.Comment as CommentEntityRequest
import fr.dcproject.repository.CommentArticle as CommentArticleRepository
@@ -23,7 +23,7 @@ import fr.dcproject.repository.CommentArticle as CommentArticleRepository
object CommentArticlePaths {
@Location("/articles/{article}/comments")
class ArticleCommentRequest(
val article: ArticleEntity,
val article: ArticleRef,
page: Int = 1,
limit: Int = 50,
val search: String? = null

View File

@@ -1,6 +1,7 @@
package fr.dcproject.routes
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.VIEW
import fr.dcproject.security.voter.assertCan
@@ -41,8 +42,7 @@ fun Route.constitution(repo: ConstitutionRepository) {
}
post<ConstitutionPaths.PostConstitutionRequest> {
val constitution = call.receive<ConstitutionEntity>()
constitution.createdBy = citizen
val constitution = call.receive<Constitution>().create(citizen)
assertCan(CREATE, constitution)
repo.upsert(constitution)

View File

@@ -1,8 +1,8 @@
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 fr.dcproject.entity.Article as ArticleEntity
import fr.dcproject.entity.Comment as CommentEntity
import fr.dcproject.entity.Vote as VoteEntity
@@ -16,23 +16,23 @@ class ArticleVoter : Voter {
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
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 {
val user = call.user
if (action == Action.CREATE && user is User) {
if (action == Action.CREATE && user is UserI) {
return Vote.GRANTED
}
if (action == Action.VIEW) {
if (subject is ArticleEntity) {
if (subject is ArticleSimpleI) {
return if (subject.isDeleted()) Vote.DENIED
else Vote.GRANTED
}
if (subject is List<*>) {
subject.forEach {
if (it !is ArticleEntity || it.isDeleted()) {
if (it !is ArticleSimpleI || it.isDeleted()) {
return Vote.DENIED
}
}
@@ -44,12 +44,12 @@ class ArticleVoter : Voter {
if (action is CommentVoter.Action) return voteForComment(action)
if (action is VoteVoter.Action) return voteForVote(action, subject)
if (subject is ArticleEntity) {
if (action == Action.DELETE && user is User && subject.createdBy?.userId == user.id) {
if (subject is ArticleSimpleI) {
if (action == Action.DELETE && user is UserI && subject.createdBy.user.id == user.id) {
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
}
@@ -66,7 +66,7 @@ class ArticleVoter : Voter {
private fun voteForVote(action: VoteVoter.Action, subject: Any?): Vote {
if (action == VoteVoter.Action.CREATE && subject is VoteEntity<*>) {
val target = subject.target
if (target !is ArticleEntity) {
if (target !is ArticleSimpleI) {
return Vote.ABSTAIN
}
if (target.isDeleted()) {

View File

@@ -1,7 +1,7 @@
package fr.dcproject.security.voter
import fr.dcproject.entity.Citizen
import fr.dcproject.entity.User
import fr.dcproject.entity.CitizenBasicI
import fr.dcproject.entity.UserI
import io.ktor.application.ApplicationCall
import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -17,7 +17,7 @@ class CitizenVoter : Voter {
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
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 {
@@ -28,13 +28,13 @@ class CitizenVoter : Voter {
if (action == Action.VIEW) {
if (user == null) return Vote.DENIED
if (subject is Citizen) {
if (subject is CitizenBasicI) {
return if (subject.isDeleted()) Vote.DENIED
else Vote.GRANTED
}
if (subject is List<*>) {
subject.forEach {
if (it !is Citizen || it.isDeleted()) {
if (it !is CitizenBasicI || it.isDeleted()) {
return Vote.DENIED
}
}
@@ -48,14 +48,14 @@ class CitizenVoter : Voter {
}
if (action == Action.UPDATE &&
user is User &&
subject is Citizen &&
subject.user?.id == user.id) {
user is UserI &&
subject is CitizenBasicI &&
subject.user.id == user.id) {
return Vote.GRANTED
}
if (action == Action.CHANGE_PASSWORD && user != null && subject is Citizen) {
val userToChange = subject.user ?: error("Citizen must have User")
if (action == Action.CHANGE_PASSWORD && user != null && subject is CitizenBasicI) {
val userToChange = subject.user
return if (user.id == userToChange.id) {
Vote.GRANTED
} else {

View File

@@ -38,7 +38,7 @@ class CommentVoter : Voter {
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
}

View File

@@ -1,9 +1,9 @@
package fr.dcproject.security.voter
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 fr.dcproject.entity.Constitution as ConstitutionEntity
import fr.dcproject.entity.Vote as VoteEntity
class ConstitutionVoter : Voter {
@@ -16,7 +16,7 @@ class ConstitutionVoter : Voter {
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
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 {
@@ -26,13 +26,13 @@ class ConstitutionVoter : Voter {
}
if (action == Action.VIEW) {
if (subject is ConstitutionEntity) {
if (subject is ConstitutionSimple<*, *>) {
return if (subject.isDeleted()) Vote.DENIED
else Vote.GRANTED
}
if (subject is List<*>) {
subject.forEach {
if (it !is ConstitutionEntity || it.isDeleted()) {
if (it !is ConstitutionSimple<*, *> || it.isDeleted()) {
return Vote.DENIED
}
}
@@ -41,11 +41,11 @@ class ConstitutionVoter : Voter {
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
}
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
}
@@ -62,7 +62,7 @@ class ConstitutionVoter : Voter {
private fun voteForVote(action: VoteVoter.Action, subject: Any?): Vote {
if (action == VoteVoter.Action.CREATE && subject is VoteEntity<*>) {
val target = subject.target
if (target !is ConstitutionEntity) {
if (target !is ConstitutionSimple<*, *>) {
return Vote.ABSTAIN
}
if (target.isDeleted()) {

View File

@@ -47,7 +47,7 @@ class FollowVoter : Voter {
}
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
}
}

View File

@@ -24,7 +24,7 @@ class VoteVoter : Voter {
if (action == Action.VIEW && user != null) {
if (subject is VoteEntity<*>) {
return if (subject.createdBy?.userId != user.id) {
return if (subject.createdBy.user.id != user.id) {
Vote.DENIED
} else {
Vote.GRANTED
@@ -33,7 +33,7 @@ class VoteVoter : Voter {
if (subject is List<*>) {
subject.forEach {
if (it !is VoteEntity<*> || it.createdBy?.userId != user.id) {
if (it !is VoteEntity<*> || it.createdBy.user.id != user.id) {
return Vote.DENIED
}
}

View File

@@ -17,20 +17,18 @@ begin
now() + (row_number() over () * interval '7 minute 3 second')
from citizen z;
insert into title (id, created_by_id, name, rank, constitution_id)
insert into title (id, name, rank, constitution_id)
select
uuid_in(md5('constitution_title'||row_number() over ())::cstring),
c.created_by_id,
'name' || row_number() over (),
row_number() over (),
c.id
from constitution c,
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
uuid_in(md5('article_in_title'||row_number() over ())::cstring),
ti.created_by_id,
row_number() over (),
ti.id,
a.id,

View File

@@ -6,7 +6,8 @@ begin
select to_json(t) into resource
from (
select
z.*
z.*,
find_user_by_id(z.user_id) as "user"
from citizen as z
where z.id = _id
) as t;

View File

@@ -7,9 +7,8 @@ begin
from (
select
z.*,
u as "user"
find_user_by_id(z.user_id) as "user"
from citizen as z
join "user" u on z.user_id = u.id
where z.id = _id
) as t;
end;

View File

@@ -13,7 +13,8 @@ begin
into resource, total
from (
select
z.*
z.*,
json_build_object('id', z.user_id) as "user"
from citizen as z
where "search" is null or (
(name->'first_name')::text ilike '%'||"search"||'%' or

View File

@@ -4,12 +4,10 @@ $$
declare
_title alias for title;
_constitution_id uuid = coalesce(constitution_id, (title#>>'{constitution_id}')::uuid);
_author_id uuid = (title#>>'{created_by, id}')::uuid;
new_id uuid;
begin
insert into title (created_by_id, name, rank, constitution_id)
insert into title (name, rank, constitution_id)
select
_author_id,
ti.name,
row_number() OVER (),
_constitution_id
@@ -17,9 +15,8 @@ begin
returning id into new_id;
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
_author_id,
row_number() over (),
new_id,
id,

View File

@@ -46,10 +46,6 @@ begin
titles := (resource->>'titles');
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);
end loop;

View File

@@ -274,7 +274,6 @@ create table title
(
id uuid default uuid_generate_v4() not null primary key,
created_at timestamptz default now() not null,
created_by_id uuid not null references citizen (id),
name text not null check ( name != '' ),
rank int not null check ( rank >= 0 ),
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,
created_at timestamptz default now() not null,
created_by_id uuid not null references citizen (id),
rank int not null check ( rank >= 0 ),
title_id uuid not null references title (id),
article_id uuid not null references article (id),

View File

@@ -1,5 +1,6 @@
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.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.serialize
@@ -57,8 +58,8 @@ class ArticleTest {
@Test
fun `test Article serialize`() {
val user = User(username = "jaque", plainPassword = "azerty")
val citizen = Citizen(
name = Citizen.Name("Jaque", "Bono"),
val citizen = CitizenBasic(
name = CitizenI.Name("Jaque", "Bono"),
birthday = DateTime.now(),
email = "jaque.bono@gmail.com",
user = user

View File

@@ -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.User
import fr.postgresjson.serializer.deserialize
@@ -78,8 +79,8 @@ class ConstitutionTest {
@Test
fun `test Constitution serialize`() {
val user = User(username = "jaque", plainPassword = "azerty")
val citizen = Citizen(
name = Citizen.Name("Jaque", "Bono"),
val citizen = CitizenBasic(
name = CitizenI.Name("Jaque", "Bono"),
email = "jaque.bono@gmail.com",
birthday = DateTime.now(),
user = user
@@ -90,7 +91,7 @@ class ConstitutionTest {
val constitution = Constitution(
title = "Hello world!",
anonymous = true,
titles = listOf(title1),
titles = mutableListOf(title1),
createdBy = citizen
)
println(constitution.serialize())

View File

@@ -1,7 +1,4 @@
import fr.dcproject.entity.Article
import fr.dcproject.entity.Citizen
import fr.dcproject.entity.Follow
import fr.dcproject.entity.User
import fr.dcproject.entity.*
import fr.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.serialize
import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -53,7 +50,25 @@ class FollowTest {
],
"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_number":null,
@@ -65,8 +80,8 @@ class FollowTest {
@Test
fun `test Follow Article serialize`() {
val user = User(username = "jaque", plainPassword = "azerty")
val citizen = Citizen(
name = Citizen.Name("Jaque", "Bono"),
val citizen = CitizenBasic(
name = CitizenI.Name("Jaque", "Bono"),
email = "jaque.bono@gmail.com",
birthday = DateTime.now(),
user = user
@@ -87,7 +102,7 @@ class FollowTest {
@Test
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"
}
}

View File

@@ -1,7 +1,4 @@
import fr.dcproject.entity.Article
import fr.dcproject.entity.Citizen
import fr.dcproject.entity.User
import fr.dcproject.entity.Vote
import fr.dcproject.entity.*
import fr.postgresjson.serializer.deserialize
import fr.postgresjson.serializer.serialize
import io.ktor.locations.KtorExperimentalLocationsAPI
@@ -40,11 +37,12 @@ class VoteTest {
"blocked_at": null,
"plain_password": "azerty",
"roles": [],
"created_at": null,
"updated_at": null
"created_at": "2019-10-01T10:59:40.570Z",
"updated_at": "2019-10-01T10:59:40.570Z"
},
"deleted": false,
"created_at": null,
"created_at": "2019-10-01T10:59:40.570Z",
"updated_at": "2019-10-01T10:59:40.570Z",
"deleted_at": null
},
"target": {
@@ -90,20 +88,20 @@ class VoteTest {
"version_id": "48dad61e-c54b-4f4c-9f66-428f90b94045",
"version_number": null,
"deleted": false,
"created_at": null,
"deleted_at": null
"created_at": "2019-10-01T10:59:40.570Z",
"deleted_at": "2019-10-01T10:59:40.570Z"
},
"note": -1,
"anonymous": true,
"updated_at": null,
"created_at": null
"updated_at": "2019-10-01T10:59:40.570Z",
"created_at": "2019-10-01T10:59:40.570Z"
}""".trimIndent()
@Test
fun `test Vote Article serialize`() {
val user = User(username = "jaque", plainPassword = "azerty")
val citizen = Citizen(
name = Citizen.Name("Jaque", "Bono"),
val citizen = CitizenBasic(
name = CitizenI.Name("Jaque", "Bono"),
email = "jaque.bono@gmail.com",
birthday = DateTime.now(),
user = user

View File

@@ -1,6 +1,6 @@
package feature
import fr.dcproject.entity.Citizen
import fr.dcproject.entity.*
import fr.dcproject.repository.CommentArticle
import io.cucumber.java8.En
import org.joda.time.DateTime
@@ -18,7 +18,7 @@ class ArticleSteps : En, KoinTest {
init {
Given("I have article with id {string}") { id: String ->
var citizen = Citizen(
name = Citizen.Name("John", "Doe"),
name = CitizenI.Name("John", "Doe"),
email = "john.doe@gmail.com",
birthday = DateTime.now(),
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 ->
var citizen = Citizen(
name = Citizen.Name("John", "Doe"),
name = CitizenI.Name("John", "Doe"),
email = "john.doe@gmail.com",
birthday = DateTime.now(),
user = UserEntity(username = "john-doe", plainPassword = "azerty")
@@ -76,7 +76,7 @@ class ArticleSteps : En, KoinTest {
)
get<ArticleRepository>().upsert(article)
val comment = CommentEntity(
val comment: CommentEntity<ArticleRef> = CommentEntity(
id = UUID.fromString(commentId),
createdBy = citizen,
target = article,

View File

@@ -1,14 +1,14 @@
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 org.joda.time.DateTime
import org.koin.test.KoinTest
import org.koin.test.get
import java.util.*
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.repository.Citizen as CitizenRepository
import fr.dcproject.repository.Constitution as ConstitutionRepository
@@ -17,7 +17,7 @@ class ConstitutionSteps : En, KoinTest {
init {
Given("I have constitution with id {string}") { id: String ->
var citizen = Citizen(
name = Citizen.Name("John", "Doe"),
name = CitizenI.Name("John", "Doe"),
email = "john.doe@gmail.com",
birthday = DateTime.now(),
user = UserEntity(username = "john-doe", plainPassword = "azerty")
@@ -29,33 +29,31 @@ class ConstitutionSteps : En, KoinTest {
citizen = get<CitizenRepository>().findByUsername("john-doe")!!
}
val title1 = TitleEntity(
val title1 = Constitution.Title(
name = "My Title"
)
val constitution = ConstitutionEntity(
id = UUID.fromString(id),
val constitution = Constitution(
title = "hello",
titles = listOf(title1),
createdBy = citizen,
titles = mutableListOf(title1),
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 ->
val citizen = get<CitizenRepository>().findByUsername(username)!!
val title1 = TitleEntity(
val title1 = TitleSimple<ArticleRef>(
name = "My Title"
)
val constitution = ConstitutionEntity(
val constitution = ConstitutionSimple<CitizenSimple, TitleSimple<ArticleRef>>(
id = UUID.fromString(id),
title = "hello",
titles = listOf(title1),
createdBy = citizen,
anonymous = false
titles = mutableListOf(title1),
anonymous = false,
createdBy = citizen
)
get<ConstitutionRepository>().upsert(constitution)
}

View File

@@ -3,6 +3,7 @@ package feature
import com.auth0.jwt.JWT
import fr.dcproject.JwtConfig
import fr.dcproject.entity.Citizen
import fr.dcproject.entity.CitizenI
import fr.dcproject.entity.User
import fr.postgresjson.connexion.Requester
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 citizen = Citizen(
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",
birthday = DateTime.now(),
user = user
@@ -52,7 +53,7 @@ class KtorServerAuthSteps : En, KoinTest {
)
val citizen = Citizen(
id = UUID.fromString(id),
name = Citizen.Name(firstName, lastName),
name = CitizenI.Name(firstName, lastName),
email = ("$firstName-$lastName".toLowerCase()) + "@dc-project.fr",
birthday = DateTime.now(),
user = user
@@ -77,7 +78,7 @@ class KtorServerAuthSteps : En, KoinTest {
)
val citizen = Citizen(
id = UUID.fromString(id),
name = Citizen.Name(firstName, lastName),
name = CitizenI.Name(firstName, lastName),
email = "$firstName-$lastName".toLowerCase() + "@gmail.com",
birthday = DateTime.now(),
user = user,

View File

@@ -1,9 +1,9 @@
Feature: comment Constitution
Scenario: Can comment an constitution
Given I am authenticated as John Doe with id "64b7b379-2298-43ec-b428-ba134930cabd"
And I have constitution with id "9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b"
When I send a POST request to "/constitutions/9226c1a3-8091-c3fa-7d0d-c2e98c9bee7b/comments" with body:
Given I am authenticated as John Toe with id "a6eb1f5a-8c02-42f4-8e8e-a722f26841ef"
And I have constitution with id "d7e20f0b-3fdd-4638-817a-bbd87054eb82" created by "john-toe"
When I send a POST request to "/constitutions/d7e20f0b-3fdd-4638-817a-bbd87054eb82/comments" with body:
"""
Hello mister
"""

View File

@@ -32,7 +32,6 @@ Feature: constitution routes
"id":"64b7b379-2298-43ec-b428-ba134930cabd"
},
"created_at":null,
"version_id":"3311a7af-2a62-4e31-b4cd-889f8ead9737",
"version_number":null
}
"""

View File

@@ -37,7 +37,7 @@ Feature: vote Article
And the response should contain object:
| [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"
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: