Compare commits
485 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4bb458e8d6 | |||
| 921a545877 | |||
| ef942b956e | |||
| ff74ad7e47 | |||
| 2bb90ced03 | |||
| a48cd52652 | |||
| dd4c2dadab | |||
| c81b63aef2 | |||
| cb762a446a | |||
| db810ab0c6 | |||
| 01c5b78325 | |||
| 1bc7293660 | |||
| 55c890aca5 | |||
| c0e364637a | |||
| 0a1ed9ba82 | |||
| 620085fda8 | |||
| 3b5c1cf68a | |||
| a0d07e88a1 | |||
| f17277c0e9 | |||
| 9f13213a35 | |||
| 5f0b8de159 | |||
| 6b66130ddc | |||
| 7f93ec5044 | |||
| 1be608e6b2 | |||
| b13cd5544c | |||
| 104f0fb3fc | |||
| b2f40ff421 | |||
| 09e81620a1 | |||
| 7e16c7bb74 | |||
| fe953fc967 | |||
| 453fd2225c | |||
| 70fd54d831 | |||
| dcf7a2bc06 | |||
| 118af0170a | |||
| 0aa8089a9a | |||
| fef5f3b396 | |||
| 1838b90ac9 | |||
| 73fa2be91f | |||
| 52183abd08 | |||
| e19266d4cc | |||
| f458d7b674 | |||
| 29d4d6ec25 | |||
| 03f68213e8 | |||
| 66fa1ba840 | |||
| 89b2abc10e | |||
| b04408219d | |||
| 5ef2345ea6 | |||
| 50b5ca03c6 | |||
| 9862e112eb | |||
| 5dc1518cfc | |||
| fbd18e4c2e | |||
| 0e5943864d | |||
| 46cc61fc25 | |||
| 786b0bc949 | |||
| 8aa809c37e | |||
| fdd69a687b | |||
| 4c1ab796e4 | |||
| c1a3590b2b | |||
| c9ce2a9dc7 | |||
| 8701815288 | |||
| d03b585372 | |||
| c9879be72c | |||
| ec8abf44b8 | |||
| 776e0257fb | |||
| 8f1f1f10d9 | |||
| cba3f1f4bc | |||
| 316b1e8318 | |||
| 78a6cc417c | |||
| 564d612e9a | |||
| 65bf9a75f0 | |||
| 33ceb13cde | |||
| 7765e6d27a | |||
| ca402dbea7 | |||
| 07ae50c40a | |||
| 42dbd940a0 | |||
| 0f768f3e4c | |||
| 5fce37f269 | |||
| 52bdcd0990 | |||
| 1655f47044 | |||
| c07a57a591 | |||
| 0cf1aea9bf | |||
| 235de4e5ff | |||
| 869093ab25 | |||
| 189aa8d549 | |||
| 97b07fb424 | |||
| 9c88adbabd | |||
| ed0873837b | |||
| 6988365473 | |||
| 4762275b5b | |||
| 97c1e47db2 | |||
| bb637dd96a | |||
| 9fc21f5459 | |||
| d9deb4836e | |||
| 79feed54dd | |||
| 2e8215eafc | |||
| 4c00095118 | |||
| bc772f168f | |||
| 8d93fc8b3c | |||
| 66dcff8f46 | |||
| c61b31cc58 | |||
| 78604301c3 | |||
| d382a89905 | |||
| 7446bd506a | |||
| c25cf64f4b | |||
| c1af949204 | |||
| 7985ea67e5 | |||
| 9a3a308841 | |||
| d83ba2d54d | |||
| 7b4066b928 | |||
| 9fb2262107 | |||
| a27099177d | |||
| b0aec9bea0 | |||
| a17bd11e9e | |||
| 8cf79a791e | |||
| bf4e01e318 | |||
| d29bb4467a | |||
| 0ecf0c205f | |||
| 55aa512aa5 | |||
| addb3cddff | |||
| f7b6cc4eb3 | |||
| 28b9ac4e54 | |||
| 02879291e8 | |||
| 066b01e86f | |||
| c85401aa86 | |||
| 34a7310944 | |||
| e8716a1e7f | |||
| f8ecd69582 | |||
| 99438b1ff9 | |||
| ec0115d613 | |||
| 55bfbb619d | |||
| edf0c00cf1 | |||
| dcf35eaccd | |||
| 905330a722 | |||
| 5979337bc3 | |||
| 8c228f666f | |||
| 678bdf7087 | |||
| 507698c7ea | |||
| fdd4742b28 | |||
| 0bbe37c6d5 | |||
| 192411a69a | |||
| 929d248841 | |||
| 8ead83941f | |||
| aeaab860b2 | |||
| 16eadc0921 | |||
| f2445f3b25 | |||
| bb212f9c6c | |||
| 89c15eb1cf | |||
| a05b5edc86 | |||
| 5704eb9e07 | |||
| 3580c33891 | |||
| 3b3a71f6eb | |||
| d479cf6bca | |||
| b54a40cef4 | |||
| 1c644768e6 | |||
| aa95de7a6a | |||
| dd6433306d | |||
| bfc0b7e796 | |||
| 81e14f1a84 | |||
| d9f19a9c23 | |||
| 1e5598cb91 | |||
| f34407962b | |||
| 49a03a57cb | |||
| c1b8b508ac | |||
| c92d0b5640 | |||
| 73e96c0c46 | |||
| fac27d0725 | |||
| 97ccb6ee51 | |||
| 93aa47c6cc | |||
| 667339979b | |||
| 3ba4a195ba | |||
| 6a32895571 | |||
| 6cdc526335 | |||
| a79e1ec086 | |||
| 4b435b925e | |||
| 78e01ba981 | |||
| 56818189ae | |||
| fbea05218b | |||
| c78dfc0f7f | |||
| 425d01c0df | |||
| 64fa0912b8 | |||
| ba673943d8 | |||
| c196bfadbc | |||
| e9f56412c5 | |||
| d12c9c2166 | |||
| 55cd97078a | |||
| d6840e8064 | |||
| 308a284280 | |||
| 1b6549eae3 | |||
| b028ff05b9 | |||
| c380ba47a5 | |||
| ecda29abe5 | |||
| bec73561e7 | |||
| 299495a03c | |||
| d87b433398 | |||
| b61fc3c7d1 | |||
| b421b03575 | |||
| 128510fe88 | |||
| ce90884758 | |||
| 42440c0041 | |||
| 459397f8e7 | |||
| 7c106f7cf8 | |||
| caadc2a969 | |||
| 91ab800272 | |||
| 64f74d0449 | |||
| 6a8c5bf717 | |||
| a1c1accc87 | |||
| 03401f711e | |||
| 74923891d0 | |||
| 317e029f79 | |||
| 46ee98c97f | |||
| 269bf09c66 | |||
| 9d6ad08834 | |||
| 06b1296192 | |||
| 5def84282d | |||
| 24d8f1d58b | |||
| a3b44588a9 | |||
| 16feab9655 | |||
| 01c3d85a17 | |||
| 3d319605de | |||
| 63c4568aeb | |||
| e2d1b697b7 | |||
| 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
|
||||||
20
.env
20
.env
@@ -1,13 +1,29 @@
|
|||||||
NAME=dc-project
|
APP_NAME=dc-project
|
||||||
|
|
||||||
DATABASE_URL=jdbc:postgresql:dc-project
|
DATABASE_URL=jdbc:postgresql:dc-project
|
||||||
|
|
||||||
|
APP_PORT=8080
|
||||||
|
OPENAPI_PORT=8181
|
||||||
|
SONARQUBE_PORT=9002
|
||||||
|
SONARQUBE_DB_PORT=5434
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
SEND_GRID_KEY=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
|
||||||
134
.github/workflows/tests.yml
vendored
Normal file
134
.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# This workflow will build a Java project with Gradle
|
||||||
|
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
|
||||||
|
|
||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 11
|
||||||
|
|
||||||
|
- name: Cache Gradle packages
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.gradle/caches
|
||||||
|
~/.gradle/wrapper
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-gradle-
|
||||||
|
- name: Build
|
||||||
|
uses: eskatos/gradle-command-action@v1
|
||||||
|
with:
|
||||||
|
gradle-version: 6.8
|
||||||
|
arguments: build -x test -x ktlintKotlinScriptCheck -x ktlintTestSourceSetCheck -x ktlintMainSourceSetCheck -x detekt
|
||||||
|
- name: Cleanup Gradle Cache
|
||||||
|
# Remove some files from the Gradle cache, so they aren't cached by GitHub Actions.
|
||||||
|
# Restoring these files from a GitHub Actions cache might cause problems for future builds.
|
||||||
|
run: |
|
||||||
|
rm -f ~/.gradle/caches/modules-2/modules-2.lock
|
||||||
|
rm -f ~/.gradle/caches/modules-2/gc.properties
|
||||||
|
|
||||||
|
- name: processResources
|
||||||
|
uses: eskatos/gradle-command-action@v1
|
||||||
|
with:
|
||||||
|
gradle-version: 6.8
|
||||||
|
arguments: processResources
|
||||||
|
- name: processTestResources
|
||||||
|
uses: eskatos/gradle-command-action@v1
|
||||||
|
with:
|
||||||
|
gradle-version: 6.8
|
||||||
|
arguments: processResources
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: Build
|
||||||
|
path: build
|
||||||
|
|
||||||
|
testSql:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 11
|
||||||
|
- uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: Build
|
||||||
|
path: build
|
||||||
|
- name: TestSql
|
||||||
|
uses: eskatos/gradle-command-action@v1
|
||||||
|
with:
|
||||||
|
gradle-version: 6.8
|
||||||
|
arguments: testSql
|
||||||
|
|
||||||
|
test:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 11
|
||||||
|
- uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: Build
|
||||||
|
path: build
|
||||||
|
- name: Test
|
||||||
|
uses: eskatos/gradle-command-action@v1
|
||||||
|
with:
|
||||||
|
gradle-version: 6.8
|
||||||
|
arguments: test -x testSql
|
||||||
|
- name: Coverage
|
||||||
|
uses: eskatos/gradle-command-action@v1
|
||||||
|
with:
|
||||||
|
gradle-version: 6.8
|
||||||
|
arguments: coveralls
|
||||||
|
env:
|
||||||
|
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
|
||||||
|
- name: Cache SonarCloud packages
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ~/.sonar/cache
|
||||||
|
key: ${{ runner.os }}-sonar
|
||||||
|
restore-keys: ${{ runner.os }}-sonar
|
||||||
|
- name: Build and analyze
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
run: ./gradlew build sonarqube --info
|
||||||
|
|
||||||
|
lint:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK 11
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 11
|
||||||
|
- uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: Build
|
||||||
|
path: build
|
||||||
|
- name: Lint
|
||||||
|
uses: eskatos/gradle-command-action@v1
|
||||||
|
with:
|
||||||
|
gradle-version: 6.8
|
||||||
|
arguments: ktlintCheck
|
||||||
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
|
||||||
73
.idea/codeStyles/Project.xml
generated
73
.idea/codeStyles/Project.xml
generated
@@ -1,16 +1,79 @@
|
|||||||
<component name="ProjectCodeStyleConfiguration">
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
<code_scheme name="Project" version="173">
|
<code_scheme name="Project" version="173">
|
||||||
<JetCodeStyleSettings>
|
<JetCodeStyleSettings>
|
||||||
|
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||||
|
<value />
|
||||||
|
</option>
|
||||||
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||||
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
</JetCodeStyleSettings>
|
</JetCodeStyleSettings>
|
||||||
<PostgresCodeStyleSettings version="2">
|
<SqlCodeStyleSettings version="5">
|
||||||
<option name="ALIAS_CASE" value="1" />
|
|
||||||
<option name="KEYWORD_CASE" value="1" />
|
<option name="KEYWORD_CASE" value="1" />
|
||||||
<option name="TYPE_CASE" value="1" />
|
|
||||||
<option name="IDENTIFIER_CASE" value="1" />
|
<option name="IDENTIFIER_CASE" value="1" />
|
||||||
</PostgresCodeStyleSettings>
|
<option name="TYPE_CASE" value="4" />
|
||||||
|
<option name="ALIAS_CASE" value="4" />
|
||||||
|
<option name="BUILT_IN_CASE" value="4" />
|
||||||
|
<option name="QUOTE_IDENTIFIER" value="1" />
|
||||||
|
<option name="QUERY_EL_LINE" value="0" />
|
||||||
|
<option name="QUERY_IN_ONE_STRING" value="3" />
|
||||||
|
<option name="SUBQUERY_CONTENT" value="2" />
|
||||||
|
<option name="SUBQUERY_CLOSING" value="2" />
|
||||||
|
<option name="INSERT_OPENING" value="4" />
|
||||||
|
<option name="INSERT_CLOSING" value="0" />
|
||||||
|
<option name="INSERT_VALUES_EL_LINE" value="101" />
|
||||||
|
<option name="INSERT_COLLAPSE_MULTI_ROW_VALUES" value="true" />
|
||||||
|
<option name="SET_EL_LINE" value="1" />
|
||||||
|
<option name="SET_EL_WRAP" value="0" />
|
||||||
|
<option name="WITH_EL_LINE" value="101" />
|
||||||
|
<option name="WITH_EL_WRAP" value="2" />
|
||||||
|
<option name="SELECT_EL_LINE" value="101" />
|
||||||
|
<option name="SELECT_EL_COMMA" value="2" />
|
||||||
|
<option name="SELECT_NEW_LINE_AFTER_ALL_DISTINCT" value="true" />
|
||||||
|
<option name="SELECT_KEEP_N_ITEMS_IN_LINE" value="4" />
|
||||||
|
<option name="SELECT_USE_AS_WORD" value="1" />
|
||||||
|
<option name="FROM_EL_LINE" value="1" />
|
||||||
|
<option name="FROM_EL_COMMA" value="2" />
|
||||||
|
<option name="FROM_ALIGN_JOIN_TABLES" value="true" />
|
||||||
|
<option name="FROM_INDENT_JOIN" value="false" />
|
||||||
|
<option name="FROM_PLACE_ON" value="10" />
|
||||||
|
<option name="WHERE_EL_LINE" value="1" />
|
||||||
|
<option name="WHERE_EL_WRAP" value="3" />
|
||||||
|
<option name="ORDER_EL_LINE" value="101" />
|
||||||
|
<option name="ORDER_EL_WRAP" value="1" />
|
||||||
|
<option name="ORDER_EL_COMMA" value="2" />
|
||||||
|
</SqlCodeStyleSettings>
|
||||||
|
<codeStyleSettings language="SQL">
|
||||||
|
<option name="KEEP_LINE_BREAKS" value="false" />
|
||||||
|
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="SMART_TABS" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
<codeStyleSettings language="kotlin">
|
<codeStyleSettings language="kotlin">
|
||||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||||
|
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||||
|
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||||
|
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||||
|
<option name="CALL_PARAMETERS_WRAP" value="5" />
|
||||||
|
<option name="CALL_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="CALL_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="METHOD_PARAMETERS_WRAP" value="5" />
|
||||||
|
<option name="METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||||
|
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
|
||||||
|
<option name="ASSIGNMENT_WRAP" value="1" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
</codeStyleSettings>
|
</codeStyleSettings>
|
||||||
</code_scheme>
|
</code_scheme>
|
||||||
</component>
|
</component>
|
||||||
2
.idea/codeStyles/codeStyleConfig.xml
generated
2
.idea/codeStyles/codeStyleConfig.xml
generated
@@ -2,4 +2,4 @@
|
|||||||
<state>
|
<state>
|
||||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
</state>
|
</state>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
13
.idea/dataSources.xml
generated
13
.idea/dataSources.xml
generated
@@ -7,5 +7,18 @@
|
|||||||
<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:15432/test</jdbc-url>
|
||||||
|
</data-source>
|
||||||
|
<data-source source="LOCAL" name="sonar@localhost" uuid="ee78beab-120d-4740-ad21-d4d9e2121d25">
|
||||||
|
<driver-ref>postgresql</driver-ref>
|
||||||
|
<synchronize>true</synchronize>
|
||||||
|
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||||
|
<jdbc-url>jdbc:postgresql://localhost:5433/sonar</jdbc-url>
|
||||||
|
<working-dir>$ProjectFileDir$</working-dir>
|
||||||
|
</data-source>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
4
.idea/gradle.xml
generated
4
.idea/gradle.xml
generated
@@ -5,16 +5,14 @@
|
|||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="delegatedBuild" value="true" />
|
<option name="delegatedBuild" value="true" />
|
||||||
<option name="testRunner" value="PLATFORM" />
|
<option name="testRunner" value="GRADLE" />
|
||||||
<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$" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="useQualifiedModuleNames" value="true" />
|
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
11
.idea/misc.xml
generated
11
.idea/misc.xml
generated
@@ -1,7 +1,16 @@
|
|||||||
<?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>
|
||||||
|
<component name="TaskProjectConfiguration">
|
||||||
|
<server type="GitHub" url="https://github.com" />
|
||||||
|
</component>
|
||||||
</project>
|
</project>
|
||||||
31
.idea/runConfigurations/All_Tests.xml
generated
31
.idea/runConfigurations/All_Tests.xml
generated
@@ -1,22 +1,37 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="All Tests" type="JUnit" factoryName="JUnit" show_console_on_std_err="true">
|
<configuration default="false" name="All Tests" type="JUnit" factoryName="JUnit" singleton="false" show_console_on_std_err="true">
|
||||||
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
<useClassPathOnly />
|
||||||
<log_file alias="test.log" path="$PROJECT_DIR$/var/log/test" />
|
<extension name="coverage">
|
||||||
<module name="dcproject.test" />
|
<pattern>
|
||||||
|
<option name="PATTERN" value="fr.dcproject.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH" value="11" />
|
<option name="ALTERNATIVE_JRE_PATH" value="corretto-11" />
|
||||||
<option name="PACKAGE_NAME" value="fr.dcproject" />
|
<option name="PACKAGE_NAME" value="" />
|
||||||
<option name="MAIN_CLASS_NAME" value="" />
|
<option name="MAIN_CLASS_NAME" value="" />
|
||||||
<option name="METHOD_NAME" value="" />
|
<option name="METHOD_NAME" value="" />
|
||||||
<option name="TEST_OBJECT" value="directory" />
|
<option name="TEST_OBJECT" value="pattern" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea -Djdk.attach.allowAttachSelf=true" />
|
||||||
<option name="PARAMETERS" value="" />
|
<option name="PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
|
||||||
<option name="TEST_SEARCH_SCOPE">
|
<option name="TEST_SEARCH_SCOPE">
|
||||||
<value defaultName="wholeProject" />
|
<value defaultName="wholeProject" />
|
||||||
</option>
|
</option>
|
||||||
|
<envs>
|
||||||
|
<env name="SEND_GRID_KEY" value="$SEND_GRID_KEY$" />
|
||||||
|
</envs>
|
||||||
<dir value="$PROJECT_DIR$" />
|
<dir value="$PROJECT_DIR$" />
|
||||||
|
<patterns>
|
||||||
|
<pattern testClass="unit..*" />
|
||||||
|
<pattern testClass="functional..*" />
|
||||||
|
<pattern testClass="integration..*" />
|
||||||
|
</patterns>
|
||||||
|
<tag value="!functional" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
||||||
|
<option name="RunConfigurationTask" enabled="true" run_configuration_name="SQL Fixtures on DEV" run_configuration_type="ShConfigurationType" />
|
||||||
|
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Lint Format" run_configuration_type="GradleRunConfiguration" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</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>
|
||||||
38
.idea/runConfigurations/Build_and_start_all_Docker.xml
generated
Normal file
38
.idea/runConfigurations/Build_and_start_all_Docker.xml
generated
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<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="envFilePath" value="" />
|
||||||
|
<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="secondarySourceFiles">
|
||||||
|
<list>
|
||||||
|
<option value="docker-compose-sonar.yml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="services">
|
||||||
|
<list>
|
||||||
|
<option value="app" />
|
||||||
|
<option value="db" />
|
||||||
|
<option value="elasticsearch" />
|
||||||
|
<option value="openapi" />
|
||||||
|
<option value="rabbitmq" />
|
||||||
|
<option value="redis" />
|
||||||
|
<option value="sonarqube" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||||
|
<option name="upExitCodeFromService" value="" />
|
||||||
|
<option name="upTimeout" value="" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</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>
|
||||||
11
.idea/runConfigurations/Docker.xml
generated
11
.idea/runConfigurations/Docker.xml
generated
@@ -1,11 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="Docker" type="docker-deploy" factoryName="docker-compose.yml" server-name="Docker">
|
|
||||||
<deployment type="docker-compose.yml">
|
|
||||||
<settings>
|
|
||||||
<option name="commandLineOptions" value="--build" />
|
|
||||||
<option name="sourceFilePath" value="docker-compose.yml" />
|
|
||||||
</settings>
|
|
||||||
</deployment>
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
23
.idea/runConfigurations/Down_Docker.xml
generated
Normal file
23
.idea/runConfigurations/Down_Docker.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Down Docker" 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="testComposeDownForced" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
23
.idea/runConfigurations/Functional_Tests.xml
generated
Normal file
23
.idea/runConfigurations/Functional_Tests.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Functional 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 />
|
||||||
|
<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="functional" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
23
.idea/runConfigurations/Functional_Tests__offline_.xml
generated
Normal file
23
.idea/runConfigurations/Functional_Tests__offline_.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Functional 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" />
|
||||||
|
<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 -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="functional&!online" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
27
.idea/runConfigurations/Integration_Tests.xml
generated
Normal file
27
.idea/runConfigurations/Integration_Tests.xml
generated
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Integration Tests" type="JUnit" factoryName="JUnit" singleton="false" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH" value="corretto-11" />
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="pattern" />
|
||||||
|
<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$" />
|
||||||
|
<patterns>
|
||||||
|
<pattern testClass="integration..*" />
|
||||||
|
</patterns>
|
||||||
|
<tag value="!functional" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
23
.idea/runConfigurations/Lint_Format.xml
generated
Normal file
23
.idea/runConfigurations/Lint_Format.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Lint Format" type="GradleRunConfiguration" factoryName="Gradle" singleton="true">
|
||||||
|
<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="ktlintFormat" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
23
.idea/runConfigurations/Migration.xml
generated
Normal file
23
.idea/runConfigurations/Migration.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Migration" 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="migration" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
15
.idea/runConfigurations/Reset_DB_on_DEV.xml
generated
Normal file
15
.idea/runConfigurations/Reset_DB_on_DEV.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<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="$PROJECT_DIR$/../../../../Program Files/Git/bin/bash.exe" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="false" />
|
||||||
|
<envs />
|
||||||
|
<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" />
|
||||||
|
|||||||
15
.idea/runConfigurations/RunCucumberTest.xml
generated
15
.idea/runConfigurations/RunCucumberTest.xml
generated
@@ -1,15 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="RunCucumberTest" type="JUnit" factoryName="JUnit" nameIsGenerated="true">
|
|
||||||
<output_file path="$PROJECT_DIR$/var/log/test/cucumber.out.log" is_save="true" />
|
|
||||||
<log_file alias="cucumber.log" path="$PROJECT_DIR$/var/log/test" />
|
|
||||||
<module name="dcproject.test" />
|
|
||||||
<option name="PACKAGE_NAME" value="" />
|
|
||||||
<option name="MAIN_CLASS_NAME" value="RunCucumberTest" />
|
|
||||||
<option name="METHOD_NAME" value="testArticle" />
|
|
||||||
<option name="TEST_OBJECT" value="class" />
|
|
||||||
<option name="PARAMETERS" value="" />
|
|
||||||
<method v="2">
|
|
||||||
<option name="Make" enabled="true" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
38
.idea/runConfigurations/Run_dependencies.xml
generated
Normal file
38
.idea/runConfigurations/Run_dependencies.xml
generated
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<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="envFilePath" value="" />
|
||||||
|
<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="secondarySourceFiles">
|
||||||
|
<list>
|
||||||
|
<option value="docker-compose-sonar.yml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="services">
|
||||||
|
<list>
|
||||||
|
<option value="db" />
|
||||||
|
<option value="elasticsearch" />
|
||||||
|
<option value="rabbitmq" />
|
||||||
|
<option value="redis" />
|
||||||
|
<option value="openapi" />
|
||||||
|
<option value="sonarqube" />
|
||||||
|
<option value="sonarqube_db" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="sourceFilePath" value="docker-compose.yml" />
|
||||||
|
<option name="upExitCodeFromService" value="" />
|
||||||
|
<option name="upTimeout" value="" />
|
||||||
|
</settings>
|
||||||
|
</deployment>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
15
.idea/runConfigurations/SQL_Fixtures_on_DEV.xml
generated
Normal file
15
.idea/runConfigurations/SQL_Fixtures_on_DEV.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<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="$PROJECT_DIR$/../../../../Program Files/Git/bin/bash.exe" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="false" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
28
.idea/runConfigurations/Sonarqube.xml
generated
Normal file
28
.idea/runConfigurations/Sonarqube.xml
generated
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Sonarqube" type="GradleRunConfiguration" factoryName="Gradle">
|
||||||
|
<ExternalSystemSettings>
|
||||||
|
<option name="env">
|
||||||
|
<map>
|
||||||
|
<entry key="SONAR_TOKEN" value="15ad34f46763706727d884ced12c48d5222fe639" />
|
||||||
|
</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="sonarqube" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
23
.idea/runConfigurations/Sonarqube_without_test.xml
generated
Normal file
23
.idea/runConfigurations/Sonarqube_without_test.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Sonarqube 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="sonarqube" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<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_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>
|
||||||
28
.idea/runConfigurations/Test.xml
generated
Normal file
28
.idea/runConfigurations/Test.xml
generated
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Test" 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="test" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
23
.idea/runConfigurations/TestSql.xml
generated
Normal file
23
.idea/runConfigurations/TestSql.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="TestSql" 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="testSql" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="vmOptions" value="" />
|
||||||
|
</ExternalSystemSettings>
|
||||||
|
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||||
|
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||||
|
<DebugAllEnabled>false</DebugAllEnabled>
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
17
.idea/runConfigurations/Test_All_SQL.xml
generated
Normal file
17
.idea/runConfigurations/Test_All_SQL.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Test All SQL" type="ShConfigurationType" singleton="false">
|
||||||
|
<option name="SCRIPT_TEXT" value="" />
|
||||||
|
<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="$PROJECT_DIR$/../../../../Program Files/Git/bin/bash.exe" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="EXECUTE_IN_TERMINAL" value="false" />
|
||||||
|
<option name="EXECUTE_SCRIPT_FILE" value="true" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
23
.idea/runConfigurations/Unit_Tests.xml
generated
Normal file
23
.idea/runConfigurations/Unit_Tests.xml
generated
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Unit 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 />
|
||||||
|
<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="unit" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
30
.idea/runConfigurations/Unit__functional_and_integration_tests.xml
generated
Normal file
30
.idea/runConfigurations/Unit__functional_and_integration_tests.xml
generated
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Unit, functional and integration tests" type="JUnit" factoryName="JUnit" singleton="false" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH" value="corretto-11" />
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="pattern" />
|
||||||
|
<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$" />
|
||||||
|
<patterns>
|
||||||
|
<pattern testClass="unit..*" />
|
||||||
|
<pattern testClass="functional..*" />
|
||||||
|
<pattern testClass="integration..*" />
|
||||||
|
</patterns>
|
||||||
|
<tag value="!functional" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
28
.idea/runConfigurations/Unit_and_functional_tests.xml
generated
Normal file
28
.idea/runConfigurations/Unit_and_functional_tests.xml
generated
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Unit and functional tests" type="JUnit" factoryName="JUnit" singleton="false" show_console_on_std_err="true">
|
||||||
|
<output_file path="$PROJECT_DIR$/var/log/test/out.log" is_save="true" />
|
||||||
|
<useClassPathOnly />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH" value="corretto-11" />
|
||||||
|
<option name="PACKAGE_NAME" value="" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="" />
|
||||||
|
<option name="METHOD_NAME" value="" />
|
||||||
|
<option name="TEST_OBJECT" value="pattern" />
|
||||||
|
<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$" />
|
||||||
|
<patterns>
|
||||||
|
<pattern testClass="unit..*" />
|
||||||
|
<pattern testClass="functional..*" />
|
||||||
|
</patterns>
|
||||||
|
<tag value="!functional" />
|
||||||
|
<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>
|
|
||||||
59
Makefile
Normal file
59
Makefile
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
.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 (alias: bd)
|
||||||
|
docker build -t dc-project -f docker/app/Dockerfile .
|
||||||
|
|
||||||
|
pd: publish-docker
|
||||||
|
|
||||||
|
publish-docker: build-docker ## Publish docker image of application to Github (alias: pd)
|
||||||
|
@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 (alias: rd)
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
rdd: run-docker-dependencies
|
||||||
|
|
||||||
|
run-docker-dependencies: ## Build and Run dependencies docker services (alias: rdd)
|
||||||
|
docker-compose up -d --build openapi rabbitmq redis elasticsearch db sonarqube_db sonarqube
|
||||||
|
|
||||||
|
pm: publish-maven
|
||||||
|
|
||||||
|
publish-maven: ## Publish JAR file to Github (alias: pm)
|
||||||
|
@git diff --quiet --exit-code || (echo "The git is DIRTY !!! You cannot publish this crap!" && exit 1)
|
||||||
|
gradlew publish
|
||||||
|
|
||||||
|
f: fixtures
|
||||||
|
|
||||||
|
fixtures: ## Import fixtures (alias: f)
|
||||||
|
bash src/main/resources/sql/fixtures/fixtures.sh
|
||||||
|
|
||||||
|
reset-database: ## Reset database !!!
|
||||||
|
cd src/main/resources/sql/ ; bash resetDB.sh
|
||||||
|
|
||||||
|
test-sql: ## Test sql
|
||||||
|
cd src/test/sql/ ; bash test.sh 1
|
||||||
|
|
||||||
|
v: version
|
||||||
|
|
||||||
|
version: ## Show current version (alias: v)
|
||||||
|
@echo ${VERSION}
|
||||||
37
README.md
Normal file
37
README.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# DC Project
|
||||||
|
|
||||||
|
[](https://www.codefactor.io/repository/github/flecomte/dc-project)
|
||||||
|
[](https://www.codacy.com/gh/flecomte/dc-project/dashboard?utm_source=github.com&utm_medium=referral&utm_content=flecomte/dc-project&utm_campaign=Badge_Grade)
|
||||||
|
[](https://sonarcloud.io/dashboard?id=dc-project)
|
||||||
|
|
||||||
|
[](https://github.com/flecomte/dc-project/actions/workflows/tests.yml)
|
||||||
|
[](https://coveralls.io/github/flecomte/dc-project?branch=master)
|
||||||
|
[](https://sonarcloud.io/dashboard?id=dc-project)
|
||||||
|
|
||||||
|
[](https://sonarcloud.io/dashboard?id=dc-project)
|
||||||
|
|
||||||
|
[Installation](./doc/installation/Installation.md)
|
||||||
|
|
||||||
|
### 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
|
||||||
|
```
|
||||||
392
build.gradle.kts
392
build.gradle.kts
@@ -1,49 +1,377 @@
|
|||||||
val ktor_version: String by project
|
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||||
val kotlin_version: String by project
|
import com.typesafe.config.ConfigFactory
|
||||||
val logback_version: String by project
|
import fr.postgresjson.connexion.Connection
|
||||||
val koinVersion: String by project
|
import fr.postgresjson.connexion.Requester
|
||||||
val postgresjson_version: String by project
|
import fr.postgresjson.migration.Migrations
|
||||||
|
import io.gitlab.arturbosch.detekt.Detekt
|
||||||
|
import org.gradle.internal.os.OperatingSystem
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
import org.owasp.dependencycheck.reporting.ReportGenerator
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
plugins {
|
val ktorVersion = "1.5.0"
|
||||||
application
|
val kotlinVersion = "1.4.30"
|
||||||
kotlin("jvm") version "1.3.40"
|
val coroutinesVersion = "1.4.3"
|
||||||
|
val logbackVersion = "1.2.3"
|
||||||
|
val koinVersion = "2.0.1"
|
||||||
|
val jacksonVersion = "2.12.1"
|
||||||
|
|
||||||
|
group = "com.github.flecomte"
|
||||||
|
version = versioning.info.run {
|
||||||
|
if (dirty) {
|
||||||
|
versioning.info.full
|
||||||
|
} else {
|
||||||
|
versioning.info.lastTag
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "fr.dcproject"
|
plugins {
|
||||||
version = "0.0.1"
|
jacoco
|
||||||
|
application
|
||||||
|
maven
|
||||||
|
|
||||||
|
id("maven-publish")
|
||||||
|
kotlin("jvm") version "1.4.30"
|
||||||
|
kotlin("plugin.serialization") version "1.4.30"
|
||||||
|
|
||||||
|
id("com.github.johnrengelman.shadow") version "5.2.0"
|
||||||
|
id("org.jlleitschuh.gradle.ktlint") version "9.4.1"
|
||||||
|
id("org.owasp.dependencycheck") version "6.1.1"
|
||||||
|
id("org.sonarqube") version "3.1.1"
|
||||||
|
id("net.nemerosa.versioning") version "2.14.0"
|
||||||
|
id("io.gitlab.arturbosch.detekt") version "1.16.0-RC1"
|
||||||
|
id("com.avast.gradle.docker-compose") version "0.14.0"
|
||||||
|
id("com.github.kt3k.coveralls") version "2.8.4"
|
||||||
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClassName = "io.ktor.server.netty.EngineMain"
|
mainClassName = "io.ktor.server.jetty.EngineMain"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
jcenter()
|
||||||
|
maven { url = uri("https://jitpack.io") }
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath("com.typesafe:config:1.4.1")
|
||||||
|
classpath("com.github.flecomte:postgres-json:2.1.2")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.distZip.configure { enabled = false }
|
||||||
|
tasks.distTar.configure { enabled = false }
|
||||||
|
|
||||||
|
tasks.withType<KotlinCompile> {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val migration by tasks.registering {
|
||||||
|
group = "application"
|
||||||
|
dependsOn(tasks.named("composeUp"))
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
val config = ConfigFactory.parseFile(file("$buildDir/resources/main/application.conf")).resolve()
|
||||||
|
val connection = Connection(
|
||||||
|
host = config.getString("db.host"),
|
||||||
|
port = config.getInt("db.port"),
|
||||||
|
database = config.getString("db.database"),
|
||||||
|
username = config.getString("db.username"),
|
||||||
|
password = config.getString("db.password")
|
||||||
|
)
|
||||||
|
Migrations(
|
||||||
|
connection,
|
||||||
|
file("$buildDir/resources/main/sql/migrations").toURI(),
|
||||||
|
file("$buildDir/resources/main/sql/functions").toURI()
|
||||||
|
).run {
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val migrationTest by tasks.registering {
|
||||||
|
group = "verification"
|
||||||
|
dependsOn(tasks.named("testComposeUp"))
|
||||||
|
finalizedBy(tasks.named("testComposeDown"))
|
||||||
|
doLast {
|
||||||
|
val config = ConfigFactory.parseFile(file("$buildDir/resources/test/application-test.conf")).resolve()
|
||||||
|
val connection = Connection(
|
||||||
|
host = config.getString("db.host"),
|
||||||
|
port = config.getInt("db.port"),
|
||||||
|
database = config.getString("db.database"),
|
||||||
|
username = config.getString("db.username"),
|
||||||
|
password = config.getString("db.password")
|
||||||
|
)
|
||||||
|
Migrations(
|
||||||
|
connection,
|
||||||
|
file("$buildDir/resources/main/sql/migrations").toURI(),
|
||||||
|
file("$buildDir/resources/main/sql/functions").toURI()
|
||||||
|
).run {
|
||||||
|
run()
|
||||||
|
connection.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val testSql by tasks.registering {
|
||||||
|
group = "verification"
|
||||||
|
dependsOn(tasks.named("processResources"))
|
||||||
|
dependsOn(tasks.named("processTestResources"))
|
||||||
|
dependsOn(tasks.named("testSqlComposeUp"))
|
||||||
|
finalizedBy(tasks.named("testSqlComposeDown"))
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
val config = ConfigFactory.parseFile(file("$buildDir/resources/test/application-test.conf")).resolve()
|
||||||
|
|
||||||
|
val connection = Connection(
|
||||||
|
host = config.getString("db.host"),
|
||||||
|
port = config.getInt("db.port"),
|
||||||
|
database = config.getString("db.database"),
|
||||||
|
username = config.getString("db.username"),
|
||||||
|
password = config.getString("db.password")
|
||||||
|
)
|
||||||
|
|
||||||
|
Migrations(
|
||||||
|
connection,
|
||||||
|
file("$buildDir/resources/main/sql/migrations").toURI(),
|
||||||
|
file("$buildDir/resources/main/sql/functions").toURI(),
|
||||||
|
file("$buildDir/resources/test/sql/fixtures").toURI()
|
||||||
|
).run()
|
||||||
|
|
||||||
|
Requester.RequesterFactory(
|
||||||
|
connection = connection,
|
||||||
|
queriesDirectory = file("$buildDir/resources/test/sql").toURI()
|
||||||
|
).createRequester().run {
|
||||||
|
getQueries().map {
|
||||||
|
try {
|
||||||
|
it.sendQuery() == 0
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<Jar> {
|
||||||
|
manifest {
|
||||||
|
attributes(
|
||||||
|
mapOf(
|
||||||
|
"Main-Class" to application.mainClassName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<KotlinCompile> {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "11"
|
||||||
|
sourceCompatibility = "11"
|
||||||
|
targetCompatibility = "11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named<ShadowJar>("shadowJar") {
|
||||||
|
mergeServiceFiles("META-INF/services")
|
||||||
|
archiveFileName.set("${archiveBaseName.get()}-latest-all.${archiveExtension.get()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.sonarqube.configure {
|
||||||
|
dependsOn(tasks.test)
|
||||||
|
dependsOn(tasks.detekt)
|
||||||
|
dependsOn(tasks.jacocoTestReport)
|
||||||
|
}
|
||||||
|
|
||||||
|
val sourcesJar by tasks.registering(Jar::class) {
|
||||||
|
group = "build"
|
||||||
|
archiveClassifier.set("sources")
|
||||||
|
from(sourceSets.getByName("main").allSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.test {
|
||||||
|
useJUnit()
|
||||||
|
useJUnitPlatform()
|
||||||
|
systemProperty("junit.jupiter.execution.parallel.enabled", true)
|
||||||
|
dependsOn(testSql)
|
||||||
|
finalizedBy(tasks.jacocoTestReport) // report is always generated after tests run
|
||||||
|
}
|
||||||
|
|
||||||
|
coveralls {
|
||||||
|
sourceDirs.add("src/main/kotlin")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("testAll") {
|
||||||
|
group = "verification"
|
||||||
|
dependsOn(testSql)
|
||||||
|
dependsOn(tasks.test)
|
||||||
|
dependsOn(tasks.ktlintCheck)
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(plugin = "docker-compose")
|
||||||
|
dockerCompose {
|
||||||
|
projectName = "dc-project"
|
||||||
|
useComposeFiles = listOf("docker-compose.yml")
|
||||||
|
startedServices = listOf("db", "elasticsearch", "rabbitmq", "redis")
|
||||||
|
stopContainers = false
|
||||||
|
removeVolumes = false
|
||||||
|
removeContainers = false
|
||||||
|
isRequiredBy(project.tasks.run)
|
||||||
|
|
||||||
|
createNested("testSql").apply {
|
||||||
|
projectName = "dc-project_test"
|
||||||
|
useComposeFiles = listOf("docker-compose-test.yml")
|
||||||
|
startedServices = listOf("db", "elasticsearch")
|
||||||
|
stopContainers = false
|
||||||
|
isRequiredBy(project.tasks.named("testSql"))
|
||||||
|
}
|
||||||
|
|
||||||
|
createNested("test").apply {
|
||||||
|
projectName = "dc-project_test"
|
||||||
|
useComposeFiles = listOf("docker-compose-test.yml")
|
||||||
|
stopContainers = false
|
||||||
|
isRequiredBy(project.tasks.test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (${versioning.info.full})")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jacoco {
|
||||||
|
toolVersion = "0.8.6"
|
||||||
|
applyTo(tasks.run.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register<JacocoReport>("applicationCodeCoverageReport") {
|
||||||
|
executionData(tasks.run.get())
|
||||||
|
sourceSets(sourceSets.main.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.jacocoTestReport {
|
||||||
|
dependsOn(tasks.test)
|
||||||
|
reports {
|
||||||
|
xml.isEnabled = true
|
||||||
|
html.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
detekt {
|
||||||
|
buildUponDefaultConfig = true // preconfigure defaults
|
||||||
|
ignoreFailures = true
|
||||||
|
// config = files("$projectDir/config/detekt.yml") // point to your custom config defining rules to run, overwriting default behavior
|
||||||
|
// baseline = file("$projectDir/config/baseline.xml") // a way of suppressing issues before introducing detekt
|
||||||
|
|
||||||
|
reports {
|
||||||
|
html.enabled = true // observe findings in your browser with structure and code snippets
|
||||||
|
xml.enabled = true // checkstyle like format mainly for integrations like Jenkins
|
||||||
|
txt.enabled = true // similar to the console output, contains issue signature to manually edit baseline files
|
||||||
|
sarif.enabled = true // standardized SARIF format (https://sarifweb.azurewebsites.net/) to support integrations with Github Code Scanning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<Detekt> {
|
||||||
|
// Target version of the generated JVM bytecode. It is used for type resolution.
|
||||||
|
this.jvmTarget = "11"
|
||||||
|
ignoreFailures = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val setMaxMapCount = tasks.create<Exec>("setMaxMapCount") {
|
||||||
|
group = "docker"
|
||||||
|
doFirst {
|
||||||
|
if (OperatingSystem.current().isWindows) {
|
||||||
|
commandLine("cmd", "/c", "Powershell -ExecutionPolicy Bypass; wsl -d docker-desktop sysctl -w vm.max_map_count=262144")
|
||||||
|
} else if (OperatingSystem.current().isLinux) {
|
||||||
|
commandLine("sysctl -w vm.max_map_count=262144")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named("testComposeUp").configure {
|
||||||
|
if (OperatingSystem.current().isWindows) {
|
||||||
|
dependsOn(setMaxMapCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(gradleApi())
|
||||||
implementation("io.ktor:ktor-server-netty:$ktor_version")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
|
||||||
implementation("ch.qos.logback:logback-classic:$logback_version")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
|
||||||
implementation("io.ktor:ktor-server-core:$ktor_version")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$coroutinesVersion")
|
||||||
implementation("io.ktor:ktor-locations:$ktor_version")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1")
|
||||||
implementation("io.ktor:ktor-auth:$ktor_version")
|
implementation("io.ktor:ktor-server-jetty:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-auth-jwt:$ktor_version")
|
implementation("io.ktor:ktor-client-jetty:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-gson:$ktor_version")
|
implementation("ch.qos.logback:logback-classic:$logbackVersion")
|
||||||
|
implementation("io.ktor:ktor-server-core:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-locations:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-auth:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-auth-jwt:$ktorVersion")
|
||||||
|
implementation("io.ktor:ktor-websockets:$ktorVersion")
|
||||||
implementation("org.koin:koin-ktor:$koinVersion")
|
implementation("org.koin:koin-ktor:$koinVersion")
|
||||||
implementation("io.ktor:ktor-jackson:$ktor_version")
|
implementation("io.ktor:ktor-jackson:$ktorVersion")
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.9")
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
|
||||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.9.9")
|
implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:$jacksonVersion")
|
||||||
implementation("net.pearx.kasechange:kasechange-jvm:1.1.0")
|
implementation("net.pearx.kasechange:kasechange-jvm:1.3.0")
|
||||||
implementation("fr.postgresjson:postgresjson-jdbc:$postgresjson_version")
|
implementation("com.auth0:java-jwt:3.12.0")
|
||||||
testImplementation("io.ktor:ktor-server-tests:$ktor_version")
|
implementation("com.github.jasync-sql:jasync-postgresql:1.1.6")
|
||||||
testImplementation("io.ktor:ktor-client-mock:$ktor_version")
|
implementation("com.github.flecomte:postgres-json:2.1.2")
|
||||||
testImplementation("io.ktor:ktor-client-mock-jvm:$ktor_version")
|
implementation("com.sendgrid:sendgrid-java:4.7.1")
|
||||||
|
implementation("io.lettuce:lettuce-core:5.3.6.RELEASE") // TODO update to 6.0.2
|
||||||
|
implementation("com.rabbitmq:amqp-client:5.10.0")
|
||||||
|
implementation("org.elasticsearch.client:elasticsearch-rest-client:6.7.1")
|
||||||
|
implementation("com.jayway.jsonpath:json-path:2.5.0")
|
||||||
|
implementation("com.avast.gradle:gradle-docker-compose-plugin:0.14.0")
|
||||||
|
|
||||||
|
testImplementation("io.ktor:ktor-server-tests:$ktorVersion")
|
||||||
|
testImplementation("io.ktor:ktor-client-mock:$ktorVersion")
|
||||||
|
testImplementation("io.ktor:ktor-client-mock-jvm:$ktorVersion")
|
||||||
testImplementation("org.koin:koin-test:$koinVersion")
|
testImplementation("org.koin:koin-test:$koinVersion")
|
||||||
testImplementation("io.mockk:mockk:1.9")
|
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter:5.5.0")
|
testImplementation("io.mockk:mockk:1.10.6")
|
||||||
testImplementation("org.amshove.kluent:kluent:1.4")
|
testImplementation("org.junit.jupiter:junit-jupiter:5.7.0")
|
||||||
testImplementation("io.cucumber:cucumber-java8:4.3.1")
|
testImplementation("org.amshove.kluent:kluent:1.61")
|
||||||
testImplementation("io.cucumber:cucumber-junit:4.3.1")
|
testImplementation("io.mockk:mockk-agent-api:1.10.6")
|
||||||
|
testImplementation("io.mockk:mockk-agent-jvm:1.10.6")
|
||||||
|
testImplementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
|
||||||
|
testImplementation("com.thedeanda:lorem:2.1")
|
||||||
|
testImplementation("org.openapi4j:openapi-operation-validator:1.0.6")
|
||||||
|
testImplementation("org.openapi4j:openapi-parser:1.0.6")
|
||||||
}
|
}
|
||||||
|
|||||||
30
doc/CreateAction.md
Normal file
30
doc/CreateAction.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Create Action
|
||||||
|
============
|
||||||
|
|
||||||
|
* [ ] Create [OpenApi](../src/main/resources/openapi.yaml) documentation
|
||||||
|
* [ ] Create route
|
||||||
|
* [ ] Create request with [Location](https://ktor.io/docs/features-locations.html)
|
||||||
|
* [ ] Create Validation of request with [Konform](https://www.konform.io)
|
||||||
|
* [ ] Test validation
|
||||||
|
* [ ] [Check auth](../src/main/kotlin/fr/dcproject/component/auth/CitizenContext.kt) on protected route
|
||||||
|
* [ ] [Create test for auth](../src/test/kotlin/integration/steps/given/Auth.kt)
|
||||||
|
* [ ] Return must not be an Entity
|
||||||
|
* [ ] Tests request:
|
||||||
|
* [ ] Route with these params
|
||||||
|
* [ ] Body of the request
|
||||||
|
* [ ] Success
|
||||||
|
* [ ] BadRequest
|
||||||
|
* [ ] Body and request params must [match with the openapi schema](../src/test/kotlin/integration/steps/then/schema.kt)
|
||||||
|
* [ ] Create [AccessControl](../src/main/kotlin/fr/dcproject/common/security/AccessControlModule.kt)
|
||||||
|
* [ ] Test [AccessControl](../src/test/kotlin/integration/steps/given/Auth.kt)
|
||||||
|
|
||||||
|
|
||||||
|
* [ ] Create Entity
|
||||||
|
|
||||||
|
|
||||||
|
* [ ] Create Repository
|
||||||
|
* [ ] Create SQL function in file
|
||||||
|
* [ ] Create Tests SQL
|
||||||
|
|
||||||
|
* [ ] Tests
|
||||||
|
* [ ] Test BadRequest
|
||||||
20
doc/installation/Installation-linux.md
Normal file
20
doc/installation/Installation-linux.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Installation on Linux (debian)
|
||||||
|
=====================
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
```bash
|
||||||
|
apt-get update
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install docker
|
||||||
|
See: [docs.docker.com/get-docker](https://docs.docker.com/get-docker/)
|
||||||
|
```bash
|
||||||
|
apt-get install docker
|
||||||
|
apt-get install docker-compose
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install Git
|
||||||
|
See: [git-scm.com/downloads](https://git-scm.com/downloads)
|
||||||
|
```powershell
|
||||||
|
apt-get install git
|
||||||
|
```
|
||||||
50
doc/installation/Installation-windows.md
Normal file
50
doc/installation/Installation-windows.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
Installation on Windows
|
||||||
|
============
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
### Install chocolaty (optional)
|
||||||
|
First install Chocolaty => [https://chocolatey.org/install](https://chocolatey.org/install)
|
||||||
|
```powershell
|
||||||
|
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install make (optional)
|
||||||
|
Install make with chocolaty
|
||||||
|
```powershell
|
||||||
|
chocolatey install make
|
||||||
|
```
|
||||||
|
Install make for gitbash
|
||||||
|
[https://gist.github.com/evanwill/0207876c3243bbb6863e65ec5dc3f058#make]()
|
||||||
|
|
||||||
|
### Install awk (optional)
|
||||||
|
Install awk with chocolaty
|
||||||
|
```powershell
|
||||||
|
chocolatey install awk
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install docker
|
||||||
|
See: [docs.docker.com/get-docker](https://docs.docker.com/get-docker/)
|
||||||
|
```powershell
|
||||||
|
choco install docker-desktop
|
||||||
|
```
|
||||||
|
|
||||||
|
If you use docker with WSL2, you need to change config for elasticsearch:
|
||||||
|
```powershell
|
||||||
|
wsl -d docker-desktop
|
||||||
|
sysctl -w vm.max_map_count=262144
|
||||||
|
echo "vm.max_map_count = 262144" > /etc/sysctl.d/99-docker-desktop.conf
|
||||||
|
```
|
||||||
|
then restart docker-desktop
|
||||||
|
|
||||||
|
### Install Git
|
||||||
|
See: [git-scm.com/downloads](https://git-scm.com/downloads)
|
||||||
|
```powershell
|
||||||
|
choco install git
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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";
|
||||||
|
```
|
||||||
7
doc/installation/Installation.md
Normal file
7
doc/installation/Installation.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
1. [Installation on Windows](Installation-windows.md)
|
||||||
|
2. [Installation on Linux](Installation-linux.md)
|
||||||
|
3. [Problems in installations](Problems.md)
|
||||||
|
|
||||||
29
doc/installation/Problems.md
Normal file
29
doc/installation/Problems.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
Problems
|
||||||
|
========
|
||||||
|
|
||||||
|
1. [max_map_count](#max_map_count)
|
||||||
|
|
||||||
|
max_map_count
|
||||||
|
-------------
|
||||||
|
|
||||||
|
**Context**:
|
||||||
|
|
||||||
|
Elasticsearch in Docker on WSL2
|
||||||
|
|
||||||
|
**Error**:
|
||||||
|
```
|
||||||
|
max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Resolution**:
|
||||||
|
|
||||||
|
[Stackoverflow](https://stackoverflow.com/questions/42111566/elasticsearch-in-windows-docker-image-vm-max-map-count)
|
||||||
|
|
||||||
|
In Powershell:
|
||||||
|
```powershell
|
||||||
|
wsl -d docker-desktop
|
||||||
|
sysctl -w vm.max_map_count=262144
|
||||||
|
echo "vm.max_map_count = 262144" > /etc/sysctl.d/99-docker-desktop.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
restart docker-desktop
|
||||||
98
doc/schema/Article.puml
Normal file
98
doc/schema/Article.puml
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
title Search / Get articles
|
||||||
|
|
||||||
|
actor Front
|
||||||
|
box Article API
|
||||||
|
control Controller
|
||||||
|
control Repository
|
||||||
|
entity Article
|
||||||
|
database Postgres
|
||||||
|
endbox
|
||||||
|
box View System
|
||||||
|
control ArticleViewManager
|
||||||
|
database Elasticsearch
|
||||||
|
endbox
|
||||||
|
box Notification System
|
||||||
|
control EventNotification
|
||||||
|
database RabbitMQ
|
||||||
|
database Redis
|
||||||
|
endbox
|
||||||
|
|
||||||
|
Front -> Controller++: GET /articles?page=1
|
||||||
|
Controller -> Repository++: find
|
||||||
|
Repository -> Postgres++: find_articles()
|
||||||
|
return
|
||||||
|
return
|
||||||
|
return: 200, Articles
|
||||||
|
|
||||||
|
newpage Create / Update Article
|
||||||
|
|
||||||
|
Front -> Controller: POST /article
|
||||||
|
activate Controller
|
||||||
|
Controller -> Controller: Convert dto to Entity
|
||||||
|
Controller -> Controller: Check Authorization
|
||||||
|
alt Authorize
|
||||||
|
Controller -> Repository++: upsert(entity)
|
||||||
|
Repository -> Postgres++: upsert_article
|
||||||
|
return
|
||||||
|
return
|
||||||
|
Controller -> Controller: Convert to dto
|
||||||
|
Front <-- Controller: 200, New Article
|
||||||
|
else not authorize
|
||||||
|
Front <-- Controller: 403, "Forbidden"
|
||||||
|
end
|
||||||
|
Controller -> EventNotification: raiseEvent(ArticleUpdate)
|
||||||
|
deactivate Controller
|
||||||
|
activate EventNotification
|
||||||
|
EventNotification ->> RabbitMQ
|
||||||
|
deactivate EventNotification
|
||||||
|
...
|
||||||
|
RabbitMQ -->> EventNotification++
|
||||||
|
EventNotification ->> : Send Email
|
||||||
|
EventNotification ->> Redis : Push Event Notification
|
||||||
|
return <<ACK>>
|
||||||
|
|
||||||
|
newpage get one article by id
|
||||||
|
|
||||||
|
Front -> Controller: GET /article/{article}
|
||||||
|
activate Controller
|
||||||
|
Controller -> Repository++: findById()
|
||||||
|
Repository -> Postgres++: find_article_by_id()
|
||||||
|
return
|
||||||
|
return
|
||||||
|
Controller -> Controller: Check Authorization
|
||||||
|
|
||||||
|
alt Authorize
|
||||||
|
Controller -> ArticleViewManager++: getViewsCount(Article)
|
||||||
|
ArticleViewManager -> Elasticsearch++
|
||||||
|
return
|
||||||
|
return
|
||||||
|
Controller -> Controller: Convert Article and Views to dto
|
||||||
|
Front <<-- Controller: 200, Article
|
||||||
|
else not authorize
|
||||||
|
Front <<-- Controller: 403, "Forbidden"
|
||||||
|
end
|
||||||
|
Controller -> ArticleViewManager++: increment the view counter
|
||||||
|
ArticleViewManager -> Elasticsearch++
|
||||||
|
return
|
||||||
|
return
|
||||||
|
deactivate Controller
|
||||||
|
|
||||||
|
newpage get article versions by id
|
||||||
|
|
||||||
|
Front -> Controller: GET /articles/{article}/versions
|
||||||
|
activate Controller
|
||||||
|
Controller -> Controller: Check Authorization
|
||||||
|
alt Authorize
|
||||||
|
Controller -> Repository++: findVersionsByVersionId
|
||||||
|
Repository -> Postgres++: find_articles_versions_by_version_id
|
||||||
|
return
|
||||||
|
return
|
||||||
|
Controller -> Controller: Convert to dto
|
||||||
|
Front <-- Controller: 200, Articles versions
|
||||||
|
else not authorize
|
||||||
|
Front <-- Controller: 403, "Forbidden"
|
||||||
|
end
|
||||||
|
deactivate Controller
|
||||||
|
@enduml
|
||||||
66
doc/schema/Notification.puml
Normal file
66
doc/schema/Notification.puml
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
@startuml
|
||||||
|
title Notification
|
||||||
|
|Server|
|
||||||
|
partition Event {
|
||||||
|
start
|
||||||
|
:Article is modified;
|
||||||
|
:Send message to "notification" exchange (RabbitMQ);
|
||||||
|
:RabbitMQ send message to "push" and "email" queue;
|
||||||
|
stop
|
||||||
|
}
|
||||||
|
split
|
||||||
|
partition Email {
|
||||||
|
-[hidden]->
|
||||||
|
:Consume "email" queue<
|
||||||
|
repeat :get next notification;
|
||||||
|
:Get followers of article from DB;
|
||||||
|
while (loop on followers)
|
||||||
|
:Send email to the citizen>
|
||||||
|
endwhile
|
||||||
|
:ACK>
|
||||||
|
repeat while()
|
||||||
|
detach
|
||||||
|
}
|
||||||
|
splitagain
|
||||||
|
partition Push {
|
||||||
|
-[hidden]->
|
||||||
|
:Consume "email" queue<
|
||||||
|
repeat :get next notification;
|
||||||
|
:Get followers of article from DB;
|
||||||
|
while (loop on followers)
|
||||||
|
:Send notification message to redis>
|
||||||
|
endwhile
|
||||||
|
:ACK>
|
||||||
|
repeat while()
|
||||||
|
detach
|
||||||
|
}
|
||||||
|
splitagain
|
||||||
|
partition "Notification direct" {
|
||||||
|
-[hidden]->
|
||||||
|
|Client|
|
||||||
|
start
|
||||||
|
:Client arrive on the web site;
|
||||||
|
:Connect to the websocket;
|
||||||
|
|Server|
|
||||||
|
:Get citizen notification
|
||||||
|
from redis;
|
||||||
|
while (on each notifications)
|
||||||
|
:Send notification to websocket>
|
||||||
|
endwhile(no notification left)
|
||||||
|
|Client|
|
||||||
|
:show notification;
|
||||||
|
|Server|
|
||||||
|
:Subscribe to redis event;
|
||||||
|
repeat :On new notification;
|
||||||
|
:Get new notification from redis;
|
||||||
|
:Send notification to websocket>
|
||||||
|
|Client|
|
||||||
|
:show notification;
|
||||||
|
|Server|
|
||||||
|
repeat while (wait notification)
|
||||||
|
detach
|
||||||
|
}
|
||||||
|
endsplit
|
||||||
|
|
||||||
|
|
||||||
|
@enduml
|
||||||
44
docker-compose-test.yml
Normal file
44
docker-compose-test.yml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
rabbitmq:
|
||||||
|
container_name: ${APP_NAME}_rabbitmq_test
|
||||||
|
image: rabbitmq:management-alpine
|
||||||
|
ports:
|
||||||
|
- 5673:5672
|
||||||
|
- 15673:15672
|
||||||
|
|
||||||
|
redis:
|
||||||
|
container_name: ${APP_NAME}_redis_test
|
||||||
|
image: redis:6-alpine
|
||||||
|
ports:
|
||||||
|
- 6380:6379
|
||||||
|
|
||||||
|
elasticsearch:
|
||||||
|
container_name: ${APP_NAME}_elasticsearch_test
|
||||||
|
image: elasticsearch:6.7.1
|
||||||
|
ports:
|
||||||
|
- 9201:9200
|
||||||
|
- 9301:9300
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://elasticsearch:9200"]
|
||||||
|
interval: 3s
|
||||||
|
timeout: 2s
|
||||||
|
retries: 20
|
||||||
|
|
||||||
|
db:
|
||||||
|
container_name: ${APP_NAME}_postgresql_test
|
||||||
|
build:
|
||||||
|
context: docker/postgresql
|
||||||
|
ports:
|
||||||
|
- 15432:5432
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: ${DB_NAME}
|
||||||
|
POSTGRES_USER: ${DB_USER}
|
||||||
|
POSTGRES_DB: ${DB_PWD}
|
||||||
|
depends_on:
|
||||||
|
- elasticsearch
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "pg_isready", "-q", "-d", "${DB_NAME}", "-U", "${DB_USER}" ]
|
||||||
|
interval: 3s
|
||||||
|
timeout: 2s
|
||||||
|
retries: 20
|
||||||
@@ -1,21 +1,68 @@
|
|||||||
# To execute this docker-compose yml file use docker-compose -f <file_name> up
|
# To execute this docker-compose yml file use docker-compose -f <file_name> up
|
||||||
# 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.8'
|
||||||
services:
|
services:
|
||||||
|
openapi:
|
||||||
|
container_name: ${APP_NAME}_openapi
|
||||||
|
image: swaggerapi/swagger-ui
|
||||||
|
ports:
|
||||||
|
- ${OPENAPI_PORT}:8080
|
||||||
|
environment:
|
||||||
|
URL: "http://localhost:8080"
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
container_name: ${APP_NAME}_rabbitmq
|
||||||
|
image: rabbitmq:management-alpine
|
||||||
|
ports:
|
||||||
|
- ${RABBITMQ_PORT}:5672
|
||||||
|
- ${RABBITMQ_MANAGEMENT_PORT}:15672
|
||||||
|
|
||||||
|
redis:
|
||||||
|
container_name: ${APP_NAME}_redis
|
||||||
|
image: redis:6-alpine
|
||||||
|
ports:
|
||||||
|
- ${REDIS_PORT}:6379
|
||||||
|
volumes:
|
||||||
|
- redis-data:/var/lib/redis:rw
|
||||||
|
|
||||||
|
app:
|
||||||
|
container_name: ${APP_NAME}_app
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: docker/app/Dockerfile
|
||||||
|
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}
|
||||||
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
|
JWT_ISSUER: ${JWT_ISSUER}
|
||||||
|
JWT_VALIDITY: ${JWT_VALIDITY}
|
||||||
|
depends_on:
|
||||||
|
- elasticsearch
|
||||||
|
- db
|
||||||
|
- redis
|
||||||
|
- rabbitmq
|
||||||
|
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
container_name: elasticsearch_${NAME}
|
container_name: ${APP_NAME}_elasticsearch
|
||||||
image: elasticsearch:6.7.1
|
image: elasticsearch:6.7.1
|
||||||
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: ${APP_NAME}_postgresql
|
||||||
build:
|
build:
|
||||||
context: docker/postgresql
|
context: docker/postgresql
|
||||||
restart: always
|
|
||||||
ports:
|
ports:
|
||||||
- ${POSTGRESQL_PORT}:5432
|
- ${POSTGRESQL_PORT}:5432
|
||||||
environment:
|
environment:
|
||||||
@@ -24,11 +71,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:
|
||||||
22
docker/app/Dockerfile
Normal file
22
docker/app/Dockerfile
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#### BUILD ####
|
||||||
|
FROM gradle:6.8-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 gradle shadowJar
|
||||||
|
|
||||||
|
#### RUN ####
|
||||||
|
FROM amazoncorretto:11-alpine as run
|
||||||
|
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,7 @@
|
|||||||
ktor_version=1.2.2
|
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
kotlin_version=1.3.40
|
systemProp.sonar.host.url=https://sonarcloud.io
|
||||||
logback_version=1.2.1
|
systemProp.sonar.projectKey=dc-project
|
||||||
postgresjson_version=0.1
|
systemProp.sonar.projectName=DC Project
|
||||||
koinVersion=2.0.1
|
systemProp.sonar.organization=flecomte
|
||||||
|
systemProp.sonar.java.coveragePlugin=jacoco
|
||||||
|
systemProp.sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml
|
||||||
|
|||||||
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-6.8-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
53
gradlew
vendored
53
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"
|
||||||
@@ -66,6 +82,7 @@ esac
|
|||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
@@ -109,10 +126,11 @@ 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"`
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
@@ -138,19 +156,19 @@ if $cygwin ; then
|
|||||||
else
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval `echo args$i`="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=`expr $i + 1`
|
||||||
done
|
done
|
||||||
case $i in
|
case $i in
|
||||||
(0) set -- ;;
|
0) set -- ;;
|
||||||
(1) set -- "$args0" ;;
|
1) set -- "$args0" ;;
|
||||||
(2) set -- "$args0" "$args1" ;;
|
2) set -- "$args0" "$args1" ;;
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -159,14 +177,9 @@ save () {
|
|||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
APP_ARGS=$(save "$@")
|
APP_ARGS=`save "$@"`
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
|
||||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
|
||||||
cd "$(dirname "$0")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
43
gradlew.bat
vendored
43
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
|
||||||
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
|
|||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
@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
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
@@ -35,7 +54,7 @@ goto fail
|
|||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
@@ -45,28 +64,14 @@ echo location of your Java installation.
|
|||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:init
|
|
||||||
@rem Get command-line arguments, handling Windows variants
|
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
|
||||||
|
|
||||||
:win9xME_args
|
|
||||||
@rem Slurp the command line arguments.
|
|
||||||
set CMD_LINE_ARGS=
|
|
||||||
set _SKIP=2
|
|
||||||
|
|
||||||
:win9xME_args_slurp
|
|
||||||
if "x%~1" == "x" goto execute
|
|
||||||
|
|
||||||
set CMD_LINE_ARGS=%*
|
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
|||||||
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
|
||||||
@@ -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) }
|
|
||||||
}
|
|
||||||
186
src/main/kotlin/fr/dcproject/application/Application.kt
Normal file
186
src/main/kotlin/fr/dcproject/application/Application.kt
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
package fr.dcproject.application
|
||||||
|
|
||||||
|
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.PropertyNamingStrategies
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
|
import com.fasterxml.jackson.datatype.joda.JodaModule
|
||||||
|
import fr.dcproject.application.Env.PROD
|
||||||
|
import fr.dcproject.application.Env.TEST
|
||||||
|
import fr.dcproject.application.http.statusPagesInstallation
|
||||||
|
import fr.dcproject.component.article.articleKoinModule
|
||||||
|
import fr.dcproject.component.article.routes.installArticleRoutes
|
||||||
|
import fr.dcproject.component.auth.authKoinModule
|
||||||
|
import fr.dcproject.component.auth.jwt.jwtInstallation
|
||||||
|
import fr.dcproject.component.auth.routes.installAuthRoutes
|
||||||
|
import fr.dcproject.component.citizen.citizenKoinModule
|
||||||
|
import fr.dcproject.component.citizen.routes.installCitizenRoutes
|
||||||
|
import fr.dcproject.component.comment.article.routes.installCommentArticleRoutes
|
||||||
|
import fr.dcproject.component.comment.commentKoinModule
|
||||||
|
import fr.dcproject.component.comment.constitution.routes.installCommentConstitutionRoutes
|
||||||
|
import fr.dcproject.component.comment.generic.routes.installCommentRoutes
|
||||||
|
import fr.dcproject.component.constitution.constitutionKoinModule
|
||||||
|
import fr.dcproject.component.constitution.routes.installConstitutionRoutes
|
||||||
|
import fr.dcproject.component.doc.routes.installDocRoutes
|
||||||
|
import fr.dcproject.component.follow.followKoinModule
|
||||||
|
import fr.dcproject.component.follow.routes.article.installFollowArticleRoutes
|
||||||
|
import fr.dcproject.component.follow.routes.constitution.installFollowConstitutionRoutes
|
||||||
|
import fr.dcproject.component.notification.NotificationConsumer
|
||||||
|
import fr.dcproject.component.notification.routes.installNotificationsRoutes
|
||||||
|
import fr.dcproject.component.opinion.opinionKoinModule
|
||||||
|
import fr.dcproject.component.opinion.routes.installOpinionRoutes
|
||||||
|
import fr.dcproject.component.views.viewKoinModule
|
||||||
|
import fr.dcproject.component.vote.routes.installVoteRoutes
|
||||||
|
import fr.dcproject.component.vote.voteKoinModule
|
||||||
|
import fr.dcproject.component.workgroup.routes.installWorkgroupRoutes
|
||||||
|
import fr.dcproject.component.workgroup.workgroupKoinModule
|
||||||
|
import fr.postgresjson.migration.Migrations
|
||||||
|
import io.ktor.application.Application
|
||||||
|
import io.ktor.application.ApplicationStopped
|
||||||
|
import io.ktor.application.install
|
||||||
|
import io.ktor.auth.Authentication
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.engine.jetty.Jetty
|
||||||
|
import io.ktor.features.AutoHeadResponse
|
||||||
|
import io.ktor.features.CORS
|
||||||
|
import io.ktor.features.CallLogging
|
||||||
|
import io.ktor.features.ContentNegotiation
|
||||||
|
import io.ktor.features.DataConversion
|
||||||
|
import io.ktor.features.StatusPages
|
||||||
|
import io.ktor.http.HttpHeaders
|
||||||
|
import io.ktor.http.HttpMethod
|
||||||
|
import io.ktor.http.cio.websocket.pingPeriod
|
||||||
|
import io.ktor.http.cio.websocket.timeout
|
||||||
|
import io.ktor.jackson.jackson
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.locations.Locations
|
||||||
|
import io.ktor.routing.Routing
|
||||||
|
import io.ktor.server.jetty.EngineMain
|
||||||
|
import io.ktor.util.KtorExperimentalAPI
|
||||||
|
import io.ktor.websocket.WebSockets
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import org.eclipse.jetty.util.log.Slf4jLog
|
||||||
|
import org.koin.dsl.module
|
||||||
|
import org.koin.ktor.ext.Koin
|
||||||
|
import org.koin.ktor.ext.get
|
||||||
|
import org.slf4j.event.Level
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
|
fun main(args: Array<String>): Unit = EngineMain.main(args)
|
||||||
|
|
||||||
|
enum class Env { PROD, TEST }
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
|
@KtorExperimentalAPI
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
|
@Suppress("unused") // Referenced in application.conf
|
||||||
|
fun Application.module(env: Env = PROD) {
|
||||||
|
install(Koin) {
|
||||||
|
Slf4jLog()
|
||||||
|
modules(
|
||||||
|
listOf(
|
||||||
|
if (env == TEST) module { single { Configuration("application-test.conf") } }
|
||||||
|
else module { single { Configuration() } },
|
||||||
|
KoinModule,
|
||||||
|
articleKoinModule,
|
||||||
|
authKoinModule,
|
||||||
|
citizenKoinModule,
|
||||||
|
commentKoinModule,
|
||||||
|
constitutionKoinModule,
|
||||||
|
followKoinModule,
|
||||||
|
opinionKoinModule,
|
||||||
|
viewKoinModule,
|
||||||
|
voteKoinModule,
|
||||||
|
workgroupKoinModule,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
install(CallLogging) {
|
||||||
|
level = Level.INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
install(DataConversion, converters)
|
||||||
|
|
||||||
|
install(Locations)
|
||||||
|
|
||||||
|
HttpClient(Jetty) {
|
||||||
|
engine {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
get<NotificationConsumer>().run {
|
||||||
|
start()
|
||||||
|
environment.monitor.subscribe(ApplicationStopped) {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
install(Authentication, jwtInstallation(get(), get()))
|
||||||
|
|
||||||
|
install(AutoHeadResponse)
|
||||||
|
|
||||||
|
install(ContentNegotiation) {
|
||||||
|
jackson {
|
||||||
|
propertyNamingStrategy = PropertyNamingStrategies.LOWER_CAMEL_CASE
|
||||||
|
|
||||||
|
registerModule(JodaModule())
|
||||||
|
disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||||
|
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
|
||||||
|
configure(SerializationFeature.INDENT_OUTPUT, true)
|
||||||
|
setDefaultPrettyPrinter(
|
||||||
|
DefaultPrettyPrinter().apply {
|
||||||
|
indentArraysWith(DefaultPrettyPrinter.FixedSpaceIndenter.instance)
|
||||||
|
indentObjectsWith(DefaultIndenter(" ", "\n"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
install(Routing.Feature) {
|
||||||
|
// trace { application.log.trace(it.buildText()) }
|
||||||
|
installArticleRoutes()
|
||||||
|
installAuthRoutes()
|
||||||
|
installCitizenRoutes()
|
||||||
|
installCommentArticleRoutes()
|
||||||
|
installCommentRoutes()
|
||||||
|
installFollowArticleRoutes()
|
||||||
|
installFollowConstitutionRoutes()
|
||||||
|
installWorkgroupRoutes()
|
||||||
|
installOpinionRoutes()
|
||||||
|
installVoteRoutes()
|
||||||
|
installConstitutionRoutes()
|
||||||
|
installCommentConstitutionRoutes()
|
||||||
|
installNotificationsRoutes()
|
||||||
|
installDocRoutes()
|
||||||
|
}
|
||||||
|
|
||||||
|
install(StatusPages, statusPagesInstallation())
|
||||||
|
|
||||||
|
install(CORS) {
|
||||||
|
method(HttpMethod.Options)
|
||||||
|
method(HttpMethod.Put)
|
||||||
|
method(HttpMethod.Delete)
|
||||||
|
header(HttpHeaders.Authorization)
|
||||||
|
if (env == PROD) {
|
||||||
|
host("localhost:4200", schemes = listOf("http", "https"))
|
||||||
|
} else {
|
||||||
|
anyHost()
|
||||||
|
}
|
||||||
|
allowCredentials = true
|
||||||
|
allowSameOrigin = true
|
||||||
|
maxAgeInSeconds = Duration.ofDays(1).seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
if (env == PROD) {
|
||||||
|
get<Migrations>().run()
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/main/kotlin/fr/dcproject/application/Configuration.kt
Normal file
57
src/main/kotlin/fr/dcproject/application/Configuration.kt
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package fr.dcproject.application
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
class Configuration(val config: Config) {
|
||||||
|
constructor(resourceBasename: String? = null) : this(if (resourceBasename == null) ConfigFactory.load() else ConfigFactory.load(resourceBasename))
|
||||||
|
|
||||||
|
interface Sql {
|
||||||
|
val migrationFiles: URI
|
||||||
|
val functionFiles: URI
|
||||||
|
val fixtureFiles: URI
|
||||||
|
}
|
||||||
|
val sql
|
||||||
|
get() = object : Sql {
|
||||||
|
override val migrationFiles: URI = this::class.java.getResource("/sql/migrations")?.toURI() ?: error("No migrations found")
|
||||||
|
override val functionFiles: URI = this::class.java.getResource("/sql/functions")?.toURI() ?: error("No sql function found")
|
||||||
|
override val fixtureFiles: URI = this::class.java.getResource("/sql/fixtures")?.toURI() ?: error("No sql fixture found")
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Database {
|
||||||
|
val host: String
|
||||||
|
val port: Int
|
||||||
|
var database: String
|
||||||
|
var username: String
|
||||||
|
var password: String
|
||||||
|
}
|
||||||
|
val database
|
||||||
|
get() = object : Database {
|
||||||
|
override val host: String = config.getString("db.host")
|
||||||
|
override val port: Int = config.getInt("db.port")
|
||||||
|
override var database: String = config.getString("db.database")
|
||||||
|
override var username: String = config.getString("db.username")
|
||||||
|
override var password: String = config.getString("db.password")
|
||||||
|
}
|
||||||
|
|
||||||
|
val envName: String = config.getString("app.envName")
|
||||||
|
val domain: String = config.getString("app.domain")
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
interface Jwt {
|
||||||
|
val secret: String
|
||||||
|
val issuer: String
|
||||||
|
val validityInMs: Int
|
||||||
|
}
|
||||||
|
val jwt = object : Jwt {
|
||||||
|
override val secret = config.getString("jwt.secret")
|
||||||
|
override val issuer = config.getString("jwt.issuer")
|
||||||
|
override val validityInMs = config.getInt("jwt.validity")
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/main/kotlin/fr/dcproject/application/Converters.kt
Normal file
31
src/main/kotlin/fr/dcproject/application/Converters.kt
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package fr.dcproject.application
|
||||||
|
|
||||||
|
import io.ktor.features.DataConversion
|
||||||
|
import io.ktor.util.KtorExperimentalAPI
|
||||||
|
import org.koin.core.context.GlobalContext
|
||||||
|
import org.koin.core.parameter.ParametersDefinition
|
||||||
|
import org.koin.core.qualifier.Qualifier
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
private typealias ConverterDeclaration = DataConversion.Configuration.() -> Unit
|
||||||
|
private inline fun <reified T> DataConversion.Configuration.get(
|
||||||
|
qualifier: Qualifier? = null,
|
||||||
|
noinline parameters: ParametersDefinition? = null
|
||||||
|
): T = GlobalContext.get().koin.rootScope.get(qualifier, parameters)
|
||||||
|
|
||||||
|
@KtorExperimentalAPI
|
||||||
|
val converters: ConverterDeclaration = {
|
||||||
|
convert<UUID> {
|
||||||
|
decode { values, _ ->
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/main/kotlin/fr/dcproject/application/KoinModule.kt
Normal file
124
src/main/kotlin/fr/dcproject/application/KoinModule.kt
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package fr.dcproject.application
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.databind.PropertyNamingStrategies
|
||||||
|
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.common.email.Mailer
|
||||||
|
import fr.dcproject.component.auth.jwt.JwtConfig
|
||||||
|
import fr.dcproject.component.notification.NotificationConsumer
|
||||||
|
import fr.dcproject.component.notification.NotificationEmailSender
|
||||||
|
import fr.dcproject.component.notification.NotificationsPush
|
||||||
|
import fr.dcproject.component.notification.Publisher
|
||||||
|
import fr.postgresjson.connexion.Connection
|
||||||
|
import fr.postgresjson.connexion.Requester
|
||||||
|
import fr.postgresjson.migration.Migrations
|
||||||
|
import io.ktor.client.HttpClient
|
||||||
|
import io.ktor.client.features.websocket.WebSockets
|
||||||
|
import io.ktor.util.KtorExperimentalAPI
|
||||||
|
import io.lettuce.core.RedisClient
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
@KtorExperimentalAPI
|
||||||
|
val KoinModule = module {
|
||||||
|
// JWT
|
||||||
|
single {
|
||||||
|
val config: Configuration = get()
|
||||||
|
JwtConfig(
|
||||||
|
config.jwt.secret,
|
||||||
|
config.jwt.issuer,
|
||||||
|
config.jwt.validityInMs,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// JWT Verifier
|
||||||
|
single {
|
||||||
|
get<JwtConfig>().verifier
|
||||||
|
}
|
||||||
|
// SQL connection
|
||||||
|
single {
|
||||||
|
val config: Configuration = get()
|
||||||
|
Connection(
|
||||||
|
host = config.database.host,
|
||||||
|
port = config.database.port,
|
||||||
|
database = config.database.database,
|
||||||
|
username = config.database.username,
|
||||||
|
password = config.database.password
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch Database migration
|
||||||
|
single {
|
||||||
|
val config: Configuration = get()
|
||||||
|
Migrations(get(), config.sql.migrationFiles, config.sql.functionFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redis client
|
||||||
|
single<RedisClient> {
|
||||||
|
val config: Configuration = get()
|
||||||
|
RedisClient.create(config.redis).apply {
|
||||||
|
connect().sync().configSet("notify-keyspace-events", "KEA")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
single { NotificationsPush.Builder(get()) }
|
||||||
|
|
||||||
|
single {
|
||||||
|
val config: Configuration = get()
|
||||||
|
NotificationConsumer(get(), get(), get(), get(), get(), config.exchangeNotificationName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RabbitMQ
|
||||||
|
single<ConnectionFactory> {
|
||||||
|
val config: Configuration = get()
|
||||||
|
ConnectionFactory().apply { setUri(config.rabbitmq) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// JsonSerializer
|
||||||
|
single<ObjectMapper> {
|
||||||
|
jacksonObjectMapper().apply {
|
||||||
|
registerModule(SimpleModule())
|
||||||
|
propertyNamingStrategy = PropertyNamingStrategies.LOWER_CAMEL_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 {
|
||||||
|
val config: Configuration = get()
|
||||||
|
Requester.RequesterFactory(
|
||||||
|
connection = get(),
|
||||||
|
functionsDirectory = config.sql.functionFiles
|
||||||
|
).createRequester()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mailer
|
||||||
|
single {
|
||||||
|
val config: Configuration = get()
|
||||||
|
Mailer(config.sendGridKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
single {
|
||||||
|
val config: Configuration = get()
|
||||||
|
Publisher(factory = get(), exchangeName = config.exchangeNotificationName)
|
||||||
|
}
|
||||||
|
|
||||||
|
single {
|
||||||
|
val config: Configuration = get()
|
||||||
|
NotificationEmailSender(get<Mailer>(), config.domain, get(), get())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package fr.dcproject.application.http
|
||||||
|
|
||||||
|
import com.github.jasync.sql.db.postgresql.exceptions.GenericDatabaseException
|
||||||
|
import fr.dcproject.common.security.AccessDeniedException
|
||||||
|
import fr.dcproject.component.auth.ForbiddenException
|
||||||
|
import fr.dcproject.component.auth.user
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.features.NotFoundException
|
||||||
|
import io.ktor.features.StatusPages
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import java.util.concurrent.CompletionException
|
||||||
|
|
||||||
|
class HttpError(
|
||||||
|
statusCode: HttpStatusCode,
|
||||||
|
val cause: Throwable? = null,
|
||||||
|
val type: String? = null,
|
||||||
|
val title: String = cause?.message ?: statusCode.description,
|
||||||
|
val detail: String? = null,
|
||||||
|
val invalidParams: List<InvalidParam>? = null,
|
||||||
|
val stackTrace: String? = cause?.stackTraceToString()
|
||||||
|
) {
|
||||||
|
val statusCode: Int = statusCode.value
|
||||||
|
data class InvalidParam(
|
||||||
|
val name: String,
|
||||||
|
val reason: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun statusPagesInstallation(): StatusPages.Configuration.() -> Unit = {
|
||||||
|
exception<CompletionException> { e ->
|
||||||
|
val parent = e.cause?.cause
|
||||||
|
if (parent is GenericDatabaseException) {
|
||||||
|
HttpError(
|
||||||
|
HttpStatusCode.BadRequest,
|
||||||
|
cause = parent
|
||||||
|
).let {
|
||||||
|
call.respond(HttpStatusCode.BadRequest, it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
HttpError(
|
||||||
|
HttpStatusCode.BadRequest,
|
||||||
|
cause = e
|
||||||
|
).let {
|
||||||
|
call.respond(HttpStatusCode.InternalServerError, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exception<NotFoundException> { e ->
|
||||||
|
HttpError(
|
||||||
|
HttpStatusCode.NotFound,
|
||||||
|
cause = e
|
||||||
|
).let {
|
||||||
|
call.respond(HttpStatusCode.NotFound, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exception<AccessDeniedException> { e ->
|
||||||
|
if (call.user == null) {
|
||||||
|
HttpError(
|
||||||
|
HttpStatusCode.Unauthorized,
|
||||||
|
cause = e
|
||||||
|
).let {
|
||||||
|
call.respond(HttpStatusCode.Unauthorized, it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
HttpError(
|
||||||
|
HttpStatusCode.Forbidden,
|
||||||
|
cause = e
|
||||||
|
).let {
|
||||||
|
call.respond(HttpStatusCode.Forbidden, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exception<ForbiddenException> { e ->
|
||||||
|
HttpError(
|
||||||
|
HttpStatusCode.Forbidden,
|
||||||
|
cause = e
|
||||||
|
).let {
|
||||||
|
call.respond(HttpStatusCode.Forbidden, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/main/kotlin/fr/dcproject/common/BitMaskEnum.kt
Normal file
11
src/main/kotlin/fr/dcproject/common/BitMaskEnum.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package fr.dcproject.common
|
||||||
|
|
||||||
|
interface BitMaskI {
|
||||||
|
val bit: Long
|
||||||
|
|
||||||
|
infix operator fun contains(which: BitMaskI): Boolean = bit and which.bit == which.bit
|
||||||
|
infix operator fun plus(mask: BitMaskI): BitMaskI = BitMask(mask.bit and this.bit)
|
||||||
|
infix operator fun minus(mask: BitMaskI): BitMaskI = BitMask(this.bit - mask.bit)
|
||||||
|
}
|
||||||
|
|
||||||
|
class BitMask(override val bit: Long) : BitMaskI
|
||||||
27
src/main/kotlin/fr/dcproject/common/email/Mailer.kt
Normal file
27
src/main/kotlin/fr/dcproject/common/email/Mailer.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package fr.dcproject.common.email
|
||||||
|
|
||||||
|
import com.sendgrid.Method
|
||||||
|
import com.sendgrid.Request
|
||||||
|
import com.sendgrid.SendGrid
|
||||||
|
import com.sendgrid.helpers.mail.Mail
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class Mailer(
|
||||||
|
private val key: String
|
||||||
|
) {
|
||||||
|
fun sendEmail(action: () -> Mail): Boolean {
|
||||||
|
val mail = action()
|
||||||
|
|
||||||
|
val sg = SendGrid(key)
|
||||||
|
val request = Request()
|
||||||
|
try {
|
||||||
|
request.method = Method.POST
|
||||||
|
request.endpoint = "mail/send"
|
||||||
|
request.body = mail.build()
|
||||||
|
val response = sg.api(request)
|
||||||
|
return response.statusCode == 202
|
||||||
|
} catch (ex: IOException) {
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/kotlin/fr/dcproject/common/entity/Action.kt
Normal file
28
src/main/kotlin/fr/dcproject/common/entity/Action.kt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package fr.dcproject.common.entity
|
||||||
|
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenI
|
||||||
|
|
||||||
|
interface Created<C : CitizenI> : CreatedAt, CreatedBy<C> {
|
||||||
|
class Imp<C : CitizenI>(createdBy: C) :
|
||||||
|
Created<C>,
|
||||||
|
CreatedBy<C> by CreatedBy.Imp(createdBy),
|
||||||
|
CreatedAt by CreatedAt.Imp()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Updated<C : CitizenI> : UpdatedAt, UpdatedBy<C> {
|
||||||
|
class Imp<C : CitizenI>(updatedAt: C) :
|
||||||
|
Updated<C>,
|
||||||
|
UpdatedBy<C> by UpdatedBy.Imp(updatedAt),
|
||||||
|
UpdatedAt by UpdatedAt.Imp()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Deleted<C : CitizenI> : DeletedAt, DeletedBy<C> {
|
||||||
|
override fun isDeleted(): Boolean = (this as DeletedAt).isDeleted()
|
||||||
|
|
||||||
|
class Imp<C : CitizenI>(deletedAt: C) :
|
||||||
|
Deleted<C>,
|
||||||
|
DeletedBy<C> by DeletedBy.Imp(deletedAt),
|
||||||
|
DeletedAt by DeletedAt.Imp() {
|
||||||
|
override fun isDeleted(): Boolean = (this as Deleted<C>).isDeleted()
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/main/kotlin/fr/dcproject/common/entity/ActionBy.kt
Normal file
25
src/main/kotlin/fr/dcproject/common/entity/ActionBy.kt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package fr.dcproject.common.entity
|
||||||
|
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenI
|
||||||
|
|
||||||
|
interface CreatedBy<T : CitizenI> {
|
||||||
|
val createdBy: T
|
||||||
|
|
||||||
|
class Imp<T : CitizenI>(override val createdBy: T) : CreatedBy<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdatedBy<T : CitizenI> {
|
||||||
|
val updatedBy: T
|
||||||
|
|
||||||
|
class Imp<T : CitizenI>(override val updatedBy: T) : UpdatedBy<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeletedBy<T : CitizenI> {
|
||||||
|
val deletedBy: T?
|
||||||
|
|
||||||
|
fun isDeleted(): Boolean {
|
||||||
|
return deletedBy?.let { true } ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
class Imp<T : CitizenI>(override val deletedBy: T?) : DeletedBy<T>
|
||||||
|
}
|
||||||
30
src/main/kotlin/fr/dcproject/common/entity/Date.kt
Normal file
30
src/main/kotlin/fr/dcproject/common/entity/Date.kt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package fr.dcproject.common.entity
|
||||||
|
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
|
||||||
|
/* Interface */
|
||||||
|
interface CreatedAt {
|
||||||
|
val createdAt: DateTime
|
||||||
|
class Imp(
|
||||||
|
override val createdAt: DateTime = DateTime.now()
|
||||||
|
) : CreatedAt
|
||||||
|
}
|
||||||
|
interface UpdatedAt {
|
||||||
|
val updatedAt: DateTime
|
||||||
|
class Imp(
|
||||||
|
override val updatedAt: DateTime = DateTime.now()
|
||||||
|
) : UpdatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeletedAt {
|
||||||
|
val deletedAt: DateTime?
|
||||||
|
fun isDeleted(): Boolean {
|
||||||
|
return deletedAt?.let {
|
||||||
|
it < DateTime.now()
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
class Imp(
|
||||||
|
override val deletedAt: DateTime? = null
|
||||||
|
) : DeletedAt
|
||||||
|
}
|
||||||
12
src/main/kotlin/fr/dcproject/common/entity/EntityI.kt
Normal file
12
src/main/kotlin/fr/dcproject/common/entity/EntityI.kt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package fr.dcproject.common.entity
|
||||||
|
|
||||||
|
import fr.postgresjson.entity.UuidEntityI
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
interface EntityI : UuidEntityI {
|
||||||
|
override val id: UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
open class Entity(id: UUID? = null) : EntityI {
|
||||||
|
override val id: UUID = id ?: UUID.randomUUID()
|
||||||
|
}
|
||||||
62
src/main/kotlin/fr/dcproject/common/entity/Extra.kt
Normal file
62
src/main/kotlin/fr/dcproject/common/entity/Extra.kt
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package fr.dcproject.common.entity
|
||||||
|
|
||||||
|
import fr.dcproject.component.article.database.ArticleRef
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenI
|
||||||
|
import fr.dcproject.component.comment.generic.database.CommentRef
|
||||||
|
import fr.dcproject.component.constitution.database.ConstitutionRef
|
||||||
|
import fr.dcproject.component.opinion.database.OpinionRef
|
||||||
|
import java.util.UUID
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.full.isSubclassOf
|
||||||
|
|
||||||
|
interface ExtraI<T : TargetI, C : CitizenI> :
|
||||||
|
EntityI,
|
||||||
|
HasTarget<T>,
|
||||||
|
CreatedAt,
|
||||||
|
CreatedBy<C>
|
||||||
|
|
||||||
|
interface HasTarget<T : TargetI> {
|
||||||
|
val target: T
|
||||||
|
}
|
||||||
|
|
||||||
|
open class TargetRef(id: UUID? = null, reference: String = "") : TargetI, Entity(id) {
|
||||||
|
|
||||||
|
final override val reference: String
|
||||||
|
get() = if (field != "") field else TargetI.getReference(this)
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.reference = reference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TargetI : EntityI {
|
||||||
|
enum class TargetName(val targetReference: String) {
|
||||||
|
Article("article"),
|
||||||
|
Constitution("constitution"),
|
||||||
|
Comment("comment"),
|
||||||
|
Opinion("opinion")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun <T : TargetI> getReference(t: KClass<T>): String {
|
||||||
|
return when {
|
||||||
|
t.isSubclassOf(ArticleRef::class) -> TargetName.Article.targetReference
|
||||||
|
t.isSubclassOf(ConstitutionRef::class) -> TargetName.Constitution.targetReference
|
||||||
|
t.isSubclassOf(CommentRef::class) -> TargetName.Comment.targetReference
|
||||||
|
t.isSubclassOf(OpinionRef::class) -> TargetName.Opinion.targetReference
|
||||||
|
else -> throw error("target not implemented: ${t.qualifiedName} \nImplement it or return 'reference' from SQL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getReference(t: TargetI): String {
|
||||||
|
val ref = this.getReference(t::class)
|
||||||
|
return if (t is ExtraI<*, *>) {
|
||||||
|
"${ref}_on_${t.target.reference}"
|
||||||
|
} else {
|
||||||
|
ref
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val reference: String
|
||||||
|
}
|
||||||
25
src/main/kotlin/fr/dcproject/common/entity/Versionable.kt
Normal file
25
src/main/kotlin/fr/dcproject/common/entity/Versionable.kt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package fr.dcproject.common.entity
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
interface VersionableId {
|
||||||
|
val versionId: UUID
|
||||||
|
|
||||||
|
class Imp(
|
||||||
|
versionId: UUID? = null,
|
||||||
|
) : VersionableId {
|
||||||
|
override val versionId: UUID = versionId ?: UUID.randomUUID()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Versionable : VersionableId {
|
||||||
|
override val versionId: UUID
|
||||||
|
val versionNumber: Int
|
||||||
|
|
||||||
|
class Imp(
|
||||||
|
override val versionNumber: Int,
|
||||||
|
versionId: UUID? = null,
|
||||||
|
) : Versionable {
|
||||||
|
override val versionId: UUID = versionId ?: UUID.randomUUID()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package fr.dcproject.routes
|
||||||
|
|
||||||
|
interface PaginatedRequestI {
|
||||||
|
val page: Int
|
||||||
|
val limit: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
open class PaginatedRequest(
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50
|
||||||
|
) : PaginatedRequestI {
|
||||||
|
override val page: Int = if (page < 1) 1 else page
|
||||||
|
override val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
|
||||||
|
}
|
||||||
16
src/main/kotlin/fr/dcproject/common/response/Paginated.kt
Normal file
16
src/main/kotlin/fr/dcproject/common/response/Paginated.kt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package fr.dcproject.common.response
|
||||||
|
|
||||||
|
import fr.dcproject.common.entity.EntityI
|
||||||
|
import fr.postgresjson.connexion.Paginated
|
||||||
|
|
||||||
|
fun <E : EntityI> Paginated<E>.toOutput(setup: (E) -> Any): Any {
|
||||||
|
return object {
|
||||||
|
val count = this@toOutput.count
|
||||||
|
val currentPage = this@toOutput.count
|
||||||
|
val limit = this@toOutput.limit
|
||||||
|
val offset = this@toOutput.offset
|
||||||
|
val total = this@toOutput.total
|
||||||
|
val totalPages = this@toOutput.totalPages
|
||||||
|
val result = this@toOutput.result.map { setup(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/main/kotlin/fr/dcproject/common/response/createdBy.kt
Normal file
21
src/main/kotlin/fr/dcproject/common/response/createdBy.kt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package fr.dcproject.common.response
|
||||||
|
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenCreatorI
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
fun CitizenCreatorI.toOutput(): Any = this.let { c ->
|
||||||
|
object {
|
||||||
|
val id: UUID = c.id
|
||||||
|
val name: Any = c.name.let { n ->
|
||||||
|
object {
|
||||||
|
val firstName: String = n.firstName
|
||||||
|
val lastName: String = n.lastName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val user: Any = c.user.let { u ->
|
||||||
|
object {
|
||||||
|
val username: String = u.username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
package fr.dcproject.common.security
|
||||||
|
|
||||||
|
/** Responses of AccessControl */
|
||||||
|
enum class AccessDecision {
|
||||||
|
GRANTED,
|
||||||
|
DENIED;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert decision to boolean
|
||||||
|
*/
|
||||||
|
fun toBoolean(): Boolean = when (this) {
|
||||||
|
GRANTED -> true
|
||||||
|
DENIED -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class AccessControl {
|
||||||
|
/**
|
||||||
|
* A Shortcut for return a GrantedResponse
|
||||||
|
*/
|
||||||
|
protected fun granted(message: String? = null, code: String? = null): GrantedResponse = GrantedResponse(this, message, code)
|
||||||
|
/**
|
||||||
|
* A Shortcut for return a DeniedResponse
|
||||||
|
*/
|
||||||
|
protected fun denied(message: String, code: String): DeniedResponse = DeniedResponse(this, message, code)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check all responses and return DENIED if one is DENIED
|
||||||
|
*
|
||||||
|
* If the list of responses is empty, return GRANTED
|
||||||
|
*/
|
||||||
|
private fun AccessResponses.getOneResponse(): AccessResponse = this.firstOrNull { it.decision == AccessDecision.DENIED } ?: granted()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An helper to convert a list of subject into one response
|
||||||
|
*/
|
||||||
|
protected fun <S : List<T>, T> canAll(items: S, action: (T) -> AccessResponse): AccessResponse = items
|
||||||
|
.map { action(it) }
|
||||||
|
.getOneResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw an Exception if AccessControl return a DENIED response
|
||||||
|
*/
|
||||||
|
fun <T : AccessControl> T.assert(action: T.() -> AccessResponse) {
|
||||||
|
action().assert()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check all responses and return DENIED if one is DENIED
|
||||||
|
*
|
||||||
|
* If the list of responses is empty, return GRANTED
|
||||||
|
*/
|
||||||
|
fun AccessResponses.getOneResponse(): AccessResponse = this.firstOrNull { it.decision == AccessDecision.DENIED } ?: GrantedResponse(first().accessControl)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw an Exception if one response is DENIED
|
||||||
|
*/
|
||||||
|
fun AccessResponses.assert() = this.getOneResponse().assert()
|
||||||
|
|
||||||
|
class AccessDeniedException(private val accessResponses: AccessResponses) : Throwable(accessResponses.first().message) {
|
||||||
|
constructor(accessResponse: AccessResponse) : this(listOf(accessResponse))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get first response
|
||||||
|
*/
|
||||||
|
fun first(): AccessResponse = accessResponses.first()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the error code is present into the responses
|
||||||
|
*/
|
||||||
|
fun hasErrorCode(code: String): Boolean = accessResponses
|
||||||
|
.filter { it.decision == AccessDecision.DENIED }
|
||||||
|
.any { it.code == code }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find and return the response than match with the error code
|
||||||
|
*/
|
||||||
|
fun getErrorCode(code: String): AccessResponse? = accessResponses
|
||||||
|
.firstOrNull { it.decision == AccessDecision.DENIED && it.code == code }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of messages of all responses
|
||||||
|
*/
|
||||||
|
fun getMessages(): List<String> = accessResponses
|
||||||
|
.mapNotNull { it.message }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first message
|
||||||
|
*/
|
||||||
|
fun getFirstMessage(): String? = accessResponses
|
||||||
|
.first()
|
||||||
|
.message
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The response that all AccessControl method return
|
||||||
|
* @see GrantedResponse
|
||||||
|
* @see DeniedResponse
|
||||||
|
*/
|
||||||
|
sealed class AccessResponse(
|
||||||
|
val decision: AccessDecision,
|
||||||
|
val accessControl: AccessControl,
|
||||||
|
val message: String?,
|
||||||
|
val code: String?
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Convert response as boolean
|
||||||
|
*/
|
||||||
|
fun toBoolean(): Boolean = decision.toBoolean()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw Exception if response if DENIED
|
||||||
|
*/
|
||||||
|
fun assert() {
|
||||||
|
if (this.decision == AccessDecision.DENIED) {
|
||||||
|
throw AccessDeniedException(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GrantedResponse(
|
||||||
|
accessControl: AccessControl,
|
||||||
|
message: String? = null,
|
||||||
|
code: String? = null
|
||||||
|
) : AccessResponse(AccessDecision.GRANTED, accessControl, message, code)
|
||||||
|
|
||||||
|
class DeniedResponse(
|
||||||
|
accessControl: AccessControl,
|
||||||
|
message: String,
|
||||||
|
code: String
|
||||||
|
) : AccessResponse(AccessDecision.DENIED, accessControl, message, code)
|
||||||
|
|
||||||
|
typealias AccessResponses = List<AccessResponse>
|
||||||
6
src/main/kotlin/fr/dcproject/common/utils/DateTime.kt
Normal file
6
src/main/kotlin/fr/dcproject/common/utils/DateTime.kt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package fr.dcproject.common.utils
|
||||||
|
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import org.joda.time.format.ISODateTimeFormat
|
||||||
|
|
||||||
|
fun DateTime.toIso(): String = ISODateTimeFormat.dateTime().print(this)
|
||||||
29
src/main/kotlin/fr/dcproject/common/utils/Elastic.kt
Normal file
29
src/main/kotlin/fr/dcproject/common/utils/Elastic.kt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package fr.dcproject.common.utils
|
||||||
|
|
||||||
|
import com.jayway.jsonpath.JsonPath
|
||||||
|
import com.jayway.jsonpath.PathNotFoundException
|
||||||
|
import org.apache.http.util.EntityUtils
|
||||||
|
import org.elasticsearch.client.Response
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
fun Response.contentToString(): String {
|
||||||
|
return EntityUtils.toString(this.entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Response.getField(jsonPath: String): Int? {
|
||||||
|
return try {
|
||||||
|
JsonPath.read(this.contentToString(), jsonPath)
|
||||||
|
} catch (e: PathNotFoundException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.getJsonField(jsonPath: String): Int? {
|
||||||
|
return try {
|
||||||
|
JsonPath.read(this, jsonPath)
|
||||||
|
} catch (e: PathNotFoundException) {
|
||||||
|
LoggerFactory.getLogger("fr.dcproject.utils.getJsonField")
|
||||||
|
.warn("No value for Json path ${JsonPath.compile(jsonPath).path}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/main/kotlin/fr/dcproject/common/utils/LoggerDelegate.kt
Normal file
10
src/main/kotlin/fr/dcproject/common/utils/LoggerDelegate.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package fr.dcproject.common.utils
|
||||||
|
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import kotlin.properties.ReadOnlyProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
internal class LoggerDelegate<in R : Any> : ReadOnlyProperty<R, Logger> {
|
||||||
|
override fun getValue(thisRef: R, property: KProperty<*>): Logger = LoggerFactory.getLogger(thisRef.javaClass.packageName)
|
||||||
|
}
|
||||||
4
src/main/kotlin/fr/dcproject/common/utils/Numeric.kt
Normal file
4
src/main/kotlin/fr/dcproject/common/utils/Numeric.kt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package fr.dcproject.common.utils
|
||||||
|
|
||||||
|
fun String.isInt(): Boolean = this.toIntOrNull() != null
|
||||||
|
fun String.isBool(): Boolean = this == "true" || this == "false"
|
||||||
27
src/main/kotlin/fr/dcproject/common/utils/Request.kt
Normal file
27
src/main/kotlin/fr/dcproject/common/utils/Request.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package fr.dcproject.common.utils
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException
|
||||||
|
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
|
||||||
|
import io.ktor.application.ApplicationCall
|
||||||
|
import io.ktor.application.log
|
||||||
|
import io.ktor.features.BadRequestException
|
||||||
|
import io.ktor.request.receive
|
||||||
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives content for this request.
|
||||||
|
* @param type instance of `KClass` specifying type to be received.
|
||||||
|
* @return instance of [T] received from this call, or `null` if content cannot be transformed to the requested type..
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
public suspend inline fun <reified T : Any> ApplicationCall.receiveOrBadRequest(message: String = "Bad Request, wrong body request"): T {
|
||||||
|
return try {
|
||||||
|
receive<T>(typeOf<T>())
|
||||||
|
} catch (cause: MissingKotlinParameterException) {
|
||||||
|
application.log.debug("Conversion failed, throw bad exception", cause)
|
||||||
|
throw BadRequestException(message, cause)
|
||||||
|
} catch (cause: UnrecognizedPropertyException) {
|
||||||
|
application.log.debug("Conversion failed, throw bad exception", cause)
|
||||||
|
throw BadRequestException(message, cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/main/kotlin/fr/dcproject/common/utils/Resources.kt
Normal file
15
src/main/kotlin/fr/dcproject/common/utils/Resources.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package fr.dcproject.common.utils
|
||||||
|
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
fun String.readResource(callback: (String) -> Unit = {}): String {
|
||||||
|
val content = callback::class.java.getResource(this)?.readText() ?: error("File not found")
|
||||||
|
callback(content)
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.getResource(callback: (URL) -> Unit = {}): URL {
|
||||||
|
val content = callback::class.java.getResource(this) ?: error("File not found")
|
||||||
|
callback(content)
|
||||||
|
return content
|
||||||
|
}
|
||||||
11
src/main/kotlin/fr/dcproject/common/utils/Uuid.kt
Normal file
11
src/main/kotlin/fr/dcproject/common/utils/Uuid.kt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package fr.dcproject.common.utils
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
fun String.toUUID(): UUID = UUID.fromString(this.trim())
|
||||||
|
|
||||||
|
fun List<String?>.toUUID(): List<UUID> = this
|
||||||
|
.filterNotNull()
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.map { UUID.fromString(it) }
|
||||||
28
src/main/kotlin/fr/dcproject/common/utils/waitElastic.kt
Normal file
28
src/main/kotlin/fr/dcproject/common/utils/waitElastic.kt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package fr.dcproject.common.utils
|
||||||
|
|
||||||
|
import org.elasticsearch.client.Request
|
||||||
|
import org.elasticsearch.client.RestClient
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
fun RestClient.waitElasticsearchIsUp() {
|
||||||
|
val logger: Logger = LoggerFactory.getLogger("fr.dcproject.elasticsearch")
|
||||||
|
val request = Request("GET", "/_cluster/health")
|
||||||
|
repeat(5 * 60 / 2) { // 5 minutes
|
||||||
|
runCatching {
|
||||||
|
performRequest(request).statusLine.statusCode
|
||||||
|
}.onSuccess {
|
||||||
|
if (it == 200) {
|
||||||
|
logger.debug("Elasticsearch is Ready! Continue...")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
logger.debug("sleep 2s and retry...")
|
||||||
|
Thread.sleep(2000)
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
logger.debug("${it.message}, sleep 2s and retry...")
|
||||||
|
Thread.sleep(2000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error("Elasticsearch is not ready")
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package fr.dcproject.component.article
|
||||||
|
|
||||||
|
import fr.dcproject.common.entity.CreatedBy
|
||||||
|
import fr.dcproject.common.entity.VersionableId
|
||||||
|
import fr.dcproject.common.security.AccessControl
|
||||||
|
import fr.dcproject.common.security.AccessResponse
|
||||||
|
import fr.dcproject.component.article.database.ArticleAuthI
|
||||||
|
import fr.dcproject.component.article.database.ArticleI
|
||||||
|
import fr.dcproject.component.article.database.ArticleRepository
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenI
|
||||||
|
|
||||||
|
class ArticleAccessControl(private val articleRepo: ArticleRepository) : AccessControl() {
|
||||||
|
fun <S : ArticleAuthI<*>> canView(subjects: List<S>, citizen: CitizenI?): AccessResponse =
|
||||||
|
canAll(subjects) { canView(it, citizen) }
|
||||||
|
|
||||||
|
fun <S : ArticleAuthI<*>> canView(subject: S, citizen: CitizenI?): AccessResponse {
|
||||||
|
return if (subject.isDeleted()) denied("Article is deleted", "article.deleted")
|
||||||
|
else if (subject.draft && (citizen == null || subject.createdBy.id != citizen.id)) denied("Article is draft, but it's not yours", "article.draft.not.yours")
|
||||||
|
else granted()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <S : CreatedBy<*>> canDelete(subject: S, citizen: CitizenI?): AccessResponse {
|
||||||
|
if (citizen == null) return denied("You must be connected to create article", "article.create.notConnected")
|
||||||
|
return if (subject.createdBy.id == citizen.id) {
|
||||||
|
granted()
|
||||||
|
} else {
|
||||||
|
denied("Cannot delete article if is not yours", "article.delete.notYours")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <S> canUpsert(subject: S, citizen: CitizenI?): AccessResponse
|
||||||
|
where S : ArticleI,
|
||||||
|
S : CreatedBy<*>,
|
||||||
|
S : VersionableId {
|
||||||
|
if (citizen == null) return denied("You must be connected to create article", "article.create.notConnected")
|
||||||
|
/* The new Article must by created by the same citizen of the connected citizen */
|
||||||
|
if (subject.createdBy.id == citizen.id) {
|
||||||
|
/* The creator must be the same of the creator of preview version of article */
|
||||||
|
val lastVersionId = articleRepo
|
||||||
|
.findVersionsByVersionId(1, 1, subject.versionId)
|
||||||
|
.result
|
||||||
|
.firstOrNull()?.createdBy?.id
|
||||||
|
|
||||||
|
return when (lastVersionId) {
|
||||||
|
null -> granted("You can create a new Article")
|
||||||
|
citizen.id -> granted("Last version is yours")
|
||||||
|
else -> denied("Last version is not yours", "article.lastVersion.notYours")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return denied("This article must be yours for update it", "article.update.notYours")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package fr.dcproject.component.article
|
||||||
|
|
||||||
|
import fr.dcproject.component.article.database.ArticleRepository
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val articleKoinModule = module {
|
||||||
|
single { ArticleRepository(get()) }
|
||||||
|
single { ArticleAccessControl(get()) }
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package fr.dcproject.component.article.database
|
||||||
|
|
||||||
|
import fr.dcproject.common.entity.CreatedAt
|
||||||
|
import fr.dcproject.common.entity.CreatedBy
|
||||||
|
import fr.dcproject.common.entity.DeletedAt
|
||||||
|
import fr.dcproject.common.entity.EntityI
|
||||||
|
import fr.dcproject.common.entity.TargetI
|
||||||
|
import fr.dcproject.common.entity.TargetRef
|
||||||
|
import fr.dcproject.common.entity.Versionable
|
||||||
|
import fr.dcproject.common.entity.VersionableId
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenCartI
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenCreator
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenI
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenRef
|
||||||
|
import fr.dcproject.component.opinion.entity.Opinionable
|
||||||
|
import fr.dcproject.component.opinion.entity.Opinions
|
||||||
|
import fr.dcproject.component.vote.entity.Votable
|
||||||
|
import fr.dcproject.component.vote.entity.VotableImp
|
||||||
|
import fr.dcproject.component.workgroup.database.WorkgroupCart
|
||||||
|
import fr.dcproject.component.workgroup.database.WorkgroupCartI
|
||||||
|
import fr.dcproject.component.workgroup.database.WorkgroupRef
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
data class ArticleForView(
|
||||||
|
override val id: UUID = UUID.randomUUID(),
|
||||||
|
override val title: String,
|
||||||
|
val anonymous: Boolean = true,
|
||||||
|
val content: String,
|
||||||
|
val description: String,
|
||||||
|
val tags: List<String> = emptyList(),
|
||||||
|
override val createdBy: CitizenCreator,
|
||||||
|
override val versionNumber: Int = 0,
|
||||||
|
override val versionId: UUID = UUID.randomUUID(),
|
||||||
|
val workgroup: WorkgroupCart? = null,
|
||||||
|
override val opinions: Opinions = emptyMap(),
|
||||||
|
override val draft: Boolean = false,
|
||||||
|
override val deletedAt: DateTime? = null
|
||||||
|
) : ArticleRef(id),
|
||||||
|
ArticleAuthI<CitizenCreator>,
|
||||||
|
ArticleWithTitleI,
|
||||||
|
Versionable,
|
||||||
|
CreatedAt by CreatedAt.Imp(),
|
||||||
|
DeletedAt by DeletedAt.Imp(deletedAt),
|
||||||
|
VersionableId,
|
||||||
|
Opinionable,
|
||||||
|
Votable by VotableImp() {
|
||||||
|
val lastVersion: Boolean = false
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArticleForUpdateI<C : CitizenRef> : ArticleI, ArticleWithTitleI, VersionableId, TargetI, CreatedBy<C> {
|
||||||
|
val anonymous: Boolean
|
||||||
|
val content: String
|
||||||
|
val description: String
|
||||||
|
val draft: Boolean
|
||||||
|
val workgroup: WorkgroupRef?
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArticleForUpdate(
|
||||||
|
override val id: UUID = UUID.randomUUID(),
|
||||||
|
override val title: String,
|
||||||
|
override val anonymous: Boolean = true,
|
||||||
|
override val content: String,
|
||||||
|
override val description: String,
|
||||||
|
tags: List<String> = emptyList(),
|
||||||
|
override val draft: Boolean = false,
|
||||||
|
override val createdBy: CitizenRef,
|
||||||
|
override val workgroup: WorkgroupRef? = null,
|
||||||
|
override val versionId: UUID = UUID.randomUUID(),
|
||||||
|
override val deletedAt: DateTime? = null,
|
||||||
|
) : ArticleRef(id),
|
||||||
|
ArticleForUpdateI<CitizenRef>,
|
||||||
|
ArticleAuthI<CitizenRef>,
|
||||||
|
VersionableId {
|
||||||
|
val tags: List<String> = tags.distinct()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArticleForListing(
|
||||||
|
id: UUID? = null,
|
||||||
|
override val title: String,
|
||||||
|
override val createdBy: CitizenCreator,
|
||||||
|
override val workgroup: WorkgroupCart? = null,
|
||||||
|
override val deletedAt: DateTime? = null,
|
||||||
|
override val draft: Boolean = false,
|
||||||
|
val lastVersion: Boolean = false
|
||||||
|
) : ArticleForListingI,
|
||||||
|
ArticleRef(id),
|
||||||
|
ArticleAuthI<CitizenCartI>,
|
||||||
|
Votable by VotableImp(),
|
||||||
|
CreatedBy<CitizenCartI>
|
||||||
|
|
||||||
|
interface ArticleForListingI : ArticleWithTitleI, CreatedBy<CitizenCartI> {
|
||||||
|
val workgroup: WorkgroupCartI?
|
||||||
|
}
|
||||||
|
|
||||||
|
open class ArticleRef(
|
||||||
|
id: UUID? = null
|
||||||
|
) : ArticleI, TargetRef(id)
|
||||||
|
|
||||||
|
interface ArticleI : EntityI, TargetI
|
||||||
|
|
||||||
|
interface ArticleWithTitleI : ArticleI {
|
||||||
|
val title: String
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArticleAuthI<U : CitizenI> :
|
||||||
|
ArticleI,
|
||||||
|
CreatedBy<U>,
|
||||||
|
DeletedAt {
|
||||||
|
val draft: Boolean
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package fr.dcproject.component.article.database
|
||||||
|
|
||||||
|
import fr.postgresjson.connexion.Paginated
|
||||||
|
import fr.postgresjson.connexion.Requester
|
||||||
|
import fr.postgresjson.entity.Parameter
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import net.pearx.kasechange.toSnakeCase
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
class ArticleRepository(override var requester: Requester) : RepositoryI {
|
||||||
|
fun findById(id: UUID): ArticleForView? {
|
||||||
|
val function = requester.getFunction("find_article_by_id")
|
||||||
|
return function.selectOne("id" to id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findVersionsById(page: Int = 1, limit: Int = 50, id: UUID): Paginated<ArticleForListing> {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_articles_versions_by_id")
|
||||||
|
.select(page, limit, "id" to id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findVersionsByVersionId(page: Int = 1, limit: Int = 50, versionId: UUID): Paginated<ArticleForListing> {
|
||||||
|
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: RepositoryI.Direction? = null,
|
||||||
|
search: String? = null,
|
||||||
|
filter: Filter = Filter()
|
||||||
|
): Paginated<ArticleForListing> {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_articles")
|
||||||
|
.select(
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
"sort" to sort?.toSnakeCase(),
|
||||||
|
"direction" to direction,
|
||||||
|
"search" to search,
|
||||||
|
"filter" to filter
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun upsert(article: ArticleForUpdate): ArticleForView? {
|
||||||
|
return requester
|
||||||
|
.getFunction("upsert_article")
|
||||||
|
.selectOne("resource" to article)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Filter(
|
||||||
|
val createdById: String? = null,
|
||||||
|
val workgroupId: String? = null
|
||||||
|
) : Parameter
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package fr.dcproject.component.article.database
|
||||||
|
|
||||||
|
import fr.dcproject.common.entity.VersionableId
|
||||||
|
import fr.dcproject.common.utils.contentToString
|
||||||
|
import fr.dcproject.common.utils.getJsonField
|
||||||
|
import fr.dcproject.common.utils.toIso
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenI
|
||||||
|
import fr.dcproject.component.views.ViewRepository
|
||||||
|
import fr.dcproject.component.views.entity.ViewAggregation
|
||||||
|
import org.elasticsearch.client.Request
|
||||||
|
import org.elasticsearch.client.RestClient
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for manage views with elasticsearch
|
||||||
|
*/
|
||||||
|
class ArticleViewRepository <A> (private val restClient: RestClient) : ViewRepository<A> where A : VersionableId, A : ArticleI {
|
||||||
|
/**
|
||||||
|
* Add view on article to elasticsearch
|
||||||
|
*/
|
||||||
|
override fun addView(ip: String, entity: A, citizen: CitizenI?, dateTime: DateTime) {
|
||||||
|
val isLogged = (citizen != null).toString()
|
||||||
|
val ref = citizen?.id ?: UUID.nameUUIDFromBytes(ip.toByteArray())!!
|
||||||
|
val request = Request(
|
||||||
|
"POST",
|
||||||
|
"/views/_doc/"
|
||||||
|
).apply {
|
||||||
|
//language=JSON
|
||||||
|
setJsonEntity(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"logged": $isLogged,
|
||||||
|
"type": "article",
|
||||||
|
"user_ref": "$ref",
|
||||||
|
"ip": "$ip",
|
||||||
|
"id": "${entity.id}",
|
||||||
|
"version_id": "${entity.versionId}",
|
||||||
|
"citizen_id": "${citizen?.id}",
|
||||||
|
"view_at": "${dateTime.toIso()}"
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
restClient.performRequest(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get article views aggregations from elasticsearch
|
||||||
|
*/
|
||||||
|
override fun getViewsCount(entity: A): ViewAggregation {
|
||||||
|
val request = Request(
|
||||||
|
"GET",
|
||||||
|
"/views/_search"
|
||||||
|
).apply {
|
||||||
|
//language=JSON
|
||||||
|
setJsonEntity(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"size": 0,
|
||||||
|
"query": {
|
||||||
|
"bool": {
|
||||||
|
"must": {
|
||||||
|
"term": {
|
||||||
|
"version_id": "${entity.versionId}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"aggs" : {
|
||||||
|
"total": {
|
||||||
|
"composite" : {
|
||||||
|
"sources" : [
|
||||||
|
{ "version_id": { "terms": {"field": "version_id" } } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unique" : {
|
||||||
|
"cardinality" : {
|
||||||
|
"field" : "user_ref",
|
||||||
|
"precision_threshold": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return restClient
|
||||||
|
.performRequest(request).contentToString().run {
|
||||||
|
ViewAggregation(
|
||||||
|
getJsonField("$.aggregations.total.buckets[0].doc_count") ?: 0,
|
||||||
|
getJsonField("$.aggregations.unique.value") ?: 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package fr.dcproject.component.article.routes
|
||||||
|
|
||||||
|
import fr.dcproject.common.response.toOutput
|
||||||
|
import fr.dcproject.common.security.assert
|
||||||
|
import fr.dcproject.component.article.ArticleAccessControl
|
||||||
|
import fr.dcproject.component.article.database.ArticleForListing
|
||||||
|
import fr.dcproject.component.article.database.ArticleRef
|
||||||
|
import fr.dcproject.component.article.database.ArticleRepository
|
||||||
|
import fr.dcproject.component.auth.citizenOrNull
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.locations.Location
|
||||||
|
import io.ktor.locations.get
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
|
object FindArticleVersions {
|
||||||
|
@Location("/articles/{article}/versions")
|
||||||
|
class ArticleVersionsRequest(
|
||||||
|
article: UUID,
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50,
|
||||||
|
val sort: String? = null,
|
||||||
|
val direction: RepositoryI.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
|
||||||
|
val article = ArticleRef(article)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ArticleRepository.findVersions(request: ArticleVersionsRequest) =
|
||||||
|
findVersionsById(request.page, request.limit, request.article.id)
|
||||||
|
|
||||||
|
fun Route.findArticleVersions(repo: ArticleRepository, ac: ArticleAccessControl) {
|
||||||
|
get<ArticleVersionsRequest> {
|
||||||
|
repo.findVersions(it)
|
||||||
|
.apply { ac.assert { canView(result, citizenOrNull) } }
|
||||||
|
.run {
|
||||||
|
call.respond(
|
||||||
|
toOutput { a: ArticleForListing ->
|
||||||
|
object {
|
||||||
|
val id = a.id
|
||||||
|
val title = a.title
|
||||||
|
val createdBy = object {
|
||||||
|
val id = a.createdBy.id
|
||||||
|
val name = a.createdBy.name.let { n ->
|
||||||
|
object {
|
||||||
|
val firstName = n.firstName
|
||||||
|
val lastName = n.lastName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val email = a.createdBy.email
|
||||||
|
}
|
||||||
|
val workgroup = a.workgroup?.let { w ->
|
||||||
|
object {
|
||||||
|
val id = w.id
|
||||||
|
val name = w.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val draft = a.draft
|
||||||
|
val lastVersion = a.lastVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package fr.dcproject.component.article.routes
|
||||||
|
|
||||||
|
import fr.dcproject.common.response.toOutput
|
||||||
|
import fr.dcproject.common.security.assert
|
||||||
|
import fr.dcproject.component.article.ArticleAccessControl
|
||||||
|
import fr.dcproject.component.article.database.ArticleForListing
|
||||||
|
import fr.dcproject.component.article.database.ArticleRepository
|
||||||
|
import fr.dcproject.component.auth.citizenOrNull
|
||||||
|
import fr.dcproject.routes.PaginatedRequest
|
||||||
|
import fr.dcproject.routes.PaginatedRequestI
|
||||||
|
import fr.postgresjson.connexion.Paginated
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.locations.Location
|
||||||
|
import io.ktor.locations.get
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
|
object FindArticles {
|
||||||
|
@Location("/articles")
|
||||||
|
class ArticlesRequest(
|
||||||
|
page: Int = 1,
|
||||||
|
limit: Int = 50,
|
||||||
|
val sort: String? = null,
|
||||||
|
val direction: RepositoryI.Direction? = null,
|
||||||
|
val search: String? = null,
|
||||||
|
val createdBy: String? = null,
|
||||||
|
val workgroup: String? = null
|
||||||
|
) : PaginatedRequestI by PaginatedRequest(page, limit)
|
||||||
|
|
||||||
|
private fun ArticleRepository.findArticles(request: ArticlesRequest): Paginated<ArticleForListing> {
|
||||||
|
return find(
|
||||||
|
request.page,
|
||||||
|
request.limit,
|
||||||
|
request.sort,
|
||||||
|
request.direction,
|
||||||
|
request.search,
|
||||||
|
ArticleRepository.Filter(createdById = request.createdBy, workgroupId = request.workgroup)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Route.findArticles(repo: ArticleRepository, ac: ArticleAccessControl) {
|
||||||
|
get<ArticlesRequest> {
|
||||||
|
repo.findArticles(it)
|
||||||
|
.apply { ac.assert { canView(result, citizenOrNull) } }
|
||||||
|
.let {
|
||||||
|
call.respond(
|
||||||
|
it.toOutput {
|
||||||
|
object {
|
||||||
|
val id = it.id
|
||||||
|
val title = it.title
|
||||||
|
val createdBy: Any = it.createdBy.toOutput()
|
||||||
|
val workgroup = it.workgroup?.let {
|
||||||
|
object {
|
||||||
|
val id = it.id
|
||||||
|
val name = it.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val draft = it.draft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package fr.dcproject.component.article.routes
|
||||||
|
|
||||||
|
import fr.dcproject.common.security.assert
|
||||||
|
import fr.dcproject.component.article.ArticleAccessControl
|
||||||
|
import fr.dcproject.component.article.database.ArticleForView
|
||||||
|
import fr.dcproject.component.article.database.ArticleRef
|
||||||
|
import fr.dcproject.component.article.database.ArticleRepository
|
||||||
|
import fr.dcproject.component.article.database.ArticleViewRepository
|
||||||
|
import fr.dcproject.component.auth.citizenOrNull
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.features.NotFoundException
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.locations.Location
|
||||||
|
import io.ktor.locations.get
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
|
object GetOneArticle {
|
||||||
|
@Location("/articles/{article}")
|
||||||
|
class ArticleRequest(article: UUID) {
|
||||||
|
val article = ArticleRef(article)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Route.getOneArticle(viewRepository: ArticleViewRepository<ArticleForView>, ac: ArticleAccessControl, repo: ArticleRepository) {
|
||||||
|
get<ArticleRequest> {
|
||||||
|
val article: ArticleForView = repo.findById(it.article.id) ?: throw NotFoundException("Article ${it.article.id} not found")
|
||||||
|
ac.assert { canView(article, citizenOrNull) }
|
||||||
|
|
||||||
|
call.respond(
|
||||||
|
article.let { a ->
|
||||||
|
object {
|
||||||
|
val id = a.id
|
||||||
|
val versionId = a.versionId
|
||||||
|
val versionNumber = a.versionNumber
|
||||||
|
val title = a.title
|
||||||
|
val anonymous = a.anonymous
|
||||||
|
val content = a.content
|
||||||
|
val description = a.description
|
||||||
|
val tags = a.tags
|
||||||
|
val draft = a.draft
|
||||||
|
val lastVersion = a.lastVersion
|
||||||
|
val createdAt = a.createdAt
|
||||||
|
val createdBy: Any = object {
|
||||||
|
val id: UUID = a.createdBy.id
|
||||||
|
val name: Any = object {
|
||||||
|
val firstName: String = a.createdBy.name.firstName
|
||||||
|
val lastName: String = a.createdBy.name.lastName
|
||||||
|
}
|
||||||
|
val email: String = a.createdBy.email
|
||||||
|
}
|
||||||
|
val workgroup: Any? = a.workgroup?.let { w ->
|
||||||
|
object {
|
||||||
|
val id: UUID = w.id
|
||||||
|
val name: String = w.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val votes: Any = object {
|
||||||
|
val up: Int = a.votes.up
|
||||||
|
val neutral: Int = a.votes.neutral
|
||||||
|
val down: Int = a.votes.down
|
||||||
|
val total: Int = a.votes.total
|
||||||
|
val score: Int = a.votes.score
|
||||||
|
}
|
||||||
|
val views: Any = viewRepository.getViewsCount(article).let { v ->
|
||||||
|
object {
|
||||||
|
val total = v.total
|
||||||
|
val unique = v.unique
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val opinions: Map<String, Int> = a.opinions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
launch {
|
||||||
|
viewRepository.addView(call.request.local.remoteHost, article, citizenOrNull)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package fr.dcproject.component.article.routes
|
||||||
|
|
||||||
|
import fr.dcproject.common.security.assert
|
||||||
|
import fr.dcproject.common.utils.receiveOrBadRequest
|
||||||
|
import fr.dcproject.component.article.ArticleAccessControl
|
||||||
|
import fr.dcproject.component.article.database.ArticleForUpdate
|
||||||
|
import fr.dcproject.component.article.database.ArticleRepository
|
||||||
|
import fr.dcproject.component.article.routes.UpsertArticle.UpsertArticleRequest.Input
|
||||||
|
import fr.dcproject.component.auth.citizen
|
||||||
|
import fr.dcproject.component.auth.citizenOrNull
|
||||||
|
import fr.dcproject.component.auth.mustBeAuth
|
||||||
|
import fr.dcproject.component.notification.ArticleUpdateNotification
|
||||||
|
import fr.dcproject.component.notification.Publisher
|
||||||
|
import fr.dcproject.component.workgroup.database.WorkgroupRef
|
||||||
|
import io.ktor.application.ApplicationCall
|
||||||
|
import io.ktor.application.call
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.locations.Location
|
||||||
|
import io.ktor.locations.post
|
||||||
|
import io.ktor.response.respond
|
||||||
|
import io.ktor.routing.Route
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
|
object UpsertArticle {
|
||||||
|
@Location("/articles")
|
||||||
|
class UpsertArticleRequest {
|
||||||
|
class Input(
|
||||||
|
val id: UUID?,
|
||||||
|
val title: String,
|
||||||
|
val anonymous: Boolean = true,
|
||||||
|
val content: String,
|
||||||
|
val description: String,
|
||||||
|
val tags: List<String> = emptyList(),
|
||||||
|
val draft: Boolean = false,
|
||||||
|
val versionId: UUID,
|
||||||
|
val workgroup: WorkgroupRef? = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Route.upsertArticle(repo: ArticleRepository, publisher: Publisher, ac: ArticleAccessControl) {
|
||||||
|
suspend fun ApplicationCall.convertRequestToEntity(): ArticleForUpdate = receiveOrBadRequest<Input>().run {
|
||||||
|
ArticleForUpdate(
|
||||||
|
id = id ?: UUID.randomUUID(),
|
||||||
|
title = title,
|
||||||
|
anonymous = anonymous,
|
||||||
|
content = content,
|
||||||
|
description = description,
|
||||||
|
tags = tags,
|
||||||
|
draft = draft,
|
||||||
|
createdBy = citizen,
|
||||||
|
workgroup = workgroup,
|
||||||
|
versionId = versionId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
post<UpsertArticleRequest> {
|
||||||
|
mustBeAuth()
|
||||||
|
val article = call.convertRequestToEntity()
|
||||||
|
ac.assert { canUpsert(article, citizenOrNull) }
|
||||||
|
repo.upsert(article)?.let { a ->
|
||||||
|
call.respond(
|
||||||
|
object {
|
||||||
|
val id: UUID = a.id
|
||||||
|
val versionId = a.versionId
|
||||||
|
val versionNumber = a.versionNumber
|
||||||
|
}
|
||||||
|
)
|
||||||
|
publisher.publish(ArticleUpdateNotification(a))
|
||||||
|
} ?: error("Article not updated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package fr.dcproject.component.article.routes
|
||||||
|
|
||||||
|
import fr.dcproject.component.article.routes.FindArticleVersions.findArticleVersions
|
||||||
|
import fr.dcproject.component.article.routes.FindArticles.findArticles
|
||||||
|
import fr.dcproject.component.article.routes.GetOneArticle.getOneArticle
|
||||||
|
import fr.dcproject.component.article.routes.UpsertArticle.upsertArticle
|
||||||
|
import io.ktor.auth.authenticate
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
import io.ktor.routing.Routing
|
||||||
|
import org.koin.ktor.ext.get
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
|
fun Routing.installArticleRoutes() {
|
||||||
|
authenticate(optional = true) {
|
||||||
|
findArticles(get(), get())
|
||||||
|
findArticleVersions(get(), get())
|
||||||
|
getOneArticle(get(), get(), get())
|
||||||
|
upsertArticle(get(), get(), get())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package fr.dcproject.component.auth
|
||||||
|
|
||||||
|
import fr.dcproject.component.auth.database.User
|
||||||
|
import fr.dcproject.component.auth.database.UserI
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenRepository
|
||||||
|
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.component.citizen.database.Citizen as CitizenEntity
|
||||||
|
|
||||||
|
class ForbiddenException(message: String) : Exception(message)
|
||||||
|
|
||||||
|
private val citizenAttributeKey = AttributeKey<CitizenEntity>("CitizenContext")
|
||||||
|
|
||||||
|
val ApplicationCall.citizen: CitizenEntity
|
||||||
|
get() = attributes.computeIfAbsent(citizenAttributeKey) {
|
||||||
|
val user = authentication.principal<UserI>() ?: throw ForbiddenException("No User Connected")
|
||||||
|
GlobalContext.get().koin.get<CitizenRepository>().findByUser(user)
|
||||||
|
?: 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 ApplicationCall.isAuth: Boolean
|
||||||
|
get() = citizenOrNull == null
|
||||||
|
|
||||||
|
fun ApplicationCall.mustBeAuth() {
|
||||||
|
citizenOrNull ?: throw ForbiddenException("No User Connected")
|
||||||
|
}
|
||||||
|
|
||||||
|
val PipelineContext<Unit, ApplicationCall>.citizen get() = context.citizen
|
||||||
|
val PipelineContext<Unit, ApplicationCall>.citizenOrNull get() = context.citizenOrNull
|
||||||
|
|
||||||
|
val ApplicationCall.user get() = authentication.principal<User>()
|
||||||
|
|
||||||
|
val PipelineContext<Unit, ApplicationCall>.isAuth: Boolean
|
||||||
|
get() = citizenOrNull == null
|
||||||
|
|
||||||
|
fun PipelineContext<Unit, ApplicationCall>.mustBeAuth() {
|
||||||
|
citizenOrNull ?: throw ForbiddenException("No User Connected")
|
||||||
|
}
|
||||||
15
src/main/kotlin/fr/dcproject/component/auth/KoinModule.kt
Normal file
15
src/main/kotlin/fr/dcproject/component/auth/KoinModule.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package fr.dcproject.component.auth
|
||||||
|
|
||||||
|
import fr.dcproject.application.Configuration
|
||||||
|
import fr.dcproject.common.email.Mailer
|
||||||
|
import fr.dcproject.component.auth.database.UserRepository
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val authKoinModule = module {
|
||||||
|
single { UserRepository(get()) }
|
||||||
|
// Used to send a connexion link by email
|
||||||
|
single {
|
||||||
|
val config: Configuration = get()
|
||||||
|
PasswordlessAuth(get<Mailer>(), config.domain, get())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package fr.dcproject.component.auth
|
||||||
|
|
||||||
|
import com.sendgrid.helpers.mail.Mail
|
||||||
|
import com.sendgrid.helpers.mail.objects.Content
|
||||||
|
import com.sendgrid.helpers.mail.objects.Email
|
||||||
|
import fr.dcproject.common.email.Mailer
|
||||||
|
import fr.dcproject.component.auth.jwt.makeToken
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenRepository
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenWithEmail
|
||||||
|
import fr.dcproject.component.citizen.database.CitizenWithUserI
|
||||||
|
import io.ktor.http.URLBuilder
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a connexion link by email
|
||||||
|
*/
|
||||||
|
class PasswordlessAuth(
|
||||||
|
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 <C> sendEmail(citizen: C, url: String) where C : CitizenWithEmail, C : CitizenWithUserI {
|
||||||
|
mailer.sendEmail {
|
||||||
|
val token = citizen.user.makeToken()
|
||||||
|
Mail(
|
||||||
|
Email("passwordless-auth@$domain"),
|
||||||
|
"Connection",
|
||||||
|
Email(citizen.email),
|
||||||
|
Content("text/plain", generateContent(token, url))
|
||||||
|
).apply {
|
||||||
|
addContent(Content("text/html", generateHtmlContent(token, url)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateHtmlContent(token: String, url: String): String? {
|
||||||
|
val urlObject = URLBuilder(url)
|
||||||
|
urlObject.parameters.append("token", token)
|
||||||
|
return "Click <a href=\"${urlObject.buildString()}\">here</a> for connect to $domain"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateContent(token: String, url: String): String {
|
||||||
|
val urlObject = URLBuilder(url)
|
||||||
|
urlObject.parameters.append("token", token)
|
||||||
|
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)
|
||||||
|
}
|
||||||
61
src/main/kotlin/fr/dcproject/component/auth/database/User.kt
Normal file
61
src/main/kotlin/fr/dcproject/component/auth/database/User.kt
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package fr.dcproject.component.auth.database
|
||||||
|
|
||||||
|
import fr.dcproject.common.entity.CreatedAt
|
||||||
|
import fr.dcproject.common.entity.Entity
|
||||||
|
import fr.dcproject.common.entity.EntityI
|
||||||
|
import fr.dcproject.common.entity.UpdatedAt
|
||||||
|
import fr.dcproject.component.auth.database.UserI.Roles
|
||||||
|
import io.ktor.auth.Principal
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
class UserForCreate(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
username: String,
|
||||||
|
override val password: String,
|
||||||
|
blockedAt: DateTime? = null,
|
||||||
|
roles: List<Roles> = emptyList()
|
||||||
|
) : User(id, username, blockedAt, roles),
|
||||||
|
UserWithPasswordI
|
||||||
|
|
||||||
|
open class User(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
override var username: String,
|
||||||
|
var blockedAt: DateTime? = null,
|
||||||
|
var roles: List<Roles> = emptyList()
|
||||||
|
) : UserRef(id),
|
||||||
|
UserWithUsername,
|
||||||
|
CreatedAt by CreatedAt.Imp(),
|
||||||
|
UpdatedAt by UpdatedAt.Imp()
|
||||||
|
|
||||||
|
class UserCreator(
|
||||||
|
id: UUID = UUID.randomUUID(),
|
||||||
|
override val username: String,
|
||||||
|
) : UserRef(id), UserWithUsername
|
||||||
|
|
||||||
|
interface UserWithUsername : UserI {
|
||||||
|
val username: String
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserWithPasswordI : UserI {
|
||||||
|
val password: String
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserWithPassword(
|
||||||
|
id: UUID,
|
||||||
|
override val password: String,
|
||||||
|
) : UserWithPasswordI,
|
||||||
|
UserRef(id)
|
||||||
|
|
||||||
|
open class UserRef(
|
||||||
|
id: UUID = UUID.randomUUID()
|
||||||
|
) : UserI, Entity(id)
|
||||||
|
|
||||||
|
interface UserI : EntityI, Principal {
|
||||||
|
enum class Roles { ROLE_USER, ROLE_ADMIN }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserForAuthI : UserI {
|
||||||
|
var roles: List<Roles>
|
||||||
|
var blockedAt: DateTime?
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package fr.dcproject.component.auth.database
|
||||||
|
|
||||||
|
import fr.postgresjson.connexion.Requester
|
||||||
|
import fr.postgresjson.repository.RepositoryI
|
||||||
|
import io.ktor.auth.UserPasswordCredential
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
class UserRepository(override var requester: Requester) : RepositoryI {
|
||||||
|
fun findByCredentials(credentials: UserPasswordCredential): User? {
|
||||||
|
return requester
|
||||||
|
.getFunction("check_user")
|
||||||
|
.selectOne(
|
||||||
|
"username" to credentials.name,
|
||||||
|
"password" to credentials.password
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findById(id: UUID): User {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_user_by_id")
|
||||||
|
.selectOne(
|
||||||
|
"id" to id
|
||||||
|
) ?: throw UserNotFound(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insert(user: User): User? {
|
||||||
|
return requester
|
||||||
|
.getFunction("insert_user")
|
||||||
|
.selectOne("resource" to user)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changePassword(user: UserWithPassword) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user