mirror of
https://github.com/UniversityOfHelsinkiCS/oodikone.git
synced 2026-10-12 19:03:39 +03:00
Merge userservice to backend
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
name: "Release oodikone to production"
|
||||
name: 'Release oodikone to production'
|
||||
|
||||
on:
|
||||
release:
|
||||
@@ -6,23 +6,23 @@ on:
|
||||
|
||||
jobs:
|
||||
build_and_release_backend:
|
||||
name: "Build and release backend"
|
||||
name: 'Build and release backend'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
- name: "Login to DockerHub"
|
||||
- name: 'Login to DockerHub'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: "Extract release version"
|
||||
- name: 'Extract release version'
|
||||
# Github context (e.g. github.ref) only has ref formatted as /refs/tags/vX.X.X
|
||||
# This steps extracts release version from GITHUB_REF env var
|
||||
# See: https://github.community/t/how-to-get-just-the-tag-name/16241/10
|
||||
id: extract_version
|
||||
run: echo ::set-output name=VERSION::"${GITHUB_REF#refs/tags/}"
|
||||
- name: "Build and push"
|
||||
- name: 'Build and push'
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
build-args: |
|
||||
@@ -38,26 +38,26 @@ jobs:
|
||||
target: production
|
||||
|
||||
build_and_release_frontend:
|
||||
name: "Build and release frontend"
|
||||
name: 'Build and release frontend'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
- name: "Login to DockerHub"
|
||||
- name: 'Login to DockerHub'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: "Get current time"
|
||||
- name: 'Get current time'
|
||||
id: get_current_time
|
||||
run: echo ::set-output name=NOW::"$(date --iso-8601=seconds)"
|
||||
- name: "Extract release version"
|
||||
- name: 'Extract release version'
|
||||
# Github context (e.g. github.ref) only has ref formatted as /refs/tags/vX.X.X
|
||||
# This steps extracts release version from GITHUB_REF env var
|
||||
# See: https://github.community/t/how-to-get-just-the-tag-name/16241/10
|
||||
id: extract_version
|
||||
run: echo ::set-output name=VERSION::"${GITHUB_REF#refs/tags/}"
|
||||
- name: "Build and push"
|
||||
- name: 'Build and push'
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
build-args: |
|
||||
@@ -72,11 +72,11 @@ jobs:
|
||||
push: true
|
||||
tags: toska/oodikone2-frontend:latest
|
||||
target: production
|
||||
- name: "Create a container from built image "
|
||||
- name: 'Create a container from built image '
|
||||
run: docker create --name frontend toska/oodikone2-frontend:latest
|
||||
- name: "Copy assets from the container"
|
||||
- name: 'Copy assets from the container'
|
||||
run: docker cp frontend:/usr/src/app/build ./build
|
||||
- name: "Create Sentry release"
|
||||
- name: 'Create Sentry release'
|
||||
uses: getsentry/action-release@v1
|
||||
env:
|
||||
SENTRY_URL: ${{ secrets.SENTRY_URL }}
|
||||
@@ -90,48 +90,15 @@ jobs:
|
||||
url_prefix: '~/static/js'
|
||||
version: ${{ steps.extract_version.outputs.VERSION }}
|
||||
|
||||
build_and_release_userservice:
|
||||
name: "Build and release userservice"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
- name: "Login to DockerHub"
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: "Extract release version"
|
||||
# Github context (e.g. github.ref) only has ref formatted as /refs/tags/vX.X.X
|
||||
# This steps extracts release version from GITHUB_REF env var
|
||||
# See: https://github.community/t/how-to-get-just-the-tag-name/16241/10
|
||||
id: extract_version
|
||||
run: echo ::set-output name=VERSION::"${GITHUB_REF#refs/tags/}"
|
||||
- name: "Build and push"
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
build-args: |
|
||||
SENTRY_RELEASE=${{ steps.extract_version.outputs.VERSION }}
|
||||
SENTRY_ENVIRONMENT=production
|
||||
cache-from: |
|
||||
type=registry,ref=toska/oodikone2-userservice:staging
|
||||
type=registry,ref=toska/oodikone2-userservice:latest
|
||||
cache-to: type=inline
|
||||
context: services/userservice
|
||||
push: true
|
||||
tags: toska/oodikone2-userservice:latest
|
||||
target: production
|
||||
|
||||
report_success:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Report deployment success to oodikone notifications"
|
||||
name: 'Report deployment success to oodikone notifications'
|
||||
needs:
|
||||
- build_and_release_backend
|
||||
- build_and_release_frontend
|
||||
- build_and_release_userservice
|
||||
if: success()
|
||||
steps:
|
||||
- name: "Send notification to slack"
|
||||
- name: 'Send notification to slack'
|
||||
uses: UniversityOfHelsinkiCS/slackbot-action@v1.4.2
|
||||
with:
|
||||
webhook-url: ${{ secrets.WEBHOOK_URL }}
|
||||
@@ -141,14 +108,13 @@ jobs:
|
||||
|
||||
report_failure:
|
||||
runs-on: ubuntu-latest
|
||||
name: "Report deployment failure to oodikone notifications"
|
||||
name: 'Report deployment failure to oodikone notifications'
|
||||
needs:
|
||||
- build_and_release_backend
|
||||
- build_and_release_frontend
|
||||
- build_and_release_userservice
|
||||
if: failure()
|
||||
steps:
|
||||
- name: "Send notification to slack"
|
||||
- name: 'Send notification to slack'
|
||||
uses: UniversityOfHelsinkiCS/slackbot-action@v1.4.2
|
||||
with:
|
||||
webhook-url: ${{ secrets.WEBHOOK_URL }}
|
||||
|
||||
@@ -45,19 +45,6 @@ jobs:
|
||||
'eslint --ext .jsx,.js services/frontend' \
|
||||
'stylelint services/frontend/src/**/*.css'
|
||||
|
||||
lint_userservice:
|
||||
name: 'Lint userservice'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
- name: 'Install dependencies'
|
||||
run: npm ci && cd services/userservice && npm ci
|
||||
- name: 'Run frontend linter'
|
||||
run: npm run eslint services/userservice
|
||||
|
||||
# === Build images ===
|
||||
build_backend:
|
||||
name: 'Build backend'
|
||||
@@ -115,32 +102,6 @@ jobs:
|
||||
with:
|
||||
path: /tmp/frontend.tar
|
||||
|
||||
build_userservice:
|
||||
name: 'Build userservice'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: docker/setup-buildx-action@v1
|
||||
- name: 'Build image and export it'
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
build-args: |
|
||||
SENTRY_RELEASE=${{ github.sha }}
|
||||
SENTRY_ENVIRONMENT=staging
|
||||
cache-from: |
|
||||
type=registry,ref=toska/oodikone2-userservice:staging
|
||||
type=registry,ref=toska/oodikone2-userservice:latest
|
||||
cache-to: type=inline
|
||||
context: services/userservice
|
||||
push: false
|
||||
tags: toska/oodikone2-userservice:staging
|
||||
target: production
|
||||
outputs: type=docker,dest=/tmp/userservice.tar
|
||||
- name: 'Upload image for other jobs'
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
path: /tmp/userservice.tar
|
||||
|
||||
build_and_push_anon_sis_db:
|
||||
name: 'Build anon sis-db and push to toska hub if new migrations have been added'
|
||||
runs-on: ubuntu-latest
|
||||
@@ -201,7 +162,7 @@ jobs:
|
||||
with:
|
||||
filters: |
|
||||
migrations:
|
||||
- 'services/userservice/src/database/migrations/**'
|
||||
- 'services/backend/src/database/migrations_user/**'
|
||||
- '.github/workflows/test-oodikone.yaml'
|
||||
- name: 'Login to toska docker registry'
|
||||
uses: docker/login-action@v1
|
||||
@@ -209,31 +170,19 @@ jobs:
|
||||
registry: registry.toska.cs.helsinki.fi
|
||||
username: ${{ secrets.TOSKAREGISTRY_USERNAME }}
|
||||
password: ${{ secrets.TOSKAREGISTRY_PASSWORD }}
|
||||
- name: 'Let userservice run migrations, then push new version of image to toska hub'
|
||||
- name: 'Let backend run migrations, then push new version of image to toska hub'
|
||||
if: steps.check_migrations.outputs.migrations == 'true'
|
||||
run: |
|
||||
docker-compose --file docker-compose.ci.yml up --detach userservice
|
||||
docker-compose --file docker-compose.ci.yml up --detach backend
|
||||
sleep 30 # wait for sis-updater-worker to start and run migrations
|
||||
./anonyymioodi/commit_and_push_to_toska_hub.sh user-db
|
||||
|
||||
# === Integration tests ===
|
||||
#
|
||||
run_userservice_integration_tests:
|
||||
name: 'Run userservice integration tests'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: 'Run tests included in docker build'
|
||||
run: docker-compose --file docker-compose.test.yml up --build userservice-test
|
||||
|
||||
# === End-to-end tests ===
|
||||
|
||||
test_course_statistics:
|
||||
name: 'Test course statistics'
|
||||
needs:
|
||||
- build_backend
|
||||
- build_frontend
|
||||
- build_userservice
|
||||
- build_and_push_anon_sis_db
|
||||
- build_and_push_anon_kone_db
|
||||
- build_and_push_anon_user_db
|
||||
@@ -248,7 +197,6 @@ jobs:
|
||||
run: |
|
||||
docker load --input /tmp/artifact/backend.tar
|
||||
docker load --input /tmp/artifact/frontend.tar
|
||||
docker load --input /tmp/artifact/userservice.tar
|
||||
- name: 'Login to toska docker registry'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
@@ -279,7 +227,6 @@ jobs:
|
||||
needs:
|
||||
- build_backend
|
||||
- build_frontend
|
||||
- build_userservice
|
||||
- build_and_push_anon_sis_db
|
||||
- build_and_push_anon_kone_db
|
||||
- build_and_push_anon_user_db
|
||||
@@ -294,7 +241,6 @@ jobs:
|
||||
run: |
|
||||
docker load --input /tmp/artifact/backend.tar
|
||||
docker load --input /tmp/artifact/frontend.tar
|
||||
docker load --input /tmp/artifact/userservice.tar
|
||||
- name: 'Login to toska docker registry'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
@@ -325,7 +271,6 @@ jobs:
|
||||
needs:
|
||||
- build_backend
|
||||
- build_frontend
|
||||
- build_userservice
|
||||
- build_and_push_anon_sis_db
|
||||
- build_and_push_anon_kone_db
|
||||
- build_and_push_anon_user_db
|
||||
@@ -340,7 +285,6 @@ jobs:
|
||||
run: |
|
||||
docker load --input /tmp/artifact/backend.tar
|
||||
docker load --input /tmp/artifact/frontend.tar
|
||||
docker load --input /tmp/artifact/userservice.tar
|
||||
- name: 'Login to toska docker registry'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
@@ -371,7 +315,6 @@ jobs:
|
||||
needs:
|
||||
- build_backend
|
||||
- build_frontend
|
||||
- build_userservice
|
||||
- build_and_push_anon_sis_db
|
||||
- build_and_push_anon_kone_db
|
||||
- build_and_push_anon_user_db
|
||||
@@ -386,7 +329,6 @@ jobs:
|
||||
run: |
|
||||
docker load --input /tmp/artifact/backend.tar
|
||||
docker load --input /tmp/artifact/frontend.tar
|
||||
docker load --input /tmp/artifact/userservice.tar
|
||||
- name: 'Login to toska docker registry'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
@@ -417,7 +359,6 @@ jobs:
|
||||
needs:
|
||||
- build_backend
|
||||
- build_frontend
|
||||
- build_userservice
|
||||
- build_and_push_anon_sis_db
|
||||
- build_and_push_anon_kone_db
|
||||
- build_and_push_anon_user_db
|
||||
@@ -432,7 +373,6 @@ jobs:
|
||||
run: |
|
||||
docker load --input /tmp/artifact/backend.tar
|
||||
docker load --input /tmp/artifact/frontend.tar
|
||||
docker load --input /tmp/artifact/userservice.tar
|
||||
- name: 'Login to toska docker registry'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
@@ -463,7 +403,6 @@ jobs:
|
||||
needs:
|
||||
- build_backend
|
||||
- build_frontend
|
||||
- build_userservice
|
||||
- build_and_push_anon_sis_db
|
||||
- build_and_push_anon_kone_db
|
||||
- build_and_push_anon_user_db
|
||||
@@ -478,7 +417,6 @@ jobs:
|
||||
run: |
|
||||
docker load --input /tmp/artifact/backend.tar
|
||||
docker load --input /tmp/artifact/frontend.tar
|
||||
docker load --input /tmp/artifact/userservice.tar
|
||||
- name: 'Login to toska docker registry'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
@@ -509,7 +447,6 @@ jobs:
|
||||
needs:
|
||||
- build_backend
|
||||
- build_frontend
|
||||
- build_userservice
|
||||
- build_and_push_anon_sis_db
|
||||
- build_and_push_anon_kone_db
|
||||
- build_and_push_anon_user_db
|
||||
@@ -524,7 +461,6 @@ jobs:
|
||||
run: |
|
||||
docker load --input /tmp/artifact/backend.tar
|
||||
docker load --input /tmp/artifact/frontend.tar
|
||||
docker load --input /tmp/artifact/userservice.tar
|
||||
- name: 'Login to toska docker registry'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
@@ -559,7 +495,6 @@ jobs:
|
||||
needs:
|
||||
- build_backend
|
||||
- build_frontend
|
||||
- build_userservice
|
||||
- build_and_push_anon_sis_db
|
||||
- build_and_push_anon_kone_db
|
||||
- build_and_push_anon_user_db
|
||||
@@ -574,7 +509,6 @@ jobs:
|
||||
run: |
|
||||
docker load --input /tmp/artifact/backend.tar
|
||||
docker load --input /tmp/artifact/frontend.tar
|
||||
docker load --input /tmp/artifact/userservice.tar
|
||||
- name: 'Login to toska docker registry'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
@@ -602,8 +536,6 @@ jobs:
|
||||
needs:
|
||||
- lint_backend
|
||||
- lint_frontend
|
||||
- lint_userservice
|
||||
- run_userservice_integration_tests
|
||||
- test_course_statistics
|
||||
- test_custom_population
|
||||
- test_filters
|
||||
@@ -622,7 +554,6 @@ jobs:
|
||||
run: |
|
||||
docker load --input /tmp/artifact/backend.tar
|
||||
docker load --input /tmp/artifact/frontend.tar
|
||||
docker load --input /tmp/artifact/userservice.tar
|
||||
- name: 'Login to DockerHub'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
@@ -649,7 +580,6 @@ jobs:
|
||||
run: |
|
||||
docker push toska/oodikone2-backend:staging
|
||||
docker push toska/oodikone2-frontend:staging
|
||||
docker push toska/oodikone2-userservice:staging
|
||||
- name: 'Send deployment success notification to slack'
|
||||
if: success()
|
||||
uses: UniversityOfHelsinkiCS/slackbot-action@v1.4.2
|
||||
@@ -672,8 +602,6 @@ jobs:
|
||||
needs:
|
||||
- lint_backend
|
||||
- lint_frontend
|
||||
- lint_userservice
|
||||
- run_userservice_integration_tests
|
||||
- test_course_statistics
|
||||
- test_custom_population
|
||||
- test_filters
|
||||
|
||||
@@ -79,7 +79,7 @@ It is recommended to spend some time to become familiar with `docker` and `docke
|
||||
- `docker-compose ps`: view the containers in the running environment
|
||||
- `docker-compose logs frontend`: print logs for just frontend
|
||||
- `docker-compose logs --follow --tail 100 backend`: print last hundred rows of backend logs and begin to follow them in your terminal window
|
||||
- `docker exec -it userservice sh`: open bash terminal inside userservice container
|
||||
- `docker exec -it backend sh`: open bash terminal inside backend container
|
||||
- `docker exec -it sis-db psql -U postgres sis-db-real`: open psql client to investigate sis real data database.
|
||||
|
||||
### User types for development
|
||||
|
||||
@@ -44,7 +44,7 @@ curl --request GET --url 'http://localhost:8082/v1/students?token=dev'
|
||||
- Ensure the newest user-db/kone-db image is pulled from the registry with `docker pull`
|
||||
- Start oodikone, let it run migrations
|
||||
- Modify db to wanted state. Tips:
|
||||
- Easy way to create new users is to modify dev user details in the frontend API config. Userservice creates a new user during login if the given uid is not present in user-db.
|
||||
- Easy way to create new users is to modify dev user details in the frontend API config. A new user is created during login if the given uid is not present in user-db.
|
||||
|
||||
By default, previous user-db/kone-db images are used as a base. If you want to build the image from a clean Postgres image, just comment out toska hub image and uncomment the clean Postgres image.
|
||||
|
||||
|
||||
+2
-16
@@ -9,17 +9,16 @@ services:
|
||||
- kone-db
|
||||
- redis
|
||||
- sis-db
|
||||
- user-db
|
||||
environment:
|
||||
CI: 'true'
|
||||
DB_URL_USER: postgres://postgres@user-db:5432/user-db
|
||||
DB_URL_KONE: postgres://postgres@kone-db:5432/kone-db
|
||||
FRONT_URL: http://localhost:1337
|
||||
REDIS: redis
|
||||
SECRET_TOKEN: dev
|
||||
SIS_DB_URL: postgres://postgres@sis-db:5432/sis-db
|
||||
SIS_UPDATER_URL: http://sis-updater-scheduler:8082
|
||||
TOKEN_SECRET: IWannaBeTheVeryBest
|
||||
USERSERVICE_SECRET: Luukk4inenApprov3dVitunMinttuSalainenAvain666NurmivaaraPJ2019
|
||||
USERSERVICE_URL: http://userservice:4567
|
||||
image: toska/oodikone2-backend:staging
|
||||
|
||||
frontend:
|
||||
@@ -63,25 +62,12 @@ services:
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
image: registry.toska.cs.helsinki.fi/user-db
|
||||
|
||||
userservice:
|
||||
<<: *defaults
|
||||
container_name: userservice
|
||||
depends_on:
|
||||
- user-db
|
||||
environment:
|
||||
CI: 'true'
|
||||
DB_URL: postgres://postgres@user-db:5432/user-db
|
||||
SECRET: Luukk4inenApprov3dVitunMinttuSalainenAvain666NurmivaaraPJ2019
|
||||
TOKEN_SECRET: IWannaBeTheVeryBest
|
||||
image: toska/oodikone2-userservice:staging
|
||||
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
container_name: nginx
|
||||
depends_on:
|
||||
- frontend
|
||||
- backend
|
||||
- userservice
|
||||
ports:
|
||||
- 1337:80
|
||||
volumes:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
services:
|
||||
|
||||
backend:
|
||||
environment:
|
||||
DB_URL_USER: postgres://postgres@user-db:5432/user-db-real
|
||||
DB_URL_KONE: postgres://postgres@kone-db:5432/kone-db-real
|
||||
SIS_DB_URL: postgres://postgres@sis-db:5432/sis-db-real
|
||||
|
||||
@@ -18,9 +18,5 @@ services:
|
||||
DB_URL: postgres://postgres@sis-db:5432/sis-db-real
|
||||
SIS_IMPORTER_DATABASE: sis-importer-db-real
|
||||
|
||||
userservice:
|
||||
environment:
|
||||
DB_URL: postgres://postgres@user-db:5432/user-db-real
|
||||
|
||||
volumes:
|
||||
redis-data-real:
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
services:
|
||||
|
||||
userservice-test:
|
||||
build:
|
||||
context: services/userservice
|
||||
target: test
|
||||
container_name: userservice-test
|
||||
environment:
|
||||
SECRET: Luukk4inenApprov3dVitunMinttuSalainenAvain666NurmivaaraPJ2019
|
||||
TOKEN_SECRET: IWannaBeTheVeryBest
|
||||
+2
-24
@@ -26,18 +26,17 @@ services:
|
||||
- kone-db
|
||||
- redis
|
||||
- sis-db
|
||||
- user-db
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
DB_URL_KONE: postgres://postgres@kone-db:5432/kone-db
|
||||
DB_URL_USER: postgres://postgres@user-db:5432/user-db
|
||||
FRONT_URL: http://localhost:3000
|
||||
REDIS: redis
|
||||
SECRET_TOKEN: dev
|
||||
SIS_DB_URL: postgres://postgres@sis-db:5432/sis-db
|
||||
SIS_UPDATER_URL: http://sis-updater-scheduler:8082
|
||||
TOKEN_SECRET: IWannaBeTheVeryBest
|
||||
USERSERVICE_SECRET: Luukk4inenApprov3dVitunMinttuSalainenAvain666NurmivaaraPJ2019
|
||||
USERSERVICE_URL: http://userservice:4567
|
||||
ports:
|
||||
- 8080:8080
|
||||
profiles:
|
||||
@@ -165,26 +164,6 @@ services:
|
||||
volumes:
|
||||
- user-db-data:/data
|
||||
|
||||
userservice:
|
||||
<<: *defaults
|
||||
build:
|
||||
<<: *build_defaults
|
||||
context: services/userservice
|
||||
container_name: userservice
|
||||
depends_on:
|
||||
- user-db
|
||||
environment:
|
||||
DB_URL: postgres://postgres@user-db:5432/user-db
|
||||
SECRET: Luukk4inenApprov3dVitunMinttuSalainenAvain666NurmivaaraPJ2019
|
||||
TOKEN_SECRET: IWannaBeTheVeryBest
|
||||
ports:
|
||||
- 4567:4567
|
||||
profiles:
|
||||
- oodikone
|
||||
volumes:
|
||||
- userservice-nodemod:/usr/src/app/node_modules
|
||||
- ./services/userservice/:/usr/src/app
|
||||
|
||||
volumes:
|
||||
backend-nodemod:
|
||||
frontend-nodemod:
|
||||
@@ -195,4 +174,3 @@ volumes:
|
||||
sis-updater-scheduler-nodemod:
|
||||
sis-updater-worker-nodemod:
|
||||
user-db-data:
|
||||
userservice-nodemod:
|
||||
|
||||
@@ -165,7 +165,6 @@ set_up_oodikone_from_scratch() {
|
||||
folders_to_set_up=(
|
||||
"$PROJECT_ROOT"
|
||||
"$PROJECT_ROOT/services/frontend"
|
||||
"$PROJECT_ROOT/services/userservice"
|
||||
"$PROJECT_ROOT/services/backend"
|
||||
"$PROJECT_ROOT/updater/sis-updater-scheduler"
|
||||
"$PROJECT_ROOT/updater/sis-updater-worker"
|
||||
|
||||
@@ -36,6 +36,7 @@ const MATOMO_URL = process.env.MATOMO_URL
|
||||
|
||||
// Other stuff
|
||||
const DB_URL_KONE = process.env.DB_URL_KONE
|
||||
const DB_URL_USER = process.env.DB_URL_USER
|
||||
|
||||
let DB_MAX_CONNECTIONS = parseInt(process.env.DB_MAX_CONNECTIONS, 10)
|
||||
if (isNaN(DB_MAX_CONNECTIONS)) {
|
||||
@@ -47,10 +48,8 @@ if (DB_MAX_CRON_CONNECTIONS < 1) {
|
||||
}
|
||||
|
||||
const redis = process.env.REDIS
|
||||
const TOKEN_SECRET = process.env.TOKEN_SECRET
|
||||
const SECRET_TOKEN = process.env.SECRET_TOKEN
|
||||
const DB_SCHEMA_KONE = process.env.DB_SCHEMA_KONE || 'public'
|
||||
const USERSERVICE_URL = process.env.USERSERVICE_URL
|
||||
const SIS_UPDATER_URL = process.env.SIS_UPDATER_URL
|
||||
|
||||
const formatURL = url => {
|
||||
@@ -60,14 +59,13 @@ const formatURL = url => {
|
||||
const addSlashToEnd = url => (url.endsWith('/') ? url : url + '/')
|
||||
|
||||
module.exports = {
|
||||
DB_URL_USER,
|
||||
DB_URL_KONE,
|
||||
DB_MAX_CONNECTIONS,
|
||||
DB_MAX_CRON_CONNECTIONS,
|
||||
redis,
|
||||
TOKEN_SECRET,
|
||||
SECRET_TOKEN,
|
||||
DB_SCHEMA_KONE,
|
||||
USERSERVICE_URL: formatURL(USERSERVICE_URL),
|
||||
requiredGroup,
|
||||
SIS_UPDATER_URL,
|
||||
ANALYTICS_INFLUXDB_URL: formatURL(ANALYTICS_INFLUXDB_URL),
|
||||
|
||||
@@ -46,6 +46,10 @@ const sequelizeKone = new Sequelize(conf.DB_URL_KONE, {
|
||||
})
|
||||
sequelizeKone.query(`SET SESSION search_path to ${conf.DB_SCHEMA_KONE}`)
|
||||
|
||||
const sequelizeUser = new Sequelize(conf.DB_URL_USER, {
|
||||
logging: false,
|
||||
})
|
||||
|
||||
const initializeDatabaseConnection = async () => {
|
||||
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
const waitSeconds = 60
|
||||
@@ -55,7 +59,7 @@ const initializeDatabaseConnection = async () => {
|
||||
break
|
||||
} catch (e) {
|
||||
if (i === waitSeconds) {
|
||||
logger.error(`Could not connect to database in ${waitSeconds} seconds`)
|
||||
logger.error(`Could not connect to kone database in ${waitSeconds} seconds`)
|
||||
throw e
|
||||
}
|
||||
await sleep(1000)
|
||||
@@ -82,11 +86,45 @@ const initializeDatabaseConnection = async () => {
|
||||
logger.error({ message: 'Kone Migration error: ', meta: e })
|
||||
throw e
|
||||
}
|
||||
|
||||
for (let i = 1; i <= waitSeconds; i++) {
|
||||
try {
|
||||
await sequelizeUser.authenticate()
|
||||
break
|
||||
} catch (e) {
|
||||
if (i === waitSeconds) {
|
||||
logger.error(`Could not connect to user database in ${waitSeconds} seconds`)
|
||||
throw e
|
||||
}
|
||||
await sleep(1000)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const migrator = new Umzug({
|
||||
storage: 'sequelize',
|
||||
storageOptions: {
|
||||
sequelize: sequelizeUser,
|
||||
tableName: 'migrations',
|
||||
},
|
||||
migrations: {
|
||||
params: [sequelizeUser.getQueryInterface(), Sequelize],
|
||||
path: `${process.cwd()}/src/database/migrations_user`,
|
||||
pattern: /\.js$/,
|
||||
},
|
||||
})
|
||||
const migrations = await migrator.up()
|
||||
logger.info({ message: 'Kone Migrations up to date', meta: migrations })
|
||||
} catch (e) {
|
||||
logger.error({ message: 'Kone Migration error: ', meta: e })
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
const dbConnections = new DbConnection()
|
||||
module.exports = {
|
||||
dbConnections,
|
||||
sequelizeKone,
|
||||
sequelizeUser,
|
||||
initializeDatabaseConnection,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const { sequelizeUser } = require('../database/connection')
|
||||
|
||||
const UserElementDetails = sequelizeUser.define(
|
||||
'user_elementdetails',
|
||||
{
|
||||
userId: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.BIGINT,
|
||||
},
|
||||
elementDetailCode: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.STRING,
|
||||
},
|
||||
},
|
||||
{
|
||||
tablename: 'user_elementdetails',
|
||||
}
|
||||
)
|
||||
|
||||
const AccessGroup = sequelizeUser.define('access_group', {
|
||||
id: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
},
|
||||
group_code: {
|
||||
type: Sequelize.STRING,
|
||||
unique: true,
|
||||
},
|
||||
group_info: {
|
||||
type: Sequelize.STRING,
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
})
|
||||
|
||||
const UserFaculties = sequelizeUser.define('user_faculties', {
|
||||
userId: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.BIGINT,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id',
|
||||
},
|
||||
},
|
||||
faculty_code: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.STRING,
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
})
|
||||
|
||||
const User = sequelizeUser.define(
|
||||
'users',
|
||||
{
|
||||
id: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
},
|
||||
full_name: { type: Sequelize.STRING },
|
||||
username: {
|
||||
type: Sequelize.STRING,
|
||||
unique: true,
|
||||
},
|
||||
email: { type: Sequelize.STRING },
|
||||
language: { type: Sequelize.STRING },
|
||||
sisu_person_id: { type: Sequelize.STRING },
|
||||
last_login: { type: Sequelize.DATE },
|
||||
},
|
||||
{
|
||||
tableName: 'users',
|
||||
timestamps: false,
|
||||
}
|
||||
)
|
||||
const HyGroup = sequelizeUser.define('hy_group', {
|
||||
id: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
},
|
||||
|
||||
code: { type: Sequelize.STRING },
|
||||
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
})
|
||||
|
||||
User.hasMany(UserElementDetails, { as: 'programme' })
|
||||
UserElementDetails.belongsTo(User)
|
||||
|
||||
User.belongsToMany(AccessGroup, {
|
||||
through: 'user_accessgroup',
|
||||
as: 'accessgroup',
|
||||
})
|
||||
AccessGroup.belongsToMany(User, { through: 'user_accessgroup' })
|
||||
|
||||
User.belongsToMany(HyGroup, { through: 'user_hy_group', as: 'hy_group' })
|
||||
HyGroup.belongsToMany(User, { through: 'user_hy_group' })
|
||||
|
||||
User.hasMany(UserFaculties, { as: 'faculty', foreignKey: 'userId' })
|
||||
|
||||
module.exports = {
|
||||
User,
|
||||
UserElementDetails,
|
||||
AccessGroup,
|
||||
HyGroup,
|
||||
UserFaculties,
|
||||
sequelizeUser,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
const axios = require('axios')
|
||||
const _ = require('lodash')
|
||||
const { USERSERVICE_URL } = require('../conf-backend')
|
||||
const UnitService = require('./units')
|
||||
const elementDetailService = require('./elementdetails')
|
||||
const { filterStudentnumbersByAccessrights } = require('./students')
|
||||
@@ -8,8 +6,8 @@ const { userDataCache } = require('./cache')
|
||||
const { getImporterClient } = require('../util/importerClient')
|
||||
const logger = require('../util/logger')
|
||||
const { facultiesAndProgrammesForTrends } = require('../services/organisations')
|
||||
|
||||
const client = axios.create({ baseURL: USERSERVICE_URL, headers: { secret: process.env.USERSERVICE_SECRET } })
|
||||
const User = require('./users/users')
|
||||
const AccessGroup = require('./users/accessgroups')
|
||||
|
||||
// Private helpers
|
||||
const enrichProgrammesFromFaculties = faculties =>
|
||||
@@ -50,9 +48,18 @@ const checkStudyGuidanceGroupsAccess = async hyPersonSisuId => {
|
||||
|
||||
const byUsernameData = async username => {
|
||||
// OK, enriched
|
||||
const url = `/user/${username}/user_data`
|
||||
const response = await client.get(url)
|
||||
return enrichWithProgrammes(response.data)
|
||||
const user = await User.byUsernameMinified(username)
|
||||
const roles = user.accessgroup.map(({ group_code }) => group_code)
|
||||
const rights = User.getUserProgrammes(user)
|
||||
const faculties = user.faculty.map(({ faculty_code }) => faculty_code)
|
||||
const data = {
|
||||
email: user.email,
|
||||
full_name: user.full_name,
|
||||
roles,
|
||||
rights,
|
||||
faculties,
|
||||
}
|
||||
return enrichWithProgrammes(data)
|
||||
}
|
||||
|
||||
// Exported helpers
|
||||
@@ -95,48 +102,61 @@ const getStudentsUserCanAccess = async (studentnumbers, roles, userId) => {
|
||||
}
|
||||
|
||||
// FOR ROUTES 1-to-1 mapping
|
||||
const modifyAccess = async body => enrichWithProgrammes((await client.post('/modifyaccess', body)).data)
|
||||
const modifyAccess = async body => {
|
||||
const { username, accessgroups } = body
|
||||
await User.modifyRights(username, accessgroups)
|
||||
const user = await User.byUsername(username)
|
||||
return enrichWithProgrammes(User.getUserData(user))
|
||||
}
|
||||
|
||||
const getAccessGroups = async () => enrichWithProgrammes((await client.get('/access_groups')).data)
|
||||
const getAccessGroups = async () => enrichWithProgrammes(await AccessGroup.findAll())
|
||||
|
||||
const enableElementDetails = async (uid, codes) =>
|
||||
enrichWithProgrammes((await client.post('/add_rights', { uid, codes })).data.user)
|
||||
const enableElementDetails = async (uid, codes) => {
|
||||
await User.addProgrammes(uid, codes)
|
||||
const user = await User.byId(uid)
|
||||
return enrichWithProgrammes(User.getUserData(user))
|
||||
}
|
||||
|
||||
const findAll = async () => enrichWithProgrammes((await client.get('/findall')).data)
|
||||
const findAll = async () => enrichWithProgrammes((await User.findAll()).map(User.getUserData))
|
||||
|
||||
const setFaculties = async (uid, faculties) =>
|
||||
enrichWithProgrammes((await client.post('/set_faculties', { uid, faculties })).data.user)
|
||||
const setFaculties = async (uid, faculties) => {
|
||||
await User.setFaculties(uid, faculties)
|
||||
const user = await User.byId(uid)
|
||||
return enrichWithProgrammes(User.getUserData(user))
|
||||
}
|
||||
|
||||
const removeElementDetails = async (uid, codes) =>
|
||||
enrichWithProgrammes((await client.post('/remove_rights', { uid, codes })).data.user)
|
||||
const removeElementDetails = async (uid, codes) => {
|
||||
await User.removeProgrammes(uid, codes)
|
||||
const user = await User.byId(uid)
|
||||
return enrichWithProgrammes(User.getUserData(user))
|
||||
}
|
||||
|
||||
const updateUser = async (username, fields) => {
|
||||
userDataCache.del(username)
|
||||
return enrichWithProgrammes((await client.put(`/user/${username}`, fields)).data)
|
||||
const uid = username
|
||||
const user = await User.byUsername(uid)
|
||||
if (!user) {
|
||||
throw new Error(`User ${uid} not found`)
|
||||
}
|
||||
await User.updateUser(user, fields)
|
||||
const returnedUser = await User.byUsername(username)
|
||||
return enrichWithProgrammes(User.getUserData(returnedUser))
|
||||
}
|
||||
|
||||
const getLoginDataWithoutToken = async (uid, full_name, hyGroups, affiliations, email, hyPersonSisuId) => {
|
||||
const hasStudyGuidanceGroupAccess = await checkStudyGuidanceGroupsAccess(hyPersonSisuId)
|
||||
return (
|
||||
await client.post('/loginwithouttoken', {
|
||||
const result = await User.loginWithoutToken(
|
||||
uid,
|
||||
full_name,
|
||||
hyGroups,
|
||||
affiliations,
|
||||
email,
|
||||
hyPersonSisuId,
|
||||
hasStudyGuidanceGroupAccess,
|
||||
})
|
||||
).data
|
||||
hasStudyGuidanceGroupAccess
|
||||
)
|
||||
return result
|
||||
}
|
||||
|
||||
const superlogin = async (uid, asUser) =>
|
||||
(
|
||||
await client.post('/superlogin', {
|
||||
uid,
|
||||
asUser,
|
||||
})
|
||||
).data
|
||||
const superlogin = async (uid, asUser) => await User.superlogin(uid, asUser)
|
||||
|
||||
module.exports = {
|
||||
updateUser,
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const { AccessGroup } = require('../models/index')
|
||||
const { AccessGroup } = require('../../models/models_user')
|
||||
const { Op } = require('sequelize')
|
||||
|
||||
const byId = id => AccessGroup.findByPk(id)
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const { HyGroup } = require('../models/index')
|
||||
const { HyGroup } = require('../../models/models_user')
|
||||
const { Op } = require('sequelize')
|
||||
|
||||
const byId = id => HyGroup.findByPk(id)
|
||||
+19
-4
@@ -1,13 +1,28 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const _ = require('lodash')
|
||||
const moment = require('moment')
|
||||
const { User, UserElementDetails, AccessGroup, HyGroup, UserFaculties, sequelize } = require('../models')
|
||||
const {
|
||||
User,
|
||||
UserElementDetails,
|
||||
AccessGroup,
|
||||
HyGroup,
|
||||
UserFaculties,
|
||||
sequelizeUser,
|
||||
} = require('../../models/models_user')
|
||||
const AccessService = require('./accessgroups')
|
||||
const HyGroupService = require('./hygroups')
|
||||
const { requiredGroup, courseStatisticsGroup, hyOneGroup } = require('../conf')
|
||||
const Op = Sequelize.Op
|
||||
|
||||
const TOKEN_VERSION = 1.1 // When token structure changes, increment in userservice, backend and frontend
|
||||
// move these to conf
|
||||
const courseStatisticsGroup = 'grp-oodikone-basic-users'
|
||||
const hyOneGroup = 'hy-one'
|
||||
const requiredGroup =
|
||||
process.env.NODE_ENV === 'staging'
|
||||
? ['grp-oodikone-staging-users', 'grp-oodikone-basic-staging-users']
|
||||
: ['grp-oodikone-users', 'grp-oodikone-basic-users']
|
||||
|
||||
// Refactor this token version away
|
||||
const TOKEN_VERSION = 1.1 // When token structure changes, increment in backend and frontend
|
||||
|
||||
// help for refactoring away from token
|
||||
const generateTokenData = async (uid, mockedBy = null) => {
|
||||
@@ -245,7 +260,7 @@ const removeProgrammes = async (uid, codes) => {
|
||||
}
|
||||
|
||||
const setFaculties = async (uid, faculties) => {
|
||||
await sequelize.transaction(async transaction => {
|
||||
await sequelizeUser.transaction(async transaction => {
|
||||
await UserFaculties.destroy({ where: { userId: uid }, transaction })
|
||||
for (const faculty of faculties) {
|
||||
await UserFaculties.create({ userId: uid, faculty_code: faculty }, { transaction })
|
||||
@@ -1,3 +0,0 @@
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
node_modules/
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
FROM node:14.17-alpine3.13 as base
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
FROM base as development
|
||||
ENV NODE_ENV=development
|
||||
EXPOSE 4567
|
||||
COPY ./package* /usr/src/app/
|
||||
RUN npm ci
|
||||
COPY . /usr/src/app
|
||||
CMD ["npm", "run", "dev"]
|
||||
|
||||
FROM base as test
|
||||
ENV NODE_ENV=test
|
||||
RUN apk add --no-cache sqlite=3.34.1-r0 && rm -rf /var/cache/apk/*
|
||||
ENV DB_URL=sqlite::memory:
|
||||
COPY ./package* /usr/src/app/
|
||||
RUN npm ci
|
||||
COPY . /usr/src/app/
|
||||
CMD ["npm", "run", "test"]
|
||||
|
||||
FROM base as production
|
||||
ENV NODE_ENV=production
|
||||
ENV NODE_ENV=production
|
||||
ARG SENTRY_ENVIRONMENT
|
||||
ENV SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
|
||||
ARG SENTRY_RELEASE
|
||||
ENV SENTRY_RELEASE=$SENTRY_RELEASE
|
||||
EXPOSE 4567
|
||||
COPY --chown=node:node ./package* /usr/src/app/
|
||||
RUN npm ci --production
|
||||
COPY --chown=node:node . /usr/src/app/
|
||||
USER node
|
||||
CMD [ "npm", "start" ]
|
||||
@@ -1,147 +0,0 @@
|
||||
require('express-async-errors')
|
||||
const express = require('express')
|
||||
const Sentry = require('@sentry/node')
|
||||
|
||||
const User = require('./src/services/users')
|
||||
const AccessGroup = require('./src/services/accessgroups')
|
||||
const { initializeDatabaseConnection } = require('./src/database/connection')
|
||||
const initializeSentry = require('./src/util/sentry')
|
||||
const errorMiddleware = require('./src/middlewares/errorMiddleware')
|
||||
|
||||
initializeDatabaseConnection()
|
||||
.then(() => {
|
||||
const app = express()
|
||||
initializeSentry(app)
|
||||
app.use(Sentry.Handlers.requestHandler())
|
||||
app.use(Sentry.Handlers.tracingHandler())
|
||||
const port = 4567
|
||||
const checkSecret = require('./src/middlewares/secret')
|
||||
|
||||
app.use(express.json())
|
||||
app.use(checkSecret)
|
||||
|
||||
app.get('/ping', (req, res) => res.json({ message: 'pong ' }))
|
||||
|
||||
app.get('/findall', async (req, res) => {
|
||||
const users = await User.findAll()
|
||||
res.json(users.map(User.getUserData))
|
||||
})
|
||||
|
||||
app.get('/user/:uid/user_data', async (req, res) => {
|
||||
const uid = req.params.uid
|
||||
const user = await User.byUsernameMinified(uid)
|
||||
const roles = user.accessgroup.map(({ group_code }) => group_code)
|
||||
const rights = User.getUserProgrammes(user)
|
||||
const faculties = user.faculty.map(({ faculty_code }) => faculty_code)
|
||||
|
||||
res.json({
|
||||
email: user.email,
|
||||
full_name: user.full_name,
|
||||
roles,
|
||||
rights,
|
||||
faculties,
|
||||
})
|
||||
})
|
||||
|
||||
app.post('/loginwithouttoken', async (req, res) => {
|
||||
const { uid, full_name, hyGroups, email, hyPersonSisuId, hasStudyGuidanceGroupAccess } = req.body
|
||||
console.log(uid, full_name, 'getting the login data without token!')
|
||||
const { payload, isNew } = await User.loginWithoutToken(
|
||||
uid,
|
||||
full_name,
|
||||
hyGroups,
|
||||
email,
|
||||
hyPersonSisuId,
|
||||
hasStudyGuidanceGroupAccess
|
||||
)
|
||||
res.status(200).json({ payload, isNew })
|
||||
})
|
||||
|
||||
app.post('/superlogin', async (req, res) => {
|
||||
const { uid, asUser } = req.body
|
||||
console.log(`${uid} superlogging as ${asUser}`)
|
||||
const token = await User.superlogin(uid, asUser)
|
||||
if (token) {
|
||||
res.status(200).json(token)
|
||||
}
|
||||
res.status(400).end()
|
||||
})
|
||||
|
||||
app.put('/user/:uid', async (req, res) => {
|
||||
const uid = req.params.uid
|
||||
const user = await User.byUsername(uid)
|
||||
if (!user) {
|
||||
return res.status(400).json({ error: 'invalid username given' })
|
||||
}
|
||||
await User.updateUser(user, req.body)
|
||||
const returnedUser = await User.byUsername(uid)
|
||||
res.json(User.getUserData(returnedUser))
|
||||
})
|
||||
|
||||
app.post('/modifyaccess', async (req, res) => {
|
||||
const { username, accessgroups } = req.body
|
||||
try {
|
||||
await User.modifyRights(username, accessgroups)
|
||||
const user = await User.byUsername(username)
|
||||
res.status(200).json(User.getUserData(user))
|
||||
} catch (e) {
|
||||
res.status(400).json({ e })
|
||||
}
|
||||
})
|
||||
app.post('/add_rights', async (req, res) => {
|
||||
const { uid, codes } = req.body
|
||||
console.log('adding rights to ', uid)
|
||||
try {
|
||||
await User.addProgrammes(uid, codes)
|
||||
const user = await User.byId(uid)
|
||||
res.status(200).json({ user: User.getUserData(user) })
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
res.status(400).json({ e })
|
||||
}
|
||||
})
|
||||
app.post('/remove_rights', async (req, res) => {
|
||||
const { uid, codes } = req.body
|
||||
console.log('removing rights from ', uid)
|
||||
try {
|
||||
await User.removeProgrammes(uid, codes)
|
||||
const user = await User.byId(uid)
|
||||
res.status(200).json({ user: User.getUserData(user) })
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
res.status(400).json({ e })
|
||||
}
|
||||
})
|
||||
app.post('/set_faculties', async (req, res) => {
|
||||
const { uid, faculties } = req.body
|
||||
try {
|
||||
await User.setFaculties(uid, faculties)
|
||||
const user = await User.byId(uid)
|
||||
res.status(200).json({ user: User.getUserData(user) })
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
res.status(400).json({ e })
|
||||
}
|
||||
})
|
||||
app.get('/access_groups', async (req, res) => {
|
||||
try {
|
||||
const groups = await AccessGroup.findAll()
|
||||
res.status(200).json(groups)
|
||||
} catch (e) {
|
||||
res.status(400).end()
|
||||
}
|
||||
})
|
||||
|
||||
app.use(errorMiddleware)
|
||||
|
||||
const server = app.listen(port, () => console.log(`Userservice listening on port ${port}!`))
|
||||
process.on('SIGTERM', () => {
|
||||
server.close(() => {
|
||||
console.log('Process terminated')
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
process.exitCode = 1
|
||||
console.log(e)
|
||||
})
|
||||
Generated
-6907
File diff suppressed because it is too large
Load Diff
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "userservice",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"dev": "nodemon index.js",
|
||||
"lint": "eslint .",
|
||||
"lintfix": "prettier --write '**/*.{js,json}' && eslint --fix .",
|
||||
"test": "jest --verbose --runInBand --silent=false"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/node": "^6.11.0",
|
||||
"@sentry/tracing": "^6.11.0",
|
||||
"express": "^4.17.1",
|
||||
"express-async-errors": "^3.1.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"moment": "^2.24.0",
|
||||
"pg": "^8.7.1",
|
||||
"sequelize": "^6.6.2",
|
||||
"umzug": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^24.8.0",
|
||||
"nodemon": "^2.0.7",
|
||||
"sqlite3": "^5.0.2"
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
const { NODE_ENV, DB_URL, SECRET, SENTRY_RELEASE, SENTRY_ENVIRONMENT, CI } = process.env
|
||||
|
||||
// Sentry
|
||||
const sentryRelease = SENTRY_RELEASE || ''
|
||||
const sentryEnvironment = SENTRY_ENVIRONMENT || ''
|
||||
const runningInCI = CI === 'true'
|
||||
|
||||
const isStaging = NODE_ENV === 'staging'
|
||||
|
||||
const courseStatisticsGroup = 'grp-oodikone-basic-users'
|
||||
|
||||
const hyOneGroup = 'hy-one'
|
||||
|
||||
const requiredGroup = isStaging
|
||||
? ['grp-oodikone-staging-users', 'grp-oodikone-basic-staging-users']
|
||||
: ['grp-oodikone-users', 'grp-oodikone-basic-users']
|
||||
|
||||
module.exports = {
|
||||
DB_URL,
|
||||
SECRET,
|
||||
requiredGroup,
|
||||
courseStatisticsGroup,
|
||||
sentryRelease,
|
||||
sentryEnvironment,
|
||||
runningInCI,
|
||||
hyOneGroup,
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const Umzug = require('umzug')
|
||||
const { DB_URL } = require('../conf')
|
||||
|
||||
const sequelize = new Sequelize(DB_URL, {
|
||||
logging: false,
|
||||
})
|
||||
const initializeDatabaseConnection = async () => {
|
||||
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
const waitSeconds = 60
|
||||
for (let i = 1; i <= waitSeconds; i++) {
|
||||
try {
|
||||
await sequelize.authenticate()
|
||||
break
|
||||
} catch (e) {
|
||||
if (i === waitSeconds) {
|
||||
console.log(`Could not connect to database in ${waitSeconds} seconds`)
|
||||
throw e
|
||||
}
|
||||
console.log('.')
|
||||
await sleep(1000)
|
||||
}
|
||||
}
|
||||
try {
|
||||
const migrator = new Umzug({
|
||||
storage: 'sequelize',
|
||||
storageOptions: {
|
||||
sequelize: sequelize,
|
||||
tableName: 'migrations',
|
||||
},
|
||||
logging: console.log,
|
||||
migrations: {
|
||||
params: [sequelize.getQueryInterface(), Sequelize],
|
||||
path: `${process.cwd()}/src/database/migrations`,
|
||||
pattern: /\.js$/,
|
||||
},
|
||||
})
|
||||
const migrations = await migrator.up()
|
||||
|
||||
console.log('Migrations up to date', migrations)
|
||||
} catch (e) {
|
||||
console.log('Migration error')
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
const forceSyncDatabase = async () => {
|
||||
await sequelize.sync({ force: true })
|
||||
}
|
||||
|
||||
module.exports = { sequelize, initializeDatabaseConnection, forceSyncDatabase }
|
||||
@@ -1,23 +0,0 @@
|
||||
const Sentry = require('@sentry/node')
|
||||
const { ApplicationError } = require('../util/customErrors')
|
||||
|
||||
const errorHandler = (error, req, res, next) => {
|
||||
console.error(`${error.message} ${error.name} ${error.stack}`)
|
||||
console.error(`Body of failing request: ${JSON.stringify(req.body)}`)
|
||||
|
||||
// enrich with user details from req body
|
||||
Sentry.captureException({
|
||||
...error,
|
||||
reqBody: req.body,
|
||||
})
|
||||
|
||||
if (res.headersSent) {
|
||||
return next(error)
|
||||
}
|
||||
|
||||
const normalizedError = error instanceof ApplicationError ? error : new ApplicationError(error.message)
|
||||
|
||||
return res.status(normalizedError.status).json(normalizedError)
|
||||
}
|
||||
|
||||
module.exports = errorHandler
|
||||
@@ -1,5 +0,0 @@
|
||||
const { SECRET } = require('../conf')
|
||||
|
||||
const checkSecret = (req, res, next) => (req.headers.secret == SECRET ? next() : res.status(403).end())
|
||||
|
||||
module.exports = checkSecret
|
||||
@@ -1,125 +0,0 @@
|
||||
const Sequelize = require('sequelize')
|
||||
const { sequelize } = require('../database/connection')
|
||||
|
||||
const UserElementDetails = sequelize.define(
|
||||
'user_elementdetails',
|
||||
{
|
||||
userId: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.BIGINT,
|
||||
},
|
||||
elementDetailCode: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.STRING,
|
||||
},
|
||||
},
|
||||
{
|
||||
tablename: 'user_elementdetails',
|
||||
}
|
||||
)
|
||||
|
||||
const AccessGroup = sequelize.define('access_group', {
|
||||
id: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
},
|
||||
group_code: {
|
||||
type: Sequelize.STRING,
|
||||
unique: true,
|
||||
},
|
||||
group_info: {
|
||||
type: Sequelize.STRING,
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
})
|
||||
|
||||
const UserFaculties = sequelize.define('user_faculties', {
|
||||
userId: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.BIGINT,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id',
|
||||
},
|
||||
},
|
||||
faculty_code: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.STRING,
|
||||
},
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
})
|
||||
|
||||
const User = sequelize.define(
|
||||
'users',
|
||||
{
|
||||
id: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
},
|
||||
full_name: { type: Sequelize.STRING },
|
||||
username: {
|
||||
type: Sequelize.STRING,
|
||||
unique: true,
|
||||
},
|
||||
email: { type: Sequelize.STRING },
|
||||
language: { type: Sequelize.STRING },
|
||||
sisu_person_id: { type: Sequelize.STRING },
|
||||
last_login: { type: Sequelize.DATE },
|
||||
},
|
||||
{
|
||||
tableName: 'users',
|
||||
timestamps: false,
|
||||
}
|
||||
)
|
||||
const HyGroup = sequelize.define('hy_group', {
|
||||
id: {
|
||||
primaryKey: true,
|
||||
type: Sequelize.BIGINT,
|
||||
autoIncrement: true,
|
||||
},
|
||||
|
||||
code: { type: Sequelize.STRING },
|
||||
|
||||
createdAt: {
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
|
||||
updatedAt: {
|
||||
type: Sequelize.DATE,
|
||||
},
|
||||
})
|
||||
|
||||
User.hasMany(UserElementDetails, { as: 'programme' })
|
||||
UserElementDetails.belongsTo(User)
|
||||
|
||||
User.belongsToMany(AccessGroup, {
|
||||
through: 'user_accessgroup',
|
||||
as: 'accessgroup',
|
||||
})
|
||||
AccessGroup.belongsToMany(User, { through: 'user_accessgroup' })
|
||||
|
||||
User.belongsToMany(HyGroup, { through: 'user_hy_group', as: 'hy_group' })
|
||||
HyGroup.belongsToMany(User, { through: 'user_hy_group' })
|
||||
|
||||
User.hasMany(UserFaculties, { as: 'faculty', foreignKey: 'userId' })
|
||||
|
||||
module.exports = {
|
||||
User,
|
||||
UserElementDetails,
|
||||
AccessGroup,
|
||||
HyGroup,
|
||||
UserFaculties,
|
||||
sequelize,
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
class ApplicationError extends Error {
|
||||
constructor(message, status, extra) {
|
||||
super()
|
||||
|
||||
Error.captureStackTrace(this, this.constructor)
|
||||
|
||||
this.name = this.constructor.name
|
||||
|
||||
this.message = message || 'Something went wrong. Please try again.'
|
||||
|
||||
this.status = status || 500
|
||||
|
||||
this.extra = extra || {}
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
error: this.message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ApplicationError,
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
const Sentry = require('@sentry/node')
|
||||
const Tracing = require('@sentry/tracing')
|
||||
|
||||
const { sentryRelease, sentryEnvironment, runningInCI } = require('../conf')
|
||||
|
||||
const initializeSentry = app => {
|
||||
if (!sentryRelease || !sentryEnvironment || runningInCI) return
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://020b79f0cbb14aad94cc9d69a1ea9d52@sentry.cs.helsinki.fi/2',
|
||||
environment: sentryEnvironment,
|
||||
release: sentryRelease,
|
||||
integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })],
|
||||
tracesSampleRate: 1.0,
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = initializeSentry
|
||||
Reference in New Issue
Block a user