Compare commits
264 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 70092efaf5 | |||
| 9c10a88a36 | |||
| 2ee8b80596 | |||
| ee60f5b4e7 | |||
| 10928251e6 | |||
| 9b79301662 | |||
| 32510652d1 | |||
| a6c36c542e | |||
| 7874f5cec4 | |||
| 8ff6fcc970 | |||
| bd123d03e9 | |||
| ac852baaf6 | |||
| 159b9de19a | |||
| e4a85722f1 | |||
| eca5d1fe33 | |||
| 5db451ef0e | |||
| 5008b8b69f | |||
| 4504600268 | |||
| 678a2f48d2 | |||
| 460222bf69 | |||
| b497c61cfc | |||
| 36d60ce6a3 | |||
| 0253480b10 | |||
| ecae3848ea | |||
| 8640d9146d | |||
| b4be28ddc8 | |||
| 8d5ee0c130 | |||
| e16c1eedc3 | |||
| 7dc1772708 | |||
| 9e50e3af58 | |||
| e572ca0024 | |||
| 575752cdc7 | |||
| 07315a5624 | |||
| 8c25f7633e | |||
| e85c8a3d55 | |||
| 75af31ab73 | |||
| 589b6f5245 | |||
| 479793503c | |||
| ddea05aea0 | |||
| c55eba4219 | |||
| 64e3fb0134 | |||
| 06684120ce | |||
| a99eaf3eef | |||
| 118193210b | |||
| cf2881c890 | |||
| 4b96080051 | |||
| 890c84c762 | |||
| c0becd8fbd | |||
| 622deb0f7d | |||
| 86b569123c | |||
| 1055e14039 | |||
| 45dbdecdb2 | |||
| d8251f1dd2 | |||
| 559adb7e2a | |||
| 60d887c5cc | |||
| bc7bfc3fef | |||
| f225f7345c | |||
| 676baa4e28 | |||
| 0932da452b | |||
| 8c7e5bdf9b | |||
| 6b4a6f4075 | |||
| 0e912ef4b1 | |||
| 10b2844620 | |||
| bbe54c3115 | |||
| 74503c9d3a | |||
| 561905f7ab | |||
| 68238494e5 | |||
| 0221d7023a | |||
| ca78db4155 | |||
| aa7ca26b51 | |||
| 8ad0281003 | |||
| f277613820 | |||
| 373ce8e593 | |||
| 27232c5ca9 | |||
| dc034f7c51 | |||
| 491ca13284 | |||
| fb3278fa47 | |||
| 90e7e0254d | |||
| f677cac779 | |||
| 559890000d | |||
| 4a4e9651fd | |||
| 0421f3cb55 | |||
| f3e0f64249 | |||
| d34ae52522 | |||
| e8d342a729 | |||
| 977d59b60b | |||
| 5d26497cdb | |||
| cd13f9a010 | |||
| 3bf20a971a | |||
| 967b370a41 | |||
| 3a08052728 | |||
| 1418dd95bc | |||
| b678f7f2cc | |||
| af33ed9ec3 | |||
| 471013984c | |||
| 60bd24e653 | |||
| 4a2d18ff87 | |||
| ec6e39b130 | |||
| 42781565dd | |||
| 976f8fac6a | |||
| 7742287884 | |||
| f622dbeecd | |||
| 2048c71881 | |||
| de6ca63165 | |||
| a4dbd43cfb | |||
| eb7a0c7210 | |||
| 42a41da066 | |||
| 77658c5f6b | |||
| 5161dca1d5 | |||
| 24bc1520f7 | |||
| 3d2d3c2e14 | |||
| 41a98f23b8 | |||
| 813d6857e9 | |||
| 3cdd1f3a46 | |||
| e68b5d87ce | |||
| ec99df1f6a | |||
| a3b35b6273 | |||
| e97ac8368f | |||
| db744e5f31 | |||
| 6746ca08e2 | |||
| 5b4d878f8a | |||
| 846b23b550 | |||
| 32417d3276 | |||
| 54393000b3 | |||
| 8cb5c35296 | |||
| 499fbd6dcf | |||
| 11b0fad0ed | |||
| e73a7b5a18 | |||
| f76f6f83bb | |||
| 194620e15b | |||
| 95f6087b8f | |||
| 2d1d2d532a | |||
| f5030933ef | |||
| 7e52808ccf | |||
| a6f25bcbb2 | |||
| 20416ce108 | |||
| f91b25b35b | |||
| ebc552a431 | |||
| afb7f7a1a6 | |||
| c156e2a7b1 | |||
| f20964878f | |||
| d90d3117d5 | |||
| 646c199292 | |||
| 6d4339f2a5 | |||
| 9cbba66a36 | |||
| 5b44d4766d | |||
| f7feb513e0 | |||
| bc8d187914 | |||
| e55e0aea44 | |||
| c5a2f92b19 | |||
| 51cc5b640d | |||
| fad625f204 | |||
| fc138790ea | |||
| 9de2c91191 | |||
| 5352c82bba | |||
| 5d1bed5f22 | |||
| 70c69bd626 | |||
| 47bdc349c5 | |||
| be03bc4df8 | |||
| 3a77eff86e | |||
| dc490dcac3 | |||
| a37afc1ada | |||
| 2311d3986e | |||
| 68acd3b075 | |||
| 93e32caa4c | |||
| acc6ecf114 | |||
| a5b55c2d87 | |||
| bb555ce69f | |||
| c2beed416e | |||
| 05c28a2f62 | |||
| 56ea1507f7 | |||
| d7d88a3295 | |||
| 742927a590 | |||
| 6423491be3 | |||
| 4990015769 | |||
| 3533eb4a5c | |||
| 369967d5f3 | |||
| 7151da1f3f | |||
| 9cf05865a0 | |||
| 3a4a90abfe | |||
| 70b11cec7c | |||
| 1b6b748b01 | |||
| cbec0e134b | |||
| befa456a38 | |||
| cb91c50e58 | |||
| 52dfaaf814 | |||
| 9e88b33595 | |||
| f5bff403f0 | |||
| 96362bbb66 | |||
| 5e5405e5f9 | |||
| 5d78f8d4c6 | |||
| d1999d84ca | |||
| 3e21884b38 | |||
| 45a8f42335 | |||
| bc75f5d9e2 | |||
| 16b71fb1e6 | |||
| 1b8a02c2b3 | |||
| 7a8f8d3d6a | |||
| e861c29b69 | |||
| fffecd5072 | |||
| 2f079d9d9d | |||
| 9c3dce7511 | |||
| 31f1504e56 | |||
| 639b839058 | |||
| 00dbcaf7ab | |||
| cb7a2c2eaf | |||
| 569f8a5fc3 | |||
| fb2b393c2e | |||
| 201430017e | |||
| 025153c4cd | |||
| 83961fd202 | |||
| 299304f379 | |||
| 20e9c3f7ef | |||
| 679dd2e4c5 | |||
| 9fb6440736 | |||
| 11fae5f19c | |||
| c057a4aad3 | |||
| bff45ab9cf | |||
| 8e94d97c2b | |||
| b00456b31a | |||
| 4c2da6ab71 | |||
| 33f4992b5e | |||
| 51ea82d2a4 | |||
| b5cb238061 | |||
| 1fb0e39038 | |||
| ff1e34c616 | |||
| b2230f2fed | |||
| ff76bd55ef | |||
| 67665350eb | |||
| 2c717609b1 | |||
| bfcbfee120 | |||
| adba5a5aad | |||
| 0e50921a0a | |||
| b285bad593 | |||
| d6fda26fbd | |||
| 0547cf5784 | |||
| ce38aa6fe2 | |||
| f167cf02f9 | |||
| d911109cd2 | |||
| 53e8bc024c | |||
| f887b99536 | |||
| 29463e310e | |||
| 1adbef817a | |||
| d5499576ed | |||
| 36be09eed2 | |||
| 4f5cd827c4 | |||
| 9b6f3aab88 | |||
| 46885ac599 | |||
| 0108d496e0 | |||
| 21b6a525fd | |||
| 5542eede27 | |||
| 4e9f737e00 | |||
| 9b2b5e681f | |||
| 7ae30bd3cd | |||
| 9821833dd8 | |||
| 17746d9b1e | |||
| 7dffc005b9 | |||
| c1f228e3c5 | |||
| d03b8dcebb | |||
| f060943565 | |||
| 41b1f75cfb | |||
| 86d699c9c0 | |||
| 6131935036 | |||
| 6ffe529b4e |
13
.dockerignore
Normal file
13
.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
build
|
||||||
|
out
|
||||||
|
GH_TOKEN.txt
|
||||||
|
Makefile
|
||||||
|
var
|
||||||
|
gradle
|
||||||
|
.idea
|
||||||
|
.gradle
|
||||||
|
docker
|
||||||
|
gradlew
|
||||||
|
gradlew.bat
|
||||||
|
docker-compose.yml
|
||||||
|
src/test
|
||||||
15
.env
15
.env
@@ -2,12 +2,25 @@ NAME=dc-project
|
|||||||
|
|
||||||
DATABASE_URL=jdbc:postgresql:dc-project
|
DATABASE_URL=jdbc:postgresql:dc-project
|
||||||
|
|
||||||
|
APP_PORT=8080
|
||||||
|
OPENAPI_PORT=8181
|
||||||
|
SONARQUBE_PORT=9002
|
||||||
|
|
||||||
ELASTIC_REST=9200
|
ELASTIC_REST=9200
|
||||||
ELASTIC_NODES=9300
|
ELASTIC_NODES=9300
|
||||||
|
ELASTICSEARCH_CONNECTION=http://elasticsearch:9200
|
||||||
|
|
||||||
POSTGRESQL_PORT=5432
|
POSTGRESQL_PORT=5432
|
||||||
DB_HOST=db
|
DB_HOST=db
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
DB_NAME=dc-project
|
DB_NAME=dc-project
|
||||||
DB_USER=dc-project
|
DB_USER=dc-project
|
||||||
DB_PWD=dc-project
|
DB_PWD=dc-project
|
||||||
|
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_CONNECTION=redis://redis:6379
|
||||||
|
REDIS_COMMANDER_PORT=8081
|
||||||
|
|
||||||
|
RABBITMQ_PORT=5672
|
||||||
|
RABBITMQ_CONNECTION=amqp://rabbitmq:5672
|
||||||
|
RABBITMQ_MANAGEMENT_PORT=15672
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,8 +1,11 @@
|
|||||||
/.gradle
|
/.gradle
|
||||||
/.idea
|
/.idea/*
|
||||||
|
!/.idea/runConfigurations
|
||||||
/out
|
/out
|
||||||
/build
|
/build
|
||||||
*.ipr
|
*.ipr
|
||||||
*.iws
|
*.iws
|
||||||
dcproject.iml
|
dcproject.iml
|
||||||
/var
|
/var
|
||||||
|
GH_TOKEN.txt
|
||||||
|
allSQL.sql
|
||||||
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dcproject
|
||||||
1
.idea/codeStyles/Project.xml
generated
1
.idea/codeStyles/Project.xml
generated
@@ -1,5 +1,6 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
|
<option name="LINE_SEPARATOR" value=" " />
|
||||||
<JetCodeStyleSettings>
|
<JetCodeStyleSettings>
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
</JetCodeStyleSettings>
|
</JetCodeStyleSettings>
|
||||||
|
|||||||
6
.idea/dataSources.xml
generated
6
.idea/dataSources.xml
generated
@@ -7,5 +7,11 @@
|
|||||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||||
<jdbc-url>jdbc:postgresql://localhost:5432/dc-project</jdbc-url>
|
<jdbc-url>jdbc:postgresql://localhost:5432/dc-project</jdbc-url>
|
||||||
</data-source>
|
</data-source>
|
||||||
|
<data-source source="LOCAL" name="test@localhost" uuid="a9a6d0e9-327d-4e7d-9b93-3cb6f7948866">
|
||||||
|
<driver-ref>postgresql</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:postgresql://localhost:5432/test</jdbc-url>
|
||||||
|
</data-source>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
3
.idea/gradle.xml
generated
3
.idea/gradle.xml
generated
@@ -4,11 +4,10 @@
|
|||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="delegatedBuild" value="true" />
|
<option name="delegatedBuild" value="false" />
|
||||||
<option name="testRunner" value="PLATFORM" />
|
<option name="testRunner" value="PLATFORM" />
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="11" />
|
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
|
|||||||
8
.idea/misc.xml
generated
8
.idea/misc.xml
generated
@@ -1,7 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="JavaScriptSettings">
|
||||||
|
<option name="languageLevel" value="ES6" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="corretto-11" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
22
.idea/runConfigurations/All_Tests.xml
generated
22
.idea/runConfigurations/All_Tests.xml
generated
@@ -1,22 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="All Tests" type="JUnit" factoryName="JUnit" show_console_on_std_err="true">
|
|
||||||
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
|
||||||
<log_file alias="test.log" path="$PROJECT_DIR$/var/log/test" />
|
|
||||||
<module name="dcproject.test" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
|
||||||
<option name="ALTERNATIVE_JRE_PATH" value="11" />
|
|
||||||
<option name="PACKAGE_NAME" value="fr.dcproject" />
|
|
||||||
<option name="MAIN_CLASS_NAME" value="" />
|
|
||||||
<option name="METHOD_NAME" value="" />
|
|
||||||
<option name="TEST_OBJECT" value="directory" />
|
|
||||||
<option name="PARAMETERS" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
|
||||||
<option name="TEST_SEARCH_SCOPE">
|
|
||||||
<value defaultName="wholeProject" />
|
|
||||||
</option>
|
|
||||||
<dir value="$PROJECT_DIR$" />
|
|
||||||
<method v="2">
|
|
||||||
<option name="Make" enabled="true" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
24
.idea/runConfigurations/All_Tests___Lint.xml
generated
Normal file
24
.idea/runConfigurations/All_Tests___Lint.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="All Tests + Lint" type="JUnit" factoryName="JUnit" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="PACKAGE_NAME" value="fr.dcproject" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="package" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Djdk.attach.allowAttachSelf=true" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Lint" run_configuration_type="GradleRunConfiguration" />
|
||||||
|
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Test All SQL" run_configuration_type="ShConfigurationType" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
25
.idea/runConfigurations/All_Tests___Lint__offline_.xml
generated
Normal file
25
.idea/runConfigurations/All_Tests___Lint__offline_.xml
generated
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="All Tests + Lint (offline)" type="JUnit" factoryName="JUnit" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="PACKAGE_NAME" value="fr.dcproject" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="tags" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Dcucumber.options="--tags ~@online" -Djdk.attach.allowAttachSelf=true" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<tag value="!online" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Lint" run_configuration_type="GradleRunConfiguration" />
|
||||||
|
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Test All SQL" run_configuration_type="ShConfigurationType" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.idea/runConfigurations/Article_Tests.xml
generated
Normal file
24
.idea/runConfigurations/Article_Tests.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Article Tests" type="JUnit" factoryName="JUnit" folderName="Cucumber" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<module name="dcproject.test" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="class" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Dcucumber.filter.tags="@article" -Dstrict" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<tag value="!online" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.idea/runConfigurations/Auth_Tests.xml
generated
Normal file
24
.idea/runConfigurations/Auth_Tests.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Auth Tests" type="JUnit" factoryName="JUnit" folderName="Cucumber" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<module name="dcproject.test" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="class" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Dcucumber.filter.tags="@auth"" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<tag value="!online" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
31
.idea/runConfigurations/Build.xml
generated
Normal file
31
.idea/runConfigurations/Build.xml
generated
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Build" 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="build" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<extension name="net.ashald.envfile">
|
||||||
|
<option name="IS_ENABLED" value="false" />
|
||||||
|
<option name="IS_SUBST" value="false" />
|
||||||
|
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||||
|
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||||
|
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||||
|
<ENTRIES>
|
||||||
|
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
|
||||||
|
</ENTRIES>
|
||||||
|
</extension>
|
||||||
|
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
19
.idea/runConfigurations/Build_and_start_all_Docker.xml
generated
Normal file
19
.idea/runConfigurations/Build_and_start_all_Docker.xml
generated
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Build and start all Docker" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
||||||
|
<deployment type="docker-compose.yml">
|
||||||
|
<settings>
|
||||||
|
<option name="envVars">
|
||||||
|
<list>
|
||||||
|
<DockerEnvVarImpl>
|
||||||
|
<option name="name" value="SEND_GRID_KEY" />
|
||||||
|
<option name="value" value="$SEND_GRID_KEY$" />
|
||||||
|
</DockerEnvVarImpl>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="commandLineOptions" value="--build" />
|
||||||
|
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
31
.idea/runConfigurations/Build_without_test.xml
generated
Normal file
31
.idea/runConfigurations/Build_without_test.xml
generated
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Build without test" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<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="taskDescriptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="taskNames">
|
||||||
|
<list>
|
||||||
|
<option value="build" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<extension name="net.ashald.envfile">
|
||||||
|
<option name="IS_ENABLED" value="false" />
|
||||||
|
<option name="IS_SUBST" value="false" />
|
||||||
|
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||||
|
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||||
|
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||||
|
<ENTRIES>
|
||||||
|
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
|
||||||
|
</ENTRIES>
|
||||||
|
</extension>
|
||||||
|
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.idea/runConfigurations/Citizen_Tests.xml
generated
Normal file
24
.idea/runConfigurations/Citizen_Tests.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Citizen Tests" type="JUnit" factoryName="JUnit" folderName="Cucumber" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<module name="dcproject.test" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="class" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Dcucumber.filter.tags="@citizen"" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<tag value="!online" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.idea/runConfigurations/Comment_Tests.xml
generated
Normal file
24
.idea/runConfigurations/Comment_Tests.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Comment Tests" type="JUnit" factoryName="JUnit" folderName="Cucumber" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<module name="dcproject.test" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="class" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Dcucumber.filter.tags="@comment"" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<tag value="!online" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.idea/runConfigurations/Constitution_Tests.xml
generated
Normal file
24
.idea/runConfigurations/Constitution_Tests.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Constitution Tests" type="JUnit" factoryName="JUnit" folderName="Cucumber" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<module name="dcproject.test" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="class" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Dcucumber.filter.tags="@constitution"" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<tag value="!online" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="RunCucumberTest" type="JUnit" factoryName="JUnit" nameIsGenerated="true">
|
<configuration default="false" name="Cucumber Tests" type="JUnit" factoryName="JUnit">
|
||||||
<output_file path="$PROJECT_DIR$/var/log/test/cucumber.out.log" is_save="true" />
|
<output_file path="$PROJECT_DIR$/var/log/test/cucumber.out.log" />
|
||||||
<log_file alias="cucumber.log" path="$PROJECT_DIR$/var/log/test" />
|
|
||||||
<module name="dcproject.test" />
|
<module name="dcproject.test" />
|
||||||
|
<useClassPathOnly />
|
||||||
<option name="PACKAGE_NAME" value="" />
|
<option name="PACKAGE_NAME" value="" />
|
||||||
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
||||||
<option name="METHOD_NAME" value="testArticle" />
|
<option name="METHOD_NAME" value="" />
|
||||||
<option name="TEST_OBJECT" value="class" />
|
<option name="TEST_OBJECT" value="class" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Djdk.attach.allowAttachSelf=true" />
|
||||||
<option name="PARAMETERS" value="" />
|
<option name="PARAMETERS" value="" />
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
30
.idea/runConfigurations/Cucumber_Tests__offline_.xml
generated
Normal file
30
.idea/runConfigurations/Cucumber_Tests__offline_.xml
generated
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Cucumber Tests (offline)" type="JUnit" factoryName="JUnit" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<module name="dcproject.test" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<extension name="coverage">
|
||||||
|
<pattern>
|
||||||
|
<option name="PATTERN" value="fr.dcproject.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="class" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Dcucumber.filter.tags=" not @online" -Djdk.attach.allowAttachSelf=true" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<tag value="!online" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.idea/runConfigurations/Follow_Tests.xml
generated
Normal file
24
.idea/runConfigurations/Follow_Tests.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Follow Tests" type="JUnit" factoryName="JUnit" folderName="Cucumber" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<module name="dcproject.test" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="class" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Dcucumber.filter.tags="@follow"" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<tag value="!online" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
31
.idea/runConfigurations/Lint.xml
generated
Normal file
31
.idea/runConfigurations/Lint.xml
generated
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Lint" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="executionName" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="externalSystemIdString" value="GRADLE" />
|
||||||
|
<option name="scriptParameters" value="" />
|
||||||
|
<option name="taskDescriptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="taskNames">
|
||||||
|
<list>
|
||||||
|
<option value="ktlintCheck" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<extension name="net.ashald.envfile">
|
||||||
|
<option name="IS_ENABLED" value="false" />
|
||||||
|
<option name="IS_SUBST" value="false" />
|
||||||
|
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||||
|
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||||
|
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||||
|
<ENTRIES>
|
||||||
|
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
|
||||||
|
</ENTRIES>
|
||||||
|
</extension>
|
||||||
|
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
39
.idea/runConfigurations/Lint_Test_Sonar___Run.xml
generated
Normal file
39
.idea/runConfigurations/Lint_Test_Sonar___Run.xml
generated
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Lint+Test+Sonar & Run" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="env">
|
||||||
|
<map>
|
||||||
|
<entry key="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
<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="run" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<extension name="net.ashald.envfile">
|
||||||
|
<option name="IS_ENABLED" value="false" />
|
||||||
|
<option name="IS_SUBST" value="false" />
|
||||||
|
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||||
|
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||||
|
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||||
|
<ENTRIES>
|
||||||
|
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
|
||||||
|
</ENTRIES>
|
||||||
|
</extension>
|
||||||
|
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
|
||||||
|
<method v="2">
|
||||||
|
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All Tests + Lint" run_configuration_type="JUnit" />
|
||||||
|
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Sonarqube" run_configuration_type="GradleRunConfiguration" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
38
.idea/runConfigurations/Lint__Test___Run.xml
generated
Normal file
38
.idea/runConfigurations/Lint__Test___Run.xml
generated
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Lint, Test & Run" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="env">
|
||||||
|
<map>
|
||||||
|
<entry key="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
<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="run" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<extension name="net.ashald.envfile">
|
||||||
|
<option name="IS_ENABLED" value="false" />
|
||||||
|
<option name="IS_SUBST" value="false" />
|
||||||
|
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||||
|
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||||
|
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||||
|
<ENTRIES>
|
||||||
|
<ENTRY IS_ENABLED="true" PARSER="runconfig" />
|
||||||
|
</ENTRIES>
|
||||||
|
</extension>
|
||||||
|
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
|
||||||
|
<method v="2">
|
||||||
|
<option name="RunConfigurationTask" enabled="true" run_configuration_name="All Tests + Lint" run_configuration_type="JUnit" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.idea/runConfigurations/Mark_as__error.xml
generated
Normal file
24
.idea/runConfigurations/Mark_as__error.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Mark as @error" type="JUnit" factoryName="JUnit" folderName="Cucumber" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<module name="dcproject.test" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="class" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Dcucumber.filter.tags="@error" -Dstrict" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<tag value="!online" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.idea/runConfigurations/Opinion_Tests.xml
generated
Normal file
24
.idea/runConfigurations/Opinion_Tests.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Opinion Tests" type="JUnit" factoryName="JUnit" folderName="Cucumber" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<module name="dcproject.test" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="class" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Dcucumber.filter.tags="@opinion"" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<tag value="!online" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
13
.idea/runConfigurations/Reset_DB_on_DEV.xml
generated
Normal file
13
.idea/runConfigurations/Reset_DB_on_DEV.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Reset DB on DEV" type="ShConfigurationType">
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/src/main/resources/sql/resetDB.sh" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$/src/main/resources/sql" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="false" />
|
||||||
|
<option name="INTERPRETER_PATH" value="C:/Program Files/Git/bin/bash.exe" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
5
.idea/runConfigurations/Run.xml
generated
5
.idea/runConfigurations/Run.xml
generated
@@ -1,6 +1,11 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Run" type="GradleRunConfiguration" factoryName="Gradle">
|
<configuration default="false" name="Run" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
<ExternalSystemSettings>
|
<ExternalSystemSettings>
|
||||||
|
<option name="env">
|
||||||
|
<map>
|
||||||
|
<entry key="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
<option name="executionName" />
|
<option name="executionName" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="externalSystemIdString" value="GRADLE" />
|
<option name="externalSystemIdString" value="GRADLE" />
|
||||||
|
|||||||
27
.idea/runConfigurations/Run_dependencies.xml
generated
Normal file
27
.idea/runConfigurations/Run_dependencies.xml
generated
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Run dependencies" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
||||||
|
<deployment type="docker-compose.yml">
|
||||||
|
<settings>
|
||||||
|
<option name="envVars">
|
||||||
|
<list>
|
||||||
|
<DockerEnvVarImpl>
|
||||||
|
<option name="name" value="SEND_GRID_KEY" />
|
||||||
|
<option name="value" value="$SEND_GRID_KEY$" />
|
||||||
|
</DockerEnvVarImpl>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="commandLineOptions" value="--build" />
|
||||||
|
<option name="services">
|
||||||
|
<list>
|
||||||
|
<option value="db" />
|
||||||
|
<option value="elasticsearch" />
|
||||||
|
<option value="rabbitmq" />
|
||||||
|
<option value="redis" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
13
.idea/runConfigurations/SQL_Fixtures_on_DEV.xml
generated
Normal file
13
.idea/runConfigurations/SQL_Fixtures_on_DEV.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="SQL Fixtures on DEV" type="ShConfigurationType">
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/src/main/resources/sql/fixtures/fixtures.sh" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$/src/main/resources/sql/fixtures/" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="false" />
|
||||||
|
<option name="INTERPRETER_PATH" value="C:/Program Files/Git/bin/bash.exe" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
21
.idea/runConfigurations/Sonarqube.xml
generated
Normal file
21
.idea/runConfigurations/Sonarqube.xml
generated
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Sonarqube" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="executionName" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="externalSystemIdString" value="GRADLE" />
|
||||||
|
<option name="scriptParameters" value="-x test" />
|
||||||
|
<option name="taskDescriptions">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="taskNames">
|
||||||
|
<list>
|
||||||
|
<option value="sonarqube" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<GradleScriptDebugEnabled>true</GradleScriptDebugEnabled>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
26
.idea/runConfigurations/Start_App.xml
generated
Normal file
26
.idea/runConfigurations/Start_App.xml
generated
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Start App" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
||||||
|
<deployment type="docker-compose.yml">
|
||||||
|
<settings>
|
||||||
|
<option name="envVars">
|
||||||
|
<list>
|
||||||
|
<DockerEnvVarImpl>
|
||||||
|
<option name="name" value="SEND_GRID_KEY" />
|
||||||
|
<option name="value" value="$SEND_GRID_KEY$" />
|
||||||
|
</DockerEnvVarImpl>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="commandLineOptions" value="--build" />
|
||||||
|
<option name="services">
|
||||||
|
<list>
|
||||||
|
<option value="app" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2">
|
||||||
|
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Build without test" run_configuration_type="GradleRunConfiguration" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.idea/runConfigurations/Start_Db.xml
generated
Normal file
24
.idea/runConfigurations/Start_Db.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Start Db" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
||||||
|
<deployment type="docker-compose.yml">
|
||||||
|
<settings>
|
||||||
|
<option name="envVars">
|
||||||
|
<list>
|
||||||
|
<DockerEnvVarImpl>
|
||||||
|
<option name="name" value="SEND_GRID_KEY" />
|
||||||
|
<option name="value" value="$SEND_GRID_KEY$" />
|
||||||
|
</DockerEnvVarImpl>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="commandLineOptions" value="--build" />
|
||||||
|
<option name="services">
|
||||||
|
<list>
|
||||||
|
<option value="db" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Docker" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
<configuration default="false" name="Start Elasticsearch" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
||||||
<deployment type="docker-compose.yml">
|
<deployment type="docker-compose.yml">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="commandLineOptions" value="--build" />
|
<option name="commandLineOptions" value="--build" />
|
||||||
|
<option name="services">
|
||||||
|
<list>
|
||||||
|
<option value="elasticsearch" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
<option name="sourceFilePath" value="docker-compose.yml" />
|
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||||
</settings>
|
</settings>
|
||||||
</deployment>
|
</deployment>
|
||||||
24
.idea/runConfigurations/Start_OpenAPI.xml
generated
Normal file
24
.idea/runConfigurations/Start_OpenAPI.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Start OpenAPI" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
||||||
|
<deployment type="docker-compose.yml">
|
||||||
|
<settings>
|
||||||
|
<option name="envVars">
|
||||||
|
<list>
|
||||||
|
<DockerEnvVarImpl>
|
||||||
|
<option name="name" value="SEND_GRID_KEY" />
|
||||||
|
<option name="value" value="$SEND_GRID_KEY$" />
|
||||||
|
</DockerEnvVarImpl>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="commandLineOptions" value="--build" />
|
||||||
|
<option name="services">
|
||||||
|
<list>
|
||||||
|
<option value="openapi" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.idea/runConfigurations/Start_RabbitMQ.xml
generated
Normal file
24
.idea/runConfigurations/Start_RabbitMQ.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Start RabbitMQ" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
||||||
|
<deployment type="docker-compose.yml">
|
||||||
|
<settings>
|
||||||
|
<option name="envVars">
|
||||||
|
<list>
|
||||||
|
<DockerEnvVarImpl>
|
||||||
|
<option name="name" value="SEND_GRID_KEY" />
|
||||||
|
<option name="value" value="$SEND_GRID_KEY$" />
|
||||||
|
</DockerEnvVarImpl>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="commandLineOptions" value="--build" />
|
||||||
|
<option name="services">
|
||||||
|
<list>
|
||||||
|
<option value="rabbitmq" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.idea/runConfigurations/Start_Redis.xml
generated
Normal file
24
.idea/runConfigurations/Start_Redis.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Start Redis" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
||||||
|
<deployment type="docker-compose.yml">
|
||||||
|
<settings>
|
||||||
|
<option name="envVars">
|
||||||
|
<list>
|
||||||
|
<DockerEnvVarImpl>
|
||||||
|
<option name="name" value="SEND_GRID_KEY" />
|
||||||
|
<option name="value" value="$SEND_GRID_KEY$" />
|
||||||
|
</DockerEnvVarImpl>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="commandLineOptions" value="--build" />
|
||||||
|
<option name="services">
|
||||||
|
<list>
|
||||||
|
<option value="redis" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
13
.idea/runConfigurations/Test_All_SQL.xml
generated
Normal file
13
.idea/runConfigurations/Test_All_SQL.xml
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Test All SQL" type="ShConfigurationType">
|
||||||
|
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
|
||||||
|
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/src/test/sql/test.sh" />
|
||||||
|
<option name="SCRIPT_OPTIONS" value="1" />
|
||||||
|
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
|
||||||
|
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$/src/test/sql" />
|
||||||
|
<option name="INDEPENDENT_INTERPRETER_PATH" value="false" />
|
||||||
|
<option name="INTERPRETER_PATH" value="C:/Program Files/Git/bin/bash.exe" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
24
.idea/runConfigurations/Vote_Tests.xml
generated
Normal file
24
.idea/runConfigurations/Vote_Tests.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Vote Tests" type="JUnit" factoryName="JUnit" folderName="Cucumber" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<module name="dcproject.test" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="class" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Dcucumber.filter.tags="@vote"" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<tag value="!online" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
29
.idea/runConfigurations/Voter_Tests.xml
generated
Normal file
29
.idea/runConfigurations/Voter_Tests.xml
generated
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Voter Tests" type="JUnit" factoryName="JUnit" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<extension name="coverage">
|
||||||
|
<pattern>
|
||||||
|
<option name="PATTERN" value="fr.dcproject.security.voter.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
|
<option name="PACKAGE_NAME" value="fr.dcproject" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="tags" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Djdk.attach.allowAttachSelf=true" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<tag value="voter" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
30
.idea/runConfigurations/Workgroup_test.xml
generated
Normal file
30
.idea/runConfigurations/Workgroup_test.xml
generated
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Workgroup test" type="JUnit" factoryName="JUnit" folderName="Cucumber" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<module name="dcproject.test" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<extension name="coverage" sample_coverage="false">
|
||||||
|
<pattern>
|
||||||
|
<option name="PATTERN" value="fr.dcproject.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="class" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Dcucumber.filter.tags="@workgroup"" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
|
<value defaultName="wholeProject" />
|
||||||
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<tag value="!online" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
48
Makefile
Normal file
48
Makefile
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
.EXPORT_ALL_VARIABLES:
|
||||||
|
VERSION=$(shell ./hook/version.sh)
|
||||||
|
GITHUB_USERNAME=$(shell git config user.email)
|
||||||
|
GITHUB_TOKEN=$(shell cat ./GH_TOKEN.txt)
|
||||||
|
|
||||||
|
# HELP
|
||||||
|
# This will output the help for each task
|
||||||
|
# thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
||||||
|
.PHONY: help
|
||||||
|
|
||||||
|
help: ## This help.
|
||||||
|
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
|
bd: build-docker
|
||||||
|
|
||||||
|
build-docker: ## Build the docker image of application
|
||||||
|
docker build -t dc-project -f docker/app/Dockerfile .
|
||||||
|
|
||||||
|
pd: publish-docker
|
||||||
|
|
||||||
|
publish-docker: build-docker ## Publish docker image of application to Github
|
||||||
|
git diff --quiet --exit-code || (echo "The git is DIRTY !!! You cannot publish this crap!" && exit 1)
|
||||||
|
cat ./GH_TOKEN.txt | docker login docker.pkg.github.com -u ${GITHUB_USERNAME} --password-stdin
|
||||||
|
docker tag dc-project docker.pkg.github.com/flecomte/dc-project/dc-project:${VERSION}
|
||||||
|
docker push docker.pkg.github.com/flecomte/dc-project/dc-project:${VERSION}
|
||||||
|
|
||||||
|
rd: run-docker
|
||||||
|
|
||||||
|
run-docker: ## Build and Run all docker services
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
pm: publish-maven
|
||||||
|
|
||||||
|
publish-maven: ## Publish JAR file to Github
|
||||||
|
@git diff --quiet --exit-code || (echo "The git is DIRTY !!! You cannot publish this crap!" && exit 1)
|
||||||
|
gradlew publish
|
||||||
|
|
||||||
|
f: fixtures
|
||||||
|
|
||||||
|
fixtures: ## Import fixtures
|
||||||
|
bash src/main/resources/sql/fixtures/fixtures.sh
|
||||||
|
|
||||||
|
v: vertion
|
||||||
|
|
||||||
|
vertion: ## Show current version
|
||||||
|
@echo ${VERSION}
|
||||||
42
README.md
Normal file
42
README.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# DC Project
|
||||||
|
|
||||||
|
## Installation of Development environment
|
||||||
|
### On windows
|
||||||
|
1. Install git
|
||||||
|
- Download and install: https://git-scm.com/download/win
|
||||||
|
2. Install Make
|
||||||
|
- Go to [ezwinports](https://sourceforge.net/projects/ezwinports/files/).
|
||||||
|
- Download `make-4.1-2-without-guile-w32-bin.zip` (get the version without guile).
|
||||||
|
- Extract zip.
|
||||||
|
- Copy the contents to your `Git\mingw64\` merging the folders, but **do NOT overwrite/replace** any existing files.
|
||||||
|
|
||||||
|
### Add JAVA_HOME path
|
||||||
|
In CMD (Not Powershell)
|
||||||
|
```cmd
|
||||||
|
$ setx -m JAVA_HOME "C:\Users\%USERNAME%\.jdks\corretto-11.0.7"
|
||||||
|
$ setx -m PATH "%PATH%;%JAVA_HOME%\bin";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run dockers
|
||||||
|
```bash
|
||||||
|
$ make run-docker
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add fixtures
|
||||||
|
```bash
|
||||||
|
$ make fixtures
|
||||||
|
```
|
||||||
|
|
||||||
|
## Publish package
|
||||||
|
1. Got to [https://github.com](https://github.com/settings/tokens/new) and create a new token with packages scopes
|
||||||
|
2. Create a file `GH_TOKEN.txt` and put it the github token
|
||||||
|
|
||||||
|
### Maven
|
||||||
|
```bash
|
||||||
|
$ make publish-maven
|
||||||
|
```
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ make publish-docker
|
||||||
|
```
|
||||||
132
build.gradle.kts
132
build.gradle.kts
@@ -1,49 +1,153 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
import org.owasp.dependencycheck.reporting.ReportGenerator
|
||||||
|
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
val ktor_version: String by project
|
val ktor_version: String by project
|
||||||
val kotlin_version: String by project
|
val kotlin_version: String by project
|
||||||
|
val coroutinesVersion: String by project
|
||||||
val logback_version: String by project
|
val logback_version: String by project
|
||||||
val koinVersion: String by project
|
val koinVersion: String by project
|
||||||
val postgresjson_version: String by project
|
val jackson_version: String by project
|
||||||
|
val cucumber_version: String by project
|
||||||
|
|
||||||
plugins {
|
group = "com.github.flecomte"
|
||||||
application
|
version = versioning.info.run {
|
||||||
kotlin("jvm") version "1.3.40"
|
if (dirty) {
|
||||||
|
versioning.info.full
|
||||||
|
} else {
|
||||||
|
versioning.info.lastTag
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "fr.dcproject"
|
plugins {
|
||||||
version = "0.0.1"
|
jacoco
|
||||||
|
application
|
||||||
|
|
||||||
|
id("maven-publish")
|
||||||
|
id("org.jetbrains.kotlin.jvm") version "1.3.50"
|
||||||
|
|
||||||
|
id("com.github.johnrengelman.shadow") version "5.2.0"
|
||||||
|
id("org.jlleitschuh.gradle.ktlint") version "8.2.0"
|
||||||
|
id("org.owasp.dependencycheck") version "5.1.0"
|
||||||
|
id("org.sonarqube") version "2.7"
|
||||||
|
id("net.nemerosa.versioning") version "2.13.1"
|
||||||
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClassName = "io.ktor.server.netty.EngineMain"
|
mainClassName = "io.ktor.server.jetty.EngineMain"
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<KotlinCompile> {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tasks.withType<Jar> {
|
||||||
|
manifest {
|
||||||
|
attributes(
|
||||||
|
mapOf(
|
||||||
|
"Main-Class" to application.mainClassName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
named<ShadowJar>("shadowJar") {
|
||||||
|
mergeServiceFiles("META-INF/services")
|
||||||
|
archiveFileName.set("${archiveBaseName.get()}-latest-all.${archiveExtension.get()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val sourcesJar by tasks.creating(Jar::class) {
|
||||||
|
archiveClassifier.set("sources")
|
||||||
|
from(sourceSets.getByName("main").allSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
if (versioning.info.dirty == false) {
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
name = "dc-project"
|
||||||
|
group = "com.github.flecomte"
|
||||||
|
url = uri("https://maven.pkg.github.com/flecomte/dc-project")
|
||||||
|
credentials {
|
||||||
|
username = System.getenv("GITHUB_USERNAME")
|
||||||
|
password = System.getenv("GITHUB_TOKEN")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
publications {
|
||||||
|
create<MavenPublication>("dc-project") {
|
||||||
|
from(components["java"])
|
||||||
|
artifact(sourcesJar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LoggerFactory.getLogger("gradle")
|
||||||
|
.warn("The git is DIRTY !!! You cannot publish this crap! (${versioning.info.full})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jacoco {
|
||||||
|
toolVersion = "0.8.3"
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.jacocoTestReport {
|
||||||
|
reports {
|
||||||
|
xml.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyCheck {
|
||||||
|
formats = listOf(ReportGenerator.Format.HTML, ReportGenerator.Format.XML)
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
jcenter()
|
jcenter()
|
||||||
maven { url = uri("https://kotlin.bintray.com/ktor") }
|
maven { url = uri("https://kotlin.bintray.com/ktor") }
|
||||||
|
maven { url = uri("https://jitpack.io") }
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version")
|
||||||
implementation("io.ktor:ktor-server-netty:$ktor_version")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$coroutinesVersion")
|
||||||
|
implementation("io.ktor:ktor-server-jetty:$ktor_version")
|
||||||
|
implementation("io.ktor:ktor-client-jetty:$ktor_version")
|
||||||
implementation("ch.qos.logback:logback-classic:$logback_version")
|
implementation("ch.qos.logback:logback-classic:$logback_version")
|
||||||
implementation("io.ktor:ktor-server-core:$ktor_version")
|
implementation("io.ktor:ktor-server-core:$ktor_version")
|
||||||
implementation("io.ktor:ktor-locations:$ktor_version")
|
implementation("io.ktor:ktor-locations:$ktor_version")
|
||||||
implementation("io.ktor:ktor-auth:$ktor_version")
|
implementation("io.ktor:ktor-auth:$ktor_version")
|
||||||
implementation("io.ktor:ktor-auth-jwt:$ktor_version")
|
implementation("io.ktor:ktor-auth-jwt:$ktor_version")
|
||||||
implementation("io.ktor:ktor-gson:$ktor_version")
|
implementation("io.ktor:ktor-gson:$ktor_version")
|
||||||
|
implementation("io.ktor:ktor-auth-jwt:$ktor_version")
|
||||||
|
implementation("io.ktor:ktor-websockets:$ktor_version")
|
||||||
implementation("org.koin:koin-ktor:$koinVersion")
|
implementation("org.koin:koin-ktor:$koinVersion")
|
||||||
implementation("io.ktor:ktor-jackson:$ktor_version")
|
implementation("io.ktor:ktor-jackson:$ktor_version")
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.9")
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jackson_version")
|
||||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.9.9")
|
implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson_version")
|
||||||
implementation("net.pearx.kasechange:kasechange-jvm:1.1.0")
|
implementation("net.pearx.kasechange:kasechange-jvm:1.1.0")
|
||||||
implementation("fr.postgresjson:postgresjson-jdbc:$postgresjson_version")
|
implementation("com.auth0:java-jwt:3.8.2")
|
||||||
|
implementation("com.github.jasync-sql:jasync-postgresql:1.0.7")
|
||||||
|
implementation("com.github.flecomte:postgres-json:1.1.1")
|
||||||
|
implementation("com.github.flecomte:ktor-voter:1.0.1")
|
||||||
|
implementation("com.sendgrid:sendgrid-java:4.4.1")
|
||||||
|
implementation("io.lettuce:lettuce-core:5.2.2.RELEASE")
|
||||||
|
implementation("com.rabbitmq:amqp-client:5.8.0")
|
||||||
|
implementation("org.elasticsearch.client:elasticsearch-rest-client:6.7.1")
|
||||||
|
implementation("com.jayway.jsonpath:json-path:2.4.0")
|
||||||
|
|
||||||
testImplementation("io.ktor:ktor-server-tests:$ktor_version")
|
testImplementation("io.ktor:ktor-server-tests:$ktor_version")
|
||||||
testImplementation("io.ktor:ktor-client-mock:$ktor_version")
|
testImplementation("io.ktor:ktor-client-mock:$ktor_version")
|
||||||
testImplementation("io.ktor:ktor-client-mock-jvm:$ktor_version")
|
testImplementation("io.ktor:ktor-client-mock-jvm:$ktor_version")
|
||||||
testImplementation("org.koin:koin-test:$koinVersion")
|
testImplementation("org.koin:koin-test:$koinVersion")
|
||||||
testImplementation("io.mockk:mockk:1.9")
|
testImplementation("io.mockk:mockk:1.9.3")
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter:5.5.0")
|
testImplementation("org.junit.jupiter:junit-jupiter:5.5.0")
|
||||||
testImplementation("org.amshove.kluent:kluent:1.4")
|
testImplementation("org.amshove.kluent:kluent:1.4")
|
||||||
testImplementation("io.cucumber:cucumber-java8:4.3.1")
|
testImplementation("io.cucumber:cucumber-java8:$cucumber_version")
|
||||||
testImplementation("io.cucumber:cucumber-junit:4.3.1")
|
testImplementation("io.cucumber:cucumber-junit:$cucumber_version")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,72 @@
|
|||||||
# Add the "-d" flag at the end for detached execution
|
# Add the "-d" flag at the end for detached execution
|
||||||
version: '3.7'
|
version: '3.7'
|
||||||
services:
|
services:
|
||||||
|
sonarqube:
|
||||||
|
container_name: sonarqube_${NAME}
|
||||||
|
image: sonarqube
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- ${SONARQUBE_PORT}:9000
|
||||||
|
|
||||||
|
openapi:
|
||||||
|
container_name: openapi_${NAME}
|
||||||
|
image: swaggerapi/swagger-ui
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- ${OPENAPI_PORT}:8080
|
||||||
|
environment:
|
||||||
|
URL: "http://localhost:8080"
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
container_name: rabbitmq_${NAME}
|
||||||
|
image: rabbitmq:management-alpine
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- ${RABBITMQ_PORT}:5672
|
||||||
|
- ${RABBITMQ_MANAGEMENT_PORT}:15672
|
||||||
|
|
||||||
|
redis:
|
||||||
|
container_name: redis_${NAME}
|
||||||
|
image: redis:6.0-rc-alpine
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- ${REDIS_PORT}:6379
|
||||||
|
volumes:
|
||||||
|
- redis-data:/var/lib/redis:rw
|
||||||
|
|
||||||
|
app:
|
||||||
|
container_name: app_${NAME}
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: docker/app/Dockerfile
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- ${APP_PORT}:8080
|
||||||
|
environment:
|
||||||
|
DB_HOST: ${DB_HOST}
|
||||||
|
SEND_GRID_KEY: ${SEND_GRID_KEY}
|
||||||
|
REDIS_CONNECTION: ${REDIS_CONNECTION}
|
||||||
|
RABBITMQ_CONNECTION: ${RABBITMQ_CONNECTION}
|
||||||
|
ELASTICSEARCH_CONNECTION: ${ELASTICSEARCH_CONNECTION}
|
||||||
|
depends_on:
|
||||||
|
- elasticsearch
|
||||||
|
- db
|
||||||
|
- redis
|
||||||
|
- rabbitmq
|
||||||
|
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
container_name: elasticsearch_${NAME}
|
container_name: elasticsearch_${NAME}
|
||||||
image: elasticsearch:6.7.1
|
image: elasticsearch:6.7.1
|
||||||
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- ${ELASTIC_REST}:9200
|
- ${ELASTIC_REST}:9200
|
||||||
- ${ELASTIC_NODES}:9300
|
- ${ELASTIC_NODES}:9300
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://elasticsearch:9200"]
|
||||||
|
interval: 3s
|
||||||
|
timeout: 2s
|
||||||
|
retries: 20
|
||||||
|
|
||||||
# Database
|
|
||||||
db:
|
db:
|
||||||
container_name: postgresql_${NAME}
|
container_name: postgresql_${NAME}
|
||||||
build:
|
build:
|
||||||
@@ -24,11 +81,15 @@ services:
|
|||||||
POSTGRES_DB: ${DB_PWD}
|
POSTGRES_DB: ${DB_PWD}
|
||||||
volumes:
|
volumes:
|
||||||
- ./var/log/postgresql:/var/log/postgresql:rw
|
- ./var/log/postgresql:/var/log/postgresql:rw
|
||||||
- ./var/postgresql/data:/var/lib/postgresql/data:rw
|
- db-data:/var/lib/postgresql/data:rw
|
||||||
depends_on:
|
depends_on:
|
||||||
- elasticsearch
|
- elasticsearch
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://elasticsearch:9200/"]
|
test: [ "CMD", "pg_isready", "-q", "-d", "${DB_NAME}", "-U", "${DB_USER}" ]
|
||||||
interval: 3s
|
interval: 3s
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
retries: 20
|
retries: 20
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db-data:
|
||||||
|
redis-data:
|
||||||
21
docker/app/Dockerfile
Normal file
21
docker/app/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#### BUILD ####
|
||||||
|
FROM gradle:5.6.4-jdk11 AS build
|
||||||
|
COPY --chown=gradle:gradle . /home/gradle/src
|
||||||
|
|
||||||
|
WORKDIR /home/gradle/src
|
||||||
|
RUN gradle build -x test -x ktlintKotlinScriptCheck -x ktlintTestSourceSetCheck -x ktlintMainSourceSetCheck --no-daemon
|
||||||
|
|
||||||
|
#### RUN ####
|
||||||
|
FROM adoptopenjdk/openjdk11:jre-11.0.4_11-alpine
|
||||||
|
ENV APPLICATION_USER ktor
|
||||||
|
RUN adduser -D -g '' $APPLICATION_USER
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
|
RUN chown -R $APPLICATION_USER /app
|
||||||
|
|
||||||
|
USER $APPLICATION_USER
|
||||||
|
|
||||||
|
COPY --from=build /home/gradle/src/build/libs/dcproject-latest-all.jar /app/dcproject.jar
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
CMD ["java", "-server", "-XX:+UnlockExperimentalVMOptions", "-XX:InitialRAMFraction=2", "-XX:MinRAMFraction=2", "-XX:MaxRAMFraction=2", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "dcproject.jar"]
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
ktor_version=1.2.2
|
ktor_version=1.2.2
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
kotlin_version=1.3.40
|
kotlin_version=1.3.40
|
||||||
|
coroutinesVersion=1.3.3
|
||||||
logback_version=1.2.1
|
logback_version=1.2.1
|
||||||
postgresjson_version=0.1
|
|
||||||
koinVersion=2.0.1
|
koinVersion=2.0.1
|
||||||
|
jackson_version=2.9.9
|
||||||
|
cucumber_version=5.1.3
|
||||||
|
systemProp.sonar.host.url=http://localhost:9000
|
||||||
|
systemProp.sonar.login=1196e8015c20035f1aa91e881b95ce9d6e879c8a
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
22
gradlew
vendored
22
gradlew
vendored
@@ -1,5 +1,21 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2015 the original author or authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
##
|
||||||
## Gradle start up script for UN*X
|
## Gradle start up script for UN*X
|
||||||
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
|
|||||||
APP_BASE_NAME=`basename "$0"`
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS=""
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
@@ -109,8 +125,8 @@ if $darwin; then
|
|||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|||||||
18
gradlew.bat
vendored
18
gradlew.bat
vendored
@@ -1,3 +1,19 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%" == "" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
|
|||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS=
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|||||||
12
hook/version.sh
Normal file
12
hook/version.sh
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [[ $(git describe --tags --dirty) =~ ^V?([0-9][0-9.]*(-dirty)?)$ ]]; then
|
||||||
|
VERSION="${BASH_REMATCH[1]}"
|
||||||
|
elif [[ $(git describe --tags --dirty) =~ ^V?([0-9][0-9.]*)-([0-9]+)-g(.+(-dirty)?)$ ]]; then
|
||||||
|
VERSION="${BASH_REMATCH[1]}-${BASH_REMATCH[3]}"
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo $VERSION
|
||||||
314
src/main/kotlin/Application.kt
Normal file
314
src/main/kotlin/Application.kt
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
package fr.dcproject
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.util.DefaultIndenter
|
||||||
|
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
|
import com.fasterxml.jackson.databind.PropertyNamingStrategy
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
|
import com.fasterxml.jackson.datatype.joda.JodaModule
|
||||||
|
import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException
|
||||||
|
import fr.dcproject.Env.PROD
|
||||||
|
import fr.dcproject.elasticsearch.configElasticIndexes
|
||||||
|
import fr.dcproject.entity.*
|
||||||
|
import fr.dcproject.event.EventNotification
|
||||||
|
import fr.dcproject.event.EventSubscriber
|
||||||
|
import fr.dcproject.routes.*
|
||||||
|
import fr.dcproject.security.voter.*
|
||||||
|
import fr.ktorVoter.AuthorizationVoter
|
||||||
|
import fr.ktorVoter.ForbiddenException
|
||||||
|
import fr.postgresjson.migration.Migrations
|
||||||
|
import io.ktor.application.Application
|
||||||
|
import io.ktor.application.ApplicationCall
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.application.install
|
||||||
|
import io.ktor.auth.Authentication
|
||||||
|
import io.ktor.auth.authenticate
|
||||||
|
import io.ktor.auth.jwt.jwt
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.engine.jetty.Jetty
|
||||||
|
import io.ktor.features.*
|
||||||
|
import io.ktor.http.HttpHeaders
|
||||||
|
import io.ktor.http.HttpMethod
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.http.auth.HttpAuthHeader
|
||||||
|
import io.ktor.jackson.jackson
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.locations.Locations
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.routing.Routing
|
||||||
|
import io.ktor.util.KtorExperimentalAPI
|
||||||
|
import io.ktor.websocket.WebSockets
|
||||||
|
import org.eclipse.jetty.util.log.Slf4jLog
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
|
import org.koin.ktor.ext.Koin
|
||||||
|
import org.koin.ktor.ext.get
|
||||||
|
import org.slf4j.event.Level
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.CompletionException
|
||||||
|
import fr.dcproject.entity.Workgroup as WorkgroupEntity
|
||||||
|
import fr.dcproject.repository.Article as RepositoryArticle
|
||||||
|
import fr.dcproject.repository.Citizen as RepositoryCitizen
|
||||||
|
import fr.dcproject.repository.Constitution as RepositoryConstitution
|
||||||
|
import fr.dcproject.repository.OpinionChoice as OpinionChoiceRepository
|
||||||
|
import fr.dcproject.repository.User as UserRepository
|
||||||
|
import fr.dcproject.repository.Workgroup as WorkgroupRepository
|
||||||
|
|
||||||
|
fun main(args: Array<String>): Unit = io.ktor.server.jetty.EngineMain.main(args)
|
||||||
|
|
||||||
|
enum class Env { PROD, TEST, CUCUMBER }
|
||||||
|
|
||||||
|
@KtorExperimentalAPI
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
|
@Suppress("unused") // Referenced in application.conf
|
||||||
|
fun Application.module(env: Env = PROD) {
|
||||||
|
install(Koin) {
|
||||||
|
Slf4jLog()
|
||||||
|
modules(Module)
|
||||||
|
}
|
||||||
|
|
||||||
|
install(CallLogging) {
|
||||||
|
level = Level.INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
install(DataConversion) {
|
||||||
|
convert<UUID> {
|
||||||
|
decode { values, _ ->
|
||||||
|
values.singleOrNull()?.let { UUID.fromString(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
encode { value ->
|
||||||
|
when (value) {
|
||||||
|
null -> listOf()
|
||||||
|
is UUID -> listOf(value.toString())
|
||||||
|
else -> throw InternalError("Cannot convert $value as UUID")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: create generic convert for entityI
|
||||||
|
convert<Article> {
|
||||||
|
decode { values, _ ->
|
||||||
|
values.singleOrNull()?.let {
|
||||||
|
get<RepositoryArticle>().findById(UUID.fromString(it))
|
||||||
|
?: throw NotFoundException("Article $values not found")
|
||||||
|
} ?: throw NotFoundException("Article $values not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
convert<ArticleRef> {
|
||||||
|
decode { values, _ ->
|
||||||
|
values.singleOrNull()?.let {
|
||||||
|
ArticleRef(UUID.fromString(it))
|
||||||
|
} ?: throw NotFoundException("""UUID "$values" is not valid for Article""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convert<CommentRef> {
|
||||||
|
decode { values, _ ->
|
||||||
|
values.singleOrNull()?.let {
|
||||||
|
CommentRef(UUID.fromString(it))
|
||||||
|
} ?: throw NotFoundException("""UUID "$values" is not valid for Comment""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
convert<ConstitutionRef> {
|
||||||
|
decode { values, _ ->
|
||||||
|
values.singleOrNull()?.let {
|
||||||
|
ConstitutionRef(UUID.fromString(it))
|
||||||
|
} ?: throw NotFoundException("""UUID "$values" is not valid for Constitution""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convert<Constitution> {
|
||||||
|
decode { values, _ ->
|
||||||
|
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
||||||
|
?: throw InternalError("Cannot convert $values to UUID")
|
||||||
|
get<RepositoryConstitution>().findById(id) ?: throw NotFoundException("Constitution $values not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convert<Citizen> {
|
||||||
|
decode { values, _ ->
|
||||||
|
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
||||||
|
?: throw InternalError("Cannot convert $values to UUID")
|
||||||
|
get<RepositoryCitizen>().findById(id) ?: throw NotFoundException("Citizen $values not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convert<CitizenRef> {
|
||||||
|
decode { values, _ ->
|
||||||
|
values.singleOrNull()?.let {
|
||||||
|
CitizenRef(UUID.fromString(it))
|
||||||
|
} ?: throw NotFoundException("""UUID "$values" is not valid for Citizen""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convert<OpinionChoice> {
|
||||||
|
decode { values, _ ->
|
||||||
|
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
||||||
|
?: throw InternalError("Cannot convert $values to UUID")
|
||||||
|
get<OpinionChoiceRepository>().findOpinionChoiceById(id)
|
||||||
|
?: throw NotFoundException("OpinionChoice $values not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convert<WorkgroupRef> {
|
||||||
|
decode { values, _ ->
|
||||||
|
values.singleOrNull()?.let {
|
||||||
|
WorkgroupRef(UUID.fromString(it))
|
||||||
|
} ?: throw NotFoundException("""UUID "$values" is not valid for Workgroup""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convert<WorkgroupEntity> {
|
||||||
|
decode { values, _ ->
|
||||||
|
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
||||||
|
?: throw InternalError("Cannot convert $values to UUID")
|
||||||
|
get<WorkgroupRepository>().findById(id)
|
||||||
|
?: throw NotFoundException("Workgroup $values not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
install(Locations) {
|
||||||
|
}
|
||||||
|
|
||||||
|
install(AuthorizationVoter) {
|
||||||
|
voters = mutableListOf(
|
||||||
|
ArticleVoter(),
|
||||||
|
ConstitutionVoter(),
|
||||||
|
CitizenVoter(),
|
||||||
|
CommentVoter(),
|
||||||
|
VoteVoter(),
|
||||||
|
FollowVoter(),
|
||||||
|
OpinionVoter(),
|
||||||
|
OpinionChoiceVoter(),
|
||||||
|
WorkgroupVoter()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpClient(Jetty) {
|
||||||
|
engine {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configElasticIndexes(get())
|
||||||
|
|
||||||
|
install(WebSockets) {
|
||||||
|
pingPeriod = Duration.ofSeconds(60) // Disabled (null) by default
|
||||||
|
timeout = Duration.ofSeconds(15)
|
||||||
|
maxFrameSize = Long.MAX_VALUE // Disabled (max value). The connection will be closed if surpassed this length.
|
||||||
|
masking = false
|
||||||
|
}
|
||||||
|
|
||||||
|
install(EventSubscriber) {
|
||||||
|
EventNotification(this, get(), get(), get(), get(), get()).config()
|
||||||
|
}
|
||||||
|
|
||||||
|
install(Authentication) {
|
||||||
|
/**
|
||||||
|
* Setup the JWT authentication to be used in [Routing].
|
||||||
|
* If the token is valid, the corresponding [User] is fetched from the database.
|
||||||
|
* The [User] can then be accessed in each [ApplicationCall].
|
||||||
|
*/
|
||||||
|
jwt {
|
||||||
|
verifier(JwtConfig.verifier)
|
||||||
|
realm = "dc-project.fr"
|
||||||
|
validate {
|
||||||
|
it.payload.getClaim("id").asString()?.let { id ->
|
||||||
|
get<UserRepository>().findById(UUID.fromString(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt("url") {
|
||||||
|
verifier(JwtConfig.verifier)
|
||||||
|
realm = "dc-project.fr"
|
||||||
|
authHeader { call ->
|
||||||
|
call.request.queryParameters.get("token")?.let {
|
||||||
|
HttpAuthHeader.Single("Bearer", it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
validate {
|
||||||
|
it.payload.getClaim("id").asString()?.let { id ->
|
||||||
|
get<UserRepository>().findById(UUID.fromString(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
install(AutoHeadResponse)
|
||||||
|
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
jackson {
|
||||||
|
propertyNamingStrategy = PropertyNamingStrategy.SNAKE_CASE
|
||||||
|
|
||||||
|
registerModule(JodaModule())
|
||||||
|
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||||
|
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||||
|
configure(SerializationFeature.INDENT_OUTPUT, true)
|
||||||
|
setDefaultPrettyPrinter(DefaultPrettyPrinter().apply {
|
||||||
|
indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
|
||||||
|
indentObjectsWith(DefaultIndenter(" ", "\n"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
install(Routing) {
|
||||||
|
// trace { application.log.trace(it.buildText()) }
|
||||||
|
authenticate(optional = true) {
|
||||||
|
article(get(), get())
|
||||||
|
auth(get(), get(), get())
|
||||||
|
citizen(get(), get())
|
||||||
|
constitution(get())
|
||||||
|
followArticle(get())
|
||||||
|
followConstitution(get())
|
||||||
|
comment(get())
|
||||||
|
commentArticle(get())
|
||||||
|
commentConstitution(get())
|
||||||
|
voteArticle(get(), get(), get())
|
||||||
|
voteConstitution(get())
|
||||||
|
opinionArticle(get())
|
||||||
|
opinionChoice(get())
|
||||||
|
workgroup(get())
|
||||||
|
definition()
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticate("url") {
|
||||||
|
notificationArticle(get(), get(named("ws")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
install(StatusPages) {
|
||||||
|
// TODO move to postgresJson lib
|
||||||
|
exception<CompletionException> { e ->
|
||||||
|
val parent = e.cause?.cause
|
||||||
|
if (parent is GenericDatabaseException) {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, parent.errorMessage.message!!)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exception<NotFoundException> { e ->
|
||||||
|
call.respond(HttpStatusCode.NotFound, e.message!!)
|
||||||
|
}
|
||||||
|
exception<ForbiddenException> {
|
||||||
|
call.respond(HttpStatusCode.Forbidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
install(CORS) {
|
||||||
|
method(HttpMethod.Options)
|
||||||
|
method(HttpMethod.Put)
|
||||||
|
method(HttpMethod.Delete)
|
||||||
|
header(HttpHeaders.Authorization)
|
||||||
|
anyHost()
|
||||||
|
// host("localhost:4200", schemes = listOf("http", "https"))
|
||||||
|
allowCredentials = true
|
||||||
|
allowSameOrigin = true
|
||||||
|
maxAge = Duration.ofDays(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env == PROD) {
|
||||||
|
get<Migrations>().run()
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/kotlin/ApplicationContext.kt
Normal file
31
src/main/kotlin/ApplicationContext.kt
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package fr.dcproject
|
||||||
|
|
||||||
|
import fr.dcproject.entity.User
|
||||||
|
import fr.dcproject.entity.UserI
|
||||||
|
import fr.ktorVoter.ForbiddenException
|
||||||
|
import io.ktor.application.ApplicationCall
|
||||||
|
import io.ktor.auth.authentication
|
||||||
|
import io.ktor.util.AttributeKey
|
||||||
|
import io.ktor.util.pipeline.PipelineContext
|
||||||
|
import org.koin.core.context.GlobalContext
|
||||||
|
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||||
|
import fr.dcproject.repository.Citizen as CitizenRepository
|
||||||
|
|
||||||
|
private val citizenAttributeKey = AttributeKey<CitizenEntity>("CitizenContext")
|
||||||
|
|
||||||
|
val ApplicationCall.citizen: CitizenEntity
|
||||||
|
get() = attributes.computeIfAbsent(citizenAttributeKey) {
|
||||||
|
val user = authentication.principal<UserI>() ?: throw ForbiddenException()
|
||||||
|
GlobalContext.get().koin.get<CitizenRepository>().findByUser(user)
|
||||||
|
?: throw ForbiddenException("Citizen not found for this user id \"${user.id}\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
val ApplicationCall.citizenOrNull: CitizenEntity?
|
||||||
|
get() = authentication.principal<UserI>()?.let {
|
||||||
|
GlobalContext.get().koin.get<CitizenRepository>().findByUser(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val PipelineContext<Unit, ApplicationCall>.citizen get() = context.citizen
|
||||||
|
val PipelineContext<Unit, ApplicationCall>.citizenOrNull get() = context.citizenOrNull
|
||||||
|
|
||||||
|
val ApplicationCall.user get() = authentication.principal<User>()
|
||||||
62
src/main/kotlin/Configuration.kt
Normal file
62
src/main/kotlin/Configuration.kt
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package fr.dcproject
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT
|
||||||
|
import com.auth0.jwt.JWTVerifier
|
||||||
|
import com.auth0.jwt.algorithms.Algorithm
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import fr.dcproject.entity.UserI
|
||||||
|
import java.util.*
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
object Config {
|
||||||
|
private var config = ConfigFactory.load()
|
||||||
|
|
||||||
|
object Sql {
|
||||||
|
val migrationFiles: URI = this::class.java.getResource("/sql/migrations").toURI()
|
||||||
|
val functionFiles: URI = this::class.java.getResource("/sql/functions").toURI()
|
||||||
|
val fixtureFiles: URI = this::class.java.getResource("/sql/fixtures").toURI()
|
||||||
|
}
|
||||||
|
|
||||||
|
val envName: String = config.getString("app.envName")
|
||||||
|
val domain: String = config.getString("app.domain")
|
||||||
|
|
||||||
|
val host: String = config.getString("db.host")
|
||||||
|
var database: String = config.getString("db.database")
|
||||||
|
var username: String = config.getString("db.username")
|
||||||
|
var password: String = config.getString("db.password")
|
||||||
|
val port: Int = config.getInt("db.port")
|
||||||
|
val redis: String = config.getString("redis.connection")
|
||||||
|
val elasticsearch: String = config.getString("elasticsearch.connection")
|
||||||
|
val rabbitmq: String = config.getString("rabbitmq.connection")
|
||||||
|
val exchangeNotificationName = "notification"
|
||||||
|
val sendGridKey: String = config.getString("mail.sendGrid.key")
|
||||||
|
}
|
||||||
|
|
||||||
|
object JwtConfig {
|
||||||
|
private const val secret = "zAP5MBA4B4Ijz0MZaS48"
|
||||||
|
const val issuer = "dc-project.fr"
|
||||||
|
private const val validityInMs = 3_600_000 * 10 // 10 hours
|
||||||
|
|
||||||
|
// TODO change to RSA512
|
||||||
|
val algorithm = Algorithm.HMAC512(secret)
|
||||||
|
|
||||||
|
val verifier: JWTVerifier = JWT
|
||||||
|
.require(algorithm)
|
||||||
|
.withIssuer(issuer)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produce a token for this combination of User and Account
|
||||||
|
*/
|
||||||
|
fun makeToken(user: UserI): String = JWT.create()
|
||||||
|
.withSubject("Authentication")
|
||||||
|
.withIssuer(issuer)
|
||||||
|
.withClaim("id", user.id.toString())
|
||||||
|
.withExpiresAt(getExpiration())
|
||||||
|
.sign(algorithm)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the expiration Date based on current time + the given validity
|
||||||
|
*/
|
||||||
|
private fun getExpiration() = Date(System.currentTimeMillis() + validityInMs)
|
||||||
|
}
|
||||||
135
src/main/kotlin/Module.kt
Normal file
135
src/main/kotlin/Module.kt
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package fr.dcproject
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.databind.PropertyNamingStrategy
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||||
|
import com.fasterxml.jackson.datatype.joda.JodaModule
|
||||||
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
import com.rabbitmq.client.ConnectionFactory
|
||||||
|
import fr.dcproject.event.publisher.Publisher
|
||||||
|
import fr.dcproject.messages.Mailer
|
||||||
|
import fr.dcproject.messages.NotificationEmailSender
|
||||||
|
import fr.dcproject.messages.SsoManager
|
||||||
|
import fr.dcproject.views.ArticleViewManager
|
||||||
|
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 io.lettuce.core.api.async.RedisAsyncCommands
|
||||||
|
import org.apache.http.HttpHost
|
||||||
|
import org.elasticsearch.client.RestClient
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import fr.dcproject.repository.Article as ArticleRepository
|
||||||
|
import fr.dcproject.repository.Citizen as CitizenRepository
|
||||||
|
import fr.dcproject.repository.CommentArticle as CommentArticleRepository
|
||||||
|
import fr.dcproject.repository.CommentConstitution as CommentConstitutionRepository
|
||||||
|
import fr.dcproject.repository.CommentGeneric as CommentGenericRepository
|
||||||
|
import fr.dcproject.repository.Constitution as ConstitutionRepository
|
||||||
|
import fr.dcproject.repository.FollowArticle as FollowArticleRepository
|
||||||
|
import fr.dcproject.repository.FollowConstitution as FollowConstitutionRepository
|
||||||
|
import fr.dcproject.repository.OpinionArticle as OpinionArticleRepository
|
||||||
|
import fr.dcproject.repository.OpinionChoice as OpinionChoiceRepository
|
||||||
|
import fr.dcproject.repository.User as UserRepository
|
||||||
|
import fr.dcproject.repository.VoteArticle as VoteArticleRepository
|
||||||
|
import fr.dcproject.repository.VoteComment as VoteCommentRepository
|
||||||
|
import fr.dcproject.repository.VoteConstitution as VoteConstitutionRepository
|
||||||
|
import fr.dcproject.repository.Workgroup as WorkgroupRepository
|
||||||
|
|
||||||
|
@KtorExperimentalAPI
|
||||||
|
val Module = module {
|
||||||
|
|
||||||
|
single { Config }
|
||||||
|
|
||||||
|
// SQL connection
|
||||||
|
single {
|
||||||
|
Connection(
|
||||||
|
host = Config.host,
|
||||||
|
port = Config.port,
|
||||||
|
database = Config.database,
|
||||||
|
username = Config.username,
|
||||||
|
password = Config.password
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch Database migration
|
||||||
|
single { Migrations(get(), Config.Sql.migrationFiles, Config.Sql.functionFiles) }
|
||||||
|
|
||||||
|
// Redis client
|
||||||
|
single<RedisAsyncCommands<String, String>> {
|
||||||
|
RedisClient.create(Config.redis).connect()?.async() ?: error("Unable to connect to redis")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RabbitMQ
|
||||||
|
single<ConnectionFactory> {
|
||||||
|
ConnectionFactory().apply { setUri(Config.rabbitmq) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// JsonSerializer
|
||||||
|
single<ObjectMapper> {
|
||||||
|
jacksonObjectMapper().apply {
|
||||||
|
registerModule(SimpleModule())
|
||||||
|
propertyNamingStrategy = PropertyNamingStrategy.SNAKE_CASE
|
||||||
|
|
||||||
|
registerModule(JodaModule())
|
||||||
|
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||||
|
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client HTTP for WebSockets
|
||||||
|
single(named("ws")) {
|
||||||
|
HttpClient {
|
||||||
|
install(WebSockets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SQL Requester (postgresJson)
|
||||||
|
single {
|
||||||
|
Requester.RequesterFactory(
|
||||||
|
connection = get(),
|
||||||
|
functionsDirectory = Config.Sql.functionFiles
|
||||||
|
).createRequester()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repositories
|
||||||
|
single { UserRepository(get()) }
|
||||||
|
single { ArticleRepository(get()) }
|
||||||
|
single { CitizenRepository(get()) }
|
||||||
|
single { ConstitutionRepository(get()) }
|
||||||
|
single { FollowArticleRepository(get()) }
|
||||||
|
single { FollowConstitutionRepository(get()) }
|
||||||
|
single { CommentGenericRepository(get()) }
|
||||||
|
single { CommentArticleRepository(get()) }
|
||||||
|
single { CommentConstitutionRepository(get()) }
|
||||||
|
single { VoteArticleRepository(get()) }
|
||||||
|
single { VoteConstitutionRepository(get()) }
|
||||||
|
single { VoteCommentRepository(get()) }
|
||||||
|
single { OpinionChoiceRepository(get()) }
|
||||||
|
single { OpinionArticleRepository(get()) }
|
||||||
|
single { WorkgroupRepository(get()) }
|
||||||
|
|
||||||
|
// Elasticsearch Client
|
||||||
|
single<RestClient> {
|
||||||
|
RestClient.builder(
|
||||||
|
HttpHost.create(Config.elasticsearch)
|
||||||
|
).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
single { ArticleViewManager(get()) }
|
||||||
|
|
||||||
|
// Mailler
|
||||||
|
single { Mailer(Config.sendGridKey) }
|
||||||
|
|
||||||
|
// SSO Manager for connection
|
||||||
|
single { SsoManager(get<Mailer>(), Config.domain, get()) }
|
||||||
|
|
||||||
|
single { Publisher(get(), get()) }
|
||||||
|
|
||||||
|
single { NotificationEmailSender(get<Mailer>(), Config.domain, get(), get()) }
|
||||||
|
}
|
||||||
83
src/main/kotlin/elasticsearch/Config.kt
Normal file
83
src/main/kotlin/elasticsearch/Config.kt
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package fr.dcproject.elasticsearch
|
||||||
|
|
||||||
|
import org.elasticsearch.client.Request
|
||||||
|
import org.elasticsearch.client.RestClient
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
fun waitElasticsearchIsUp(client: RestClient) {
|
||||||
|
val logger: Logger = LoggerFactory.getLogger("fr.dcproject.elasticsearch")
|
||||||
|
val request = Request("GET", "/_cluster/health")
|
||||||
|
repeat(40) {
|
||||||
|
runCatching {
|
||||||
|
client.performRequest(request).statusLine.statusCode
|
||||||
|
}.onSuccess {
|
||||||
|
if (it == 200) {
|
||||||
|
logger.debug("Elasticsearch is Ready! Continue...")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
logger.debug("sleep 2s and retry...")
|
||||||
|
Thread.sleep(2000)
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
logger.debug("${it.message}, sleep 2s and retry...")
|
||||||
|
Thread.sleep(2000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error("Elasticsearch is not ready")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun configElasticIndexes(client: RestClient) {
|
||||||
|
waitElasticsearchIsUp(client)
|
||||||
|
|
||||||
|
/* Create index if not exist */
|
||||||
|
client.run {
|
||||||
|
if (performRequest(Request("HEAD", "/views?include_type_name=false")).statusLine.statusCode == 404) {
|
||||||
|
Request(
|
||||||
|
"PUT",
|
||||||
|
"/views?include_type_name=false"
|
||||||
|
).apply {
|
||||||
|
//language=JSON
|
||||||
|
setJsonEntity(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"settings": {
|
||||||
|
"number_of_shards": 5
|
||||||
|
},
|
||||||
|
"mappings": {
|
||||||
|
"properties": {
|
||||||
|
"logged": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"user_ref": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"version_id": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"ip": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"citizen_id": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"view_at": {
|
||||||
|
"type": "date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}.let {
|
||||||
|
performRequest(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/main/kotlin/entity/Article.kt
Normal file
85
src/main/kotlin/entity/Article.kt
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.postgresjson.entity.immutable.*
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAt
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAtImp
|
||||||
|
import fr.postgresjson.entity.mutable.EntityVersioning
|
||||||
|
import fr.postgresjson.entity.mutable.UuidEntityVersioning
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class Article(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
title: String,
|
||||||
|
override var anonymous: Boolean = true,
|
||||||
|
override var content: String,
|
||||||
|
override var description: String,
|
||||||
|
override var tags: List<String> = emptyList(),
|
||||||
|
draft: Boolean = false,
|
||||||
|
override var lastVersion: Boolean = false,
|
||||||
|
override val createdBy: CitizenBasic
|
||||||
|
) : ArticleFull,
|
||||||
|
ArticleAuthI<CitizenBasicI>,
|
||||||
|
ArticleSimple(id, title, createdBy, draft),
|
||||||
|
Viewable by ViewableImp() {
|
||||||
|
init {
|
||||||
|
tags = tags.distinct()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class ArticleSimple(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
override var title: String,
|
||||||
|
override val createdBy: CitizenBasic,
|
||||||
|
override var draft: Boolean = false
|
||||||
|
) : ArticleSimpleI,
|
||||||
|
ArticleAuthI<CitizenBasicI>,
|
||||||
|
ArticleRefVersioning(id),
|
||||||
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
|
EntityCreatedBy<CitizenBasicI> by EntityCreatedByImp(createdBy),
|
||||||
|
EntityDeletedAt by EntityDeletedAtImp(),
|
||||||
|
Votable by VotableImp(),
|
||||||
|
Opinionable by OpinionableImp()
|
||||||
|
|
||||||
|
open class ArticleRefVersioning(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
versionNumber: Int? = null,
|
||||||
|
versionId: UUID = UUID.randomUUID()
|
||||||
|
) : ArticleRef(id),
|
||||||
|
EntityVersioning<UUID, Int> by UuidEntityVersioning(versionNumber, versionId)
|
||||||
|
|
||||||
|
open class ArticleRef(
|
||||||
|
id: UUID = UUID.randomUUID()
|
||||||
|
) : ArticleI, TargetRef(id)
|
||||||
|
|
||||||
|
interface ArticleI : UuidEntityI, TargetI
|
||||||
|
|
||||||
|
interface ArticleSimpleI :
|
||||||
|
ArticleI,
|
||||||
|
EntityVersioning<UUID, Int>,
|
||||||
|
EntityCreatedBy<CitizenBasicI>,
|
||||||
|
EntityCreatedAt,
|
||||||
|
EntityDeletedAt,
|
||||||
|
Votable {
|
||||||
|
var title: String
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArticleBasicI :
|
||||||
|
ArticleSimpleI {
|
||||||
|
var anonymous: Boolean
|
||||||
|
var content: String
|
||||||
|
var description: String
|
||||||
|
var tags: List<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArticleFull :
|
||||||
|
ArticleBasicI {
|
||||||
|
var draft: Boolean
|
||||||
|
var lastVersion: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArticleAuthI<U : CitizenWithUserI> :
|
||||||
|
ArticleI,
|
||||||
|
EntityCreatedBy<U>,
|
||||||
|
EntityDeletedAt {
|
||||||
|
var draft: Boolean
|
||||||
|
}
|
||||||
85
src/main/kotlin/entity/Citizen.kt
Normal file
85
src/main/kotlin/entity/Citizen.kt
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.dcproject.entity.CitizenI.Name
|
||||||
|
import fr.postgresjson.entity.immutable.EntityCreatedAt
|
||||||
|
import fr.postgresjson.entity.immutable.EntityCreatedAtImp
|
||||||
|
import fr.postgresjson.entity.immutable.UuidEntity
|
||||||
|
import fr.postgresjson.entity.immutable.UuidEntityI
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAt
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAtImp
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class Citizen(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
name: Name,
|
||||||
|
email: String,
|
||||||
|
birthday: DateTime,
|
||||||
|
voteAnonymous: Boolean = true,
|
||||||
|
followAnonymous: Boolean = true,
|
||||||
|
override val user: User
|
||||||
|
) : CitizenFull,
|
||||||
|
CitizenBasic(id, name, email, birthday, voteAnonymous, followAnonymous, user),
|
||||||
|
EntityCreatedAt by EntityCreatedAtImp() {
|
||||||
|
var workgroups: List<WorkgroupAndRoles> = emptyList()
|
||||||
|
|
||||||
|
class WorkgroupAndRoles(
|
||||||
|
val roles: List<String>,
|
||||||
|
val workgroup: WorkgroupSimple<CitizenRef>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class CitizenBasic(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
name: Name,
|
||||||
|
override var email: String,
|
||||||
|
override var birthday: DateTime,
|
||||||
|
override var voteAnonymous: Boolean = true,
|
||||||
|
override var followAnonymous: Boolean = true,
|
||||||
|
override val user: User
|
||||||
|
) : CitizenBasicI,
|
||||||
|
CitizenSimple(id, name, user)
|
||||||
|
|
||||||
|
open class CitizenSimple(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
var name: Name,
|
||||||
|
user: UserRef
|
||||||
|
) : CitizenRefWithUser(id, user)
|
||||||
|
|
||||||
|
open class CitizenRefWithUser(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
override val user: UserRef
|
||||||
|
) : CitizenWithUserI,
|
||||||
|
CitizenRef(id),
|
||||||
|
EntityDeletedAt by EntityDeletedAtImp()
|
||||||
|
|
||||||
|
open class CitizenRef(
|
||||||
|
id: UUID = UUID.randomUUID()
|
||||||
|
) : UuidEntity(id),
|
||||||
|
CitizenI
|
||||||
|
|
||||||
|
interface CitizenI : UuidEntityI {
|
||||||
|
data class Name(
|
||||||
|
var firstName: String,
|
||||||
|
var lastName: String,
|
||||||
|
var civility: String? = null
|
||||||
|
) {
|
||||||
|
fun getFullName(): String = "${civility ?: ""} $firstName $lastName".trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CitizenBasicI : CitizenWithUserI, EntityDeletedAt {
|
||||||
|
var name: Name
|
||||||
|
var email: String
|
||||||
|
var birthday: DateTime
|
||||||
|
var voteAnonymous: Boolean
|
||||||
|
var followAnonymous: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CitizenFull : CitizenBasicI {
|
||||||
|
override val user: User
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CitizenWithUserI : CitizenI {
|
||||||
|
val user: UserI
|
||||||
|
}
|
||||||
39
src/main/kotlin/entity/Comment.kt
Normal file
39
src/main/kotlin/entity/Comment.kt
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.postgresjson.entity.immutable.*
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAt
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAtImp
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
open class Comment<T : TargetI>(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
override val createdBy: CitizenBasic,
|
||||||
|
override var target: T,
|
||||||
|
var content: String,
|
||||||
|
val responses: List<Comment<T>>? = null,
|
||||||
|
var parent: Comment<T>? = null,
|
||||||
|
val parentsIds: List<UUID>? = null,
|
||||||
|
val childrenCount: Int? = null
|
||||||
|
) : ExtraI<T, CitizenBasicI>,
|
||||||
|
CommentRef(id),
|
||||||
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
|
EntityCreatedBy<CitizenBasicI> by EntityCreatedByImp(createdBy),
|
||||||
|
EntityUpdatedAt by EntityUpdatedAtImp(),
|
||||||
|
EntityDeletedAt by EntityDeletedAtImp(),
|
||||||
|
Votable by VotableImp(),
|
||||||
|
TargetI {
|
||||||
|
constructor(
|
||||||
|
createdBy: CitizenBasic,
|
||||||
|
parent: Comment<T>,
|
||||||
|
content: String
|
||||||
|
) : this(
|
||||||
|
createdBy = createdBy,
|
||||||
|
parent = parent,
|
||||||
|
target = parent.target,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class CommentRef(id: UUID = UUID.randomUUID()) : CommentS(id)
|
||||||
|
|
||||||
|
sealed class CommentS(id: UUID) : TargetRef(id)
|
||||||
69
src/main/kotlin/entity/Constitution.kt
Normal file
69
src/main/kotlin/entity/Constitution.kt
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.postgresjson.entity.immutable.*
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAt
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAtImp
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class Constitution(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
title: String,
|
||||||
|
anonymous: Boolean = true,
|
||||||
|
titles: MutableList<TitleSimple<ArticleSimple>> = mutableListOf(),
|
||||||
|
draft: Boolean = false,
|
||||||
|
lastVersion: Boolean = false,
|
||||||
|
override val createdBy: CitizenSimple
|
||||||
|
) : ConstitutionSimple<CitizenSimple, ConstitutionSimple.TitleSimple<ArticleSimple>>(
|
||||||
|
id,
|
||||||
|
title = title,
|
||||||
|
anonymous = anonymous,
|
||||||
|
titles = titles,
|
||||||
|
draft = draft,
|
||||||
|
lastVersion = lastVersion,
|
||||||
|
createdBy = createdBy
|
||||||
|
) {
|
||||||
|
|
||||||
|
class Title(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
name: String,
|
||||||
|
rank: Int? = null,
|
||||||
|
override val articles: MutableList<ArticleSimple> = mutableListOf()
|
||||||
|
) : ConstitutionSimple.TitleSimple<ArticleSimple>(id, name, rank)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class ConstitutionSimple<Cr : CitizenRefWithUser, T : ConstitutionSimple.TitleSimple<*>>(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
var title: String,
|
||||||
|
var anonymous: Boolean = true,
|
||||||
|
open var titles: MutableList<T> = mutableListOf(),
|
||||||
|
var draft: Boolean = false,
|
||||||
|
var lastVersion: Boolean = false,
|
||||||
|
override val createdBy: Cr,
|
||||||
|
versionId: UUID = UUID.randomUUID()
|
||||||
|
) : ConstitutionRef(id),
|
||||||
|
EntityVersioning<UUID, Int?> by UuidEntityVersioning(versionId = versionId),
|
||||||
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
|
EntityCreatedBy<Cr> by EntityCreatedByImp(createdBy),
|
||||||
|
EntityDeletedAt by EntityDeletedAtImp() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
titles.forEachIndexed { index, title ->
|
||||||
|
title.rank = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class TitleSimple<A : ArticleI>(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
var name: String,
|
||||||
|
var rank: Int? = null,
|
||||||
|
open val articles: MutableList<A> = mutableListOf()
|
||||||
|
) : TitleRef(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class ConstitutionRef(id: UUID = UUID.randomUUID()) : ConstitutionS(id) {
|
||||||
|
open class TitleRef(
|
||||||
|
id: UUID = UUID.randomUUID()
|
||||||
|
) : UuidEntity(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class ConstitutionS(id: UUID = UUID.randomUUID()) : TargetRef(id), TargetI
|
||||||
58
src/main/kotlin/entity/Extra.kt
Normal file
58
src/main/kotlin/entity/Extra.kt
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.postgresjson.entity.immutable.EntityCreatedAt
|
||||||
|
import fr.postgresjson.entity.immutable.EntityCreatedBy
|
||||||
|
import fr.postgresjson.entity.immutable.UuidEntity
|
||||||
|
import fr.postgresjson.entity.immutable.UuidEntityI
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.full.isSubclassOf
|
||||||
|
|
||||||
|
interface ExtraI<T : TargetI, C : CitizenI> :
|
||||||
|
UuidEntityI,
|
||||||
|
EntityCreatedAt,
|
||||||
|
EntityCreatedBy<C> {
|
||||||
|
val target: T
|
||||||
|
}
|
||||||
|
|
||||||
|
open class TargetRef(id: UUID = UUID.randomUUID(), reference: String = "") : TargetI, UuidEntity(id) {
|
||||||
|
|
||||||
|
final override val reference: String
|
||||||
|
get() = if (field != "") field else TargetI.getReference(this)
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.reference = reference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TargetI : UuidEntityI {
|
||||||
|
enum class TargetName(val targetReference: String) {
|
||||||
|
Article("article"),
|
||||||
|
Constitution("constitution"),
|
||||||
|
Comment("comment"),
|
||||||
|
Opinion("opinion")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun <T : TargetI> getReference(t: KClass<T>): String {
|
||||||
|
return when {
|
||||||
|
t.isSubclassOf(ArticleRef::class) -> TargetName.Article.targetReference
|
||||||
|
t.isSubclassOf(ConstitutionRef::class) -> TargetName.Constitution.targetReference
|
||||||
|
t.isSubclassOf(CommentRef::class) -> TargetName.Comment.targetReference
|
||||||
|
t.isSubclassOf(Opinion::class) -> TargetName.Opinion.targetReference
|
||||||
|
else -> throw error("target not implemented: ${t.qualifiedName} \nImplement it or return 'reference' from SQL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getReference(t: TargetI): String {
|
||||||
|
val ref = this.getReference(t::class)
|
||||||
|
return if (t is ExtraI<*, *>) {
|
||||||
|
"${ref}_on_${t.target.reference}"
|
||||||
|
} else {
|
||||||
|
ref
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val reference: String
|
||||||
|
}
|
||||||
20
src/main/kotlin/entity/Follow.kt
Normal file
20
src/main/kotlin/entity/Follow.kt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.postgresjson.entity.immutable.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class Follow<T : TargetI>(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
override val createdBy: CitizenBasic,
|
||||||
|
override var target: T
|
||||||
|
) : ExtraI<T, CitizenBasicI>,
|
||||||
|
FollowSimple<T, CitizenBasicI>(id, createdBy, target)
|
||||||
|
|
||||||
|
open class FollowSimple<T : TargetI, C : CitizenI>(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
override val createdBy: C,
|
||||||
|
override var target: T
|
||||||
|
) : ExtraI<T, C>,
|
||||||
|
UuidEntity(id),
|
||||||
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
|
EntityCreatedBy<C> by EntityCreatedByImp(createdBy)
|
||||||
27
src/main/kotlin/entity/Opinion.kt
Normal file
27
src/main/kotlin/entity/Opinion.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.postgresjson.entity.immutable.EntityCreatedAt
|
||||||
|
import fr.postgresjson.entity.immutable.EntityCreatedAtImp
|
||||||
|
import fr.postgresjson.entity.immutable.EntityCreatedBy
|
||||||
|
import fr.postgresjson.entity.immutable.EntityCreatedByImp
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
open class Opinion<T : TargetI>(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
override val createdBy: CitizenBasic,
|
||||||
|
override val target: T,
|
||||||
|
val choice: OpinionChoice
|
||||||
|
) : ExtraI<T, CitizenBasicI>,
|
||||||
|
TargetRef(id),
|
||||||
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
|
EntityCreatedBy<CitizenBasicI> by EntityCreatedByImp(createdBy) {
|
||||||
|
|
||||||
|
fun getName(): String = choice.name
|
||||||
|
}
|
||||||
|
|
||||||
|
class OpinionArticle(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
createdBy: CitizenBasic,
|
||||||
|
target: ArticleRef,
|
||||||
|
choice: OpinionChoice
|
||||||
|
) : Opinion<ArticleRef>(id, createdBy, target, choice)
|
||||||
20
src/main/kotlin/entity/OpinionChoice.kt
Normal file
20
src/main/kotlin/entity/OpinionChoice.kt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.postgresjson.entity.immutable.EntityCreatedAt
|
||||||
|
import fr.postgresjson.entity.immutable.EntityCreatedAtImp
|
||||||
|
import fr.postgresjson.entity.immutable.UuidEntity
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAt
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAtImp
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class OpinionChoice(
|
||||||
|
id: UUID? = null,
|
||||||
|
val name: String,
|
||||||
|
val target: List<String>?
|
||||||
|
) : OpinionChoiceRef(id),
|
||||||
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
|
EntityDeletedAt by EntityDeletedAtImp()
|
||||||
|
|
||||||
|
open class OpinionChoiceRef(
|
||||||
|
id: UUID?
|
||||||
|
) : UuidEntity(id ?: UUID.randomUUID())
|
||||||
15
src/main/kotlin/entity/Opinionable.kt
Normal file
15
src/main/kotlin/entity/Opinionable.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.postgresjson.entity.EntityI
|
||||||
|
|
||||||
|
class OpinionAggregation(
|
||||||
|
private val underlying: MutableMap<String, Any> = mutableMapOf()
|
||||||
|
) : MutableMap<String, Any> by underlying, EntityI
|
||||||
|
|
||||||
|
interface Opinionable {
|
||||||
|
var opinions: MutableMap<String, Int>
|
||||||
|
}
|
||||||
|
|
||||||
|
class OpinionableImp : Opinionable {
|
||||||
|
override var opinions: MutableMap<String, Int> = mutableMapOf()
|
||||||
|
}
|
||||||
41
src/main/kotlin/entity/User.kt
Normal file
41
src/main/kotlin/entity/User.kt
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.dcproject.entity.UserI.Roles
|
||||||
|
import fr.postgresjson.entity.immutable.*
|
||||||
|
import io.ktor.auth.Principal
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class User(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
username: String,
|
||||||
|
blockedAt: DateTime? = null,
|
||||||
|
override var plainPassword: String? = null,
|
||||||
|
override var roles: List<Roles> = emptyList()
|
||||||
|
) : UserFull, UserBasic(id, username, blockedAt),
|
||||||
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
|
EntityUpdatedAt by EntityUpdatedAtImp()
|
||||||
|
|
||||||
|
open class UserBasic(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
override var username: String,
|
||||||
|
override var blockedAt: DateTime? = null
|
||||||
|
) : UserBasicI, UserRef(id)
|
||||||
|
|
||||||
|
open class UserRef(
|
||||||
|
id: UUID = UUID.randomUUID()
|
||||||
|
) : UserI, UuidEntity(id)
|
||||||
|
|
||||||
|
interface UserI : UuidEntityI, Principal {
|
||||||
|
enum class Roles { ROLE_USER, ROLE_ADMIN }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserBasicI : UserI {
|
||||||
|
var username: String
|
||||||
|
var blockedAt: DateTime?
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserFull : UserBasicI, EntityCreatedAt, EntityUpdatedAt {
|
||||||
|
var plainPassword: String?
|
||||||
|
var roles: List<Roles>
|
||||||
|
}
|
||||||
13
src/main/kotlin/entity/ViewAggregation.kt
Normal file
13
src/main/kotlin/entity/ViewAggregation.kt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.postgresjson.entity.EntityI
|
||||||
|
import fr.postgresjson.entity.immutable.EntityUpdatedAt
|
||||||
|
import fr.postgresjson.entity.immutable.EntityUpdatedAtImp
|
||||||
|
|
||||||
|
open class ViewAggregation(
|
||||||
|
val total: Int,
|
||||||
|
val unique: Int
|
||||||
|
) : EntityI,
|
||||||
|
EntityUpdatedAt by EntityUpdatedAtImp() {
|
||||||
|
constructor() : this(0, 0)
|
||||||
|
}
|
||||||
9
src/main/kotlin/entity/Viewable.kt
Normal file
9
src/main/kotlin/entity/Viewable.kt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
interface Viewable {
|
||||||
|
var views: ViewAggregation
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewableImp : Viewable {
|
||||||
|
override var views: ViewAggregation = ViewAggregation()
|
||||||
|
}
|
||||||
9
src/main/kotlin/entity/Votable.kt
Normal file
9
src/main/kotlin/entity/Votable.kt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
interface Votable {
|
||||||
|
var votes: VoteAggregation
|
||||||
|
}
|
||||||
|
|
||||||
|
class VotableImp : Votable {
|
||||||
|
override var votes: VoteAggregation = VoteAggregation()
|
||||||
|
}
|
||||||
22
src/main/kotlin/entity/Vote.kt
Normal file
22
src/main/kotlin/entity/Vote.kt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.postgresjson.entity.immutable.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
open class Vote<T : TargetI>(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
override val createdBy: CitizenBasic,
|
||||||
|
override var target: T,
|
||||||
|
var note: Int,
|
||||||
|
var anonymous: Boolean = true
|
||||||
|
) : ExtraI<T, CitizenBasicI>,
|
||||||
|
UuidEntity(id),
|
||||||
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
|
EntityCreatedBy<CitizenBasicI> by EntityCreatedByImp(createdBy),
|
||||||
|
EntityUpdatedAt by EntityUpdatedAtImp() {
|
||||||
|
init {
|
||||||
|
if (note > 1 && note < -1) {
|
||||||
|
error("note must be 1, 0 or -1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/kotlin/entity/VoteAggregation.kt
Normal file
16
src/main/kotlin/entity/VoteAggregation.kt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.postgresjson.entity.EntityI
|
||||||
|
import fr.postgresjson.entity.mutable.EntityUpdatedAt
|
||||||
|
import fr.postgresjson.entity.mutable.EntityUpdatedAtImp
|
||||||
|
|
||||||
|
open class VoteAggregation(
|
||||||
|
val up: Int,
|
||||||
|
val neutral: Int,
|
||||||
|
val down: Int,
|
||||||
|
val total: Int,
|
||||||
|
val score: Int
|
||||||
|
) : EntityI,
|
||||||
|
EntityUpdatedAt by EntityUpdatedAtImp() {
|
||||||
|
constructor() : this(0, 0, 0, 0, 0)
|
||||||
|
}
|
||||||
95
src/main/kotlin/entity/Workgroup.kt
Normal file
95
src/main/kotlin/entity/Workgroup.kt
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package fr.dcproject.entity
|
||||||
|
|
||||||
|
import fr.dcproject.entity.WorkgroupWithMembersI.Member
|
||||||
|
import fr.dcproject.entity.WorkgroupWithMembersI.Member.Role
|
||||||
|
import fr.postgresjson.entity.EntityI
|
||||||
|
import fr.postgresjson.entity.immutable.*
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAt
|
||||||
|
import fr.postgresjson.entity.mutable.EntityDeletedAtImp
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class Workgroup(
|
||||||
|
id: UUID? = null,
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
logo: String? = null,
|
||||||
|
anonymous: Boolean = true,
|
||||||
|
createdBy: CitizenBasic,
|
||||||
|
override var members: List<Member<CitizenBasic>> = emptyList()
|
||||||
|
) : WorkgroupWithAuthI<CitizenBasic>,
|
||||||
|
WorkgroupSimple<CitizenBasic>(
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
logo,
|
||||||
|
anonymous,
|
||||||
|
createdBy
|
||||||
|
),
|
||||||
|
EntityCreatedAt by EntityCreatedAtImp(),
|
||||||
|
EntityUpdatedAt by EntityUpdatedAtImp()
|
||||||
|
|
||||||
|
open class WorkgroupSimple<Z : CitizenRef>(
|
||||||
|
id: UUID? = null,
|
||||||
|
var name: String,
|
||||||
|
var description: String,
|
||||||
|
var logo: String? = null,
|
||||||
|
var anonymous: Boolean = true,
|
||||||
|
createdBy: Z
|
||||||
|
) : WorkgroupRef(id),
|
||||||
|
EntityCreatedBy<Z> by EntityCreatedByImp(createdBy),
|
||||||
|
EntityDeletedAt by EntityDeletedAtImp()
|
||||||
|
|
||||||
|
open class WorkgroupRef(
|
||||||
|
id: UUID? = null
|
||||||
|
) : UuidEntity(id ?: UUID.randomUUID()), WorkgroupI
|
||||||
|
|
||||||
|
interface WorkgroupWithAuthI<Z : CitizenWithUserI> : WorkgroupWithMembersI<Z>, EntityCreatedBy<Z>, EntityDeletedAt {
|
||||||
|
val anonymous: Boolean
|
||||||
|
|
||||||
|
fun isMember(user: UserI): Boolean = members.isMember(user)
|
||||||
|
fun isMember(citizen: CitizenWithUserI): Boolean = members.isMember(citizen)
|
||||||
|
|
||||||
|
fun hasRole(expectedRole: Role, user: UserI): Boolean = members.hasRole(expectedRole, user)
|
||||||
|
fun hasRole(expectedRole: Role, citizen: CitizenI): Boolean = members.hasRole(expectedRole, citizen)
|
||||||
|
|
||||||
|
fun getRoles(user: UserI): List<Role> = members.getRoles(user)
|
||||||
|
fun getRoles(citizen: CitizenI): List<Role> = members.getRoles(citizen)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorkgroupWithMembersI<Z : CitizenI> : WorkgroupI {
|
||||||
|
var members: List<Member<Z>>
|
||||||
|
|
||||||
|
class Member<C : CitizenI> (
|
||||||
|
val citizen: C,
|
||||||
|
val roles: List<Role> = emptyList()
|
||||||
|
) : EntityI {
|
||||||
|
enum class Role {
|
||||||
|
MASTER,
|
||||||
|
MANAGER,
|
||||||
|
EDITOR,
|
||||||
|
REPORTER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun List<CitizenI>.hasCitizen(citizen: CitizenI): Boolean = this.map { it.id }.contains(citizen.id)
|
||||||
|
|
||||||
|
fun <Z : CitizenWithUserI> List<Member<Z>>.isMember(user: UserI): Boolean =
|
||||||
|
map { it.citizen.user.id }.contains(user.id)
|
||||||
|
|
||||||
|
fun <Z : CitizenI> List<Member<Z>>.isMember(citizen: CitizenI): Boolean =
|
||||||
|
map { it.citizen.id }.contains(citizen.id)
|
||||||
|
|
||||||
|
fun <Z : CitizenI> List<Member<Z>>.hasRole(expectedRole: Role, citizen: CitizenI): Boolean =
|
||||||
|
any { member -> member.citizen.id == citizen.id && member.roles.any { it == expectedRole } }
|
||||||
|
|
||||||
|
fun <Z : CitizenWithUserI> List<Member<Z>>.hasRole(expectedRole: Role, user: UserI): Boolean =
|
||||||
|
any { member -> member.citizen.user.id == user.id && member.roles.any { it == expectedRole } }
|
||||||
|
|
||||||
|
fun <Z : CitizenWithUserI> List<Member<Z>>.getRoles(user: UserI): List<Role> =
|
||||||
|
firstOrNull { it.citizen.user.id == user.id }?.roles ?: emptyList()
|
||||||
|
|
||||||
|
fun <Z : CitizenWithUserI> List<Member<Z>>.getRoles(citizen: CitizenI): List<Role> =
|
||||||
|
firstOrNull { it.citizen.id == citizen.id }?.roles ?: emptyList()
|
||||||
|
|
||||||
|
interface WorkgroupI : UuidEntityI
|
||||||
133
src/main/kotlin/event/EventNotification.kt
Normal file
133
src/main/kotlin/event/EventNotification.kt
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package fr.dcproject.event
|
||||||
|
|
||||||
|
import com.rabbitmq.client.*
|
||||||
|
import com.rabbitmq.client.BuiltinExchangeType.DIRECT
|
||||||
|
import fr.dcproject.Config
|
||||||
|
import fr.dcproject.entity.Article
|
||||||
|
import fr.dcproject.entity.CitizenRef
|
||||||
|
import fr.dcproject.entity.FollowSimple
|
||||||
|
import fr.dcproject.entity.TargetRef
|
||||||
|
import fr.dcproject.event.publisher.Publisher
|
||||||
|
import fr.dcproject.messages.NotificationEmailSender
|
||||||
|
import fr.dcproject.repository.Follow
|
||||||
|
import fr.postgresjson.serializer.deserialize
|
||||||
|
import io.ktor.application.ApplicationCall
|
||||||
|
import io.ktor.application.EventDefinition
|
||||||
|
import io.ktor.application.application
|
||||||
|
import io.ktor.util.pipeline.PipelineContext
|
||||||
|
import io.lettuce.core.api.async.RedisAsyncCommands
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.io.errors.IOException
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import fr.dcproject.repository.FollowArticle as FollowArticleRepository
|
||||||
|
|
||||||
|
class ArticleUpdate(
|
||||||
|
target: Article
|
||||||
|
) : EntityEvent(target, "article", "update") {
|
||||||
|
companion object {
|
||||||
|
val event = EventDefinition<ArticleUpdate>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : Event> PipelineContext<Unit, ApplicationCall>.raiseEvent(definition: EventDefinition<T>, value: T) =
|
||||||
|
application.environment.monitor.raise(definition, value)
|
||||||
|
|
||||||
|
class EventNotification(
|
||||||
|
private val config: EventSubscriber.Configuration,
|
||||||
|
private val rabbitFactory: ConnectionFactory,
|
||||||
|
private val redis: RedisAsyncCommands<String, String>,
|
||||||
|
private val followRepo: FollowArticleRepository,
|
||||||
|
private val publisher: Publisher,
|
||||||
|
private val notificationEmailSender: NotificationEmailSender
|
||||||
|
) {
|
||||||
|
private val logger: Logger = LoggerFactory.getLogger(Publisher::class.qualifiedName)
|
||||||
|
|
||||||
|
fun config() {
|
||||||
|
/* Config Rabbit */
|
||||||
|
val exchangeName = Config.exchangeNotificationName
|
||||||
|
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, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Declare publisher on event */
|
||||||
|
config.subscribe(ArticleUpdate.event) {
|
||||||
|
publisher.publish(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Launch Consumer */
|
||||||
|
GlobalScope.launch {
|
||||||
|
val rabbitChannel = rabbitFactory.newConnection().createChannel()
|
||||||
|
|
||||||
|
val consumerPush: Consumer = object : DefaultConsumer(rabbitChannel) {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun handleDelivery(
|
||||||
|
consumerTag: String,
|
||||||
|
envelope: Envelope,
|
||||||
|
properties: AMQP.BasicProperties,
|
||||||
|
body: ByteArray
|
||||||
|
) = runBlocking {
|
||||||
|
decodeEvent(body) {
|
||||||
|
redis.zadd(
|
||||||
|
"notification:${follow.createdBy.id}",
|
||||||
|
event.id,
|
||||||
|
rawEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
rabbitChannel.basicAck(envelope.deliveryTag, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val consumerEmail: Consumer = object : DefaultConsumer(rabbitChannel) {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun handleDelivery(
|
||||||
|
consumerTag: String,
|
||||||
|
envelope: Envelope,
|
||||||
|
properties: AMQP.BasicProperties,
|
||||||
|
body: ByteArray
|
||||||
|
) {
|
||||||
|
runBlocking {
|
||||||
|
decodeEvent(body) {
|
||||||
|
logger.debug("EmailSend to: ${follow.createdBy.id}")
|
||||||
|
notificationEmailSender.sendEmail(follow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rabbitChannel.basicAck(envelope.deliveryTag, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rabbitChannel.basicConsume("push", false, consumerPush) // The front consume the redis via Websocket
|
||||||
|
rabbitChannel.basicConsume("email", false, consumerEmail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun decodeEvent(body: ByteArray, action: suspend Msg.() -> Unit) {
|
||||||
|
val rawEvent = body.toString(Charsets.UTF_8)
|
||||||
|
val event = rawEvent.deserialize<EntityEvent>() ?: error("Unable to unserialise event message from rabbit")
|
||||||
|
val repo = when (event.type) {
|
||||||
|
"article" -> followRepo
|
||||||
|
else -> error("event '${event.type}' not implemented")
|
||||||
|
} as Follow<*, *>
|
||||||
|
|
||||||
|
repo
|
||||||
|
.findFollowsByTarget(event.target)
|
||||||
|
.collect {
|
||||||
|
Msg(event, rawEvent, it).action()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Msg(
|
||||||
|
val event: EntityEvent,
|
||||||
|
val rawEvent: String,
|
||||||
|
val follow: FollowSimple<out TargetRef, CitizenRef>
|
||||||
|
)
|
||||||
|
}
|
||||||
54
src/main/kotlin/event/EventSubscriber.kt
Normal file
54
src/main/kotlin/event/EventSubscriber.kt
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package fr.dcproject.event
|
||||||
|
|
||||||
|
import fr.postgresjson.entity.Serializable
|
||||||
|
import fr.postgresjson.entity.immutable.UuidEntity
|
||||||
|
import io.ktor.application.*
|
||||||
|
import io.ktor.util.AttributeKey
|
||||||
|
import io.ktor.util.KtorExperimentalAPI
|
||||||
|
import kotlinx.coroutines.DisposableHandle
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import kotlin.random.Random.Default.nextInt
|
||||||
|
|
||||||
|
open class Event(
|
||||||
|
val type: String,
|
||||||
|
val createdAt: DateTime = DateTime.now()
|
||||||
|
) : Serializable {
|
||||||
|
val id: Double = randId(createdAt.millis)
|
||||||
|
|
||||||
|
private fun randId(time: Long): Double {
|
||||||
|
return (time.toString() + nextInt(1000, 9999).toString()).toDouble()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class EntityEvent(
|
||||||
|
val target: UuidEntity,
|
||||||
|
type: String,
|
||||||
|
val action: String
|
||||||
|
) : Event(type)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installation Class
|
||||||
|
*/
|
||||||
|
class EventSubscriber {
|
||||||
|
class Configuration(private val monitor: ApplicationEvents) {
|
||||||
|
private val subscribers = mutableListOf<DisposableHandle>()
|
||||||
|
fun <T : Event> subscribe(definition: EventDefinition<T>, handler: EventHandler<T>): DisposableHandle {
|
||||||
|
return monitor.subscribe(definition, handler).also {
|
||||||
|
subscribers.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object Feature : ApplicationFeature<Application, Configuration, EventSubscriber> {
|
||||||
|
override val key = AttributeKey<EventSubscriber>("EventSubscriber")
|
||||||
|
|
||||||
|
@KtorExperimentalAPI
|
||||||
|
override fun install(
|
||||||
|
pipeline: Application,
|
||||||
|
configure: Configuration.() -> Unit
|
||||||
|
): EventSubscriber {
|
||||||
|
Configuration(pipeline.environment.monitor).apply(configure)
|
||||||
|
return EventSubscriber()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/main/kotlin/event/publisher/Publisher.kt
Normal file
32
src/main/kotlin/event/publisher/Publisher.kt
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package fr.dcproject.event.publisher
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.rabbitmq.client.ConnectionFactory
|
||||||
|
import fr.dcproject.Config
|
||||||
|
import fr.dcproject.event.EntityEvent
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
class Publisher(
|
||||||
|
private val mapper: ObjectMapper,
|
||||||
|
private val factory: ConnectionFactory,
|
||||||
|
private val logger: Logger = LoggerFactory.getLogger(Publisher::class.qualifiedName)
|
||||||
|
) {
|
||||||
|
fun <T : EntityEvent> publish(it: T): Job {
|
||||||
|
return GlobalScope.launch {
|
||||||
|
factory.newConnection().use { connection ->
|
||||||
|
connection.createChannel().use { channel ->
|
||||||
|
channel.basicPublish(Config.exchangeNotificationName, "", null, it.serialize().toByteArray())
|
||||||
|
logger.debug("Publish message ${it.target.id}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun EntityEvent.serialize(): String {
|
||||||
|
return mapper.writeValueAsString(this) ?: error("Unable tu serialize message")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
package fr.dcproject
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.util.DefaultIndenter
|
|
||||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter
|
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
|
||||||
import com.fasterxml.jackson.databind.PropertyNamingStrategy
|
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
|
||||||
import com.fasterxml.jackson.datatype.joda.JodaModule
|
|
||||||
import fr.dcproject.entity.Article
|
|
||||||
import fr.dcproject.routes.article
|
|
||||||
import io.ktor.application.Application
|
|
||||||
import io.ktor.application.install
|
|
||||||
import io.ktor.auth.Authentication
|
|
||||||
import io.ktor.features.AutoHeadResponse
|
|
||||||
import io.ktor.features.ContentNegotiation
|
|
||||||
import io.ktor.features.DataConversion
|
|
||||||
import io.ktor.jackson.jackson
|
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
|
||||||
import io.ktor.locations.Locations
|
|
||||||
import io.ktor.routing.Routing
|
|
||||||
import io.ktor.util.KtorExperimentalAPI
|
|
||||||
import org.koin.ktor.ext.Koin
|
|
||||||
import org.koin.ktor.ext.get
|
|
||||||
import java.util.*
|
|
||||||
import fr.dcproject.repository.Article as RepositoryArticle
|
|
||||||
|
|
||||||
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
|
|
||||||
|
|
||||||
@KtorExperimentalAPI
|
|
||||||
@KtorExperimentalLocationsAPI
|
|
||||||
@Suppress("unused") // Referenced in application.conf
|
|
||||||
fun Application.module() {
|
|
||||||
install(Koin) {
|
|
||||||
// Slf4jLog()
|
|
||||||
modules(Module)
|
|
||||||
}
|
|
||||||
|
|
||||||
install(DataConversion) {
|
|
||||||
convert<UUID> {
|
|
||||||
decode { values, _ ->
|
|
||||||
values.singleOrNull()?.let { UUID.fromString(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
encode { value ->
|
|
||||||
when (value) {
|
|
||||||
null -> listOf()
|
|
||||||
is UUID -> listOf(value.toString())
|
|
||||||
else -> throw InternalError("Cannot convert $value as UUID")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
convert<Article> {
|
|
||||||
decode { values, _ ->
|
|
||||||
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
|
||||||
?: throw InternalError("Cannot convert $values to UUID")
|
|
||||||
get<RepositoryArticle>().findById(id) ?: throw InternalError("Article $values not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
install(Locations) {
|
|
||||||
}
|
|
||||||
|
|
||||||
install(Authentication) {
|
|
||||||
}
|
|
||||||
|
|
||||||
install(AutoHeadResponse)
|
|
||||||
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
// TODO move to postgresJson lib
|
|
||||||
jackson {
|
|
||||||
propertyNamingStrategy = PropertyNamingStrategy.SNAKE_CASE
|
|
||||||
|
|
||||||
registerModule(JodaModule())
|
|
||||||
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
|
||||||
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
|
||||||
configure(SerializationFeature.INDENT_OUTPUT, true)
|
|
||||||
setDefaultPrettyPrinter(DefaultPrettyPrinter().apply {
|
|
||||||
indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
|
|
||||||
indentObjectsWith(DefaultIndenter(" ", "\n"))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
install(Routing) {
|
|
||||||
article(get())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package fr.dcproject
|
|
||||||
|
|
||||||
import com.typesafe.config.ConfigFactory
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class Config {
|
|
||||||
private var config = ConfigFactory.load()
|
|
||||||
val sqlFiles = File(this::class.java.getResource("/sql").toURI())
|
|
||||||
val envName: String = config.getString("app.envName")
|
|
||||||
|
|
||||||
val host: String = config.getString("db.host")
|
|
||||||
var database: String = config.getString("db.database")
|
|
||||||
var username: String = config.getString("db.username")
|
|
||||||
var password: String = config.getString("db.password")
|
|
||||||
val port: Int = config.getInt("db.port")
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package fr.dcproject
|
|
||||||
|
|
||||||
import fr.postgresjson.connexion.Connection
|
|
||||||
import fr.postgresjson.connexion.Requester
|
|
||||||
import fr.postgresjson.migration.Migrations
|
|
||||||
import io.ktor.util.KtorExperimentalAPI
|
|
||||||
import org.koin.dsl.module
|
|
||||||
import fr.dcproject.repository.Article as ArticleRepository
|
|
||||||
|
|
||||||
val config = Config()
|
|
||||||
|
|
||||||
@KtorExperimentalAPI
|
|
||||||
val Module = module {
|
|
||||||
|
|
||||||
single { config }
|
|
||||||
|
|
||||||
single { Connection(host = config.host, port = config.port, database = config.database, username = config.username, password = config.password) }
|
|
||||||
|
|
||||||
single { Requester.RequesterFactory(
|
|
||||||
connection = get(),
|
|
||||||
functionsDirectory = config.sqlFiles.resolve("functions")
|
|
||||||
).createRequester() }
|
|
||||||
|
|
||||||
single { ArticleRepository(get()) }
|
|
||||||
single { Migrations(connection = get(), directory = config.sqlFiles) }
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package fr.dcproject.entity
|
|
||||||
import fr.postgresjson.entity.*
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
class Article(
|
|
||||||
id: UUID = UUID.randomUUID(),
|
|
||||||
var versionId: UUID = UUID.randomUUID(),
|
|
||||||
var versionNumber: Int? = null,
|
|
||||||
var title: String?,
|
|
||||||
var annonymous: Boolean? = true,
|
|
||||||
var content: String?,
|
|
||||||
var description: String?,
|
|
||||||
var tags: List<String> = emptyList(),
|
|
||||||
override var createdBy: Citizen?
|
|
||||||
):
|
|
||||||
UuidEntity(id),
|
|
||||||
EntityCreatedAt by EntityCreatedAtImp(),
|
|
||||||
CreatedBy<Citizen> by EntityCreatedByImp()
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package fr.dcproject.entity
|
|
||||||
|
|
||||||
import fr.postgresjson.entity.EntityCreatedAt
|
|
||||||
import fr.postgresjson.entity.EntityCreatedAtImp
|
|
||||||
import fr.postgresjson.entity.UuidEntity
|
|
||||||
import org.joda.time.DateTime
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
class Citizen(
|
|
||||||
id: UUID = UUID.randomUUID(),
|
|
||||||
var name: Name?,
|
|
||||||
var birthday: DateTime?,
|
|
||||||
var userId: String? = null,
|
|
||||||
var voteAnnonymous: Boolean? = null,
|
|
||||||
var followAnnonymous: Boolean? = null,
|
|
||||||
var user: User?
|
|
||||||
) : UuidEntity(id),
|
|
||||||
EntityCreatedAt by EntityCreatedAtImp() {
|
|
||||||
data class Name(
|
|
||||||
var firstName: String?,
|
|
||||||
var lastName: String?,
|
|
||||||
var civility: String? = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package fr.dcproject.entity
|
|
||||||
|
|
||||||
import fr.postgresjson.entity.*
|
|
||||||
import org.joda.time.DateTime
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class User(
|
|
||||||
id: UUID? = UUID.randomUUID(),
|
|
||||||
var username: String?,
|
|
||||||
var blockedAt: DateTime? = null,
|
|
||||||
var plainPassword: String?
|
|
||||||
) : UuidEntity(id),
|
|
||||||
EntityCreatedAt by EntityCreatedAtImp(),
|
|
||||||
EntityUpdatedAt by EntityUpdatedAtImp()
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package fr.dcproject.repository
|
|
||||||
|
|
||||||
import fr.postgresjson.connexion.Paginated
|
|
||||||
import fr.postgresjson.connexion.Requester
|
|
||||||
import fr.postgresjson.entity.EntitiesCollections
|
|
||||||
import fr.postgresjson.repository.RepositoryI
|
|
||||||
import net.pearx.kasechange.toSnakeCase
|
|
||||||
import java.util.*
|
|
||||||
import fr.dcproject.entity.Article as ArticleEntity
|
|
||||||
|
|
||||||
class Article(override var requester: Requester) : RepositoryI<ArticleEntity> {
|
|
||||||
override val entityName = ArticleEntity::class
|
|
||||||
|
|
||||||
fun findById(id: UUID): ArticleEntity? {
|
|
||||||
val function = requester.getFunction("find_article_by_id")
|
|
||||||
return when (val e = EntitiesCollections().get(id) as ArticleEntity?) {
|
|
||||||
null -> {
|
|
||||||
function.selectOne("id" to id)
|
|
||||||
}
|
|
||||||
else -> e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun find(
|
|
||||||
page: Int = 1,
|
|
||||||
limit: Int = 50,
|
|
||||||
sort: String? = null,
|
|
||||||
direction: Direction? = null,
|
|
||||||
search: String? = null
|
|
||||||
): Paginated<ArticleEntity> {
|
|
||||||
return requester
|
|
||||||
.getFunction("find_articles")
|
|
||||||
.select(
|
|
||||||
page, limit,
|
|
||||||
"sort" to sort?.toSnakeCase(),
|
|
||||||
"direction" to direction,
|
|
||||||
"search" to search
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun upsert(article: ArticleEntity): ArticleEntity? {
|
|
||||||
return requester
|
|
||||||
.getFunction("upsert_article")
|
|
||||||
.selectOne<ArticleEntity>("resource" to article)?.also {
|
|
||||||
EntitiesCollections().set(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class Direction {
|
|
||||||
asc,
|
|
||||||
desc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package fr.dcproject.routes
|
|
||||||
|
|
||||||
import Paths
|
|
||||||
import io.ktor.application.call
|
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
|
||||||
import io.ktor.locations.get
|
|
||||||
import io.ktor.locations.post
|
|
||||||
import io.ktor.request.receive
|
|
||||||
import io.ktor.response.respond
|
|
||||||
import io.ktor.routing.Route
|
|
||||||
import fr.dcproject.entity.Article as ArticleEntity
|
|
||||||
import fr.dcproject.repository.Article as ArticleRepository
|
|
||||||
|
|
||||||
@KtorExperimentalLocationsAPI
|
|
||||||
fun Route.article(repo: ArticleRepository) {
|
|
||||||
get<Paths.ArticlesRequest> {
|
|
||||||
val articles = repo.find(it.page, it.limit, it.sort, it.direction, it.search)
|
|
||||||
call.respond(articles)
|
|
||||||
}
|
|
||||||
|
|
||||||
get<Paths.ArticleRequest> {
|
|
||||||
call.respond(it.article)
|
|
||||||
}
|
|
||||||
|
|
||||||
post<Paths.PostArticleRequest>() {
|
|
||||||
val article = call.receive<ArticleEntity>()
|
|
||||||
repo.upsert(article)
|
|
||||||
call.respond(article)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import fr.dcproject.entity.Article
|
|
||||||
import fr.dcproject.repository.Article.Direction
|
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
|
||||||
import io.ktor.locations.Location
|
|
||||||
|
|
||||||
@KtorExperimentalLocationsAPI
|
|
||||||
object Paths {
|
|
||||||
@Location("/articles") class ArticlesRequest(page: Int = 1, limit: Int = 50, val sort: String? = null, val direction: Direction? = null, val search: String? = null) {
|
|
||||||
val page: Int = if (page < 1) 1 else page
|
|
||||||
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
|
|
||||||
}
|
|
||||||
@Location("/articles/{article}") class ArticleRequest(val article: Article)
|
|
||||||
@Location("/articles") class PostArticleRequest
|
|
||||||
}
|
|
||||||
27
src/main/kotlin/messages/Mailer.kt
Normal file
27
src/main/kotlin/messages/Mailer.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package fr.dcproject.messages
|
||||||
|
|
||||||
|
import com.sendgrid.Method
|
||||||
|
import com.sendgrid.Request
|
||||||
|
import com.sendgrid.SendGrid
|
||||||
|
import com.sendgrid.helpers.mail.Mail
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class Mailer(
|
||||||
|
private val key: String
|
||||||
|
) {
|
||||||
|
fun sendEmail(action: () -> Mail): Boolean {
|
||||||
|
val mail = action()
|
||||||
|
|
||||||
|
val sg = SendGrid(key)
|
||||||
|
val request = Request()
|
||||||
|
try {
|
||||||
|
request.method = Method.POST
|
||||||
|
request.endpoint = "mail/send"
|
||||||
|
request.body = mail.build()
|
||||||
|
val response = sg.api(request)
|
||||||
|
return response.statusCode == 202
|
||||||
|
} catch (ex: IOException) {
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/main/kotlin/messages/NotificationEmailSender.kt
Normal file
66
src/main/kotlin/messages/NotificationEmailSender.kt
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package fr.dcproject.messages
|
||||||
|
|
||||||
|
import com.sendgrid.helpers.mail.Mail
|
||||||
|
import com.sendgrid.helpers.mail.objects.Content
|
||||||
|
import com.sendgrid.helpers.mail.objects.Email
|
||||||
|
import fr.dcproject.entity.*
|
||||||
|
import fr.postgresjson.entity.immutable.UuidEntityI
|
||||||
|
import java.util.*
|
||||||
|
import fr.dcproject.repository.Citizen as CitizenRepository
|
||||||
|
import fr.dcproject.repository.Article as ArticleRepository
|
||||||
|
|
||||||
|
class NotificationEmailSender(
|
||||||
|
private val mailer: Mailer,
|
||||||
|
private val domain: String,
|
||||||
|
private val citizenRepo: CitizenRepository,
|
||||||
|
private val articleRepo: ArticleRepository
|
||||||
|
) {
|
||||||
|
fun sendEmail(follow: FollowSimple<out TargetRef, CitizenRef>) {
|
||||||
|
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: CitizenBasicI, target: UuidEntityI): String? {
|
||||||
|
return when (target) {
|
||||||
|
is Article -> """
|
||||||
|
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: CitizenBasicI, target: UuidEntityI): String {
|
||||||
|
return when (target) {
|
||||||
|
is Article -> """
|
||||||
|
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")
|
||||||
|
}
|
||||||
51
src/main/kotlin/messages/SsoManager.kt
Normal file
51
src/main/kotlin/messages/SsoManager.kt
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package fr.dcproject.messages
|
||||||
|
|
||||||
|
import com.sendgrid.helpers.mail.Mail
|
||||||
|
import com.sendgrid.helpers.mail.objects.Content
|
||||||
|
import com.sendgrid.helpers.mail.objects.Email
|
||||||
|
import fr.dcproject.JwtConfig
|
||||||
|
import fr.dcproject.entity.CitizenBasicI
|
||||||
|
import io.ktor.http.URLBuilder
|
||||||
|
import fr.dcproject.repository.Citizen as CitizenRepository
|
||||||
|
|
||||||
|
class SsoManager(
|
||||||
|
private val mailer: Mailer,
|
||||||
|
private val domain: String,
|
||||||
|
private val citizenRepo: CitizenRepository
|
||||||
|
) {
|
||||||
|
fun sendEmail(email: String, url: String) {
|
||||||
|
val citizen = citizenRepo.findByEmail(email) ?: noEmail(email)
|
||||||
|
sendEmail(citizen, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendEmail(citizen: CitizenBasicI, url: String) {
|
||||||
|
mailer.sendEmail {
|
||||||
|
Mail(
|
||||||
|
Email("sso@$domain"),
|
||||||
|
"Connection",
|
||||||
|
Email(citizen.email),
|
||||||
|
Content("text/plain", generateContent(citizen, url))
|
||||||
|
).apply {
|
||||||
|
addContent(Content("text/html", generateHtmlContent(citizen, url)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateHtmlContent(citizen: CitizenBasicI, url: String): String? {
|
||||||
|
val urlObject = URLBuilder(url)
|
||||||
|
urlObject.parameters.append("token", JwtConfig.makeToken(citizen.user))
|
||||||
|
return "Click <a href=\"${urlObject.buildString()}\">here</a> for connect to $domain"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateContent(citizen: CitizenBasicI, url: String): String {
|
||||||
|
val urlObject = URLBuilder(url)
|
||||||
|
urlObject.parameters.append("token", JwtConfig.makeToken(citizen.user))
|
||||||
|
return "Copy this link into your browser for connect to $domain: \n${urlObject.buildString()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
class EmailNotFound(val email: String) : Exception() {
|
||||||
|
override val message: String = "No Citizen with this email : $email"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun noEmail(email: String): Nothing = throw EmailNotFound(email)
|
||||||
|
}
|
||||||
54
src/main/kotlin/repository/Article.kt
Normal file
54
src/main/kotlin/repository/Article.kt
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.dcproject.entity.ArticleFull
|
||||||
|
import fr.dcproject.entity.ArticleSimple
|
||||||
|
import fr.postgresjson.connexion.Paginated
|
||||||
|
import fr.postgresjson.connexion.Requester
|
||||||
|
import fr.postgresjson.entity.Parameter
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import fr.postgresjson.repository.RepositoryI.Direction
|
||||||
|
import net.pearx.kasechange.toSnakeCase
|
||||||
|
import java.util.*
|
||||||
|
import fr.dcproject.entity.Article as ArticleEntity
|
||||||
|
|
||||||
|
class Article(override var requester: Requester) : RepositoryI {
|
||||||
|
fun findById(id: UUID): ArticleEntity? {
|
||||||
|
val function = requester.getFunction("find_article_by_id")
|
||||||
|
return function.selectOne("id" to id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findVerionsByVersionsId(page: Int = 1, limit: Int = 50, versionId: UUID): Paginated<ArticleEntity> {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_articles_versions_by_version_id")
|
||||||
|
.select(page, limit, "version_id" to versionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun find(
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50,
|
||||||
|
sort: String? = null,
|
||||||
|
direction: Direction? = null,
|
||||||
|
search: String? = null,
|
||||||
|
filter: Filter = Filter()
|
||||||
|
): Paginated<ArticleSimple> {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_articles")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"sort" to sort?.toSnakeCase(),
|
||||||
|
"direction" to direction,
|
||||||
|
"search" to search,
|
||||||
|
"filter" to filter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun upsert(article: ArticleFull): ArticleEntity? {
|
||||||
|
return requester
|
||||||
|
.getFunction("upsert_article")
|
||||||
|
.selectOne("resource" to article)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Filter(
|
||||||
|
val createdById: String? = null
|
||||||
|
) : Parameter
|
||||||
|
}
|
||||||
53
src/main/kotlin/repository/Citizen.kt
Normal file
53
src/main/kotlin/repository/Citizen.kt
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.dcproject.entity.CitizenBasic
|
||||||
|
import fr.dcproject.entity.CitizenFull
|
||||||
|
import fr.dcproject.entity.UserI
|
||||||
|
import fr.postgresjson.connexion.Paginated
|
||||||
|
import fr.postgresjson.connexion.Requester
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import fr.postgresjson.repository.RepositoryI.Direction
|
||||||
|
import net.pearx.kasechange.toSnakeCase
|
||||||
|
import java.util.*
|
||||||
|
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||||
|
|
||||||
|
class Citizen(override var requester: Requester) : RepositoryI {
|
||||||
|
fun findById(id: UUID): CitizenEntity? = requester
|
||||||
|
.getFunction("find_citizen_by_id_with_user_and_workgroups")
|
||||||
|
.selectOne("id" to id)
|
||||||
|
|
||||||
|
fun findByUser(user: UserI): CitizenEntity? = requester
|
||||||
|
.getFunction("find_citizen_by_user_id")
|
||||||
|
.selectOne("user_id" to user.id)
|
||||||
|
|
||||||
|
fun findByUsername(unsername: String): CitizenEntity? = requester
|
||||||
|
.getFunction("find_citizen_by_username")
|
||||||
|
.selectOne("username" to unsername)
|
||||||
|
|
||||||
|
fun findByEmail(email: String): CitizenEntity? = requester
|
||||||
|
.getFunction("find_citizen_by_email")
|
||||||
|
.selectOne("email" to email)
|
||||||
|
|
||||||
|
fun find(
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50,
|
||||||
|
sort: String? = null,
|
||||||
|
direction: Direction? = null,
|
||||||
|
search: String? = null
|
||||||
|
): Paginated<CitizenBasic> = requester
|
||||||
|
.getFunction("find_citizens")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"sort" to sort?.toSnakeCase(),
|
||||||
|
"direction" to direction,
|
||||||
|
"search" to search
|
||||||
|
)
|
||||||
|
|
||||||
|
fun upsert(citizen: CitizenFull): CitizenEntity? = requester
|
||||||
|
.getFunction("upsert_citizen")
|
||||||
|
.selectOne("resource" to citizen)
|
||||||
|
|
||||||
|
fun insertWithUser(citizen: CitizenFull): CitizenEntity? = requester
|
||||||
|
.getFunction("insert_citizen_with_user")
|
||||||
|
.selectOne("resource" to citizen)
|
||||||
|
}
|
||||||
210
src/main/kotlin/repository/Comment.kt
Normal file
210
src/main/kotlin/repository/Comment.kt
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.dcproject.entity.ArticleRef
|
||||||
|
import fr.dcproject.entity.ConstitutionRef
|
||||||
|
import fr.dcproject.entity.TargetI
|
||||||
|
import fr.dcproject.entity.TargetRef
|
||||||
|
import fr.postgresjson.connexion.Paginated
|
||||||
|
import fr.postgresjson.connexion.Requester
|
||||||
|
import fr.postgresjson.entity.immutable.UuidEntityI
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import java.util.*
|
||||||
|
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||||
|
import fr.dcproject.entity.Comment as CommentEntity
|
||||||
|
import fr.dcproject.entity.Article as ArticleEntity
|
||||||
|
|
||||||
|
abstract class Comment<T : TargetI>(override var requester: Requester) : RepositoryI {
|
||||||
|
abstract fun findById(id: UUID): CommentEntity<T>?
|
||||||
|
|
||||||
|
abstract fun findByCitizen(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50
|
||||||
|
): Paginated<CommentEntity<T>>
|
||||||
|
|
||||||
|
open fun findByParent(
|
||||||
|
parent: CommentEntity<T>,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50
|
||||||
|
): Paginated<CommentEntity<T>> {
|
||||||
|
return findByParent(parent.id, page, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun findByParent(
|
||||||
|
parentId: UUID,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50
|
||||||
|
): Paginated<CommentEntity<T>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_comments_by_parent")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"parent_id" to parentId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun findByTarget(
|
||||||
|
target: UuidEntityI,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50,
|
||||||
|
sort: CommentArticle.Sort = CommentArticle.Sort.CREATED_AT
|
||||||
|
): Paginated<CommentEntity<T>> {
|
||||||
|
return findByTarget(target.id, page, limit, sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun findByTarget(
|
||||||
|
targetId: UUID,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50,
|
||||||
|
sort: CommentArticle.Sort = CommentArticle.Sort.CREATED_AT
|
||||||
|
): Paginated<CommentEntity<T>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_comments_by_target")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"target_id" to targetId,
|
||||||
|
"sort" to sort.sql
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <I : T> comment(comment: CommentEntity<I>) {
|
||||||
|
requester
|
||||||
|
.getFunction("comment")
|
||||||
|
.sendQuery(
|
||||||
|
"reference" to comment.target.reference,
|
||||||
|
"resource" to comment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <I : T> edit(comment: CommentEntity<I>) {
|
||||||
|
requester
|
||||||
|
.getFunction("edit_comment")
|
||||||
|
.sendQuery(
|
||||||
|
"id" to comment.id,
|
||||||
|
"content" to comment.content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CommentGeneric(requester: Requester) : Comment<TargetRef>(requester) {
|
||||||
|
override fun findById(id: UUID): CommentEntity<TargetRef>? {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_comment_by_id")
|
||||||
|
.selectOne(mapOf("id" to id))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findByCitizen(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
page: Int,
|
||||||
|
limit: Int
|
||||||
|
): Paginated<CommentEntity<TargetRef>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_comments_by_citizen")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"created_by_id" to citizen.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findByParent(
|
||||||
|
parentId: UUID,
|
||||||
|
page: Int,
|
||||||
|
limit: Int
|
||||||
|
): Paginated<CommentEntity<TargetRef>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_comments_by_parent")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"parent_id" to parentId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CommentArticle(requester: Requester) : Comment<ArticleEntity>(requester) {
|
||||||
|
override fun findById(id: UUID): CommentEntity<ArticleEntity>? {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_comment_by_id")
|
||||||
|
.selectOne(mapOf("id" to id))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findByCitizen(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
page: Int,
|
||||||
|
limit: Int
|
||||||
|
): Paginated<CommentEntity<ArticleEntity>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_comments_by_citizen")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"created_by_id" to citizen.id,
|
||||||
|
"reference" to TargetI.getReference(ArticleRef::class)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findByTarget(
|
||||||
|
target: UuidEntityI,
|
||||||
|
page: Int,
|
||||||
|
limit: Int,
|
||||||
|
sort: Sort
|
||||||
|
): Paginated<CommentEntity<ArticleEntity>> = requester
|
||||||
|
.getFunction("find_comments_by_target")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"target_id" to target.id,
|
||||||
|
"sort" to sort.sql
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class Sort(val sql: String) {
|
||||||
|
CREATED_AT("created_at"), VOTES("votes");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromString(string: String): Sort? {
|
||||||
|
return values().firstOrNull { it.sql == string }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CommentConstitution(requester: Requester) : Comment<ConstitutionRef>(requester) {
|
||||||
|
override fun findById(id: UUID): CommentEntity<ConstitutionRef>? {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_comment_by_id")
|
||||||
|
.selectOne(mapOf("id" to id))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findByCitizen(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
page: Int,
|
||||||
|
limit: Int
|
||||||
|
): Paginated<CommentEntity<ConstitutionRef>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_comments_by_citizen")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"created_by_id" to citizen.id,
|
||||||
|
"reference" to TargetI.getReference(ConstitutionRef::class)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findByTarget(
|
||||||
|
target: UuidEntityI,
|
||||||
|
page: Int,
|
||||||
|
limit: Int,
|
||||||
|
sort: CommentArticle.Sort
|
||||||
|
): Paginated<CommentEntity<ConstitutionRef>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_comments_by_target")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"target_id" to target.id,
|
||||||
|
"sort" to sort.sql
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/main/kotlin/repository/Constitution.kt
Normal file
42
src/main/kotlin/repository/Constitution.kt
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.dcproject.entity.ArticleRef
|
||||||
|
import fr.dcproject.entity.CitizenSimple
|
||||||
|
import fr.dcproject.entity.ConstitutionSimple
|
||||||
|
import fr.postgresjson.connexion.Paginated
|
||||||
|
import fr.postgresjson.connexion.Requester
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import fr.postgresjson.repository.RepositoryI.Direction
|
||||||
|
import net.pearx.kasechange.toSnakeCase
|
||||||
|
import java.util.*
|
||||||
|
import fr.dcproject.entity.Constitution as ConstitutionEntity
|
||||||
|
|
||||||
|
class Constitution(override var requester: Requester) : RepositoryI {
|
||||||
|
fun findById(id: UUID): ConstitutionEntity? {
|
||||||
|
val function = requester.getFunction("find_constitution_by_id")
|
||||||
|
return function.selectOne("id" to id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun find(
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50,
|
||||||
|
sort: String? = null,
|
||||||
|
direction: Direction? = null,
|
||||||
|
search: String? = null
|
||||||
|
): Paginated<ConstitutionEntity> {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_constitutions")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"sort" to sort?.toSnakeCase(),
|
||||||
|
"direction" to direction,
|
||||||
|
"search" to search
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun upsert(constitution: ConstitutionSimple<CitizenSimple, ConstitutionSimple.TitleSimple<ArticleRef>>): ConstitutionEntity? {
|
||||||
|
return requester
|
||||||
|
.getFunction("upsert_constitution")
|
||||||
|
.selectOne("resource" to constitution)
|
||||||
|
}
|
||||||
|
}
|
||||||
140
src/main/kotlin/repository/Follow.kt
Normal file
140
src/main/kotlin/repository/Follow.kt
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.dcproject.entity.*
|
||||||
|
import fr.postgresjson.connexion.Paginated
|
||||||
|
import fr.postgresjson.connexion.Requester
|
||||||
|
import fr.postgresjson.entity.immutable.UuidEntity
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import java.util.*
|
||||||
|
import fr.dcproject.entity.Article as ArticleEntity
|
||||||
|
import fr.dcproject.entity.Constitution as ConstitutionEntity
|
||||||
|
import fr.dcproject.entity.Follow as FollowEntity
|
||||||
|
|
||||||
|
sealed class Follow<IN : TargetRef, OUT : TargetRef>(override var requester: Requester) : RepositoryI {
|
||||||
|
open fun findByCitizen(
|
||||||
|
citizen: CitizenI,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50
|
||||||
|
): Paginated<FollowEntity<OUT>> =
|
||||||
|
findByCitizen(citizen.id, page, limit)
|
||||||
|
|
||||||
|
open fun findByCitizen(
|
||||||
|
citizenId: UUID,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50
|
||||||
|
): Paginated<FollowEntity<OUT>> {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_follows_by_citizen")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"created_by_id" to citizenId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun follow(follow: FollowEntity<IN>) {
|
||||||
|
requester
|
||||||
|
.getFunction("follow")
|
||||||
|
.sendQuery(
|
||||||
|
"reference" to follow.target.reference,
|
||||||
|
"target_id" to follow.target.id,
|
||||||
|
"created_by_id" to follow.createdBy.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unfollow(follow: FollowEntity<IN>) {
|
||||||
|
requester
|
||||||
|
.getFunction("unfollow")
|
||||||
|
.sendQuery(
|
||||||
|
"reference" to follow.target.reference,
|
||||||
|
"target_id" to follow.target.id,
|
||||||
|
"created_by_id" to follow.createdBy.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun findFollow(
|
||||||
|
citizen: CitizenI,
|
||||||
|
target: TargetRef
|
||||||
|
): FollowEntity<OUT>? =
|
||||||
|
requester
|
||||||
|
.getFunction("find_follow")
|
||||||
|
.selectOne(
|
||||||
|
"citizen_id" to citizen.id,
|
||||||
|
"target_id" to target.id,
|
||||||
|
"target_reference" to target.reference
|
||||||
|
)
|
||||||
|
|
||||||
|
fun findFollowsByTarget(
|
||||||
|
target: UuidEntity,
|
||||||
|
bulkSize: Int = 300
|
||||||
|
): Flow<FollowSimple<IN, CitizenRef>> = flow {
|
||||||
|
var nextPage = 1
|
||||||
|
do {
|
||||||
|
val paginate = findFollowsByTarget(target, nextPage, bulkSize)
|
||||||
|
paginate.result.forEach {
|
||||||
|
emit(it)
|
||||||
|
}
|
||||||
|
nextPage = paginate.currentPage + 1
|
||||||
|
} while (!paginate.isLastPage())
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun findFollowsByTarget(
|
||||||
|
target: UuidEntity,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 300
|
||||||
|
): Paginated<FollowSimple<IN, CitizenRef>>
|
||||||
|
}
|
||||||
|
|
||||||
|
class FollowArticle(requester: Requester) : Follow<ArticleRef, ArticleEntity>(requester) {
|
||||||
|
override fun findByCitizen(
|
||||||
|
citizenId: UUID,
|
||||||
|
page: Int,
|
||||||
|
limit: Int
|
||||||
|
): Paginated<FollowEntity<ArticleEntity>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_follows_article_by_citizen")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"created_by_id" to citizenId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findFollowsByTarget(
|
||||||
|
target: UuidEntity,
|
||||||
|
page: Int,
|
||||||
|
limit: Int
|
||||||
|
): Paginated<FollowSimple<ArticleRef, CitizenRef>> {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_follows_article_by_target")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"target_id" to target.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FollowConstitution(requester: Requester) : Follow<ConstitutionRef, ConstitutionEntity>(requester) {
|
||||||
|
override fun findByCitizen(
|
||||||
|
citizenId: UUID,
|
||||||
|
page: Int,
|
||||||
|
limit: Int
|
||||||
|
): Paginated<FollowEntity<ConstitutionEntity>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_follows_constitution_by_citizen")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"created_by_id" to citizenId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findFollowsByTarget(
|
||||||
|
target: UuidEntity,
|
||||||
|
page: Int,
|
||||||
|
limit: Int
|
||||||
|
): Paginated<FollowSimple<ConstitutionRef, CitizenRef>> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
156
src/main/kotlin/repository/Opinion.kt
Normal file
156
src/main/kotlin/repository/Opinion.kt
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference
|
||||||
|
import fr.dcproject.entity.ArticleRef
|
||||||
|
import fr.dcproject.entity.CitizenRef
|
||||||
|
import fr.dcproject.entity.OpinionChoiceRef
|
||||||
|
import fr.dcproject.entity.TargetRef
|
||||||
|
import fr.postgresjson.connexion.Paginated
|
||||||
|
import fr.postgresjson.connexion.Requester
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import net.pearx.kasechange.toSnakeCase
|
||||||
|
import java.util.*
|
||||||
|
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||||
|
import fr.dcproject.entity.Opinion as OpinionEntity
|
||||||
|
import fr.dcproject.entity.OpinionArticle as OpinionArticleEntity
|
||||||
|
import fr.dcproject.entity.OpinionChoice as OpinionChoiceEntity
|
||||||
|
|
||||||
|
open class OpinionChoice(override val requester: Requester) : RepositoryI {
|
||||||
|
/**
|
||||||
|
* find all opinion choices
|
||||||
|
* can be filtered by target compatibility
|
||||||
|
*/
|
||||||
|
fun findOpinionsChoices(targets: List<String> = emptyList()): List<OpinionChoiceEntity> =
|
||||||
|
requester
|
||||||
|
.getFunction("find_opinion_choices")
|
||||||
|
.select(
|
||||||
|
"targets" to targets
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find opinion choices by name
|
||||||
|
*/
|
||||||
|
fun findOpinionsChoiceByName(name: String): OpinionChoiceEntity? =
|
||||||
|
findOpinionsChoices().first {
|
||||||
|
it.name == name
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find one opinion choices by id
|
||||||
|
*/
|
||||||
|
fun findOpinionChoiceById(id: UUID): OpinionChoiceEntity? =
|
||||||
|
requester
|
||||||
|
.getFunction("find_opinion_choice_by_id")
|
||||||
|
.selectOne(
|
||||||
|
"id" to id
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find one opinion choices by id
|
||||||
|
*/
|
||||||
|
fun findOpinionChoicesByIds(ids: List<UUID>): List<OpinionChoiceEntity> =
|
||||||
|
requester
|
||||||
|
.getFunction("find_opinion_choices_by_ids")
|
||||||
|
.select(
|
||||||
|
"ids" to ids
|
||||||
|
)
|
||||||
|
|
||||||
|
fun upsertOpinionChoice(opinionChoice: OpinionChoiceEntity): OpinionChoiceEntity = requester
|
||||||
|
.getFunction("upsert_opinion_choice")
|
||||||
|
.selectOne(
|
||||||
|
"resource" to opinionChoice
|
||||||
|
)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Opinion<T : TargetRef>(requester: Requester) : OpinionChoice(requester) {
|
||||||
|
/**
|
||||||
|
* Create an Opinion on target (article,...)
|
||||||
|
*/
|
||||||
|
abstract fun updateOpinions(choices: List<OpinionChoiceRef>, citizen: CitizenRef, target: TargetRef): List<OpinionEntity<T>>
|
||||||
|
fun updateOpinions(choice: OpinionChoiceRef, citizen: CitizenRef, target: TargetRef): List<OpinionEntity<T>> =
|
||||||
|
updateOpinions(listOf(choice), citizen, target)
|
||||||
|
|
||||||
|
abstract fun addOpinion(opinion: OpinionEntity<T>): OpinionEntity<T>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find opinions of one citizen filtered by target ids
|
||||||
|
*/
|
||||||
|
fun findCitizenOpinionsByTargets(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
targets: List<UUID>
|
||||||
|
): List<OpinionEntity<T>> {
|
||||||
|
val typeReference = object : TypeReference<List<OpinionEntity<T>>>() {}
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_citizen_opinions_by_target_ids")
|
||||||
|
.select(
|
||||||
|
typeReference, mapOf(
|
||||||
|
"citizen_id" to citizen.id,
|
||||||
|
"ids" to targets
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find opinion of citizen filtered by one target id
|
||||||
|
*/
|
||||||
|
fun findCitizenOpinionsByTarget(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
target: UUID
|
||||||
|
): List<OpinionEntity<T>> {
|
||||||
|
val typeReference = object : TypeReference<List<OpinionEntity<T>>>() {}
|
||||||
|
return requester
|
||||||
|
.getFunction("find_citizen_opinions_by_target_id")
|
||||||
|
.select(
|
||||||
|
typeReference, mapOf(
|
||||||
|
"citizen_id" to citizen.id,
|
||||||
|
"id" to target
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find paginated opinion of one citizen
|
||||||
|
* can be sorted
|
||||||
|
*/
|
||||||
|
fun findCitizenOpinions(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50,
|
||||||
|
sort: String? = null,
|
||||||
|
direction: RepositoryI.Direction? = null
|
||||||
|
): Paginated<OpinionEntity<TargetRef>> {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_citizen_opinions")
|
||||||
|
.select(page, limit,
|
||||||
|
"sort" to sort?.toSnakeCase(),
|
||||||
|
"direction" to direction,
|
||||||
|
"citizen_id" to citizen.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OpinionArticle(requester: Requester) : Opinion<ArticleRef>(requester) {
|
||||||
|
/**
|
||||||
|
* Update Opinions on Article (Delete old one)
|
||||||
|
*/
|
||||||
|
override fun updateOpinions(choices: List<OpinionChoiceRef>, citizen: CitizenRef, target: TargetRef): List<OpinionArticleEntity> {
|
||||||
|
return requester
|
||||||
|
.getFunction("update_citizen_opinions_by_target_id")
|
||||||
|
.select(
|
||||||
|
"choices_ids" to choices.map { it.id },
|
||||||
|
"citizen_id" to citizen.id,
|
||||||
|
"target_id" to target.id,
|
||||||
|
"target_reference" to target.reference
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Opinions on Article
|
||||||
|
*/
|
||||||
|
override fun addOpinion(opinion: OpinionEntity<ArticleRef>): OpinionArticleEntity {
|
||||||
|
return requester
|
||||||
|
.getFunction("upsert_opinion")
|
||||||
|
.selectOne("resource" to opinion)!!
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/main/kotlin/repository/User.kt
Normal file
43
src/main/kotlin/repository/User.kt
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.dcproject.entity.UserFull
|
||||||
|
import fr.postgresjson.connexion.Requester
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import io.ktor.auth.UserPasswordCredential
|
||||||
|
import java.util.*
|
||||||
|
import fr.dcproject.entity.User as UserEntity
|
||||||
|
|
||||||
|
class User(override var requester: Requester) : RepositoryI {
|
||||||
|
fun findByCredentials(credentials: UserPasswordCredential): UserEntity? {
|
||||||
|
return requester
|
||||||
|
.getFunction("check_user")
|
||||||
|
.selectOne(
|
||||||
|
"username" to credentials.name,
|
||||||
|
"plain_password" to credentials.password
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findById(id: UUID): UserEntity {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_user_by_id")
|
||||||
|
.selectOne(
|
||||||
|
"id" to id
|
||||||
|
) ?: throw UserNotFound(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insert(user: UserEntity): UserEntity? {
|
||||||
|
return requester
|
||||||
|
.getFunction("insert_user")
|
||||||
|
.selectOne("resource" to user)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changePassword(user: UserFull) {
|
||||||
|
requester
|
||||||
|
.getFunction("change_user_password")
|
||||||
|
.sendQuery("resource" to user)
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserNotFound(override val message: String?, override val cause: Throwable?) : Throwable(message, cause) {
|
||||||
|
constructor(id: UUID) : this("No User with ID $id", null)
|
||||||
|
}
|
||||||
|
}
|
||||||
123
src/main/kotlin/repository/Vote.kt
Normal file
123
src/main/kotlin/repository/Vote.kt
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference
|
||||||
|
import fr.dcproject.entity.*
|
||||||
|
import fr.dcproject.entity.Article
|
||||||
|
import fr.dcproject.entity.Comment
|
||||||
|
import fr.dcproject.entity.Constitution
|
||||||
|
import fr.postgresjson.connexion.Paginated
|
||||||
|
import fr.postgresjson.connexion.Requester
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import java.util.*
|
||||||
|
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||||
|
import fr.dcproject.entity.Vote as VoteEntity
|
||||||
|
|
||||||
|
open class Vote<T : TargetI>(override var requester: Requester) : RepositoryI {
|
||||||
|
fun vote(vote: VoteEntity<T>): VoteAggregation {
|
||||||
|
val author = vote.createdBy
|
||||||
|
val anonymous = author.voteAnonymous
|
||||||
|
return requester
|
||||||
|
.getFunction("vote")
|
||||||
|
.selectOne(
|
||||||
|
"reference" to vote.target.reference,
|
||||||
|
"target_id" to vote.target.id,
|
||||||
|
"note" to vote.note,
|
||||||
|
"created_by_id" to author.id,
|
||||||
|
"anonymous" to anonymous
|
||||||
|
)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findByCitizen(
|
||||||
|
citizenId: UUID,
|
||||||
|
target: String,
|
||||||
|
typeReference: TypeReference<List<VoteEntity<T>>>,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50
|
||||||
|
): Paginated<VoteEntity<T>> {
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_votes_by_citizen")
|
||||||
|
.select(
|
||||||
|
page, limit, typeReference, mapOf(
|
||||||
|
"created_by_id" to citizenId,
|
||||||
|
"reference" to target
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findCitizenVotesByTargets(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
targets: List<UUID>
|
||||||
|
): List<VoteEntity<*>> {
|
||||||
|
val typeReference = object : TypeReference<List<VoteEntity<TargetRef>>>() {}
|
||||||
|
return requester.run {
|
||||||
|
getFunction("find_citizen_votes_by_target_ids")
|
||||||
|
.select(
|
||||||
|
typeReference, mapOf(
|
||||||
|
"citizen_id" to citizen.id,
|
||||||
|
"ids" to targets
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VoteArticle(requester: Requester) : Vote<Article>(requester) {
|
||||||
|
fun findByCitizen(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50
|
||||||
|
): Paginated<VoteEntity<Article>> =
|
||||||
|
findByCitizen(
|
||||||
|
citizen.id,
|
||||||
|
"article",
|
||||||
|
object : TypeReference<List<VoteEntity<Article>>>() {},
|
||||||
|
page,
|
||||||
|
limit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class VoteArticleComment(requester: Requester) : Vote<Comment<Article>>(requester) {
|
||||||
|
fun findByCitizen(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50
|
||||||
|
): Paginated<VoteEntity<Comment<Article>>> =
|
||||||
|
findByCitizen(
|
||||||
|
citizen.id,
|
||||||
|
"article",
|
||||||
|
object : TypeReference<List<VoteEntity<Comment<Article>>>>() {},
|
||||||
|
page,
|
||||||
|
limit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class VoteComment(requester: Requester) : Vote<Comment<TargetRef>>(requester) {
|
||||||
|
fun findByCitizen(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50
|
||||||
|
): Paginated<VoteEntity<Comment<TargetRef>>> =
|
||||||
|
findByCitizen(
|
||||||
|
citizen.id,
|
||||||
|
"article",
|
||||||
|
object : TypeReference<List<VoteEntity<Comment<TargetRef>>>>() {},
|
||||||
|
page,
|
||||||
|
limit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class VoteConstitution(requester: Requester) : Vote<Constitution>(requester) {
|
||||||
|
fun findByCitizen(
|
||||||
|
citizen: CitizenEntity,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50
|
||||||
|
): Paginated<VoteEntity<Constitution>> =
|
||||||
|
findByCitizen(
|
||||||
|
citizen.id,
|
||||||
|
"constitution",
|
||||||
|
object : TypeReference<List<VoteEntity<Constitution>>>() {},
|
||||||
|
page,
|
||||||
|
limit
|
||||||
|
)
|
||||||
|
}
|
||||||
93
src/main/kotlin/repository/Workgroup.kt
Normal file
93
src/main/kotlin/repository/Workgroup.kt
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package fr.dcproject.repository
|
||||||
|
|
||||||
|
import fr.dcproject.entity.*
|
||||||
|
import fr.dcproject.entity.WorkgroupWithMembersI.Member
|
||||||
|
import fr.postgresjson.connexion.Paginated
|
||||||
|
import fr.postgresjson.connexion.Requester
|
||||||
|
import fr.postgresjson.entity.Parameter
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import fr.postgresjson.repository.RepositoryI.Direction
|
||||||
|
import fr.postgresjson.serializer.serialize
|
||||||
|
import net.pearx.kasechange.toSnakeCase
|
||||||
|
import java.util.*
|
||||||
|
import fr.dcproject.entity.Workgroup as WorkgroupEntity
|
||||||
|
|
||||||
|
class Workgroup(override var requester: Requester) : RepositoryI {
|
||||||
|
fun findById(id: UUID): WorkgroupEntity? {
|
||||||
|
val function = requester.getFunction("find_workgroup_by_id")
|
||||||
|
return function.selectOne("id" to id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun find(
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50,
|
||||||
|
sort: String? = null,
|
||||||
|
direction: Direction? = null,
|
||||||
|
search: String? = null,
|
||||||
|
filter: Filter = Filter()
|
||||||
|
): Paginated<WorkgroupEntity> {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_workgroups")
|
||||||
|
.select(
|
||||||
|
page, limit,
|
||||||
|
"sort" to sort?.toSnakeCase(),
|
||||||
|
"direction" to direction,
|
||||||
|
"search" to search,
|
||||||
|
"filter" to filter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun upsert(workgroup: WorkgroupSimple<CitizenRef>): WorkgroupEntity = requester
|
||||||
|
.getFunction("upsert_workgroup")
|
||||||
|
.selectOne("resource" to workgroup) ?: error("query 'upsert_workgroup' return null")
|
||||||
|
|
||||||
|
fun delete(workgroup: WorkgroupRef) = requester
|
||||||
|
.getFunction("delete_workgroup")
|
||||||
|
.perform("id" to workgroup.id)
|
||||||
|
|
||||||
|
fun addMember(workgroup: WorkgroupI, member: Member<CitizenI>): Member<CitizenBasic>? =
|
||||||
|
addMember(workgroup, member.citizen, member.roles)
|
||||||
|
|
||||||
|
fun addMember(workgroup: WorkgroupI, citizen: CitizenI, roles: List<Member.Role>): Member<CitizenBasic>? = requester
|
||||||
|
.getFunction("add_workgroup_member")
|
||||||
|
.selectOne(
|
||||||
|
"id" to workgroup.id,
|
||||||
|
"members" to Member(citizen, roles).serialize()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun <Z : CitizenI> addMembers(workgroup: WorkgroupI, members: List<Member<Z>>): List<Member<CitizenBasic>> = requester
|
||||||
|
.getFunction("add_workgroup_members")
|
||||||
|
.select(
|
||||||
|
"id" to workgroup.id,
|
||||||
|
"members" to members.serialize()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun <Z : CitizenI> removeMember(workgroup: WorkgroupI, memberToDelete: Member<Z>): List<Member<CitizenBasic>> =
|
||||||
|
removeMembers(workgroup, listOf(memberToDelete))
|
||||||
|
|
||||||
|
fun <Z : CitizenI> removeMembers(workgroup: WorkgroupI, membersToDelete: List<Member<Z>>): List<Member<CitizenBasic>> = requester
|
||||||
|
.getFunction("remove_workgroup_members")
|
||||||
|
.select(
|
||||||
|
"id" to workgroup.id,
|
||||||
|
"members" to membersToDelete
|
||||||
|
)
|
||||||
|
|
||||||
|
fun <Z : CitizenI> updateMembers(workgroup: WorkgroupI, members: List<Member<Z>>): List<Member<CitizenBasic>> = requester
|
||||||
|
.getFunction("update_workgroup_members")
|
||||||
|
.select(
|
||||||
|
"id" to workgroup.id,
|
||||||
|
"members" to members
|
||||||
|
)
|
||||||
|
|
||||||
|
fun <W : WorkgroupWithMembersI<Z>, Z : CitizenI> updateMembers(workgroup: W): W {
|
||||||
|
updateMembers(workgroup, workgroup.members).let {
|
||||||
|
workgroup.members = it as List<Member<Z>>
|
||||||
|
}
|
||||||
|
|
||||||
|
return workgroup
|
||||||
|
}
|
||||||
|
|
||||||
|
class Filter(
|
||||||
|
val createdById: String? = null
|
||||||
|
) : Parameter
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user