Compare commits
43 Commits
notificati
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c013e3e15 | |||
| 003aa9ff05 | |||
| 44c5f1b15b | |||
| 2f3a2f9b8e | |||
| 1a62c5ec9a | |||
| 55674cbef1 | |||
| ac5dbbc384 | |||
| 9460992f37 | |||
| 8013cfb266 | |||
| f577ea70b3 | |||
| 2673cf527a | |||
| 371483ccde | |||
| 76e4033a22 | |||
| fee5e5784b | |||
| 1c33c026f0 | |||
| 4871e7d780 | |||
| 359450ad8f | |||
| 11903a4cda | |||
| 1a8b544cdb | |||
| e2c1f15ab8 | |||
| dc87c95bb4 | |||
| d4cc3f21da | |||
| 59c050d14d | |||
| cb9bd0c14b | |||
| 1ce3a18d8e | |||
| be6fb8dc12 | |||
| cd9fd569d7 | |||
| 2920186352 | |||
| cccabb2cc9 | |||
| 620cd73fec | |||
| e474a40068 | |||
| 242bf9c9b3 | |||
| 543f3fb9bb | |||
| 994e266b52 | |||
| 3f392eece6 | |||
| 518b59e9aa | |||
| 596b7ff0c9 | |||
| 87175eb8ea | |||
| 1118866856 | |||
| 367f59ee18 | |||
| 0588f88f9a | |||
| 496cf50d88 | |||
| 13253e4af1 |
76
.github/workflows/tests.yml
vendored
76
.github/workflows/tests.yml
vendored
@@ -4,6 +4,9 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
@@ -18,6 +21,10 @@ jobs:
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-version: '7.4'
|
||||
|
||||
- name: Cache Gradle packages
|
||||
uses: actions/cache@v2
|
||||
@@ -29,26 +36,17 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Build
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-version: 6.8
|
||||
gradle-version: '7.4'
|
||||
arguments: build -x test -x ktlintKotlinScriptCheck -x ktlintTestSourceSetCheck -x ktlintMainSourceSetCheck -x detekt
|
||||
- name: Cleanup Gradle Cache
|
||||
# Remove some files from the Gradle cache, so they aren't cached by GitHub Actions.
|
||||
# Restoring these files from a GitHub Actions cache might cause problems for future builds.
|
||||
run: |
|
||||
rm -f ~/.gradle/caches/modules-2/modules-2.lock
|
||||
rm -f ~/.gradle/caches/modules-2/gc.properties
|
||||
|
||||
- name: processResources
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
with:
|
||||
gradle-version: 6.8
|
||||
arguments: processResources
|
||||
run: gradle processResources
|
||||
- name: processTestResources
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-version: 6.8
|
||||
gradle-version: '7.4'
|
||||
arguments: processResources
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@@ -69,10 +67,17 @@ jobs:
|
||||
with:
|
||||
name: Build
|
||||
path: build
|
||||
- name: TestSql
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
|
||||
- name: Composer Up
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-version: 6.8
|
||||
gradle-version: '7.4'
|
||||
arguments: testSqlComposeUp
|
||||
|
||||
- name: TestSql
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-version: '7.4'
|
||||
arguments: testSql
|
||||
|
||||
test:
|
||||
@@ -81,37 +86,58 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: Build
|
||||
path: build
|
||||
|
||||
- name: Composer Up
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-version: '7.4'
|
||||
arguments: testComposeUp
|
||||
|
||||
- name: Test
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-version: 6.8
|
||||
arguments: test -x testSql
|
||||
gradle-version: '7.4'
|
||||
arguments: test
|
||||
|
||||
- name: Coverage
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-version: 6.8
|
||||
gradle-version: '7.4'
|
||||
arguments: coveralls
|
||||
env:
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.sonar/cache
|
||||
key: ${{ runner.os }}-sonar
|
||||
restore-keys: ${{ runner.os }}-sonar
|
||||
|
||||
- name: Test
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-version: '7.4'
|
||||
arguments: test
|
||||
|
||||
- name: Build and analyze
|
||||
uses: gradle/gradle-build-action@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
run: ./gradlew build sonarqube --info
|
||||
with:
|
||||
gradle-version: '7.4'
|
||||
arguments: sonarqube --info
|
||||
|
||||
lint:
|
||||
needs: build
|
||||
@@ -128,7 +154,7 @@ jobs:
|
||||
name: Build
|
||||
path: build
|
||||
- name: Lint
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-version: 6.8
|
||||
gradle-version: '7.4'
|
||||
arguments: ktlintCheck
|
||||
|
||||
12
.idea/codeStyles/Project.xml
generated
12
.idea/codeStyles/Project.xml
generated
@@ -8,7 +8,7 @@
|
||||
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<SqlCodeStyleSettings version="5">
|
||||
<SqlCodeStyleSettings version="6">
|
||||
<option name="KEYWORD_CASE" value="1" />
|
||||
<option name="IDENTIFIER_CASE" value="1" />
|
||||
<option name="TYPE_CASE" value="4" />
|
||||
@@ -56,21 +56,13 @@
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
|
||||
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||
<option name="CALL_PARAMETERS_WRAP" value="5" />
|
||||
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="METHOD_PARAMETERS_WRAP" value="5" />
|
||||
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
|
||||
<option name="ASSIGNMENT_WRAP" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
|
||||
@@ -25,12 +25,9 @@
|
||||
<option value="openapi" />
|
||||
<option value="rabbitmq" />
|
||||
<option value="redis" />
|
||||
<option value="sonarqube" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||
<option name="upExitCodeFromService" value="" />
|
||||
<option name="upTimeout" value="" />
|
||||
</settings>
|
||||
</deployment>
|
||||
<method v="2" />
|
||||
|
||||
2
.idea/runConfigurations/Build_without_test.xml
generated
2
.idea/runConfigurations/Build_without_test.xml
generated
@@ -4,7 +4,7 @@
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="-x test -x ktlintKotlinScriptCheck -x ktlintTestSourceSetCheck -x ktlintMainSourceSetCheck" />
|
||||
<option name="scriptParameters" value="-x test -x ktlintKotlinScriptCheck -x ktlintTestSourceSetCheck -x ktlintMainSourceSetCheck -x detekt" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
|
||||
23
.idea/runConfigurations/Create_lock_dependencies.xml
generated
Normal file
23
.idea/runConfigurations/Create_lock_dependencies.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Create lock dependencies" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="--write-locks" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="dependencies" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
4
.idea/runConfigurations/Run_dependencies.xml
generated
4
.idea/runConfigurations/Run_dependencies.xml
generated
@@ -24,13 +24,9 @@
|
||||
<option value="rabbitmq" />
|
||||
<option value="redis" />
|
||||
<option value="openapi" />
|
||||
<option value="sonarqube" />
|
||||
<option value="sonarqube_db" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||
<option name="upExitCodeFromService" value="" />
|
||||
<option name="upTimeout" value="" />
|
||||
</settings>
|
||||
</deployment>
|
||||
<method v="2" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Sonarqube without test" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<configuration default="false" name="Sonarqube (Send without run test)" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
23
.idea/runConfigurations/Test_With_Dependencies.xml
generated
Normal file
23
.idea/runConfigurations/Test_With_Dependencies.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Test With Dependencies" 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="testWithDependencies" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
23
.idea/runConfigurations/Update_Dependency.xml
generated
Normal file
23
.idea/runConfigurations/Update_Dependency.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Update Dependency" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="--update-locks *:*" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="classes" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" value="" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
116
build.gradle.kts
116
build.gradle.kts
@@ -9,12 +9,12 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.owasp.dependencycheck.reporting.ReportGenerator
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
val ktorVersion = "1.5.0"
|
||||
val kotlinVersion = "1.4.30"
|
||||
val coroutinesVersion = "1.4.3"
|
||||
val ktorVersion = "1.5.4"
|
||||
val kotlinVersion = "1.5.31"
|
||||
val coroutinesVersion = "1.5.2"
|
||||
val logbackVersion = "1.2.3"
|
||||
val koinVersion = "2.0.1"
|
||||
val jacksonVersion = "2.12.1"
|
||||
val koinVersion = "3.1.5"
|
||||
val jacksonVersion = "2.13.1"
|
||||
|
||||
group = "com.github.flecomte"
|
||||
version = versioning.info.run {
|
||||
@@ -28,20 +28,24 @@ version = versioning.info.run {
|
||||
plugins {
|
||||
jacoco
|
||||
application
|
||||
maven
|
||||
`maven-publish`
|
||||
|
||||
id("maven-publish")
|
||||
kotlin("jvm") version "1.4.30"
|
||||
kotlin("plugin.serialization") version "1.4.30"
|
||||
kotlin("jvm") version "1.5.31"
|
||||
kotlin("plugin.serialization") version "1.5.31"
|
||||
|
||||
id("com.github.johnrengelman.shadow") version "5.2.0"
|
||||
id("org.jlleitschuh.gradle.ktlint") version "9.4.1"
|
||||
id("org.owasp.dependencycheck") version "6.1.1"
|
||||
id("org.sonarqube") version "3.1.1"
|
||||
id("net.nemerosa.versioning") version "2.14.0"
|
||||
id("io.gitlab.arturbosch.detekt") version "1.16.0-RC1"
|
||||
id("com.avast.gradle.docker-compose") version "0.14.0"
|
||||
id("com.github.kt3k.coveralls") version "2.8.4"
|
||||
id("com.github.johnrengelman.shadow") version "7.1.2"
|
||||
id("org.jlleitschuh.gradle.ktlint") version "10.2.1"
|
||||
id("org.owasp.dependencycheck") version "6.1.5"
|
||||
id("org.sonarqube") version "3.3"
|
||||
id("net.nemerosa.versioning") version "2.15.1"
|
||||
id("io.gitlab.arturbosch.detekt") version "1.19.0"
|
||||
id("com.avast.gradle.docker-compose") version "0.15.1"
|
||||
id("com.github.kt3k.coveralls") version "2.12.0"
|
||||
}
|
||||
|
||||
dependencyLocking {
|
||||
lockAllConfigurations()
|
||||
// lockMode.set(LockMode.STRICT)
|
||||
}
|
||||
|
||||
application {
|
||||
@@ -56,7 +60,7 @@ buildscript {
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.typesafe:config:1.4.1")
|
||||
classpath("com.typesafe:config:1.4.2")
|
||||
classpath("com.github.flecomte:postgres-json:2.1.2")
|
||||
}
|
||||
}
|
||||
@@ -94,7 +98,7 @@ val migration by tasks.registering {
|
||||
}
|
||||
|
||||
val migrationTest by tasks.registering {
|
||||
group = "verification"
|
||||
group = "tests"
|
||||
dependsOn(tasks.named("testComposeUp"))
|
||||
finalizedBy(tasks.named("testComposeDown"))
|
||||
doLast {
|
||||
@@ -118,11 +122,9 @@ val migrationTest by tasks.registering {
|
||||
}
|
||||
|
||||
val testSql by tasks.registering {
|
||||
group = "verification"
|
||||
group = "tests"
|
||||
dependsOn(tasks.named("processResources"))
|
||||
dependsOn(tasks.named("processTestResources"))
|
||||
dependsOn(tasks.named("testSqlComposeUp"))
|
||||
finalizedBy(tasks.named("testSqlComposeDown"))
|
||||
|
||||
doLast {
|
||||
val config = ConfigFactory.parseFile(file("$buildDir/resources/test/application-test.conf")).resolve()
|
||||
@@ -167,6 +169,7 @@ tasks.withType<Jar> {
|
||||
)
|
||||
)
|
||||
}
|
||||
isZip64 = true
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
@@ -180,11 +183,10 @@ tasks.withType<KotlinCompile> {
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
mergeServiceFiles("META-INF/services")
|
||||
archiveFileName.set("${archiveBaseName.get()}-latest-all.${archiveExtension.get()}")
|
||||
isZip64 = true
|
||||
}
|
||||
|
||||
tasks.sonarqube.configure {
|
||||
dependsOn(tasks.test)
|
||||
dependsOn(tasks.detekt)
|
||||
dependsOn(tasks.jacocoTestReport)
|
||||
}
|
||||
|
||||
@@ -197,8 +199,7 @@ val sourcesJar by tasks.registering(Jar::class) {
|
||||
tasks.test {
|
||||
useJUnit()
|
||||
useJUnitPlatform()
|
||||
// systemProperty("junit.jupiter.execution.parallel.enabled", true)
|
||||
dependsOn(testSql)
|
||||
systemProperty("junit.jupiter.execution.parallel.enabled", true)
|
||||
finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
|
||||
}
|
||||
|
||||
@@ -206,13 +207,6 @@ coveralls {
|
||||
sourceDirs.add("src/main/kotlin")
|
||||
}
|
||||
|
||||
tasks.register("testAll") {
|
||||
group = "verification"
|
||||
dependsOn(testSql)
|
||||
dependsOn(tasks.test)
|
||||
dependsOn(tasks.ktlintCheck)
|
||||
}
|
||||
|
||||
apply(plugin = "docker-compose")
|
||||
dockerCompose {
|
||||
projectName = "dc-project"
|
||||
@@ -228,14 +222,12 @@ dockerCompose {
|
||||
useComposeFiles = listOf("docker-compose-test.yml")
|
||||
startedServices = listOf("db", "elasticsearch")
|
||||
stopContainers = false
|
||||
isRequiredBy(project.tasks.named("testSql"))
|
||||
}
|
||||
|
||||
createNested("test").apply {
|
||||
projectName = "dc-project_test"
|
||||
useComposeFiles = listOf("docker-compose-test.yml")
|
||||
stopContainers = false
|
||||
isRequiredBy(project.tasks.test)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +258,7 @@ publishing {
|
||||
}
|
||||
|
||||
jacoco {
|
||||
toolVersion = "0.8.6"
|
||||
toolVersion = "0.8.7"
|
||||
applyTo(tasks.run.get())
|
||||
}
|
||||
|
||||
@@ -320,6 +312,14 @@ tasks.named("testComposeUp").configure {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("testWithDependencies", Test::class) {
|
||||
group = "tests"
|
||||
dependsOn(tasks.named("testComposeUp"))
|
||||
dependsOn(tasks.ktlintCheck)
|
||||
dependsOn(testSql)
|
||||
dependsOn(tasks.jacocoTestReport)
|
||||
finalizedBy(tasks.sonarqube) // report is always generated after tests run
|
||||
}
|
||||
tasks.register("testArticles", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
@@ -356,6 +356,30 @@ tasks.register("testNotifications", Test::class) {
|
||||
includeTags("notification")
|
||||
}
|
||||
}
|
||||
tasks.register("testOpinions", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("opinion")
|
||||
}
|
||||
}
|
||||
tasks.register("testVotes", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("vote")
|
||||
}
|
||||
}
|
||||
tasks.register("testWorkgroups", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("workgroup")
|
||||
}
|
||||
}
|
||||
tasks.register("testViews", Test::class) {
|
||||
group = "tests"
|
||||
useJUnitPlatform {
|
||||
includeTags("view")
|
||||
}
|
||||
}
|
||||
|
||||
dependencyCheck {
|
||||
formats = listOf(ReportGenerator.Format.HTML, ReportGenerator.Format.XML)
|
||||
@@ -364,9 +388,7 @@ dependencyCheck {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven("https://kotlin.bintray.com/ktor")
|
||||
maven("https://jitpack.io")
|
||||
maven("https://dl.bintray.com/konform-kt/konform")
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -383,7 +405,7 @@ dependencies {
|
||||
implementation("io.ktor:ktor-auth:$ktorVersion")
|
||||
implementation("io.ktor:ktor-auth-jwt:$ktorVersion")
|
||||
implementation("io.ktor:ktor-websockets:$ktorVersion")
|
||||
implementation("org.koin:koin-ktor:$koinVersion")
|
||||
implementation("io.insert-koin:koin-ktor:$koinVersion")
|
||||
implementation("io.ktor:ktor-jackson:$ktorVersion")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:$jacksonVersion")
|
||||
@@ -397,18 +419,18 @@ dependencies {
|
||||
implementation("org.elasticsearch.client:elasticsearch-rest-client:6.7.1")
|
||||
implementation("com.jayway.jsonpath:json-path:2.5.0")
|
||||
implementation("com.avast.gradle:gradle-docker-compose-plugin:0.14.0")
|
||||
implementation("io.konform:konform-jvm:0.2.0")
|
||||
implementation("io.konform:konform:0.3.0")
|
||||
|
||||
testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
|
||||
testImplementation("io.ktor:ktor-client-mock:$ktorVersion")
|
||||
testImplementation("io.ktor:ktor-client-mock-jvm:$ktorVersion")
|
||||
testImplementation("org.koin:koin-test:$koinVersion")
|
||||
testImplementation("io.insert-koin:koin-test:$koinVersion")
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
|
||||
testImplementation("io.mockk:mockk:1.10.6")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.7.0")
|
||||
testImplementation("org.amshove.kluent:kluent:1.61")
|
||||
testImplementation("io.mockk:mockk-agent-api:1.10.6")
|
||||
testImplementation("io.mockk:mockk-agent-jvm:1.10.6")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter:5.8.2")
|
||||
testImplementation("org.amshove.kluent:kluent:1.68")
|
||||
testImplementation("io.mockk:mockk:1.12.2")
|
||||
testImplementation("io.mockk:mockk-agent-api:1.12.2")
|
||||
testImplementation("io.mockk:mockk-agent-jvm:1.12.2")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
|
||||
testImplementation("com.thedeanda:lorem:2.1")
|
||||
testImplementation("org.openapi4j:openapi-operation-validator:1.0.6")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version: '3.8'
|
||||
version: '3.3'
|
||||
services:
|
||||
rabbitmq:
|
||||
container_name: ${APP_NAME}_rabbitmq_test
|
||||
|
||||
275
gradle.lockfile
Normal file
275
gradle.lockfile
Normal file
@@ -0,0 +1,275 @@
|
||||
# This is a Gradle generated file for dependency locking.
|
||||
# Manual edits can break the build and are not advised.
|
||||
# This file is expected to be part of source control.
|
||||
ch.qos.logback:logback-classic:1.2.3=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
ch.qos.logback:logback-core:1.2.3=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.auth0:java-jwt:3.12.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.auth0:jwks-rsa:0.9.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.avast.gradle:gradle-docker-compose-plugin:0.14.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.beust:jcommander:1.81=detekt
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.13.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-core:2.13.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-databind:2.13.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.13.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.fasterxml.jackson.datatype:jackson-datatype-joda:2.13.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.fasterxml.jackson:jackson-bom:2.13.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.github.flecomte:postgres-json:2.1.2=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.github.jasync-sql:jasync-common:1.1.6=compileClasspath,implementationDependenciesMetadata,testCompileClasspath,testImplementationDependenciesMetadata
|
||||
com.github.jasync-sql:jasync-common:1.1.7=runtimeClasspath,testRuntimeClasspath
|
||||
com.github.jasync-sql:jasync-pool:1.1.6=compileClasspath,implementationDependenciesMetadata,testCompileClasspath,testImplementationDependenciesMetadata
|
||||
com.github.jasync-sql:jasync-pool:1.1.7=runtimeClasspath,testRuntimeClasspath
|
||||
com.github.jasync-sql:jasync-postgresql:1.1.6=compileClasspath,implementationDependenciesMetadata,testCompileClasspath,testImplementationDependenciesMetadata
|
||||
com.github.jasync-sql:jasync-postgresql:1.1.7=runtimeClasspath,testRuntimeClasspath
|
||||
com.github.shyiko.klob:klob:0.2.1=ktlint
|
||||
com.google.code.findbugs:jsr305:3.0.2=runtimeClasspath,testRuntimeClasspath
|
||||
com.google.errorprone:error_prone_annotations:2.2.0=runtimeClasspath,testRuntimeClasspath
|
||||
com.google.guava:failureaccess:1.0.1=runtimeClasspath,testRuntimeClasspath
|
||||
com.google.guava:guava:27.1-jre=runtimeClasspath,testRuntimeClasspath
|
||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=runtimeClasspath,testRuntimeClasspath
|
||||
com.google.j2objc:j2objc-annotations:1.1=runtimeClasspath,testRuntimeClasspath
|
||||
com.googlecode.json-simple:json-simple:1.1.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.jayway.jsonpath:json-path:2.5.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.ongres.scram:client:2.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.ongres.scram:common:2.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.ongres.stringprep:saslprep:1.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.ongres.stringprep:stringprep:1.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.pinterest.ktlint:ktlint-core:0.42.1=ktlint,ktlintBaselineReporter
|
||||
com.pinterest.ktlint:ktlint-reporter-baseline:0.42.1=ktlint,ktlintBaselineReporter
|
||||
com.pinterest.ktlint:ktlint-reporter-checkstyle:0.42.1=ktlint
|
||||
com.pinterest.ktlint:ktlint-reporter-html:0.42.1=ktlint
|
||||
com.pinterest.ktlint:ktlint-reporter-json:0.42.1=ktlint
|
||||
com.pinterest.ktlint:ktlint-reporter-plain:0.42.1=ktlint
|
||||
com.pinterest.ktlint:ktlint-reporter-sarif:0.42.1=ktlint
|
||||
com.pinterest.ktlint:ktlint-ruleset-experimental:0.42.1=ktlint
|
||||
com.pinterest.ktlint:ktlint-ruleset-standard:0.42.1=ktlint
|
||||
com.pinterest.ktlint:ktlint-ruleset-test:0.42.1=ktlint
|
||||
com.pinterest:ktlint:0.42.1=ktlint
|
||||
com.rabbitmq:amqp-client:5.10.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.sendgrid:java-http-client:4.3.6=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.sendgrid:sendgrid-java:4.7.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.thedeanda:lorem:2.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
com.typesafe:config:1.3.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
commons-codec:commons-codec:1.11=compileClasspath,implementationDependenciesMetadata,testCompileClasspath,testImplementationDependenciesMetadata
|
||||
commons-codec:commons-codec:1.14=runtimeClasspath,testRuntimeClasspath
|
||||
commons-io:commons-io:2.6=runtimeClasspath,testRuntimeClasspath
|
||||
commons-logging:commons-logging:1.2=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
info.picocli:picocli:3.9.6=ktlint
|
||||
io.github.detekt.sarif4k:sarif4k:0.0.1=detekt,ktlint
|
||||
io.github.microutils:kotlin-logging:1.7.6=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.gitlab.arturbosch.detekt:detekt-api:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-cli:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-core:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-metrics:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-parser:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-psi-utils:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-report-html:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-report-sarif:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-report-txt:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-report-xml:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-rules-complexity:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-rules-coroutines:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-rules-documentation:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-rules-empty:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-rules-errorprone:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-rules-exceptions:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-rules-naming:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-rules-performance:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-rules-style:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-rules:1.19.0=detekt
|
||||
io.gitlab.arturbosch.detekt:detekt-tooling:1.19.0=detekt
|
||||
io.insert-koin:koin-core-jvm:3.1.5=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.insert-koin:koin-core:3.1.5=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.insert-koin:koin-ktor:3.1.5=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.insert-koin:koin-test-jvm:3.1.5=testCompileClasspath,testRuntimeClasspath
|
||||
io.insert-koin:koin-test:3.1.5=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.konform:konform-jvm:0.3.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.konform:konform:0.3.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-auth-jwt-kotlinMultiplatform:1.5.4=implementationDependenciesMetadata,testImplementationDependenciesMetadata
|
||||
io.ktor:ktor-auth-jwt:1.5.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-auth-kotlinMultiplatform:1.5.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-auth:1.5.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-client-cio-jvm:1.5.4=testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-client-cio:1.5.4=testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-client-core-jvm:1.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-client-core:1.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-client-jetty-kotlinMultiplatform:1.5.4=implementationDependenciesMetadata,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-client-jetty:1.5.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-client-mock-jvm:1.5.4=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-client-mock:1.5.4=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-http-cio-jvm:1.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-http-cio:1.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-http-jvm:1.6.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-http:1.6.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-io-jvm:1.6.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-io:1.6.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-jackson-kotlinMultiplatform:1.5.4=implementationDependenciesMetadata,testImplementationDependenciesMetadata
|
||||
io.ktor:ktor-jackson:1.5.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-locations-kotlinMultiplatform:1.5.4=implementationDependenciesMetadata,testImplementationDependenciesMetadata
|
||||
io.ktor:ktor-locations:1.5.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-network-jvm:1.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-network-tls-certificates-kotlinMultiplatform:1.5.4=testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-network-tls-certificates:1.5.4=testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-network-tls-jvm:1.5.4=testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-network-tls:1.5.4=testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-network:1.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-server-core-kotlinMultiplatform:1.5.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-server-core:1.6.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-server-host-common-kotlinMultiplatform:1.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-server-host-common:1.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-server-jetty-kotlinMultiplatform:1.5.4=implementationDependenciesMetadata,testImplementationDependenciesMetadata
|
||||
io.ktor:ktor-server-jetty:1.5.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-server-servlet-kotlinMultiplatform:1.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-server-servlet:1.5.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-server-test-host-kotlinMultiplatform:1.5.4=testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-server-test-host:1.5.4=testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-server-tests-kotlinMultiplatform:1.5.4=testImplementationDependenciesMetadata
|
||||
io.ktor:ktor-server-tests:1.5.4=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-utils-jvm:1.6.4=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.ktor:ktor-utils:1.6.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-websockets-kotlinMultiplatform:1.5.4=implementationDependenciesMetadata,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.ktor:ktor-websockets:1.5.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.lettuce:lettuce-core:5.3.6.RELEASE=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.mockk:mockk-agent-api:1.12.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.mockk:mockk-agent-common:1.12.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.mockk:mockk-agent-jvm:1.12.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.mockk:mockk-common:1.12.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.mockk:mockk-dsl-jvm:1.12.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.mockk:mockk-dsl:1.12.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.mockk:mockk:1.12.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.netty:netty-buffer:4.1.56.Final=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.netty:netty-codec:4.1.56.Final=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.netty:netty-common:4.1.56.Final=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.netty:netty-handler:4.1.56.Final=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.netty:netty-resolver:4.1.56.Final=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.netty:netty-transport:4.1.56.Final=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
io.projectreactor:reactor-core:3.4.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
javax.servlet:javax.servlet-api:3.1.0=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
joda-time:joda-time:2.10.8=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
junit:junit:4.12=testCompileClasspath,testRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy-agent:1.12.5=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy:1.12.5=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
net.java.dev.jna:jna-platform:5.5.0=testRuntimeClasspath
|
||||
net.java.dev.jna:jna:5.5.0=testRuntimeClasspath
|
||||
net.minidev:accessors-smart:1.2=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
net.minidev:json-smart:2.3=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
net.pearx.kasechange:kasechange-jvm:1.3.0=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
net.pearx.kasechange:kasechange-metadata:1.3.0=implementationDependenciesMetadata,testImplementationDependenciesMetadata
|
||||
org.amshove.kluent:kluent-common:1.68=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.amshove.kluent:kluent:1.68=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.apache.httpcomponents:httpasyncclient:4.1.2=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.apache.httpcomponents:httpclient:4.5.12=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.apache.httpcomponents:httpcore-nio:4.4.5=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.apache.httpcomponents:httpcore:4.4.13=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath,testImplementationDependenciesMetadata
|
||||
org.bouncycastle:bcprov-jdk15on:1.67=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.checkerframework:checker-qual:2.5.2=runtimeClasspath,testRuntimeClasspath
|
||||
org.codehaus.mojo:animal-sniffer-annotations:1.17=runtimeClasspath,testRuntimeClasspath
|
||||
org.ec4j.core:ec4j-core:0.3.0=ktlint,ktlintBaselineReporter
|
||||
org.eclipse.jetty.http2:http2-client:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty.http2:http2-common:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty.http2:http2-hpack:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty.http2:http2-http-client-transport:9.4.31.v20200723=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty.http2:http2-server:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-alpn-client:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-alpn-java-client:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-alpn-java-server:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-alpn-openjdk8-client:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-alpn-openjdk8-server:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-alpn-server:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-client:9.4.31.v20200723=testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-continuation:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-http:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-io:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-server:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-servlets:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.jetty:jetty-util:9.4.31.v20200723=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.elasticsearch.client:elasticsearch-rest-client:6.7.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.fusesource.jansi:jansi:2.3.4=runtimeClasspath,testRuntimeClasspath
|
||||
org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath
|
||||
org.jacoco:org.jacoco.agent:0.8.7=jacocoAgent,jacocoAnt
|
||||
org.jacoco:org.jacoco.ant:0.8.7=jacocoAnt
|
||||
org.jacoco:org.jacoco.core:0.8.7=jacocoAnt
|
||||
org.jacoco:org.jacoco.report:0.8.7=jacocoAnt
|
||||
org.jetbrains.intellij.deps:trove4j:1.0.20181211=detekt,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,ktlint,ktlintBaselineReporter
|
||||
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.20=ktlint,ktlintBaselineReporter
|
||||
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=detekt,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
|
||||
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.20=ktlint,ktlintBaselineReporter
|
||||
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=detekt,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
|
||||
org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.5.31=kotlinKlibCommonizerClasspath
|
||||
org.jetbrains.kotlin:kotlin-native-utils:1.5.31=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-project-model:1.5.31=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-reflect:1.5.20=ktlint,ktlintBaselineReporter
|
||||
org.jetbrains.kotlin:kotlin-reflect:1.5.30=compileClasspath,implementationDependenciesMetadata,runtimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-reflect:1.5.31=detekt,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-script-runtime:1.5.20=ktlint,ktlintBaselineReporter
|
||||
org.jetbrains.kotlin:kotlin-script-runtime:1.5.31=detekt,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath
|
||||
org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-serialization:1.5.31=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-stdlib-common:1.5.20=ktlint,ktlintBaselineReporter
|
||||
org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=compileClasspath,detekt,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.0=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.20=ktlint,ktlintBaselineReporter
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.0=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.20=ktlint,ktlintBaselineReporter
|
||||
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-stdlib:1.5.20=ktlint,ktlintBaselineReporter
|
||||
org.jetbrains.kotlin:kotlin-stdlib:1.5.31=compileClasspath,detekt,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-test-annotations-common:1.5.31=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-test-common:1.5.31=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-test-junit:1.5.31=testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-test:1.5.31=testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlin:kotlin-util-io:1.5.31=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.4.2=implementationDependenciesMetadata,testImplementationDependenciesMetadata
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core-metadata:1.4.2=implementationDependenciesMetadata,testImplementationDependenciesMetadata
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-debug:1.5.2=testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.5.1-native-mt=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-reactive:1.5.2=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-reactor:1.5.2=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.3=detekt
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.0.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.1.0=detekt,ktlint
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core-metadata:1.0.1=implementationDependenciesMetadata,testImplementationDependenciesMetadata
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-core:1.1.0=detekt,ktlint
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.0.1=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.1.0=detekt,ktlint
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-json-metadata:1.0.1=implementationDependenciesMetadata,testImplementationDependenciesMetadata
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0=detekt,ktlint
|
||||
org.jetbrains:annotations:13.0=compileClasspath,detekt,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspathMain,kotlinCompilerPluginClasspathTest,kotlinKlibCommonizerClasspath,ktlint,ktlintBaselineReporter,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.joda:joda-convert:1.8.1=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:5.8.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:5.8.2=testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-params:5.8.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter:5.8.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:1.8.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:1.8.2=testRuntimeClasspath
|
||||
org.junit:junit-bom:5.8.2=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.objenesis:objenesis:3.1=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.openapi4j:openapi-core:1.0.6=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.openapi4j:openapi-operation-validator:1.0.6=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.openapi4j:openapi-parser:1.0.6=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.openapi4j:openapi-schema-validator:1.0.6=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.opentest4j:opentest4j:1.2.0=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.ow2.asm:asm-analysis:9.1=jacocoAnt
|
||||
org.ow2.asm:asm-commons:9.1=jacocoAnt
|
||||
org.ow2.asm:asm-tree:9.1=jacocoAnt
|
||||
org.ow2.asm:asm:5.0.4=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.ow2.asm:asm:9.1=jacocoAnt
|
||||
org.reactivestreams:reactive-streams:1.0.3=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.slf4j:slf4j-api:1.7.30=compileClasspath,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.yaml:snakeyaml:1.27=runtimeClasspath
|
||||
org.yaml:snakeyaml:1.28=testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
|
||||
org.yaml:snakeyaml:1.29=detekt
|
||||
empty=annotationProcessor,apiDependenciesMetadata,compileOnly,compileOnlyDependenciesMetadata,detektPlugins,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinNativeCompilerPluginClasspath,kotlinScriptDef,kotlinScriptDefExtensions,ktlintReporter,ktlintRuleset,runtimeOnlyDependenciesMetadata,shadow,testAnnotationProcessor,testApiDependenciesMetadata,testCompileOnly,testCompileOnlyDependenciesMetadata,testIntransitiveDependenciesMetadata,testKotlinScriptDef,testKotlinScriptDefExtensions,testRuntimeOnlyDependenciesMetadata
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.datatype.joda.JodaModule
|
||||
import fr.dcproject.application.Env.PROD
|
||||
import fr.dcproject.application.Env.TEST
|
||||
import fr.dcproject.application.http.statusPagesInstallation
|
||||
import fr.dcproject.common.utils.onApplicationStopped
|
||||
import fr.dcproject.component.article.articleKoinModule
|
||||
import fr.dcproject.component.article.routes.installArticleRoutes
|
||||
import fr.dcproject.component.auth.authKoinModule
|
||||
@@ -25,8 +26,10 @@ import fr.dcproject.component.constitution.routes.installConstitutionRoutes
|
||||
import fr.dcproject.component.doc.routes.installDocRoutes
|
||||
import fr.dcproject.component.follow.followKoinModule
|
||||
import fr.dcproject.component.follow.routes.article.installFollowArticleRoutes
|
||||
import fr.dcproject.component.follow.routes.citizen.installFollowCitizenRoutes
|
||||
import fr.dcproject.component.follow.routes.constitution.installFollowConstitutionRoutes
|
||||
import fr.dcproject.component.notification.NotificationConsumer
|
||||
import fr.dcproject.component.notification.email.NotificationEmailConsumer
|
||||
import fr.dcproject.component.notification.push.NotificationPushConsumer
|
||||
import fr.dcproject.component.notification.routes.installNotificationsRoutes
|
||||
import fr.dcproject.component.opinion.opinionKoinModule
|
||||
import fr.dcproject.component.opinion.routes.installOpinionRoutes
|
||||
@@ -37,7 +40,6 @@ import fr.dcproject.component.workgroup.routes.installWorkgroupRoutes
|
||||
import fr.dcproject.component.workgroup.workgroupKoinModule
|
||||
import fr.postgresjson.migration.Migrations
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.ApplicationStopped
|
||||
import io.ktor.application.install
|
||||
import io.ktor.auth.Authentication
|
||||
import io.ktor.client.HttpClient
|
||||
@@ -57,7 +59,6 @@ import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Locations
|
||||
import io.ktor.routing.Routing
|
||||
import io.ktor.server.jetty.EngineMain
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import io.ktor.websocket.WebSockets
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import org.eclipse.jetty.util.log.Slf4jLog
|
||||
@@ -72,7 +73,6 @@ fun main(args: Array<String>): Unit = EngineMain.main(args)
|
||||
enum class Env { PROD, TEST }
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@KtorExperimentalAPI
|
||||
@KtorExperimentalLocationsAPI
|
||||
@Suppress("unused") // Referenced in application.conf
|
||||
fun Application.module(env: Env = PROD) {
|
||||
@@ -117,11 +117,14 @@ fun Application.module(env: Env = PROD) {
|
||||
masking = false
|
||||
}
|
||||
|
||||
get<NotificationConsumer>().run {
|
||||
get<NotificationEmailConsumer>().run {
|
||||
start()
|
||||
environment.monitor.subscribe(ApplicationStopped) {
|
||||
close()
|
||||
onApplicationStopped { close() }
|
||||
}
|
||||
|
||||
get<NotificationPushConsumer>().run {
|
||||
start()
|
||||
onApplicationStopped { close() }
|
||||
}
|
||||
|
||||
install(Authentication, jwtInstallation(get(), get()))
|
||||
@@ -154,6 +157,7 @@ fun Application.module(env: Env = PROD) {
|
||||
installCommentRoutes()
|
||||
installFollowArticleRoutes()
|
||||
installFollowConstitutionRoutes()
|
||||
installFollowCitizenRoutes()
|
||||
installWorkgroupRoutes()
|
||||
installOpinionRoutes()
|
||||
installVoteRoutes()
|
||||
|
||||
@@ -5,20 +5,10 @@ import fr.dcproject.application.http.HttpErrorBadRequest
|
||||
import fr.dcproject.application.http.HttpErrorBadRequest.InvalidParam
|
||||
import io.ktor.features.DataConversion
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import org.koin.core.context.GlobalContext
|
||||
import org.koin.core.parameter.ParametersDefinition
|
||||
import org.koin.core.qualifier.Qualifier
|
||||
import java.util.UUID
|
||||
|
||||
private typealias ConverterDeclaration = DataConversion.Configuration.() -> Unit
|
||||
|
||||
private inline fun <reified T> DataConversion.Configuration.get(
|
||||
qualifier: Qualifier? = null,
|
||||
noinline parameters: ParametersDefinition? = null
|
||||
): T = GlobalContext.get().koin.rootScope.get(qualifier, parameters)
|
||||
|
||||
@KtorExperimentalAPI
|
||||
val converters: ConverterDeclaration = {
|
||||
convert<UUID> {
|
||||
decode { values, _ ->
|
||||
|
||||
@@ -10,21 +10,20 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.rabbitmq.client.ConnectionFactory
|
||||
import fr.dcproject.common.email.Mailer
|
||||
import fr.dcproject.component.auth.jwt.JwtConfig
|
||||
import fr.dcproject.component.notification.NotificationConsumer
|
||||
import fr.dcproject.component.notification.NotificationEmailSender
|
||||
import fr.dcproject.component.notification.NotificationsPush
|
||||
import fr.dcproject.component.notification.Publisher
|
||||
import fr.dcproject.component.notification.NotificationPublisherAsync
|
||||
import fr.dcproject.component.notification.email.NotificationEmailConsumer
|
||||
import fr.dcproject.component.notification.email.NotificationEmailSender
|
||||
import fr.dcproject.component.notification.push.NotificationPushConsumer
|
||||
import fr.dcproject.component.notification.push.NotificationPushListener
|
||||
import fr.postgresjson.connexion.Connection
|
||||
import fr.postgresjson.connexion.Requester
|
||||
import fr.postgresjson.migration.Migrations
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.features.websocket.WebSockets
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import io.lettuce.core.RedisClient
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
|
||||
@KtorExperimentalAPI
|
||||
val KoinModule = module {
|
||||
// JWT
|
||||
single {
|
||||
@@ -65,11 +64,15 @@ val KoinModule = module {
|
||||
}
|
||||
}
|
||||
|
||||
single { NotificationsPush.Builder(get()) }
|
||||
single { NotificationPushListener.Builder(get()) }
|
||||
|
||||
single {
|
||||
val config: Configuration = get()
|
||||
NotificationConsumer(get(), get(), get(), get(), get(), config.exchangeNotificationName)
|
||||
NotificationEmailConsumer(get(), get(), get(), get(), get(), config.exchangeNotificationName)
|
||||
}
|
||||
single {
|
||||
val config: Configuration = get()
|
||||
NotificationPushConsumer(get(), get(), get(), get(), get(), config.exchangeNotificationName)
|
||||
}
|
||||
|
||||
// RabbitMQ
|
||||
@@ -114,7 +117,7 @@ val KoinModule = module {
|
||||
|
||||
single {
|
||||
val config: Configuration = get()
|
||||
Publisher(factory = get(), exchangeName = config.exchangeNotificationName)
|
||||
NotificationPublisherAsync(factory = get(), exchangeName = config.exchangeNotificationName)
|
||||
}
|
||||
|
||||
single {
|
||||
|
||||
@@ -9,6 +9,9 @@ import java.io.IOException
|
||||
class Mailer(
|
||||
private val key: String
|
||||
) {
|
||||
/**
|
||||
* Send email via Sendgrid
|
||||
*/
|
||||
fun sendEmail(action: () -> Mail): Boolean {
|
||||
val mail = action()
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package fr.dcproject.common.entity
|
||||
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.citizen.database.CitizenI
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.comment.generic.database.CommentRef
|
||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||
import fr.dcproject.component.opinion.database.OpinionRef
|
||||
@@ -34,7 +35,8 @@ interface TargetI : EntityI {
|
||||
Article("article"),
|
||||
Constitution("constitution"),
|
||||
Comment("comment"),
|
||||
Opinion("opinion")
|
||||
Opinion("opinion"),
|
||||
Citizen("citizen"),
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -44,6 +46,7 @@ interface TargetI : EntityI {
|
||||
t.isSubclassOf(ConstitutionRef::class) -> TargetName.Constitution.targetReference
|
||||
t.isSubclassOf(CommentRef::class) -> TargetName.Comment.targetReference
|
||||
t.isSubclassOf(OpinionRef::class) -> TargetName.Opinion.targetReference
|
||||
t.isSubclassOf(CitizenRef::class) -> TargetName.Citizen.targetReference
|
||||
else -> throw error("target not implemented: ${t.qualifiedName} \nImplement it or return 'reference' from SQL")
|
||||
}
|
||||
}
|
||||
|
||||
10
src/main/kotlin/fr/dcproject/common/utils/Ktor.kt
Normal file
10
src/main/kotlin/fr/dcproject/common/utils/Ktor.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
package fr.dcproject.common.utils
|
||||
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.ApplicationStopped
|
||||
|
||||
fun Application.onApplicationStopped(callback: Application.() -> Unit) {
|
||||
environment.monitor.subscribe(ApplicationStopped) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
30
src/main/kotlin/fr/dcproject/common/utils/RabbitConsume.kt
Normal file
30
src/main/kotlin/fr/dcproject/common/utils/RabbitConsume.kt
Normal file
@@ -0,0 +1,30 @@
|
||||
package fr.dcproject.common.utils
|
||||
|
||||
import com.rabbitmq.client.AMQP
|
||||
import com.rabbitmq.client.Channel
|
||||
import com.rabbitmq.client.Consumer
|
||||
import com.rabbitmq.client.DefaultConsumer
|
||||
import com.rabbitmq.client.Envelope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.IOException
|
||||
|
||||
fun Channel.consumeQueue(queueName: String, callback: DefaultConsumer.(ByteArray) -> Unit) {
|
||||
val consumer: Consumer = object : DefaultConsumer(this) {
|
||||
@Throws(IOException::class)
|
||||
override fun handleDelivery(
|
||||
consumerTag: String,
|
||||
envelope: Envelope,
|
||||
properties: AMQP.BasicProperties,
|
||||
body: ByteArray
|
||||
) = runBlocking {
|
||||
try {
|
||||
callback(body)
|
||||
basicAck(envelope.deliveryTag, false)
|
||||
} catch (e: Throwable) {
|
||||
basicNack(envelope.deliveryTag, false, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Launch Consumer */
|
||||
basicConsume(queueName, false, consumer)
|
||||
}
|
||||
41
src/main/kotlin/fr/dcproject/common/utils/retry.kt
Normal file
41
src/main/kotlin/fr/dcproject/common/utils/retry.kt
Normal file
@@ -0,0 +1,41 @@
|
||||
package fr.dcproject.common.utils
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
@ExperimentalTime
|
||||
fun <T> retry(numOfRetries: Int, duration: Duration = Duration.ZERO, block: (RetryContext) -> T): T {
|
||||
val logger: Logger = LoggerFactory.getLogger("fr.dcproject.utils.retry")
|
||||
var throwable: Throwable? = null
|
||||
for (attempt in 1..numOfRetries) {
|
||||
val context = RetryContext()
|
||||
try {
|
||||
val output = block(context)
|
||||
if (context.hasStop()) {
|
||||
break
|
||||
}
|
||||
return output
|
||||
} catch (e: Throwable) {
|
||||
throwable = e
|
||||
logger.debug("Failed attempt $attempt / $numOfRetries. Wait ${duration.inSeconds} seconds")
|
||||
Thread.sleep(duration.inMilliseconds.toLong())
|
||||
} finally {
|
||||
if (context.hasStop()) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
throw throwable!!
|
||||
}
|
||||
|
||||
class RetryContext() {
|
||||
var stoped = false
|
||||
|
||||
fun stop() {
|
||||
stoped = true
|
||||
}
|
||||
|
||||
fun hasStop(): Boolean = stoped
|
||||
}
|
||||
15
src/main/kotlin/fr/dcproject/common/validation/Url.kt
Normal file
15
src/main/kotlin/fr/dcproject/common/validation/Url.kt
Normal file
@@ -0,0 +1,15 @@
|
||||
package fr.dcproject.common.validation
|
||||
|
||||
import io.konform.validation.ValidationBuilder
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
|
||||
fun ValidationBuilder<String>.isUrl() =
|
||||
addConstraint("is not url") {
|
||||
try {
|
||||
URL(it)
|
||||
true
|
||||
} catch (e: MalformedURLException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ class ArticleAccessControl(private val articleRepo: ArticleRepository) : AccessC
|
||||
if (subject.createdBy.id == citizen.id) {
|
||||
/* The creator must be the same of the creator of preview version of article */
|
||||
val lastVersionId = articleRepo
|
||||
.findVersionsByVersionId(1, 1, subject.versionId)
|
||||
.findSiblingVersions(1, 1, subject)
|
||||
.result
|
||||
.firstOrNull()?.createdBy?.id
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ class ArticleForListing(
|
||||
ArticleRef(id),
|
||||
ArticleAuthI<CitizenCartI>,
|
||||
Votable by VotableImp(),
|
||||
CreatedAt by CreatedAt.Imp(),
|
||||
CreatedBy<CitizenCartI>
|
||||
|
||||
interface ArticleForListingI : ArticleWithTitleI, CreatedBy<CitizenCartI> {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.article.database
|
||||
|
||||
import fr.dcproject.common.entity.VersionableId
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
import fr.postgresjson.connexion.Requester
|
||||
import fr.postgresjson.entity.Parameter
|
||||
@@ -19,10 +20,10 @@ class ArticleRepository(override var requester: Requester) : RepositoryI {
|
||||
.select(page, limit, "id" to id)
|
||||
}
|
||||
|
||||
fun findVersionsByVersionId(page: Int = 1, limit: Int = 50, versionId: UUID): Paginated<ArticleForListing> {
|
||||
fun <A> findSiblingVersions(page: Int = 1, limit: Int = 50, article: A): Paginated<ArticleForListing> where A : VersionableId, A : ArticleI {
|
||||
return requester
|
||||
.getFunction("find_articles_versions_by_version_id")
|
||||
.select(page, limit, "version_id" to versionId)
|
||||
.select(page, limit, "version_id" to article.versionId)
|
||||
}
|
||||
|
||||
fun find(
|
||||
|
||||
@@ -83,6 +83,7 @@ object FindArticles {
|
||||
object {
|
||||
val id = it.id
|
||||
val title = it.title
|
||||
val createdAt = it.createdAt
|
||||
val createdBy: Any = it.createdBy.toOutput()
|
||||
val workgroup = it.workgroup?.let {
|
||||
object {
|
||||
|
||||
@@ -10,8 +10,8 @@ import fr.dcproject.component.article.routes.UpsertArticle.UpsertArticleRequest.
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.notification.ArticleUpdateNotification
|
||||
import fr.dcproject.component.notification.Publisher
|
||||
import fr.dcproject.component.notification.ArticleUpdateNotificationMessage
|
||||
import fr.dcproject.component.notification.NotificationPublisherAsync
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRef
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maxItems
|
||||
@@ -63,7 +63,7 @@ object UpsertArticle {
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.upsertArticle(repo: ArticleRepository, publisher: Publisher, ac: ArticleAccessControl) {
|
||||
fun Route.upsertArticle(repo: ArticleRepository, notificationPublisher: NotificationPublisherAsync, ac: ArticleAccessControl) {
|
||||
suspend fun ApplicationCall.convertRequestToEntity(): ArticleForUpdate = receiveOrBadRequest<Input>().run {
|
||||
validate().badRequestIfNotValid()
|
||||
ArticleForUpdate(
|
||||
@@ -92,7 +92,7 @@ object UpsertArticle {
|
||||
val versionNumber = a.versionNumber
|
||||
}
|
||||
)
|
||||
publisher.publish(ArticleUpdateNotification(a))
|
||||
notificationPublisher.publishAsync(ArticleUpdateNotificationMessage(a))
|
||||
} ?: error("Article not updated")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,13 @@ private val citizenAttributeKey = AttributeKey<CitizenEntity>("CitizenContext")
|
||||
val ApplicationCall.citizen: CitizenEntity
|
||||
get() = attributes.computeIfAbsent(citizenAttributeKey) {
|
||||
val user = authentication.principal<UserI>() ?: throw ForbiddenException("No User Connected")
|
||||
GlobalContext.get().koin.get<CitizenRepository>().findByUser(user)
|
||||
GlobalContext.get().get<CitizenRepository>().findByUser(user)
|
||||
?: throw ForbiddenException("Citizen not found for this user id \"${user.id}\"")
|
||||
}
|
||||
|
||||
val ApplicationCall.citizenOrNull: CitizenEntity?
|
||||
get() = authentication.principal<UserI>()?.let {
|
||||
GlobalContext.get().koin.get<CitizenRepository>().findByUser(it)
|
||||
GlobalContext.get().get<CitizenRepository>().findByUser(it)
|
||||
}
|
||||
|
||||
val ApplicationCall.isAuth: Boolean
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.koin.core.context.GlobalContext
|
||||
/**
|
||||
* Produce a token for this combination of User and Account
|
||||
*/
|
||||
fun UserI.makeToken(): String = GlobalContext.get().koin.get<JwtConfig>().run {
|
||||
fun UserI.makeToken(): String = GlobalContext.get().get<JwtConfig>().run {
|
||||
JWT.create()
|
||||
.withSubject("Authentication")
|
||||
.withIssuer(issuer)
|
||||
|
||||
@@ -2,8 +2,9 @@ package fr.dcproject.component.citizen.database
|
||||
|
||||
import fr.dcproject.common.entity.CreatedAt
|
||||
import fr.dcproject.common.entity.DeletedAt
|
||||
import fr.dcproject.common.entity.Entity
|
||||
import fr.dcproject.common.entity.EntityI
|
||||
import fr.dcproject.common.entity.TargetI
|
||||
import fr.dcproject.common.entity.TargetRef
|
||||
import fr.dcproject.component.auth.database.User
|
||||
import fr.dcproject.component.auth.database.UserCreator
|
||||
import fr.dcproject.component.auth.database.UserForCreate
|
||||
@@ -95,10 +96,10 @@ open class CitizenRefWithUser(
|
||||
|
||||
open class CitizenRef(
|
||||
id: UUID = UUID.randomUUID()
|
||||
) : Entity(id),
|
||||
) : TargetRef(id),
|
||||
CitizenI
|
||||
|
||||
interface CitizenI : EntityI {
|
||||
interface CitizenI : EntityI, TargetI {
|
||||
data class Name(
|
||||
override val firstName: String,
|
||||
override val lastName: String,
|
||||
|
||||
@@ -21,7 +21,7 @@ abstract class CommentRepositoryAbs<T : TargetI>(override var requester: Request
|
||||
): Paginated<CommentForView<T, CitizenCreatorI>>
|
||||
|
||||
open fun findByParent(
|
||||
parent: CommentForView<T, CitizenCreatorI>,
|
||||
parent: CommentI,
|
||||
page: Int = 1,
|
||||
limit: Int = 50
|
||||
): Paginated<CommentForView<T, CitizenCreatorI>> {
|
||||
|
||||
@@ -4,6 +4,7 @@ import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.comment.generic.CommentAccessControl
|
||||
import fr.dcproject.component.comment.generic.database.CommentRef
|
||||
import fr.dcproject.component.comment.generic.database.CommentRepository
|
||||
import fr.dcproject.component.comment.toOutput
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
@@ -21,11 +22,13 @@ import java.util.UUID
|
||||
object GetCommentChildren {
|
||||
@Location("/comments/{comment}/children")
|
||||
class CommentChildrenRequest(
|
||||
val comment: UUID,
|
||||
comment: UUID,
|
||||
page: Int = 1,
|
||||
limit: Int = 50,
|
||||
val search: String? = null
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit)
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit) {
|
||||
val comment = CommentRef(comment)
|
||||
}
|
||||
|
||||
fun Route.getChildrenComments(repo: CommentRepository, ac: CommentAccessControl) {
|
||||
get<CommentChildrenRequest> {
|
||||
|
||||
@@ -7,10 +7,8 @@ import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.get
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
@KtorExperimentalAPI
|
||||
fun Route.definition() {
|
||||
get("/") {
|
||||
call.respondText("/openapi.yaml".readResource(), ContentType("text", "yaml"))
|
||||
|
||||
@@ -3,10 +3,8 @@ package fr.dcproject.component.doc.routes
|
||||
import io.ktor.auth.authenticate
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.routing.Routing
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
||||
@KtorExperimentalAPI
|
||||
@ExperimentalCoroutinesApi
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Routing.installDocRoutes() {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package fr.dcproject.component.follow
|
||||
|
||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||
import org.koin.dsl.module
|
||||
|
||||
val followKoinModule = module {
|
||||
single { FollowArticleRepository(get()) }
|
||||
single { FollowConstitutionRepository(get()) }
|
||||
single { FollowCitizenRepository(get()) }
|
||||
single { FollowAccessControl() }
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import fr.dcproject.common.entity.Entity
|
||||
import fr.dcproject.common.entity.TargetRef
|
||||
import fr.dcproject.component.article.database.ArticleForView
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
import fr.dcproject.component.citizen.database.Citizen
|
||||
import fr.dcproject.component.citizen.database.CitizenI
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.constitution.database.ConstitutionForView
|
||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
@@ -72,21 +74,24 @@ sealed class FollowRepository<IN : TargetRef, OUT : TargetRef>(override var requ
|
||||
target: Entity,
|
||||
bulkSize: Int = 300
|
||||
): Flow<FollowForView<IN>> = flow {
|
||||
var nextPage = 1
|
||||
do {
|
||||
val paginate = findFollowsByTarget(target, nextPage, bulkSize)
|
||||
paginate.result.forEach {
|
||||
var lastId: UUID? = null
|
||||
while (true) {
|
||||
val result = findFollowsByTarget(target, lastId, bulkSize)
|
||||
if (result.count() == 0) {
|
||||
break
|
||||
}
|
||||
result.forEach {
|
||||
emit(it)
|
||||
}
|
||||
nextPage = paginate.currentPage + 1
|
||||
} while (!paginate.isLastPage())
|
||||
lastId = result.last().id
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun findFollowsByTarget(
|
||||
target: Entity,
|
||||
page: Int = 1,
|
||||
lastId: UUID?,
|
||||
limit: Int = 300
|
||||
): Paginated<FollowForView<IN>>
|
||||
): List<FollowForView<IN>>
|
||||
}
|
||||
|
||||
class FollowArticleRepository(requester: Requester) : FollowRepository<ArticleRef, ArticleForView>(requester) {
|
||||
@@ -107,14 +112,14 @@ class FollowArticleRepository(requester: Requester) : FollowRepository<ArticleRe
|
||||
|
||||
override fun findFollowsByTarget(
|
||||
target: Entity,
|
||||
page: Int,
|
||||
lastId: UUID?,
|
||||
limit: Int
|
||||
): Paginated<FollowForView<ArticleRef>> {
|
||||
): List<FollowForView<ArticleRef>> {
|
||||
return requester
|
||||
.getFunction("find_follows_article_by_target")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"start_id" to lastId,
|
||||
"limit" to limit,
|
||||
"target_id" to target.id
|
||||
)
|
||||
}
|
||||
@@ -138,9 +143,34 @@ class FollowConstitutionRepository(requester: Requester) : FollowRepository<Cons
|
||||
|
||||
override fun findFollowsByTarget(
|
||||
target: Entity,
|
||||
page: Int,
|
||||
lastId: UUID?,
|
||||
limit: Int
|
||||
): Paginated<FollowForView<ConstitutionRef>> {
|
||||
): List<FollowForView<ConstitutionRef>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
class FollowCitizenRepository(requester: Requester) : FollowRepository<CitizenRef, Citizen>(requester) {
|
||||
override fun findByCitizen(
|
||||
citizenId: UUID,
|
||||
page: Int,
|
||||
limit: Int
|
||||
): Paginated<FollowForView<Citizen>> {
|
||||
return requester.run {
|
||||
getFunction("find_follows_citizen_by_citizen")
|
||||
.select(
|
||||
page,
|
||||
limit,
|
||||
"created_by_id" to citizenId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun findFollowsByTarget(
|
||||
target: Entity,
|
||||
lastId: UUID?,
|
||||
limit: Int
|
||||
): List<FollowForView<CitizenRef>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.routes.citizen.toOutput
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -14,7 +15,6 @@ import io.ktor.locations.Location
|
||||
import io.ktor.locations.get
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import org.joda.time.DateTime
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
@@ -30,19 +30,7 @@ object GetFollowArticle {
|
||||
ac.assert { canView(follow, citizenOrNull) }
|
||||
call.respond(
|
||||
HttpStatusCode.OK,
|
||||
follow.let { f ->
|
||||
object {
|
||||
val id: UUID = f.id
|
||||
val createdBy: Any = f.createdBy.toOutput()
|
||||
val target: Any = f.target.let { t ->
|
||||
object {
|
||||
val id: UUID = t.id
|
||||
val reference: String = f.target.reference
|
||||
}
|
||||
}
|
||||
val createdAt: DateTime = f.createdAt
|
||||
}
|
||||
}
|
||||
follow.toOutput()
|
||||
)
|
||||
} ?: call.respond(HttpStatusCode.NoContent)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package fr.dcproject.component.follow.routes.citizen
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||
import fr.dcproject.component.follow.database.FollowForUpdate
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.post
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object FollowCitizen {
|
||||
@Location("/citizens/{citizen}/follows")
|
||||
class CitizenFollowRequest(citizen: UUID) {
|
||||
val citizen = CitizenRef(citizen)
|
||||
}
|
||||
|
||||
fun Route.followCitizen(repo: FollowCitizenRepository, ac: FollowAccessControl) {
|
||||
post<CitizenFollowRequest> {
|
||||
mustBeAuth()
|
||||
val follow = FollowForUpdate(target = it.citizen, createdBy = this.citizen)
|
||||
ac.assert { canCreate(follow, citizenOrNull) }
|
||||
repo.follow(follow)
|
||||
call.respond(HttpStatusCode.Created)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package fr.dcproject.component.follow.routes.citizen
|
||||
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.get
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object GetFollowCitizen {
|
||||
@Location("/citizens/{citizen}/follows")
|
||||
class CitizenFollowRequest(citizen: UUID) {
|
||||
val citizen = CitizenRef(citizen)
|
||||
}
|
||||
|
||||
fun Route.getFollowCitizen(repo: FollowCitizenRepository, ac: FollowAccessControl) {
|
||||
get<CitizenFollowRequest> {
|
||||
repo.findFollow(citizen, it.citizen)?.let { follow ->
|
||||
ac.assert { canView(follow, citizenOrNull) }
|
||||
call.respond(
|
||||
HttpStatusCode.OK,
|
||||
follow.toOutput()
|
||||
)
|
||||
} ?: call.respond(HttpStatusCode.NoContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package fr.dcproject.component.follow.routes.citizen
|
||||
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.get
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import org.joda.time.DateTime
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object GetMyFollowsCitizen {
|
||||
@Location("/citizens/{citizen}/follows/citizens")
|
||||
class CitizenFollowCitizenRequest(citizen: UUID) {
|
||||
val citizen = CitizenRef(citizen)
|
||||
}
|
||||
|
||||
fun Route.getMyFollowsCitizen(repo: FollowCitizenRepository, ac: FollowAccessControl) {
|
||||
get<CitizenFollowCitizenRequest> {
|
||||
mustBeAuth()
|
||||
val follows = repo.findByCitizen(it.citizen)
|
||||
ac.assert { canView(follows.result, citizenOrNull) }
|
||||
call.respond(
|
||||
HttpStatusCode.OK,
|
||||
follows.toOutput { f ->
|
||||
object {
|
||||
val id: UUID = f.id
|
||||
val createdBy: Any = f.createdBy.toOutput()
|
||||
val target: Any = f.target.let { t ->
|
||||
object {
|
||||
val id: UUID = t.id
|
||||
val reference: String = f.target.reference
|
||||
}
|
||||
}
|
||||
val createdAt: DateTime = f.createdAt
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package fr.dcproject.component.follow.routes.citizen
|
||||
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||
import fr.dcproject.component.follow.database.FollowForUpdate
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.delete
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object UnfollowCitizen {
|
||||
@Location("/citizens/{citizen}/follows")
|
||||
class CitizenFollowRequest(citizen: UUID) {
|
||||
val citizen = CitizenRef(citizen)
|
||||
}
|
||||
|
||||
fun Route.unfollowCitizen(repo: FollowCitizenRepository, ac: FollowAccessControl) {
|
||||
delete<CitizenFollowRequest> {
|
||||
mustBeAuth()
|
||||
val follow = FollowForUpdate(target = it.citizen, createdBy = this.citizen)
|
||||
ac.assert { canDelete(follow, citizenOrNull) }
|
||||
repo.unfollow(follow)
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package fr.dcproject.component.follow.routes.citizen
|
||||
|
||||
import fr.dcproject.component.follow.routes.citizen.FollowCitizen.followCitizen
|
||||
import fr.dcproject.component.follow.routes.citizen.GetFollowCitizen.getFollowCitizen
|
||||
import fr.dcproject.component.follow.routes.citizen.GetMyFollowsCitizen.getMyFollowsCitizen
|
||||
import fr.dcproject.component.follow.routes.citizen.UnfollowCitizen.unfollowCitizen
|
||||
import io.ktor.auth.authenticate
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.routing.Routing
|
||||
import org.koin.ktor.ext.get
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Routing.installFollowCitizenRoutes() {
|
||||
authenticate(optional = true) {
|
||||
followCitizen(get(), get())
|
||||
unfollowCitizen(get(), get())
|
||||
getFollowCitizen(get(), get())
|
||||
getMyFollowsCitizen(get(), get())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package fr.dcproject.component.follow.routes.citizen
|
||||
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.component.follow.database.FollowForView
|
||||
import org.joda.time.DateTime
|
||||
import java.util.UUID
|
||||
|
||||
fun FollowForView<*>.toOutput(): Any = this.let { f ->
|
||||
object {
|
||||
val id: UUID = f.id
|
||||
val createdBy: Any = f.createdBy.toOutput()
|
||||
val target: Any = f.target.let { t ->
|
||||
object {
|
||||
val id: UUID = t.id
|
||||
val reference: String = f.target.reference
|
||||
}
|
||||
}
|
||||
val createdAt: DateTime = f.createdAt
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||
import fr.dcproject.component.follow.FollowAccessControl
|
||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||
import fr.dcproject.component.follow.routes.citizen.toOutput
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -14,7 +15,6 @@ import io.ktor.locations.Location
|
||||
import io.ktor.locations.get
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import org.joda.time.DateTime
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
@@ -30,19 +30,7 @@ object GetFollowConstitution {
|
||||
ac.assert { canView(follow, citizenOrNull) }
|
||||
call.respond(
|
||||
HttpStatusCode.OK,
|
||||
follow.let { f ->
|
||||
object {
|
||||
val id: UUID = f.id
|
||||
val createdBy: Any = f.createdBy.toOutput()
|
||||
val target: Any = f.target.let { t ->
|
||||
object {
|
||||
val id: UUID = t.id
|
||||
val reference: String = f.target.reference
|
||||
}
|
||||
}
|
||||
val createdAt: DateTime = f.createdAt
|
||||
}
|
||||
}
|
||||
follow.toOutput()
|
||||
)
|
||||
} ?: call.respond(HttpStatusCode.NotFound)
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
package fr.dcproject.component.notification
|
||||
|
||||
import com.rabbitmq.client.AMQP.BasicProperties
|
||||
import com.rabbitmq.client.BuiltinExchangeType.DIRECT
|
||||
import com.rabbitmq.client.ConnectionFactory
|
||||
import com.rabbitmq.client.Consumer
|
||||
import com.rabbitmq.client.DefaultConsumer
|
||||
import com.rabbitmq.client.Envelope
|
||||
import fr.dcproject.common.entity.TargetRef
|
||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||
import fr.dcproject.component.follow.database.FollowForView
|
||||
import io.ktor.utils.io.errors.IOException
|
||||
import io.lettuce.core.RedisClient
|
||||
import io.lettuce.core.api.async.RedisAsyncCommands
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class NotificationConsumer(
|
||||
private val rabbitFactory: ConnectionFactory,
|
||||
private val redisClient: RedisClient,
|
||||
private val followConstitutionRepo: FollowConstitutionRepository,
|
||||
private val followArticleRepo: FollowArticleRepository,
|
||||
private val notificationEmailSender: NotificationEmailSender,
|
||||
private val exchangeName: String,
|
||||
) {
|
||||
private val redisConnection = redisClient.connect() ?: error("Unable to connect to redis")
|
||||
private val redis: RedisAsyncCommands<String, String> = redisConnection.async() ?: error("Unable to connect to redis")
|
||||
private val rabbitConnection = rabbitFactory.newConnection()
|
||||
private val rabbitChannel = rabbitConnection.createChannel()
|
||||
private val logger: Logger = LoggerFactory.getLogger(Publisher::class.qualifiedName)
|
||||
|
||||
fun close() {
|
||||
rabbitChannel.close()
|
||||
rabbitConnection.close()
|
||||
}
|
||||
|
||||
fun start() {
|
||||
/* Config Rabbit */
|
||||
rabbitFactory.newConnection().use { connection ->
|
||||
connection.createChannel().use { channel ->
|
||||
channel.queueDeclare("push", true, false, false, null)
|
||||
channel.queueDeclare("email", true, false, false, null)
|
||||
channel.exchangeDeclare(exchangeName, DIRECT, true)
|
||||
channel.queueBind("push", exchangeName, "")
|
||||
channel.queueBind("email", exchangeName, "")
|
||||
}
|
||||
}
|
||||
|
||||
/* Define Consumer */
|
||||
val consumerPush: Consumer = object : DefaultConsumer(rabbitChannel) {
|
||||
@Throws(IOException::class)
|
||||
override fun handleDelivery(
|
||||
consumerTag: String,
|
||||
envelope: Envelope,
|
||||
properties: BasicProperties,
|
||||
body: ByteArray
|
||||
) = runBlocking {
|
||||
followersFromMessage(body) {
|
||||
redis.zadd(
|
||||
"notification:${it.follow.createdBy.id}",
|
||||
it.event.id,
|
||||
it.rawMessage
|
||||
)
|
||||
}
|
||||
|
||||
rabbitChannel.basicAck(envelope.deliveryTag, false)
|
||||
}
|
||||
}
|
||||
|
||||
val consumerEmail: Consumer = object : DefaultConsumer(rabbitChannel) {
|
||||
@Throws(IOException::class)
|
||||
override fun handleDelivery(
|
||||
consumerTag: String,
|
||||
envelope: Envelope,
|
||||
properties: BasicProperties,
|
||||
body: ByteArray
|
||||
) {
|
||||
runBlocking {
|
||||
followersFromMessage(body) {
|
||||
notificationEmailSender.sendEmail(it.follow)
|
||||
logger.debug("EmailSend to: ${it.follow.createdBy.id}")
|
||||
}
|
||||
}
|
||||
rabbitChannel.basicAck(envelope.deliveryTag, false)
|
||||
}
|
||||
}
|
||||
/* Launch Consumer */
|
||||
rabbitChannel.basicConsume("push", false, consumerPush) // The front consume the redis via Websocket
|
||||
rabbitChannel.basicConsume("email", false, consumerEmail)
|
||||
}
|
||||
|
||||
private suspend fun followersFromMessage(body: ByteArray, action: suspend (DecodedMessage) -> Unit) {
|
||||
val rawMessage: String = body.toString(Charsets.UTF_8)
|
||||
val notification: EntityNotification = Notification.fromString(rawMessage)
|
||||
val follows = when (notification.type) {
|
||||
"article" -> followArticleRepo.findFollowsByTarget(notification.target)
|
||||
"constitution" -> followConstitutionRepo.findFollowsByTarget(notification.target)
|
||||
else -> error("event '${notification.type}' not implemented")
|
||||
}
|
||||
|
||||
follows.collect { action(DecodedMessage(notification, rawMessage, it)) }
|
||||
}
|
||||
|
||||
private class DecodedMessage(
|
||||
val event: EntityNotification,
|
||||
val rawMessage: String,
|
||||
val follow: FollowForView<out TargetRef>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package fr.dcproject.component.notification
|
||||
|
||||
import com.rabbitmq.client.BuiltinExchangeType.DIRECT
|
||||
import com.rabbitmq.client.ConnectionFactory
|
||||
import com.rabbitmq.client.DefaultConsumer
|
||||
import fr.dcproject.common.entity.Entity
|
||||
import fr.dcproject.common.entity.TargetRef
|
||||
import fr.dcproject.common.utils.consumeQueue
|
||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||
import fr.dcproject.component.follow.database.FollowForView
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
abstract class NotificationConsumerAbstract(
|
||||
private val rabbitFactory: ConnectionFactory,
|
||||
private val followConstitutionRepo: FollowConstitutionRepository,
|
||||
private val followArticleRepo: FollowArticleRepository,
|
||||
private val followCitizenRepo: FollowCitizenRepository,
|
||||
) {
|
||||
private val rabbitConnection = rabbitFactory.newConnection()
|
||||
private val rabbitChannel = rabbitConnection.createChannel()
|
||||
|
||||
fun close() {
|
||||
rabbitChannel.close()
|
||||
rabbitConnection.close()
|
||||
}
|
||||
|
||||
fun declareQueue(queueName: String, exchangeName: String) {
|
||||
rabbitFactory.newConnection().use { connection ->
|
||||
connection.createChannel().use { channel ->
|
||||
channel.queueDeclare(queueName, true, false, false, null)
|
||||
channel.exchangeDeclare(exchangeName, DIRECT, true)
|
||||
channel.queueBind(queueName, exchangeName, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun consumeQueue(queueName: String, callback: DefaultConsumer.(DecodedMessage<*>) -> Unit) =
|
||||
rabbitChannel.consumeQueue(queueName) { body ->
|
||||
runBlocking {
|
||||
followersFromMessage(body) {
|
||||
callback(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected suspend fun followersFromMessage(body: ByteArray, action: suspend (DecodedMessage<*>) -> Unit) {
|
||||
val rawMessage: String = body.toString(Charsets.UTF_8)
|
||||
val notification: EntityNotificationMessage<*> = NotificationMessage.fromString(rawMessage)
|
||||
val follows = when (notification.type) {
|
||||
"article" -> followArticleRepo.findFollowsByTarget(notification.target)
|
||||
"constitution" -> followConstitutionRepo.findFollowsByTarget(notification.target)
|
||||
"citizen" -> followCitizenRepo.findFollowsByTarget(notification.target)
|
||||
else -> error("event '${notification.type}' not implemented")
|
||||
}
|
||||
|
||||
follows.collect { action(DecodedMessage(notification, rawMessage, it)) }
|
||||
}
|
||||
|
||||
protected class DecodedMessage <E : Entity> (
|
||||
val event: EntityNotificationMessage<E>,
|
||||
val rawMessage: String,
|
||||
val follow: FollowForView<out TargetRef>
|
||||
)
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package fr.dcproject.component.notification
|
||||
|
||||
import com.sendgrid.helpers.mail.Mail
|
||||
import com.sendgrid.helpers.mail.objects.Content
|
||||
import com.sendgrid.helpers.mail.objects.Email
|
||||
import fr.dcproject.common.email.Mailer
|
||||
import fr.dcproject.common.entity.EntityI
|
||||
import fr.dcproject.common.entity.TargetRef
|
||||
import fr.dcproject.component.article.database.ArticleRepository
|
||||
import fr.dcproject.component.article.database.ArticleWithTitleI
|
||||
import fr.dcproject.component.citizen.database.CitizenCreatorI
|
||||
import fr.dcproject.component.citizen.database.CitizenRepository
|
||||
import fr.dcproject.component.follow.database.FollowForView
|
||||
import java.util.UUID
|
||||
|
||||
class NotificationEmailSender(
|
||||
private val mailer: Mailer,
|
||||
private val domain: String,
|
||||
private val citizenRepo: CitizenRepository,
|
||||
private val articleRepo: ArticleRepository
|
||||
) {
|
||||
fun sendEmail(follow: FollowForView<out TargetRef>) {
|
||||
val citizen = citizenRepo.findById(follow.createdBy.id) ?: noCitizen(follow.createdBy.id)
|
||||
val target = when (follow.target.reference) {
|
||||
"article" ->
|
||||
articleRepo.findById(follow.target.id) ?: noTarget(follow.target.id)
|
||||
else -> noTarget(follow.target.id)
|
||||
}
|
||||
val subject = when (follow.target.reference) {
|
||||
"article" -> """New version for article "${target.title}""""
|
||||
else -> "Notification"
|
||||
}
|
||||
mailer.sendEmail {
|
||||
Mail(
|
||||
Email("notification@$domain"),
|
||||
subject,
|
||||
Email(citizen.email),
|
||||
Content("text/plain", generateContent(citizen, target))
|
||||
).apply {
|
||||
addContent(Content("text/html", generateHtmlContent(citizen, target)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateHtmlContent(citizen: CitizenCreatorI, target: EntityI): String? {
|
||||
return when (target) {
|
||||
is ArticleWithTitleI -> """
|
||||
Hello ${citizen.name.getFullName()},<br/>
|
||||
The article "${target.title}" was updated, check it <a href="http://$domain/articles/${target.id}">here</a>
|
||||
""".trimIndent()
|
||||
else -> noTarget(target.id)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateContent(citizen: CitizenCreatorI, target: EntityI): String {
|
||||
return when (target) {
|
||||
is ArticleWithTitleI -> """
|
||||
Hello ${citizen.name.getFullName()},
|
||||
The article "${target.title}" was updated, check it here: http://$domain/articles/${target.id}
|
||||
""".trimIndent()
|
||||
else -> noTarget(target.id)
|
||||
}
|
||||
}
|
||||
|
||||
class NoCitizen(message: String) : Exception(message)
|
||||
class NoTarget(message: String) : Exception(message)
|
||||
|
||||
private fun noCitizen(id: UUID): Nothing = throw NoCitizen("No Citizen with this id : $id")
|
||||
private fun noTarget(id: UUID): Nothing = throw NoTarget("No Target with this id : $id")
|
||||
}
|
||||
@@ -11,16 +11,14 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import fr.dcproject.common.entity.Entity
|
||||
import fr.dcproject.component.article.database.ArticleForView
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.enum
|
||||
import org.joda.time.DateTime
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
|
||||
@JsonSubTypes(
|
||||
JsonSubTypes.Type(value = ArticleUpdateNotification::class, name = "article")
|
||||
JsonSubTypes.Type(value = ArticleUpdateNotificationMessage::class, name = "article")
|
||||
)
|
||||
open class Notification(
|
||||
open class NotificationMessage(
|
||||
val type: String,
|
||||
val createdAt: DateTime = DateTime.now()
|
||||
) {
|
||||
@@ -50,24 +48,16 @@ open class Notification(
|
||||
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
}
|
||||
|
||||
inline fun <reified T : Notification> fromString(raw: String): T = mapper.readValue(raw)
|
||||
}
|
||||
|
||||
fun getValidation() = Validation<Notification> {
|
||||
Notification::type {
|
||||
enum(
|
||||
"article"
|
||||
)
|
||||
}
|
||||
inline fun <reified T : NotificationMessage> fromString(raw: String): T = mapper.readValue(raw)
|
||||
}
|
||||
}
|
||||
|
||||
open class EntityNotification(
|
||||
val target: Entity,
|
||||
open class EntityNotificationMessage <E : Entity> (
|
||||
val target: E,
|
||||
type: String,
|
||||
val action: String
|
||||
) : Notification(type)
|
||||
) : NotificationMessage(type)
|
||||
|
||||
class ArticleUpdateNotification(
|
||||
class ArticleUpdateNotificationMessage(
|
||||
target: ArticleForView
|
||||
) : EntityNotification(target, "article", "update")
|
||||
) : EntityNotificationMessage<ArticleForView>(target, "article", "update")
|
||||
@@ -7,12 +7,15 @@ import kotlinx.coroutines.coroutineScope
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class Publisher(
|
||||
class NotificationPublisherAsync(
|
||||
private val factory: ConnectionFactory,
|
||||
private val logger: Logger = LoggerFactory.getLogger(Publisher::class.qualifiedName),
|
||||
private val logger: Logger = LoggerFactory.getLogger(NotificationPublisherAsync::class.qualifiedName),
|
||||
private val exchangeName: String,
|
||||
) {
|
||||
suspend fun <T : EntityNotification> publish(it: T): Deferred<Unit> = coroutineScope {
|
||||
/**
|
||||
* Publish a new notification message to RabbitMQ
|
||||
*/
|
||||
suspend fun <T : EntityNotificationMessage<*>> publishAsync(it: T): Deferred<Unit> = coroutineScope {
|
||||
async {
|
||||
factory.newConnection().use { connection ->
|
||||
connection.createChannel().use { channel ->
|
||||
@@ -0,0 +1,33 @@
|
||||
package fr.dcproject.component.notification.email
|
||||
|
||||
import com.rabbitmq.client.ConnectionFactory
|
||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||
import fr.dcproject.component.notification.NotificationConsumerAbstract
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class NotificationEmailConsumer(
|
||||
rabbitFactory: ConnectionFactory,
|
||||
followConstitutionRepo: FollowConstitutionRepository,
|
||||
followArticleRepo: FollowArticleRepository,
|
||||
followCitizenRepo: FollowCitizenRepository,
|
||||
private val notificationEmailSender: NotificationEmailSender,
|
||||
private val exchangeName: String,
|
||||
) : NotificationConsumerAbstract(rabbitFactory, followConstitutionRepo, followArticleRepo, followCitizenRepo) {
|
||||
private val logger: Logger = LoggerFactory.getLogger(NotificationEmailConsumer::class.qualifiedName)
|
||||
|
||||
fun start() {
|
||||
/* Config Rabbit */
|
||||
declareQueue(QUEUE_NAME, exchangeName)
|
||||
consumeQueue(QUEUE_NAME) { message ->
|
||||
notificationEmailSender.sendEmail(message.follow)
|
||||
logger.debug("EmailSend to: ${message.follow.createdBy.id}")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val QUEUE_NAME = "email"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package fr.dcproject.component.notification.email
|
||||
|
||||
import com.sendgrid.helpers.mail.Mail
|
||||
import com.sendgrid.helpers.mail.objects.Content
|
||||
import com.sendgrid.helpers.mail.objects.Email
|
||||
import fr.dcproject.common.email.Mailer
|
||||
import fr.dcproject.common.entity.TargetRef
|
||||
import fr.dcproject.component.article.database.ArticleForView
|
||||
import fr.dcproject.component.article.database.ArticleRepository
|
||||
import fr.dcproject.component.citizen.database.Citizen
|
||||
import fr.dcproject.component.citizen.database.CitizenRepository
|
||||
import fr.dcproject.component.follow.database.FollowForView
|
||||
import fr.dcproject.component.notification.email.content.ArticleNotificationEmailContent
|
||||
import fr.dcproject.component.notification.email.content.CitizenNotificationEmailContent
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Send notification email on the follower
|
||||
*/
|
||||
class NotificationEmailSender(
|
||||
private val mailer: Mailer,
|
||||
private val domain: String,
|
||||
private val citizenRepo: CitizenRepository,
|
||||
private val articleRepo: ArticleRepository
|
||||
) {
|
||||
/**
|
||||
* Send the Notification Email to the follower user
|
||||
*/
|
||||
fun sendEmail(follow: FollowForView<out TargetRef>) {
|
||||
val citizen = citizenRepo.findById(follow.createdBy.id) ?: noCitizen(follow.createdBy.id)
|
||||
|
||||
/**
|
||||
* Find the complete target entity by its ID according to its reference
|
||||
*/
|
||||
val target = when (follow.target.reference) {
|
||||
"article" -> articleRepo.findById(follow.target.id) ?: noTarget(follow.target.id)
|
||||
"citizen" -> citizenRepo.findById(follow.target.id) ?: noTarget(follow.target.id)
|
||||
else -> noTarget(follow.target.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Find content of the email according to the target type
|
||||
*/
|
||||
val content = when (target) {
|
||||
is ArticleForView -> ArticleNotificationEmailContent(citizen, target, domain)
|
||||
is Citizen -> CitizenNotificationEmailContent(citizen, target, domain)
|
||||
else -> noTargetTypeImplementation(follow.target.reference)
|
||||
}
|
||||
|
||||
/* Send email */
|
||||
mailer.sendEmail {
|
||||
Mail(
|
||||
Email("notification@$domain"),
|
||||
content.subject,
|
||||
Email(citizen.email),
|
||||
Content("text/plain", content.content)
|
||||
).apply {
|
||||
addContent(Content("text/html", content.contentHtml))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NoCitizen(message: String) : Exception(message)
|
||||
class NoTarget(message: String) : Exception(message)
|
||||
class NoTargetTypeImplement(message: String) : Exception(message)
|
||||
|
||||
private fun noCitizen(id: UUID): Nothing = throw NoCitizen("No Citizen with this id : $id")
|
||||
private fun noTarget(id: UUID): Nothing = throw NoTarget("No Target with this id : $id")
|
||||
private fun noTargetTypeImplementation(type: String): Nothing = throw NoTargetTypeImplement("No Target type implemented: $type")
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package fr.dcproject.component.notification.email.content
|
||||
|
||||
import fr.dcproject.component.article.database.ArticleWithTitleI
|
||||
import fr.dcproject.component.citizen.database.Citizen
|
||||
|
||||
class ArticleNotificationEmailContent(
|
||||
private val citizen: Citizen,
|
||||
private val target: ArticleWithTitleI,
|
||||
private val domain: String,
|
||||
) : NotificationEmailContent {
|
||||
override val subject: String
|
||||
get() = """New version for article "${target.title}""""
|
||||
|
||||
override val contentHtml
|
||||
get() = run {
|
||||
"""
|
||||
Hello ${citizen.name.getFullName()},<br/>
|
||||
The article "${target.title}" was updated, check it <a href="http://$domain/articles/${target.id}">here</a>
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
override val content
|
||||
get() = run {
|
||||
"""
|
||||
Hello ${citizen.name.getFullName()},
|
||||
The article "${target.title}" was updated, check it here: http://$domain/articles/${target.id}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package fr.dcproject.component.notification.email.content
|
||||
|
||||
import fr.dcproject.component.citizen.database.Citizen
|
||||
|
||||
class CitizenNotificationEmailContent(
|
||||
private val citizen: Citizen,
|
||||
private val target: Citizen,
|
||||
private val domain: String,
|
||||
) : NotificationEmailContent {
|
||||
override val subject: String
|
||||
get() = """New activity for the citizen "${target.name}""""
|
||||
|
||||
override val contentHtml
|
||||
get() = run {
|
||||
"""
|
||||
Hello ${citizen.name.getFullName()},
|
||||
The citizen "${target.name}" was new activity, check it here: <a href="http://$domain/citizens/${target.id}">here</a>
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
override val content
|
||||
get() = run {
|
||||
"""
|
||||
Hello ${citizen.name.getFullName()},
|
||||
The citizen "${target.name}" was new activity, check it here: http://$domain/citizens/${target.id}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package fr.dcproject.component.notification.email.content
|
||||
|
||||
interface NotificationEmailContent {
|
||||
val subject: String
|
||||
val content: String
|
||||
val contentHtml: String
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package fr.dcproject.component.notification.push
|
||||
|
||||
import com.rabbitmq.client.ConnectionFactory
|
||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||
import fr.dcproject.component.notification.NotificationConsumerAbstract
|
||||
import io.lettuce.core.RedisClient
|
||||
import io.lettuce.core.api.async.RedisAsyncCommands
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class NotificationPushConsumer(
|
||||
rabbitFactory: ConnectionFactory,
|
||||
followConstitutionRepo: FollowConstitutionRepository,
|
||||
followArticleRepo: FollowArticleRepository,
|
||||
followCitizenRepo: FollowCitizenRepository,
|
||||
redisClient: RedisClient,
|
||||
private val exchangeName: String,
|
||||
) : NotificationConsumerAbstract(rabbitFactory, followConstitutionRepo, followArticleRepo, followCitizenRepo) {
|
||||
private val redisConnection = redisClient.connect() ?: error("Unable to connect to redis")
|
||||
private val redis: RedisAsyncCommands<String, String> = redisConnection.async() ?: error("Unable to connect to redis")
|
||||
private val logger: Logger = LoggerFactory.getLogger(NotificationPushConsumer::class.qualifiedName)
|
||||
|
||||
fun start() {
|
||||
/* Config Rabbit */
|
||||
declareQueue(QUEUE_NAME, exchangeName)
|
||||
consumeQueue(QUEUE_NAME) { message ->
|
||||
redis.zadd(
|
||||
"notification:${message.follow.createdBy.id}",
|
||||
message.event.id,
|
||||
message.rawMessage
|
||||
)
|
||||
logger.debug("Notification was transferred to the redis (follower: ${message.follow.createdBy.id})")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val QUEUE_NAME = "push"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package fr.dcproject.component.notification
|
||||
package fr.dcproject.component.notification.push
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.citizen.database.CitizenI
|
||||
import fr.dcproject.component.notification.NotificationMessage
|
||||
import io.ktor.http.cio.websocket.Frame
|
||||
import io.ktor.http.cio.websocket.Frame.Text
|
||||
import io.ktor.http.cio.websocket.readText
|
||||
@@ -29,34 +29,42 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class NotificationsPush (
|
||||
/**
|
||||
* Listen a custom flow to mark as read a message.
|
||||
*
|
||||
* And listen the redis subscription flow and call a callback when a new message arrives
|
||||
*/
|
||||
class NotificationPushListener(
|
||||
private val redis: RedisAsyncCommands<String, String>,
|
||||
private val redisConnectionPubSub: StatefulRedisPubSubConnection<String, String>,
|
||||
citizen: CitizenI,
|
||||
incoming: Flow<Notification>,
|
||||
onReceive: suspend (Notification) -> Unit,
|
||||
incoming: Flow<NotificationMessage>,
|
||||
onReceive: suspend (NotificationMessage) -> Unit,
|
||||
) {
|
||||
class Builder(val redisClient: RedisClient) {
|
||||
private val redisConnection = redisClient.connect() ?: error("Unable to connect to redis")
|
||||
private val redisConnectionPubSub = redisClient.connectPubSub() ?: error("Unable to connect to redis PubSub")
|
||||
private val redis: RedisAsyncCommands<String, String> = redisConnection.async() ?: error("Unable to connect to redis Async")
|
||||
class Builder(redisClient: RedisClient) {
|
||||
private val redisConnection = redisClient.connect()
|
||||
private val redisConnectionPubSub = redisClient.connectPubSub()
|
||||
private val redis: RedisAsyncCommands<String, String> = redisConnection.async()
|
||||
|
||||
/**
|
||||
* Build Listener with citizen, incoming flow and set an outgoing callback
|
||||
*/
|
||||
fun build(
|
||||
citizen: CitizenI,
|
||||
incoming: Flow<Notification>,
|
||||
onReceive: suspend (Notification) -> Unit,
|
||||
): NotificationsPush = NotificationsPush(redis, redisConnectionPubSub, citizen, incoming, onReceive)
|
||||
incoming: Flow<NotificationMessage>,
|
||||
onReceive: suspend (NotificationMessage) -> Unit,
|
||||
): NotificationPushListener = NotificationPushListener(redis, redisConnectionPubSub, citizen, incoming, onReceive)
|
||||
|
||||
/**
|
||||
* Build NotificationPush with only a WebSocket session
|
||||
*/
|
||||
@ExperimentalCoroutinesApi
|
||||
fun build(ws: DefaultWebSocketServerSession): NotificationsPush {
|
||||
fun build(ws: DefaultWebSocketServerSession): NotificationPushListener {
|
||||
/* Convert channel of string from websocket, to a flow of Notification object */
|
||||
val incomingFlow: Flow<Notification> = ws.incoming.consumeAsFlow()
|
||||
.mapNotNull<Frame, Text> { it as? Frame.Text }
|
||||
val incomingFlow: Flow<NotificationMessage> = ws.incoming.consumeAsFlow()
|
||||
.mapNotNull<Frame, Text> { it as? Text }
|
||||
.map { it.readText() }
|
||||
.map {
|
||||
Notification.fromString<Notification>(it)
|
||||
.apply { getValidation().validate(this).badRequestIfNotValid() }
|
||||
}
|
||||
.map { NotificationMessage.fromString(it) }
|
||||
|
||||
return build(ws.call.citizen, incomingFlow) {
|
||||
ws.outgoing.send(Text(it.toString()))
|
||||
@@ -66,30 +74,42 @@ class NotificationsPush (
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The key of the SortedSet in Redis which contains all the messages of a user
|
||||
*/
|
||||
private val key = "notification:${citizen.id}"
|
||||
private var score: Double = 0.0
|
||||
/**
|
||||
* The last score (a kind of sorted ids) of message
|
||||
*/
|
||||
private var lastScore: Double = 0.0
|
||||
/**
|
||||
* Configure the listener to listen all new notifications
|
||||
*/
|
||||
private val listener = object : RedisPubSubAdapter<String, String>() {
|
||||
/* On new key publish */
|
||||
override fun message(pattern: String?, channel: String?, message: String?) {
|
||||
runBlocking {
|
||||
getNotifications().collect {
|
||||
getNewUnreadNotifications().collect {
|
||||
onReceive(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the listener and the callback
|
||||
*/
|
||||
init {
|
||||
/* Mark as read all incoming notifications */
|
||||
GlobalScope.launch {
|
||||
incoming.collect {
|
||||
markAsRead(it)
|
||||
it.markAsRead()
|
||||
}
|
||||
}
|
||||
|
||||
/* Get old notification and sent it to websocket */
|
||||
runBlocking {
|
||||
getNotifications().collect {
|
||||
getNewUnreadNotifications().collect {
|
||||
onReceive(it)
|
||||
}
|
||||
}
|
||||
@@ -99,38 +119,55 @@ class NotificationsPush (
|
||||
addListener(listener)
|
||||
|
||||
/* Register to the events */
|
||||
async()?.psubscribe("__key*__:$key") ?: error("Unable to subscribe to redis events")
|
||||
async()?.psubscribe("__key*__:$key")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the redis subscription
|
||||
*/
|
||||
fun close() {
|
||||
redisConnectionPubSub.removeListener(listener)
|
||||
}
|
||||
|
||||
/* Return flow with all new notifications */
|
||||
private fun getNotifications() = flow<Notification> {
|
||||
/**
|
||||
* Get All new notification from redis and
|
||||
* Return flow with notifications
|
||||
*
|
||||
* On start, on the first call, this method return all unread notification of the user
|
||||
*
|
||||
* Internally this method return all messages that greater of the lastScore,
|
||||
* then define the lastScore with the score of the last message.
|
||||
*/
|
||||
private fun getNewUnreadNotifications() = flow<NotificationMessage> {
|
||||
redis
|
||||
.zrangebyscoreWithScores(
|
||||
key,
|
||||
Range.from(
|
||||
Boundary.excluding(score),
|
||||
Boundary.excluding(lastScore),
|
||||
Boundary.including(Double.POSITIVE_INFINITY)
|
||||
),
|
||||
Limit.from(100)
|
||||
)
|
||||
.get().forEach {
|
||||
emit(Notification.fromString(it.value))
|
||||
if (it.score > score) score = it.score
|
||||
/* Build message object from raw string and return it */
|
||||
emit(NotificationMessage.fromString(it.value))
|
||||
if (it.score > lastScore) lastScore = it.score
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun markAsRead(notificationMessage: Notification) = coroutineScope {
|
||||
/**
|
||||
* Mark one notification as read.
|
||||
*
|
||||
* Internally, this method remove the message of the SortedSet in redis
|
||||
*/
|
||||
private suspend fun NotificationMessage.markAsRead() = coroutineScope {
|
||||
try {
|
||||
redis.zremrangebyscore(
|
||||
key,
|
||||
Range.from(
|
||||
Boundary.including(notificationMessage.id),
|
||||
Boundary.including(notificationMessage.id)
|
||||
Boundary.including(id),
|
||||
Boundary.including(id)
|
||||
)
|
||||
)
|
||||
} catch (e: JsonProcessingException) {
|
||||
@@ -1,6 +1,6 @@
|
||||
package fr.dcproject.component.notification.routes
|
||||
|
||||
import fr.dcproject.component.notification.NotificationsPush
|
||||
import fr.dcproject.component.notification.push.NotificationPushListener
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.websocket.webSocket
|
||||
@@ -13,8 +13,8 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
*/
|
||||
@ExperimentalCoroutinesApi
|
||||
@KtorExperimentalLocationsAPI
|
||||
fun Route.notificationArticle(pushBuilder: NotificationsPush.Builder) {
|
||||
fun Route.notificationArticle(pushListenerBuilder: NotificationPushListener.Builder) {
|
||||
webSocket("/notifications") {
|
||||
pushBuilder.build(this)
|
||||
pushListenerBuilder.build(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.opinion.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.entity.TargetRef
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
@@ -12,6 +13,9 @@ import fr.dcproject.component.opinion.database.Opinion
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
import fr.dcproject.routes.PaginatedRequestI
|
||||
import fr.postgresjson.connexion.Paginated
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maximum
|
||||
import io.konform.validation.jsonschema.minimum
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -34,11 +38,22 @@ object GetMyOpinionsArticle {
|
||||
limit: Int = 50
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit) {
|
||||
val citizen = CitizenRef(citizen)
|
||||
fun validate() = Validation<CitizenOpinionsArticleRequest> {
|
||||
CitizenOpinionsArticleRequest::page {
|
||||
minimum(1)
|
||||
}
|
||||
CitizenOpinionsArticleRequest::limit {
|
||||
minimum(1)
|
||||
maximum(50)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
|
||||
fun Route.getMyOpinionsArticle(repo: OpinionArticleRepository, ac: OpinionAccessControl) {
|
||||
get<CitizenOpinionsArticleRequest> {
|
||||
mustBeAuth()
|
||||
it.validate().badRequestIfNotValid()
|
||||
|
||||
val opinions: Paginated<Opinion<TargetRef>> = repo.findCitizenOpinions(citizen, it.page, it.limit)
|
||||
ac.assert { canView(opinions.result, citizenOrNull) }
|
||||
call.respond(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.vote.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
@@ -9,6 +10,9 @@ import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.database.VoteArticleRepository
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
import fr.dcproject.routes.PaginatedRequestI
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maximum
|
||||
import io.konform.validation.jsonschema.minimum
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -28,11 +32,22 @@ object GetCitizenVotesOnArticle {
|
||||
val search: String? = null
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit) {
|
||||
val citizen = CitizenRef(citizen)
|
||||
fun validate() = Validation<CitizenVoteArticleRequest> {
|
||||
CitizenVoteArticleRequest::page {
|
||||
minimum(1)
|
||||
}
|
||||
CitizenVoteArticleRequest::limit {
|
||||
minimum(1)
|
||||
maximum(50)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
|
||||
fun Route.getCitizenVotesOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl) {
|
||||
get<CitizenVoteArticleRequest> {
|
||||
mustBeAuth()
|
||||
it.validate().badRequestIfNotValid()
|
||||
|
||||
val votes = repo.findByCitizen(it.citizen, it.page, it.limit)
|
||||
ac.assert { canView(votes.result, citizenOrNull) }
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.vote.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.article.database.ArticleRef
|
||||
@@ -10,6 +11,9 @@ import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.database.VoteArticleRepository
|
||||
import fr.dcproject.component.vote.database.VoteForUpdate
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maximum
|
||||
import io.konform.validation.jsonschema.minimum
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@@ -25,13 +29,22 @@ object PutVoteOnArticle {
|
||||
@Location("/articles/{article}/vote")
|
||||
class ArticleVoteRequest(article: UUID) {
|
||||
val article = ArticleRef(article)
|
||||
data class Input(var note: Int)
|
||||
data class Input(var note: Int) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::note {
|
||||
minimum(-1)
|
||||
maximum(1)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.putVoteOnArticle(repo: VoteArticleRepository, ac: VoteAccessControl, articleRepo: ArticleRepository) {
|
||||
put<ArticleVoteRequest> {
|
||||
mustBeAuth()
|
||||
|
||||
val input = call.receiveOrBadRequest<ArticleVoteRequest.Input>()
|
||||
.apply { validate().badRequestIfNotValid() }
|
||||
val article = articleRepo.findById(it.article.id) ?: throw NotFoundException("Article ${it.article.id} not found")
|
||||
val vote = VoteForUpdate(
|
||||
target = article,
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package fr.dcproject.component.vote.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.comment.generic.database.CommentRef
|
||||
import fr.dcproject.component.comment.generic.database.CommentRepository
|
||||
import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.database.VoteCommentRepository
|
||||
import fr.dcproject.component.vote.database.VoteForUpdate
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maximum
|
||||
import io.konform.validation.jsonschema.minimum
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
@@ -21,18 +27,29 @@ import java.util.UUID
|
||||
@KtorExperimentalLocationsAPI
|
||||
object PutVoteOnComment {
|
||||
@Location("/comments/{comment}/vote")
|
||||
class CommentVoteRequest(val comment: UUID) {
|
||||
data class Content(var note: Int)
|
||||
class CommentVoteRequest(comment: UUID) {
|
||||
val comment = CommentRef(comment)
|
||||
data class Input(var note: Int) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::note {
|
||||
minimum(-1)
|
||||
maximum(1)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.putVoteOnComment(voteCommentRepo: VoteCommentRepository, commentRepo: CommentRepository, ac: VoteAccessControl) {
|
||||
put<CommentVoteRequest> {
|
||||
mustBeAuth()
|
||||
val comment = commentRepo.findById(it.comment)!!
|
||||
val content = call.receiveOrBadRequest<CommentVoteRequest.Content>()
|
||||
|
||||
val comment = commentRepo.findById(it.comment.id) ?: throw NotFoundException("Comment ${it.comment.id} not found")
|
||||
val input = call.receiveOrBadRequest<CommentVoteRequest.Input>()
|
||||
.apply { validate().badRequestIfNotValid() }
|
||||
|
||||
val vote = VoteForUpdate(
|
||||
target = comment,
|
||||
note = content.note,
|
||||
note = input.note,
|
||||
createdBy = this.citizen
|
||||
)
|
||||
ac.assert { canCreate(vote, citizenOrNull) }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package fr.dcproject.component.vote.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.component.auth.citizen
|
||||
@@ -11,6 +12,9 @@ import fr.dcproject.component.vote.VoteAccessControl
|
||||
import fr.dcproject.component.vote.database.VoteConstitutionRepository
|
||||
import fr.dcproject.component.vote.database.VoteForUpdate
|
||||
import fr.dcproject.component.vote.routes.PutVoteOnConstitution.ConstitutionVoteRequest.Input
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maximum
|
||||
import io.konform.validation.jsonschema.minimum
|
||||
import io.ktor.application.call
|
||||
import io.ktor.features.NotFoundException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
@@ -26,17 +30,25 @@ object PutVoteOnConstitution {
|
||||
@Location("/constitutions/{constitution}/vote")
|
||||
class ConstitutionVoteRequest(constitution: UUID) {
|
||||
val constitution = ConstitutionRef(constitution)
|
||||
data class Input(var note: Int)
|
||||
data class Input(var note: Int) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::note {
|
||||
minimum(-1)
|
||||
maximum(1)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.voteConstitution(repo: VoteConstitutionRepository, ac: VoteAccessControl, constitutionRepo: ConstitutionRepository) {
|
||||
put<ConstitutionVoteRequest> {
|
||||
mustBeAuth()
|
||||
val constitution = constitutionRepo.findById(it.constitution.id) ?: throw NotFoundException("Unable to find constitution ${it.constitution.id}")
|
||||
val content = call.receiveOrBadRequest<Input>()
|
||||
val input = call.receiveOrBadRequest<Input>()
|
||||
.apply { validate().badRequestIfNotValid() }
|
||||
val vote = VoteForUpdate(
|
||||
target = constitution,
|
||||
note = content.note,
|
||||
note = input.note,
|
||||
createdBy = this.citizen
|
||||
)
|
||||
ac.assert { canCreate(vote, citizenOrNull) }
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package fr.dcproject.component.workgroup.routes
|
||||
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.common.validation.isUrl
|
||||
import fr.dcproject.component.auth.citizen
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
@@ -10,6 +11,9 @@ import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupForUpdate
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRepository
|
||||
import fr.dcproject.component.workgroup.routes.CreateWorkgroup.PostWorkgroupRequest.Input
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maxLength
|
||||
import io.konform.validation.jsonschema.minLength
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -29,13 +33,30 @@ object CreateWorkgroup {
|
||||
val description: String,
|
||||
val logo: String?,
|
||||
val anonymous: Boolean?
|
||||
)
|
||||
) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::name {
|
||||
minLength(5)
|
||||
maxLength(80)
|
||||
}
|
||||
Input::description {
|
||||
minLength(50)
|
||||
maxLength(6000)
|
||||
}
|
||||
Input::logo ifPresent {
|
||||
isUrl()
|
||||
maxLength(2048)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.createWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
post<PostWorkgroupRequest> {
|
||||
mustBeAuth()
|
||||
call.receiveOrBadRequest<Input>().run {
|
||||
validate().badRequestIfNotValid()
|
||||
|
||||
WorkgroupForUpdate(
|
||||
id ?: UUID.randomUUID(),
|
||||
name,
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package fr.dcproject.component.workgroup.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||
import fr.dcproject.common.validation.isUrl
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.auth.mustBeAuth
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupForUpdate
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRepository
|
||||
import fr.dcproject.component.workgroup.routes.EditWorkgroup.PutWorkgroupRequest.Input
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.maxLength
|
||||
import io.konform.validation.jsonschema.minLength
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -15,19 +20,33 @@ import io.ktor.locations.Location
|
||||
import io.ktor.locations.put
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import org.koin.core.KoinComponent
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object EditWorkgroup {
|
||||
@Location("/workgroups/{workgroupId}")
|
||||
class PutWorkgroupRequest(val workgroupId: UUID) : KoinComponent {
|
||||
class PutWorkgroupRequest(val workgroupId: UUID) {
|
||||
class Input(
|
||||
val name: String?,
|
||||
val description: String?,
|
||||
val logo: String?,
|
||||
val anonymous: Boolean?
|
||||
)
|
||||
) {
|
||||
fun validate() = Validation<Input> {
|
||||
Input::name ifPresent {
|
||||
minLength(5)
|
||||
maxLength(80)
|
||||
}
|
||||
Input::description ifPresent {
|
||||
minLength(50)
|
||||
maxLength(6000)
|
||||
}
|
||||
Input::logo ifPresent {
|
||||
isUrl()
|
||||
maxLength(2048)
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.editWorkgroup(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
@@ -35,6 +54,7 @@ object EditWorkgroup {
|
||||
mustBeAuth()
|
||||
repo.findById(it.workgroupId)?.let { old ->
|
||||
call.receiveOrBadRequest<Input>().run {
|
||||
validate().badRequestIfNotValid()
|
||||
WorkgroupForUpdate(
|
||||
id = old.id,
|
||||
name = name ?: old.name,
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
package fr.dcproject.component.workgroup.routes
|
||||
|
||||
import fr.dcproject.application.http.badRequestIfNotValid
|
||||
import fr.dcproject.common.response.toOutput
|
||||
import fr.dcproject.common.security.assert
|
||||
import fr.dcproject.common.utils.toUUID
|
||||
import fr.dcproject.common.validation.isUuid
|
||||
import fr.dcproject.component.auth.citizenOrNull
|
||||
import fr.dcproject.component.workgroup.WorkgroupAccessControl
|
||||
import fr.dcproject.component.workgroup.database.WorkgroupRepository
|
||||
import fr.dcproject.routes.PaginatedRequest
|
||||
import fr.dcproject.routes.PaginatedRequestI
|
||||
import fr.postgresjson.repository.RepositoryI
|
||||
import io.konform.validation.Validation
|
||||
import io.konform.validation.jsonschema.enum
|
||||
import io.konform.validation.jsonschema.maximum
|
||||
import io.konform.validation.jsonschema.minimum
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
@@ -27,16 +35,33 @@ object GetWorkgroups {
|
||||
val search: String? = null,
|
||||
val createdBy: String? = null,
|
||||
members: List<String?>? = null
|
||||
) {
|
||||
val page: Int = if (page < 1) 1 else page
|
||||
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
|
||||
) : PaginatedRequestI by PaginatedRequest(page, limit) {
|
||||
val members: List<UUID>? = members?.toUUID()
|
||||
fun validate() = Validation<WorkgroupsRequest> {
|
||||
WorkgroupsRequest::page {
|
||||
minimum(1)
|
||||
}
|
||||
WorkgroupsRequest::limit {
|
||||
minimum(1)
|
||||
maximum(50)
|
||||
}
|
||||
WorkgroupsRequest::sort ifPresent {
|
||||
enum(
|
||||
"name",
|
||||
"createdAt",
|
||||
)
|
||||
}
|
||||
WorkgroupsRequest::createdBy ifPresent {
|
||||
isUuid()
|
||||
}
|
||||
}.validate(this)
|
||||
}
|
||||
|
||||
fun Route.getWorkgroups(repo: WorkgroupRepository, ac: WorkgroupAccessControl) {
|
||||
get<WorkgroupsRequest> {
|
||||
val workgroups =
|
||||
repo.find(
|
||||
it.validate().badRequestIfNotValid()
|
||||
|
||||
val workgroups = repo.find(
|
||||
it.page,
|
||||
it.limit,
|
||||
it.sort,
|
||||
|
||||
@@ -17,13 +17,12 @@ import io.ktor.locations.Location
|
||||
import io.ktor.locations.post
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import org.koin.core.KoinComponent
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object AddMemberToWorkgroup {
|
||||
@Location("/workgroups/{workgroupId}/members")
|
||||
class WorkgroupsMembersRequest(val workgroupId: UUID) : KoinComponent {
|
||||
class WorkgroupsMembersRequest(val workgroupId: UUID) {
|
||||
class Input : MutableList<Input.Member> by mutableListOf() {
|
||||
class Member(val citizen: CitizenRef, roles: List<String> = emptyList()) {
|
||||
val roles: List<WorkgroupWithMembersI.Member.Role> = roles.map {
|
||||
|
||||
@@ -17,14 +17,13 @@ import io.ktor.locations.Location
|
||||
import io.ktor.locations.delete
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import org.koin.core.KoinComponent
|
||||
import java.util.UUID
|
||||
import fr.dcproject.component.workgroup.routes.members.DeleteMembersOfWorkgroup.WorkgroupsMembersRequest.Input as Input
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object DeleteMembersOfWorkgroup {
|
||||
@Location("/workgroups/{workgroupId}/members")
|
||||
class WorkgroupsMembersRequest(val workgroupId: UUID) : KoinComponent {
|
||||
class WorkgroupsMembersRequest(val workgroupId: UUID) {
|
||||
class Input : MutableList<Input.Member> by mutableListOf() {
|
||||
class Member(val citizen: CitizenRef)
|
||||
}
|
||||
|
||||
@@ -17,13 +17,12 @@ import io.ktor.locations.Location
|
||||
import io.ktor.locations.put
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.Route
|
||||
import org.koin.core.KoinComponent
|
||||
import java.util.UUID
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
object UpdateMemberOfWorkgroup {
|
||||
@Location("/workgroups/{workgroupId}/members")
|
||||
class WorkgroupsMembersRequest(val workgroupId: UUID) : KoinComponent {
|
||||
class WorkgroupsMembersRequest(val workgroupId: UUID) {
|
||||
class Input : MutableList<Input.Item> by mutableListOf() {
|
||||
class Item(val citizen: CitizenRef, roles: List<String> = emptyList()) {
|
||||
val roles: List<WorkgroupWithMembersI.Member.Role> = roles.map {
|
||||
|
||||
@@ -170,6 +170,18 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/404'
|
||||
401:
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/401'
|
||||
403:
|
||||
description: Forbidden
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/403'
|
||||
|
||||
/articles/{article}/versions:
|
||||
parameters:
|
||||
@@ -943,13 +955,105 @@ paths:
|
||||
description: Return only http status 204 on success
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
|
||||
/citizens/{citizen}/follows:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/citizen'
|
||||
get:
|
||||
security:
|
||||
- JWTAuth: [ ]
|
||||
summary: Return Follows of citizen
|
||||
tags:
|
||||
- follow
|
||||
- citizen
|
||||
responses:
|
||||
200:
|
||||
description: Return follows
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/FollowResponse'
|
||||
404:
|
||||
description: Citizen not exist
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/404'
|
||||
post:
|
||||
security:
|
||||
- JWTAuth: []
|
||||
summary: Follow citizen
|
||||
description: Follow a citizen to receive notifications of his activity
|
||||
tags:
|
||||
- follow
|
||||
- citizen
|
||||
responses:
|
||||
201:
|
||||
description: Return only http status 201 on success
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
404:
|
||||
description: Citizen not exist
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/404'
|
||||
delete:
|
||||
security:
|
||||
- JWTAuth: [ ]
|
||||
summary: Unfollow one citizen
|
||||
tags:
|
||||
- follow
|
||||
- citizen
|
||||
responses:
|
||||
204:
|
||||
description: Return only http status 204 on success
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
404:
|
||||
description: Citizen not exist
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/404'
|
||||
/citizens/{citizen}/follows/citizens:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/citizen'
|
||||
get:
|
||||
security:
|
||||
- JWTAuth: [ ]
|
||||
summary: Return citizen Follow of citizen
|
||||
tags:
|
||||
- follow
|
||||
- citizen
|
||||
responses:
|
||||
200:
|
||||
description: Return citizen Follow of citizen
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Paginated'
|
||||
- type: object
|
||||
properties:
|
||||
result:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/FollowResponse'
|
||||
404:
|
||||
description: Citizen not exist
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/404'
|
||||
|
||||
/citizens/{citizen}/follows/articles:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/citizen'
|
||||
get:
|
||||
security:
|
||||
- JWTAuth: [ ]
|
||||
summary: Return Follow or nothing if you not follow
|
||||
summary: Return article Follow of citizen
|
||||
tags:
|
||||
- follow
|
||||
- article
|
||||
@@ -1024,7 +1128,7 @@ paths:
|
||||
- citizen
|
||||
responses:
|
||||
200:
|
||||
description: Return your follows
|
||||
description: Return constitution Follow of citizen
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
@@ -1118,6 +1222,9 @@ paths:
|
||||
tags:
|
||||
- opinion
|
||||
- citizen
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/page'
|
||||
- $ref: '#/components/parameters/limit'
|
||||
responses:
|
||||
200:
|
||||
description: Opinions
|
||||
@@ -1132,6 +1239,13 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Opinion'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
|
||||
/articles/{article}/opinions:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/article'
|
||||
@@ -1184,6 +1298,12 @@ paths:
|
||||
responses:
|
||||
201:
|
||||
description: Return only http status 201 on success
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
/citizens/{citizen}/votes:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/citizen'
|
||||
@@ -1242,6 +1362,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VoteAggregation'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
/citizens/{citizen}/votes/articles:
|
||||
@@ -1255,6 +1381,9 @@ paths:
|
||||
- vote
|
||||
- article
|
||||
- citizen
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/page'
|
||||
- $ref: '#/components/parameters/limit'
|
||||
responses:
|
||||
200:
|
||||
description: Votes
|
||||
@@ -1269,6 +1398,12 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/VoteResponse'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
/articles/{article}/vote:
|
||||
@@ -1293,6 +1428,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VoteAggregation'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
|
||||
@@ -1322,6 +1463,12 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/WorkgroupListing'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
post:
|
||||
summary: Create new Workgroup
|
||||
security:
|
||||
@@ -1360,6 +1507,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Workgroup'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
/workgroups/{workgroup}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/workgroup'
|
||||
@@ -1408,6 +1561,12 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Workgroup'
|
||||
400:
|
||||
description: BadReqest
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/400'
|
||||
delete:
|
||||
summary: Delete one workgroup
|
||||
security:
|
||||
@@ -2413,7 +2572,20 @@ components:
|
||||
reason:
|
||||
type: string
|
||||
example: 'Cannot be null'
|
||||
|
||||
403:
|
||||
description: Forbiden
|
||||
properties:
|
||||
statusCode:
|
||||
type: integer
|
||||
title:
|
||||
type: string
|
||||
401:
|
||||
description: Unauthorized
|
||||
properties:
|
||||
statusCode:
|
||||
type: integer
|
||||
title:
|
||||
type: string
|
||||
404:
|
||||
description: Not Found
|
||||
required:
|
||||
|
||||
@@ -23,6 +23,7 @@ begin
|
||||
select
|
||||
a.id,
|
||||
a.title,
|
||||
a.created_at,
|
||||
a.deleted_at,
|
||||
a.draft,
|
||||
find_citizen_by_id_with_user(a.created_by_id) as created_by,
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
create or replace function find_follows_article_by_target(
|
||||
_target_id uuid,
|
||||
"limit" int default 50,
|
||||
"offset" int default 0,
|
||||
out resource json,
|
||||
out total int
|
||||
_limit int default 50,
|
||||
_start_id uuid default null,
|
||||
out resource json
|
||||
) language plpgsql as
|
||||
$$
|
||||
declare
|
||||
_version_id uuid = (select version_id from article where id = _target_id);
|
||||
_start_at timestamp default '2000-01-01 00:00:00'::timestamp;
|
||||
_article_creator_id uuid = (select created_by_id from article where id = _target_id);
|
||||
begin
|
||||
select json_agg(t), (
|
||||
select count(f.id)
|
||||
from follow f
|
||||
join article a on f.target_id = a.id
|
||||
where a.version_id = _version_id)
|
||||
into resource, total
|
||||
if _start_id is not null then
|
||||
select created_at into _start_at from follow where id = _start_id;
|
||||
end if;
|
||||
|
||||
select json_agg(t)
|
||||
into resource
|
||||
from (
|
||||
select
|
||||
f.id,
|
||||
@@ -22,11 +23,17 @@ begin
|
||||
f.target_reference,
|
||||
json_build_object('id', f.target_id) as target,
|
||||
find_citizen_by_id_with_user(f.created_by_id) as created_by
|
||||
from follow_article as f
|
||||
join article a on f.target_id = a.id
|
||||
where a.version_id = _version_id
|
||||
from follow as f
|
||||
left join article a on f.target_reference = 'article'::regclass and f.target_id = a.id
|
||||
where (
|
||||
(f.target_reference = 'article'::regclass and a.version_id = _version_id)
|
||||
or
|
||||
(f.target_reference = 'citizen'::regclass and f.target_id = _article_creator_id)
|
||||
)
|
||||
and f.created_at >= _start_at
|
||||
and (_start_id is null or f.id != _start_id)
|
||||
order by f.created_at
|
||||
limit "limit" offset "offset"
|
||||
limit _limit
|
||||
) as t;
|
||||
end
|
||||
$$;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
create or replace function find_follows_citizen_by_citizen(
|
||||
_created_by_id uuid,
|
||||
"limit" int default 50,
|
||||
"offset" int default 0,
|
||||
out resource json,
|
||||
out total int
|
||||
) language plpgsql as
|
||||
$$
|
||||
begin
|
||||
select json_agg(t), (select count(id) from follow)
|
||||
into resource, total
|
||||
from (
|
||||
select
|
||||
f.*,
|
||||
find_citizen_by_id_with_user(f.target_id) as target,
|
||||
find_citizen_by_id_with_user(f.created_by_id) as created_by
|
||||
from follow as f
|
||||
where created_by_id = _created_by_id
|
||||
order by created_at desc,
|
||||
f.created_at desc
|
||||
limit "limit" offset "offset"
|
||||
) as t;
|
||||
end;
|
||||
$$;
|
||||
@@ -2,7 +2,7 @@ create or replace function find_workgroups(
|
||||
_search text default null,
|
||||
_filter json default '{}',
|
||||
direction text default 'desc',
|
||||
sort text default 'created_at',
|
||||
sort text default 'createdAt',
|
||||
"limit" int default 50,
|
||||
"offset" int default 0,
|
||||
out resource json,
|
||||
@@ -41,14 +41,14 @@ begin
|
||||
case direction when 'asc' then
|
||||
case sort
|
||||
when 'name' then w.name
|
||||
when 'created_at' then w.created_at::text
|
||||
when 'createdAt' then w.created_at::text
|
||||
else null
|
||||
end
|
||||
end,
|
||||
case direction when 'desc' then
|
||||
case sort
|
||||
when 'name' then w.name
|
||||
when 'created_at' then w.created_at::text
|
||||
when 'createdAt' then w.created_at::text
|
||||
end
|
||||
end
|
||||
desc,
|
||||
|
||||
@@ -8,22 +8,19 @@ import fr.dcproject.application.module
|
||||
import fr.dcproject.common.email.Mailer
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.server.testing.withTestApplication
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.InternalCoroutinesApi
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Tags
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.koin.test.AutoCloseKoinTest
|
||||
import org.koin.test.KoinTest
|
||||
import org.koin.test.get
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
@KtorExperimentalAPI
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@Tags(Tag("functional"), Tag("mail"))
|
||||
class MailerTest : KoinTest, AutoCloseKoinTest() {
|
||||
class MailerTest : KoinTest {
|
||||
@InternalCoroutinesApi
|
||||
@ExperimentalCoroutinesApi
|
||||
@Test
|
||||
|
||||
@@ -10,12 +10,12 @@ import fr.dcproject.component.citizen.database.CitizenCreator
|
||||
import fr.dcproject.component.citizen.database.CitizenI
|
||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.database.FollowForView
|
||||
import fr.dcproject.component.notification.ArticleUpdateNotification
|
||||
import fr.dcproject.component.notification.NotificationConsumer
|
||||
import fr.dcproject.component.notification.NotificationEmailSender
|
||||
import fr.dcproject.component.notification.Publisher
|
||||
import fr.dcproject.component.notification.ArticleUpdateNotificationMessage
|
||||
import fr.dcproject.component.notification.NotificationPublisherAsync
|
||||
import fr.dcproject.component.notification.email.NotificationEmailConsumer
|
||||
import fr.dcproject.component.notification.email.NotificationEmailSender
|
||||
import fr.dcproject.component.notification.push.NotificationPushConsumer
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import io.lettuce.core.RedisClient
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
@@ -62,10 +62,9 @@ class NotificationConsumerTest {
|
||||
|
||||
@InternalCoroutinesApi
|
||||
@KtorExperimentalLocationsAPI
|
||||
@KtorExperimentalAPI
|
||||
@ExperimentalCoroutinesApi
|
||||
@Test
|
||||
fun `can be send notification`() = runBlocking {
|
||||
fun `can be receive article update notification when follow article`() = runBlocking {
|
||||
val config: Configuration = Configuration("application-test.conf")
|
||||
/* Create mocks and spy's */
|
||||
val emailSender = mockk<NotificationEmailSender>() {
|
||||
@@ -88,21 +87,30 @@ class NotificationConsumerTest {
|
||||
}
|
||||
|
||||
/* Config consumer */
|
||||
val consumer = NotificationConsumer(
|
||||
val emailConsumer = NotificationEmailConsumer(
|
||||
rabbitFactory = rabbitFactory,
|
||||
redisClient = redisClient,
|
||||
followArticleRepo = followArticleRepo,
|
||||
followConstitutionRepo = mockk(),
|
||||
followConstitutionRepo = mockk(), // TODO test followConstitution
|
||||
followCitizenRepo = mockk(), // TODO test followCitizen
|
||||
notificationEmailSender = emailSender,
|
||||
exchangeName = "notification",
|
||||
).apply { start() }
|
||||
|
||||
val pushConsumer = NotificationPushConsumer(
|
||||
rabbitFactory = rabbitFactory,
|
||||
followArticleRepo = followArticleRepo,
|
||||
followConstitutionRepo = mockk(), // TODO test followConstitution
|
||||
followCitizenRepo = mockk(), // TODO test followCitizen
|
||||
redisClient = redisClient,
|
||||
exchangeName = "notification",
|
||||
).apply { start() }
|
||||
|
||||
/* Push message */
|
||||
Publisher(
|
||||
NotificationPublisherAsync(
|
||||
factory = rabbitFactory,
|
||||
exchangeName = "notification",
|
||||
).publish(
|
||||
ArticleUpdateNotification(
|
||||
).publishAsync(
|
||||
ArticleUpdateNotificationMessage(
|
||||
ArticleForView(
|
||||
title = "MyTitle",
|
||||
content = "myContent",
|
||||
@@ -121,6 +129,7 @@ class NotificationConsumerTest {
|
||||
verify(timeout = 2000) { emailSender.sendEmail(any()) }
|
||||
verify(timeout = 2000) { asyncCommand.zadd(any<String>(), any<Double>(), any<String>()) }
|
||||
|
||||
consumer.close()
|
||||
emailConsumer.close()
|
||||
pushConsumer.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import fr.dcproject.component.article.database.ArticleForView
|
||||
import fr.dcproject.component.auth.database.UserCreator
|
||||
import fr.dcproject.component.citizen.database.CitizenCreator
|
||||
import fr.dcproject.component.citizen.database.CitizenI
|
||||
import fr.dcproject.component.notification.ArticleUpdateNotification
|
||||
import fr.dcproject.component.notification.Notification
|
||||
import fr.dcproject.component.notification.NotificationsPush
|
||||
import fr.dcproject.component.notification.ArticleUpdateNotificationMessage
|
||||
import fr.dcproject.component.notification.NotificationMessage
|
||||
import fr.dcproject.component.notification.push.NotificationPushListener
|
||||
import io.lettuce.core.RedisClient
|
||||
import io.mockk.every
|
||||
import io.mockk.spyk
|
||||
@@ -68,14 +68,14 @@ internal class NotificationsPushTest {
|
||||
title = "Super Title",
|
||||
)
|
||||
/* Init two notification, one called before subscription, and the other after */
|
||||
val notifBeforeSubscribe = ArticleUpdateNotification(article)
|
||||
val notifBeforeSubscribe = ArticleUpdateNotificationMessage(article)
|
||||
runBlocking {
|
||||
delay(100)
|
||||
}
|
||||
val notifAfterSubscribe = ArticleUpdateNotification(article)
|
||||
val notifAfterSubscribe = ArticleUpdateNotificationMessage(article)
|
||||
|
||||
/* init event for emulate incoming message from websocket */
|
||||
val event = MutableSharedFlow<Notification>()
|
||||
val event = MutableSharedFlow<NotificationMessage>()
|
||||
val incomingFlow = event.asSharedFlow()
|
||||
|
||||
spyk(object { var counter = 0 }).run { /* Counter for count the callback of notification */
|
||||
@@ -90,7 +90,7 @@ internal class NotificationsPushTest {
|
||||
}
|
||||
|
||||
/* Init NotificationPush system, and set assertion in callback */
|
||||
val notificationPush = NotificationsPush.Builder(redisClient).build(citizen, incomingFlow) {
|
||||
val notificationPush = NotificationPushListener.Builder(redisClient).build(citizen, incomingFlow) {
|
||||
counter++
|
||||
if (counter == 1) it.id `should be equal to` notifBeforeSubscribe.id
|
||||
else it.id `should be equal to` notifAfterSubscribe.id
|
||||
|
||||
@@ -2,6 +2,7 @@ package functional
|
||||
|
||||
import fr.dcproject.application.Env.TEST
|
||||
import fr.dcproject.application.module
|
||||
import fr.dcproject.common.utils.retry
|
||||
import fr.dcproject.component.article.database.ArticleForView
|
||||
import fr.dcproject.component.article.database.ArticleViewRepository
|
||||
import fr.dcproject.component.auth.database.UserCreator
|
||||
@@ -10,7 +11,6 @@ import fr.dcproject.component.citizen.database.CitizenI
|
||||
import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.server.testing.withTestApplication
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import org.amshove.kluent.`should be equal to`
|
||||
import org.junit.jupiter.api.Tag
|
||||
@@ -20,13 +20,15 @@ import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
|
||||
import org.koin.ktor.ext.get
|
||||
import java.util.UUID
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.seconds
|
||||
|
||||
@KtorExperimentalLocationsAPI
|
||||
@KtorExperimentalAPI
|
||||
@ExperimentalCoroutinesApi
|
||||
@TestInstance(PER_CLASS)
|
||||
@Tags(Tag("functional"), Tag("view"))
|
||||
class ViewTest {
|
||||
@ExperimentalTime
|
||||
@Test
|
||||
fun `test View Article`() {
|
||||
val article = ArticleForView(
|
||||
@@ -75,9 +77,8 @@ class ViewTest {
|
||||
article
|
||||
)
|
||||
|
||||
/* Sleep because ES is not sync ! */
|
||||
Thread.sleep(1000)
|
||||
|
||||
/* Retry because ES is not sync ! */
|
||||
retry(10, 0.3.seconds) {
|
||||
/* Get view */
|
||||
val afterView = viewRepository.getViewsCount(article)
|
||||
|
||||
@@ -87,3 +88,4 @@ class ViewTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.common.utils.toUUID
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import integration.steps.given.`Given I have article`
|
||||
import integration.steps.given.`Given I have article created by workgroup`
|
||||
import integration.steps.given.`Given I have articles`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have draft article`
|
||||
import integration.steps.given.`Given I have workgroup`
|
||||
import integration.steps.given.`authenticated as`
|
||||
import integration.steps.then.`And have property`
|
||||
import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should contain list`
|
||||
import integration.steps.then.`And the response should contain pattern`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`And the response should not contain`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.then.`which contains`
|
||||
import integration.steps.`when`.Validate
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.given.`Given I have article created by workgroup`
|
||||
import integration.steps.given.`Given I have article`
|
||||
import integration.steps.given.`Given I have articles`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have workgroup`
|
||||
import integration.steps.given.`authenticated as`
|
||||
import integration.steps.then.`And have property`
|
||||
import integration.steps.then.`And the response should contain list`
|
||||
import integration.steps.then.`And the response should contain pattern`
|
||||
import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`And the response should not contain`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.`which contains`
|
||||
import integration.steps.then.and
|
||||
import io.ktor.http.HttpStatusCode.Companion.BadRequest
|
||||
import io.ktor.http.HttpStatusCode.Companion.Forbidden
|
||||
import io.ktor.http.HttpStatusCode.Companion.NotFound
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
import io.ktor.http.HttpStatusCode.Companion.Unauthorized
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Tags
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -41,6 +44,7 @@ class `Article routes` : BaseTest() {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain pattern`("$.result[0].createdBy.name.firstName", "firstName.+")
|
||||
`And the response should not contain`("$.result[1]")
|
||||
`And the response should contain pattern`("$.result[0].createdAt", """[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z""") // 2021-04-16T16:39:06.890Z
|
||||
`And the response should contain list`("$.result", 1)
|
||||
}
|
||||
}
|
||||
@@ -79,6 +83,7 @@ class `Article routes` : BaseTest() {
|
||||
`When I send a GET request`("/articles/65cda9f3-8991-4420-8d41-1da9da72c9bb") `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And have property`("$.id") `which contains` "65cda9f3-8991-4420-8d41-1da9da72c9bb"
|
||||
`And the response should contain pattern`("$.createdAt", """[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z""") // 2021-04-16T16:39:06.890Z
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,6 +99,47 @@ class `Article routes` : BaseTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("draft")
|
||||
fun `I can get my draft article by id`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Neil", "Armstrong")
|
||||
`Given I have draft article`(id = "d946e16f-ca42-4cf9-a711-a0f8cae60a55", createdBy = Name("Neil", "Armstrong"))
|
||||
`When I send a GET request`("/articles/d946e16f-ca42-4cf9-a711-a0f8cae60a55") {
|
||||
`authenticated as`("Neil", "Armstrong")
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And have property`("$.id") `which contains` "d946e16f-ca42-4cf9-a711-a0f8cae60a55"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("draft")
|
||||
fun `I cannot get draft article by id if not owner`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Thomas", "Pesquet")
|
||||
`Given I have citizen`("Youri", "Gagarine")
|
||||
`Given I have draft article`(id = "bf13c84c-609f-49b9-9d1d-e2e9655ed8ad")
|
||||
`When I send a GET request`("/articles/bf13c84c-609f-49b9-9d1d-e2e9655ed8ad") {
|
||||
`authenticated as`("Youri", "Gagarine")
|
||||
} `Then the response should be` Forbidden and {
|
||||
`And the response should not be null`()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("draft")
|
||||
fun `I cannot get draft article by id if not connected`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have draft article`(id = "bf13c84c-609f-49b9-9d1d-e2e9655ed8ad")
|
||||
`When I send a GET request`("/articles/bf13c84c-609f-49b9-9d1d-e2e9655ed8ad") `Then the response should be` Unauthorized and {
|
||||
`And the response should not be null`()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot get article by id with wrong id format`() {
|
||||
|
||||
@@ -10,7 +10,6 @@ import fr.postgresjson.migration.Migrations
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.server.testing.TestApplicationEngine
|
||||
import io.ktor.server.testing.createTestEnvironment
|
||||
import io.ktor.util.KtorExperimentalAPI
|
||||
import io.lettuce.core.RedisClient
|
||||
import io.lettuce.core.api.sync.RedisCommands
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -41,7 +40,6 @@ abstract class BaseTest : KoinTest {
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@KtorExperimentalAPI
|
||||
@KtorExperimentalLocationsAPI
|
||||
@BeforeAll
|
||||
fun before() {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package integration
|
||||
|
||||
import integration.steps.`when`.Validate
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`authenticated as`
|
||||
import integration.steps.then.`And have property`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.`which contains`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.then.`which contains`
|
||||
import integration.steps.`when`.Validate
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import io.ktor.http.HttpStatusCode.Companion.BadRequest
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import integration.steps.`when`.Validate.ALL
|
||||
import integration.steps.`when`.Validate.REQUEST_BODY
|
||||
import integration.steps.`when`.Validate.REQUEST_PARAM
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.given.`Given I have article`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have comment on article`
|
||||
@@ -15,6 +9,12 @@ import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.`when`.Validate.ALL
|
||||
import integration.steps.`when`.Validate.REQUEST_BODY
|
||||
import integration.steps.`when`.Validate.REQUEST_PARAM
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import io.ktor.http.HttpStatusCode.Companion.BadRequest
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have comment on constitution`
|
||||
import integration.steps.given.`Given I have constitution`
|
||||
import integration.steps.given.`authenticated as`
|
||||
import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should contain list`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.`when`.Validate
|
||||
import integration.steps.`when`.Validate.ALL
|
||||
import integration.steps.`when`.Validate.REQUEST_BODY
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have comment on constitution`
|
||||
import integration.steps.given.`Given I have constitution`
|
||||
import integration.steps.given.`authenticated as`
|
||||
import integration.steps.then.`And the response should contain list`
|
||||
import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import io.ktor.http.HttpStatusCode.Companion.BadRequest
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.component.citizen.database.CitizenI
|
||||
import integration.steps.`when`.Validate.ALL
|
||||
import integration.steps.`when`.Validate.REQUEST_BODY
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`When I send a PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.given.`Given I have article`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have comment on article`
|
||||
@@ -16,6 +10,12 @@ import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.`when`.Validate.ALL
|
||||
import integration.steps.`when`.Validate.REQUEST_BODY
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`When I send a PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import io.ktor.http.HttpStatusCode.Companion.BadRequest
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
package integration
|
||||
|
||||
import integration.steps.`when`.Validate.ALL
|
||||
import integration.steps.`when`.Validate.REQUEST_BODY
|
||||
import integration.steps.`when`.Validate.REQUEST_PARAM
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have constitution`
|
||||
import integration.steps.given.`Given I have constitutions`
|
||||
@@ -14,8 +8,14 @@ import integration.steps.then.`And have property`
|
||||
import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.`which contains`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.then.`which contains`
|
||||
import integration.steps.`when`.Validate.ALL
|
||||
import integration.steps.`when`.Validate.REQUEST_BODY
|
||||
import integration.steps.`when`.Validate.REQUEST_PARAM
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import io.ktor.http.HttpStatusCode.Companion.BadRequest
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package integration
|
||||
|
||||
import integration.steps.`when`.`When I send a DELETE request`
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.given.`And follow article`
|
||||
import integration.steps.given.`Given I have article`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
@@ -13,6 +10,9 @@ import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.`when`.`When I send a DELETE request`
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.NoContent
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
|
||||
96
src/test/kotlin/integration/Follow citizen routes.kt
Normal file
96
src/test/kotlin/integration/Follow citizen routes.kt
Normal file
@@ -0,0 +1,96 @@
|
||||
package integration
|
||||
|
||||
import integration.steps.given.`And follow citizen`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`authenticated as`
|
||||
import integration.steps.given.`with no content`
|
||||
import integration.steps.then.`And the response should be null`
|
||||
import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.`when`.`When I send a DELETE request`
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.NoContent
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Tags
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@Tags(Tag("integration"), Tag("article"), Tag("follow"))
|
||||
class `Follow citizen routes` : BaseTest() {
|
||||
@Test
|
||||
fun `I can follow citizen`() {
|
||||
withIntegrationApplication {
|
||||
/* Followed user */
|
||||
`Given I have citizen`("John", "Glenn", id = "7e1580c5-05b7-4557-84f4-faac9f0a9441")
|
||||
/* Current user */
|
||||
`Given I have citizen`("Valentina", "Terechkova")
|
||||
`When I send a POST request`("/citizens/7e1580c5-05b7-4557-84f4-faac9f0a9441/follows") {
|
||||
`authenticated as`("Valentina", "Terechkova")
|
||||
`with no content`()
|
||||
} `Then the response should be` Created
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can get my follow citizen`() {
|
||||
withIntegrationApplication {
|
||||
/* Followed user */
|
||||
`Given I have citizen`("Jean-Loup", "Chrétien", id = "c2432b94-a509-4116-a8b6-9774bc963372")
|
||||
/* Current user */
|
||||
`Given I have citizen`("John", "Young", id = "6d41ce65-9df7-47e0-af46-8da4a909490b") {
|
||||
`And follow citizen`("c2432b94-a509-4116-a8b6-9774bc963372")
|
||||
}
|
||||
/* Get my all follows */
|
||||
`When I send a GET request`("/citizens/6d41ce65-9df7-47e0-af46-8da4a909490b/follows/citizens") {
|
||||
`authenticated as`("John", "Young")
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.currentPage", 1)
|
||||
`And the response should contain`("$.limit", 50)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can unfollow citizen`() {
|
||||
withIntegrationApplication {
|
||||
/* Followed user */
|
||||
`Given I have citizen`("Bruce", "McCandless", id = "680c7af7-d2de-4249-bfcb-47007ef546fe")
|
||||
/* Current user */
|
||||
`Given I have citizen`("Jean-François", "Clervoy", id = "a12455ae-1047-43ff-826d-0d826dbe90f7") {
|
||||
`And follow citizen`("680c7af7-d2de-4249-bfcb-47007ef546fe")
|
||||
}
|
||||
`When I send a DELETE request`("/citizens/680c7af7-d2de-4249-bfcb-47007ef546fe/follows") {
|
||||
`authenticated as`("Jean-François", "Clervoy")
|
||||
`with no content`()
|
||||
} `Then the response should be` NoContent and {
|
||||
`And the response should be null`()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can know if I follow an citizen`() {
|
||||
withIntegrationApplication {
|
||||
/* Followed user */
|
||||
`Given I have citizen`("Eugene", "Cernan", id = "c755788f-7f48-4cde-8ff0-e75bcffdafc2")
|
||||
/* Current user */
|
||||
`Given I have citizen`("Buzz", "Aldrin", id = "39e2915a-e96f-43ea-babd-bd339d8bf197") {
|
||||
`And follow citizen`("c755788f-7f48-4cde-8ff0-e75bcffdafc2")
|
||||
}
|
||||
`When I send a GET request`("/citizens/c755788f-7f48-4cde-8ff0-e75bcffdafc2/follows") {
|
||||
`authenticated as`("Buzz", "Aldrin")
|
||||
`with no content`()
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.target.id", "c755788f-7f48-4cde-8ff0-e75bcffdafc2")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
package integration
|
||||
|
||||
import integration.steps.`when`.`When I send a DELETE request`
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.given.`And follow constitution`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have constitution`
|
||||
@@ -13,6 +10,9 @@ import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.`when`.`When I send a DELETE request`
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.NoContent
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package integration
|
||||
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`authenticated as`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.`and should contains`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.then.`and should contains`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import io.ktor.http.HttpStatusCode.Companion.NoContent
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
import org.junit.jupiter.api.Tag
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.common.utils.toUUID
|
||||
import fr.dcproject.component.article.database.ArticleForView
|
||||
import fr.dcproject.component.auth.database.UserCreator
|
||||
import fr.dcproject.component.citizen.database.CitizenCreator
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import fr.dcproject.component.notification.ArticleUpdateNotification
|
||||
import fr.dcproject.component.notification.Notification
|
||||
import fr.dcproject.component.notification.Publisher
|
||||
import fr.dcproject.component.notification.ArticleUpdateNotificationMessage
|
||||
import fr.dcproject.component.notification.NotificationMessage
|
||||
import integration.steps.given.`And follow citizen`
|
||||
import integration.steps.given.`Given I have article`
|
||||
import integration.steps.given.`Given I have article update notification`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have follow on article`
|
||||
import integration.steps.given.`authenticated in url as`
|
||||
import io.ktor.http.cio.websocket.Frame
|
||||
import io.ktor.http.cio.websocket.readText
|
||||
import kotlinx.coroutines.launch
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Tags
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.koin.test.get
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@@ -31,26 +26,7 @@ class `Notification routes` : BaseTest() {
|
||||
`Given I have citizen`("John", "Doe", id = "1a34191a-9cde-45ba-8ac1-230138a102d3")
|
||||
`Given I have article`(id = "a06cbfb7-3094-4d64-aaa1-7486c0c292f4", createdBy = Name(firstName = "John", lastName = "Doe"))
|
||||
`Given I have follow on article`("John", "Doe", article = "a06cbfb7-3094-4d64-aaa1-7486c0c292f4")
|
||||
val notification = ArticleUpdateNotification(
|
||||
ArticleForView(
|
||||
id = "a06cbfb7-3094-4d64-aaa1-7486c0c292f4".toUUID(),
|
||||
title = "MyTitle",
|
||||
content = "myContent",
|
||||
description = "myDescription",
|
||||
createdBy = CitizenCreator(
|
||||
id = "1a34191a-9cde-45ba-8ac1-230138a102d3".toUUID(),
|
||||
name = Name(firstName = "John", lastName = "Doe"),
|
||||
email = "john-doe@plop.com",
|
||||
user = UserCreator(username = "john-doe"),
|
||||
)
|
||||
)
|
||||
)
|
||||
val publisher = get<Publisher>()
|
||||
launch {
|
||||
publisher
|
||||
.publish(notification)
|
||||
.await()
|
||||
}
|
||||
`Given I have article update notification`("a06cbfb7-3094-4d64-aaa1-7486c0c292f4")
|
||||
|
||||
Thread.sleep(1000)
|
||||
|
||||
@@ -62,7 +38,41 @@ class `Notification routes` : BaseTest() {
|
||||
) { incoming, outgoing ->
|
||||
incoming.receive().let {
|
||||
when (it) {
|
||||
is Frame.Text -> Notification.fromString<ArticleUpdateNotification>(it.readText()).let { notif ->
|
||||
is Frame.Text -> NotificationMessage.fromString<ArticleUpdateNotificationMessage>(it.readText()).let { notif ->
|
||||
assertEquals(
|
||||
"a06cbfb7-3094-4d64-aaa1-7486c0c292f4",
|
||||
notif.target.id.toString()
|
||||
)
|
||||
outgoing.send(it)
|
||||
}
|
||||
else -> error(it.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can receive article update notification when follow the creator`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Thomas", "Pesquet", id = "1a34191a-9cde-45ba-8ac1-230138a102d3")
|
||||
`Given I have article`(id = "a06cbfb7-3094-4d64-aaa1-7486c0c292f4", createdBy = Name(firstName = "Thomas", lastName = "Pesquet"))
|
||||
`Given I have citizen`("Alan", "Bean") {
|
||||
`And follow citizen`(Name("Thomas", "Pesquet"))
|
||||
}
|
||||
`Given I have article update notification`("a06cbfb7-3094-4d64-aaa1-7486c0c292f4")
|
||||
|
||||
Thread.sleep(1000)
|
||||
|
||||
handleWebSocketConversation(
|
||||
"/notifications",
|
||||
{
|
||||
`authenticated in url as`("Alan", "Bean")
|
||||
}
|
||||
) { incoming, outgoing ->
|
||||
incoming.receive().let {
|
||||
when (it) {
|
||||
is Frame.Text -> NotificationMessage.fromString<ArticleUpdateNotificationMessage>(it.readText()).let { notif ->
|
||||
assertEquals(
|
||||
"a06cbfb7-3094-4d64-aaa1-7486c0c292f4",
|
||||
notif.target.id.toString()
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.given.`Given I have an opinion choice`
|
||||
import integration.steps.given.`Given I have article`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have opinion on article`
|
||||
import integration.steps.given.`authenticated as`
|
||||
import integration.steps.then.`And the response should contain list`
|
||||
import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should contain list`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.`when`.Validate.ALL
|
||||
import integration.steps.`when`.Validate.REQUEST_PARAM
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
import org.junit.jupiter.api.Tag
|
||||
@@ -133,7 +137,7 @@ class `Opinion routes` : BaseTest() {
|
||||
article = "8651b530-ac1b-4214-a784-706781371074",
|
||||
Name("Albert", "Einstein")
|
||||
)
|
||||
`When I send a GET request`("/citizens/c1542096-3431-432d-8e35-9dc071d4c818/opinions/articles") {
|
||||
`When I send a GET request`("/citizens/c1542096-3431-432d-8e35-9dc071d4c818/opinions/articles?page=1&limit=10") {
|
||||
`authenticated as`("Albert", "Einstein")
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain`("$.result[0].name", "Opinion9")
|
||||
@@ -141,4 +145,26 @@ class `Opinion routes` : BaseTest() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tags(Tag("article"), Tag("BadRequest"))
|
||||
fun `I cannot get all my opinion of one article with wrong request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Albert", "Einstein", id = "c1542096-3431-432d-8e35-9dc071d4c818")
|
||||
`Given I have an opinion choice`("Opinion9")
|
||||
`Given I have article`("8651b530-ac1b-4214-a784-706781371074")
|
||||
`Given I have opinion on article`(
|
||||
"Opinion9",
|
||||
article = "8651b530-ac1b-4214-a784-706781371074",
|
||||
Name("Albert", "Einstein")
|
||||
)
|
||||
`When I send a GET request`("/citizens/c1542096-3431-432d-8e35-9dc071d4c818/opinions/articles?page=1&limit=60", ALL - REQUEST_PARAM) {
|
||||
`authenticated as`("Albert", "Einstein")
|
||||
} `Then the response should be` HttpStatusCode.BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".limit")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must be at most '50'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package integration
|
||||
|
||||
import integration.steps.`when`.Validate
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.then.`And the response should be null`
|
||||
import integration.steps.then.`And the response should contain pattern`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.`when`.Validate
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import io.ktor.http.HttpStatusCode.Companion.BadRequest
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
import org.junit.jupiter.api.Tag
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.given.`Given I have article`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have comment on article`
|
||||
@@ -12,53 +9,141 @@ import integration.steps.given.`Given I have vote +1 on article`
|
||||
import integration.steps.given.`Given I have vote -1 on article`
|
||||
import integration.steps.given.`authenticated as`
|
||||
import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.`when`.Validate.ALL
|
||||
import integration.steps.`when`.Validate.REQUEST_BODY
|
||||
import integration.steps.`when`.Validate.REQUEST_PARAM
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import io.ktor.http.HttpStatusCode.Companion.BadRequest
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
import org.junit.jupiter.api.DynamicTest
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Tags
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestFactory
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@Tags(Tag("integration"), Tag("vote"))
|
||||
class `Vote routes` : BaseTest() {
|
||||
@Test
|
||||
fun `I can vote article`() {
|
||||
@TestFactory
|
||||
fun `I can vote article`(): List<DynamicTest> {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Thalès", "Milet")
|
||||
`Given I have article`(id = "835c5101-ca39-4038-a4e6-da6ee62ca6d5")
|
||||
}
|
||||
return (-1..1).map { note ->
|
||||
DynamicTest.dynamicTest("""I can vote article with note "$note"""") {
|
||||
withIntegrationApplication {
|
||||
`When I send a PUT request`("/articles/835c5101-ca39-4038-a4e6-da6ee62ca6d5/vote") {
|
||||
`authenticated as`("Thalès", "Milet")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"note": 1
|
||||
"note": $note
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` Created
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can vote constitution`() {
|
||||
@TestFactory
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot vote article with wrong request`(): List<DynamicTest> {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Thalès", "Milet")
|
||||
`Given I have article`(id = "835c5101-ca39-4038-a4e6-da6ee62ca6d5")
|
||||
}
|
||||
|
||||
return listOf(-10, -2, +2, +10).map { note ->
|
||||
DynamicTest.dynamicTest("""I can vote article with note "$note"""") {
|
||||
withIntegrationApplication {
|
||||
`When I send a PUT request`(
|
||||
"/articles/835c5101-ca39-4038-a4e6-da6ee62ca6d5/vote",
|
||||
ALL - REQUEST_BODY
|
||||
) {
|
||||
`authenticated as`("Thalès", "Milet")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"note": $note
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".note")
|
||||
`And the response should contain`("$.invalidParams[0].reason", if (note > 0) "must be at most '1'" else "must be at least '-1'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
fun `I can vote constitution`(): List<DynamicTest> {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Gregor", "Mendel")
|
||||
`Given I have constitution`(id = "76e79c89-efc1-492d-9e8f-dc9717363a11")
|
||||
}
|
||||
return (-1..1).map { note ->
|
||||
DynamicTest.dynamicTest("""I can vote constitution with note "$note"""") {
|
||||
withIntegrationApplication {
|
||||
`When I send a PUT request`("/constitutions/76e79c89-efc1-492d-9e8f-dc9717363a11/vote") {
|
||||
`authenticated as`("Gregor", "Mendel")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"note": 1
|
||||
}
|
||||
"note": $note
|
||||
}²
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` Created
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot vote constitution with wrong request`(): List<DynamicTest> {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Gregor", "Mendel")
|
||||
`Given I have constitution`(id = "76e79c89-efc1-492d-9e8f-dc9717363a11")
|
||||
}
|
||||
|
||||
return listOf(-10, -2, +2, +10).map { note ->
|
||||
DynamicTest.dynamicTest("""I can vote constitution with note "$note"""") {
|
||||
withIntegrationApplication {
|
||||
`When I send a PUT request`(
|
||||
"/constitutions/76e79c89-efc1-492d-9e8f-dc9717363a11/vote",
|
||||
ALL - REQUEST_BODY
|
||||
) {
|
||||
`authenticated as`("Gregor", "Mendel")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"note": $note
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".note")
|
||||
`And the response should contain`("$.invalidParams[0].reason", if (note > 0) "must be at most '1'" else "must be at least '-1'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can get votes of current citizen`() {
|
||||
@@ -66,7 +151,7 @@ class `Vote routes` : BaseTest() {
|
||||
`Given I have citizen`("Carl", "Gauss", id = "c044823d-e778-4256-9016-b1334bf933d3")
|
||||
`Given I have article`("7c9286db-470d-448c-aab1-3f0b072213b1")
|
||||
`Given I have vote +1 on article`("7c9286db-470d-448c-aab1-3f0b072213b1", Name("Carl", "Gauss"))
|
||||
`When I send a GET request`("/citizens/c044823d-e778-4256-9016-b1334bf933d3/votes/articles") {
|
||||
`When I send a GET request`("/citizens/c044823d-e778-4256-9016-b1334bf933d3/votes/articles?page=1&limit=50") {
|
||||
`authenticated as`("Carl", "Gauss")
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain`("$.currentPage", 1)
|
||||
@@ -77,6 +162,23 @@ class `Vote routes` : BaseTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot get votes of current citizen with wrong request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Carl", "Gauss", id = "c044823d-e778-4256-9016-b1334bf933d3")
|
||||
`Given I have article`("7c9286db-470d-448c-aab1-3f0b072213b1")
|
||||
`Given I have vote +1 on article`("7c9286db-470d-448c-aab1-3f0b072213b1", Name("Carl", "Gauss"))
|
||||
`When I send a GET request`("/citizens/c044823d-e778-4256-9016-b1334bf933d3/votes/articles?page=1&limit=60", ALL - REQUEST_PARAM) {
|
||||
`authenticated as`("Carl", "Gauss")
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".limit")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must be at most '50'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can get votes of current citizen by target ids`() {
|
||||
withIntegrationApplication {
|
||||
@@ -118,4 +220,39 @@ class `Vote routes` : BaseTest() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot vote comment with wrong request`(): List<DynamicTest> {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Antoine", "Lavoisier")
|
||||
`Given I have article`(id = "835c5101-ca39-4038-a4e6-da6ee62ca6d5")
|
||||
`Given I have comment on article`(
|
||||
createdBy = Name("Antoine", "Lavoisier"),
|
||||
article = "835c5101-ca39-4038-a4e6-da6ee62ca6d5",
|
||||
id = "e793eccc-456b-4450-a292-46d592229b74",
|
||||
)
|
||||
}
|
||||
|
||||
return listOf(-10, -2, +2, +10).map { note ->
|
||||
DynamicTest.dynamicTest("""I can vote comment with note "$note"""") {
|
||||
withIntegrationApplication {
|
||||
`When I send a PUT request`("/comments/e793eccc-456b-4450-a292-46d592229b74/vote", ALL - REQUEST_BODY) {
|
||||
`authenticated as`("Antoine", "Lavoisier")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"note": $note
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".note")
|
||||
`And the response should contain`("$.invalidParams[0].reason", if (note > 0) "must be at most '1'" else "must be at least '-1'")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
118
src/test/kotlin/integration/Workgroup Members routes.kt
Normal file
118
src/test/kotlin/integration/Workgroup Members routes.kt
Normal file
@@ -0,0 +1,118 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have workgroup`
|
||||
import integration.steps.given.`With members`
|
||||
import integration.steps.given.`authenticated as`
|
||||
import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should contain list`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.`when`.`When I send a DELETE request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`When I send a PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.OK
|
||||
import org.junit.jupiter.api.Tag
|
||||
import org.junit.jupiter.api.Tags
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@Tags(Tag("integration"), Tag("workgroup"), Tag("workgroupMember"))
|
||||
class `Workgroup Members routes` : BaseTest() {
|
||||
@Test
|
||||
fun `I can add member to workgroup`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Blaise", "Pascal")
|
||||
`Given I have citizen`("Roger", "Penrose", id = "6d883fe7-5fc0-4a50-8858-72230673eba4")
|
||||
`Given I have citizen`("Alessandro", "Volta", id = "b5bac515-45d4-4aeb-9b6d-2627a0bbc419")
|
||||
`Given I have workgroup`("b0ea1922-3bc6-44e2-aa7c-40158998cfbb", createdBy = Name("Blaise", "Pascal"))
|
||||
`When I send a POST request`("/workgroups/b0ea1922-3bc6-44e2-aa7c-40158998cfbb/members") {
|
||||
`authenticated as`("Blaise", "Pascal")
|
||||
`with body`(
|
||||
"""
|
||||
[
|
||||
{
|
||||
"citizen": {"id":"6d883fe7-5fc0-4a50-8858-72230673eba4"},
|
||||
"roles": ["MASTER"]
|
||||
},
|
||||
{
|
||||
"citizen": {"id":"b5bac515-45d4-4aeb-9b6d-2627a0bbc419"},
|
||||
"roles": ["MASTER"]
|
||||
}
|
||||
]
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` Created
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can remove member to workgroup`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Heinrich", "Hertz", id = "94f92424-c257-4582-907c-98564a8c4ac9")
|
||||
`Given I have citizen`("William", "Thomson", id = "87909ba3-2069-431c-9924-219fd8411cf2")
|
||||
`Given I have citizen`("Paul", "Dirac", id = "1baf48bb-02bc-4d8f-ac86-33335354f5e7")
|
||||
`Given I have workgroup`("b6c975df-dd44-4e99-adc1-f605746b0e11", createdBy = Name("Heinrich", "Hertz")) {
|
||||
`With members`(
|
||||
Name("William", "Thomson"),
|
||||
Name("Paul", "Dirac"),
|
||||
)
|
||||
}
|
||||
`When I send a DELETE request`("/workgroups/b6c975df-dd44-4e99-adc1-f605746b0e11/members") {
|
||||
`authenticated as`("Heinrich", "Hertz")
|
||||
"""
|
||||
[
|
||||
{
|
||||
"citizen": {"id":"87909ba3-2069-431c-9924-219fd8411cf2"}
|
||||
}
|
||||
]
|
||||
"""
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain list`("$", 2)
|
||||
`And the response should contain`("$.[0]citizen.id", "94f92424-c257-4582-907c-98564a8c4ac9")
|
||||
`And the response should contain`("$.[1]citizen.id", "1baf48bb-02bc-4d8f-ac86-33335354f5e7")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can update members on workgroup`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Leon", "Foucault")
|
||||
`Given I have citizen`("Sadi", "Carnot", id = "be3b0926-8628-4426-804a-75188a6eb315")
|
||||
`Given I have citizen`("Joseph", "Fourier", id = "b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1")
|
||||
`Given I have citizen`("Georg", "Ohm")
|
||||
`Given I have workgroup`("784fe6bc-7635-4ae2-b080-3a4743b998bf", createdBy = Name("Leon", "Foucault")) {
|
||||
`With members`(
|
||||
Name("Sadi", "Carnot"),
|
||||
Name("Joseph", "Fourier"),
|
||||
)
|
||||
}
|
||||
`When I send a PUT request`("/workgroups/784fe6bc-7635-4ae2-b080-3a4743b998bf/members") {
|
||||
`authenticated as`("Leon", "Foucault")
|
||||
`with body`(
|
||||
"""
|
||||
[
|
||||
{
|
||||
"citizen": {"id":"be3b0926-8628-4426-804a-75188a6eb315"},
|
||||
"roles": ["MASTER"]
|
||||
},
|
||||
{
|
||||
"citizen": {"id":"b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1"},
|
||||
"roles": ["MASTER"]
|
||||
}
|
||||
]
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain list`("$", 2)
|
||||
`And the response should contain`("$.[0]citizen.id", "be3b0926-8628-4426-804a-75188a6eb315")
|
||||
`And the response should contain`("$.[1]citizen.id", "b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
package integration
|
||||
|
||||
import fr.dcproject.component.citizen.database.CitizenI.Name
|
||||
import integration.steps.`when`.`When I send a DELETE request`
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`When I send a PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import integration.steps.given.`Given I have citizen`
|
||||
import integration.steps.given.`Given I have workgroup`
|
||||
import integration.steps.given.`With members`
|
||||
@@ -13,10 +8,19 @@ import integration.steps.given.`authenticated as`
|
||||
import integration.steps.given.`with no content`
|
||||
import integration.steps.then.`And have property`
|
||||
import integration.steps.then.`And the response should be null`
|
||||
import integration.steps.then.`And the response should contain list`
|
||||
import integration.steps.then.`And the response should contain`
|
||||
import integration.steps.then.`And the response should contain list`
|
||||
import integration.steps.then.`And the response should not be null`
|
||||
import integration.steps.then.`Then the response should be`
|
||||
import integration.steps.then.and
|
||||
import integration.steps.`when`.Validate.REQUEST_BODY
|
||||
import integration.steps.`when`.Validate.REQUEST_PARAM
|
||||
import integration.steps.`when`.`When I send a DELETE request`
|
||||
import integration.steps.`when`.`When I send a GET request`
|
||||
import integration.steps.`when`.`When I send a POST request`
|
||||
import integration.steps.`when`.`When I send a PUT request`
|
||||
import integration.steps.`when`.`with body`
|
||||
import io.ktor.http.HttpStatusCode.Companion.BadRequest
|
||||
import io.ktor.http.HttpStatusCode.Companion.Created
|
||||
import io.ktor.http.HttpStatusCode.Companion.NoContent
|
||||
import io.ktor.http.HttpStatusCode.Companion.NotFound
|
||||
@@ -73,7 +77,7 @@ class `Workgroup routes` : BaseTest() {
|
||||
{
|
||||
"id":"f496d86d-6654-4068-91ff-90e1dbcc5f38",
|
||||
"name":"Les Bouffons",
|
||||
"description":"La vie est belle",
|
||||
"description":"Pellentesque eleifend malesuada aliquam. Maecenas et urna quis nunc lacinia scelerisque.",
|
||||
"anonymous":false
|
||||
}
|
||||
"""
|
||||
@@ -81,7 +85,7 @@ class `Workgroup routes` : BaseTest() {
|
||||
} `Then the response should be` Created and {
|
||||
`And the response should contain`("$.id", "f496d86d-6654-4068-91ff-90e1dbcc5f38")
|
||||
`And the response should contain`("$.name", "Les Bouffons")
|
||||
`And the response should contain`("$.description", "La vie est belle")
|
||||
`And the response should contain`("$.description", "Pellentesque eleifend malesuada aliquam. Maecenas et urna quis nunc lacinia scelerisque.")
|
||||
`And the response should contain`("$.anonymous", false)
|
||||
}
|
||||
|
||||
@@ -91,6 +95,36 @@ class `Workgroup routes` : BaseTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot create a workgroup with wrong request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Werner", "Heisenberg")
|
||||
`When I send a POST request`("/workgroups") {
|
||||
`authenticated as`("Werner", "Heisenberg")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"id":"f496d86d-6654-4068-91ff-90e1dbcc5f38",
|
||||
"name":"sm",
|
||||
"description":"small",
|
||||
"anonymous":false,
|
||||
"logo": "www.plop.com"
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".name")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must have at least 5 characters")
|
||||
`And the response should contain`("$.invalidParams[1].name", ".description")
|
||||
`And the response should contain`("$.invalidParams[1].reason", "must have at least 50 characters")
|
||||
`And the response should contain`("$.invalidParams[2].name", ".logo")
|
||||
`And the response should contain`("$.invalidParams[2].reason", "is not url")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can edit a workgroup`() {
|
||||
withIntegrationApplication {
|
||||
@@ -109,14 +143,15 @@ class `Workgroup routes` : BaseTest() {
|
||||
"""
|
||||
{
|
||||
"name":"La ratatouille",
|
||||
"description":"Une petite souris"
|
||||
"description":"Une petite souris avec un chapeau et qui aime la cuisine",
|
||||
"logo": "http://sdf@exemple.com/sdfsd?sdf=sss"
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain`("$.id", "aa875a24-0050-4252-9130-d37391714e26")
|
||||
`And the response should contain`("$.name", "La ratatouille")
|
||||
`And the response should contain`("$.description", "Une petite souris")
|
||||
`And the response should contain`("$.description", "Une petite souris avec un chapeau et qui aime la cuisine")
|
||||
|
||||
`And have property`("$.members")
|
||||
`And the response should contain list`("$.members", 3)
|
||||
@@ -129,7 +164,43 @@ class `Workgroup routes` : BaseTest() {
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain`("$.id", "aa875a24-0050-4252-9130-d37391714e26")
|
||||
`And the response should contain`("$.name", "La ratatouille")
|
||||
`And the response should contain`("$.description", "Une petite souris")
|
||||
`And the response should contain`("$.description", "Une petite souris avec un chapeau et qui aime la cuisine")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot edit a workgroup with bad request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("John", "Wheeler")
|
||||
`Given I have citizen`("Heinrich", "Hertz", id = "94f92424-c257-4582-907c-98564a8c4ac9")
|
||||
`Given I have citizen`("William", "Thomson", id = "87909ba3-2069-431c-9924-219fd8411cf2")
|
||||
`Given I have workgroup`("aa875a24-0050-4252-9130-d37391714e26", createdBy = Name("John", "Wheeler")) {
|
||||
`With members`(
|
||||
Name("Heinrich", "Hertz"),
|
||||
Name("William", "Thomson"),
|
||||
)
|
||||
}
|
||||
`When I send a PUT request`("/workgroups/aa875a24-0050-4252-9130-d37391714e26", -REQUEST_BODY) {
|
||||
`authenticated as`("John", "Wheeler")
|
||||
`with body`(
|
||||
"""
|
||||
{
|
||||
"name":"sm",
|
||||
"description":"small2",
|
||||
"logo": "ws://sdfs.sdok"
|
||||
}
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".name")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must have at least 5 characters")
|
||||
`And the response should contain`("$.invalidParams[1].name", ".description")
|
||||
`And the response should contain`("$.invalidParams[1].reason", "must have at least 50 characters")
|
||||
`And the response should contain`("$.invalidParams[2].name", ".logo")
|
||||
`And the response should contain`("$.invalidParams[2].reason", "is not url")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,7 +228,7 @@ class `Workgroup routes` : BaseTest() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Max", "Planck")
|
||||
`Given I have workgroup`("3fd8edb6-c4b4-4c94-bc75-ddd9b290d32c")
|
||||
`When I send a GET request`("/workgroups") {
|
||||
`When I send a GET request`("/workgroups?page=1&limit=10&sort=createdAt") {
|
||||
`authenticated as`("Max", "Planck")
|
||||
`with no content`()
|
||||
} `Then the response should be` OK and {
|
||||
@@ -167,94 +238,15 @@ class `Workgroup routes` : BaseTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can add member to workgroup`() {
|
||||
@Tag("BadRequest")
|
||||
fun `I cannot get workgroups list with wrong request`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Blaise", "Pascal")
|
||||
`Given I have citizen`("Roger", "Penrose", id = "6d883fe7-5fc0-4a50-8858-72230673eba4")
|
||||
`Given I have citizen`("Alessandro", "Volta", id = "b5bac515-45d4-4aeb-9b6d-2627a0bbc419")
|
||||
`Given I have workgroup`("b0ea1922-3bc6-44e2-aa7c-40158998cfbb", createdBy = Name("Blaise", "Pascal"))
|
||||
`When I send a POST request`("/workgroups/b0ea1922-3bc6-44e2-aa7c-40158998cfbb/members") {
|
||||
`authenticated as`("Blaise", "Pascal")
|
||||
`with body`(
|
||||
"""
|
||||
[
|
||||
{
|
||||
"citizen": {"id":"6d883fe7-5fc0-4a50-8858-72230673eba4"},
|
||||
"roles": ["MASTER"]
|
||||
},
|
||||
{
|
||||
"citizen": {"id":"b5bac515-45d4-4aeb-9b6d-2627a0bbc419"},
|
||||
"roles": ["MASTER"]
|
||||
}
|
||||
]
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` Created
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can remove member to workgroup`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Heinrich", "Hertz", id = "94f92424-c257-4582-907c-98564a8c4ac9")
|
||||
`Given I have citizen`("William", "Thomson", id = "87909ba3-2069-431c-9924-219fd8411cf2")
|
||||
`Given I have citizen`("Paul", "Dirac", id = "1baf48bb-02bc-4d8f-ac86-33335354f5e7")
|
||||
`Given I have workgroup`("b6c975df-dd44-4e99-adc1-f605746b0e11", createdBy = Name("Heinrich", "Hertz")) {
|
||||
`With members`(
|
||||
Name("William", "Thomson"),
|
||||
Name("Paul", "Dirac"),
|
||||
)
|
||||
}
|
||||
`When I send a DELETE request`("/workgroups/b6c975df-dd44-4e99-adc1-f605746b0e11/members") {
|
||||
`authenticated as`("Heinrich", "Hertz")
|
||||
"""
|
||||
[
|
||||
{
|
||||
"citizen": {"id":"87909ba3-2069-431c-9924-219fd8411cf2"}
|
||||
}
|
||||
]
|
||||
"""
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain list`("$", 2)
|
||||
`And the response should contain`("$.[0]citizen.id", "94f92424-c257-4582-907c-98564a8c4ac9")
|
||||
`And the response should contain`("$.[1]citizen.id", "1baf48bb-02bc-4d8f-ac86-33335354f5e7")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `I can update members on workgroup`() {
|
||||
withIntegrationApplication {
|
||||
`Given I have citizen`("Leon", "Foucault")
|
||||
`Given I have citizen`("Sadi", "Carnot", id = "be3b0926-8628-4426-804a-75188a6eb315")
|
||||
`Given I have citizen`("Joseph", "Fourier", id = "b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1")
|
||||
`Given I have citizen`("Georg", "Ohm")
|
||||
`Given I have workgroup`("784fe6bc-7635-4ae2-b080-3a4743b998bf", createdBy = Name("Leon", "Foucault")) {
|
||||
`With members`(
|
||||
Name("Sadi", "Carnot"),
|
||||
Name("Joseph", "Fourier"),
|
||||
)
|
||||
}
|
||||
`When I send a PUT request`("/workgroups/784fe6bc-7635-4ae2-b080-3a4743b998bf/members") {
|
||||
`authenticated as`("Leon", "Foucault")
|
||||
`with body`(
|
||||
"""
|
||||
[
|
||||
{
|
||||
"citizen": {"id":"be3b0926-8628-4426-804a-75188a6eb315"},
|
||||
"roles": ["MASTER"]
|
||||
},
|
||||
{
|
||||
"citizen": {"id":"b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1"},
|
||||
"roles": ["MASTER"]
|
||||
}
|
||||
]
|
||||
"""
|
||||
)
|
||||
} `Then the response should be` OK and {
|
||||
`And the response should contain list`("$", 2)
|
||||
`And the response should contain`("$.[0]citizen.id", "be3b0926-8628-4426-804a-75188a6eb315")
|
||||
`And the response should contain`("$.[1]citizen.id", "b49e20c1-8393-45d6-a6a0-3fa5c71cbdc1")
|
||||
`Given I have workgroup`("3fd8edb6-c4b4-4c94-bc75-ddd9b290d32c")
|
||||
`When I send a GET request`("/workgroups?sort=plop", -REQUEST_PARAM) {
|
||||
} `Then the response should be` BadRequest and {
|
||||
`And the response should not be null`()
|
||||
`And the response should contain`("$.invalidParams[0].name", ".sort")
|
||||
`And the response should contain`("$.invalidParams[0].reason", "must be one of: 'name', 'createdAt'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,14 @@ fun TestApplicationEngine.`Given I have article`(
|
||||
createArticle(id?.toUUID(), workgroup, createCitizen(name = createdBy))
|
||||
}
|
||||
|
||||
fun TestApplicationEngine.`Given I have draft article`(
|
||||
id: String? = null,
|
||||
workgroup: WorkgroupRef? = null,
|
||||
createdBy: Name? = null
|
||||
) {
|
||||
createArticle(id?.toUUID(), workgroup, createCitizen(name = createdBy), draft = true)
|
||||
}
|
||||
|
||||
fun TestApplicationEngine.`Given I have article`(
|
||||
id: String? = null,
|
||||
workgroup: WorkgroupRef? = null,
|
||||
@@ -44,9 +52,10 @@ fun TestApplicationEngine.`Given I have article created by workgroup`(
|
||||
fun createArticle(
|
||||
id: UUID? = null,
|
||||
workgroup: WorkgroupRef? = null,
|
||||
createdBy: CitizenRef = createCitizen()
|
||||
createdBy: CitizenRef = createCitizen(),
|
||||
draft: Boolean = false,
|
||||
): ArticleForView {
|
||||
val articleRepository: ArticleRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val articleRepository: ArticleRepository by lazy { GlobalContext.get().get() }
|
||||
|
||||
val article = ArticleForUpdate(
|
||||
id = id ?: UUID.randomUUID(),
|
||||
@@ -55,7 +64,8 @@ fun createArticle(
|
||||
description = LoremIpsum().getParagraphs(1, 2),
|
||||
createdBy = createdBy,
|
||||
workgroup = workgroup,
|
||||
versionId = UUID.randomUUID()
|
||||
versionId = UUID.randomUUID(),
|
||||
draft = draft,
|
||||
)
|
||||
return articleRepository.upsert(article) ?: error("Cannot create article")
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ fun TestApplicationRequest.`authenticated as`(
|
||||
lastName: String,
|
||||
): Citizen {
|
||||
val username = "$firstName-$lastName".toLowerCase()
|
||||
val repo: CitizenRepository by lazy<CitizenRepository> { GlobalContext.get().koin.get() }
|
||||
val repo: CitizenRepository by lazy<CitizenRepository> { GlobalContext.get().get() }
|
||||
val citizen = repo.findByUsername(username) ?: error("Citizen not exist with username $username")
|
||||
val algorithm = GlobalContext.get().koin.get<JwtConfig>().algorithm
|
||||
val algorithm = GlobalContext.get().get<JwtConfig>().algorithm
|
||||
val jwtAsString: String = JWT.create()
|
||||
.withIssuer("dc-project.fr")
|
||||
.withClaim("id", citizen.user.id.toString())
|
||||
@@ -30,9 +30,9 @@ fun TestApplicationRequest.`authenticated in url as`(
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
): Citizen {
|
||||
val repo: CitizenRepository by lazy<CitizenRepository> { GlobalContext.get().koin.get() }
|
||||
val repo: CitizenRepository by lazy<CitizenRepository> { GlobalContext.get().get() }
|
||||
val citizen = repo.findByName(CitizenI.Name(firstName, lastName)) ?: error("Citizen not exist with name $firstName $lastName")
|
||||
val algorithm = GlobalContext.get().koin.get<JwtConfig>().algorithm
|
||||
val algorithm = GlobalContext.get().get<JwtConfig>().algorithm
|
||||
val jwtAsString: String = JWT.create()
|
||||
.withIssuer("dc-project.fr")
|
||||
.withClaim("id", citizen.user.id.toString())
|
||||
|
||||
@@ -18,7 +18,7 @@ fun TestApplicationEngine.`Given I have citizen`(
|
||||
id: String = UUID.randomUUID().toString(),
|
||||
callback: Citizen.() -> Unit = {}
|
||||
): Citizen? {
|
||||
val repo: CitizenRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val repo: CitizenRepository by lazy { GlobalContext.get().get() }
|
||||
|
||||
val user = UserForCreate(
|
||||
id = id.toUUID(),
|
||||
@@ -37,7 +37,7 @@ fun TestApplicationEngine.`Given I have citizen`(
|
||||
}
|
||||
|
||||
fun createCitizen(name: CitizenI.Name? = null, id: UUID = UUID.randomUUID()): Citizen {
|
||||
val citizenRepository: CitizenRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val citizenRepository: CitizenRepository by lazy { GlobalContext.get().get() }
|
||||
|
||||
return if (name != null) {
|
||||
citizenRepository.findByName(name) ?: error("Citizen not exist")
|
||||
|
||||
@@ -43,7 +43,7 @@ fun <A : ArticleRef> createComment(
|
||||
createdBy: Name? = null,
|
||||
content: String? = null
|
||||
): CommentForView<TargetRef, CitizenCreator> {
|
||||
val articleRepository: ArticleRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val articleRepository: ArticleRepository by lazy { GlobalContext.get().get() }
|
||||
return createCommentOnTarget(
|
||||
id,
|
||||
article?.id?.let { articleRepository.findById(article.id) } ?: createArticle(article?.id),
|
||||
@@ -67,7 +67,7 @@ fun <C : ConstitutionRef> createComment(
|
||||
createdBy: Name? = null,
|
||||
content: String? = null
|
||||
): CommentForView<TargetRef, CitizenCreator> {
|
||||
val constitutionRepository: ConstitutionRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val constitutionRepository: ConstitutionRepository by lazy { GlobalContext.get().get() }
|
||||
return createCommentOnTarget(
|
||||
id,
|
||||
constitution?.id?.let { constitutionRepository.findById(constitution.id) } ?: createConstitution(constitution?.id),
|
||||
@@ -82,7 +82,7 @@ fun <T : TargetI> createCommentOnTarget(
|
||||
createdBy: Name? = null,
|
||||
content: String? = null
|
||||
): CommentForView<TargetRef, CitizenCreator> {
|
||||
val commentRepository: CommentRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val commentRepository: CommentRepository by lazy { GlobalContext.get().get() }
|
||||
val creator = createCitizen(createdBy)
|
||||
val comment = CommentForUpdate(
|
||||
id = id ?: UUID.randomUUID(),
|
||||
@@ -114,7 +114,7 @@ fun createCommentOnComment(
|
||||
content: String? = null
|
||||
): CommentForView<out TargetRef, CitizenCreator> {
|
||||
val creator = createCitizen(createdBy)
|
||||
val commentRepository: CommentRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val commentRepository: CommentRepository by lazy { GlobalContext.get().get() }
|
||||
val parentComment = if (parent == null) {
|
||||
createComment<ArticleRef>()
|
||||
} else {
|
||||
|
||||
@@ -44,7 +44,7 @@ fun createConstitution(
|
||||
titles: List<TitleForUpdate<ArticleRef>>? = null,
|
||||
createdBy: Name? = null
|
||||
): ConstitutionForView {
|
||||
val constitutionRepository: ConstitutionRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val constitutionRepository: ConstitutionRepository by lazy { GlobalContext.get().get() }
|
||||
|
||||
val creator: CitizenWithUserI = createCitizen(createdBy)
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import fr.dcproject.component.citizen.database.CitizenRef
|
||||
import fr.dcproject.component.citizen.database.CitizenRepository
|
||||
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||
import fr.dcproject.component.follow.database.FollowArticleRepository
|
||||
import fr.dcproject.component.follow.database.FollowCitizenRepository
|
||||
import fr.dcproject.component.follow.database.FollowConstitutionRepository
|
||||
import fr.dcproject.component.follow.database.FollowForUpdate
|
||||
import io.ktor.server.testing.TestApplicationEngine
|
||||
@@ -24,35 +25,64 @@ fun Citizen.`And follow constitution`(
|
||||
) {
|
||||
createFollow(this, ConstitutionRef(constitution.toUUID()))
|
||||
}
|
||||
fun Citizen.`And follow citizen`(
|
||||
citizen: String,
|
||||
) {
|
||||
createFollow(this, CitizenRef(citizen.toUUID()))
|
||||
}
|
||||
fun Citizen.`And follow citizen`(
|
||||
name: CitizenI.Name,
|
||||
) {
|
||||
val citizenRepository: CitizenRepository by lazy { GlobalContext.get().get() }
|
||||
val citizen = citizenRepository.findByName(name) ?: error("Citizen not exist")
|
||||
createFollow(this, CitizenRef(citizen.id))
|
||||
}
|
||||
|
||||
fun TestApplicationEngine.`Given I have follow on article`(
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
article: String,
|
||||
) {
|
||||
val citizenRepository: CitizenRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val citizenRepository: CitizenRepository by lazy { GlobalContext.get().get() }
|
||||
val citizen = citizenRepository.findByName(CitizenI.Name(firstName, lastName)) ?: error("Citizen not exist")
|
||||
createFollow(citizen, ArticleRef(article.toUUID()))
|
||||
}
|
||||
|
||||
fun TestApplicationEngine.`Given I have follow on citizen`(
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
target: CitizenI.Name,
|
||||
) {
|
||||
val citizenRepository: CitizenRepository by lazy { GlobalContext.get().get() }
|
||||
val citizen = citizenRepository.findByName(CitizenI.Name(firstName, lastName)) ?: error("Citizen not exist")
|
||||
val targetCitizen = citizenRepository.findByName(target) ?: error("Citizen not exist")
|
||||
createFollow(citizen, CitizenRef(targetCitizen.id))
|
||||
}
|
||||
|
||||
fun TestApplicationEngine.`Given I have follow on constitution`(
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
constitution: String,
|
||||
) {
|
||||
val citizenRepository: CitizenRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val citizenRepository: CitizenRepository by lazy { GlobalContext.get().get() }
|
||||
val citizen = citizenRepository.findByName(CitizenI.Name(firstName, lastName)) ?: error("Citizen not exist")
|
||||
createFollow(citizen, ArticleRef(constitution.toUUID()))
|
||||
}
|
||||
|
||||
fun createFollow(citizen: CitizenRef, article: ArticleRef) {
|
||||
val followArticleRepository: FollowArticleRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val followArticleRepository: FollowArticleRepository by lazy { GlobalContext.get().get() }
|
||||
val follow = FollowForUpdate(createdBy = citizen, target = article)
|
||||
followArticleRepository.follow(follow)
|
||||
}
|
||||
|
||||
fun createFollow(citizen: CitizenRef, constitution: ConstitutionRef) {
|
||||
val followConstitutionRepository: FollowConstitutionRepository by lazy { GlobalContext.get().koin.get() }
|
||||
val followConstitutionRepository: FollowConstitutionRepository by lazy { GlobalContext.get().get() }
|
||||
val follow = FollowForUpdate(createdBy = citizen, target = constitution)
|
||||
followConstitutionRepository.follow(follow)
|
||||
}
|
||||
|
||||
fun createFollow(createdBy: CitizenRef, target: CitizenRef) {
|
||||
val followCitizenRepository: FollowCitizenRepository by lazy { GlobalContext.get().get() }
|
||||
val follow = FollowForUpdate(createdBy = createdBy, target = target)
|
||||
followCitizenRepository.follow(follow)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user