Move all file in fr.dcproject.

This commit is contained in:
2021-02-11 01:37:29 +01:00
parent c85401aa86
commit 066b01e86f
148 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
package fr.dcproject.common.dto
import fr.postgresjson.entity.EntityCreatedAt
import org.joda.time.DateTime
interface CreatedAt {
val createdAt: DateTime
class Imp(parent: EntityCreatedAt) : CreatedAt {
override val createdAt: DateTime = parent.createdAt
}
}

View File

@@ -0,0 +1,14 @@
package fr.dcproject.common.dto
import fr.postgresjson.entity.EntityVersioning
import java.util.UUID
interface Versionable {
val versionId: UUID
val versionNumber: Int
class Imp(parent: EntityVersioning<UUID, Int>) : Versionable {
override val versionNumber: Int = parent.versionNumber
override val versionId: UUID = parent.versionId
}
}

View File

@@ -0,0 +1,27 @@
package fr.dcproject.common.email
import com.sendgrid.Method
import com.sendgrid.Request
import com.sendgrid.SendGrid
import com.sendgrid.helpers.mail.Mail
import java.io.IOException
class Mailer(
private val key: String
) {
fun sendEmail(action: () -> Mail): Boolean {
val mail = action()
val sg = SendGrid(key)
val request = Request()
try {
request.method = Method.POST
request.endpoint = "mail/send"
request.body = mail.build()
val response = sg.api(request)
return response.statusCode == 202
} catch (ex: IOException) {
throw ex
}
}
}

View File

@@ -0,0 +1,14 @@
package fr.dcproject.common.entity
import fr.dcproject.component.citizen.CitizenI
import fr.postgresjson.entity.EntityCreatedBy
import fr.postgresjson.entity.EntityI
/**
* TODO remove EntityCreatedBy<EntityI>
*/
interface CreatedBy<T : CitizenI> : EntityCreatedBy<EntityI> {
override val createdBy: T
}
class CreatedByImp<T : CitizenI>(override val createdBy: T) : CreatedBy<T>

View File

@@ -0,0 +1,8 @@
package fr.dcproject.common.entity
import fr.postgresjson.entity.EntityI
import java.util.UUID
interface EntityI : EntityI {
val id: UUID
}

View File

@@ -0,0 +1,66 @@
package fr.dcproject.common.entity
import fr.dcproject.component.article.ArticleRef
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.comment.generic.CommentRef
import fr.dcproject.component.constitution.ConstitutionRef
import fr.dcproject.component.opinion.entity.OpinionRef
import fr.postgresjson.entity.EntityCreatedAt
import fr.postgresjson.entity.EntityCreatedBy
import fr.postgresjson.entity.UuidEntity
import fr.postgresjson.entity.UuidEntityI
import java.util.UUID
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
interface ExtraI<T : TargetI, C : CitizenI> :
UuidEntityI,
HasTarget<T>,
EntityCreatedAt,
EntityCreatedBy<C>
interface HasTarget<T : TargetI> {
val target: T
}
open class TargetRef(id: UUID? = null, reference: String = "") : TargetI, UuidEntity(id) {
final override val reference: String
get() = if (field != "") field else TargetI.getReference(this)
init {
this.reference = reference
}
}
interface TargetI : UuidEntityI {
enum class TargetName(val targetReference: String) {
Article("article"),
Constitution("constitution"),
Comment("comment"),
Opinion("opinion")
}
companion object {
fun <T : TargetI> getReference(t: KClass<T>): String {
return when {
t.isSubclassOf(ArticleRef::class) -> TargetName.Article.targetReference
t.isSubclassOf(ConstitutionRef::class) -> TargetName.Constitution.targetReference
t.isSubclassOf(CommentRef::class) -> TargetName.Comment.targetReference
t.isSubclassOf(OpinionRef::class) -> TargetName.Opinion.targetReference
else -> throw error("target not implemented: ${t.qualifiedName} \nImplement it or return 'reference' from SQL")
}
}
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

@@ -0,0 +1,17 @@
package fr.dcproject.common.entity
import fr.postgresjson.entity.EntityVersioning
import java.util.UUID
interface VersionableRef {
val versionId: UUID
}
class VersionableRefImp(
override val versionId: UUID
) : VersionableRef
interface Versionable : VersionableRef, EntityVersioning<UUID, Int> {
override val versionId: UUID
override val versionNumber: Int
}

View File

@@ -0,0 +1,14 @@
package fr.dcproject.routes
interface PaginatedRequestI {
val page: Int
val limit: Int
}
open class PaginatedRequest(
page: Int = 1,
limit: Int = 50
) : PaginatedRequestI {
override val page: Int = if (page < 1) 1 else page
override val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
}

View File

@@ -0,0 +1,134 @@
package fr.dcproject.common.security
/** Responses of AccessControl */
enum class AccessDecision {
GRANTED,
DENIED;
/**
* Convert decision to boolean
*/
fun toBoolean(): Boolean = when (this) {
GRANTED -> true
DENIED -> false
}
}
abstract class AccessControl {
/**
* A Shortcut for return a GrantedResponse
*/
protected fun granted(message: String? = null, code: String? = null): GrantedResponse = GrantedResponse(this, message, code)
/**
* A Shortcut for return a DeniedResponse
*/
protected fun denied(message: String, code: String): DeniedResponse = DeniedResponse(this, message, code)
/**
* Check all responses and return DENIED if one is DENIED
*
* If the list of responses is empty, return GRANTED
*/
private fun AccessResponses.getOneResponse(): AccessResponse = this.firstOrNull { it.decision == AccessDecision.DENIED } ?: granted()
/**
* An helper to convert a list of subject into one response
*/
protected fun <S : List<T>, T> canAll(items: S, action: (T) -> AccessResponse): AccessResponse = items
.map { action(it) }
.getOneResponse()
}
/**
* Throw an Exception if AccessControl return a DENIED response
*/
fun <T : AccessControl> T.assert(action: T.() -> AccessResponse) {
action().assert()
}
/**
* Check all responses and return DENIED if one is DENIED
*
* If the list of responses is empty, return GRANTED
*/
fun AccessResponses.getOneResponse(): AccessResponse = this.firstOrNull { it.decision == AccessDecision.DENIED } ?: GrantedResponse(first().accessControl)
/**
* Throw an Exception if one response is DENIED
*/
fun AccessResponses.assert() = this.getOneResponse().assert()
class AccessDeniedException(private val accessResponses: AccessResponses) : Throwable(accessResponses.first().message) {
constructor(accessResponse: AccessResponse) : this(listOf(accessResponse))
/**
* Get first response
*/
fun first(): AccessResponse = accessResponses.first()
/**
* Check if the error code is present into the responses
*/
fun hasErrorCode(code: String): Boolean = accessResponses
.filter { it.decision == AccessDecision.DENIED }
.any { it.code == code }
/**
* Find and return the response than match with the error code
*/
fun getErrorCode(code: String): AccessResponse? = accessResponses
.firstOrNull { it.decision == AccessDecision.DENIED && it.code == code }
/**
* Get a list of messages of all responses
*/
fun getMessages(): List<String> = accessResponses
.mapNotNull { it.message }
/**
* Get the first message
*/
fun getFirstMessage(): String? = accessResponses
.first()
.message
}
/**
* The response that all AccessControl method return
* @see GrantedResponse
* @see DeniedResponse
*/
sealed class AccessResponse(
val decision: AccessDecision,
val accessControl: AccessControl,
val message: String?,
val code: String?
) {
/**
* Convert response as boolean
*/
fun toBoolean(): Boolean = decision.toBoolean()
/**
* Throw Exception if response if DENIED
*/
fun assert() {
if (this.decision == AccessDecision.DENIED) {
throw AccessDeniedException(this)
}
}
}
class GrantedResponse(
accessControl: AccessControl,
message: String? = null,
code: String? = null
) : AccessResponse(AccessDecision.GRANTED, accessControl, message, code)
class DeniedResponse(
accessControl: AccessControl,
message: String,
code: String
) : AccessResponse(AccessDecision.DENIED, accessControl, message, code)
typealias AccessResponses = List<AccessResponse>

View File

@@ -0,0 +1,6 @@
package fr.dcproject.common.utils
import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat
fun DateTime.toIso(): String = ISODateTimeFormat.dateTime().print(this)

View File

@@ -0,0 +1,29 @@
package fr.dcproject.common.utils
import com.jayway.jsonpath.JsonPath
import com.jayway.jsonpath.PathNotFoundException
import org.apache.http.util.EntityUtils
import org.elasticsearch.client.Response
import org.slf4j.LoggerFactory
fun Response.contentToString(): String {
return EntityUtils.toString(this.entity)
}
fun Response.getField(jsonPath: String): Int? {
return try {
JsonPath.read(this.contentToString(), jsonPath)
} catch (e: PathNotFoundException) {
null
}
}
fun String.getJsonField(jsonPath: String): Int? {
return try {
JsonPath.read(this, jsonPath)
} catch (e: PathNotFoundException) {
LoggerFactory.getLogger("fr.dcproject.utils.getJsonField")
.warn("No value for Json path ${JsonPath.compile(jsonPath).path}")
null
}
}

View File

@@ -0,0 +1,10 @@
package fr.dcproject.common.utils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
internal class LoggerDelegate<in R : Any> : ReadOnlyProperty<R, Logger> {
override fun getValue(thisRef: R, property: KProperty<*>): Logger = LoggerFactory.getLogger(thisRef.javaClass.packageName)
}

View File

@@ -0,0 +1,23 @@
package fr.dcproject.common.utils
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
import io.ktor.application.ApplicationCall
import io.ktor.application.log
import io.ktor.features.BadRequestException
import io.ktor.request.receive
import kotlin.reflect.typeOf
/**
* Receives content for this request.
* @param type instance of `KClass` specifying type to be received.
* @return instance of [T] received from this call, or `null` if content cannot be transformed to the requested type..
*/
@OptIn(ExperimentalStdlibApi::class)
public suspend inline fun <reified T : Any> ApplicationCall.receiveOrBadRequest(message: String = "Bad Request, wrong body request"): T {
return try {
receive<T>(typeOf<T>())
} catch (cause: MissingKotlinParameterException) {
application.log.debug("Conversion failed, throw bad exeption", cause)
throw BadRequestException(message, cause)
}
}

View File

@@ -0,0 +1,7 @@
package fr.dcproject.common.utils
fun String.readResource(callback: (String) -> Unit = {}): String {
val content = callback::class.java.getResource(this).readText()
callback(content)
return content
}

View File

@@ -0,0 +1,11 @@
package fr.dcproject.common.utils
import java.util.UUID
fun String.toUUID(): UUID = UUID.fromString(this.trim())
fun List<String?>.toUUID(): List<UUID> = this
.filterNotNull()
.map { it.trim() }
.filter { it.isNotBlank() }
.map { UUID.fromString(it) }

View File

@@ -0,0 +1,28 @@
package fr.dcproject.common.utils
import org.elasticsearch.client.Request
import org.elasticsearch.client.RestClient
import org.slf4j.Logger
import org.slf4j.LoggerFactory
fun RestClient.waitElasticsearchIsUp() {
val logger: Logger = LoggerFactory.getLogger("fr.dcproject.elasticsearch")
val request = Request("GET", "/_cluster/health")
repeat(5 * 60 / 2) { // 5 minutes
runCatching {
performRequest(request).statusLine.statusCode
}.onSuccess {
if (it == 200) {
logger.debug("Elasticsearch is Ready! Continue...")
return
} else {
logger.debug("sleep 2s and retry...")
Thread.sleep(2000)
}
}.onFailure {
logger.debug("${it.message}, sleep 2s and retry...")
Thread.sleep(2000)
}
}
error("Elasticsearch is not ready")
}