diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 3e391de0..00000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 81e33d61..163416f9 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,10 @@ -FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:20-bullseye +FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:22-bullseye + +# pnpm +ENV PNPM_HOME=/workspaces/pnpm +ENV PATH="$PNPM_HOME:$PATH" +RUN su node -c "npm install -g pnpm@9.15.2" +RUN corepack enable ENV TZ=Europe/Berlin RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index cb967e12..42190bc0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,52 +1,55 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/docker-outside-of-docker { - "name": "brahmsee-digital", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - // "image": "mcr.microsoft.com/devcontainers/base:bullseye", - - "dockerComposeFile": ["../docker-compose-services.yml", "./docker-compose.yml"], - "service": "app", - "features": { - "ghcr.io/devcontainers/features/github-cli:1": {}, - "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {} - }, - "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", - // "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind", - // Use this environment variable if you need to bind mount your local source code into a new container. - "remoteEnv": { - "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" - }, - "forwardPorts": [3030, 8080, 7700], - "portsAttributes":{ - "3030": { - "label": "API", - "onAutoForward": "silent" - }, - "8080": { - "label": "Frontend", - "onAutoForward": "silent" - }, - "7700": { - "label": "Meilisearch", - "onAutoForward": "silent" - } - }, - "customizations": { - "vscode": { - "extensions": [ - "eamodio.gitlens", - "dbaeumer.vscode-eslint", - "GitHub.vscode-pull-request-github", - "bradlc.vscode-tailwindcss", - "prisma.prisma", - "github.copilot", - "actboy168.tasks" - ] - } - }, - "onCreateCommand": { - "dependencies": "npm ci", - "environment": "cp -n api/.env.example api/.env" - } + "name": "brahmsee-digital", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + // "image": "mcr.microsoft.com/devcontainers/base:bullseye", + "dockerComposeFile": [ + "../docker-compose-services.yml", + "./docker-compose.yml" + ], + "service": "app", + "features": { + "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { + "dockerDashComposeVersion": "v2.32.3" + } + }, + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + // "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind", + // Use this environment variable if you need to bind mount your local source code into a new container. + "remoteEnv": { + "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" + }, + "customizations": { + "vscode": { + "extensions": [ + "Vue.volar", + "eamodio.gitlens", + "dbaeumer.vscode-eslint", + "GitHub.vscode-pull-request-github", + "bradlc.vscode-tailwindcss", + "prisma.prisma", + "github.copilot", + "actboy168.tasks", + "esbenp.prettier-vscode", + "YoavBls.pretty-ts-errors", + "christian-kohler.path-intellisense", + "herrmannplatz.npm-dependency-links", + "mskelton.npm-outdated" + ], + "settings": { + "terminal.integrated.defaultProfile.linux": "zsh", + "terminal.integrated.profiles.linux": { + "zsh": { + "path": "/bin/zsh" + } + } + } + } + }, + "onCreateCommand": { + // "dependencies": "pnpm install", + "environment": "cp -n apps/api/.env.example apps/api/.env" + } } diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 60433432..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,166 +0,0 @@ -const importSettings = { - 'import/first': 'error', - 'import/no-duplicates': 'error', - 'import/no-unresolved': 'off', - 'import/newline-after-import': 'error', - 'import/order': [ - 'error', - { - 'newlines-between': 'always', - alphabetize: { order: 'asc', caseInsensitive: true }, - pathGroups: [ - { - pattern: '@/**/*', - group: 'internal', - }, - ], - }, - ], -} - -module.exports = { - root: true, - env: { - es6: true, - es2022: true, - node: true, - }, - parserOptions: { - ecmaVersion: 2022, - }, - rules: { - 'no-console': ['error', { allow: ['error', 'warn'] }], - }, - overrides: [ - { - files: ['api/**/*'], - settings: { - 'import/resolver': { - node: true, - }, - }, - }, - { - files: ['**/*.vue'], - parser: 'vue-eslint-parser', - parserOptions: { - parser: '@typescript-eslint/parser', - }, - settings: { - 'import/internal-regex': '^@codeanker/', - }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:vue/vue3-recommended', - 'plugin:import/recommended', - 'plugin:prettier/recommended', - ], - rules: { - 'vue/component-name-in-template-casing': 'warn', - 'vue/match-component-file-name': [ - 'warn', - { - extensions: ['vue'], - }, - ], - 'vue/no-static-inline-styles': 'warn', - 'vue/no-template-target-blank': 'warn', - 'vue/no-potential-component-option-typo': 'warn', - 'vue/multi-word-component-names': 'off', - 'vue/require-default-prop': 'off', // Durch typescript ist das immer ersichtlich, dass es undefined sein kann. - 'vue/no-ref-object-destructure': 'error', - 'vue/no-undef-components': ['error', { ignorePatterns: ['ValidationProvider', 'router-link', 'router-view'] }], - 'vue/no-unused-refs': 'error', - 'vue/no-useless-mustaches': 'error', - 'vue/no-useless-v-bind': 'error', - 'vue/v-on-function-call': 'error', - 'vue/no-unused-properties': ['error', { groups: ['props', 'data', 'computed', 'methods', 'setup'] }], - 'vue/html-button-has-type': 'error', - 'vue/no-ref-object-reactivity-loss': 'error', - 'vue/padding-line-between-blocks': 'error', - 'vue/define-macros-order': 'error', - 'vue/block-order': [ - 'error', - { - order: ['script', 'template', 'style'], - }, - ], - 'vue/require-macro-variable-name': 'error', - 'vue/define-props-declaration': 'error', - 'vue/define-emits-declaration': 'error', - 'vue/component-api-style': ['error', ['script-setup']], - 'vue/block-lang': ['error', { script: { lang: 'ts' } }], - 'vue/no-v-html': 'off', - ...importSettings, - }, - }, - { - files: ['**/*.ts'], - plugins: ['@typescript-eslint'], - parser: '@typescript-eslint/parser', - parserOptions: { - project: ['./tsconfig.base.json'], // Specify it only for TypeScript files - tsconfigRootDir: __dirname, - }, - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], - settings: { - 'import/internal-regex': '^@codeanker/', - 'import/parsers': { - '@typescript-eslint/parser': ['.ts', '.tsx'], - }, - 'import/resolver': { - typescript: true, - }, - }, - rules: { - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-empty-interface': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', - '@typescript-eslint/no-unused-vars': [ - 'error', - { argsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_' }, - ], - '@typescript-eslint/strict-boolean-expressions': [ - 'error', - { - allowAny: true, - allowNullableObject: true, - allowNullableBoolean: true, - }, - ], - ...importSettings, - }, - }, - // ts rules that require a parserOptions.project - { - files: ['api/**/*.ts', 'frontend/**/*.ts'], - }, - { - files: ['**/*.js'], - extends: ['eslint:recommended', 'plugin:prettier/recommended'], - rules: { - 'no-unused-vars': [ - 'error', - { argsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_', ignoreRestSiblings: true }, - ], - }, - }, - { - files: ['frontend/**/*'], - parserOptions: { - sourceType: 'module', - }, - env: { - browser: true, - }, - }, - { - files: ['api/test/**/*.js'], - env: { - mocha: true, - }, - }, - ], -} diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 374aef9e..234ece0e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,35 +4,37 @@ about: Melde einen Fehler und helfe uns diesen zu beheben title: '' labels: bug assignees: '' - --- -**Beschreibung des Fehlers +\*\*Beschreibung des Fehlers Klare und prägnante Beschreibung des Fehlers. -**Reproduzieren +\*\*Reproduzieren Schritte, um das Verhalten zu reproduzieren: + 1. Gehen Sie zu '...'. 2. Klicken Sie auf '....'. 3. Scrollen Sie nach unten zu '....'. 4. Siehe Fehler -**Erwartetes Verhalten +\*\*Erwartetes Verhalten Eine klare und prägnante Beschreibung dessen, was Sie erwartet haben. -**Screenshots +\*\*Screenshots Fügen Sie Screenshots bei, um das Problem zu veranschaulichen. **Desktop (bitte füllen Sie die folgenden Informationen aus):**. - - Betriebssystem: [z.B. iOS]. - - Browser [z.B. Chrome, Safari]. - - Version [z.B. 22] + +- Betriebssystem: [z.B. iOS]. +- Browser [z.B. Chrome, Safari]. +- Version [z.B. 22] **Smartphone (bitte füllen Sie die folgenden Felder aus):**. - - Gerät: [z.B. iPhone6]. - - Betriebssystem: [z.B. iOS8.1]. - - Browser: [z.B. Standardbrowser, Safari] - - Version [z.B. 22] -**Zusätzlicher Kontext +- Gerät: [z.B. iPhone6]. +- Betriebssystem: [z.B. iOS8.1]. +- Browser: [z.B. Standardbrowser, Safari] +- Version [z.B. 22] + +\*\*Zusätzlicher Kontext Fügen Sie hier weitere Informationen zum Problem hinzu. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index acb82c48..14084708 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: feature-request assignees: '' - --- **Bezieht sich Ihre Funktionsanfrage auf ein Problem? Bitte beschreiben Sie es.** diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4eeafebe..cc7afd7c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: "" # See documentation for possible values - directory: "/" # Location of package manifests + - package-ecosystem: 'npm' # See documentation for possible values + directory: '/' # Location of package manifests schedule: - interval: "daily" + interval: 'daily' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b290352..380d59ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,32 +10,6 @@ on: branches: [development, master] jobs: - lint: - name: ✨ Lint - runs-on: ubuntu-latest - container: node:20-bullseye - steps: - - uses: actions/checkout@v3 - - name: Cache node modules - id: cache-npm - uses: actions/cache@v3 - env: - cache-name: cache-node-modules - with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} - name: List the state of node modules - continue-on-error: true - run: npm list - - run: npm ci - - run: npm run lint - prepare: name: 🔨 Initialisieren runs-on: ubuntu-latest @@ -43,17 +17,16 @@ jobs: version: ${{ steps.version.outputs.prop }} commit: ${{ steps.set-commit.outputs.commit }} steps: - - uses: actions/checkout@v4 - - name: get version - id: version - uses: notiz-dev/github-action-json-property@v0.2.0 - with: + - uses: actions/checkout@v4 + - name: get version + id: version + uses: notiz-dev/github-action-json-property@v0.2.0 + with: path: 'package.json' prop_path: 'version' - - name: set commit - id: set-commit - run: echo "commit=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT - + - name: set commit + id: set-commit + run: echo "commit=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT # build and upload docker image to github container registry build: @@ -87,71 +60,3 @@ jobs: commitHash=${{needs.prepare.outputs.commit}} version=${{needs.prepare.outputs.version}} mode=${{ env.MODE }} - typecheck: - name: 🙆‍♀️ Typecheck - runs-on: ubuntu-latest - container: node:20-bullseye - steps: - - uses: actions/checkout@v3 - - name: Cache node modules - id: cache-npm - uses: actions/cache@v3 - env: - cache-name: cache-node-modules - with: - # npm cache files are stored in `~/.npm` on Linux/macOS - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} - name: List the state of node modules - continue-on-error: true - run: npm list - - run: npm ci - - run: npm run typecheck - - # test: - # name: 🧪 Test - # runs-on: ubuntu-latest - # container: node:20-bullseye - # services: - # postgres: - # image: postgres - # env: - # POSTGRES_USER: postgres - # POSTGRES_PASSWORD: postgres - # POSTGRES_DB: brahmsee.digital - # options: >- - # --health-cmd pg_isready - # --health-interval 10s - # --health-timeout 5s - # --health-retries 5 - # steps: - # - uses: actions/checkout@v3 - # - name: Cache node modules - # id: cache-npm - # uses: actions/cache@v3 - # env: - # cache-name: cache-node-modules - # with: - # # npm cache files are stored in `~/.npm` on Linux/macOS - # path: ~/.npm - # key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - # restore-keys: | - # ${{ runner.os }}-build-${{ env.cache-name }}- - # ${{ runner.os }}-build- - # ${{ runner.os }}- - # - if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }} - # name: List the state of node modules - # continue-on-error: true - # run: npm list - # - run: npm ci - # - run: cp -n api/.env.example api/.env - # - name: set test env - # run: sed -i 's/NODE_ENV=development/NODE_ENV=test/g' api/.env - # - run: npx -w api prisma migrate reset --force - # - run: npx playwright install --with-deps - # - run: npm run test:all diff --git a/.github/workflows/code-checks.yml b/.github/workflows/code-checks.yml new file mode 100644 index 00000000..f12e1b05 --- /dev/null +++ b/.github/workflows/code-checks.yml @@ -0,0 +1,37 @@ +name: code-checks + +on: + push: + branches: [main, master, development, feature/**, hotfix/**] + +jobs: + turbo-checks: + name: 🔍 turbo-checks + runs-on: ubuntu-latest + steps: + - name: Git checkout + uses: actions/checkout@v4 + - name: Cache turbo build setup + uses: actions/cache@v4 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo- + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + - name: Use Node.js 18 + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'pnpm' + - name: corepack + run: corepack enable + - name: Install dependencies + run: | + export COREPACK_INTEGRITY_KEYS="$(curl https://registry.npmjs.org/-/npm/v1/keys | jq -c '{npm: .keys}')" + pnpm install + - name: turbo-checks + run: pnpm run turbo-checks diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 3fa14c6a..00000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Fly Deploy -on: - push: - branches: - - development -jobs: - deploy: - name: Deploy app - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: get version - id: version - uses: notiz-dev/github-action-json-property@v0.2.0 - with: - path: 'package.json' - prop_path: 'version' - - name: set commit - id: set-commit - run: echo "commit=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT - - uses: superfly/flyctl-actions/setup-flyctl@master - - run: flyctl deploy --remote-only --build-arg version=${{steps.version.outputs.prop}} --build-arg commitHash=${{steps.set-commit.outputs.commit}} - env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} diff --git a/.github/workflows/helm.yml b/.github/workflows/helm.yml index 98d920b6..64d1ac93 100644 --- a/.github/workflows/helm.yml +++ b/.github/workflows/helm.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - development workflow_dispatch: jobs: @@ -14,20 +15,21 @@ jobs: version: ${{ steps.version.outputs.prop }} commit: ${{ steps.set-commit.outputs.commit }} steps: - - uses: actions/checkout@v4 - - name: get version - id: version - uses: notiz-dev/github-action-json-property@v0.2.0 - with: + - uses: actions/checkout@v4 + - name: get version + id: version + uses: notiz-dev/github-action-json-property@v0.2.0 + with: path: 'package.json' prop_path: 'version' - - name: set commit - id: set-commit - run: echo "commit=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT + - name: set commit + id: set-commit + run: echo "commit=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT release: needs: prepare permissions: contents: write + packages: write runs-on: ubuntu-latest steps: - name: Checkout @@ -35,9 +37,6 @@ jobs: with: fetch-depth: 0 - - name: Install Helm - uses: azure/setup-helm@v3 - - name: set chart version run: | sed -i "s/version: .*/version: ${{ needs.prepare.outputs.version }}-${{ needs.prepare.outputs.commit }}/g" chart/brahmsee-digital/Chart.yaml @@ -45,10 +44,13 @@ jobs: run: | sed -i "s/commit: .*/commit: ${{ needs.prepare.outputs.commit }}/g" chart/brahmsee-digital/values.yaml - - name: pack chart - run: helm package chart/brahmsee-digital - - - name: upload chart artifact - uses: actions/upload-artifact@v4 + - name: Chart | Push brahmsee-digital + uses: appany/helm-oci-chart-releaser@v0.3.0 with: - path: brahmsee-digital-*.tgz + name: brahmsee-digital + repository: codeanker/brahmsee.digital + tag: ${{needs.prepare.outputs.version}}-${{needs.prepare.outputs.commit}} + path: chart/brahmsee-digital + registry: ghcr.io + registry_username: ${{ github.actor }} + registry_password: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index f7dc85b5..a7699dcf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,38 @@ - -# Created by https://www.gitignore.io/api/node +# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,node,macos +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,node,macos + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud ### Node ### # Logs @@ -8,6 +41,11 @@ logs npm-debug.log* yarn-debug.log* yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids @@ -20,11 +58,12 @@ lib-cov # Coverage directory used by tools like istanbul coverage +*.lcov # nyc test coverage .nyc_output -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) @@ -40,8 +79,11 @@ build/Release node_modules/ jspm_packages/ -# TypeScript v1 declaration files -typings/ +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo # Optional npm cache directory .npm @@ -49,35 +91,109 @@ typings/ # Optional eslint cache .eslintcache +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + # Optional REPL history .node_repl_history # Output of 'npm pack' -# *.tgz +*.tgz # Yarn Integrity file .yarn-integrity -# dotenv environment variables file +# dotenv environment variable files .env +.env.development.local +.env.test.local +.env.production.local +.env.local # parcel-bundler cache (https://parceljs.org/) .cache +.parcel-cache -# next.js build output +# Next.js build output .next +out -# nuxt.js build output +# Nuxt.js build / generate output .nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public # vuepress build output .vuepress/dist +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + # Serverless directories -.serverless +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide -# End of https://www.gitignore.io/api/node +# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,node,macos # Editor directories and files .idea @@ -89,3 +205,6 @@ typings/ reports **/tsconfig.tsbuildinfo tsconfig.tsbuildinfo +uploads/ +.turbo +dist/ diff --git a/.husky/pre-commit b/.husky/pre-commit index 741f3999..3dd1ddf9 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npm run lint-staged && npm run typecheck +pnpm run lint diff --git a/.lintstagedrc.js b/.lintstagedrc.js deleted file mode 100644 index 7ec2cd16..00000000 --- a/.lintstagedrc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - "*.{js,ts,vue}": ["eslint --cache"] -} diff --git a/.vscode/service.code-snippets b/.vscode/service.code-snippets index a5556297..ee094d42 100644 --- a/.vscode/service.code-snippets +++ b/.vscode/service.code-snippets @@ -1,49 +1,44 @@ { - // Place your brahmsee.digital workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and - // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope - // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is - // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: - // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. - // Placeholders with the same ids are connected. - // Example: - // "Print to console": { - // "scope": "javascript,typescript", - // "prefix": "log", - // "body": [ - // "console.log('$1');", - // "$2" - // ], - // "description": "Log output to console" - // } - "router": { - "scope": "typescript", - "prefix": "trouter", - "body": [ - "import { publicProcedure, router } from '../../trpc'", + // Place your brahmsee.digital workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is + // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. + // Placeholders with the same ids are connected. + // Example: + // "Print to console": { + // "scope": "javascript,typescript", + // "prefix": "log", + // "body": [ + // "console.log('$1');", + // "$2" + // ], + // "description": "Log output to console" + // } + "router": { + "scope": "typescript", + "prefix": "trouter", + "body": [ + "import { publicProcedure, router } from '../../trpc'", "", "export const $1Router = router({", " // TODO: add your API methods here", "})", - - ], - "description": "Create a router" - }, - "router-route-mutation": { - "prefix": "troute-mutation", - "body": [ - "$1: protectedProcedure([]).input(Z${1/(.)/${1:/capitalize}/}InputSchema).mutation($1),", - ] - }, - "router-route-query": { - "prefix": "troute-query", - "body": [ - "$1: protectedProcedure([]).input(Z${1/(.)/${1:/capitalize}/}InputSchema).query($1),", - ] - }, - "endpoint-create": { - "prefix": "endpoint-create", - "body": [ - "import z from 'zod'", + ], + "description": "Create a router", + }, + "router-route-mutation": { + "prefix": "troute-mutation", + "body": ["$1: protectedProcedure([]).input(Z${1/(.)/${1:/capitalize}/}InputSchema).mutation($1),"], + }, + "router-route-query": { + "prefix": "troute-query", + "body": ["$1: protectedProcedure([]).input(Z${1/(.)/${1:/capitalize}/}InputSchema).query($1),"], + }, + "endpoint-create": { + "prefix": "endpoint-create", + "body": [ + "import z from 'zod'", "", "import prisma from '../../prisma'", "", @@ -67,6 +62,6 @@ " },", " })", "}", - ] - } + ], + }, } diff --git a/.vscode/settings.json b/.vscode/settings.json index b4ec819f..177a41a3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,19 +1,111 @@ { - "eslint.workingDirectories": [ - "./frontend", - "./api" - ], - "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit" - }, - "editor.tabSize": 2, - "files.trimTrailingWhitespace": true, - "files.trimFinalNewlines": true, - "files.insertFinalNewline": true, - "volar.completion.preferredTagNameCase": "pascal", - "workbench.colorCustomizations": { - "titleBar.activeBackground": "#4d7c0f", - "titleBar.inactiveBackground": "#4d7c0f" - }, - "git.enableCommitSigning": false + "editor.formatOnSave": true, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[vue]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[html]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[css]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[scss]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "editor.tabSize": 2, + "files.associations": { + "*.mjml": "html" + }, + "files.trimTrailingWhitespace": true, + "files.trimFinalNewlines": true, + "files.insertFinalNewline": true, + "vue.server.hybridMode": true, + "workbench.colorCustomizations": { + "titleBar.activeBackground": "#4d7c0f", + "titleBar.inactiveBackground": "#4d7c0f" + }, + "git.enableCommitSigning": false, + "explorer.fileNesting.enabled": true, + "explorer.fileNesting.expand": false, + "explorer.fileNesting.patterns": { + "*.asax": "$(capture).*.cs, $(capture).*.vb", + "*.ascx": "$(capture).*.cs, $(capture).*.vb", + "*.ashx": "$(capture).*.cs, $(capture).*.vb", + "*.aspx": "$(capture).*.cs, $(capture).*.vb", + "*.bloc.dart": "$(capture).event.dart, $(capture).state.dart", + "*.c": "$(capture).h", + "*.cc": "$(capture).hpp, $(capture).h, $(capture).hxx", + "*.cjs": "$(capture).cjs.map, $(capture).*.cjs, $(capture)_*.cjs", + "*.component.ts": "$(capture).component.html, $(capture).component.spec.ts, $(capture).component.css, $(capture).component.scss, $(capture).component.sass, $(capture).component.less", + "*.cpp": "$(capture).hpp, $(capture).h, $(capture).hxx", + "*.cs": "$(capture).*.cs", + "*.cshtml": "$(capture).cshtml.cs", + "*.csproj": "*.config, *proj.user, appsettings.*, bundleconfig.json", + "*.css": "$(capture).css.map, $(capture).*.css", + "*.cxx": "$(capture).hpp, $(capture).h, $(capture).hxx", + "*.dart": "$(capture).freezed.dart, $(capture).g.dart", + "*.ex": "$(capture).html.eex, $(capture).html.heex, $(capture).html.leex", + "*.go": "$(capture)_test.go", + "*.java": "$(capture).class", + "*.js": "$(capture).js.map, $(capture).*.js, $(capture)_*.js", + "*.jsx": "$(capture).js, $(capture).*.jsx, $(capture)_*.js, $(capture)_*.jsx", + "*.master": "$(capture).*.cs, $(capture).*.vb", + "*.mjs": "$(capture).mjs.map, $(capture).*.mjs, $(capture)_*.mjs", + "*.module.ts": "$(capture).resolver.ts, $(capture).controller.ts, $(capture).service.ts", + "*.pubxml": "$(capture).pubxml.user", + "*.resx": "$(capture).*.resx, $(capture).designer.cs, $(capture).designer.vb", + "*.tex": "$(capture).acn, $(capture).acr, $(capture).alg, $(capture).aux, $(capture).bbl, $(capture).blg, $(capture).fdb_latexmk, $(capture).fls, $(capture).glg, $(capture).glo, $(capture).gls, $(capture).idx, $(capture).ind, $(capture).ist, $(capture).lof, $(capture).log, $(capture).lot, $(capture).out, $(capture).pdf, $(capture).synctex.gz, $(capture).toc, $(capture).xdv", + "*.ts": "$(capture).js, $(capture).d.ts.map, $(capture).*.ts, $(capture)_*.js, $(capture)_*.ts", + "*.tsx": "$(capture).ts, $(capture).*.tsx, $(capture)_*.ts, $(capture)_*.tsx", + "*.vbproj": "*.config, *proj.user, appsettings.*, bundleconfig.json", + "*.vue": "$(capture).*.ts, $(capture).*.js, $(capture).story.vue", + "*.xaml": "$(capture).xaml.cs", + "+layout.svelte": "+layout.ts,+layout.ts,+layout.js,+layout.server.ts,+layout.server.js", + "+page.svelte": "+page.server.ts,+page.server.js,+page.ts,+page.js ", + ".clang-tidy": ".clang-format, .clangd, compile_commands.json", + ".env": "*.env, .env.*, .envrc, env.d.ts", + ".project": ".classpath", + "BUILD.bazel": "*.bzl, *.bazel, *.bazelrc, bazel.rc, .bazelignore, .bazelproject, WORKSPACE", + "CMakeLists.txt": "*.cmake, *.cmake.in, .cmake-format.yaml, CMakePresets.json", + "I*.cs": "$(capture).cs", + "artisan": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, server.php, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, webpack.mix.js, windi.config.*", + "astro.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", + "cargo.toml": ".clippy.toml, .rustfmt.toml, cargo.lock, clippy.toml, cross.toml, rust-toolchain.toml, rustfmt.toml", + "composer.json": ".php*.cache, composer.lock, phpunit.xml*, psalm*.xml", + "default.nix": "shell.nix", + "deno.json*": "*.env, .env.*, .envrc, api-extractor.json, deno.lock, env.d.ts, import-map.json, import_map.json, jsconfig.*, tsconfig.*, tsdoc.*", + "dockerfile": ".dockerignore, docker-compose.*, dockerfile*", + "flake.nix": "flake.lock", + "gatsby-config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, gatsby-browser.*, gatsby-node.*, gatsby-ssr.*, gatsby-transformer.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", + "gemfile": ".ruby-version, gemfile.lock", + "go.mod": ".air*, go.sum", + "go.work": "go.work.sum", + "mix.exs": ".credo.exs, .dialyzer_ignore.exs, .formatter.exs, .iex.exs, .tool-versions, mix.lock", + "next.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, next-env.d.ts, playwright.config.*, postcss.config.*, puppeteer.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", + "nuxt.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", + "package.json": ".browserslist*, .circleci*, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, eslint.config.*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lintstagedrc*, .markdownlint*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, apollo.config.*, appveyor*, azure-pipelines*, bower.json, build.config.*, commitlint*, crowdin*, dangerfile*, dlint.json, dprint.json, firebase.json, grunt*, gulp*, jenkins*, lerna*, lint-staged*, nest-cli.*, netlify*, nodemon*, npm-shrinkwrap.json, nx.*, package-lock.json, package.nls*.json, phpcs.xml, pm2.*, pnpm*, prettier*, pullapprove*, pyrightconfig.json, release-tasks.sh, renovate*, rollup.config.*, stylelint*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, webpack*, workspace.json, xo.config.*, yarn*, tsconfig.*, .gitignore", + "pubspec.yaml": ".metadata, .packages, all_lint_rules.yaml, analysis_options.yaml, build.yaml, pubspec.lock, pubspec_overrides.yaml", + "pyproject.toml": ".pdm.toml, pdm.lock, pyproject.toml", + "quasar.conf.js": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, quasar.extensions.json, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", + "readme*": "authors, backers*, changelog*, citation*, code_of_conduct*, codeowners, contributing*, contributors, copying, credits, governance.md, history.md, license*, maintainers, readme*, security.md, sponsors*", + "remix.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, remix.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", + "rush.json": ".browserslist*, .circleci*, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lintstagedrc*, .markdownlint*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, apollo.config.*, appveyor*, azure-pipelines*, bower.json, build.config.*, commitlint*, crowdin*, dangerfile*, dlint.json, dprint.json, firebase.json, grunt*, gulp*, jenkins*, lerna*, lint-staged*, nest-cli.*, netlify*, nodemon*, npm-shrinkwrap.json, nx.*, package-lock.json, package.nls*.json, phpcs.xml, pm2.*, pnpm*, prettier*, pullapprove*, pyrightconfig.json, release-tasks.sh, renovate*, rollup.config.*, stylelint*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, webpack*, workspace.json, xo.config.*, yarn*", + "shims.d.ts": "*.d.ts", + "svelte.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, mdsvex.config.js, playwright.config.*, postcss.config.*, puppeteer.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", + "vite.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", + "vue.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*", + "docker-compose.yml": "docker-compose-*, .dockerignore, e2e-vnc.yaml" + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6ffbbe5d..68d940fb 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,216 +1,213 @@ { - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "start:frontend", - "label": "start:frontend", - "problemMatcher": [], - "presentation": { - "reveal": "always", - "panel": "new", - "group": "start", - "close": false - }, - "options": { - "statusbar": { - "hide" : true - } - } - }, - { - "type": "npm", - "script": "start:api", - "label": "start:api", - "problemMatcher": [], - "presentation": { - "reveal": "always", - "panel": "new", - "group": "start", - "close": false - }, - "options": { - "statusbar": { - "hide" : true - } - } - }, - { - "type": "npm", - "script": "start:docs", - "label": "start:docs", - "problemMatcher": [], - "presentation": { - "reveal": "silent", - "panel": "new", - "group": "start", - "close": false - }, - "options": { - "statusbar": { - "hide" : true - } - } - }, - { - "type": "npm", - "script": "test", - "label": "start:test", - "problemMatcher": [], - "presentation": { - "reveal": "always", - "panel": "new", - "group": "start", - "close": false - }, - "options": { - "statusbar": { - "hide" : true - } - } - }, - { - "type": "npm", - "script": "start:services", - "label": "start:services", - "problemMatcher": [], - "presentation": { - "reveal": "always", - "panel": "new", - "group": "start", - "close": false - }, - "options": { - "statusbar": { - "hide" : true - } - } - }, - { - "label": "start:apps", - "dependsOn": [ - "start:api", - "start:frontend", - "start:test" - ], - "problemMatcher": [] - }, - { - "label": "start:apps and services", - "dependsOn": [ - "start:api", - "start:frontend", - "start:services" - ], - "problemMatcher": [], - "options": { - "statusbar": { - "hide" : true - } - } - }, - { - "label": "start:prisma studio", - "type": "shell", - "command": "npx prisma studio", - "options": { - "cwd": "${workspaceFolder}/api" + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start:frontend", + "label": "start:frontend", + "problemMatcher": [], + "presentation": { + "reveal": "always", + "panel": "new", + "group": "start", + "close": false }, - "problemMatcher": [], - "presentation": { - "reveal": "silent", - "revealProblems": "onProblem", - "panel": "new", - "close": true - } - }, - { - "label": "run:migration", - "type": "shell", - "command": "npx prisma migrate deploy & npx prisma generate", - "options": { - "cwd": "${workspaceFolder}/api" + "options": { + "statusbar": { + "hide": true + } + } + }, + { + "type": "npm", + "script": "start:api", + "label": "start:api", + "problemMatcher": [], + "presentation": { + "reveal": "always", + "panel": "new", + "group": "start", + "close": false }, - "problemMatcher": [], - "presentation": { - "reveal": "always", - "revealProblems": "onProblem", - "panel": "new", - "close": true - } - }, - { - "type": "npm", - "script": "createAccount", - "label": "run:createAccount", - "problemMatcher": [], - "options": { - "cwd": "${workspaceFolder}/api", - "statusbar": { - "hide" : false - } - }, - "presentation": { - "reveal": "always", - "panel": "new", - "group": "start", - "close": false - }, - - }, - { - "type": "npm", - "script": "initMeilisearch", - "label": "run:initMeilisearch", - "problemMatcher": [], - "options": { - "cwd": "${workspaceFolder}/api", - "statusbar": { - "hide" : false - } - }, - "presentation": { - "reveal": "always", - "panel": "new", - "group": "start", - "close": false - }, - - }, - { - "type": "shell", - "label": "install:playwright", - "command": "npx playwright install && npx playwright install-deps", - "options": { - "statusbar": { - "hide" : true - } - }, - "problemMatcher": [], - "presentation": { - "reveal": "always", - "revealProblems": "onProblem", - "panel": "new", - "close": true - } - }, - { - "type": "shell", - "label": "run:reset_db", - "command": "npx prisma migrate reset", - "problemMatcher": [], - "options": { - "cwd": "${workspaceFolder}/api", - "statusbar": { - "hide" : true - } - }, - "presentation": { - "reveal": "always", - "revealProblems": "onProblem", - "panel": "new", - "close": true - } - } - ] + "options": { + "statusbar": { + "hide": true + } + } + }, + { + "type": "npm", + "script": "start:docs", + "label": "start:docs", + "problemMatcher": [], + "presentation": { + "reveal": "silent", + "panel": "new", + "group": "start", + "close": false + }, + "options": { + "statusbar": { + "hide": true + } + } + }, + { + "type": "npm", + "script": "test", + "label": "start:test", + "problemMatcher": [], + "presentation": { + "reveal": "always", + "panel": "new", + "group": "start", + "close": false + }, + "options": { + "statusbar": { + "hide": true + } + } + }, + { + "type": "npm", + "script": "start:services", + "label": "start:services", + "problemMatcher": [], + "presentation": { + "reveal": "always", + "panel": "new", + "group": "start", + "close": false + }, + "options": { + "statusbar": { + "hide": true + } + } + }, + { + "label": "start:apps", + "dependsOn": [ + "start:api", + "start:frontend" + ], + "problemMatcher": [] + }, + { + "label": "start:apps and services", + "dependsOn": [ + "start:api", + "start:frontend", + "start:services" + ], + "problemMatcher": [], + "options": { + "statusbar": { + "hide": true + } + } + }, + { + "label": "start:prisma studio", + "type": "shell", + "command": "npx prisma studio", + "options": { + "cwd": "${workspaceFolder}/apps/api" + }, + "problemMatcher": [], + "presentation": { + "reveal": "silent", + "revealProblems": "onProblem", + "panel": "new", + "close": true + } + }, + { + "label": "run:migration", + "type": "shell", + "command": "npx prisma migrate deploy & npx prisma generate", + "options": { + "cwd": "${workspaceFolder}/apps/api" + }, + "problemMatcher": [], + "presentation": { + "reveal": "always", + "revealProblems": "onProblem", + "panel": "new", + "close": false + } + }, + { + "type": "npm", + "script": "createAccount", + "label": "run:createAccount", + "problemMatcher": [], + "options": { + "cwd": "${workspaceFolder}/apps/api", + "statusbar": { + "hide": false + } + }, + "presentation": { + "reveal": "always", + "panel": "new", + "group": "start", + "close": false + } + }, + { + "type": "npm", + "script": "initMeilisearch", + "label": "run:initMeilisearch", + "problemMatcher": [], + "options": { + "cwd": "${workspaceFolder}/apps/api", + "statusbar": { + "hide": false + } + }, + "presentation": { + "reveal": "always", + "panel": "new", + "group": "start", + "close": false + } + }, + { + "type": "shell", + "label": "install:playwright", + "command": "npx playwright install && npx playwright install-deps", + "options": { + "statusbar": { + "hide": true + } + }, + "problemMatcher": [], + "presentation": { + "reveal": "always", + "revealProblems": "onProblem", + "panel": "new", + "close": true + } + }, + { + "type": "shell", + "label": "run:reset_db", + "command": "npx prisma migrate reset", + "problemMatcher": [], + "options": { + "cwd": "${workspaceFolder}/apps/api", + "statusbar": { + "hide": false + } + }, + "presentation": { + "reveal": "always", + "revealProblems": "onProblem", + "panel": "new", + "close": true + } + } + ] } diff --git a/Dockerfile b/Dockerfile index 2f9294b4..0c10f120 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,45 +1,53 @@ -FROM node:20-alpine3.17 AS workspace-base +FROM node:18.14.2-alpine3.16 AS workspace-base + +RUN apk add --no-cache bash curl jq + +RUN export COREPACK_INTEGRITY_KEYS="$(curl https://registry.npmjs.org/-/npm/v1/keys | jq -c '{npm: .keys}')" + +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable + ENV CI=true ENV HUSKY=0 WORKDIR /app -COPY frontend/package.json ./frontend/ -COPY api/package.json ./api/ +COPY apps/frontend/package.json ./apps/frontend/ +COPY apps/api/package.json ./apps/api/ COPY packages/authentication/package.json ./packages/authentication/ COPY packages/helpers/package.json ./packages/helpers/ COPY packages/validation/package.json ./packages/validation/ COPY vendor/ ./vendor/ -COPY package*.json ./ - -RUN npm ci +COPY pnpm-lock.yaml ./ +RUN pnpm fetch COPY . ./ -RUN npm run postinstall --workspace ./api +RUN pnpm install --frozen-lockfile ENV TZ=Europe/Berlin RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone ARG commitHash -ENV COMMIT_HASH $commitHash +ENV COMMIT_HASH=$commitHash ARG version -ENV VERSION $version +ENV VERSION=$version + +FROM api-build-stage AS api-production-stage -FROM api-build-stage as api-production-stage +CMD [ "pnpm", "start", "-w", "./apps/api"] -CMD [ "npm", "start", "-w", "./api"] +FROM workspace-base AS frontend-build-stage -FROM workspace-base as frontend-build-stage -ARG tenant -ENV VITE_APP_COMMIT_HASH $commitHash -ENV VITE_APP_VERSION $version -RUN npm run build --workspace ./frontend +ENV VITE_APP_COMMIT_HASH=$commitHash +ENV VITE_APP_VERSION=$version +RUN npm run build --workspace ./apps/frontend -FROM workspace-base as api-build-stage +FROM workspace-base AS api-build-stage # todo packing -# RUN npm run build --workspace ./api +# RUN npm run build --workspace ./apps/api -COPY --from=frontend-build-stage /app/frontend/dist ./api/static/ +COPY --from=frontend-build-stage /app/apps/frontend/dist ./apps/api/static/ ENV NODE_ENV=production -CMD [ "npm", "start", "-w", "./api" ] +CMD [ "npm", "start", "-w", "./apps/api" ] diff --git a/README.md b/README.md index 85f068cf..31ab564c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,9 @@ - # brahmsee.digital ![ci workflow](https://github.com/codeanker/brahmsee.digital/actions/workflows/ci.yml/badge.svg) -![deploy workflow](https://github.com/codeanker/brahmsee.digital/actions/workflows/deploy.yml/badge.svg) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![GitHub package.json version (branch)](https://img.shields.io/github/package-json/v/codeanker/brahmsee.digital/development) - Dieses Projekt dient der Verwaltung der Großveranstaltung "Landeskindertreffen im Waldheim am Brahmsee", der DLRG-Jugend Schleswig-Holstein. Es ist ein Open Source Project und darf gerne weiterentwickelt werden. ## Install und Shortcodes @@ -15,15 +12,17 @@ Am besten wird das Projekt in einem Devcontainer gestartet. Dazu wird nur Docker Über den Eintrag "Clone Repository in Container Volume" kann das Projekt heruntergeladen und geöffnet werden. Das Projekt wird dann automatisch eingerichtet und kann wenn das Abgeschlossen ist auch gestartet werden. + ## Starten Find all Tasks in CMD/CTRL-SHIFT-P -> `Tasks: Run Tasks` 1. Clone via VSCode CMD/CTRL-SHIFT-P -> `Dev Containers: Clone Repository in Container Volume` 2. Run Task: `run:migration` -2. Run Task: `run:createAccount` -2. Run Task: `install:playwright` -4. Run Task: `start:apps` +3. Run Task: `run:createAccount` +4. Run Task: `install:playwright` +5. Run Task: `start:apps` ## Mitarbeit + Bei Fragen kannst Du dich einfach an @danielswiatek wenden. Dieses Project nutzt Issues um sich zu organisieren. Wenn Du entwickelts erstelle einfach einen Branch der dann mittels PR zurück geführt werden kann. diff --git a/api/config/custom-environment-variables.json b/api/config/custom-environment-variables.json deleted file mode 100644 index 4f21cd4b..00000000 --- a/api/config/custom-environment-variables.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "server": { - "host": "SERVER_HOST", - "port": "SERVER_PORT" - }, - - "loggingLevel": "LOGGING_LEVEL", - - "clientUrl": "CLIENT_URL", - - "db": { - "url": "DATABASE_URL" - }, - - "authentication": { - "secret": "AUTHENTICATION_SECRET" - }, - "mail":{ - "sendMails": "MAIL_SENDMAILS", - "sendgridApiKey": "MAIL_SENDGRID_APIKEY" - }, - "meilisearch": { - "host": "MEILISEARCH_HOST", - "apiKey": "MEILISEARCH_KEY" - } -} diff --git a/api/package.json b/api/package.json deleted file mode 100644 index f9cbbea7..00000000 --- a/api/package.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "name": "@codeanker/api", - "type": "module", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "start": "npx prisma migrate deploy && tsx --env-file .env src/server.ts", - "dev": "tsx watch --clear-screen=false --env-file .env src/server.ts", - "createAccount": "tsx src/scripts/createAccount.ts", - "initMeilisearch": "tsx src/scripts/initMeilisearch.ts", - "postinstall": "prisma generate" - }, - "exports": { - ".": { - "import": "./src/client.ts", - "require": "./src/client.ts" - }, - "./test": { - "import": "./src/util/test.ts", - "require": "./src/util/test.ts" - } - }, - "prisma": { - "seed": "tsx prisma/seeders/index.ts" - }, - "dependencies": { - "@faker-js/faker": "^8.4.1", - "@koa/cors": "^5.0.0", - "@koa/router": "^12.0.1", - "@prisma/client": "^5.6.0", - "@prisma/extension-accelerate": "^0.6.3", - "@sendgrid/mail": "^8.1.0", - "@trpc/server": "^10.28.1", - "config": "^3.3.9", - "dayjs": "^1.11.10", - "fast-csv": "^5.0.1", - "grant": "^5.4.22", - "koa": "^2.14.2", - "koa-body": "^6.0.1", - "koa-helmet": "^7.0.2", - "koa-router": "^12.0.1", - "koa-session": "^6.4.0", - "koa-static": "^5.0.0", - "meilisearch": "^0.37.0", - "prom-client": "^15.0.0", - "superjson": "^2.2.1", - "trpc-koa-adapter": "^1.1.3", - "trpc-panel": "^1.3.4", - "uuid": "^9.0.1", - "winston": "^3.11.0", - "xlsx": "^0.18.5", - "zod": "^3.22.4" - }, - "devDependencies": { - "@edge-runtime/types": "^2.2.8", - "@inquirer/prompts": "^2.3.1", - "@types/config": "^3.3.3", - "@types/http-status-codes": "^1.2.0", - "@types/koa": "^2.14.0", - "@types/koa-bodyparser": "^4.3.12", - "@types/koa-router": "^7.4.8", - "@types/node": "^20.11.1", - "edge-runtime": "^2.5.8", - "prisma": "^5.6.0", - "tsx": "^4.2.0", - "typescript": "^5.3.2" - } -} diff --git a/api/prisma/migrations/20230717212242_init/migration.sql b/api/prisma/migrations/20230717212242_init/migration.sql deleted file mode 100644 index 65f83513..00000000 --- a/api/prisma/migrations/20230717212242_init/migration.sql +++ /dev/null @@ -1,38 +0,0 @@ --- CreateEnum -CREATE TYPE "Role" AS ENUM ('GLIEDERUNG_ADMIN', 'ADMIN'); - --- CreateEnum -CREATE TYPE "Gender" AS ENUM ('MALE', 'FEMALE', 'UNSPECIFIED'); - --- CreateTable -CREATE TABLE "User" ( - "id" SERIAL NOT NULL, - "email" TEXT NOT NULL, - "password" TEXT NOT NULL, - "firstname" TEXT NOT NULL, - "lastname" TEXT NOT NULL, - "birthday" TEXT, - "gender" "Gender", - "role" "Role" NOT NULL, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Gliederung" ( - "id" SERIAL NOT NULL, - "name" TEXT NOT NULL, - "edv" INTEGER NOT NULL, - - CONSTRAINT "Gliederung_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Anmeldung" ( - "id" SERIAL NOT NULL, - - CONSTRAINT "Anmeldung_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); diff --git a/api/prisma/migrations/20231124205546_/migration.sql b/api/prisma/migrations/20231124205546_/migration.sql deleted file mode 100644 index dfaf710c..00000000 --- a/api/prisma/migrations/20231124205546_/migration.sql +++ /dev/null @@ -1,48 +0,0 @@ -/* - Warnings: - - - A unique constraint covering the columns `[edv]` on the table `Gliederung` will be added. If there are existing duplicate values, this will fail. - - Added the required column `landesverbandId` to the `Gliederung` table without a default value. This is not possible if the table is not empty. - -*/ --- AlterTable -ALTER TABLE "Gliederung" ADD COLUMN "bezirkId" INTEGER, -ADD COLUMN "landesverbandId" INTEGER NOT NULL, -ALTER COLUMN "edv" SET DATA TYPE TEXT; - --- CreateTable -CREATE TABLE "Landesverband" ( - "id" SERIAL NOT NULL, - "name" TEXT NOT NULL, - "edv" TEXT NOT NULL, - - CONSTRAINT "Landesverband_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Bezirk" ( - "id" SERIAL NOT NULL, - "name" TEXT NOT NULL, - "edv" TEXT NOT NULL, - "landesverbandId" INTEGER, - - CONSTRAINT "Bezirk_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "Landesverband_edv_key" ON "Landesverband"("edv"); - --- CreateIndex -CREATE UNIQUE INDEX "Bezirk_edv_key" ON "Bezirk"("edv"); - --- CreateIndex -CREATE UNIQUE INDEX "Gliederung_edv_key" ON "Gliederung"("edv"); - --- AddForeignKey -ALTER TABLE "Bezirk" ADD CONSTRAINT "Bezirk_landesverbandId_fkey" FOREIGN KEY ("landesverbandId") REFERENCES "Landesverband"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Gliederung" ADD CONSTRAINT "Gliederung_landesverbandId_fkey" FOREIGN KEY ("landesverbandId") REFERENCES "Landesverband"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Gliederung" ADD CONSTRAINT "Gliederung_bezirkId_fkey" FOREIGN KEY ("bezirkId") REFERENCES "Bezirk"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20231124221217_/migration.sql b/api/prisma/migrations/20231124221217_/migration.sql deleted file mode 100644 index bb446641..00000000 --- a/api/prisma/migrations/20231124221217_/migration.sql +++ /dev/null @@ -1,93 +0,0 @@ -/* - Warnings: - - - You are about to drop the `User` table. If the table is not empty, all the data it contains will be lost. - - Added the required column `personId` to the `Anmeldung` table without a default value. This is not possible if the table is not empty. - - Added the required column `unterveranstaltungId` to the `Anmeldung` table without a default value. This is not possible if the table is not empty. - -*/ --- CreateEnum -CREATE TYPE "AnmeldungStatus" AS ENUM ('OFFEN', 'ANGENOMMEN', 'STORNIERT', 'ABGELEHNT'); - --- AlterTable -ALTER TABLE "Anmeldung" ADD COLUMN "personId" INTEGER NOT NULL, -ADD COLUMN "status" "AnmeldungStatus" NOT NULL DEFAULT 'OFFEN', -ADD COLUMN "unterveranstaltungId" INTEGER NOT NULL; - --- DropTable -DROP TABLE "User"; - --- CreateTable -CREATE TABLE "Person" ( - "id" SERIAL NOT NULL, - "firstname" TEXT NOT NULL, - "lastname" TEXT NOT NULL, - "birthday" TEXT, - "gender" "Gender", - "accountId" INTEGER NOT NULL, - "gliederungId" INTEGER, - - CONSTRAINT "Person_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Account" ( - "id" SERIAL NOT NULL, - "email" TEXT NOT NULL, - "password" TEXT NOT NULL, - "role" "Role" NOT NULL, - - CONSTRAINT "Account_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Veranstaltung" ( - "id" SERIAL NOT NULL, - "name" TEXT NOT NULL, - "beginn" DATE NOT NULL, - "ende" DATE NOT NULL, - "meldebeginn" DATE NOT NULL, - "meldeschluss" DATE NOT NULL, - "ort" TEXT NOT NULL DEFAULT 'Brahmsee', - "maxTeilnehmende" INTEGER NOT NULL, - "teilnahmegebuehr" INTEGER NOT NULL, - - CONSTRAINT "Veranstaltung_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Unterveranstaltung" ( - "id" SERIAL NOT NULL, - "maxTeilnehmende" INTEGER NOT NULL, - "teilnahmegebuehr" INTEGER NOT NULL, - "meldebeginn" DATE NOT NULL, - "meldeschluss" DATE NOT NULL, - "veranstaltungId" INTEGER NOT NULL, - "gliederungId" INTEGER NOT NULL, - - CONSTRAINT "Unterveranstaltung_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "Person_accountId_key" ON "Person"("accountId"); - --- CreateIndex -CREATE UNIQUE INDEX "Account_email_key" ON "Account"("email"); - --- AddForeignKey -ALTER TABLE "Person" ADD CONSTRAINT "Person_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Person" ADD CONSTRAINT "Person_gliederungId_fkey" FOREIGN KEY ("gliederungId") REFERENCES "Gliederung"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Anmeldung" ADD CONSTRAINT "Anmeldung_unterveranstaltungId_fkey" FOREIGN KEY ("unterveranstaltungId") REFERENCES "Unterveranstaltung"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Anmeldung" ADD CONSTRAINT "Anmeldung_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Unterveranstaltung" ADD CONSTRAINT "Unterveranstaltung_veranstaltungId_fkey" FOREIGN KEY ("veranstaltungId") REFERENCES "Veranstaltung"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Unterveranstaltung" ADD CONSTRAINT "Unterveranstaltung_gliederungId_fkey" FOREIGN KEY ("gliederungId") REFERENCES "Gliederung"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20231124222000_/migration.sql b/api/prisma/migrations/20231124222000_/migration.sql deleted file mode 100644 index cc522d5b..00000000 --- a/api/prisma/migrations/20231124222000_/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ --- DropForeignKey -ALTER TABLE "Person" DROP CONSTRAINT "Person_accountId_fkey"; - --- AlterTable -ALTER TABLE "Person" ALTER COLUMN "accountId" DROP NOT NULL; - --- AddForeignKey -ALTER TABLE "Person" ADD CONSTRAINT "Person_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20231124223126_/migration.sql b/api/prisma/migrations/20231124223126_/migration.sql deleted file mode 100644 index 580a6297..00000000 --- a/api/prisma/migrations/20231124223126_/migration.sql +++ /dev/null @@ -1,17 +0,0 @@ -/* - Warnings: - - - A unique constraint covering the columns `[personId]` on the table `Account` will be added. If there are existing duplicate values, this will fail. - -*/ --- DropForeignKey -ALTER TABLE "Person" DROP CONSTRAINT "Person_accountId_fkey"; - --- AlterTable -ALTER TABLE "Account" ADD COLUMN "personId" INTEGER; - --- CreateIndex -CREATE UNIQUE INDEX "Account_personId_key" ON "Account"("personId"); - --- AddForeignKey -ALTER TABLE "Account" ADD CONSTRAINT "Account_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20231125082417_/migration.sql b/api/prisma/migrations/20231125082417_/migration.sql deleted file mode 100644 index 74961026..00000000 --- a/api/prisma/migrations/20231125082417_/migration.sql +++ /dev/null @@ -1,9 +0,0 @@ -/* - Warnings: - - - The `birthday` column on the `Person` table would be dropped and recreated. This will lead to data loss if there is data in the column. - -*/ --- AlterTable -ALTER TABLE "Person" DROP COLUMN "birthday", -ADD COLUMN "birthday" DATE; diff --git a/api/prisma/migrations/20231125083936_remove_account_id_from_person/migration.sql b/api/prisma/migrations/20231125083936_remove_account_id_from_person/migration.sql deleted file mode 100644 index e17d5b3e..00000000 --- a/api/prisma/migrations/20231125083936_remove_account_id_from_person/migration.sql +++ /dev/null @@ -1,11 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `accountId` on the `Person` table. All the data in the column will be lost. - -*/ --- DropIndex -DROP INDEX "Person_accountId_key"; - --- AlterTable -ALTER TABLE "Person" DROP COLUMN "accountId"; diff --git a/api/prisma/migrations/20231125095739_add_gliederung_admin/migration.sql b/api/prisma/migrations/20231125095739_add_gliederung_admin/migration.sql deleted file mode 100644 index fc501e02..00000000 --- a/api/prisma/migrations/20231125095739_add_gliederung_admin/migration.sql +++ /dev/null @@ -1,20 +0,0 @@ --- AlterTable -ALTER TABLE "Gliederung" ADD COLUMN "adminIds" INTEGER[]; - --- CreateTable -CREATE TABLE "_AccountToGliederung" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "_AccountToGliederung_AB_unique" ON "_AccountToGliederung"("A", "B"); - --- CreateIndex -CREATE INDEX "_AccountToGliederung_B_index" ON "_AccountToGliederung"("B"); - --- AddForeignKey -ALTER TABLE "_AccountToGliederung" ADD CONSTRAINT "_AccountToGliederung_A_fkey" FOREIGN KEY ("A") REFERENCES "Account"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_AccountToGliederung" ADD CONSTRAINT "_AccountToGliederung_B_fkey" FOREIGN KEY ("B") REFERENCES "Gliederung"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20231125100914_remove_bezirk_and_landesverband/migration.sql b/api/prisma/migrations/20231125100914_remove_bezirk_and_landesverband/migration.sql deleted file mode 100644 index a70282d6..00000000 --- a/api/prisma/migrations/20231125100914_remove_bezirk_and_landesverband/migration.sql +++ /dev/null @@ -1,27 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `bezirkId` on the `Gliederung` table. All the data in the column will be lost. - - You are about to drop the column `landesverbandId` on the `Gliederung` table. All the data in the column will be lost. - - You are about to drop the `Bezirk` table. If the table is not empty, all the data it contains will be lost. - - You are about to drop the `Landesverband` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropForeignKey -ALTER TABLE "Bezirk" DROP CONSTRAINT "Bezirk_landesverbandId_fkey"; - --- DropForeignKey -ALTER TABLE "Gliederung" DROP CONSTRAINT "Gliederung_bezirkId_fkey"; - --- DropForeignKey -ALTER TABLE "Gliederung" DROP CONSTRAINT "Gliederung_landesverbandId_fkey"; - --- AlterTable -ALTER TABLE "Gliederung" DROP COLUMN "bezirkId", -DROP COLUMN "landesverbandId"; - --- DropTable -DROP TABLE "Bezirk"; - --- DropTable -DROP TABLE "Landesverband"; diff --git a/api/prisma/migrations/20231125121017_add_activated_at_to_account/migration.sql b/api/prisma/migrations/20231125121017_add_activated_at_to_account/migration.sql deleted file mode 100644 index 71cd0902..00000000 --- a/api/prisma/migrations/20231125121017_add_activated_at_to_account/migration.sql +++ /dev/null @@ -1,3 +0,0 @@ --- AlterTable -ALTER TABLE "Account" ADD COLUMN "activatedAt" TIMESTAMP(3); -UPDATE "Account" SET "activatedAt" = NOW() diff --git a/api/prisma/migrations/20231125132618_make_person_required_in_account/migration.sql b/api/prisma/migrations/20231125132618_make_person_required_in_account/migration.sql deleted file mode 100644 index 7292d790..00000000 --- a/api/prisma/migrations/20231125132618_make_person_required_in_account/migration.sql +++ /dev/null @@ -1,14 +0,0 @@ -/* - Warnings: - - - Made the column `personId` on table `Account` required. This step will fail if there are existing NULL values in that column. - -*/ --- DropForeignKey -ALTER TABLE "Account" DROP CONSTRAINT "Account_personId_fkey"; - --- AlterTable -ALTER TABLE "Account" ALTER COLUMN "personId" SET NOT NULL; - --- AddForeignKey -ALTER TABLE "Account" ADD CONSTRAINT "Account_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20231125190338_anmeldedaten/migration.sql b/api/prisma/migrations/20231125190338_anmeldedaten/migration.sql deleted file mode 100644 index a2185d6b..00000000 --- a/api/prisma/migrations/20231125190338_anmeldedaten/migration.sql +++ /dev/null @@ -1,102 +0,0 @@ --- CreateEnum -CREATE TYPE "Essgewohnheit" AS ENUM ('OMNIVORE', 'VEGETARISCH', 'VEGAN'); - --- CreateEnum -CREATE TYPE "NahrungsmittelIntoleranz" AS ENUM ('SCHWEIN', 'GLUTEN', 'LAKTOSE', 'FRUCTOSE'); - --- CreateEnum -CREATE TYPE "QualificationFahrerlaubnis" AS ENUM ('B', 'BE', 'C', 'CE', 'D1', 'D', 'D1E', 'DE', 'T', 'L'); - --- CreateEnum -CREATE TYPE "QualificationSchwimmer" AS ENUM ('BRONZE', 'SILBER', 'GOLD', 'JUNIORRETTER', 'RETTUNGSSCHWIMMER_BRONZE', 'RETTUNGSSCHWIMMER_SILBER', 'RETTUNGSSCHWIMMER_GOLD'); - --- CreateEnum -CREATE TYPE "QualificationErsteHilfe" AS ENUM ('MODULE_1', 'MODULE_2', 'MODULE_3', 'AUSBILDUNG', 'KINDERNOTFAELLE', 'BILDUNGS_UND_BETREUUNGSEINRICHTUNGEN_KINDER', 'AUSBILDER'); - --- CreateEnum -CREATE TYPE "QualificationSanitaeter" AS ENUM ('SAN_A', 'SAN_B', 'FORTBILDUNG', 'AUSBILDER'); - --- CreateEnum -CREATE TYPE "QualificationFunk" AS ENUM ('DLRG_SPRECHFUNKER', 'BOS_SPRECHFUNKER_ANALOG', 'BOS_SPRECHFUNKER_DIGITAL', 'AUSBILDER_SPRECHFUNK', 'AUSBILDER_BOS_SPRECHFUNK', 'MULTIPLIKATOR_SPRECHFUNK', 'MULTIPLIKATOR_BOS_SPRECHFUNK', 'EINSATZFAEHIGKEIT'); - --- CreateEnum -CREATE TYPE "Konfektionsgroesse" AS ENUM ('JUNIOR_98_104', 'JUNIOR_110_116', 'JUNIOR_122_128', 'JUNIOR_134_140', 'JUNIOR_146_152', 'JUNIOR_158_164', 'XS', 'S', 'M', 'L', 'XL', 'XXL', 'XXXL'); - --- CreateEnum -CREATE TYPE "MahlzeitType" AS ENUM ('FRUEHSTUECK', 'MITTAGESSEN', 'ABENDESSEN'); - --- AlterTable -ALTER TABLE "Anmeldung" ADD COLUMN "mahlzeitenIds" INTEGER[], -ADD COLUMN "uebernachtungsTage" DATE[]; - --- AlterTable -ALTER TABLE "Person" ADD COLUMN "essgewohnheit" "Essgewohnheit", -ADD COLUMN "konfektionsgroesse" "Konfektionsgroesse", -ADD COLUMN "nahrungsmittelIntoleranzen" "NahrungsmittelIntoleranz"[], -ADD COLUMN "notfallkontaktIds" INTEGER[], -ADD COLUMN "qualifikationenErsteHilfe" "QualificationErsteHilfe"[], -ADD COLUMN "qualifikationenFahrerlaubnis" "QualificationFahrerlaubnis"[], -ADD COLUMN "qualifikationenFunk" "QualificationFunk"[], -ADD COLUMN "qualifikationenSanitaeter" "QualificationSanitaeter"[], -ADD COLUMN "qualifikationenSchwimmer" "QualificationSchwimmer"[], -ADD COLUMN "weitereIntoleranzen" TEXT[]; - --- CreateTable -CREATE TABLE "Notfallkontakt" ( - "id" SERIAL NOT NULL, - "firstname" TEXT NOT NULL, - "lastname" TEXT NOT NULL, - "telefon" TEXT NOT NULL, - "istErziehungsberechtigt" BOOLEAN, - - CONSTRAINT "Notfallkontakt_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Mahlzeit" ( - "id" SERIAL NOT NULL, - "type" "MahlzeitType" NOT NULL, - "date" DATE NOT NULL, - "veranstaltungId" INTEGER NOT NULL, - - CONSTRAINT "Mahlzeit_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "_NotfallkontaktToPerson" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL -); - --- CreateTable -CREATE TABLE "_AnmeldungToMahlzeit" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "_NotfallkontaktToPerson_AB_unique" ON "_NotfallkontaktToPerson"("A", "B"); - --- CreateIndex -CREATE INDEX "_NotfallkontaktToPerson_B_index" ON "_NotfallkontaktToPerson"("B"); - --- CreateIndex -CREATE UNIQUE INDEX "_AnmeldungToMahlzeit_AB_unique" ON "_AnmeldungToMahlzeit"("A", "B"); - --- CreateIndex -CREATE INDEX "_AnmeldungToMahlzeit_B_index" ON "_AnmeldungToMahlzeit"("B"); - --- AddForeignKey -ALTER TABLE "Mahlzeit" ADD CONSTRAINT "Mahlzeit_veranstaltungId_fkey" FOREIGN KEY ("veranstaltungId") REFERENCES "Veranstaltung"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_NotfallkontaktToPerson" ADD CONSTRAINT "_NotfallkontaktToPerson_A_fkey" FOREIGN KEY ("A") REFERENCES "Notfallkontakt"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_NotfallkontaktToPerson" ADD CONSTRAINT "_NotfallkontaktToPerson_B_fkey" FOREIGN KEY ("B") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_AnmeldungToMahlzeit" ADD CONSTRAINT "_AnmeldungToMahlzeit_A_fkey" FOREIGN KEY ("A") REFERENCES "Anmeldung"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_AnmeldungToMahlzeit" ADD CONSTRAINT "_AnmeldungToMahlzeit_B_fkey" FOREIGN KEY ("B") REFERENCES "Mahlzeit"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20231125191634_add_more_erste_hilfe_qualifications/migration.sql b/api/prisma/migrations/20231125191634_add_more_erste_hilfe_qualifications/migration.sql deleted file mode 100644 index f28b95c5..00000000 --- a/api/prisma/migrations/20231125191634_add_more_erste_hilfe_qualifications/migration.sql +++ /dev/null @@ -1,11 +0,0 @@ --- AlterEnum --- This migration adds more than one value to an enum. --- With PostgreSQL versions 11 and earlier, this is not possible --- in a single migration. This can be worked around by creating --- multiple migrations, each migration adding only one value to --- the enum. - - -ALTER TYPE "QualificationErsteHilfe" ADD VALUE 'EINWEISER_EHSH'; -ALTER TYPE "QualificationErsteHilfe" ADD VALUE 'AUSBILDER_EHSH_MODUL_1_2'; -ALTER TYPE "QualificationErsteHilfe" ADD VALUE 'AUSBILDER_EHSH_MODUL_3'; diff --git a/api/prisma/migrations/20231125192806_add_gliederungen_to_account_and_remove_admin_ids/migration.sql b/api/prisma/migrations/20231125192806_add_gliederungen_to_account_and_remove_admin_ids/migration.sql deleted file mode 100644 index 17c79624..00000000 --- a/api/prisma/migrations/20231125192806_add_gliederungen_to_account_and_remove_admin_ids/migration.sql +++ /dev/null @@ -1,37 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `adminIds` on the `Gliederung` table. All the data in the column will be lost. - - You are about to drop the `_AccountToGliederung` table. If the table is not empty, all the data it contains will be lost. - -*/ --- CreateEnum -CREATE TYPE "GliederungAccountRole" AS ENUM ('DELIGATIONSLEITER', 'BETREUER', 'TEILNEHMER'); - --- DropForeignKey -ALTER TABLE "_AccountToGliederung" DROP CONSTRAINT "_AccountToGliederung_A_fkey"; - --- DropForeignKey -ALTER TABLE "_AccountToGliederung" DROP CONSTRAINT "_AccountToGliederung_B_fkey"; - --- AlterTable -ALTER TABLE "Gliederung" DROP COLUMN "adminIds"; - --- DropTable -DROP TABLE "_AccountToGliederung"; - --- CreateTable -CREATE TABLE "GliederungToAccount" ( - "id" SERIAL NOT NULL, - "gliederungId" INTEGER NOT NULL, - "accountId" INTEGER NOT NULL, - "role" "GliederungAccountRole" NOT NULL, - - CONSTRAINT "GliederungToAccount_pkey" PRIMARY KEY ("id") -); - --- AddForeignKey -ALTER TABLE "GliederungToAccount" ADD CONSTRAINT "GliederungToAccount_gliederungId_fkey" FOREIGN KEY ("gliederungId") REFERENCES "Gliederung"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "GliederungToAccount" ADD CONSTRAINT "GliederungToAccount_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20231125193543_unique_gliederung_account_relation/migration.sql b/api/prisma/migrations/20231125193543_unique_gliederung_account_relation/migration.sql deleted file mode 100644 index 3bf6181d..00000000 --- a/api/prisma/migrations/20231125193543_unique_gliederung_account_relation/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ -/* - Warnings: - - - A unique constraint covering the columns `[gliederungId,accountId]` on the table `GliederungToAccount` will be added. If there are existing duplicate values, this will fail. - -*/ --- CreateIndex -CREATE UNIQUE INDEX "GliederungToAccount_gliederungId_accountId_key" ON "GliederungToAccount"("gliederungId", "accountId"); diff --git a/api/prisma/migrations/20231125195102_add_unterveranstaltung_beschreibung/migration.sql b/api/prisma/migrations/20231125195102_add_unterveranstaltung_beschreibung/migration.sql deleted file mode 100644 index 665aa9b8..00000000 --- a/api/prisma/migrations/20231125195102_add_unterveranstaltung_beschreibung/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Unterveranstaltung" ADD COLUMN "beschreibung" TEXT; diff --git a/api/prisma/migrations/20231125195901_add_tshirt_bestellt_to_anmeldung/migration.sql b/api/prisma/migrations/20231125195901_add_tshirt_bestellt_to_anmeldung/migration.sql deleted file mode 100644 index 693dd976..00000000 --- a/api/prisma/migrations/20231125195901_add_tshirt_bestellt_to_anmeldung/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Anmeldung" ADD COLUMN "tshirtBestellt" BOOLEAN NOT NULL DEFAULT false; diff --git a/api/prisma/migrations/20231126083025_add_unterveranstaltung_type/migration.sql b/api/prisma/migrations/20231126083025_add_unterveranstaltung_type/migration.sql deleted file mode 100644 index 52116c5e..00000000 --- a/api/prisma/migrations/20231126083025_add_unterveranstaltung_type/migration.sql +++ /dev/null @@ -1,5 +0,0 @@ --- CreateEnum -CREATE TYPE "UnterveranstaltungType" AS ENUM ('CREW', 'GLIEDERUNG'); - --- AlterTable -ALTER TABLE "Unterveranstaltung" ADD COLUMN "type" "UnterveranstaltungType" NOT NULL DEFAULT 'GLIEDERUNG'; diff --git a/api/prisma/migrations/20231126090617_veranstaltung_add_beschreibung/migration.sql b/api/prisma/migrations/20231126090617_veranstaltung_add_beschreibung/migration.sql deleted file mode 100644 index bc662072..00000000 --- a/api/prisma/migrations/20231126090617_veranstaltung_add_beschreibung/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Veranstaltung" ADD COLUMN "beschreibung" TEXT; diff --git a/api/prisma/migrations/20231126091627_veranstaltung_add_datenschutz_and_teilnahmebedingungen/migration.sql b/api/prisma/migrations/20231126091627_veranstaltung_add_datenschutz_and_teilnahmebedingungen/migration.sql deleted file mode 100644 index 1a0c9901..00000000 --- a/api/prisma/migrations/20231126091627_veranstaltung_add_datenschutz_and_teilnahmebedingungen/migration.sql +++ /dev/null @@ -1,3 +0,0 @@ --- AlterTable -ALTER TABLE "Veranstaltung" ADD COLUMN "datenschutz" TEXT, -ADD COLUMN "teilnahmeBedingungen" TEXT; diff --git a/api/prisma/migrations/20231126092320_veranstaltung_add_zielgruppe/migration.sql b/api/prisma/migrations/20231126092320_veranstaltung_add_zielgruppe/migration.sql deleted file mode 100644 index a25fa83a..00000000 --- a/api/prisma/migrations/20231126092320_veranstaltung_add_zielgruppe/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Veranstaltung" ADD COLUMN "zielgruppe" TEXT; diff --git a/api/prisma/migrations/20231126093736_dates_to_datetime/migration.sql b/api/prisma/migrations/20231126093736_dates_to_datetime/migration.sql deleted file mode 100644 index 380f7843..00000000 --- a/api/prisma/migrations/20231126093736_dates_to_datetime/migration.sql +++ /dev/null @@ -1,7 +0,0 @@ --- AlterTable -ALTER TABLE "Unterveranstaltung" ALTER COLUMN "meldebeginn" SET DATA TYPE TIMESTAMP(3), -ALTER COLUMN "meldeschluss" SET DATA TYPE TIMESTAMP(3); - --- AlterTable -ALTER TABLE "Veranstaltung" ALTER COLUMN "meldebeginn" SET DATA TYPE TIMESTAMP(3), -ALTER COLUMN "meldeschluss" SET DATA TYPE TIMESTAMP(3); diff --git a/api/prisma/migrations/20231126105007_address_and_ort/migration.sql b/api/prisma/migrations/20231126105007_address_and_ort/migration.sql deleted file mode 100644 index afb7b743..00000000 --- a/api/prisma/migrations/20231126105007_address_and_ort/migration.sql +++ /dev/null @@ -1,41 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `ort` on the `Veranstaltung` table. All the data in the column will be lost. - -*/ --- AlterTable -ALTER TABLE "Person" ADD COLUMN "addressId" INTEGER; - --- AlterTable -ALTER TABLE "Veranstaltung" DROP COLUMN "ort", -ADD COLUMN "ortId" INTEGER; - --- CreateTable -CREATE TABLE "Address" ( - "id" SERIAL NOT NULL, - "street" TEXT NOT NULL, - "number" TEXT NOT NULL, - "zip" TEXT NOT NULL, - "city" TEXT NOT NULL, - - CONSTRAINT "Address_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Ort" ( - "id" SERIAL NOT NULL, - "name" TEXT NOT NULL, - "addressId" INTEGER, - - CONSTRAINT "Ort_pkey" PRIMARY KEY ("id") -); - --- AddForeignKey -ALTER TABLE "Person" ADD CONSTRAINT "Person_addressId_fkey" FOREIGN KEY ("addressId") REFERENCES "Address"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Ort" ADD CONSTRAINT "Ort_addressId_fkey" FOREIGN KEY ("addressId") REFERENCES "Address"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Veranstaltung" ADD CONSTRAINT "Veranstaltung_ortId_fkey" FOREIGN KEY ("ortId") REFERENCES "Ort"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20231202115527_add_email_to_anmeldung/migration.sql b/api/prisma/migrations/20231202115527_add_email_to_anmeldung/migration.sql deleted file mode 100644 index f25e90b2..00000000 --- a/api/prisma/migrations/20231202115527_add_email_to_anmeldung/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ -/* - Warnings: - - - Added the required column `email` to the `Anmeldung` table without a default value. This is not possible if the table is not empty. - -*/ --- AlterTable -ALTER TABLE "Anmeldung" ADD COLUMN "email" TEXT NOT NULL; diff --git a/api/prisma/migrations/20231203154638_set_relation_actions/migration.sql b/api/prisma/migrations/20231203154638_set_relation_actions/migration.sql deleted file mode 100644 index 7b54ed11..00000000 --- a/api/prisma/migrations/20231203154638_set_relation_actions/migration.sql +++ /dev/null @@ -1,47 +0,0 @@ --- DropForeignKey -ALTER TABLE "Account" DROP CONSTRAINT "Account_personId_fkey"; - --- DropForeignKey -ALTER TABLE "Anmeldung" DROP CONSTRAINT "Anmeldung_personId_fkey"; - --- DropForeignKey -ALTER TABLE "Anmeldung" DROP CONSTRAINT "Anmeldung_unterveranstaltungId_fkey"; - --- DropForeignKey -ALTER TABLE "GliederungToAccount" DROP CONSTRAINT "GliederungToAccount_accountId_fkey"; - --- DropForeignKey -ALTER TABLE "GliederungToAccount" DROP CONSTRAINT "GliederungToAccount_gliederungId_fkey"; - --- DropForeignKey -ALTER TABLE "Mahlzeit" DROP CONSTRAINT "Mahlzeit_veranstaltungId_fkey"; - --- DropForeignKey -ALTER TABLE "Unterveranstaltung" DROP CONSTRAINT "Unterveranstaltung_gliederungId_fkey"; - --- DropForeignKey -ALTER TABLE "Unterveranstaltung" DROP CONSTRAINT "Unterveranstaltung_veranstaltungId_fkey"; - --- AddForeignKey -ALTER TABLE "Account" ADD CONSTRAINT "Account_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "GliederungToAccount" ADD CONSTRAINT "GliederungToAccount_gliederungId_fkey" FOREIGN KEY ("gliederungId") REFERENCES "Gliederung"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "GliederungToAccount" ADD CONSTRAINT "GliederungToAccount_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Anmeldung" ADD CONSTRAINT "Anmeldung_unterveranstaltungId_fkey" FOREIGN KEY ("unterveranstaltungId") REFERENCES "Unterveranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Anmeldung" ADD CONSTRAINT "Anmeldung_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Mahlzeit" ADD CONSTRAINT "Mahlzeit_veranstaltungId_fkey" FOREIGN KEY ("veranstaltungId") REFERENCES "Veranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Unterveranstaltung" ADD CONSTRAINT "Unterveranstaltung_veranstaltungId_fkey" FOREIGN KEY ("veranstaltungId") REFERENCES "Veranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Unterveranstaltung" ADD CONSTRAINT "Unterveranstaltung_gliederungId_fkey" FOREIGN KEY ("gliederungId") REFERENCES "Gliederung"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20240115082853_edit_account_schema/migration.sql b/api/prisma/migrations/20240115082853_edit_account_schema/migration.sql deleted file mode 100644 index abb48c21..00000000 --- a/api/prisma/migrations/20240115082853_edit_account_schema/migration.sql +++ /dev/null @@ -1,16 +0,0 @@ -/* - Warnings: - - - A unique constraint covering the columns `[activationToken]` on the table `Account` will be added. If there are existing duplicate values, this will fail. - - A unique constraint covering the columns `[passwordResetToken]` on the table `Account` will be added. If there are existing duplicate values, this will fail. - -*/ --- AlterTable -ALTER TABLE "Account" ADD COLUMN "activationToken" TEXT, -ADD COLUMN "passwordResetToken" TEXT; - --- CreateIndex -CREATE UNIQUE INDEX "Account_activationToken_key" ON "Account"("activationToken"); - --- CreateIndex -CREATE UNIQUE INDEX "Account_passwordResetToken_key" ON "Account"("passwordResetToken"); diff --git a/api/prisma/migrations/20240115114556_add_oauth_to_user/migration.sql b/api/prisma/migrations/20240115114556_add_oauth_to_user/migration.sql deleted file mode 100644 index 98e0c696..00000000 --- a/api/prisma/migrations/20240115114556_add_oauth_to_user/migration.sql +++ /dev/null @@ -1,12 +0,0 @@ -/* - Warnings: - - - A unique constraint covering the columns `[dlrgOauthId]` on the table `Account` will be added. If there are existing duplicate values, this will fail. - -*/ --- AlterTable -ALTER TABLE "Account" ADD COLUMN "dlrgOauthId" TEXT, -ALTER COLUMN "password" DROP NOT NULL; - --- CreateIndex -CREATE UNIQUE INDEX "Account_dlrgOauthId_key" ON "Account"("dlrgOauthId"); diff --git a/api/prisma/migrations/20240117182608_add_account_status/migration.sql b/api/prisma/migrations/20240117182608_add_account_status/migration.sql deleted file mode 100644 index 8a8086eb..00000000 --- a/api/prisma/migrations/20240117182608_add_account_status/migration.sql +++ /dev/null @@ -1,5 +0,0 @@ --- CreateEnum -CREATE TYPE "AccountStatus" AS ENUM ('OPEN', 'ACTIVE', 'DISABLED'); - --- AlterTable -ALTER TABLE "Account" ADD COLUMN "status" "AccountStatus" NOT NULL DEFAULT 'OPEN'; diff --git a/api/prisma/migrations/20240121141654_contact_infos/migration.sql b/api/prisma/migrations/20240121141654_contact_infos/migration.sql deleted file mode 100644 index 2468baa4..00000000 --- a/api/prisma/migrations/20240121141654_contact_infos/migration.sql +++ /dev/null @@ -1,10 +0,0 @@ -/* - Warnings: - - - Added the required column `email` to the `Person` table without a default value. This is not possible if the table is not empty. - - Added the required column `telefon` to the `Person` table without a default value. This is not possible if the table is not empty. - -*/ --- AlterTable -ALTER TABLE "Person" ADD COLUMN "email" TEXT NOT NULL, -ADD COLUMN "telefon" TEXT NOT NULL; diff --git a/api/prisma/migrations/20240121150140_no_email_in_anmeldung/migration.sql b/api/prisma/migrations/20240121150140_no_email_in_anmeldung/migration.sql deleted file mode 100644 index 6332b824..00000000 --- a/api/prisma/migrations/20240121150140_no_email_in_anmeldung/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `email` on the `Anmeldung` table. All the data in the column will be lost. - -*/ --- AlterTable -ALTER TABLE "Anmeldung" DROP COLUMN "email"; diff --git a/api/prisma/migrations/20240123080731_rename_enum_omnivore/migration.sql b/api/prisma/migrations/20240123080731_rename_enum_omnivore/migration.sql deleted file mode 100644 index 6462d9eb..00000000 --- a/api/prisma/migrations/20240123080731_rename_enum_omnivore/migration.sql +++ /dev/null @@ -1,14 +0,0 @@ -/* - Warnings: - - - The values [OMNIVORE] on the enum `Essgewohnheit` will be removed. If these variants are still used in the database, this will fail. - -*/ --- AlterEnum -BEGIN; -CREATE TYPE "Essgewohnheit_new" AS ENUM ('OMNIVOR', 'VEGETARISCH', 'VEGAN'); -ALTER TABLE "Person" ALTER COLUMN "essgewohnheit" TYPE "Essgewohnheit_new" USING ("essgewohnheit"::text::"Essgewohnheit_new"); -ALTER TYPE "Essgewohnheit" RENAME TO "Essgewohnheit_old"; -ALTER TYPE "Essgewohnheit_new" RENAME TO "Essgewohnheit"; -DROP TYPE "Essgewohnheit_old"; -COMMIT; diff --git a/api/prisma/migrations/20240123082718_rename_enum_gliederungs_rolle/migration.sql b/api/prisma/migrations/20240123082718_rename_enum_gliederungs_rolle/migration.sql deleted file mode 100644 index 4e690315..00000000 --- a/api/prisma/migrations/20240123082718_rename_enum_gliederungs_rolle/migration.sql +++ /dev/null @@ -1,14 +0,0 @@ -/* - Warnings: - - - The values [DELIGATIONSLEITER] on the enum `GliederungAccountRole` will be removed. If these variants are still used in the database, this will fail. - -*/ --- AlterEnum -BEGIN; -CREATE TYPE "GliederungAccountRole_new" AS ENUM ('DELEGATIONSLEITER', 'BETREUER', 'TEILNEHMER'); -ALTER TABLE "GliederungToAccount" ALTER COLUMN "role" TYPE "GliederungAccountRole_new" USING ("role"::text::"GliederungAccountRole_new"); -ALTER TYPE "GliederungAccountRole" RENAME TO "GliederungAccountRole_old"; -ALTER TYPE "GliederungAccountRole_new" RENAME TO "GliederungAccountRole"; -DROP TYPE "GliederungAccountRole_old"; -COMMIT; diff --git a/api/prisma/migrations/20240124161620_anmeldung_created_at/migration.sql b/api/prisma/migrations/20240124161620_anmeldung_created_at/migration.sql deleted file mode 100644 index 594ad409..00000000 --- a/api/prisma/migrations/20240124161620_anmeldung_created_at/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Anmeldung" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; diff --git a/api/prisma/migrations/20240127155128_update_notfallkontakt_relation/migration.sql b/api/prisma/migrations/20240127155128_update_notfallkontakt_relation/migration.sql deleted file mode 100644 index 6a5b6e92..00000000 --- a/api/prisma/migrations/20240127155128_update_notfallkontakt_relation/migration.sql +++ /dev/null @@ -1,24 +0,0 @@ -/* - Warnings: - - - You are about to drop the `_NotfallkontaktToPerson` table. If the table is not empty, all the data it contains will be lost. - - Added the required column `personId` to the `Notfallkontakt` table without a default value. This is not possible if the table is not empty. - - Made the column `istErziehungsberechtigt` on table `Notfallkontakt` required. This step will fail if there are existing NULL values in that column. - -*/ --- DropForeignKey -ALTER TABLE "_NotfallkontaktToPerson" DROP CONSTRAINT "_NotfallkontaktToPerson_A_fkey"; - --- DropForeignKey -ALTER TABLE "_NotfallkontaktToPerson" DROP CONSTRAINT "_NotfallkontaktToPerson_B_fkey"; - --- AlterTable -ALTER TABLE "Notfallkontakt" ADD COLUMN "personId" INTEGER NOT NULL, -ALTER COLUMN "istErziehungsberechtigt" SET NOT NULL, -ALTER COLUMN "istErziehungsberechtigt" SET DEFAULT false; - --- DropTable -DROP TABLE "_NotfallkontaktToPerson"; - --- AddForeignKey -ALTER TABLE "Notfallkontakt" ADD CONSTRAINT "Notfallkontakt_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20240127183701_restructure_status_enums/migration.sql b/api/prisma/migrations/20240127183701_restructure_status_enums/migration.sql deleted file mode 100644 index 1905063d..00000000 --- a/api/prisma/migrations/20240127183701_restructure_status_enums/migration.sql +++ /dev/null @@ -1,31 +0,0 @@ -/* - Warnings: - - - The values [OPEN,ACTIVE,DISABLED] on the enum `AccountStatus` will be removed. If these variants are still used in the database, this will fail. - - The values [ANGENOMMEN] on the enum `AnmeldungStatus` will be removed. If these variants are still used in the database, this will fail. - -*/ --- AlterEnum -BEGIN; -CREATE TYPE "AccountStatus_new" AS ENUM ('OFFEN', 'AKTIV', 'DEAKTIVIERT'); -ALTER TABLE "Account" ALTER COLUMN "status" DROP DEFAULT; -ALTER TABLE "Account" ALTER COLUMN "status" TYPE "AccountStatus_new" USING ("status"::text::"AccountStatus_new"); -ALTER TYPE "AccountStatus" RENAME TO "AccountStatus_old"; -ALTER TYPE "AccountStatus_new" RENAME TO "AccountStatus"; -DROP TYPE "AccountStatus_old"; -ALTER TABLE "Account" ALTER COLUMN "status" SET DEFAULT 'OFFEN'; -COMMIT; - --- AlterEnum -BEGIN; -CREATE TYPE "AnmeldungStatus_new" AS ENUM ('OFFEN', 'BESTAETIGT', 'STORNIERT', 'ABGELEHNT'); -ALTER TABLE "Anmeldung" ALTER COLUMN "status" DROP DEFAULT; -ALTER TABLE "Anmeldung" ALTER COLUMN "status" TYPE "AnmeldungStatus_new" USING ("status"::text::"AnmeldungStatus_new"); -ALTER TYPE "AnmeldungStatus" RENAME TO "AnmeldungStatus_old"; -ALTER TYPE "AnmeldungStatus_new" RENAME TO "AnmeldungStatus"; -DROP TYPE "AnmeldungStatus_old"; -ALTER TABLE "Anmeldung" ALTER COLUMN "status" SET DEFAULT 'OFFEN'; -COMMIT; - --- AlterTable -ALTER TABLE "Account" ALTER COLUMN "status" SET DEFAULT 'OFFEN'; diff --git a/api/prisma/migrations/20240203135805_activity_logging/migration.sql b/api/prisma/migrations/20240203135805_activity_logging/migration.sql deleted file mode 100644 index 10fc217d..00000000 --- a/api/prisma/migrations/20240203135805_activity_logging/migration.sql +++ /dev/null @@ -1,19 +0,0 @@ --- CreateEnum -CREATE TYPE "ActivityType" AS ENUM ('CREATE', 'UPDATE', 'DELETE', 'EMAIL', 'OTHER'); - --- CreateTable -CREATE TABLE "Activity" ( - "id" SERIAL NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "type" "ActivityType" NOT NULL, - "description" TEXT, - "subjectType" TEXT NOT NULL, - "subjectId" INTEGER, - "causerId" INTEGER, - "metadata" JSONB NOT NULL DEFAULT '{}', - - CONSTRAINT "Activity_pkey" PRIMARY KEY ("id") -); - --- AddForeignKey -ALTER TABLE "Activity" ADD CONSTRAINT "Activity_causerId_fkey" FOREIGN KEY ("causerId") REFERENCES "Account"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20240209202702_add_cacade_deletion_person/migration.sql b/api/prisma/migrations/20240209202702_add_cacade_deletion_person/migration.sql deleted file mode 100644 index a4198eb9..00000000 --- a/api/prisma/migrations/20240209202702_add_cacade_deletion_person/migration.sql +++ /dev/null @@ -1,5 +0,0 @@ --- DropForeignKey -ALTER TABLE "Person" DROP CONSTRAINT "Person_addressId_fkey"; - --- AddForeignKey -ALTER TABLE "Person" ADD CONSTRAINT "Person_addressId_fkey" FOREIGN KEY ("addressId") REFERENCES "Address"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20240218163639_add_commentfield_to_anmeldung/migration.sql b/api/prisma/migrations/20240218163639_add_commentfield_to_anmeldung/migration.sql deleted file mode 100644 index b8879afe..00000000 --- a/api/prisma/migrations/20240218163639_add_commentfield_to_anmeldung/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Anmeldung" ADD COLUMN "comment" TEXT; diff --git a/api/prisma/migrations/20240222191226_addhostname/migration.sql b/api/prisma/migrations/20240222191226_addhostname/migration.sql deleted file mode 100644 index f59b83cf..00000000 --- a/api/prisma/migrations/20240222191226_addhostname/migration.sql +++ /dev/null @@ -1,13 +0,0 @@ --- AlterTable -ALTER TABLE "Veranstaltung" ADD COLUMN "hostnameId" INTEGER; - --- CreateTable -CREATE TABLE "Hostname" ( - "id" SERIAL NOT NULL, - "hostname" TEXT NOT NULL, - - CONSTRAINT "Hostname_pkey" PRIMARY KEY ("id") -); - --- AddForeignKey -ALTER TABLE "Veranstaltung" ADD CONSTRAINT "Veranstaltung_hostnameId_fkey" FOREIGN KEY ("hostnameId") REFERENCES "Hostname"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20240226065838_add_field_bedingungen_to_unterveranstaltung/migration.sql b/api/prisma/migrations/20240226065838_add_field_bedingungen_to_unterveranstaltung/migration.sql deleted file mode 100644 index 4b80c112..00000000 --- a/api/prisma/migrations/20240226065838_add_field_bedingungen_to_unterveranstaltung/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Unterveranstaltung" ADD COLUMN "bedingungen" TEXT; diff --git a/api/prisma/migrations/20240226072942_add_public_teilnahmebedingungen/migration.sql b/api/prisma/migrations/20240226072942_add_public_teilnahmebedingungen/migration.sql deleted file mode 100644 index 772287cb..00000000 --- a/api/prisma/migrations/20240226072942_add_public_teilnahmebedingungen/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "Veranstaltung" ADD COLUMN "teilnahmeBedingungenPublic" TEXT; diff --git a/api/prisma/migrations/20240323130234_custom_fields/migration.sql b/api/prisma/migrations/20240323130234_custom_fields/migration.sql deleted file mode 100644 index bc9f859f..00000000 --- a/api/prisma/migrations/20240323130234_custom_fields/migration.sql +++ /dev/null @@ -1,40 +0,0 @@ --- CreateTable -CREATE TABLE "CustomField" ( - "id" SERIAL NOT NULL, - "name" TEXT NOT NULL, - "description" TEXT, - "type" TEXT NOT NULL, - "required" BOOLEAN NOT NULL, - "options" TEXT[], - "role" "Role"[], - "veranstaltungId" INTEGER, - "unterveranstaltungId" INTEGER, - "personId" INTEGER, - - CONSTRAINT "CustomField_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "CustomFieldValue" ( - "id" SERIAL NOT NULL, - "value" TEXT, - "fieldId" INTEGER NOT NULL, - "anmeldungId" INTEGER NOT NULL, - - CONSTRAINT "CustomFieldValue_pkey" PRIMARY KEY ("id") -); - --- AddForeignKey -ALTER TABLE "CustomField" ADD CONSTRAINT "CustomField_veranstaltungId_fkey" FOREIGN KEY ("veranstaltungId") REFERENCES "Veranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "CustomField" ADD CONSTRAINT "CustomField_unterveranstaltungId_fkey" FOREIGN KEY ("unterveranstaltungId") REFERENCES "Unterveranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "CustomField" ADD CONSTRAINT "CustomField_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "CustomFieldValue" ADD CONSTRAINT "CustomFieldValue_fieldId_fkey" FOREIGN KEY ("fieldId") REFERENCES "CustomField"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "CustomFieldValue" ADD CONSTRAINT "CustomFieldValue_anmeldungId_fkey" FOREIGN KEY ("anmeldungId") REFERENCES "Anmeldung"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/api/prisma/migrations/20240324082502_custom_fields_positions/migration.sql b/api/prisma/migrations/20240324082502_custom_fields_positions/migration.sql deleted file mode 100644 index f6400577..00000000 --- a/api/prisma/migrations/20240324082502_custom_fields_positions/migration.sql +++ /dev/null @@ -1,5 +0,0 @@ --- CreateEnum -CREATE TYPE "CustomFieldPosition" AS ENUM ('PUBLIC_PERSON', 'PUBLIC_ANMELDUNG', 'INTERN_PERSON', 'INTERN_ANMELDUNG', 'INTERN_VERANSTALTUNG', 'INTERN_AUSSCHREIBUNG'); - --- AlterTable -ALTER TABLE "CustomField" ADD COLUMN "positions" "CustomFieldPosition"[]; diff --git a/api/prisma/migrations/20240329183615_add_custo_field_types/migration.sql b/api/prisma/migrations/20240329183615_add_custo_field_types/migration.sql deleted file mode 100644 index 868e5b2b..00000000 --- a/api/prisma/migrations/20240329183615_add_custo_field_types/migration.sql +++ /dev/null @@ -1,16 +0,0 @@ -/* - Warnings: - - - Changed the type of `type` on the `CustomField` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. - -*/ --- CreateEnum -CREATE TYPE "CustomFieldType" AS ENUM ('BASIC_INPUT', 'BASIC_TEXT_AREA', 'BASIC_EDITOR', 'BASIC_SWITCH', 'BASIC_CHECKBOX', 'BASIC_INPUT_NUMBER', 'BASIC_RADIO', 'BASIC_SELECT', 'BASIC_DROPDOWN'); - --- AlterTable -ALTER TABLE "CustomField" DROP COLUMN "type", -ADD COLUMN "type" "CustomFieldType" NOT NULL, -ALTER COLUMN "required" SET DEFAULT false; - --- AlterTable -ALTER TABLE "CustomFieldValue" ALTER COLUMN "anmeldungId" DROP NOT NULL; diff --git a/api/prisma/migrations/20240331173416_/migration.sql b/api/prisma/migrations/20240331173416_/migration.sql deleted file mode 100644 index 54cbdb7a..00000000 --- a/api/prisma/migrations/20240331173416_/migration.sql +++ /dev/null @@ -1,16 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `personId` on the `CustomField` table. All the data in the column will be lost. - - The `value` column on the `CustomFieldValue` table would be dropped and recreated. This will lead to data loss if there is data in the column. - -*/ --- DropForeignKey -ALTER TABLE "CustomField" DROP CONSTRAINT "CustomField_personId_fkey"; - --- AlterTable -ALTER TABLE "CustomField" DROP COLUMN "personId"; - --- AlterTable -ALTER TABLE "CustomFieldValue" DROP COLUMN "value", -ADD COLUMN "value" JSONB NOT NULL DEFAULT '{}'; diff --git a/api/prisma/migrations/20240331213716_edit_customfield_positions/migration.sql b/api/prisma/migrations/20240331213716_edit_customfield_positions/migration.sql deleted file mode 100644 index 8b35b344..00000000 --- a/api/prisma/migrations/20240331213716_edit_customfield_positions/migration.sql +++ /dev/null @@ -1,14 +0,0 @@ -/* - Warnings: - - - The values [PUBLIC_PERSON,INTERN_PERSON,INTERN_VERANSTALTUNG,INTERN_AUSSCHREIBUNG] on the enum `CustomFieldPosition` will be removed. If these variants are still used in the database, this will fail. - -*/ --- AlterEnum -BEGIN; -CREATE TYPE "CustomFieldPosition_new" AS ENUM ('PUBLIC_ANMELDUNG', 'INTERN_ANMELDUNG'); -ALTER TABLE "CustomField" ALTER COLUMN "positions" TYPE "CustomFieldPosition_new"[] USING ("positions"::text::"CustomFieldPosition_new"[]); -ALTER TYPE "CustomFieldPosition" RENAME TO "CustomFieldPosition_old"; -ALTER TYPE "CustomFieldPosition_new" RENAME TO "CustomFieldPosition"; -DROP TYPE "CustomFieldPosition_old"; -COMMIT; diff --git a/api/prisma/migrations/migration_lock.toml b/api/prisma/migrations/migration_lock.toml deleted file mode 100644 index fbffa92c..00000000 --- a/api/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "postgresql" \ No newline at end of file diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma deleted file mode 100644 index 9f405b99..00000000 --- a/api/prisma/schema.prisma +++ /dev/null @@ -1,360 +0,0 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -generator client { - provider = "prisma-client-js" - previewFeatures = ["fullTextSearch"] -} - -datasource db { - provider = "postgres" - url = env("DATABASE_URL") -} - -enum AccountStatus { - OFFEN - AKTIV - DEAKTIVIERT -} - -enum Role { - GLIEDERUNG_ADMIN - ADMIN -} - -enum Gender { - MALE - FEMALE - UNSPECIFIED -} - -enum Essgewohnheit { - OMNIVOR - VEGETARISCH - VEGAN -} - -enum NahrungsmittelIntoleranz { - SCHWEIN - GLUTEN - LAKTOSE - FRUCTOSE -} - -enum QualificationFahrerlaubnis { - B - BE - C - CE - D1 - D - D1E - DE - T - L -} - -enum QualificationSchwimmer { - BRONZE - SILBER - GOLD - JUNIORRETTER - RETTUNGSSCHWIMMER_BRONZE - RETTUNGSSCHWIMMER_SILBER - RETTUNGSSCHWIMMER_GOLD -} - -enum QualificationErsteHilfe { - EINWEISER_EHSH - AUSBILDER_EHSH_MODUL_1_2 - AUSBILDER_EHSH_MODUL_3 - MODULE_1 - MODULE_2 - MODULE_3 - AUSBILDUNG - KINDERNOTFAELLE - BILDUNGS_UND_BETREUUNGSEINRICHTUNGEN_KINDER - AUSBILDER -} - -enum QualificationSanitaeter { - SAN_A - SAN_B - FORTBILDUNG - AUSBILDER -} - -enum QualificationFunk { - DLRG_SPRECHFUNKER - BOS_SPRECHFUNKER_ANALOG - BOS_SPRECHFUNKER_DIGITAL - AUSBILDER_SPRECHFUNK - AUSBILDER_BOS_SPRECHFUNK - MULTIPLIKATOR_SPRECHFUNK - MULTIPLIKATOR_BOS_SPRECHFUNK - EINSATZFAEHIGKEIT -} - -enum Konfektionsgroesse { - JUNIOR_98_104 - JUNIOR_110_116 - JUNIOR_122_128 - JUNIOR_134_140 - JUNIOR_146_152 - JUNIOR_158_164 - XS - S - M - L - XL - XXL - XXXL -} - -model Hostname { - id Int @id @default(autoincrement()) - hostname String - veranstaltung Veranstaltung[] -} - -model Notfallkontakt { - id Int @id @default(autoincrement()) - firstname String - lastname String - telefon String - istErziehungsberechtigt Boolean @default(false) - personId Int - person Person @relation(fields: [personId], references: [id], onDelete: Cascade) -} - -model Person { - id Int @id @default(autoincrement()) - firstname String - lastname String - birthday DateTime? @db.Date - gender Gender? - email String - telefon String - anmeldungen Anmeldung[] - gliederungId Int? - gliederung Gliederung? @relation(fields: [gliederungId], references: [id], onDelete: SetNull) - account Account? - essgewohnheit Essgewohnheit? - nahrungsmittelIntoleranzen NahrungsmittelIntoleranz[] - weitereIntoleranzen String[] - qualifikationenFahrerlaubnis QualificationFahrerlaubnis[] - qualifikationenSchwimmer QualificationSchwimmer[] - qualifikationenErsteHilfe QualificationErsteHilfe[] - qualifikationenSanitaeter QualificationSanitaeter[] - qualifikationenFunk QualificationFunk[] - konfektionsgroesse Konfektionsgroesse? - notfallkontaktIds Int[] - notfallkontakte Notfallkontakt[] - addressId Int? - address Address? @relation(fields: [addressId], references: [id], onDelete: Cascade) -} - -model Account { - id Int @id @default(autoincrement()) - email String @unique - dlrgOauthId String? @unique - password String? - role Role - personId Int @unique - person Person @relation(fields: [personId], references: [id], onDelete: Cascade) - activatedAt DateTime? - GliederungToAccount GliederungToAccount[] - activationToken String? @unique - status AccountStatus @default(OFFEN) - passwordResetToken String? @unique - activities Activity[] -} - -model Gliederung { - id Int @id @default(autoincrement()) - name String - edv String @unique - unterveranstaltungen Unterveranstaltung[] - personen Person[] - GliederungToAccount GliederungToAccount[] -} - -enum GliederungAccountRole { - DELEGATIONSLEITER - BETREUER - TEILNEHMER -} - -model GliederungToAccount { - id Int @id @default(autoincrement()) - gliederungId Int - gliederung Gliederung @relation(fields: [gliederungId], references: [id], onDelete: Cascade) - accountId Int - account Account @relation(fields: [accountId], references: [id], onDelete: Cascade) - role GliederungAccountRole - - @@unique([gliederungId, accountId]) -} - -enum AnmeldungStatus { - OFFEN - BESTAETIGT - STORNIERT - ABGELEHNT -} - -model Anmeldung { - id Int @id @default(autoincrement()) - unterveranstaltungId Int - unterveranstaltung Unterveranstaltung @relation(fields: [unterveranstaltungId], references: [id], onDelete: Cascade) - personId Int - person Person @relation(fields: [personId], references: [id], onDelete: Cascade) - status AnmeldungStatus @default(OFFEN) - mahlzeitenIds Int[] - mahlzeiten Mahlzeit[] - uebernachtungsTage DateTime[] @db.Date - tshirtBestellt Boolean @default(false) - comment String? - createdAt DateTime @default(now()) - customFieldValues CustomFieldValue[] -} - -model Address { - id Int @id @default(autoincrement()) - street String - number String - zip String - city String - Ort Ort[] - Person Person[] -} - -model Ort { - id Int @id @default(autoincrement()) - name String - addressId Int? - address Address? @relation(fields: [addressId], references: [id], onDelete: SetNull) - Veranstaltung Veranstaltung[] -} - -model Veranstaltung { - id Int @id @default(autoincrement()) - name String - beginn DateTime @db.Date - ende DateTime @db.Date - meldebeginn DateTime - meldeschluss DateTime - ortId Int? - ort Ort? @relation(fields: [ortId], references: [id], onDelete: SetNull) - maxTeilnehmende Int - teilnahmegebuehr Int - unterveranstaltungen Unterveranstaltung[] - mahlzeiten Mahlzeit[] - beschreibung String? - datenschutz String? - teilnahmeBedingungen String? - teilnahmeBedingungenPublic String? - zielgruppe String? - hostnameId Int? - hostname Hostname? @relation(fields: [hostnameId], references: [id]) - customFields CustomField[] -} - -enum MahlzeitType { - FRUEHSTUECK - MITTAGESSEN - ABENDESSEN -} - -model Mahlzeit { - id Int @id @default(autoincrement()) - type MahlzeitType - date DateTime @db.Date - veranstaltungId Int - veranstaltung Veranstaltung @relation(fields: [veranstaltungId], references: [id], onDelete: Cascade) - anmeldung Anmeldung[] -} - -enum UnterveranstaltungType { - CREW - GLIEDERUNG -} - -model Unterveranstaltung { - id Int @id @default(autoincrement()) - maxTeilnehmende Int - teilnahmegebuehr Int - meldebeginn DateTime - meldeschluss DateTime - veranstaltungId Int - veranstaltung Veranstaltung @relation(fields: [veranstaltungId], references: [id], onDelete: Cascade) - gliederungId Int - gliederung Gliederung @relation(fields: [gliederungId], references: [id], onDelete: Cascade) - Anmeldung Anmeldung[] - beschreibung String? - bedingungen String? - type UnterveranstaltungType @default(GLIEDERUNG) - customFields CustomField[] -} - -enum ActivityType { - CREATE - UPDATE - DELETE - EMAIL - OTHER -} - -model Activity { - id Int @id @default(autoincrement()) - createdAt DateTime @default(now()) - type ActivityType - description String? - subjectType String - subjectId Int? - causerId Int? - causer Account? @relation(fields: [causerId], references: [id], onDelete: SetNull) - metadata Json @default("{}") -} - -enum CustomFieldType { - BASIC_INPUT - BASIC_TEXT_AREA - BASIC_EDITOR - BASIC_SWITCH - BASIC_CHECKBOX - BASIC_INPUT_NUMBER - BASIC_RADIO - BASIC_SELECT - BASIC_DROPDOWN -} - -enum CustomFieldPosition { - PUBLIC_ANMELDUNG - INTERN_ANMELDUNG -} - -model CustomField { - id Int @id @default(autoincrement()) - name String - description String? - type CustomFieldType - required Boolean @default(false) - options String[] - role Role[] - values CustomFieldValue[] - positions CustomFieldPosition[] - veranstaltungId Int? - unterveranstaltungId Int? - veranstaltung Veranstaltung? @relation(fields: [veranstaltungId], references: [id], onDelete: Cascade) - unterveranstaltung Unterveranstaltung? @relation(fields: [unterveranstaltungId], references: [id], onDelete: Cascade) -} - -model CustomFieldValue { - id Int @id @default(autoincrement()) - value Json @default("{}") - fieldId Int - field CustomField @relation(fields: [fieldId], references: [id], onDelete: Cascade) - anmeldungId Int? - anmeldung Anmeldung? @relation(fields: [anmeldungId], references: [id], onDelete: Cascade) -} diff --git a/api/prisma/seeders/veranstaltung.ts b/api/prisma/seeders/veranstaltung.ts deleted file mode 100644 index e5555068..00000000 --- a/api/prisma/seeders/veranstaltung.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { fakerDE as faker } from '@faker-js/faker' -import { PrismaClient } from '@prisma/client' - -import logActivity from '../../src/util/activity' - -import { Seeder } from '.' - -const createVeranstaltung: Seeder = async (prisma: PrismaClient) => { - const veranstaltung = await prisma.veranstaltung.create({ - data: { - name: 'Brahmsee 2024', - beginn: new Date(), - ende: new Date(), - meldebeginn: new Date(), - meldeschluss: new Date(), - maxTeilnehmende: faker.number.int({ min: 7, max: 50 }), - teilnahmegebuehr: faker.number.int({ min: 80, max: 110 }), - beschreibung: faker.lorem.text(), - datenschutz: faker.lorem.text(), - teilnahmeBedingungen: faker.lorem.text(), - teilnahmeBedingungenPublic: faker.lorem.text(), - zielgruppe: faker.lorem.text(), - hostname: { - create: { - hostname: 'localhost:8080', - }, - }, - ort: { - create: { - name: 'Waldheim am Brahmsee', - address: { - create: { - city: 'Langwedel', - zip: '24631', - number: '1', - street: 'Am Waldheim', - }, - }, - }, - }, - unterveranstaltungen: { - create: { - teilnahmegebuehr: 13.37, - maxTeilnehmende: 800, - meldebeginn: new Date(), - meldeschluss: new Date(), - beschreibung: faker.lorem.text(), - type: 'GLIEDERUNG', - gliederung: { - connect: { - edv: '1205013', - }, - }, - }, - }, - }, - }) - - await prisma.customField.create({ - data: { - name: 'Rolle', - description: 'Dieses Feld wurde duch den Seeder erstellt', - type: 'BASIC_SELECT', - required: true, - options: ['Schwimmer:in', 'Teilnehmer:in'], - positions: ['PUBLIC_ANMELDUNG', 'INTERN_ANMELDUNG'], - veranstaltungId: veranstaltung.id, - }, - }) - - await logActivity({ - type: 'CREATE', - subjectType: 'veranstaltung', - subjectId: veranstaltung.id, - description: 'veranstaltung created via db seeder', - }) -} - -export default createVeranstaltung diff --git a/api/src/context.ts b/api/src/context.ts deleted file mode 100644 index 1e02deca..00000000 --- a/api/src/context.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { type inferAsyncReturnType } from '@trpc/server' -import type { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch' -import type { CreateTrpcKoaContextOptions } from 'trpc-koa-adapter' - -import { getEntityIdFromHeader } from './authentication' -import { logger } from './logger' - -function getAuthorizationHeader( - headers: CreateTrpcKoaContextOptions['req']['headers'] | FetchCreateContextFnOptions['req']['headers'] -) { - if ('authorization' in headers && typeof headers['authorization'] === 'string') { - return headers['authorization'] - } else { - return (headers as FetchCreateContextFnOptions['req']['headers']).get('authorization') - } -} - -export async function createContext({ req }: CreateTrpcKoaContextOptions | FetchCreateContextFnOptions) { - try { - const authorization = getAuthorizationHeader(req.headers) - if (authorization === null) throw new Error('No authorization header found.') - - const accountId = await getEntityIdFromHeader(authorization) - return { - accountId: typeof accountId === 'string' ? parseInt(accountId) : accountId, - } - } catch (error) { - logger.error(error) - throw error - } -} -export type Context = inferAsyncReturnType diff --git a/api/src/edge.ts b/api/src/edge.ts deleted file mode 100644 index a3273327..00000000 --- a/api/src/edge.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { fetchRequestHandler } from '@trpc/server/adapters/fetch' - -import { createContext } from './context' - -import { appRouter } from './index' - -addEventListener('fetch', (event) => { - return event.respondWith( - fetchRequestHandler({ - endpoint: '/trpc', - req: event.request, - router: appRouter, - createContext, - }) - ) -}) - -export const config = { - runtime: 'nodejs', -} diff --git a/api/src/enumMappings/Konfektionsgroesse.ts b/api/src/enumMappings/Konfektionsgroesse.ts deleted file mode 100644 index 4622738f..00000000 --- a/api/src/enumMappings/Konfektionsgroesse.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Konfektionsgroesse } from '@prisma/client' - -import { defineEnumMapping } from './defineEnumMapping' - -export const KonfektionsgroesseMapping = defineEnumMapping({ - JUNIOR_98_104: { human: 'Junior 98/104' }, - JUNIOR_110_116: { human: 'Junior 110/116' }, - JUNIOR_122_128: { human: 'Junior 122/128' }, - JUNIOR_134_140: { human: 'Junior 134/140' }, - JUNIOR_146_152: { human: 'Junior 146/152' }, - JUNIOR_158_164: { human: 'Junior 158/164' }, - XS: { human: 'XS' }, - S: { human: 'S' }, - M: { human: 'M' }, - L: { human: 'L' }, - XL: { human: 'XL' }, - XXL: { human: 'XXL' }, - XXXL: { human: 'XXXL' }, -}) diff --git a/api/src/enumMappings/QualificationErsteHilfe.ts b/api/src/enumMappings/QualificationErsteHilfe.ts deleted file mode 100644 index 85a9edc4..00000000 --- a/api/src/enumMappings/QualificationErsteHilfe.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { QualificationErsteHilfe } from '@prisma/client' - -import { defineEnumMapping } from './defineEnumMapping' - -export const QualificationErsteHilfeMapping = defineEnumMapping({ - EINWEISER_EHSH: { human: 'Einweiser EHSH' }, - AUSBILDER_EHSH_MODUL_1_2: { human: 'Ausbilder EHSH Modul 1 und 2' }, - AUSBILDER_EHSH_MODUL_3: { human: 'Ausbilder EHSH Modul 3' }, - AUSBILDER: { human: 'Ausbilder' }, - AUSBILDUNG: { human: 'Ausbildung' }, - BILDUNGS_UND_BETREUUNGSEINRICHTUNGEN_KINDER: { human: 'Bildungs- und Betreuungseinrichtungen für Kinder' }, - KINDERNOTFAELLE: { human: 'Kindernotfälle' }, - MODULE_1: { human: 'Modul 1' }, - MODULE_2: { human: 'Modul 2' }, - MODULE_3: { human: 'Modul 3' }, -}) diff --git a/api/src/enumMappings/QualificationFahrerlaubnis.ts b/api/src/enumMappings/QualificationFahrerlaubnis.ts deleted file mode 100644 index 03e5517d..00000000 --- a/api/src/enumMappings/QualificationFahrerlaubnis.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { QualificationFahrerlaubnis } from '@prisma/client' - -import { defineEnumMapping } from './defineEnumMapping' - -export const QualificationFahrerlaubnisMapping = defineEnumMapping({ - B: { human: 'B' }, - BE: { human: 'BE' }, - C: { human: 'C' }, - CE: { human: 'CE' }, - D: { human: 'D' }, - D1: { human: 'D1' }, - D1E: { human: 'D1E' }, - DE: { human: 'DE' }, - L: { human: 'L' }, - T: { human: 'T' }, -}) diff --git a/api/src/enumMappings/QualificationFunk.ts b/api/src/enumMappings/QualificationFunk.ts deleted file mode 100644 index 2f45c97f..00000000 --- a/api/src/enumMappings/QualificationFunk.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { QualificationFunk } from '@prisma/client' - -import { defineEnumMapping } from './defineEnumMapping' - -export const QualificationFunkMapping = defineEnumMapping({ - AUSBILDER_BOS_SPRECHFUNK: { human: 'Ausbilder BOS Sprechfunk' }, - AUSBILDER_SPRECHFUNK: { human: 'Ausbilder Sprechfunk' }, - BOS_SPRECHFUNKER_ANALOG: { human: 'BOS Sprechfunker analog' }, - BOS_SPRECHFUNKER_DIGITAL: { human: 'BOS Sprechfunker digital' }, - DLRG_SPRECHFUNKER: { human: 'DLRG Sprechfunker' }, - EINSATZFAEHIGKEIT: { human: 'Einsatzfähigkeit' }, - MULTIPLIKATOR_BOS_SPRECHFUNK: { human: 'Multiplikator BOS Sprechfunk' }, - MULTIPLIKATOR_SPRECHFUNK: { human: 'Multiplikator Sprechfunk' }, -}) diff --git a/api/src/enumMappings/QualificationSanitaeter.ts b/api/src/enumMappings/QualificationSanitaeter.ts deleted file mode 100644 index 43ee238e..00000000 --- a/api/src/enumMappings/QualificationSanitaeter.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { QualificationSanitaeter } from '@prisma/client' - -import { defineEnumMapping } from './defineEnumMapping' - -export const QualificationSanitaeterMapping = defineEnumMapping({ - AUSBILDER: { human: 'Ausbilder' }, - FORTBILDUNG: { human: 'Fortbildung' }, - SAN_A: { human: 'San A' }, - SAN_B: { human: 'San B' }, -}) diff --git a/api/src/enumMappings/QualificationSchwimmer.ts b/api/src/enumMappings/QualificationSchwimmer.ts deleted file mode 100644 index b321b6d4..00000000 --- a/api/src/enumMappings/QualificationSchwimmer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { QualificationSchwimmer } from '@prisma/client' - -import { defineEnumMapping } from './defineEnumMapping' - -export const QualificationSchwimmerMapping = defineEnumMapping({ - BRONZE: { human: 'Bronze' }, - SILBER: { human: 'Silber' }, - GOLD: { human: 'Gold' }, - JUNIORRETTER: { human: 'Juniorretter' }, - RETTUNGSSCHWIMMER_BRONZE: { human: 'Rettungsschwimmer Bronze' }, - RETTUNGSSCHWIMMER_SILBER: { human: 'Rettungsschwimmer Silber' }, - RETTUNGSSCHWIMMER_GOLD: { human: 'Rettungsschwimmer Gold' }, -}) diff --git a/api/src/enumMappings/index.ts b/api/src/enumMappings/index.ts deleted file mode 100644 index 5346e5a4..00000000 --- a/api/src/enumMappings/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export * from './AccountStatus' -export * from './Activity' -export * from './AnmeldungStatus' -export * from './role' -export * from './getEnumOptions' -export * from './Gender' -export * from './Essgewohnheit' -export * from './Konfektionsgroesse' -export * from './NahrungsmittelIntoleranz' -export * from './QualificationErsteHilfe' -export * from './QualificationFahrerlaubnis' -export * from './QualificationFunk' -export * from './QualificationSanitaeter' -export * from './QualificationSchwimmer' -export * from './UnterveranstaltungType' -export * from './CustomFields' diff --git a/api/src/exports/index.ts b/api/src/exports/index.ts deleted file mode 100644 index fa80c7d0..00000000 --- a/api/src/exports/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type Router from 'koa-router' - -import { veranstaltungTeilnehmendenliste } from './sheets/teilnehmendenliste' -import { veranstaltungVerpflegung } from './sheets/verpflegung' - -export default function addExports(router: Router) { - router.get('/export/sheet/teilnehmendenliste', veranstaltungTeilnehmendenliste) - router.get('/export/sheet/verpflegung', veranstaltungVerpflegung) -} diff --git a/api/src/index.ts b/api/src/index.ts deleted file mode 100644 index bb1e903b..00000000 --- a/api/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { serviceRouter } from './services' -import { mergeRouters } from './trpc' - -export const appRouter = mergeRouters(serviceRouter) diff --git a/api/src/middleware/index.ts b/api/src/middleware/index.ts deleted file mode 100644 index 7f98723b..00000000 --- a/api/src/middleware/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type Router from 'koa-router' - -import { importAnmeldungen } from './importAnmeldungen' - -export default function addMiddlewares(router: Router) { - router.post('/upload/anmeldungen', async (ctx, next) => { - return await importAnmeldungen(ctx, next) - }) -} diff --git a/api/src/routes/index.ts b/api/src/routes/index.ts deleted file mode 100644 index 7d80c101..00000000 --- a/api/src/routes/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import Router from 'koa-router' -import { renderTrpcPanel } from 'trpc-panel' - -import { appRouter } from '..' -import config from '../config' -import addExports from '../exports' -import addMiddlewares from '../middleware' -import { isDevelopment } from '../util/is-production' - -import connect from './connect' - -const koaRouter = new Router() - -koaRouter.get('/connect/dlrg/callback', connect) - -if (isDevelopment()) { - koaRouter.get('/debug', async (ctx) => { - ctx.headers['content-type'] = 'text/html' - ctx.body = renderTrpcPanel(appRouter, { - url: `http://localhost:${config.server.port}/api/trpc`, - transformer: 'superjson', - }) - }) -} - -addExports(koaRouter) -addMiddlewares(koaRouter) - -export default koaRouter diff --git a/api/src/scripts/initMeilisearch.ts b/api/src/scripts/initMeilisearch.ts deleted file mode 100644 index 0018d155..00000000 --- a/api/src/scripts/initMeilisearch.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { syncAllPersonsToMeili } from '../meilisearch/person' - -initMeilisearch() -async function initMeilisearch() { - syncAllPersonsToMeili() -} diff --git a/api/src/services/account/accountActivate.ts b/api/src/services/account/accountActivate.ts deleted file mode 100644 index 721e7da6..00000000 --- a/api/src/services/account/accountActivate.ts +++ /dev/null @@ -1,42 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import logActivity from '../../util/activity' -import { sendMail } from '../../util/mail' - -export const accountActivateProcedure = defineProcedure({ - key: 'activate', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: z.strictObject({ - accountId: z.number().int(), - }), - async handler(options) { - const account = await prisma.account.update({ - where: { - id: options.input.accountId, - }, - data: { - activatedAt: new Date(), - }, - }) - - await logActivity({ - type: 'UPDATE', - description: `account was activated`, - subjectType: 'account', - subjectId: options.input.accountId, - causerId: options.ctx.accountId, - }) - - await sendMail({ - to: account.email, - subject: 'brahmsee.digital Account aktiviert', - categories: ['account', 'activate'], - html: 'Dein brahmsee.digital Account wurde aktiviert.', - }) - - return 'activated' - }, -}) diff --git a/api/src/services/account/accountVerwaltungCreate.ts b/api/src/services/account/accountVerwaltungCreate.ts deleted file mode 100644 index 4975998a..00000000 --- a/api/src/services/account/accountVerwaltungCreate.ts +++ /dev/null @@ -1,32 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -import { sendMailConfirmEmailRequest } from './helpers/sendMailConfirmEmailRequest' -import { accountSchema, getAccountCreateData } from './schema/account.schema' - -export const accountVerwaltungCreateProcedure = defineProcedure({ - key: 'verwaltungCreate', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: z.strictObject({ - data: accountSchema, - }), - async handler(options) { - const accountData = await getAccountCreateData(options.input.data) - const res = prisma.account.create({ - data: accountData, - select: { - id: true, - }, - }) - - await sendMailConfirmEmailRequest({ - email: accountData.email, - activationToken: accountData.activationToken, - }) - - return res - }, -}) diff --git a/api/src/services/account/accountVerwaltungList.ts b/api/src/services/account/accountVerwaltungList.ts deleted file mode 100644 index 505172b3..00000000 --- a/api/src/services/account/accountVerwaltungList.ts +++ /dev/null @@ -1,51 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { defineQuery } from '../../types/defineQuery' - -export const accountVerwaltungListProcedure = defineProcedure({ - key: 'verwaltungList', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: defineQuery({ - filter: z.strictObject({ - email: z.string().optional(), - }), - }), - async handler(options) { - const { skip, take } = options.input.pagination - const list = await prisma.account.findMany({ - skip, - take, - where: { - email: options.input.filter.email, - }, - select: { - id: true, - email: true, - activatedAt: true, - status: true, - role: true, - personId: true, - person: { - select: { - firstname: true, - lastname: true, - }, - }, - GliederungToAccount: { - select: { - role: true, - gliederung: { - select: { - name: true, - }, - }, - }, - }, - }, - }) - return list - }, -}) diff --git a/api/src/services/account/helpers/sendMailConfirmEmailRequest.ts b/api/src/services/account/helpers/sendMailConfirmEmailRequest.ts deleted file mode 100644 index 3efb6cb5..00000000 --- a/api/src/services/account/helpers/sendMailConfirmEmailRequest.ts +++ /dev/null @@ -1,12 +0,0 @@ -import config from '../../../config' -import { sendMail } from '../../../util/mail' - -export async function sendMailConfirmEmailRequest(data: { email: string; activationToken: string }) { - const activationUrl = `${config.clientUrl}/registrierung/confirm/${data.activationToken}` - await sendMail({ - to: data.email, - subject: 'brahmsee.digital Bestätige deine E-Mail Adresse', - categories: ['account', 'confirm'], - html: `Bitte bestätige deine E-Mail Adresse, indem du auf folgenden Link klickst: ${activationUrl}`, - }) -} diff --git a/api/src/services/activity/activity.routes.ts b/api/src/services/activity/activity.routes.ts deleted file mode 100644 index 2340effa..00000000 --- a/api/src/services/activity/activity.routes.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable prettier/prettier */ // Prettier ignored is because this file is generated -import { mergeRouters } from '../../trpc' - -import { activityVerwaltungListProcedure } from './activityVerwaltungList' -// Import Routes here - do not delete this line - -export const activityRouter = mergeRouters( - activityVerwaltungListProcedure.router, - // Add Routes here - do not delete this line -) diff --git a/api/src/services/activity/activityVerwaltungList.ts b/api/src/services/activity/activityVerwaltungList.ts deleted file mode 100644 index 5261314b..00000000 --- a/api/src/services/activity/activityVerwaltungList.ts +++ /dev/null @@ -1,41 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { defineQuery } from '../../types/defineQuery' - -export const activityVerwaltungListProcedure = defineProcedure({ - key: 'verwaltungList', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: defineQuery({ - filter: z.strictObject({ - veranstaltungId: z.string().optional(), - }), - }), - async handler({ input }) { - const { skip, take } = input.pagination - - const activities = await prisma.activity.findMany({ - skip, - take, - orderBy: { - createdAt: 'desc', - }, - include: { - causer: { - select: { - person: { - select: { - firstname: true, - lastname: true, - }, - }, - }, - }, - }, - }) - - return activities - }, -}) diff --git a/api/src/services/anmeldung/anmeldungGliederungGet.ts b/api/src/services/anmeldung/anmeldungGliederungGet.ts deleted file mode 100644 index 7796b103..00000000 --- a/api/src/services/anmeldung/anmeldungGliederungGet.ts +++ /dev/null @@ -1,82 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const anmeldungGliederungGetProcedure = defineProcedure({ - key: 'gliederungGet', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'] }, - inputSchema: z.strictObject({ - anmeldungId: z.number().optional(), - personId: z.number().optional(), - }), - async handler(options) { - const anmeldungen = await prisma.anmeldung.findMany({ - where: { - OR: [ - { - personId: options.input.personId, - }, - { - id: options.input.anmeldungId, - }, - ], - }, - select: { - id: true, - status: true, - mahlzeiten: true, - uebernachtungsTage: true, - tshirtBestellt: true, - createdAt: true, - comment: true, - customFieldValues: { - select: { - id: true, - value: true, - field: true, - }, - }, - person: { - select: { - id: true, - firstname: true, - lastname: true, - birthday: true, - gender: true, - email: true, - telefon: true, - gliederung: { - select: { - id: true, - name: true, - edv: true, - }, - }, - essgewohnheit: true, - nahrungsmittelIntoleranzen: true, - weitereIntoleranzen: true, - konfektionsgroesse: true, - notfallkontakte: true, - address: true, - }, - }, - unterveranstaltung: { - select: { - id: true, - veranstaltung: { - select: { - id: true, - name: true, - meldeschluss: true, - }, - }, - }, - }, - }, - }) - - return anmeldungen - }, -}) diff --git a/api/src/services/anmeldung/anmeldungVerwaltungCreate.ts b/api/src/services/anmeldung/anmeldungVerwaltungCreate.ts deleted file mode 100644 index 06f7936c..00000000 --- a/api/src/services/anmeldung/anmeldungVerwaltungCreate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineProcedure } from '../../types/defineProcedure' - -import { handle, inputSchema } from './anmeldungPublicCreate' - -export const anmeldungVerwaltungCreateProcedure = defineProcedure({ - key: 'verwaltungCreate', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'] }, - inputSchema: inputSchema, - async handler(options) { - await handle(options.input, false) - }, -}) diff --git a/api/src/services/anmeldung/anmeldungVerwaltungGet.ts b/api/src/services/anmeldung/anmeldungVerwaltungGet.ts deleted file mode 100644 index 885a5275..00000000 --- a/api/src/services/anmeldung/anmeldungVerwaltungGet.ts +++ /dev/null @@ -1,81 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const anmeldungVerwaltungGetProcedure = defineProcedure({ - key: 'verwaltungGet', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: z.strictObject({ - anmeldungId: z.number().optional(), - personId: z.number().optional(), - }), - async handler(options) { - const anmeldungen = await prisma.anmeldung.findMany({ - where: { - OR: [ - { - personId: options.input.personId, - }, - { - id: options.input.anmeldungId, - }, - ], - }, - select: { - id: true, - status: true, - mahlzeiten: true, - uebernachtungsTage: true, - tshirtBestellt: true, - createdAt: true, - comment: true, - customFieldValues: { - select: { - id: true, - value: true, - field: true, - }, - }, - person: { - select: { - id: true, - firstname: true, - lastname: true, - birthday: true, - gender: true, - email: true, - telefon: true, - gliederung: { - select: { - id: true, - name: true, - edv: true, - }, - }, - essgewohnheit: true, - nahrungsmittelIntoleranzen: true, - weitereIntoleranzen: true, - konfektionsgroesse: true, - notfallkontakte: true, - address: true, - }, - }, - unterveranstaltung: { - select: { - veranstaltung: { - select: { - id: true, - name: true, - meldeschluss: true, - }, - }, - }, - }, - }, - }) - - return anmeldungen - }, -}) diff --git a/api/src/services/anmeldung/anmeldungVerwaltungList.ts b/api/src/services/anmeldung/anmeldungVerwaltungList.ts deleted file mode 100644 index ff8c790a..00000000 --- a/api/src/services/anmeldung/anmeldungVerwaltungList.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { AnmeldungStatus } from '@prisma/client' -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { ZPaginationSchema } from '../../types/defineQuery' - -const filter = z.strictObject({ - unterveranstaltungId: z.number().optional(), - veranstaltungId: z.number().optional(), -}) - -const where = (filter: { unterveranstaltungId?: number; veranstaltungId?: number }) => { - return { - OR: [ - { - unterveranstaltungId: filter.unterveranstaltungId, - }, - { - unterveranstaltung: { - veranstaltungId: filter.veranstaltungId, - }, - }, - ], - } -} - -export const anmeldungVerwaltungListProcedure = defineProcedure({ - key: 'verwaltungList', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: z.strictObject({ - pagination: ZPaginationSchema, - filter: filter, - orderBy: z - .array( - z.union([ - z.object({ - status: z.union([z.literal('asc'), z.literal('desc')]), - }), - z.object({ - createdAt: z.union([z.literal('asc'), z.literal('desc')]), - }), - ]) - ) - .optional(), - }), - async handler(options) { - const { skip, take } = options.input.pagination - const anmeldungen = await prisma.anmeldung.findMany({ - skip, - take, - where: where(options.input.filter), - select: { - id: true, - person: { - select: { - id: true, - firstname: true, - lastname: true, - birthday: true, - konfektionsgroesse: true, - gliederung: { - select: { - id: true, - name: true, - }, - }, - }, - }, - status: true, - tshirtBestellt: true, - unterveranstaltung: { - select: { - veranstaltung: { - select: { - meldeschluss: true, - }, - }, - }, - }, - }, - orderBy: { - createdAt: 'desc', - }, - }) - - return anmeldungen - }, -}) - -export const anmeldungVerwaltungCountProcedure = defineProcedure({ - key: 'verwaltungCount', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: z.strictObject({ - filter: filter, - }), - async handler(options) { - const countEntries = await Promise.all( - Object.values(AnmeldungStatus).map(async (status) => { - return [ - status, - await prisma.anmeldung.count({ - where: { - ...where(options.input.filter), - status: status, - }, - }), - ] - }) - ) - const total = countEntries.reduce((acc, [, count]) => acc + Number(count), 0) - return { total, ...Object.fromEntries(countEntries) } - }, -}) diff --git a/api/src/services/authentication/authenticationLogin.ts b/api/src/services/authentication/authenticationLogin.ts deleted file mode 100644 index 3f5019b3..00000000 --- a/api/src/services/authentication/authenticationLogin.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { z } from 'zod' - -import { authenticationLogin } from '../../authentication' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const authenticationLoginProcedure = defineProcedure({ - key: 'login', - method: 'mutation', - protection: { type: 'public' }, - inputSchema: z.strictObject({ - email: z.string(), - password: z.string(), - }), - async handler(options) { - const authResult = await authenticationLogin(options.input) - const account = await prisma.account.findUniqueOrThrow({ - where: { - id: authResult.user.id, - }, - select: { - role: true, - person: { - select: { - id: true, - firstname: true, - lastname: true, - gliederungId: true, - }, - }, - status: true, - }, - }) - const person = account.person - return { - ...authResult, - user: { - ...authResult.user, - person, - role: account.role, - }, - } - }, -}) diff --git a/api/src/services/customFields/customFieldsList.ts b/api/src/services/customFields/customFieldsList.ts deleted file mode 100644 index e4415133..00000000 --- a/api/src/services/customFields/customFieldsList.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type { CustomField } from '@prisma/client' -import { z } from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const customFieldsList = defineProcedure({ - key: 'list', - method: 'query', - protection: { type: 'public' }, - inputSchema: z.strictObject({ - entity: z.enum(['veranstaltung', 'unterveranstaltung']), - entityId: z.number(), - }), - async handler({ input }) { - let fields: CustomField[] = [] - - if (input.entity === 'veranstaltung') { - const veranstaltung = await prisma.veranstaltung.findUniqueOrThrow({ - where: { - id: input.entityId, - }, - include: { - customFields: true, - }, - }) - - fields = veranstaltung.customFields - } else if (input.entity === 'unterveranstaltung') { - const ausschreibung = await prisma.unterveranstaltung.findUniqueOrThrow({ - where: { - id: input.entityId, - }, - include: { - customFields: true, - veranstaltung: { - include: { - customFields: true, - }, - }, - }, - }) - - fields = fields.concat(...ausschreibung.veranstaltung.customFields, ...ausschreibung.customFields) - } - - return fields - }, -}) diff --git a/api/src/services/customFields/customFieldsVeranstaltungCreate.ts b/api/src/services/customFields/customFieldsVeranstaltungCreate.ts deleted file mode 100644 index 857505c7..00000000 --- a/api/src/services/customFields/customFieldsVeranstaltungCreate.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { CustomFieldPosition, CustomFieldType } from '@prisma/client' -import { z } from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const customFieldsVeranstaltungCreate = defineProcedure({ - key: 'verwaltungCreate', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: z.strictObject({ - veranstaltungId: z.number(), - data: z.strictObject({ - name: z.string().min(1), - description: z.string().nullable(), - type: z.nativeEnum(CustomFieldType), - required: z.boolean(), - options: z.array(z.string()), - positions: z.nativeEnum(CustomFieldPosition).array(), - }), - }), - async handler({ input }) { - return await prisma.customField.create({ - data: { - ...input.data, - veranstaltungId: input.veranstaltungId, - }, - }) - }, -}) diff --git a/api/src/services/customFields/customFieldsVeranstaltungUpdate.ts b/api/src/services/customFields/customFieldsVeranstaltungUpdate.ts deleted file mode 100644 index 331cb3ff..00000000 --- a/api/src/services/customFields/customFieldsVeranstaltungUpdate.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { CustomFieldPosition, CustomFieldType } from '@prisma/client' -import { z } from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const customFieldsVeranstaltungUpdate = defineProcedure({ - key: 'verwaltungUpdate', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: z.strictObject({ - fieldId: z.number(), - data: z.strictObject({ - name: z.string().min(1), - description: z.string().nullable(), - type: z.nativeEnum(CustomFieldType), - required: z.boolean(), - options: z.array(z.string()), - positions: z.nativeEnum(CustomFieldPosition).array(), - }), - }), - async handler({ input }) { - return await prisma.customField.update({ - where: { - id: input.fieldId, - }, - data: { - ...input.data, - }, - }) - }, -}) diff --git a/api/src/services/gliederung/gliederungVerwaltungList.ts b/api/src/services/gliederung/gliederungVerwaltungList.ts deleted file mode 100644 index cdce3384..00000000 --- a/api/src/services/gliederung/gliederungVerwaltungList.ts +++ /dev/null @@ -1,36 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { defineQuery } from '../../types/defineQuery' - -export const gliederungVerwaltungListProcedure = defineProcedure({ - key: 'verwaltungList', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: defineQuery({ - filter: z.strictObject({ - name: z.string().optional(), - }), - }), - async handler(options) { - const { skip, take } = options.input.pagination - const list = await prisma.gliederung.findMany({ - skip, - take, - where: { - name: { - contains: options.input.filter.name, - mode: 'insensitive', - }, - }, - select: { - id: true, - name: true, - edv: true, - }, - }) - - return list - }, -}) diff --git a/api/src/services/ort/ortVerwaltungList.ts b/api/src/services/ort/ortVerwaltungList.ts deleted file mode 100644 index ba68cec1..00000000 --- a/api/src/services/ort/ortVerwaltungList.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { Prisma } from '@prisma/client' -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { defineQuery } from '../../types/defineQuery' - -const inputSchema = defineQuery({ - filter: z.strictObject({ - name: z.string().optional(), - }), -}) - -function getFilterWhere(input: z.infer['filter']): Prisma.OrtWhereInput { - return { - name: input.name, - } -} - -export const ortVerwaltungListProcedure = defineProcedure({ - key: 'verwaltungList', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema, - async handler(options) { - const { skip, take } = options.input.pagination - - return await prisma.ort.findMany({ - skip, - take, - where: getFilterWhere(options.input.filter), - select: { - id: true, - name: true, - address: true, - }, - }) - }, -}) - -export const ortVerwaltungListCountProcedure = defineProcedure({ - key: 'verwaltungListCount', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema, - async handler(options) { - return await prisma.ort.count({ - where: { - name: options.input.filter.name, - }, - }) - }, -}) diff --git a/api/src/services/person/person.router.ts b/api/src/services/person/person.router.ts deleted file mode 100644 index 93568c9a..00000000 --- a/api/src/services/person/person.router.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* eslint-disable prettier/prettier */ // Prettier ignored is because this file is generated -import { mergeRouters } from '../../trpc' - -import { personAuthenticatedGetProcedure } from './personAuthenticatedGet' -import { personGliederungGetProcedure } from './personGliederungGet' -import { personGliederungListProcedure } from './personGliederungList' -import { personGliederungPatchProcedure } from './personGliederungPatch' -import { personTshirtGliederungPatchProcedure } from './personTshirtGliederungPatch' -import { personTshirtVerwaltungPatchProcedure } from './personTshirtVerwaltungPatch' -import { personVerwaltungCreateProcedure } from './personVerwaltungCreate' -import { personVerwaltungGetProcedure } from './personVerwaltungGet' -import { personVerwaltungListProcedure } from './personVerwaltungList' -import { personVerwaltungPatchProcedure } from './personVerwaltungPatch' -import { personVerwaltungRemoveProcedure } from './personVerwaltungRemove' -// Import Routes here - do not delete this line - -export const personRouter = mergeRouters( - personAuthenticatedGetProcedure.router, - personGliederungListProcedure.router, - personVerwaltungGetProcedure.router, - personVerwaltungCreateProcedure.router, - personVerwaltungListProcedure.router, - personVerwaltungPatchProcedure.router, - personVerwaltungRemoveProcedure.router, - personGliederungGetProcedure.router, - personGliederungPatchProcedure.router, - personTshirtVerwaltungPatchProcedure.router, - personTshirtGliederungPatchProcedure.router, - // Add Routes here - do not delete this line -) diff --git a/api/src/services/person/personAuthenticatedGet.ts b/api/src/services/person/personAuthenticatedGet.ts deleted file mode 100644 index 31b0e6d4..00000000 --- a/api/src/services/person/personAuthenticatedGet.ts +++ /dev/null @@ -1,30 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const personAuthenticatedGetProcedure = defineProcedure({ - key: 'authenticatedGet', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'] }, - inputSchema: z.undefined(), - async handler(options) { - return prisma.account.findUniqueOrThrow({ - where: { - id: options.ctx.accountId, - }, - select: { - id: true, - role: true, - person: { - select: { - id: true, - firstname: true, - lastname: true, - gliederungId: true, - }, - }, - }, - }) - }, -}) diff --git a/api/src/services/person/personGliederungGet.ts b/api/src/services/person/personGliederungGet.ts deleted file mode 100644 index 7de9f9ab..00000000 --- a/api/src/services/person/personGliederungGet.ts +++ /dev/null @@ -1,50 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const personGliederungGetProcedure = defineProcedure({ - key: 'gliederungGet', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['GLIEDERUNG_ADMIN'] }, - inputSchema: z.strictObject({ - id: z.number().int(), - }), - async handler(options) { - return await prisma.person.findUniqueOrThrow({ - where: { - id: options.input.id, - }, - select: { - id: true, - firstname: true, - lastname: true, - birthday: true, - gender: true, - email: true, - telefon: true, - address: { - select: { - zip: true, - city: true, - street: true, - number: true, - }, - }, - nahrungsmittelIntoleranzen: true, - weitereIntoleranzen: true, - konfektionsgroesse: true, - essgewohnheit: true, - notfallkontakte: { - select: { - firstname: true, - lastname: true, - telefon: true, - istErziehungsberechtigt: true, - }, - }, - gliederung: true, - }, - }) - }, -}) diff --git a/api/src/services/person/personGliederungList.ts b/api/src/services/person/personGliederungList.ts deleted file mode 100644 index 3b00da23..00000000 --- a/api/src/services/person/personGliederungList.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { TRPCError } from '@trpc/server' -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { defineQuery } from '../../types/defineQuery' - -export const personGliederungListProcedure = defineProcedure({ - key: 'gliederungList', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['GLIEDERUNG_ADMIN'] }, - inputSchema: defineQuery({ - filter: z.strictObject({ - firstname: z.string().optional(), - lastname: z.string().optional(), - }), - }), - async handler(options) { - if (typeof options.ctx.account.person.gliederungId !== 'number') { - throw new TRPCError({ - code: 'UNAUTHORIZED', - message: 'Du bist noch keiner Gliederung zugeordnet!', - }) - } - - const { skip, take } = options.input.pagination - - const list = await prisma.person.findMany({ - skip, - take, - where: { - firstname: options.input.filter.firstname, - lastname: options.input.filter.lastname, - gliederungId: options.ctx.account.person.gliederungId, - }, - include: { - gliederung: { - select: { - name: true, - }, - }, - account: { - select: { - id: true, - }, - }, - }, - }) - return list - }, -}) diff --git a/api/src/services/person/personTshirtGliederungPatch.ts b/api/src/services/person/personTshirtGliederungPatch.ts deleted file mode 100644 index f60ce7f8..00000000 --- a/api/src/services/person/personTshirtGliederungPatch.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Konfektionsgroesse } from '@prisma/client' -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const personTshirtGliederungPatchProcedure = defineProcedure({ - key: 'tshirtGliederungPatch', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['GLIEDERUNG_ADMIN'] }, - inputSchema: z.strictObject({ - anmeldungId: z.number().int(), - bestellen: z.boolean(), - groesse: z.nativeEnum(Konfektionsgroesse), - }), - async handler({ input }) { - // TODO: Check if allowed - await prisma.anmeldung.update({ - where: { - id: input.anmeldungId, - }, - data: { - tshirtBestellt: input.bestellen, - person: { - update: { - konfektionsgroesse: input.groesse, - }, - }, - }, - }) - }, -}) diff --git a/api/src/services/person/personTshirtVerwaltungPatch.ts b/api/src/services/person/personTshirtVerwaltungPatch.ts deleted file mode 100644 index 3c82737f..00000000 --- a/api/src/services/person/personTshirtVerwaltungPatch.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Konfektionsgroesse } from '@prisma/client' -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const personTshirtVerwaltungPatchProcedure = defineProcedure({ - key: 'tshirtVerwaltungPatch', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: z.strictObject({ - anmeldungId: z.number().int(), - bestellen: z.boolean(), - groesse: z.nativeEnum(Konfektionsgroesse), - }), - async handler({ input }) { - await prisma.anmeldung.update({ - where: { - id: input.anmeldungId, - }, - data: { - tshirtBestellt: input.bestellen, - person: { - update: { - konfektionsgroesse: input.groesse, - }, - }, - }, - }) - }, -}) diff --git a/api/src/services/person/personVerwaltungList.ts b/api/src/services/person/personVerwaltungList.ts deleted file mode 100644 index fda756c1..00000000 --- a/api/src/services/person/personVerwaltungList.ts +++ /dev/null @@ -1,55 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { defineQuery } from '../../types/defineQuery' - -export const personVerwaltungListProcedure = defineProcedure({ - key: 'verwaltungList', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: defineQuery({ - filter: z.strictObject({ - firstname: z.string().optional(), - lastname: z.string().optional(), - }), - }), - async handler(options) { - const { skip, take } = options.input.pagination - const list = await prisma.person.findMany({ - skip, - take, - where: { - firstname: options.input.filter.firstname, - lastname: options.input.filter.lastname, - }, - include: { - gliederung: { - select: { - id: true, - name: true, - }, - }, - account: { - select: { - id: true, - activatedAt: true, - role: true, - status: true, - GliederungToAccount: { - select: { - role: true, - gliederung: { - select: { - name: true, - }, - }, - }, - }, - }, - }, - }, - }) - return list - }, -}) diff --git a/api/src/services/person/personVerwaltungRemove.ts b/api/src/services/person/personVerwaltungRemove.ts deleted file mode 100644 index 9917cc77..00000000 --- a/api/src/services/person/personVerwaltungRemove.ts +++ /dev/null @@ -1,20 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const personVerwaltungRemoveProcedure = defineProcedure({ - key: 'verwaltungRemove', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: z.strictObject({ - id: z.number().int(), - }), - async handler(options) { - return prisma.person.delete({ - where: { - id: options.input.id, - }, - }) - }, -}) diff --git a/api/src/services/search/search.router.ts b/api/src/services/search/search.router.ts deleted file mode 100644 index 787672e6..00000000 --- a/api/src/services/search/search.router.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable prettier/prettier */ // Prettier ignored is because this file is generated -import { mergeRouters } from '../../trpc' - -import { searchProcedure } from './search' - -// Import Routes here - do not delete this line - -export const searchRouter = mergeRouters( - searchProcedure.router, - // Add Routes here - do not delete this line -) diff --git a/api/src/services/system/systemHostnamesGet.ts b/api/src/services/system/systemHostnamesGet.ts deleted file mode 100644 index 73ec1861..00000000 --- a/api/src/services/system/systemHostnamesGet.ts +++ /dev/null @@ -1,18 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const systemHostnamesGetProcedure = defineProcedure({ - key: 'hostnamesGet', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: z.strictObject({}), - async handler() { - return await prisma.hostname.findMany({ - orderBy: { - hostname: 'asc', - }, - }) - }, -}) diff --git a/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts b/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts deleted file mode 100644 index b816edec..00000000 --- a/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { TRPCError } from '@trpc/server' -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin' - -export const unterveranstaltungGliederungCreateProcedure = defineProcedure({ - key: 'gliederungCreate', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['GLIEDERUNG_ADMIN', 'ADMIN'] }, - inputSchema: z.strictObject({ - data: z.strictObject({ - veranstaltungId: z.number().int(), - maxTeilnehmende: z.number().int(), - teilnahmegebuehr: z.number({ description: 'In Cent' }).int(), - meldebeginn: z.date(), - meldeschluss: z.date(), - beschreibung: z.string().optional(), - bedingungen: z.string().optional(), - }), - }), - async handler(options) { - // check logged in user is admin of gliederung - const gliederung = await getGliederungRequireAdmin(options.ctx.accountId) - const veranstaltung = await prisma.veranstaltung.findUniqueOrThrow({ - where: { - id: options.input.data.veranstaltungId, - }, - select: { - meldebeginn: true, - meldeschluss: true, - }, - }) - // check meldebegin is after parent meldebeginn - if (new Date(options.input.data.meldebeginn) < veranstaltung.meldebeginn) { - throw new TRPCError({ - message: 'Der Meldebeginn darf nicht vor dem Meldebeginn der übergeordneten Veranstaltung liegen', - code: 'BAD_REQUEST', - }) - } - // check meldeschluss is before parent meldeschluss - if (new Date(options.input.data.meldeschluss) > veranstaltung.meldeschluss) { - throw new TRPCError({ - message: 'Der Meldeschluss darf nicht nach dem Meldeschluss der übergeordneten Veranstaltung liegen', - code: 'BAD_REQUEST', - }) - } - return prisma.unterveranstaltung.create({ - data: { - ...options.input.data, - type: 'GLIEDERUNG', - gliederungId: gliederung.id, - }, - select: { - id: true, - }, - }) - }, -}) diff --git a/api/src/services/unterveranstaltung/unterveranstaltungGliederungGet.ts b/api/src/services/unterveranstaltung/unterveranstaltungGliederungGet.ts deleted file mode 100644 index 75b9297f..00000000 --- a/api/src/services/unterveranstaltung/unterveranstaltungGliederungGet.ts +++ /dev/null @@ -1,49 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const unterveranstaltungGliederungGetProcedure = defineProcedure({ - key: 'gliederungGet', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['GLIEDERUNG_ADMIN', 'ADMIN'] }, - inputSchema: z.strictObject({ - id: z.number().int(), - }), - async handler(options) { - return prisma.unterveranstaltung.findUniqueOrThrow({ - where: { - id: options.input.id, - }, - select: { - id: true, - beschreibung: true, - bedingungen: true, - maxTeilnehmende: true, - teilnahmegebuehr: true, - meldebeginn: true, - meldeschluss: true, - veranstaltung: { - select: { - id: true, - name: true, - beginn: true, - ende: true, - ort: true, - meldebeginn: true, - meldeschluss: true, - maxTeilnehmende: true, - teilnahmegebuehr: true, - beschreibung: true, - zielgruppe: true, - teilnahmeBedingungen: true, - teilnahmeBedingungenPublic: true, - datenschutz: true, - hostname: true, - }, - }, - gliederung: true, - }, - }) - }, -}) diff --git a/api/src/services/unterveranstaltung/unterveranstaltungGliederungList.ts b/api/src/services/unterveranstaltung/unterveranstaltungGliederungList.ts deleted file mode 100644 index 4b98acec..00000000 --- a/api/src/services/unterveranstaltung/unterveranstaltungGliederungList.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { Prisma } from '@prisma/client' -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { defineQuery } from '../../types/defineQuery' -import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin' - -export const unterveranstaltungGliederungListProcedure = defineProcedure({ - key: 'gliederungList', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['GLIEDERUNG_ADMIN', 'ADMIN'] }, - inputSchema: defineQuery({ - filter: z.strictObject({ - veranstaltungId: z.number().optional(), - }), - }), - async handler(options) { - const { skip, take } = options.input.pagination - const where: Prisma.UnterveranstaltungWhereInput = {} - - if (options.input.filter.veranstaltungId !== undefined) { - where.veranstaltungId = options.input.filter.veranstaltungId - } - - const gliederung = await getGliederungRequireAdmin(options.ctx.accountId) - where.gliederungId = gliederung.id - - const veranstaltungen = await prisma.unterveranstaltung.findMany({ - skip, - take, - where: where, - select: { - id: true, - gliederung: { - select: { - id: true, - name: true, - }, - }, - veranstaltung: { - select: { - id: true, - name: true, - }, - }, - _count: { - select: { Anmeldung: true }, - }, - meldebeginn: true, - meldeschluss: true, - maxTeilnehmende: true, - teilnahmegebuehr: true, - }, - }) - - return veranstaltungen - }, -}) diff --git a/api/src/services/unterveranstaltung/unterveranstaltungGliederungPatch.ts b/api/src/services/unterveranstaltung/unterveranstaltungGliederungPatch.ts deleted file mode 100644 index 9bdf823a..00000000 --- a/api/src/services/unterveranstaltung/unterveranstaltungGliederungPatch.ts +++ /dev/null @@ -1,35 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin' - -export const unterveranstaltungGliederungPatchProcedure = defineProcedure({ - key: 'gliederungPatch', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['GLIEDERUNG_ADMIN', 'ADMIN'] }, - inputSchema: z.strictObject({ - id: z.number().int(), - data: z.strictObject({ - maxTeilnehmende: z.number().int().optional(), - teilnahmegebuehr: z.number({ description: 'In Cent' }).int().optional(), - meldebeginn: z.date().optional(), - meldeschluss: z.date().optional(), - beschreibung: z.string().optional(), - bedingungen: z.string().optional(), - }), - }), - async handler(options) { - const gliederung = await getGliederungRequireAdmin(options.ctx.accountId) - return prisma.unterveranstaltung.update({ - where: { - id: options.input.id, - gliederungId: gliederung.id, - }, - data: options.input.data, - select: { - id: true, - }, - }) - }, -}) diff --git a/api/src/services/unterveranstaltung/unterveranstaltungPublicGet.ts b/api/src/services/unterveranstaltung/unterveranstaltungPublicGet.ts deleted file mode 100644 index 6e5a2b88..00000000 --- a/api/src/services/unterveranstaltung/unterveranstaltungPublicGet.ts +++ /dev/null @@ -1,55 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const unterveranstaltungPublicGetProcedure = defineProcedure({ - key: 'publicGet', - method: 'query', - protection: { type: 'public' }, - inputSchema: z.strictObject({ - id: z.number(), - }), - async handler(options) { - const unterveranstaltung = await prisma.unterveranstaltung.findUniqueOrThrow({ - where: { - id: options.input.id, - }, - select: { - id: true, - veranstaltungId: true, - meldebeginn: true, - meldeschluss: true, - maxTeilnehmende: true, - teilnahmegebuehr: true, - type: true, - veranstaltung: { - select: { - name: true, - beginn: true, - ende: true, - ort: { - select: { - name: true, - }, - }, - datenschutz: true, - teilnahmeBedingungen: true, - teilnahmeBedingungenPublic: true, - zielgruppe: true, - hostname: true, - }, - }, - gliederung: { - select: { - id: true, - name: true, - }, - }, - beschreibung: true, - bedingungen: true, - }, - }) - return unterveranstaltung - }, -}) diff --git a/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts b/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts deleted file mode 100644 index 536bb597..00000000 --- a/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { UnterveranstaltungType } from '@prisma/client' -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const unterveranstaltungVerwaltungCreateProcedure = defineProcedure({ - key: 'verwaltungCreate', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: z.strictObject({ - data: z.strictObject({ - veranstaltungId: z.number().int(), - maxTeilnehmende: z.number().int(), - teilnahmegebuehr: z.number({ description: 'In Cent' }).int(), - meldebeginn: z.date(), - meldeschluss: z.date(), - gliederungId: z.number().int(), - type: z.nativeEnum(UnterveranstaltungType), - beschreibung: z.string().optional(), - bedingungen: z.string().optional(), - }), - }), - async handler(options) { - return prisma.unterveranstaltung.create({ - data: options.input.data, - select: { - id: true, - }, - }) - }, -}) diff --git a/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungGet.ts b/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungGet.ts deleted file mode 100644 index 9601b1df..00000000 --- a/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungGet.ts +++ /dev/null @@ -1,54 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const unterveranstaltungVerwaltungGetProcedure = defineProcedure({ - key: 'verwaltungGet', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: z.strictObject({ - id: z.number().int(), - }), - async handler(options) { - return prisma.unterveranstaltung.findUniqueOrThrow({ - where: { - id: options.input.id, - }, - select: { - id: true, - beschreibung: true, - bedingungen: true, - maxTeilnehmende: true, - teilnahmegebuehr: true, - meldebeginn: true, - meldeschluss: true, - gliederung: { - select: { - id: true, - name: true, - }, - }, - veranstaltung: { - select: { - id: true, - name: true, - beginn: true, - ende: true, - ort: true, - meldebeginn: true, - meldeschluss: true, - maxTeilnehmende: true, - teilnahmegebuehr: true, - beschreibung: true, - zielgruppe: true, - teilnahmeBedingungen: true, - teilnahmeBedingungenPublic: true, - datenschutz: true, - hostname: true, - }, - }, - }, - }) - }, -}) diff --git a/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungList.ts b/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungList.ts deleted file mode 100644 index ea393a06..00000000 --- a/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungList.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { Prisma } from '@prisma/client' -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { defineQuery } from '../../types/defineQuery' - -export const unterveranstaltungVerwaltungListProcedure = defineProcedure({ - key: 'verwaltungList', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: defineQuery({ - filter: z.strictObject({ - veranstaltungId: z.number().optional(), - gliederungId: z.number().optional(), - }), - }), - async handler(options) { - const { skip, take } = options.input.pagination - const where: Prisma.UnterveranstaltungWhereInput = {} - - if (options.input.filter.gliederungId !== undefined) { - where.gliederungId = options.input.filter.gliederungId - } - if (options.input.filter.veranstaltungId !== undefined) { - where.veranstaltungId = options.input.filter.veranstaltungId - } - const veranstaltungen = await prisma.unterveranstaltung.findMany({ - skip, - take, - where: where, - select: { - id: true, - gliederung: { - select: { - id: true, - name: true, - }, - }, - veranstaltung: { - select: { - id: true, - name: true, - hostname: true, - }, - }, - _count: { - select: { Anmeldung: true }, - }, - meldebeginn: true, - meldeschluss: true, - maxTeilnehmende: true, - teilnahmegebuehr: true, - }, - }) - - return veranstaltungen - }, -}) diff --git a/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungPatch.ts b/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungPatch.ts deleted file mode 100644 index a7fe5370..00000000 --- a/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungPatch.ts +++ /dev/null @@ -1,32 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' - -export const unterveranstaltungVerwaltungPatchProcedure = defineProcedure({ - key: 'verwaltungPatch', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: z.strictObject({ - id: z.number().int(), - data: z.strictObject({ - maxTeilnehmende: z.number().int().optional(), - teilnahmegebuehr: z.number({ description: 'In Cent' }).optional(), - meldebeginn: z.date().optional(), - meldeschluss: z.date().optional(), - beschreibung: z.string().optional(), - bedingungen: z.string().optional(), - }), - }), - async handler(options) { - return prisma.unterveranstaltung.update({ - where: { - id: options.input.id, - }, - data: options.input.data, - select: { - id: true, - }, - }) - }, -}) diff --git a/api/src/services/veranstaltung/veranstaltungVerwaltungList.ts b/api/src/services/veranstaltung/veranstaltungVerwaltungList.ts deleted file mode 100644 index e1727cb0..00000000 --- a/api/src/services/veranstaltung/veranstaltungVerwaltungList.ts +++ /dev/null @@ -1,57 +0,0 @@ -import z from 'zod' - -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { defineQuery } from '../../types/defineQuery' - -export const veranstaltungVerwaltungListProcedure = defineProcedure({ - key: 'verwaltungList', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, - inputSchema: defineQuery({ - filter: z.strictObject({ - name: z.string().optional(), - }), - }), - async handler(options) { - const { skip, take } = options.input.pagination - const veranstaltungen = await prisma.veranstaltung.findMany({ - skip, - take, - select: { - id: true, - name: true, - beginn: true, - ende: true, - ort: { - select: { - name: true, - id: true, - }, - }, - meldebeginn: true, - meldeschluss: true, - maxTeilnehmende: true, - teilnahmegebuehr: true, - unterveranstaltungen: { - select: { - id: true, - maxTeilnehmende: true, - teilnahmegebuehr: true, - meldebeginn: true, - meldeschluss: true, - gliederungId: true, - }, - }, - hostname: { - select: { - id: true, - hostname: true, - }, - }, - }, - }) - - return veranstaltungen - }, -}) diff --git a/api/src/types/defineProcedure.ts b/api/src/types/defineProcedure.ts deleted file mode 100644 index 5aec9031..00000000 --- a/api/src/types/defineProcedure.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { Role } from '@prisma/client' -import type { BuildProcedure, CreateRouterInner, ProcedureBuilder, ProcedureParams } from '@trpc/server' -import type { z } from 'zod' - -import { protectedProcedure, publicProcedure, router } from '../trpc' - -type GetProcedureConfig> = - TProcedure extends ProcedureBuilder ? T : never - -type ProtectedProcedureConfig = GetProcedureConfig> -type PublicProcedureConfig = GetProcedureConfig - -type ProcedureResult< - TProcedureConfig extends ProcedureParams, - TMethod extends 'query' | 'mutation', - TTnput, - TResult, -> = BuildProcedure< - TMethod, - { - _config: TProcedureConfig['_config'] - _ctx_out: TProcedureConfig['_ctx_out'] - _input_in: TTnput - _input_out: TTnput - _output_in: TProcedureConfig['_output_in'] - _output_out: TResult - _meta: TProcedureConfig['_meta'] - }, - TResult -> - -type RootRouterConfig = ReturnType extends CreateRouterInner ? T : never - -export function defineProcedure< - const TProcedureKey extends string, - TMethod extends 'query' | 'mutation', - TInputSchema extends z.ZodSchema, - TResult, - TProtection extends { type: 'public' } | { type: 'restrictToRoleIds'; roleIds: Role[] }, - THandler extends (options: { - input: z.infer - ctx: TProtection extends { type: 'public' } - ? PublicProcedureConfig['_ctx_out'] - : ProtectedProcedureConfig['_ctx_out'] - }) => Promise, - TProcedureResult extends ProcedureResult< - TProtection extends { type: 'public' } ? PublicProcedureConfig : ProtectedProcedureConfig, - TMethod, - z.infer, - ReturnType - >, - TProcedureRouter extends CreateRouterInner< - RootRouterConfig, - { - [TKey in TProcedureKey]: TProcedureResult - } - >, ->(config: { - /** Der Key unter dem der Endpunkt aufgerufen werden kann */ - key: TProcedureKey - /** Die trpc Methode. 'query' um Daten zu lesen und 'mutation' wenn Daten geändert werden */ - method: TMethod - /** Die Protection Art des Endpunktes */ - protection: TProtection - /** Das Schema der Eingabedaten */ - inputSchema: TInputSchema - /** Der Handler der die Daten verarbeitet */ - handler: THandler -}) { - const procedure = - config.protection.type === 'public' ? publicProcedure : protectedProcedure(config.protection.roleIds) - return { - ...config, - router: router({ - [config.key]: procedure - .input(config.inputSchema) - [config.method](config.handler as any) as unknown as TProcedureResult, - }) as TProcedureRouter, - } -} diff --git a/api/src/types/defineQuery.ts b/api/src/types/defineQuery.ts deleted file mode 100644 index c9200ab1..00000000 --- a/api/src/types/defineQuery.ts +++ /dev/null @@ -1,24 +0,0 @@ -import z from 'zod' - -export const ZPaginationSchema = z.strictObject({ - skip: z.number().optional(), - take: z.number().max(100).optional(), -}) - -export type TQueryPagination = z.infer - -export const defineQuery = ({ filter }: { filter: TFilter }) => - z.strictObject({ - pagination: ZPaginationSchema, - filter: filter, - }) - -export interface TQuery { - pagination: { - take: number - skip: number - } - groupBy?: Record - sortBy?: Record - filter: Record -} diff --git a/api/static/index.html b/api/static/index.html deleted file mode 100644 index b8996548..00000000 --- a/api/static/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - API - - -

API

- - diff --git a/api/tsconfig.json b/api/tsconfig.json deleted file mode 100644 index 0713e1e2..00000000 --- a/api/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "lib": [ - "ES2022" - ], - "types": ["@edge-runtime/types"] - }, - "include": [ - "src" - ], -} diff --git a/api/.env.example b/apps/api/.env.example similarity index 61% rename from api/.env.example rename to apps/api/.env.example index 2af6b937..fc7a27a5 100644 --- a/api/.env.example +++ b/apps/api/.env.example @@ -1,2 +1,5 @@ NODE_ENV=development DATABASE_URL="postgresql://postgres:postgres@postgres:5432/brahmsee.digital?schema=public" +TOMTOM_APIKEY="your-tom" +MAIL_SENDMAILS="true" +MAIL_SENDGRID_APIKEY="" diff --git a/api/.gitignore b/apps/api/.gitignore similarity index 100% rename from api/.gitignore rename to apps/api/.gitignore diff --git a/apps/api/config/custom-environment-variables.json b/apps/api/config/custom-environment-variables.json new file mode 100644 index 00000000..eab89594 --- /dev/null +++ b/apps/api/config/custom-environment-variables.json @@ -0,0 +1,47 @@ +{ + "server": { + "host": "SERVER_HOST", + "port": "SERVER_PORT" + }, + + "loggingLevel": "LOGGING_LEVEL", + + "clientUrl": "CLIENT_URL", + + "db": { + "url": "DATABASE_URL" + }, + + "authentication": { + "secret": "AUTHENTICATION_SECRET" + }, + "mail": { + "sendMails": "MAIL_SENDMAILS", + "sendgridApiKey": "MAIL_SENDGRID_APIKEY" + }, + "meilisearch": { + "host": "MEILISEARCH_HOST", + "apiKey": "MEILISEARCH_KEY" + }, + "fileDefaultProvider": "FILE_DEFAULT_PROVIDER", + "fileProviders": { + "LOCAL": { + "path": "FILE_PROVIDER_LOCAL_PATH" + }, + "AZURE": { + "account": "FILE_PROVIDER_AZURE_ACCOUNT", + "accountKey": "FILE_PROVIDER_AZURE_ACCOUNT_KEY", + "container": "FILE_PROVIDER_AZURE_CONTAINER", + "folder": "FILE_PROVIDER_AZURE_FOLDER" + } + }, + "tomtom": { + "apiKey": "TOMTOM_APIKEY" + }, + "public": { + "legal": { + "imprint": "PUBLIC_LEGAL_IMPRINT", + "privacy": "PUBLIC_LEGAL_PRIVACY" + } + } +} diff --git a/api/config/default.json b/apps/api/config/default.json similarity index 57% rename from api/config/default.json rename to apps/api/config/default.json index 36b82d39..3e516148 100644 --- a/api/config/default.json +++ b/apps/api/config/default.json @@ -1,6 +1,6 @@ { "server": { - "host": "localhost", + "host": "0.0.0.0", "port": 3030 }, @@ -23,12 +23,33 @@ "max": 50 }, "loggingLevel": "info", - "mail":{ + "mail": { "sendMails": "true", "sendgridApiKey": "" }, "meilisearch": { "host": "meilisearch:7700", "apiKey": "xbAPupQKhkKvF176vE2JxAdPGpmWVu251Hldn6K4Z6Y" + }, + "fileDefaultProvider": "LOCAL", + "fileProviders": { + "LOCAL": { + "path": "../../uploads" + }, + "AZURE": { + "account": "", + "accountKey": "", + "container": "", + "folder": "" + } + }, + "tomtom": { + "apiKey": "" + }, + "public": { + "legal": { + "imprint": "https://localhost:8080", + "privacy": "https://localhost:8080" + } } } diff --git a/api/config/production.json b/apps/api/config/production.json similarity index 100% rename from api/config/production.json rename to apps/api/config/production.json diff --git a/apps/api/email/_layout.mjml b/apps/api/email/_layout.mjml new file mode 100644 index 00000000..419d42a0 --- /dev/null +++ b/apps/api/email/_layout.mjml @@ -0,0 +1,84 @@ + + + + + + + .footer { padding: 10px 0; } .footer a, .footer p { color: #696969; } + + + + + + + {{ veranstaltung }} + + + DLRG {{ gliederung }} + + + + + + + {{ subject }} + + + Moin {{ name }}, + + {{> @partial-block }} + + + + + + Viele Grüße + Dein Orga-Team + + + + + + + + + + + + + + + Impressum + - + Datenschutz + + + + + diff --git a/apps/api/email/account-activated.mjml b/apps/api/email/account-activated.mjml new file mode 100644 index 00000000..0f65bd56 --- /dev/null +++ b/apps/api/email/account-activated.mjml @@ -0,0 +1,11 @@ +{{#> layout }} + + + Dein {{ hostname }} Account wurde bestätigt. Bevor du dich anmelden kannst, muss dein Account noch durch einen + Administrator aktiviert werden. + + +{{/layout}} diff --git a/apps/api/email/account-email-confirm.mjml b/apps/api/email/account-email-confirm.mjml new file mode 100644 index 00000000..0f049b72 --- /dev/null +++ b/apps/api/email/account-email-confirm.mjml @@ -0,0 +1,21 @@ +{{#> layout }} + + + Dein {{ hostname }} Account wurde erstellt. Um den Registrierungsprozess abzuschließen, bestätige deine E-Mail + Adresse bitte mit dem nachfolgenden Link. + + + + Account bestätigen + + +{{/layout}} diff --git a/apps/api/email/account-password-reset.mjml b/apps/api/email/account-password-reset.mjml new file mode 100644 index 00000000..35b57c17 --- /dev/null +++ b/apps/api/email/account-password-reset.mjml @@ -0,0 +1,21 @@ +{{#> layout }} + + + Wir haben eine Anfrage erhalten, das Passwort für deinen Account bei {{ hostname }} zurückzusetzen. Nutze den + nachfolgenden Link, um das zu tun. + + + + Passwort zurücksetzen + + +{{/layout}} diff --git a/apps/api/email/account-status-changed.mjml b/apps/api/email/account-status-changed.mjml new file mode 100644 index 00000000..de372e4c --- /dev/null +++ b/apps/api/email/account-status-changed.mjml @@ -0,0 +1,20 @@ +{{#> layout }} + + + Der Status deines {{ hostname }} Account wurde auf {{ status }} geändert. + + +{{#if isActive }} + + Zur Anmeldung + +{{/if}} {{/layout}} diff --git a/apps/api/email/registration-canceled.mjml b/apps/api/email/registration-canceled.mjml new file mode 100644 index 00000000..b9f1e730 --- /dev/null +++ b/apps/api/email/registration-canceled.mjml @@ -0,0 +1,17 @@ +{{#> layout }} + + + Deine Anmeldung zur Veranstaltung {{ veranstaltung }} wurde storniert. + + + + Für Fragen wende dich bitte an deine Gliederung. + + +{{/layout}} diff --git a/apps/api/email/registration-confirmed.mjml b/apps/api/email/registration-confirmed.mjml new file mode 100644 index 00000000..b7f2878e --- /dev/null +++ b/apps/api/email/registration-confirmed.mjml @@ -0,0 +1,17 @@ +{{#> layout }} + + + Deine Anmeldung zur Veranstaltung {{ veranstaltung }} wurde bestätigt. + + + + Für Fragen wende dich bitte an deine Gliederung. + + +{{/layout}} diff --git a/apps/api/email/registration-rejected.mjml b/apps/api/email/registration-rejected.mjml new file mode 100644 index 00000000..1ac7ea62 --- /dev/null +++ b/apps/api/email/registration-rejected.mjml @@ -0,0 +1,17 @@ +{{#> layout }} + + + Deine Anmeldung zur Veranstaltung {{ veranstaltung }} wurde abgelehnt. + + + + Bei Fragen wende dich bitte an deine Gliederung unter der nachfolgenden E-Mail Adresse: + + +{{/layout}} diff --git a/apps/api/email/registration-successful.mjml b/apps/api/email/registration-successful.mjml new file mode 100644 index 00000000..c78b0b3e --- /dev/null +++ b/apps/api/email/registration-successful.mjml @@ -0,0 +1,42 @@ +{{#> layout }} + + + Vielen Dank für deine Anmeldung zur Veranstaltung {{ veranstaltung }}. + + + + Wichtig: Deine Anmeldung ist noch nicht bestätigt. Sobald sich der Status ändert, erhältst Du eine weitere + E-Mail. + + +{{#if assignmentCode}} + + Noch ein wichtiger Hinweis: Die Anmeldung wurde keinem Account zugeordnet, da du nicht angemeldet warst, als + die Daten abgeschickt worden sind. + + + + Das macht aber nichts, denn wir haben einen sog. Zuordnungscode für dich erstellt. Solltest du dich in Zukunft + dazu entscheiden, einen Account bei {{ hostname }} zu erstellen, kannst du den Code nutzen, um die Anmeldung + deinem Account zuzuordnen. + + + + Dein Code lautet: {{ assignmentCode }} + +{{/if}} {{/layout}} diff --git a/apps/api/eslint.config.js b/apps/api/eslint.config.js new file mode 100644 index 00000000..db0ad79b --- /dev/null +++ b/apps/api/eslint.config.js @@ -0,0 +1,3 @@ +import eslintConfig from '@codeanker/eslint-config/eslint-node-config' + +export default eslintConfig diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 00000000..1ec487d3 --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,87 @@ +{ + "name": "@codeanker/api", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "npx prisma migrate deploy && tsx src/server.ts", + "dev": "tsx watch --clear-screen=false --env-file .env src/server.ts", + "createAccount": "tsx src/scripts/createAccount.ts", + "initMeilisearch": "tsx src/scripts/initMeilisearch.ts", + "cli": "tsx src/cli/index.ts", + "postinstall": "prisma generate", + "tsc": "tsc --noCheck", + "typecheck": "tsc --noEmit", + "lint": "eslint ." + }, + "exports": { + ".": { + "import": "./src/client.ts", + "require": "./src/client.ts" + }, + "./test": { + "import": "./src/util/test.ts", + "require": "./src/util/test.ts" + } + }, + "prisma": { + "seed": "tsx prisma/seeders/index.ts" + }, + "dependencies": { + "@azure/storage-blob": "^12.17.0", + "@codeanker/authentication": "workspace:*", + "@codeanker/datagrid": "file:../../vendor/codeanker-datagrid-2.7.1-trimmed.tgz", + "@codeanker/helpers": "workspace:*", + "@codeanker/service-sms": "file:../../vendor/codeanker-service-sms-0.0.2.tar.gz", + "@e965/xlsx": "^0.20.3", + "@faker-js/faker": "^9.4.0", + "@koa/cors": "^5.0.0", + "@koa/router": "^12.0.1", + "@prisma/client": "^5.19.1", + "@prisma/extension-accelerate": "^1.1.0", + "@sendgrid/mail": "^8.1.0", + "@trpc/server": "catalog:", + "axios": "^1.7.7", + "config": "^3.3.9", + "dayjs": "^1.11.10", + "dot-prop": "^9.0.0", + "fast-csv": "^5.0.1", + "grant": "^5.4.22", + "handlebars": "^4.7.8", + "jsonwebtoken": "^9.0.2", + "koa": "^2.14.2", + "koa-body": "^6.0.1", + "koa-helmet": "^7.0.2", + "koa-router": "^13.0.1", + "koa-session": "^6.4.0", + "koa-static": "^5.0.0", + "lodash-es": "^4.17.21", + "meilisearch": "^0.37.0", + "mime-types": "^2.1.35", + "mjml": "^4.15.3", + "prom-client": "^15.0.0", + "superjson": "catalog:", + "trpc-koa-adapter": "^1.1.3", + "uuid": "^11.0.5", + "winston": "^3.11.0", + "zod": "^3.22.4" + }, + "devDependencies": { + "@codeanker/eslint-config": "workspace:*", + "@codeanker/typescript-config": "workspace:*", + "@inquirer/prompts": "^7.1.0", + "@types/config": "^3.3.3", + "@types/http-status-codes": "^1.2.0", + "@types/jsonwebtoken": "^9.0.5", + "@types/koa": "^2.14.0", + "@types/koa-bodyparser": "^4.3.12", + "@types/koa-router": "^7.4.8", + "@types/mjml": "^4.7.4", + "@types/node": "catalog:", + "commander": "^13.0.0", + "eslint": "catalog:", + "inquirer": "^12.3.0", + "prisma": "^5.19.1", + "tsx": "^4.2.0", + "typescript": "catalog:" + } +} diff --git a/apps/api/prisma/migrations/20240906171118_init/migration.sql b/apps/api/prisma/migrations/20240906171118_init/migration.sql new file mode 100644 index 00000000..60d7a9db --- /dev/null +++ b/apps/api/prisma/migrations/20240906171118_init/migration.sql @@ -0,0 +1,351 @@ +-- CreateEnum +CREATE TYPE "AccountStatus" AS ENUM ('OFFEN', 'AKTIV', 'DEAKTIVIERT'); + +-- CreateEnum +CREATE TYPE "Role" AS ENUM ('GLIEDERUNG_ADMIN', 'ADMIN'); + +-- CreateEnum +CREATE TYPE "Gender" AS ENUM ('MALE', 'FEMALE', 'UNSPECIFIED'); + +-- CreateEnum +CREATE TYPE "Essgewohnheit" AS ENUM ('OMNIVOR', 'VEGETARISCH', 'VEGAN'); + +-- CreateEnum +CREATE TYPE "NahrungsmittelIntoleranz" AS ENUM ('SCHWEIN', 'GLUTEN', 'LAKTOSE', 'FRUCTOSE'); + +-- CreateEnum +CREATE TYPE "QualificationFahrerlaubnis" AS ENUM ('B', 'BE', 'C', 'CE', 'D1', 'D', 'D1E', 'DE', 'T', 'L'); + +-- CreateEnum +CREATE TYPE "QualificationSchwimmer" AS ENUM ('BRONZE', 'SILBER', 'GOLD', 'JUNIORRETTER', 'RETTUNGSSCHWIMMER_BRONZE', 'RETTUNGSSCHWIMMER_SILBER', 'RETTUNGSSCHWIMMER_GOLD'); + +-- CreateEnum +CREATE TYPE "QualificationErsteHilfe" AS ENUM ('EINWEISER_EHSH', 'AUSBILDER_EHSH_MODUL_1_2', 'AUSBILDER_EHSH_MODUL_3', 'MODULE_1', 'MODULE_2', 'MODULE_3', 'AUSBILDUNG', 'KINDERNOTFAELLE', 'BILDUNGS_UND_BETREUUNGSEINRICHTUNGEN_KINDER', 'AUSBILDER'); + +-- CreateEnum +CREATE TYPE "QualificationSanitaeter" AS ENUM ('SAN_A', 'SAN_B', 'FORTBILDUNG', 'AUSBILDER'); + +-- CreateEnum +CREATE TYPE "QualificationFunk" AS ENUM ('DLRG_SPRECHFUNKER', 'BOS_SPRECHFUNKER_ANALOG', 'BOS_SPRECHFUNKER_DIGITAL', 'AUSBILDER_SPRECHFUNK', 'AUSBILDER_BOS_SPRECHFUNK', 'MULTIPLIKATOR_SPRECHFUNK', 'MULTIPLIKATOR_BOS_SPRECHFUNK', 'EINSATZFAEHIGKEIT'); + +-- CreateEnum +CREATE TYPE "Konfektionsgroesse" AS ENUM ('JUNIOR_98_104', 'JUNIOR_110_116', 'JUNIOR_122_128', 'JUNIOR_134_140', 'JUNIOR_146_152', 'JUNIOR_158_164', 'XS', 'S', 'M', 'L', 'XL', 'XXL', 'XXXL'); + +-- CreateEnum +CREATE TYPE "GliederungAccountRole" AS ENUM ('DELEGATIONSLEITER', 'BETREUER', 'TEILNEHMER'); + +-- CreateEnum +CREATE TYPE "AnmeldungStatus" AS ENUM ('OFFEN', 'BESTAETIGT', 'STORNIERT', 'ABGELEHNT'); + +-- CreateEnum +CREATE TYPE "MahlzeitType" AS ENUM ('FRUEHSTUECK', 'MITTAGESSEN', 'ABENDESSEN'); + +-- CreateEnum +CREATE TYPE "UnterveranstaltungType" AS ENUM ('CREW', 'GLIEDERUNG'); + +-- CreateEnum +CREATE TYPE "ActivityType" AS ENUM ('CREATE', 'UPDATE', 'DELETE', 'EMAIL', 'OTHER'); + +-- CreateEnum +CREATE TYPE "CustomFieldType" AS ENUM ('BASIC_INPUT', 'BASIC_TEXT_AREA', 'BASIC_EDITOR', 'BASIC_SWITCH', 'BASIC_CHECKBOX', 'BASIC_INPUT_NUMBER', 'BASIC_RADIO', 'BASIC_SELECT', 'BASIC_DROPDOWN'); + +-- CreateEnum +CREATE TYPE "CustomFieldPosition" AS ENUM ('PUBLIC_ANMELDUNG', 'INTERN_ANMELDUNG'); + +-- CreateTable +CREATE TABLE "Hostname" ( + "id" SERIAL NOT NULL, + "hostname" TEXT NOT NULL, + + CONSTRAINT "Hostname_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Notfallkontakt" ( + "id" SERIAL NOT NULL, + "firstname" TEXT NOT NULL, + "lastname" TEXT NOT NULL, + "telefon" TEXT NOT NULL, + "istErziehungsberechtigt" BOOLEAN NOT NULL DEFAULT false, + "personId" INTEGER NOT NULL, + + CONSTRAINT "Notfallkontakt_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Person" ( + "id" SERIAL NOT NULL, + "firstname" TEXT NOT NULL, + "lastname" TEXT NOT NULL, + "birthday" DATE, + "gender" "Gender", + "email" TEXT NOT NULL, + "telefon" TEXT NOT NULL, + "gliederungId" INTEGER, + "essgewohnheit" "Essgewohnheit", + "nahrungsmittelIntoleranzen" "NahrungsmittelIntoleranz"[], + "weitereIntoleranzen" TEXT[], + "qualifikationenFahrerlaubnis" "QualificationFahrerlaubnis"[], + "qualifikationenSchwimmer" "QualificationSchwimmer"[], + "qualifikationenErsteHilfe" "QualificationErsteHilfe"[], + "qualifikationenSanitaeter" "QualificationSanitaeter"[], + "qualifikationenFunk" "QualificationFunk"[], + "konfektionsgroesse" "Konfektionsgroesse", + "notfallkontaktIds" INTEGER[], + "addressId" INTEGER, + + CONSTRAINT "Person_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Account" ( + "id" SERIAL NOT NULL, + "email" TEXT NOT NULL, + "dlrgOauthId" TEXT, + "password" TEXT, + "role" "Role" NOT NULL, + "personId" INTEGER NOT NULL, + "activatedAt" TIMESTAMP(3), + "activationToken" TEXT, + "status" "AccountStatus" NOT NULL DEFAULT 'OFFEN', + "passwordResetToken" TEXT, + + CONSTRAINT "Account_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Gliederung" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "edv" TEXT NOT NULL, + + CONSTRAINT "Gliederung_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "GliederungToAccount" ( + "id" SERIAL NOT NULL, + "gliederungId" INTEGER NOT NULL, + "accountId" INTEGER NOT NULL, + "role" "GliederungAccountRole" NOT NULL, + + CONSTRAINT "GliederungToAccount_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Anmeldung" ( + "id" SERIAL NOT NULL, + "unterveranstaltungId" INTEGER NOT NULL, + "personId" INTEGER NOT NULL, + "status" "AnmeldungStatus" NOT NULL DEFAULT 'OFFEN', + "mahlzeitenIds" INTEGER[], + "uebernachtungsTage" DATE[], + "tshirtBestellt" BOOLEAN NOT NULL DEFAULT false, + "comment" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Anmeldung_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Address" ( + "id" SERIAL NOT NULL, + "street" TEXT NOT NULL, + "number" TEXT NOT NULL, + "zip" TEXT NOT NULL, + "city" TEXT NOT NULL, + + CONSTRAINT "Address_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Ort" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "addressId" INTEGER, + + CONSTRAINT "Ort_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Veranstaltung" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "beginn" DATE NOT NULL, + "ende" DATE NOT NULL, + "meldebeginn" TIMESTAMP(3) NOT NULL, + "meldeschluss" TIMESTAMP(3) NOT NULL, + "ortId" INTEGER, + "maxTeilnehmende" INTEGER NOT NULL, + "teilnahmegebuehr" INTEGER NOT NULL, + "beschreibung" TEXT, + "datenschutz" TEXT, + "teilnahmeBedingungen" TEXT, + "teilnahmeBedingungenPublic" TEXT, + "zielgruppe" TEXT, + "hostnameId" INTEGER, + + CONSTRAINT "Veranstaltung_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Mahlzeit" ( + "id" SERIAL NOT NULL, + "type" "MahlzeitType" NOT NULL, + "date" DATE NOT NULL, + "veranstaltungId" INTEGER NOT NULL, + + CONSTRAINT "Mahlzeit_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Unterveranstaltung" ( + "id" SERIAL NOT NULL, + "maxTeilnehmende" INTEGER NOT NULL, + "teilnahmegebuehr" INTEGER NOT NULL, + "meldebeginn" TIMESTAMP(3) NOT NULL, + "meldeschluss" TIMESTAMP(3) NOT NULL, + "veranstaltungId" INTEGER NOT NULL, + "gliederungId" INTEGER NOT NULL, + "beschreibung" TEXT, + "bedingungen" TEXT, + "type" "UnterveranstaltungType" NOT NULL DEFAULT 'GLIEDERUNG', + + CONSTRAINT "Unterveranstaltung_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Activity" ( + "id" SERIAL NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "type" "ActivityType" NOT NULL, + "description" TEXT, + "subjectType" TEXT NOT NULL, + "subjectId" INTEGER, + "causerId" INTEGER, + "metadata" JSONB NOT NULL DEFAULT '{}', + + CONSTRAINT "Activity_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CustomField" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "type" "CustomFieldType" NOT NULL, + "required" BOOLEAN NOT NULL DEFAULT false, + "options" TEXT[], + "role" "Role"[], + "positions" "CustomFieldPosition"[], + "veranstaltungId" INTEGER, + "unterveranstaltungId" INTEGER, + + CONSTRAINT "CustomField_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CustomFieldValue" ( + "id" SERIAL NOT NULL, + "value" JSONB NOT NULL DEFAULT '{}', + "fieldId" INTEGER NOT NULL, + "anmeldungId" INTEGER, + + CONSTRAINT "CustomFieldValue_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "_AnmeldungToMahlzeit" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "Account_email_key" ON "Account"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Account_dlrgOauthId_key" ON "Account"("dlrgOauthId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Account_personId_key" ON "Account"("personId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Account_activationToken_key" ON "Account"("activationToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "Account_passwordResetToken_key" ON "Account"("passwordResetToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "Gliederung_edv_key" ON "Gliederung"("edv"); + +-- CreateIndex +CREATE UNIQUE INDEX "GliederungToAccount_gliederungId_accountId_key" ON "GliederungToAccount"("gliederungId", "accountId"); + +-- CreateIndex +CREATE UNIQUE INDEX "_AnmeldungToMahlzeit_AB_unique" ON "_AnmeldungToMahlzeit"("A", "B"); + +-- CreateIndex +CREATE INDEX "_AnmeldungToMahlzeit_B_index" ON "_AnmeldungToMahlzeit"("B"); + +-- AddForeignKey +ALTER TABLE "Notfallkontakt" ADD CONSTRAINT "Notfallkontakt_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Person" ADD CONSTRAINT "Person_gliederungId_fkey" FOREIGN KEY ("gliederungId") REFERENCES "Gliederung"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Person" ADD CONSTRAINT "Person_addressId_fkey" FOREIGN KEY ("addressId") REFERENCES "Address"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Account" ADD CONSTRAINT "Account_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "GliederungToAccount" ADD CONSTRAINT "GliederungToAccount_gliederungId_fkey" FOREIGN KEY ("gliederungId") REFERENCES "Gliederung"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "GliederungToAccount" ADD CONSTRAINT "GliederungToAccount_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Anmeldung" ADD CONSTRAINT "Anmeldung_unterveranstaltungId_fkey" FOREIGN KEY ("unterveranstaltungId") REFERENCES "Unterveranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Anmeldung" ADD CONSTRAINT "Anmeldung_personId_fkey" FOREIGN KEY ("personId") REFERENCES "Person"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Ort" ADD CONSTRAINT "Ort_addressId_fkey" FOREIGN KEY ("addressId") REFERENCES "Address"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Veranstaltung" ADD CONSTRAINT "Veranstaltung_ortId_fkey" FOREIGN KEY ("ortId") REFERENCES "Ort"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Veranstaltung" ADD CONSTRAINT "Veranstaltung_hostnameId_fkey" FOREIGN KEY ("hostnameId") REFERENCES "Hostname"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Mahlzeit" ADD CONSTRAINT "Mahlzeit_veranstaltungId_fkey" FOREIGN KEY ("veranstaltungId") REFERENCES "Veranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Unterveranstaltung" ADD CONSTRAINT "Unterveranstaltung_veranstaltungId_fkey" FOREIGN KEY ("veranstaltungId") REFERENCES "Veranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Unterveranstaltung" ADD CONSTRAINT "Unterveranstaltung_gliederungId_fkey" FOREIGN KEY ("gliederungId") REFERENCES "Gliederung"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Activity" ADD CONSTRAINT "Activity_causerId_fkey" FOREIGN KEY ("causerId") REFERENCES "Account"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CustomField" ADD CONSTRAINT "CustomField_veranstaltungId_fkey" FOREIGN KEY ("veranstaltungId") REFERENCES "Veranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CustomField" ADD CONSTRAINT "CustomField_unterveranstaltungId_fkey" FOREIGN KEY ("unterveranstaltungId") REFERENCES "Unterveranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CustomFieldValue" ADD CONSTRAINT "CustomFieldValue_fieldId_fkey" FOREIGN KEY ("fieldId") REFERENCES "CustomField"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CustomFieldValue" ADD CONSTRAINT "CustomFieldValue_anmeldungId_fkey" FOREIGN KEY ("anmeldungId") REFERENCES "Anmeldung"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_AnmeldungToMahlzeit" ADD CONSTRAINT "_AnmeldungToMahlzeit_A_fkey" FOREIGN KEY ("A") REFERENCES "Anmeldung"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_AnmeldungToMahlzeit" ADD CONSTRAINT "_AnmeldungToMahlzeit_B_fkey" FOREIGN KEY ("B") REFERENCES "Mahlzeit"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/api/prisma/migrations/20240907155549_rename_address_schema/migration.sql b/apps/api/prisma/migrations/20240907155549_rename_address_schema/migration.sql new file mode 100644 index 00000000..4b7b39ed --- /dev/null +++ b/apps/api/prisma/migrations/20240907155549_rename_address_schema/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - You are about to drop the column `number` on the `Address` table. All the data in the column will be lost. + - Added the required column `streetNumber` to the `Address` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Address" DROP COLUMN "number", +ADD COLUMN "lat" TEXT, +ADD COLUMN "lon" TEXT, +ADD COLUMN "streetNumber" TEXT NOT NULL; diff --git a/apps/api/prisma/migrations/20240907172928_add_field_to_address/migration.sql b/apps/api/prisma/migrations/20240907172928_add_field_to_address/migration.sql new file mode 100644 index 00000000..f0246974 --- /dev/null +++ b/apps/api/prisma/migrations/20240907172928_add_field_to_address/migration.sql @@ -0,0 +1,9 @@ +/* + Warnings: + + - Added the required column `country` to the `Address` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Address" ADD COLUMN "country" TEXT NOT NULL, +ADD COLUMN "valid" BOOLEAN NOT NULL DEFAULT false; diff --git a/apps/api/prisma/migrations/20240907174812_edit_fields_address/migration.sql b/apps/api/prisma/migrations/20240907174812_edit_fields_address/migration.sql new file mode 100644 index 00000000..b7fa33f7 --- /dev/null +++ b/apps/api/prisma/migrations/20240907174812_edit_fields_address/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - The `lat` column on the `Address` table would be dropped and recreated. This will lead to data loss if there is data in the column. + - The `lon` column on the `Address` table would be dropped and recreated. This will lead to data loss if there is data in the column. + +*/ +-- AlterTable +ALTER TABLE "Address" DROP COLUMN "lat", +ADD COLUMN "lat" DOUBLE PRECISION, +DROP COLUMN "lon", +ADD COLUMN "lon" DOUBLE PRECISION; diff --git a/apps/api/prisma/migrations/20241118163314_init_file/migration.sql b/apps/api/prisma/migrations/20241118163314_init_file/migration.sql new file mode 100644 index 00000000..33e407ff --- /dev/null +++ b/apps/api/prisma/migrations/20241118163314_init_file/migration.sql @@ -0,0 +1,42 @@ +-- CreateEnum +CREATE TYPE "FileProvider" AS ENUM ('LOCAL', 'AZURE'); + +-- CreateTable +CREATE TABLE "File" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "uploaded" BOOLEAN NOT NULL DEFAULT false, + "uploadedAt" TIMESTAMP(3), + "provider" "FileProvider" NOT NULL, + "key" TEXT NOT NULL, + "filename" TEXT, + "mimetype" TEXT, + + CONSTRAINT "File_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "UnterveranstaltungDocument" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "unterveranstaltungId" INTEGER NOT NULL, + "fileId" TEXT NOT NULL, + + CONSTRAINT "UnterveranstaltungDocument_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "File_id_key" ON "File"("id"); + +-- CreateIndex +CREATE UNIQUE INDEX "File_key_key" ON "File"("key"); + +-- CreateIndex +CREATE UNIQUE INDEX "UnterveranstaltungDocument_fileId_key" ON "UnterveranstaltungDocument"("fileId"); + +-- AddForeignKey +ALTER TABLE "UnterveranstaltungDocument" ADD CONSTRAINT "UnterveranstaltungDocument_unterveranstaltungId_fkey" FOREIGN KEY ("unterveranstaltungId") REFERENCES "Unterveranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UnterveranstaltungDocument" ADD CONSTRAINT "UnterveranstaltungDocument_fileId_fkey" FOREIGN KEY ("fileId") REFERENCES "File"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/apps/api/prisma/migrations/20241229191039_person_photo/migration.sql b/apps/api/prisma/migrations/20241229191039_person_photo/migration.sql new file mode 100644 index 00000000..e121de46 --- /dev/null +++ b/apps/api/prisma/migrations/20241229191039_person_photo/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Person" ADD COLUMN "photoId" TEXT; + +-- AddForeignKey +ALTER TABLE "Person" ADD CONSTRAINT "Person_photoId_fkey" FOREIGN KEY ("photoId") REFERENCES "File"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/api/prisma/migrations/20250115215644_change_subject_id_type/migration.sql b/apps/api/prisma/migrations/20250115215644_change_subject_id_type/migration.sql new file mode 100644 index 00000000..e1f6cd1f --- /dev/null +++ b/apps/api/prisma/migrations/20250115215644_change_subject_id_type/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Activity" ALTER COLUMN "subjectId" SET DATA TYPE TEXT; diff --git a/apps/api/prisma/migrations/20250201150234_multiple_addresses_to_schema/migration.sql b/apps/api/prisma/migrations/20250201150234_multiple_addresses_to_schema/migration.sql new file mode 100644 index 00000000..6f02445b --- /dev/null +++ b/apps/api/prisma/migrations/20250201150234_multiple_addresses_to_schema/migration.sql @@ -0,0 +1,53 @@ +/* + Warnings: + + - You are about to drop the column `tshirtBestellt` on the `Anmeldung` table. All the data in the column will be lost. + - You are about to drop the column `konfektionsgroesse` on the `Person` table. All the data in the column will be lost. + - You are about to drop the column `qualifikationenErsteHilfe` on the `Person` table. All the data in the column will be lost. + - You are about to drop the column `qualifikationenFahrerlaubnis` on the `Person` table. All the data in the column will be lost. + - You are about to drop the column `qualifikationenFunk` on the `Person` table. All the data in the column will be lost. + - You are about to drop the column `qualifikationenSanitaeter` on the `Person` table. All the data in the column will be lost. + - You are about to drop the column `qualifikationenSchwimmer` on the `Person` table. All the data in the column will be lost. + +*/ +-- AlterEnum +ALTER TYPE "Role" ADD VALUE 'USER'; + +-- AlterTable +ALTER TABLE "Anmeldung" DROP COLUMN "tshirtBestellt", +ADD COLUMN "accountId" INTEGER; + +-- AlterTable +ALTER TABLE "Person" DROP COLUMN "konfektionsgroesse", +DROP COLUMN "qualifikationenErsteHilfe", +DROP COLUMN "qualifikationenFahrerlaubnis", +DROP COLUMN "qualifikationenFunk", +DROP COLUMN "qualifikationenSanitaeter", +DROP COLUMN "qualifikationenSchwimmer"; + +-- AlterTable +ALTER TABLE "_AnmeldungToMahlzeit" ADD CONSTRAINT "_AnmeldungToMahlzeit_AB_pkey" PRIMARY KEY ("A", "B"); + +-- DropIndex +DROP INDEX "_AnmeldungToMahlzeit_AB_unique"; + +-- DropEnum +DROP TYPE "Konfektionsgroesse"; + +-- DropEnum +DROP TYPE "QualificationErsteHilfe"; + +-- DropEnum +DROP TYPE "QualificationFahrerlaubnis"; + +-- DropEnum +DROP TYPE "QualificationFunk"; + +-- DropEnum +DROP TYPE "QualificationSanitaeter"; + +-- DropEnum +DROP TYPE "QualificationSchwimmer"; + +-- AddForeignKey +ALTER TABLE "Anmeldung" ADD CONSTRAINT "Anmeldung_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/api/prisma/migrations/20250201154538_anmeldung_linkage/migration.sql b/apps/api/prisma/migrations/20250201154538_anmeldung_linkage/migration.sql new file mode 100644 index 00000000..efe2b687 --- /dev/null +++ b/apps/api/prisma/migrations/20250201154538_anmeldung_linkage/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Anmeldung" ADD COLUMN "assignmentCode" UUID; diff --git a/apps/api/prisma/migrations/20250203181204_public_landing/migration.sql b/apps/api/prisma/migrations/20250203181204_public_landing/migration.sql new file mode 100644 index 00000000..02012517 --- /dev/null +++ b/apps/api/prisma/migrations/20250203181204_public_landing/migration.sql @@ -0,0 +1,104 @@ +-- DropForeignKey +ALTER TABLE "Person" DROP CONSTRAINT "Person_photoId_fkey"; + +-- CreateTable +CREATE TABLE "faq_categories" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "unterveranstaltungId" INTEGER NOT NULL, + + CONSTRAINT "faq_categories_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "faqs" ( + "id" SERIAL NOT NULL, + "question" TEXT NOT NULL, + "answer" TEXT NOT NULL, + "categoryId" INTEGER NOT NULL, + + CONSTRAINT "faqs_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "UnterveranstaltungLandingSettings" ( + "unterveranstaltungId" SERIAL NOT NULL, + "heroTitle" TEXT NOT NULL, + "heroSubtitle" TEXT NOT NULL, + "eventDetailsTitle" TEXT NOT NULL, + "eventDetailsContent" TEXT NOT NULL, + "miscellaneousVisible" BOOLEAN, + "miscellaneousTitle" TEXT, + "faqVisible" BOOLEAN, + "faqEmail" TEXT, + "instagramVisible" BOOLEAN, + "instagramUrl" TEXT, + "facebookVisible" BOOLEAN, + "facebookUrl" TEXT, + + CONSTRAINT "UnterveranstaltungLandingSettings_pkey" PRIMARY KEY ("unterveranstaltungId") +); + +-- CreateTable +CREATE TABLE "UnterveranstaltungLandingImages" ( + "id" SERIAL NOT NULL, + "name" TEXT, + "unterveranstaltungLandingSettingsId" INTEGER, + "fileId" TEXT NOT NULL, + + CONSTRAINT "UnterveranstaltungLandingImages_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "UnterveranstaltungLandingMiscellaneous" ( + "id" SERIAL NOT NULL, + "title" TEXT NOT NULL, + "content" TEXT NOT NULL, + "unterveranstaltungLandingSettingsId" INTEGER, + + CONSTRAINT "UnterveranstaltungLandingMiscellaneous_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "_FaqToUnterveranstaltung" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL, + + CONSTRAINT "_FaqToUnterveranstaltung_AB_pkey" PRIMARY KEY ("A","B") +); + +-- CreateIndex +CREATE UNIQUE INDEX "faq_categories_name_unterveranstaltungId_key" ON "faq_categories"("name", "unterveranstaltungId"); + +-- CreateIndex +CREATE UNIQUE INDEX "UnterveranstaltungLandingImages_fileId_key" ON "UnterveranstaltungLandingImages"("fileId"); + +-- CreateIndex +CREATE INDEX "_FaqToUnterveranstaltung_B_index" ON "_FaqToUnterveranstaltung"("B"); + +-- AddForeignKey +ALTER TABLE "faq_categories" ADD CONSTRAINT "faq_categories_unterveranstaltungId_fkey" FOREIGN KEY ("unterveranstaltungId") REFERENCES "Unterveranstaltung"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "faqs" ADD CONSTRAINT "faqs_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "faq_categories"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Person" ADD CONSTRAINT "Person_photoId_fkey" FOREIGN KEY ("photoId") REFERENCES "File"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UnterveranstaltungLandingSettings" ADD CONSTRAINT "UnterveranstaltungLandingSettings_unterveranstaltungId_fkey" FOREIGN KEY ("unterveranstaltungId") REFERENCES "Unterveranstaltung"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UnterveranstaltungLandingImages" ADD CONSTRAINT "UnterveranstaltungLandingImages_unterveranstaltungLandingS_fkey" FOREIGN KEY ("unterveranstaltungLandingSettingsId") REFERENCES "UnterveranstaltungLandingSettings"("unterveranstaltungId") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UnterveranstaltungLandingImages" ADD CONSTRAINT "UnterveranstaltungLandingImages_fileId_fkey" FOREIGN KEY ("fileId") REFERENCES "File"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UnterveranstaltungLandingMiscellaneous" ADD CONSTRAINT "UnterveranstaltungLandingMiscellaneous_unterveranstaltungL_fkey" FOREIGN KEY ("unterveranstaltungLandingSettingsId") REFERENCES "UnterveranstaltungLandingSettings"("unterveranstaltungId") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_FaqToUnterveranstaltung" ADD CONSTRAINT "_FaqToUnterveranstaltung_A_fkey" FOREIGN KEY ("A") REFERENCES "faqs"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_FaqToUnterveranstaltung" ADD CONSTRAINT "_FaqToUnterveranstaltung_B_fkey" FOREIGN KEY ("B") REFERENCES "Unterveranstaltung"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/api/prisma/migrations/20250204082400_edit_landing_relation/migration.sql b/apps/api/prisma/migrations/20250204082400_edit_landing_relation/migration.sql new file mode 100644 index 00000000..3f43a2b4 --- /dev/null +++ b/apps/api/prisma/migrations/20250204082400_edit_landing_relation/migration.sql @@ -0,0 +1,34 @@ +/* + Warnings: + + - The primary key for the `_AnmeldungToMahlzeit` table will be changed. If it partially fails, the table could be left without primary key constraint. + - The primary key for the `_FaqToUnterveranstaltung` table will be changed. If it partially fails, the table could be left without primary key constraint. + - A unique constraint covering the columns `[landingSettingsId]` on the table `Unterveranstaltung` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[A,B]` on the table `_AnmeldungToMahlzeit` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[A,B]` on the table `_FaqToUnterveranstaltung` will be added. If there are existing duplicate values, this will fail. + - Added the required column `landingSettingsId` to the `Unterveranstaltung` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropForeignKey +ALTER TABLE "UnterveranstaltungLandingSettings" DROP CONSTRAINT "UnterveranstaltungLandingSettings_unterveranstaltungId_fkey"; + +-- AlterTable +ALTER TABLE "Unterveranstaltung" ADD COLUMN "landingSettingsId" INTEGER NOT NULL; + +-- AlterTable +ALTER TABLE "_AnmeldungToMahlzeit" DROP CONSTRAINT "_AnmeldungToMahlzeit_AB_pkey"; + +-- AlterTable +ALTER TABLE "_FaqToUnterveranstaltung" DROP CONSTRAINT "_FaqToUnterveranstaltung_AB_pkey"; + +-- CreateIndex +CREATE UNIQUE INDEX "Unterveranstaltung_landingSettingsId_key" ON "Unterveranstaltung"("landingSettingsId"); + +-- CreateIndex +CREATE UNIQUE INDEX "_AnmeldungToMahlzeit_AB_unique" ON "_AnmeldungToMahlzeit"("A", "B"); + +-- CreateIndex +CREATE UNIQUE INDEX "_FaqToUnterveranstaltung_AB_unique" ON "_FaqToUnterveranstaltung"("A", "B"); + +-- AddForeignKey +ALTER TABLE "Unterveranstaltung" ADD CONSTRAINT "Unterveranstaltung_landingSettingsId_fkey" FOREIGN KEY ("landingSettingsId") REFERENCES "UnterveranstaltungLandingSettings"("unterveranstaltungId") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/api/prisma/migrations/20250204090657_add_subtitle/migration.sql b/apps/api/prisma/migrations/20250204090657_add_subtitle/migration.sql new file mode 100644 index 00000000..9f64f091 --- /dev/null +++ b/apps/api/prisma/migrations/20250204090657_add_subtitle/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "UnterveranstaltungLandingSettings" ADD COLUMN "miscellaneousSubtitle" TEXT; diff --git a/apps/api/prisma/migrations/20250209183036_make_landingsettingsid_optional/migration.sql b/apps/api/prisma/migrations/20250209183036_make_landingsettingsid_optional/migration.sql new file mode 100644 index 00000000..7965b4fb --- /dev/null +++ b/apps/api/prisma/migrations/20250209183036_make_landingsettingsid_optional/migration.sql @@ -0,0 +1,14 @@ +-- AlterTable +ALTER TABLE "Unterveranstaltung" ALTER COLUMN "landingSettingsId" DROP NOT NULL; + +-- AlterTable +ALTER TABLE "_AnmeldungToMahlzeit" ADD CONSTRAINT "_AnmeldungToMahlzeit_AB_pkey" PRIMARY KEY ("A", "B"); + +-- DropIndex +DROP INDEX "_AnmeldungToMahlzeit_AB_unique"; + +-- AlterTable +ALTER TABLE "_FaqToUnterveranstaltung" ADD CONSTRAINT "_FaqToUnterveranstaltung_AB_pkey" PRIMARY KEY ("A", "B"); + +-- DropIndex +DROP INDEX "_FaqToUnterveranstaltung_AB_unique"; diff --git a/apps/api/prisma/migrations/migration_lock.toml b/apps/api/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..648c57fd --- /dev/null +++ b/apps/api/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" \ No newline at end of file diff --git a/apps/api/prisma/schema/Account.prisma b/apps/api/prisma/schema/Account.prisma new file mode 100644 index 00000000..f407ce8c --- /dev/null +++ b/apps/api/prisma/schema/Account.prisma @@ -0,0 +1,28 @@ +enum Role { + USER + GLIEDERUNG_ADMIN + ADMIN +} + +enum AccountStatus { + OFFEN + AKTIV + DEAKTIVIERT +} + +model Account { + id Int @id @default(autoincrement()) + email String @unique + dlrgOauthId String? @unique + password String? + role Role + personId Int @unique + person Person @relation(fields: [personId], references: [id], onDelete: Cascade) + activatedAt DateTime? + GliederungToAccount GliederungToAccount[] + activationToken String? @unique + status AccountStatus @default(OFFEN) + passwordResetToken String? @unique + activities Activity[] + Anmeldung Anmeldung[] +} diff --git a/apps/api/prisma/schema/Activity.prisma b/apps/api/prisma/schema/Activity.prisma new file mode 100644 index 00000000..c9b949f5 --- /dev/null +++ b/apps/api/prisma/schema/Activity.prisma @@ -0,0 +1,19 @@ +enum ActivityType { + CREATE + UPDATE + DELETE + EMAIL + OTHER +} + +model Activity { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + type ActivityType + description String? + subjectType String + subjectId String? + causerId Int? + causer Account? @relation(fields: [causerId], references: [id], onDelete: SetNull) + metadata Json @default("{}") +} diff --git a/apps/api/prisma/schema/Address.prisma b/apps/api/prisma/schema/Address.prisma new file mode 100644 index 00000000..98fb4c2d --- /dev/null +++ b/apps/api/prisma/schema/Address.prisma @@ -0,0 +1,21 @@ +model Address { + id Int @id @default(autoincrement()) + street String + streetNumber String + zip String + city String + country String + lat Float? + lon Float? + valid Boolean @default(false) + Ort Ort[] + Person Person[] +} + +model Ort { + id Int @id @default(autoincrement()) + name String + addressId Int? + address Address? @relation(fields: [addressId], references: [id], onDelete: SetNull) + Veranstaltung Veranstaltung[] +} diff --git a/apps/api/prisma/schema/Anmeldung.prisma b/apps/api/prisma/schema/Anmeldung.prisma new file mode 100644 index 00000000..ba804334 --- /dev/null +++ b/apps/api/prisma/schema/Anmeldung.prisma @@ -0,0 +1,28 @@ +enum AnmeldungStatus { + OFFEN + BESTAETIGT + STORNIERT + ABGELEHNT +} + +model Anmeldung { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + status AnmeldungStatus @default(OFFEN) + comment String? + assignmentCode String? @db.Uuid + + unterveranstaltungId Int + unterveranstaltung Unterveranstaltung @relation(fields: [unterveranstaltungId], references: [id], onDelete: Cascade) + + personId Int + person Person @relation(fields: [personId], references: [id], onDelete: Cascade) + + accountId Int? + account Account? @relation(fields: [accountId], references: [id], onDelete: Cascade) + + mahlzeitenIds Int[] + mahlzeiten Mahlzeit[] + uebernachtungsTage DateTime[] @db.Date + customFieldValues CustomFieldValue[] +} diff --git a/apps/api/prisma/schema/CustomField.prisma b/apps/api/prisma/schema/CustomField.prisma new file mode 100644 index 00000000..a06b4cd2 --- /dev/null +++ b/apps/api/prisma/schema/CustomField.prisma @@ -0,0 +1,41 @@ +enum CustomFieldType { + BASIC_INPUT + BASIC_TEXT_AREA + BASIC_EDITOR + BASIC_SWITCH + BASIC_CHECKBOX + BASIC_INPUT_NUMBER + BASIC_RADIO + BASIC_SELECT + BASIC_DROPDOWN +} + +enum CustomFieldPosition { + PUBLIC_ANMELDUNG + INTERN_ANMELDUNG +} + +model CustomField { + id Int @id @default(autoincrement()) + name String + description String? + type CustomFieldType + required Boolean @default(false) + options String[] + role Role[] + values CustomFieldValue[] + positions CustomFieldPosition[] + veranstaltungId Int? + unterveranstaltungId Int? + veranstaltung Veranstaltung? @relation(fields: [veranstaltungId], references: [id], onDelete: Cascade) + unterveranstaltung Unterveranstaltung? @relation(fields: [unterveranstaltungId], references: [id], onDelete: Cascade) +} + +model CustomFieldValue { + id Int @id @default(autoincrement()) + value Json @default("{}") + fieldId Int + field CustomField @relation(fields: [fieldId], references: [id], onDelete: Cascade) + anmeldungId Int? + anmeldung Anmeldung? @relation(fields: [anmeldungId], references: [id], onDelete: Cascade) +} diff --git a/apps/api/prisma/schema/Faq.prisma b/apps/api/prisma/schema/Faq.prisma new file mode 100644 index 00000000..1968ba92 --- /dev/null +++ b/apps/api/prisma/schema/Faq.prisma @@ -0,0 +1,24 @@ +model FaqCategory { + id Int @id @default(autoincrement()) + name String + + unterveranstaltungId Int + unterveranstaltung Unterveranstaltung @relation(fields: [unterveranstaltungId], references: [id]) + faqs Faq[] + + @@unique([name, unterveranstaltungId]) + @@map("faq_categories") +} + +model Faq { + id Int @id @default(autoincrement()) + question String + answer String + + categoryId Int + category FaqCategory @relation(fields: [categoryId], references: [id]) + + unterveranstaltung Unterveranstaltung[] + + @@map("faqs") +} diff --git a/apps/api/prisma/schema/File.prisma b/apps/api/prisma/schema/File.prisma new file mode 100644 index 00000000..15f8768c --- /dev/null +++ b/apps/api/prisma/schema/File.prisma @@ -0,0 +1,18 @@ +enum FileProvider { + LOCAL + AZURE +} + +model File { + id String @id @unique @default(uuid()) + createdAt DateTime @default(now()) + uploaded Boolean @default(false) + uploadedAt DateTime? + provider FileProvider + key String @unique() + filename String? + mimetype String? + UnterveranstaltungDocument UnterveranstaltungDocument? + Persons Person[] + UnterveranstaltungLandingImages UnterveranstaltungLandingImages[] +} diff --git a/apps/api/prisma/schema/Gliederung.prisma b/apps/api/prisma/schema/Gliederung.prisma new file mode 100644 index 00000000..7478a2c5 --- /dev/null +++ b/apps/api/prisma/schema/Gliederung.prisma @@ -0,0 +1,8 @@ +model Gliederung { + id Int @id @default(autoincrement()) + name String + edv String @unique + unterveranstaltungen Unterveranstaltung[] + personen Person[] + GliederungToAccount GliederungToAccount[] +} diff --git a/apps/api/prisma/schema/GliederungToAccount.prisma b/apps/api/prisma/schema/GliederungToAccount.prisma new file mode 100644 index 00000000..f91d51a9 --- /dev/null +++ b/apps/api/prisma/schema/GliederungToAccount.prisma @@ -0,0 +1,16 @@ +enum GliederungAccountRole { + DELEGATIONSLEITER + BETREUER + TEILNEHMER +} + +model GliederungToAccount { + id Int @id @default(autoincrement()) + gliederungId Int + gliederung Gliederung @relation(fields: [gliederungId], references: [id], onDelete: Cascade) + accountId Int + account Account @relation(fields: [accountId], references: [id], onDelete: Cascade) + role GliederungAccountRole + + @@unique([gliederungId, accountId]) +} diff --git a/apps/api/prisma/schema/Hostname.prisma b/apps/api/prisma/schema/Hostname.prisma new file mode 100644 index 00000000..1ec2d477 --- /dev/null +++ b/apps/api/prisma/schema/Hostname.prisma @@ -0,0 +1,5 @@ +model Hostname { + id Int @id @default(autoincrement()) + hostname String + veranstaltung Veranstaltung[] +} diff --git a/apps/api/prisma/schema/Mahlzeit.prisma b/apps/api/prisma/schema/Mahlzeit.prisma new file mode 100644 index 00000000..31f56189 --- /dev/null +++ b/apps/api/prisma/schema/Mahlzeit.prisma @@ -0,0 +1,14 @@ +enum MahlzeitType { + FRUEHSTUECK + MITTAGESSEN + ABENDESSEN +} + +model Mahlzeit { + id Int @id @default(autoincrement()) + type MahlzeitType + date DateTime @db.Date + veranstaltungId Int + veranstaltung Veranstaltung @relation(fields: [veranstaltungId], references: [id], onDelete: Cascade) + anmeldung Anmeldung[] +} diff --git a/apps/api/prisma/schema/Person.prisma b/apps/api/prisma/schema/Person.prisma new file mode 100644 index 00000000..a35a6329 --- /dev/null +++ b/apps/api/prisma/schema/Person.prisma @@ -0,0 +1,55 @@ +//region Enums + +enum Gender { + MALE + FEMALE + UNSPECIFIED +} + +enum Essgewohnheit { + OMNIVOR + VEGETARISCH + VEGAN +} + +enum NahrungsmittelIntoleranz { + SCHWEIN + GLUTEN + LAKTOSE + FRUCTOSE +} + +//endregion + +model Person { + id Int @id @default(autoincrement()) + firstname String + lastname String + birthday DateTime? @db.Date + gender Gender? + email String + telefon String + anmeldungen Anmeldung[] + gliederungId Int? + gliederung Gliederung? @relation(fields: [gliederungId], references: [id], onDelete: SetNull) + account Account? + essgewohnheit Essgewohnheit? + nahrungsmittelIntoleranzen NahrungsmittelIntoleranz[] + weitereIntoleranzen String[] + notfallkontaktIds Int[] + notfallkontakte Notfallkontakt[] + addressId Int? + address Address? @relation(fields: [addressId], references: [id], onDelete: Cascade) + photoId String? + photo File? @relation(fields: [photoId], references: [id]) +} + +model Notfallkontakt { + id Int @id @default(autoincrement()) + firstname String + lastname String + telefon String + istErziehungsberechtigt Boolean @default(false) + personId Int + person Person @relation(fields: [personId], references: [id], onDelete: Cascade) +} diff --git a/apps/api/prisma/schema/PublicLanding.prisma b/apps/api/prisma/schema/PublicLanding.prisma new file mode 100644 index 00000000..ed359ab9 --- /dev/null +++ b/apps/api/prisma/schema/PublicLanding.prisma @@ -0,0 +1,46 @@ +model UnterveranstaltungLandingSettings { + unterveranstaltungId Int @id @default(autoincrement()) + unterveranstaltung Unterveranstaltung? + + heroTitle String + heroSubtitle String + heroImages UnterveranstaltungLandingImages[] + + eventDetailsTitle String + eventDetailsContent String + + miscellaneousVisible Boolean? + miscellaneousTitle String? + miscellaneousSubtitle String? + miscellaneousItems UnterveranstaltungLandingMiscellaneous[] + + faqVisible Boolean? + faqEmail String? + + instagramVisible Boolean? + instagramUrl String? + + facebookVisible Boolean? + facebookUrl String? +} + +model UnterveranstaltungLandingImages { + id Int @id @default(autoincrement()) + name String? + + UnterveranstaltungLandingSettings UnterveranstaltungLandingSettings? @relation(fields: [unterveranstaltungLandingSettingsId], references: [unterveranstaltungId]) + unterveranstaltungLandingSettingsId Int? + + file File @relation(fields: [fileId], references: [id]) + fileId String @unique +} + +model UnterveranstaltungLandingMiscellaneous { + id Int @id @default(autoincrement()) + + title String + content String + + UnterveranstaltungLandingSettings UnterveranstaltungLandingSettings? @relation(fields: [unterveranstaltungLandingSettingsId], references: [unterveranstaltungId]) + unterveranstaltungLandingSettingsId Int? +} diff --git a/apps/api/prisma/schema/Unterveranstaltung.prisma b/apps/api/prisma/schema/Unterveranstaltung.prisma new file mode 100644 index 00000000..63c4b192 --- /dev/null +++ b/apps/api/prisma/schema/Unterveranstaltung.prisma @@ -0,0 +1,38 @@ +enum UnterveranstaltungType { + CREW + GLIEDERUNG +} + +model Unterveranstaltung { + id Int @id @default(autoincrement()) + maxTeilnehmende Int + teilnahmegebuehr Int + meldebeginn DateTime + meldeschluss DateTime + veranstaltungId Int + veranstaltung Veranstaltung @relation(fields: [veranstaltungId], references: [id], onDelete: Cascade) + gliederungId Int + gliederung Gliederung @relation(fields: [gliederungId], references: [id], onDelete: Cascade) + Anmeldung Anmeldung[] + beschreibung String? + bedingungen String? + type UnterveranstaltungType @default(GLIEDERUNG) + customFields CustomField[] + documents UnterveranstaltungDocument[] + + faqs Faq[] + faqCategories FaqCategory[] + + landingSettings UnterveranstaltungLandingSettings? @relation(fields: [landingSettingsId], references: [unterveranstaltungId], onDelete: Cascade) + landingSettingsId Int? @unique +} + +model UnterveranstaltungDocument { + id Int @id @default(autoincrement()) + name String + description String? + unterveranstaltungId Int + unterveranstaltung Unterveranstaltung? @relation(fields: [unterveranstaltungId], references: [id], onDelete: Cascade) + file File @relation(fields: [fileId], references: [id]) + fileId String @unique +} diff --git a/apps/api/prisma/schema/Veranstaltung.prisma b/apps/api/prisma/schema/Veranstaltung.prisma new file mode 100644 index 00000000..6df0d30e --- /dev/null +++ b/apps/api/prisma/schema/Veranstaltung.prisma @@ -0,0 +1,22 @@ +model Veranstaltung { + id Int @id @default(autoincrement()) + name String + beginn DateTime @db.Date + ende DateTime @db.Date + meldebeginn DateTime + meldeschluss DateTime + ortId Int? + ort Ort? @relation(fields: [ortId], references: [id], onDelete: SetNull) + maxTeilnehmende Int + teilnahmegebuehr Int + unterveranstaltungen Unterveranstaltung[] + mahlzeiten Mahlzeit[] + beschreibung String? + datenschutz String? + teilnahmeBedingungen String? + teilnahmeBedingungenPublic String? + zielgruppe String? + hostnameId Int? + hostname Hostname? @relation(fields: [hostnameId], references: [id]) + customFields CustomField[] +} diff --git a/apps/api/prisma/schema/_main.prisma b/apps/api/prisma/schema/_main.prisma new file mode 100644 index 00000000..8ed44ec5 --- /dev/null +++ b/apps/api/prisma/schema/_main.prisma @@ -0,0 +1,9 @@ +generator client { + provider = "prisma-client-js" + previewFeatures = ["fullTextSearch", "prismaSchemaFolder"] +} + +datasource db { + provider = "postgres" + url = env("DATABASE_URL") +} diff --git a/api/prisma/seeders/account.ts b/apps/api/prisma/seeders/account.ts similarity index 71% rename from api/prisma/seeders/account.ts rename to apps/api/prisma/seeders/account.ts index c3e7bc54..1f090ee2 100644 --- a/api/prisma/seeders/account.ts +++ b/apps/api/prisma/seeders/account.ts @@ -1,13 +1,13 @@ -/* eslint-disable no-console */ -import { PrismaClient, AccountStatus } from '@prisma/client' +import { PrismaClient, AccountStatus, Role } from '@prisma/client' -import { logger } from '../../src/logger' -import logActivity from '../../src/util/activity' -import { isProduction } from '../../src/util/is-production' +import { logger } from '../../src/logger.js' +import logActivity from '../../src/util/activity.js' +import { isProduction } from '../../src/util/is-production.js' -import { Seeder } from '.' +import type { Seeder } from './index.js' import { hashPassword } from '@codeanker/authentication' +import { faker } from '@faker-js/faker' const createAccount: Seeder = async (prisma: PrismaClient) => { // create default user in development @@ -22,7 +22,7 @@ const createAccount: Seeder = async (prisma: PrismaClient) => { data: { email, password: await hashPassword(password), - role: 'ADMIN', + role: Role.ADMIN, status: AccountStatus.AKTIV, activatedAt: new Date(), person: { @@ -30,6 +30,7 @@ const createAccount: Seeder = async (prisma: PrismaClient) => { firstname: 'Gabi', lastname: 'Musterfrau', telefon: '+49 123 4567890', + birthday: faker.date.past(), email, }, }, diff --git a/api/prisma/seeders/anmeldungen.ts b/apps/api/prisma/seeders/anmeldungen.ts similarity index 54% rename from api/prisma/seeders/anmeldungen.ts rename to apps/api/prisma/seeders/anmeldungen.ts index 4a3164ff..c342c7b6 100644 --- a/api/prisma/seeders/anmeldungen.ts +++ b/apps/api/prisma/seeders/anmeldungen.ts @@ -3,17 +3,16 @@ import { AnmeldungStatus, Essgewohnheit, Gender, - Konfektionsgroesse, NahrungsmittelIntoleranz, PrismaClient, - Unterveranstaltung, + type Unterveranstaltung, } from '@prisma/client' -import logActivity from '../../src/util/activity' +import logActivity from '../../src/util/activity.js' -import { Seeder } from '.' +import type { Seeder } from './index.js' -const ENTRY_COUNT = 1000 +const ENTRY_COUNT = 100 faker.seed(123) @@ -23,7 +22,8 @@ async function create(prisma: PrismaClient, unterveranstaltung: Unterveranstaltu city: faker.location.city(), zip: faker.location.zipCode(), street: faker.location.street(), - number: faker.location.buildingNumber(), + streetNumber: faker.location.buildingNumber(), + country: 'DE', }, select: { id: true, @@ -37,39 +37,47 @@ async function create(prisma: PrismaClient, unterveranstaltung: Unterveranstaltu description: 'address created via db seeder', }) - const person = await prisma.person.create({ + const account = await prisma.account.create({ data: { - firstname: faker.person.firstName(), - lastname: faker.person.lastName(), - birthday: faker.date.birthdate({ min: 12, max: 30, mode: 'age' }), - gender: faker.helpers.enumValue(Gender), email: faker.internet.email(), - telefon: faker.string.numeric('+49151########'), - essgewohnheit: faker.helpers.enumValue(Essgewohnheit), - konfektionsgroesse: faker.helpers.enumValue(Konfektionsgroesse), - nahrungsmittelIntoleranzen: faker.helpers.arrayElements(Object.values(NahrungsmittelIntoleranz)), - addressId: address.id, - gliederungId: unterveranstaltung.gliederungId, - notfallkontakte: { - create: [ - { - firstname: faker.person.firstName(), - lastname: faker.person.lastName(), - telefon: faker.string.numeric('+49151########'), - istErziehungsberechtigt: faker.datatype.boolean(), + role: 'USER', + activatedAt: new Date(), + status: 'AKTIV', + person: { + create: { + firstname: faker.person.firstName(), + lastname: faker.person.lastName(), + birthday: faker.date.birthdate({ min: 12, max: 30, mode: 'age' }), + gender: faker.helpers.enumValue(Gender), + email: faker.internet.email(), + telefon: faker.string.numeric('+49151########'), + essgewohnheit: faker.helpers.enumValue(Essgewohnheit), + nahrungsmittelIntoleranzen: faker.helpers.arrayElements(Object.values(NahrungsmittelIntoleranz)), + addressId: address.id, + gliederungId: unterveranstaltung.gliederungId, + notfallkontakte: { + create: [ + { + firstname: faker.person.firstName(), + lastname: faker.person.lastName(), + telefon: faker.string.numeric('+49151########'), + istErziehungsberechtigt: faker.datatype.boolean(), + }, + ], }, - ], + }, }, }, select: { id: true, + personId: true, }, }) await logActivity({ type: 'CREATE', subjectType: 'person', - subjectId: person.id, + subjectId: account.id, description: 'person created via db seeder', }) @@ -77,8 +85,8 @@ async function create(prisma: PrismaClient, unterveranstaltung: Unterveranstaltu data: { unterveranstaltungId: unterveranstaltung.id, status: faker.helpers.enumValue(AnmeldungStatus), - tshirtBestellt: faker.datatype.boolean(), - personId: person.id, + personId: account.personId, + accountId: account.id, comment: faker.lorem.sentence(), }, select: { diff --git a/apps/api/prisma/seeders/customFields.ts b/apps/api/prisma/seeders/customFields.ts new file mode 100644 index 00000000..e69de29b diff --git a/api/prisma/seeders/gliederungen.ts b/apps/api/prisma/seeders/gliederungen.ts similarity index 99% rename from api/prisma/seeders/gliederungen.ts rename to apps/api/prisma/seeders/gliederungen.ts index 2c81687e..6dc81a81 100644 --- a/api/prisma/seeders/gliederungen.ts +++ b/apps/api/prisma/seeders/gliederungen.ts @@ -1,8 +1,8 @@ import { PrismaClient } from '@prisma/client' -import logActivity from '../../src/util/activity' +import logActivity from '../../src/util/activity.js' -import { Seeder } from '.' +import type { Seeder } from './index.js' export const gliederungen: { edv: string; name: string }[] = [ { edv: '1600000', name: 'DLRG Bundesverband' }, diff --git a/api/prisma/seeders/index.ts b/apps/api/prisma/seeders/index.ts similarity index 69% rename from api/prisma/seeders/index.ts rename to apps/api/prisma/seeders/index.ts index f424c955..e38c1a75 100644 --- a/api/prisma/seeders/index.ts +++ b/apps/api/prisma/seeders/index.ts @@ -1,14 +1,13 @@ -/* eslint-disable no-console */ import { PrismaClient } from '@prisma/client' import dayjs from 'dayjs' -import { logger } from '../../src/logger' -import { isProduction } from '../../src/util/is-production' +import { logger } from '../../src/logger.js' +import { isProduction } from '../../src/util/is-production.js' -import createAccount from './account' -import createAnmeldung from './anmeldungen' -import importGliederungen from './gliederungen' -import createVeranstaltung from './veranstaltung' +import createAccount from './account.js' +import createAnmeldung from './anmeldungen.js' +import importGliederungen from './gliederungen.js' +import createVeranstaltung from './veranstaltung.js' export type Seeder = (prisma: PrismaClient) => Promise diff --git a/apps/api/prisma/seeders/veranstaltung.ts b/apps/api/prisma/seeders/veranstaltung.ts new file mode 100644 index 00000000..0e69584a --- /dev/null +++ b/apps/api/prisma/seeders/veranstaltung.ts @@ -0,0 +1,135 @@ +import { fakerDE as faker } from '@faker-js/faker' +import { PrismaClient } from '@prisma/client' + +import logActivity from '../../src/util/activity.js' + +import type { Seeder } from './index.js' +import { dayjs } from '@codeanker/helpers' + +const createVeranstaltung: Seeder = async (prisma: PrismaClient) => { + const beginn = dayjs().add(1, 'month') + + const veranstaltung = await prisma.veranstaltung.create({ + data: { + name: 'Brahmsee 2024', + beginn: beginn.toDate(), + ende: beginn.add(2, 'day').toDate(), + meldebeginn: beginn.subtract(1, 'month').toDate(), + meldeschluss: beginn.subtract(1, 'week').toDate(), + maxTeilnehmende: faker.number.int({ min: 7, max: 50 }), + teilnahmegebuehr: faker.number.int({ min: 80, max: 110 }), + beschreibung: faker.lorem.text(), + datenschutz: faker.lorem.text(), + teilnahmeBedingungen: faker.lorem.text(), + teilnahmeBedingungenPublic: faker.lorem.text(), + zielgruppe: faker.lorem.text(), + hostname: { + create: { + hostname: 'localhost:8080', + }, + }, + ort: { + create: { + name: 'Waldheim am Brahmsee', + address: { + create: { + city: 'Langwedel', + zip: '24631', + streetNumber: '1', + street: 'Am Waldheim', + country: 'DE', + }, + }, + }, + }, + unterveranstaltungen: { + create: { + teilnahmegebuehr: 13.37, + maxTeilnehmende: 800, + meldebeginn: beginn.subtract(1, 'month').toDate(), + meldeschluss: beginn.subtract(1, 'week').toDate(), + beschreibung: faker.lorem.text(), + type: 'GLIEDERUNG', + gliederung: { + connect: { + edv: '1205013', + }, + }, + landingSettings: { + create: { + heroTitle: faker.lorem.sentence(), + heroSubtitle: faker.lorem.sentence(), + eventDetailsTitle: faker.lorem.sentence(), + eventDetailsContent: faker.lorem.paragraph(), + miscellaneousVisible: true, + miscellaneousTitle: faker.lorem.sentence(), + miscellaneousItems: { + create: Array(3) + .fill(0) + .map(() => ({ + title: faker.lorem.sentence(), + content: faker.lorem.paragraph(), + })), + }, + faqVisible: true, + faqEmail: faker.internet.email(), + + instagramVisible: true, + instagramUrl: 'https://www.instagram.com/dlrgjugendsh', + + facebookVisible: true, + facebookUrl: 'https://www.facebook.com/DLRGJugendSH/?locale=de_DE', + }, + }, + }, + }, + }, + select: { + id: true, + unterveranstaltungen: { + select: { + id: true, + }, + }, + }, + }) + + const faqCategories = await prisma.faqCategory.createManyAndReturn({ + data: Array(3) + .fill(0) + .map(() => ({ + name: faker.color.human(), + unterveranstaltungId: veranstaltung.unterveranstaltungen[0]!.id, + })), + }) + await prisma.faq.createMany({ + data: Array(9) + .fill(0) + .map(() => ({ + question: faker.commerce.productAdjective(), + answer: faker.commerce.productDescription(), + categoryId: faqCategories[Math.floor(Math.random() * faqCategories.length)]!.id, + })), + }) + + await prisma.customField.create({ + data: { + name: 'Rolle', + description: 'Dieses Feld wurde duch den Seeder erstellt', + type: 'BASIC_SELECT', + required: true, + options: ['Schwimmer:in', 'Teilnehmer:in'], + positions: ['PUBLIC_ANMELDUNG', 'INTERN_ANMELDUNG'], + veranstaltungId: veranstaltung.id, + }, + }) + + await logActivity({ + type: 'CREATE', + subjectType: 'veranstaltung', + subjectId: veranstaltung.id, + description: 'veranstaltung created via db seeder', + }) +} + +export default createVeranstaltung diff --git a/api/src/authentication.ts b/apps/api/src/authentication.ts similarity index 81% rename from api/src/authentication.ts rename to apps/api/src/authentication.ts index ce8048ea..c6e1639f 100644 --- a/api/src/authentication.ts +++ b/apps/api/src/authentication.ts @@ -1,5 +1,5 @@ -import config from './config' -import prisma from './prisma' +import config from './config.js' +import prisma from './prisma.js' import { createAuthentication } from '@codeanker/authentication' @@ -21,7 +21,8 @@ export const { getEntityIdFromHeader, authenticationLogin, sign } = createAuthen if (account === null) throw new Error('Es konnte kein Account gefunden werden.') if (account.activationToken !== null) throw new Error('Account noch nicht bestätigt, bitte bestätige deine E-Mail-Adresse.') - if (account.status !== 'AKTIV') throw new Error('Account ist nicht Aktiv.') + if (account.status !== 'AKTIV') + throw new Error('Dein Account ist noch nicht von einem Administrator freigeschaltet worden.') if (account.password === null) throw new Error('Account has no password') return { id: account.id, diff --git a/apps/api/src/azureStorage.ts b/apps/api/src/azureStorage.ts new file mode 100644 index 00000000..27b2d494 --- /dev/null +++ b/apps/api/src/azureStorage.ts @@ -0,0 +1,30 @@ +import { BlobServiceClient, StorageSharedKeyCredential } from '@azure/storage-blob' + +import config from './config.js' + +const isAzureConfigured = + config.fileProviders.AZURE.account !== '' && + config.fileProviders.AZURE.accountKey !== '' && + config.fileProviders.AZURE.container !== '' + +async function init() { + if (!isAzureConfigured) return null + const account = config.fileProviders.AZURE.account + const accountKey = config.fileProviders.AZURE.accountKey + const sharedKeyCredential = new StorageSharedKeyCredential(account, accountKey) + + const blobServiceClient = new BlobServiceClient(`https://${account}.blob.core.windows.net`, sharedKeyCredential) + + // Check if container exists, if not create it + const containerClient = blobServiceClient.getContainerClient(config.fileProviders.AZURE.container) + if (!(await containerClient.exists())) await containerClient.create() + return blobServiceClient +} + +export let azureStorage: BlobServiceClient | null = null + +init() + .then((blobServiceClient) => (azureStorage = blobServiceClient)) + .catch((e) => { + console.error('Failed to initialize Azure Blob Storage', e) + }) diff --git a/api/src/cli/generator/generateProcedureAction.ts b/apps/api/src/cli/generator/generateProcedureAction.ts similarity index 56% rename from api/src/cli/generator/generateProcedureAction.ts rename to apps/api/src/cli/generator/generateProcedureAction.ts index 319817f1..15284b89 100644 --- a/api/src/cli/generator/generateProcedureAction.ts +++ b/apps/api/src/cli/generator/generateProcedureAction.ts @@ -1,8 +1,8 @@ import { writeFile } from 'fs/promises' import path from 'path' -import { toPascalCase } from '../../util/casing' -import { checkFileExists } from '../../util/files' +import { toPascalCase } from '../../util/casing.js' +import { checkFileExists } from '../../util/files.js' import { type ProcedureOptions, @@ -10,7 +10,7 @@ import { addProcedureToRouter, getProtectionContent, type GeneratorContext, -} from './utlils' +} from './utlils.js' export async function generateProcedureAction(procedure: ProcedureOptions, context: GeneratorContext) { const sericeDir = path.join(context.servicesDir, procedure.service) @@ -25,20 +25,33 @@ export async function generateProcedureAction(procedure: ProcedureOptions, conte throw new Error(`Procedure ${procedureFileName} already exists`) } + let procedureFunction = 'defineProcedure' + let protectionContent = getProtectionContent(procedure.protection) + let roleIds = '' + + if (procedure.protection.type === 'restrictToRoleIds') { + procedureFunction = 'defineProtectedQueryProcedure' + roleIds = `roleIds: ${JSON.stringify(procedure.protection.roleIds)},` + protectionContent = '' + } else if (procedure.protection.type === 'public') { + procedureFunction = 'definePublicQueryProcedure' + protectionContent = '' + } + const content = `import z from 'zod' import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import { ${procedureFunction} } from '../../types/defineProcedure' -export const ${procedureFileName}Procedure = defineProcedure({ +export const ${procedureFileName}Procedure = ${procedureFunction}({ key: '${procedureAction}', method: '${procedureMethod}', - protection: ${getProtectionContent(procedure.protection)}, + ${roleIds} + protection: ${protectionContent}, inputSchema: z.undefined(), async handler(options) {}, }) ` - writeFile(procedurePath, content) - + await writeFile(procedurePath, content) await addProcedureToRouter(procedure, sericeDir, procedureType) } diff --git a/api/src/cli/generator/generateProcedureCreate.ts b/apps/api/src/cli/generator/generateProcedureCreate.ts similarity index 60% rename from api/src/cli/generator/generateProcedureCreate.ts rename to apps/api/src/cli/generator/generateProcedureCreate.ts index 83bf4db6..97d74fbe 100644 --- a/api/src/cli/generator/generateProcedureCreate.ts +++ b/apps/api/src/cli/generator/generateProcedureCreate.ts @@ -1,8 +1,8 @@ import { writeFile } from 'fs/promises' import path from 'path' -import { toPascalCase } from '../../util/casing' -import { checkFileExists } from '../../util/files' +import { toPascalCase } from '../../util/casing.js' +import { checkFileExists } from '../../util/files.js' import { type ProcedureOptions, @@ -10,7 +10,7 @@ import { addProcedureToRouter, getProtectionContent, type GeneratorContext, -} from './utlils' +} from './utlils.js' export async function generateProcedureCreate(procedure: ProcedureOptions, context: GeneratorContext) { const procedureType = 'create' @@ -26,15 +26,29 @@ export async function generateProcedureCreate(procedure: ProcedureOptions, conte throw new Error(`Procedure ${procedureFileName} already exists`) } + let procedureFunction = 'defineProcedure' + let protectionContent = getProtectionContent(procedure.protection) + let roleIds = '' + + if (procedure.protection.type === 'restrictToRoleIds') { + procedureFunction = 'defineProtectedQueryProcedure' + roleIds = `roleIds: ${JSON.stringify(procedure.protection.roleIds)},` + protectionContent = '' + } else if (procedure.protection.type === 'public') { + procedureFunction = 'definePublicQueryProcedure' + protectionContent = '' + } + const content = `import z from 'zod' import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import { ${procedureFunction} } from '../../types/defineProcedure' -export const ${procedureFileName}Procedure = defineProcedure({ +export const ${procedureFileName}Procedure = ${procedureFunction}({ key: '${procedureAction}', method: '${procedureMethod}', - protection: ${getProtectionContent(procedure.protection)}, + ${roleIds} + protection: ${protectionContent}, inputSchema: z.strictObject({ data: z.strictObject({}), }), @@ -48,7 +62,6 @@ export const ${procedureFileName}Procedure = defineProcedure({ }, }) ` - writeFile(procedurePath, content) - + await writeFile(procedurePath, content) await addProcedureToRouter(procedure, sericeDir, procedureType) } diff --git a/api/src/cli/generator/generateProcedureDelete.ts b/apps/api/src/cli/generator/generateProcedureDelete.ts similarity index 60% rename from api/src/cli/generator/generateProcedureDelete.ts rename to apps/api/src/cli/generator/generateProcedureDelete.ts index d9e88dac..53d2ae2d 100644 --- a/api/src/cli/generator/generateProcedureDelete.ts +++ b/apps/api/src/cli/generator/generateProcedureDelete.ts @@ -1,8 +1,8 @@ import { writeFile } from 'fs/promises' import path from 'path' -import { toPascalCase } from '../../util/casing' -import { checkFileExists } from '../../util/files' +import { toPascalCase } from '../../util/casing.js' +import { checkFileExists } from '../../util/files.js' import { type ProcedureOptions, @@ -10,7 +10,7 @@ import { addProcedureToRouter, getProtectionContent, type GeneratorContext, -} from './utlils' +} from './utlils.js' export async function generateProcedureDelete(procedure: ProcedureOptions, context: GeneratorContext) { const sericeDir = path.join(context.servicesDir, procedure.service) @@ -25,15 +25,29 @@ export async function generateProcedureDelete(procedure: ProcedureOptions, conte throw new Error(`Procedure ${procedureFileName} already exists`) } + let procedureFunction = 'defineProcedure' + let protectionContent = getProtectionContent(procedure.protection) + let roleIds = '' + + if (procedure.protection.type === 'restrictToRoleIds') { + procedureFunction = 'defineProtectedQueryProcedure' + roleIds = `roleIds: ${JSON.stringify(procedure.protection.roleIds)},` + protectionContent = '' + } else if (procedure.protection.type === 'public') { + procedureFunction = 'definePublicQueryProcedure' + protectionContent = '' + } + const content = `import z from 'zod' import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import { ${procedureFunction} } from '../../types/defineProcedure' -export const ${procedureFileName}Procedure = defineProcedure({ +export const ${procedureFileName}Procedure = ${procedureFunction}({ key: '${procedureAction}', method: '${procedureMethod}', - protection: ${getProtectionContent(procedure.protection)}, + ${roleIds} + protection: ${protectionContent}, inputSchema: z.strictObject({ id: z.number().int(), }), @@ -49,7 +63,6 @@ export const ${procedureFileName}Procedure = defineProcedure({ }, }) ` - writeFile(procedurePath, content) - + await writeFile(procedurePath, content) await addProcedureToRouter(procedure, sericeDir, procedureType) } diff --git a/api/src/cli/generator/generateProcedureGet.ts b/apps/api/src/cli/generator/generateProcedureGet.ts similarity index 60% rename from api/src/cli/generator/generateProcedureGet.ts rename to apps/api/src/cli/generator/generateProcedureGet.ts index dcb6f99b..83998180 100644 --- a/api/src/cli/generator/generateProcedureGet.ts +++ b/apps/api/src/cli/generator/generateProcedureGet.ts @@ -1,8 +1,8 @@ import { writeFile } from 'fs/promises' import path from 'path' -import { toPascalCase } from '../../util/casing' -import { checkFileExists } from '../../util/files' +import { toPascalCase } from '../../util/casing.js' +import { checkFileExists } from '../../util/files.js' import { type ProcedureOptions, @@ -10,7 +10,7 @@ import { addProcedureToRouter, getProtectionContent, type GeneratorContext, -} from './utlils' +} from './utlils.js' export async function generateProcedureGet(procedure: ProcedureOptions, context: GeneratorContext) { const sericeDir = path.join(context.servicesDir, procedure.service) @@ -25,15 +25,29 @@ export async function generateProcedureGet(procedure: ProcedureOptions, context: throw new Error(`Procedure ${procedureFileName} already exists`) } + let procedureFunction = 'defineProcedure' + let protectionContent = getProtectionContent(procedure.protection) + let roleIds = '' + + if (procedure.protection.type === 'restrictToRoleIds') { + procedureFunction = 'defineProtectedQueryProcedure' + roleIds = `roleIds: ${JSON.stringify(procedure.protection.roleIds)},` + protectionContent = '' + } else if (procedure.protection.type === 'public') { + procedureFunction = 'definePublicQueryProcedure' + protectionContent = '' + } + const content = `import z from 'zod' import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import { ${procedureFunction} } from '../../types/defineProcedure' -export const ${procedureFileName}Procedure = defineProcedure({ +export const ${procedureFileName}Procedure = ${procedureFunction}({ key: '${procedureAction}', method: '${procedureMethod}', - protection: ${getProtectionContent(procedure.protection)}, + ${roleIds} + protection: ${protectionContent}, inputSchema: z.strictObject({ id: z.number().int(), }), @@ -49,7 +63,6 @@ export const ${procedureFileName}Procedure = defineProcedure({ }, }) ` - writeFile(procedurePath, content) - + await writeFile(procedurePath, content) await addProcedureToRouter(procedure, sericeDir, procedureType) } diff --git a/api/src/cli/generator/generateProcedureList.ts b/apps/api/src/cli/generator/generateProcedureList.ts similarity index 60% rename from api/src/cli/generator/generateProcedureList.ts rename to apps/api/src/cli/generator/generateProcedureList.ts index c32026f0..ec4a0a43 100644 --- a/api/src/cli/generator/generateProcedureList.ts +++ b/apps/api/src/cli/generator/generateProcedureList.ts @@ -1,8 +1,8 @@ import { writeFile } from 'fs/promises' import path from 'path' -import { toPascalCase } from '../../util/casing' -import { checkFileExists } from '../../util/files' +import { toPascalCase } from '../../util/casing.js' +import { checkFileExists } from '../../util/files.js' import { type ProcedureOptions, @@ -10,7 +10,7 @@ import { addListProcedureToRouter, getProtectionContent, type GeneratorContext, -} from './utlils' +} from './utlils.js' export async function generateProcedureList(procedure: ProcedureOptions, context: GeneratorContext) { const sericeDir = path.join(context.servicesDir, procedure.service) @@ -27,7 +27,7 @@ export async function generateProcedureList(procedure: ProcedureOptions, context import z from 'zod' import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import { ${procedure.protection.type === 'restrictToRoleIds' ? 'defineProtectedQueryProcedure' : 'definePublicQueryProcedure'} } from '../../types/defineProcedure' import { defineQuery } from '../../types/defineQuery' const inputSchema = defineQuery({ @@ -40,10 +40,10 @@ function getFilterWhere(filter: z.infer['filter']): Prisma.$ return filter } -export const ${procedureFileName}Procedure = defineProcedure({ +export const ${procedureFileName}Procedure = ${procedure.protection.type === 'restrictToRoleIds' ? 'defineProtectedQueryProcedure' : 'definePublicQueryProcedure'}({ key: '${procedureAction}', - method: 'query', - protection: ${getProtectionContent(procedure.protection)}, + ${procedure.protection.type === 'restrictToRoleIds' ? `roleIds: ${JSON.stringify(procedure.protection.roleIds)},` : ''} + protection: ${procedure.protection.type === 'restrictToRoleIds' ? '' : getProtectionContent(procedure.protection)}, inputSchema, async handler(options) { const { skip, take } = options.input.pagination @@ -59,10 +59,10 @@ export const ${procedureFileName}Procedure = defineProcedure({ }, }) -export const ${procedureFileName}CountProcedure = defineProcedure({ +export const ${procedureFileName}CountProcedure = ${procedure.protection.type === 'restrictToRoleIds' ? 'defineProtectedQueryProcedure' : 'definePublicQueryProcedure'}({ key: '${procedureAction}Count', - method: 'query', - protection: ${getProtectionContent(procedure.protection)}, + ${procedure.protection.type === 'restrictToRoleIds' ? `roleIds: ${JSON.stringify(procedure.protection.roleIds)},` : ''} + protection: ${procedure.protection.type === 'restrictToRoleIds' ? '' : getProtectionContent(procedure.protection)}, inputSchema, async handler(options) { return await prisma.${procedure.service}.count({ @@ -71,7 +71,6 @@ export const ${procedureFileName}CountProcedure = defineProcedure({ }, }) ` - writeFile(procedurePath, content) - + await writeFile(procedurePath, content) await addListProcedureToRouter(procedure, sericeDir, procedureType) } diff --git a/api/src/cli/generator/generateProcedurePatch.ts b/apps/api/src/cli/generator/generateProcedurePatch.ts similarity index 61% rename from api/src/cli/generator/generateProcedurePatch.ts rename to apps/api/src/cli/generator/generateProcedurePatch.ts index fd02062c..168325ca 100644 --- a/api/src/cli/generator/generateProcedurePatch.ts +++ b/apps/api/src/cli/generator/generateProcedurePatch.ts @@ -1,8 +1,8 @@ import { writeFile } from 'fs/promises' import path from 'path' -import { toPascalCase } from '../../util/casing' -import { checkFileExists } from '../../util/files' +import { toPascalCase } from '../../util/casing.js' +import { checkFileExists } from '../../util/files.js' import { type ProcedureOptions, @@ -10,7 +10,7 @@ import { addProcedureToRouter, getProtectionContent, type GeneratorContext, -} from './utlils' +} from './utlils.js' export async function generateProcedurePatch(procedure: ProcedureOptions, context: GeneratorContext) { const procedureType = 'patch' @@ -25,15 +25,30 @@ export async function generateProcedurePatch(procedure: ProcedureOptions, contex if (alreadyExists) { throw new Error(`Procedure ${procedureFileName} already exists`) } + + let procedureFunction = 'defineProcedure' + let protectionContent = getProtectionContent(procedure.protection) + let roleIds = '' + + if (procedure.protection.type === 'restrictToRoleIds') { + procedureFunction = 'defineProtectedQueryProcedure' + roleIds = `roleIds: ${JSON.stringify(procedure.protection.roleIds)},` + protectionContent = '' + } else if (procedure.protection.type === 'public') { + procedureFunction = 'definePublicQueryProcedure' + protectionContent = '' + } + const content = `import z from 'zod' import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import { ${procedureFunction} } from '../../types/defineProcedure' -export const ${procedureFileName}Procedure = defineProcedure({ +export const ${procedureFileName}Procedure = ${procedureFunction}({ key: '${procedureAction}', method: '${procedureMethod}', - protection: ${getProtectionContent(procedure.protection)}, + ${roleIds} + protection: ${protectionContent}, inputSchema: z.strictObject({ id: z.number().int(), data: z.strictObject({}), @@ -51,7 +66,6 @@ export const ${procedureFileName}Procedure = defineProcedure({ }, }) ` - writeFile(procedurePath, content) - + await writeFile(procedurePath, content) await addProcedureToRouter(procedure, sericeDir, procedureType) } diff --git a/api/src/cli/generator/generateService.ts b/apps/api/src/cli/generator/generateService.ts similarity index 82% rename from api/src/cli/generator/generateService.ts rename to apps/api/src/cli/generator/generateService.ts index 367171ed..8ece3e55 100644 --- a/api/src/cli/generator/generateService.ts +++ b/apps/api/src/cli/generator/generateService.ts @@ -1,15 +1,15 @@ import { mkdir, readFile, writeFile } from 'fs/promises' import path from 'path' -import { checkFileExists } from '../../util/files' +import { checkFileExists } from '../../util/files.js' -import { applyInserts, type GeneratorContext } from './utlils' +import { applyInserts, type GeneratorContext } from './utlils.js' export async function generateService(name: string, context: GeneratorContext) { const sericeDir = path.join(context.servicesDir, name) const servicePath = path.join(sericeDir, `${name}.router.ts`) - const content = `/* eslint-disable prettier/prettier */ // Prettier ignored is because this file is generated + const content = `// Prettier ignored is because this file is generated import { mergeRouters } from '../../trpc' // Import Routes here - do not delete this line @@ -39,5 +39,5 @@ export const ${name}Router = mergeRouters( const indexPath = path.join(context.servicesDir, 'index.ts') const currentContent = await readFile(indexPath, 'utf-8') const newContent = applyInserts(currentContent.split('\n'), inserts).join('\n') - writeFile(indexPath, newContent) + await writeFile(indexPath, newContent) } diff --git a/api/src/cli/generator/utlils.ts b/apps/api/src/cli/generator/utlils.ts similarity index 98% rename from api/src/cli/generator/utlils.ts rename to apps/api/src/cli/generator/utlils.ts index a0a23f97..dda3a557 100644 --- a/api/src/cli/generator/utlils.ts +++ b/apps/api/src/cli/generator/utlils.ts @@ -1,7 +1,7 @@ import { readFile, writeFile } from 'fs/promises' import path from 'path' -import { toPascalCase } from '../../util/casing' +import { toPascalCase } from '../../util/casing.js' export type GeneratorContext = { servicesDir: string diff --git a/api/src/cli/ignoreList.ts b/apps/api/src/cli/ignoreList.ts similarity index 100% rename from api/src/cli/ignoreList.ts rename to apps/api/src/cli/ignoreList.ts diff --git a/api/src/cli/index.ts b/apps/api/src/cli/index.ts similarity index 67% rename from api/src/cli/index.ts rename to apps/api/src/cli/index.ts index ee0d0f6a..17f3f7a3 100644 --- a/api/src/cli/index.ts +++ b/apps/api/src/cli/index.ts @@ -4,15 +4,15 @@ import { fileURLToPath } from 'url' import { Prisma } from '@prisma/client' import { Command } from 'commander' -import pkg from '../../../package.json' -import { pascalToCamelCase } from '../util/casing' -import { getDirectories } from '../util/files' +import pkg from '../../../../package.json' assert { type: 'json' } +import { pascalToCamelCase } from '../util/casing.js' +import { getDirectories } from '../util/files.js' -import { generateService } from './generator/generateService' -import type { GeneratorContext } from './generator/utlils' -import { ignoreList } from './ignoreList' -import { inquireGenerateProcedure } from './inquireGenerateProcedure' -import { inquireGenerateService } from './inquireGenerateService' +import { generateService } from './generator/generateService.js' +import type { GeneratorContext } from './generator/utlils.js' +import { ignoreList } from './ignoreList.js' +import { inquireGenerateProcedure, type ProcedureArgs } from './inquireGenerateProcedure.js' +import { inquireGenerateService } from './inquireGenerateService.js' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) @@ -38,11 +38,11 @@ program .command('service') .description('Generate a service') .option('-n, --name ', 'Service name') - .action(async ({ name }) => { + .action(async ({ name }: { name: string }) => { if (name) { await generateService(name, context) } else { - inquireGenerateService(missingServices, context) + await inquireGenerateService(missingServices, context) } }) @@ -53,6 +53,6 @@ program .option('-u, --usecase ', 'Usecase') .option('-a, --action ', 'Action') .option('-p, --protection ', 'Protection type: public | restrictToRoleIds=ADMIN,USER') - .action((args) => inquireGenerateProcedure(args, context, existingServices)) + .action((args: ProcedureArgs) => inquireGenerateProcedure(args, context, existingServices)) program.parse(process.argv) diff --git a/api/src/cli/inquireGenerateProcedure.ts b/apps/api/src/cli/inquireGenerateProcedure.ts similarity index 85% rename from api/src/cli/inquireGenerateProcedure.ts rename to apps/api/src/cli/inquireGenerateProcedure.ts index bfc71de5..e46176de 100644 --- a/api/src/cli/inquireGenerateProcedure.ts +++ b/apps/api/src/cli/inquireGenerateProcedure.ts @@ -1,15 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ import inquirer from 'inquirer' -import { generateProcedureAction } from './generator/generateProcedureAction' -import { generateProcedureCreate } from './generator/generateProcedureCreate' -import { generateProcedureDelete } from './generator/generateProcedureDelete' -import { generateProcedureGet } from './generator/generateProcedureGet' -import { generateProcedureList } from './generator/generateProcedureList' -import { generateProcedurePatch } from './generator/generateProcedurePatch' -import type { GenerateProcedureType, GeneratorContext } from './generator/utlils' -import { inquireProtection } from './inquireProtection' +import { generateProcedureAction } from './generator/generateProcedureAction.js' +import { generateProcedureCreate } from './generator/generateProcedureCreate.js' +import { generateProcedureDelete } from './generator/generateProcedureDelete.js' +import { generateProcedureGet } from './generator/generateProcedureGet.js' +import { generateProcedureList } from './generator/generateProcedureList.js' +import { generateProcedurePatch } from './generator/generateProcedurePatch.js' +import type { GenerateProcedureType, GeneratorContext } from './generator/utlils.js' +import { inquireProtection } from './inquireProtection.js' -type ProcedureArgs = { service?: string; usecase?: string; action?: GenerateProcedureType; protection?: string } +export type ProcedureArgs = { service?: string; usecase?: string; action?: GenerateProcedureType; protection?: string } export async function inquireGenerateProcedure(args: ProcedureArgs, context: GeneratorContext, services: string[]) { let service if (args.service !== undefined) { @@ -68,7 +69,9 @@ export async function inquireGenerateProcedure(args: ProcedureArgs, context: Gen type: 'public', } } else if (args.protection.startsWith('restrictToRoleIds')) { - const roleIds = args.protection.split('=')[1].split(',') + const roles = args.protection.split('=') + const commaSeperatedRoles = roles[1] as string + const roleIds = commaSeperatedRoles.split(',') protection = { type: 'restrictToRoleIds', roleIds, diff --git a/api/src/cli/inquireGenerateService.ts b/apps/api/src/cli/inquireGenerateService.ts similarity index 82% rename from api/src/cli/inquireGenerateService.ts rename to apps/api/src/cli/inquireGenerateService.ts index 82fe5f4f..a2c884de 100644 --- a/api/src/cli/inquireGenerateService.ts +++ b/apps/api/src/cli/inquireGenerateService.ts @@ -1,6 +1,9 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import inquirer from 'inquirer' -import { generateService } from './generator/generateService' +import { generateService } from './generator/generateService.js' export async function inquireGenerateService(missingServices, context) { const prismOrCustomAnswer = (await inquirer.prompt([ @@ -14,7 +17,6 @@ export async function inquireGenerateService(missingServices, context) { if (prismOrCustomAnswer.serviceType === 'prisma') { if (missingServices.length === 0) { - // eslint-disable-next-line no-console console.log('No services to create') process.exit(0) } diff --git a/api/src/cli/inquireProtection.ts b/apps/api/src/cli/inquireProtection.ts similarity index 94% rename from api/src/cli/inquireProtection.ts rename to apps/api/src/cli/inquireProtection.ts index f640337d..a0eb36a2 100644 --- a/api/src/cli/inquireProtection.ts +++ b/apps/api/src/cli/inquireProtection.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import { Role } from '@prisma/client' import inquirer from 'inquirer' diff --git a/api/src/client.ts b/apps/api/src/client.ts similarity index 66% rename from api/src/client.ts rename to apps/api/src/client.ts index 86345e5c..35646dcf 100644 --- a/api/src/client.ts +++ b/apps/api/src/client.ts @@ -1,11 +1,15 @@ import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server' -import type { appRouter } from './index' +import type { appRouter } from './index.js' + +export * from '@prisma/client' -export * from './enumMappings/index' export type AppRouter = typeof appRouter -export type { TKontaktSchema } from './services/kontakt/schema/kontakt.schema' -export type { TAccountSchema } from './services/account/schema/account.schema' -export type { TPersonSchema } from './services/person/schema/person.schema' +export type { TKontaktSchema } from './services/kontakt/schema/kontakt.schema.js' +export type { TAccountSchema } from './services/account/schema/account.schema.js' +export type { TPersonSchema } from './services/person/schema/person.schema.js' export type RouterInput = inferRouterInputs export type RouterOutput = inferRouterOutputs + +export * from './types/enums/index.js' +export * from './types/enums/mappings/index.js' diff --git a/api/src/config.ts b/apps/api/src/config.ts similarity index 62% rename from api/src/config.ts rename to apps/api/src/config.ts index 23cb3451..f2db9a09 100644 --- a/api/src/config.ts +++ b/apps/api/src/config.ts @@ -1,12 +1,14 @@ import path from 'node:path' import { fileURLToPath } from 'url' +import { FileProvider } from '@prisma/client' import config from 'config' import { z } from 'zod' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const baseConfig = config.util.loadFileConfigs(path.join(__dirname, '..', 'config')) export const configSchema = z.strictObject({ @@ -43,6 +45,28 @@ export const configSchema = z.strictObject({ host: z.string(), apiKey: z.string(), }), + fileDefaultProvider: z.nativeEnum(FileProvider), + fileProviders: z.strictObject({ + LOCAL: z.strictObject({ + path: z.string(), + }), + AZURE: z.strictObject({ + account: z.string(), + accountKey: z.string(), + container: z.string(), + folder: z.string(), + }), + }), + + tomtom: z.strictObject({ + apiKey: z.string(), + }), + public: z.strictObject({ + legal: z.strictObject({ + imprint: z.string().url(), + privacy: z.string().url(), + }), + }), }) export default configSchema.parse(baseConfig) diff --git a/apps/api/src/context.ts b/apps/api/src/context.ts new file mode 100644 index 00000000..bce50cf7 --- /dev/null +++ b/apps/api/src/context.ts @@ -0,0 +1,69 @@ +import type { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch' +import type { CreateTrpcKoaContextOptions } from 'trpc-koa-adapter' + +import { getEntityIdFromHeader } from './authentication.js' +import { logger } from './logger.js' +import client from './prisma.js' +import type { Account } from '@prisma/client' + +function getAuthorizationHeader( + headers: CreateTrpcKoaContextOptions['req']['headers'] | FetchCreateContextFnOptions['req']['headers'] +) { + if ('authorization' in headers && typeof headers['authorization'] === 'string') { + return headers['authorization'] + } else { + return (headers as FetchCreateContextFnOptions['req']['headers']).get('authorization') + } +} + +export async function createContext({ + req, +}: CreateTrpcKoaContextOptions | FetchCreateContextFnOptions): Promise { + try { + const authorization = getAuthorizationHeader(req.headers) + if (authorization === null) throw new Error('No authorization header found.') + + const accountIdFromHeader = getEntityIdFromHeader(authorization) + if (accountIdFromHeader === undefined) { + return { + authenticated: false, + account: undefined, + accountId: undefined, + } + } + + const accountId = parseInt(accountIdFromHeader) + const account = await client.account.findFirstOrThrow({ + where: { + id: accountId, + }, + }) + + return { + authenticated: true, + accountId, + account, + } + } catch (error) { + if (error instanceof Error) { + logger.error(error.message) + } else { + logger.error('An unknown error occurred') + } + throw error + } +} + +type AuthContext = + | { + authenticated: false + accountId: undefined + account: undefined + } + | { + authenticated: true + accountId: number + account: Account + } + +export type Context = AuthContext diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts new file mode 100644 index 00000000..efad3b30 --- /dev/null +++ b/apps/api/src/index.ts @@ -0,0 +1,4 @@ +import { serviceRouter } from './services/index.js' +import { mergeRouters } from './trpc.js' + +export const appRouter = mergeRouters(serviceRouter) diff --git a/api/src/logger.ts b/apps/api/src/logger.ts similarity index 76% rename from api/src/logger.ts rename to apps/api/src/logger.ts index dfe5f9a0..df4e19d9 100644 --- a/api/src/logger.ts +++ b/apps/api/src/logger.ts @@ -1,10 +1,10 @@ import { createLogger, format, transports } from 'winston' -import config from './config' +import config from './config.js' const myFormat = format.printf(({ level, message, label, timestamp, metadata }) => { - const base = `${timestamp} [${label}] ${level} >> ${message}` - if (Object.keys(metadata).length === 0) { + const base = `${timestamp as string} [${label as string}] ${level} >> ${message as string}` + if (Object.keys(metadata as Record).length === 0) { return base } diff --git a/api/src/meilisearch/index.ts b/apps/api/src/meilisearch/index.ts similarity index 94% rename from api/src/meilisearch/index.ts rename to apps/api/src/meilisearch/index.ts index ac62b6f8..53512e48 100644 --- a/api/src/meilisearch/index.ts +++ b/apps/api/src/meilisearch/index.ts @@ -1,6 +1,6 @@ import { MeiliSearch } from 'meilisearch' -import config from '.././config' +import config from '.././config.js' /** * Initalisiert Meilisearch diff --git a/api/src/meilisearch/person.ts b/apps/api/src/meilisearch/person.ts similarity index 72% rename from api/src/meilisearch/person.ts rename to apps/api/src/meilisearch/person.ts index fba93bc4..f45d9b9e 100644 --- a/api/src/meilisearch/person.ts +++ b/apps/api/src/meilisearch/person.ts @@ -1,6 +1,6 @@ -import prisma from '../prisma' +import prisma from '../prisma.js' -import { meilisearchClient, updateSettings } from './index' +import { meilisearchClient, updateSettings } from './index.js' const searchIndex = 'person' @@ -28,17 +28,15 @@ export async function syncAllPersonsToMeili() { await meilisearchClient.index(searchIndex).updateSettings(updateSettings) await meilisearchClient.updateIndex(searchIndex, { primaryKey: 'id' }) - let cursorValue + let cursorValue: number | undefined const batchSize = 1000 do { + const skip: number = cursorValue ? 1 : 0 + const cursor: { id: number } | undefined = cursorValue ? { id: cursorValue } : undefined const persons = await prisma.person.findMany({ take: batchSize, - skip: cursorValue ? 1 : 0, - cursor: cursorValue - ? { - id: cursorValue, - } - : undefined, + skip, + cursor, select: { id: true, firstname: true, @@ -54,7 +52,7 @@ export async function syncAllPersonsToMeili() { }, }) await meilisearchClient.index(searchIndex).addDocuments(persons) - if (persons.length < batchSize) cursorValue = null - else cursorValue = persons[persons.length - 1].id + if (persons.length < batchSize) cursorValue = undefined + else cursorValue = persons[persons.length - 1]?.id } while (cursorValue) } diff --git a/api/src/metrics.ts b/apps/api/src/metrics.ts similarity index 100% rename from api/src/metrics.ts rename to apps/api/src/metrics.ts diff --git a/api/src/middleware/cache-control.ts b/apps/api/src/middleware/cache-control.ts similarity index 95% rename from api/src/middleware/cache-control.ts rename to apps/api/src/middleware/cache-control.ts index a52f706b..55d8cfda 100644 --- a/api/src/middleware/cache-control.ts +++ b/apps/api/src/middleware/cache-control.ts @@ -15,7 +15,7 @@ const cacheControl: Middleware = async (ctx, next) => { ctx.set('cache-control', 'max-age: 31536000, immutable') // 1 week } - return await next() + await next() } export default cacheControl diff --git a/api/src/prisma.ts b/apps/api/src/prisma.ts similarity index 89% rename from api/src/prisma.ts rename to apps/api/src/prisma.ts index dd5dc5fd..375ea946 100644 --- a/api/src/prisma.ts +++ b/apps/api/src/prisma.ts @@ -5,8 +5,8 @@ // import config from 'config' import { PrismaClient } from '@prisma/client' -import config from './config' -import { logger } from './logger' +import config from './config.js' +import { logger } from './logger.js' const log = logger.child({ label: 'db' }) diff --git a/api/src/routes/connect.ts b/apps/api/src/routes/connect.ts similarity index 87% rename from api/src/routes/connect.ts rename to apps/api/src/routes/connect.ts index cc8faccc..99fa967a 100644 --- a/api/src/routes/connect.ts +++ b/apps/api/src/routes/connect.ts @@ -2,9 +2,9 @@ import jwt from 'jsonwebtoken' import type { Context } from 'koa' import { z } from 'zod' -import { sign } from '../authentication' -import config from '../config' -import prisma from '../prisma' +import { sign } from '../authentication.js' +import config from '../config.js' +import prisma from '../prisma.js' const ZUserInfoReponse = z.object({ access_token: z.string(), @@ -32,10 +32,11 @@ export const ZOauthRegisterJwtPayloadSchema = z.object({ type TOauthRegisterJwtPayloadSchema = z.infer export default async function (ctx: Context) { - const userInfoResponse = ZUserInfoReponse.parse(ctx.session.grant.response) + const session = ctx.session as { grant: { response: unknown; dynamic: { mode: unknown } } } + const userInfoResponse = ZUserInfoReponse.parse(session.grant.response) const profile = userInfoResponse.profile - const mode = ZOauthMode.parse(ctx.session.grant.dynamic.mode) + const mode = ZOauthMode.parse(session.grant.dynamic.mode) if (mode === 'register') { const payload: TOauthRegisterJwtPayloadSchema = { diff --git a/api/src/exports/helpers/getSecurityWorksheet.ts b/apps/api/src/routes/exports/helpers/getSecurityWorksheet.ts similarity index 89% rename from api/src/exports/helpers/getSecurityWorksheet.ts rename to apps/api/src/routes/exports/helpers/getSecurityWorksheet.ts index 152b675d..415ade1c 100644 --- a/api/src/exports/helpers/getSecurityWorksheet.ts +++ b/apps/api/src/routes/exports/helpers/getSecurityWorksheet.ts @@ -1,4 +1,5 @@ import dayjs from 'dayjs' +import XLSX from '@e965/xlsx' interface exportAccount { person: { @@ -7,7 +8,7 @@ interface exportAccount { } } -export function getSecurityWorksheet(XLSX, account: exportAccount, countDataEntries: number) { +export function getSecurityWorksheet(account: exportAccount, countDataEntries: number) { const securityWorksheet = XLSX.utils.json_to_sheet([ { '': 'Sicherheit' }, { '': '' }, diff --git a/api/src/exports/helpers/getWorkbookDefaultProps.ts b/apps/api/src/routes/exports/helpers/getWorkbookDefaultProps.ts similarity index 100% rename from api/src/exports/helpers/getWorkbookDefaultProps.ts rename to apps/api/src/routes/exports/helpers/getWorkbookDefaultProps.ts diff --git a/apps/api/src/routes/exports/sheets/sheets.schema.ts b/apps/api/src/routes/exports/sheets/sheets.schema.ts new file mode 100644 index 00000000..30d0622d --- /dev/null +++ b/apps/api/src/routes/exports/sheets/sheets.schema.ts @@ -0,0 +1,66 @@ +import { Role, type Gliederung } from '@prisma/client' +import type { Context } from 'koa' +import { z } from 'zod' +import { getEntityIdFromHeader } from '../../../authentication.js' +import prisma from '../../../prisma.js' +import { getGliederungRequireAdmin } from '../../../util/getGliederungRequireAdmin.js' +import { zodSafe } from '../../../util/zod.js' + +export const sheetQuerySchema = z + .object({ + jwt: z.string(), + veranstaltungId: z.coerce.number().min(0).optional(), + unterveranstaltungId: z.coerce.number().min(0).optional(), + }) + .refine((data) => !!data.veranstaltungId || !!data.unterveranstaltungId, { + message: 'Exactly one of veranstaltungId or unterveranstaltungId must be provided', + }) + +export async function sheetAuthorize(ctx: Context) { + const [success, query] = await zodSafe(sheetQuerySchema, ctx.query) + if (!success) { + ctx.response.status = 400 + return false + } + + const accountId = getEntityIdFromHeader(`Bearer ${query.jwt}`) + if (typeof accountId !== 'string') { + ctx.response.status = 401 + return false + } + + const accountIdNumber = parseInt(accountId) + + const account = await prisma.account.findUnique({ + where: { + id: accountIdNumber, + }, + select: { + role: true, + person: { + select: { + firstname: true, + lastname: true, + }, + }, + }, + }) + + if (account == null) { + ctx.res.statusCode = 401 + return false + } + + let gliederung: Gliederung | undefined = undefined + if (account.role == Role.GLIEDERUNG_ADMIN) { + try { + gliederung = await getGliederungRequireAdmin(accountIdNumber) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + ctx.res.statusCode = 401 + return false + } + } + + return { query, account, gliederung } +} diff --git a/api/src/exports/sheets/teilnehmendenliste.ts b/apps/api/src/routes/exports/sheets/teilnehmendenliste.ts similarity index 68% rename from api/src/exports/sheets/teilnehmendenliste.ts rename to apps/api/src/routes/exports/sheets/teilnehmendenliste.ts index 48d89e43..933efb05 100644 --- a/api/src/exports/sheets/teilnehmendenliste.ts +++ b/apps/api/src/routes/exports/sheets/teilnehmendenliste.ts @@ -1,66 +1,28 @@ -import { Role } from '@prisma/client' import dayjs from 'dayjs' -import XLSX from 'xlsx' +import type { Context } from 'koa' +import XLSX from '@e965/xlsx' +import { AnmeldungStatusMapping, GenderMapping } from '../../../client.js' +import prisma from '../../../prisma.js' +import { getSecurityWorksheet } from '../helpers/getSecurityWorksheet.js' +import { sheetAuthorize } from './sheets.schema.js' -import { getEntityIdFromHeader } from '../../authentication' -import { AnmeldungStatusMapping, GenderMapping } from '../../enumMappings' -import prisma from '../../prisma' -import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin' -import { getSecurityWorksheet } from '../helpers/getSecurityWorksheet' - -export async function veranstaltungTeilnehmendenliste(ctx) { - const jwt = ctx.query.jwt - const accountId = await getEntityIdFromHeader('Bearer ' + jwt) - const unterveranstaltungId = ctx.query.unterveranstaltungId - const veranstaltungId = ctx.query.veranstaltungId - - if (accountId == null || (unterveranstaltungId == null && veranstaltungId == null)) { - ctx.res.statusCode = 401 - ctx.res.end() - return - } - - const account = await prisma.account.findUnique({ - where: { - id: parseInt(accountId), - }, - select: { - role: true, - person: { - select: { - firstname: true, - lastname: true, - }, - }, - }, - }) - - if (account == null) { - ctx.res.statusCode = 401 - ctx.res.end() +export async function veranstaltungTeilnehmendenliste(ctx: Context) { + const authorization = await sheetAuthorize(ctx) + if (!authorization) { return } - let gliederung - if (account.role == Role.GLIEDERUNG_ADMIN) { - try { - gliederung = await getGliederungRequireAdmin(parseInt(accountId)) - } catch (error) { - ctx.res.statusCode = 401 - ctx.res.end() - return - } - } + const { query, account, gliederung } = authorization const anmeldungenList = await prisma.anmeldung.findMany({ where: { OR: [ { - unterveranstaltungId: parseInt(unterveranstaltungId || -1), + unterveranstaltungId: query.unterveranstaltungId, }, { unterveranstaltung: { - veranstaltungId: parseInt(veranstaltungId || -1), + veranstaltungId: query.veranstaltungId, }, }, ], @@ -80,7 +42,6 @@ export async function veranstaltungTeilnehmendenliste(ctx) { email: true, telefon: true, essgewohnheit: true, - konfektionsgroesse: true, gliederung: { select: { id: true, @@ -106,12 +67,12 @@ export async function veranstaltungTeilnehmendenliste(ctx) { }, createdAt: true, status: true, - tshirtBestellt: true, unterveranstaltung: { select: { veranstaltung: { select: { meldeschluss: true, + beginn: true, }, }, }, @@ -148,11 +109,13 @@ export async function veranstaltungTeilnehmendenliste(ctx) { ['Vorname']: anmeldung.person.firstname, ['Nachname']: anmeldung.person.lastname, ['Geburtstag']: anmeldung.person.birthday, + ['Alter zu Beginn']: dayjs(anmeldung.unterveranstaltung.veranstaltung.beginn).diff( + anmeldung.person.birthday, + 'years' + ), ['Gliederung']: anmeldung.person.gliederung?.name, ['Email']: anmeldung.person.email, ['Telefon']: anmeldung.person.telefon, - ['T-Shirt']: anmeldung.tshirtBestellt, - ['Konfektionsgröße']: anmeldung.person.konfektionsgroesse, ['Essgewohnheit']: anmeldung.person.essgewohnheit, ['Anmeldedatum']: anmeldung.createdAt, ...customFields, @@ -175,13 +138,13 @@ export async function veranstaltungTeilnehmendenliste(ctx) { XLSX.utils.book_append_sheet(workbook, worksheet, `Teilnehmendenliste`) /** add Security Worksheet */ - const { securityWorksheet, securityWorksheetName } = getSecurityWorksheet(XLSX, account, rows.length) + const { securityWorksheet, securityWorksheetName } = getSecurityWorksheet(account, rows.length) XLSX.utils.book_append_sheet(workbook, securityWorksheet, securityWorksheetName) - const buf = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }) - /* prepare response headers */ - ctx.res.statusCode = 200 const filename = `${dayjs().format('YYYYMMDD-hhmm')}-Teilnehmenden.xlsx` + const buf = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }) as Buffer + + ctx.res.statusCode = 201 ctx.res.setHeader('Content-Disposition', `attachment; filename="${filename}"`) ctx.res.setHeader('Content-Type', 'application/vnd.ms-excel') ctx.res.end(buf) diff --git a/api/src/exports/sheets/verpflegung.ts b/apps/api/src/routes/exports/sheets/verpflegung.ts similarity index 62% rename from api/src/exports/sheets/verpflegung.ts rename to apps/api/src/routes/exports/sheets/verpflegung.ts index d9c42c6c..e8222319 100644 --- a/api/src/exports/sheets/verpflegung.ts +++ b/apps/api/src/routes/exports/sheets/verpflegung.ts @@ -1,56 +1,19 @@ -import { AnmeldungStatus, Essgewohnheit, NahrungsmittelIntoleranz, Role } from '@prisma/client' +import { AnmeldungStatus, Essgewohnheit, NahrungsmittelIntoleranz } from '@prisma/client' import dayjs from 'dayjs' -import XLSX from 'xlsx' +import type { Context } from 'koa' +import XLSX from '@e965/xlsx' +import prisma from '../../../prisma.js' +import { getSecurityWorksheet } from '../helpers/getSecurityWorksheet.js' +import { getWorkbookDefaultProps } from '../helpers/getWorkbookDefaultProps.js' +import { sheetAuthorize } from './sheets.schema.js' -import { getEntityIdFromHeader } from '../../authentication' -import prisma from '../../prisma' -import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin' -import { getSecurityWorksheet } from '../helpers/getSecurityWorksheet' -import { getWorkbookDefaultProps } from '../helpers/getWorkbookDefaultProps' - -export async function veranstaltungVerpflegung(ctx) { - const jwt = ctx.query.jwt - const accountId = await getEntityIdFromHeader('Bearer ' + jwt) - const unterveranstaltungId = ctx.query.unterveranstaltungId - const veranstaltungId = ctx.query.veranstaltungId - - if (accountId == null || (unterveranstaltungId == null && veranstaltungId == null)) { - ctx.res.statusCode = 401 - ctx.res.end() - return - } - - const account = await prisma.account.findUnique({ - where: { - id: parseInt(accountId), - }, - select: { - role: true, - person: { - select: { - firstname: true, - lastname: true, - }, - }, - }, - }) - - if (account == null) { - ctx.res.statusCode = 401 - ctx.res.end() +export async function veranstaltungVerpflegung(ctx: Context) { + const authorization = await sheetAuthorize(ctx) + if (!authorization) { return } - let gliederung: { id: number; name: string } | undefined - if (account.role == Role.GLIEDERUNG_ADMIN) { - try { - gliederung = await getGliederungRequireAdmin(parseInt(accountId)) - } catch (error) { - ctx.res.statusCode = 401 - ctx.res.end() - return - } - } + const { query, account, gliederung } = authorization const gliederungen = await prisma.gliederung.findMany({ where: { @@ -59,10 +22,10 @@ export async function veranstaltungVerpflegung(ctx) { some: { OR: [ { - id: parseInt(unterveranstaltungId || -1), + id: query.unterveranstaltungId, }, { - veranstaltungId: parseInt(veranstaltungId || -1), + veranstaltungId: query.veranstaltungId, }, ], }, @@ -75,10 +38,10 @@ export async function veranstaltungVerpflegung(ctx) { where: { OR: [ { - id: parseInt(unterveranstaltungId || -1), + id: query.unterveranstaltungId, }, { - veranstaltungId: parseInt(veranstaltungId || -1), + veranstaltungId: query.veranstaltungId, }, ], }, @@ -114,9 +77,13 @@ export async function veranstaltungVerpflegung(ctx) { ] const rows = gliederungen - .filter((gliederung) => gliederung.unterveranstaltungen[0]?.Anmeldung.length > 0) // Filtere Gliederungen ohne Anmeldungen + .filter((gliederung) => { + return gliederung.unterveranstaltungen.every((unterveranstaltung) => { + return unterveranstaltung.Anmeldung.length > 0 + }) + }) // Filtere Gliederungen ohne Anmeldungen .map((gliederung) => { - const anmeldungen = gliederung.unterveranstaltungen[0].Anmeldung + const anmeldungen = gliederung.unterveranstaltungen.flatMap((unterveranstaltung) => unterveranstaltung.Anmeldung) const aggregatedEssgewohnheiten = Object.fromEntries( Object.values(Essgewohnheit).map((essgewohnheit) => [ essgewohnheit, @@ -157,15 +124,14 @@ export async function veranstaltungVerpflegung(ctx) { worksheet['!cols'] = [{ wch: 6 }, { wch: 40 }] XLSX.utils.book_append_sheet(workbook, worksheet, `Verpflegung`) - /** add Security Worksheet */ - const { securityWorksheet, securityWorksheetName } = getSecurityWorksheet(XLSX, account, rows.length) + const { securityWorksheet, securityWorksheetName } = getSecurityWorksheet(account, rows.length) XLSX.utils.book_append_sheet(workbook, securityWorksheet, securityWorksheetName) - const buf = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }) - /* prepare response headers */ - ctx.res.statusCode = 200 const filename = `${dayjs().format('YYYYMMDD-hhmm')}-Verpflegung.xlsx` + const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }) as Buffer + + ctx.res.statusCode = 201 ctx.res.setHeader('Content-Disposition', `attachment; filename="${filename}"`) ctx.res.setHeader('Content-Type', 'application/vnd.ms-excel') - ctx.res.end(buf) + ctx.res.end(buffer) } diff --git a/apps/api/src/routes/files/downloadFileLocal.ts b/apps/api/src/routes/files/downloadFileLocal.ts new file mode 100644 index 00000000..73139213 --- /dev/null +++ b/apps/api/src/routes/files/downloadFileLocal.ts @@ -0,0 +1,36 @@ +import * as fs from 'fs' +import * as path from 'path' + +import type { Middleware } from 'koa' +import mime from 'mime-types' + +import config from '../../config.js' +import prisma from '../../prisma.js' + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const downloadFileLocal: Middleware = async function (ctx, next) { + const params = ctx.params as { id: string } + const fileId = params.id + const file = await prisma.file.findFirst({ + where: { + id: fileId, + }, + }) + if (file === null) { + ctx.response.status = 404 + return + } + + if (file.provider !== 'LOCAL') { + ctx.response.status = 404 + return + } + + const uploadDir = path.join(process.cwd(), config.fileProviders.LOCAL.path) + const mimetype = file.mimetype ?? 'application/octet-stream' + // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + const filename = file.filename ?? `${file.id}.${mime.extension(mimetype)}` + ctx.set('Content-disposition', `attachment; filename=${filename.replace(/[^a-zA-Z0-9._-]/g, '_')}`) + ctx.set('Content-type', mimetype) + ctx.response.body = fs.createReadStream(uploadDir + '/' + file.key) +} diff --git a/api/src/middleware/importAnmeldungen.ts b/apps/api/src/routes/files/importAnmeldungen.ts similarity index 70% rename from api/src/middleware/importAnmeldungen.ts rename to apps/api/src/routes/files/importAnmeldungen.ts index 368f88e8..c3f05c70 100644 --- a/api/src/middleware/importAnmeldungen.ts +++ b/apps/api/src/routes/files/importAnmeldungen.ts @@ -5,28 +5,27 @@ import { Role } from '@prisma/client' import * as csv from 'fast-csv' import type { Middleware } from 'koa' -import { getEntityIdFromHeader } from '../authentication' -import prisma from '../prisma' -import { inputSchema as anmeldungCreateSchema } from '../services/anmeldung/anmeldungPublicCreate' -import { getPersonCreateData } from '../services/person/schema/person.schema' -import { customFieldValuesCreateMany } from '../types/defineCustomFieldValues' +import { getEntityIdFromHeader } from '../../authentication.js' +import prisma from '../../prisma.js' +import { inputSchema as anmeldungCreateSchema } from '../../services/anmeldung/anmeldungPublicCreate.js' +import { getPersonCreateData } from '../../services/person/schema/person.schema.js' +import { customFieldValuesCreateMany } from '../../types/defineCustomFieldValues.js' -import { customParseFormat, dayjs } from '@codeanker/helpers' - -dayjs.extend(customParseFormat) +import { dayjs } from '@codeanker/helpers' // eslint-disable-next-line @typescript-eslint/no-unused-vars export const importAnmeldungen: Middleware = async function (ctx, next) { - let accountId + let accountId: string | undefined try { - accountId = await getEntityIdFromHeader(ctx.request.header.authorization) - } catch (e) { + accountId = getEntityIdFromHeader(ctx.request.header.authorization) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { ctx.response.status = 401 ctx.response.message = 'Token not valid' return } - - if (accountId == null || !ctx.request.files || !ctx.request.body.unterveranstaltungId) { + const ctxRequestBody = ctx.request.body as { unterveranstaltungId: string } + if (accountId == null || !ctx.request.files || !ctxRequestBody.unterveranstaltungId) { ctx.response.status = 400 ctx.response.message = 'Es wurden keine Dateien oder Unterveranstaltung übergeben' return @@ -59,14 +58,17 @@ export const importAnmeldungen: Middleware = async function (ctx, next) { return } - let files: any = [] + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let files: any[] = [] if (Array.isArray(ctx.request.files.files)) { files = ctx.request.files.files } else { files.push(ctx.request.files.files) } + // eslint-disable-next-line @typescript-eslint/no-explicit-any files.filter((file: any) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (file.mimetype !== 'text/csv') { ctx.response.status = 406 ctx.response.message = 'Datei muss vom Typ CSV sein' @@ -74,7 +76,7 @@ export const importAnmeldungen: Middleware = async function (ctx, next) { } }) - const unterveranstaltung = await findUnterveranstaltung(parseInt(ctx.request.body.unterveranstaltungId)) + const unterveranstaltung = await findUnterveranstaltung(parseInt(ctxRequestBody.unterveranstaltungId)) if (!unterveranstaltung) { ctx.response.status = 400 ctx.response.message = 'Unterveranstaltung nicht gefunden' @@ -82,11 +84,13 @@ export const importAnmeldungen: Middleware = async function (ctx, next) { } files.forEach((file) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access fs.createReadStream(path.resolve(file.filepath)) .pipe(csv.parse({ headers: true, delimiter: ';', ignoreEmpty: true })) .on('error', (error) => console.error(error)) + // eslint-disable-next-line @typescript-eslint/no-misused-promises, @typescript-eslint/no-unsafe-argument .on('data', (row) => createAnmeldung(row, unterveranstaltung)) - // eslint-disable-next-line no-console + .on('end', (rowCount: number) => console.log(`Parsed ${rowCount} rows`)) ctx.response.status = 200 @@ -101,9 +105,29 @@ export const importAnmeldungen: Middleware = async function (ctx, next) { * @param row * @param unterveranstaltungId */ -async function createAnmeldung(row: any, unterveranstaltung) { - // console.log(unterveranstaltungId, row) - +async function createAnmeldung( + row: { + vorname: string + nachname: string + geschlecht: string + email: string + telefon: string + strasse: string + geburtstag: string + hausnummer: string + plz: string + ort: string + essgewohnheit: string + nahrungsmittelIntoleranzen: string + weitereIntoleranzen: string + notfallkontaktVorname: string + notfallkontaktNachname: string + notfallkontaktTelefon: string + istErziehungsberechtigt: string + comment: string + }, + unterveranstaltung: { id: number; gliederungId: number } +) { try { const mappedRow = { data: { @@ -180,10 +204,11 @@ function formatNahrungsmittelIntoleranzen(nahrungsmittelIntoleranzen: string) { return nahrungsmittelIntoleranzen.split(',').map((item) => item.trim()) } -function mapCustomFields(obj) { - const customFields = [] +function mapCustomFields(obj: Record) { + const customFields: { fieldId: number; value: string | boolean }[] = [] for (const key in obj) { if (key.startsWith('customFieldId_')) { + if (!obj[key]) continue customFields.push({ fieldId: parseInt(key.replace('customFieldId_', '')), value: parseValue(obj[key]), @@ -193,7 +218,7 @@ function mapCustomFields(obj) { return customFields } -function parseValue(value) { +function parseValue(value: string) { if (value === 'Ja') return true if (value === 'Nein') return false return value diff --git a/apps/api/src/routes/files/uploadFileLocal.ts b/apps/api/src/routes/files/uploadFileLocal.ts new file mode 100644 index 00000000..a81fd44f --- /dev/null +++ b/apps/api/src/routes/files/uploadFileLocal.ts @@ -0,0 +1,91 @@ +import * as fs from 'fs/promises' +import * as path from 'path' + +import type { Middleware } from 'koa' + +import config from '../../config.js' +import prisma from '../../prisma.js' + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export const uploadFileLocal: Middleware = async function (ctx, next) { + const params = ctx.params as { id: string } + const fileId = params.id + const file = await prisma.file.findFirst({ + where: { + id: fileId, + }, + }) + if (file === null) { + ctx.response.status = 400 + ctx.response.body = { error: `File with id '${fileId}' not found` } + return + } + + if (file.uploaded) { + ctx.response.status = 400 + ctx.response.body = { error: `File with id '${fileId}' already uploaded` } + return + } + + if (file.provider !== 'LOCAL') { + ctx.response.status = 400 + ctx.response.body = { error: `File provider is '${file.provider}'. This endpoint is for LOCAL` } + return + } + + const uploadDir = path.join(process.cwd(), config.fileProviders.LOCAL.path) + try { + await checkLocalUploadFolder(uploadDir) + } catch (e) { + console.error('Error while creating upload-directory\n', e) + ctx.response.status = 500 + ctx.response.body = { + error: 'Something went wrong during creation of upload-directory', + } + return + } + + const fileData = ctx.request.files?.file + if (!fileData || Array.isArray(fileData)) { + ctx.response.status = 400 + ctx.response.body = { + error: 'No or Invalid File provided', + } + return + } + + try { + await fs.copyFile(fileData.filepath, uploadDir + '/' + file.key) + } catch (e) { + console.error('Error while copy to upload-directory\n', e) + ctx.response.status = 500 + ctx.response.body = { + error: 'Something went wrong during copy to upload-directory', + } + return + } + + await prisma.file.update({ + where: { id: fileId }, + data: { + mimetype: fileData.mimetype ?? 'application/octet-stream', + filename: fileData.originalFilename ?? undefined, + uploaded: true, + uploadedAt: new Date(), + }, + }) + + ctx.response.status = 201 + ctx.response.body = { uploaded: true } +} + +async function checkLocalUploadFolder(uploadDir: string) { + try { + await fs.stat(uploadDir) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (e.code === 'ENOENT') await fs.mkdir(uploadDir, { recursive: true }) + else throw e + } +} diff --git a/apps/api/src/routes/index.ts b/apps/api/src/routes/index.ts new file mode 100644 index 00000000..75f3b380 --- /dev/null +++ b/apps/api/src/routes/index.ts @@ -0,0 +1,22 @@ +import Router from 'koa-router' + +import connect from './connect.js' +import { veranstaltungTeilnehmendenliste } from './exports/sheets/teilnehmendenliste.js' +import { veranstaltungVerpflegung } from './exports/sheets/verpflegung.js' +import { downloadFileLocal } from './files/downloadFileLocal.js' +import { importAnmeldungen } from './files/importAnmeldungen.js' +import { uploadFileLocal } from './files/uploadFileLocal.js' + +const koaRouter = new Router() + +koaRouter.get('/connect/dlrg/callback', connect) + +koaRouter.get('/export/sheet/teilnehmendenliste', veranstaltungTeilnehmendenliste) +koaRouter.get('/export/sheet/verpflegung', veranstaltungVerpflegung) + +koaRouter.get('/download/file/LOCAL/:id', downloadFileLocal) +koaRouter.post('/upload/file/LOCAL/:id', uploadFileLocal) + +koaRouter.post('/upload/anmeldungen', importAnmeldungen) + +export default koaRouter diff --git a/api/src/scripts/createAccount.ts b/apps/api/src/scripts/createAccount.ts similarity index 58% rename from api/src/scripts/createAccount.ts rename to apps/api/src/scripts/createAccount.ts index a220c5fa..3e6cd917 100644 --- a/api/src/scripts/createAccount.ts +++ b/apps/api/src/scripts/createAccount.ts @@ -1,17 +1,17 @@ -import { input, password as passwordInput, select } from '@inquirer/prompts' +import { input, password as passwordInput, select, search } from '@inquirer/prompts' +import { Role } from '@prisma/client' -import { getEnumOptions, roleMapping } from '../enumMappings' -import prisma from '../prisma' -import { getAccountCreateData } from '../services/account/schema/account.schema' -import logActivity from '../util/activity' +import prisma from '../prisma.js' +import { getAccountCreateData } from '../services/account/schema/account.schema.js' +import logActivity from '../util/activity.js' +import { getEnumOptions, roleMapping } from '../client.js' -createUser() async function createUser() { const email = await input({ message: 'E-Mail' }) const firstname = await input({ message: 'Vorname' }) const lastname = await input({ message: 'Nachname' }) const password = await passwordInput({ message: 'Passwort' }) - const roleId = (await select({ + const roleId = await select({ message: 'Rolle', choices: getEnumOptions(roleMapping).map((option) => { return { @@ -19,18 +19,30 @@ async function createUser() { value: option.value, } }), - })) as keyof typeof roleMapping + }) async function selectGliederung(): Promise { - return await select({ + return await search({ message: 'Deine Gliederung', - choices: (await prisma.gliederung.findMany()).map((v) => ({ - name: v.name, - value: v.id, - })), + source: async (term) => { + const results = await prisma.gliederung.findMany({ + where: { + name: { + contains: term, + mode: 'insensitive', + }, + }, + }) + + return results.map((v) => ({ + name: v.name, + value: v.id, + })) + }, }) } + const gliederungId = await selectGliederung() const accountData = await getAccountCreateData({ email: email, firstname: firstname, @@ -38,7 +50,8 @@ async function createUser() { password: password, roleId: roleId, isActiv: true, - adminInGliederungId: roleId === 'GLIEDERUNG_ADMIN' ? await selectGliederung() : undefined, + gliederungId, + adminInGliederungId: roleId === Role.GLIEDERUNG_ADMIN ? gliederungId : undefined, birthday: new Date(), gender: 'FEMALE', }) @@ -61,7 +74,8 @@ async function createUser() { subjectId: res.id, }) - // eslint-disable-next-line no-console console.log('Nutzer erstellt') process.exit() } + +await createUser() diff --git a/apps/api/src/scripts/initMeilisearch.ts b/apps/api/src/scripts/initMeilisearch.ts new file mode 100644 index 00000000..f7049956 --- /dev/null +++ b/apps/api/src/scripts/initMeilisearch.ts @@ -0,0 +1,3 @@ +import { syncAllPersonsToMeili } from '../meilisearch/person.js' + +await syncAllPersonsToMeili() diff --git a/api/src/server.ts b/apps/api/src/server.ts similarity index 74% rename from api/src/server.ts rename to apps/api/src/server.ts index ada36d6b..35f7a75e 100644 --- a/api/src/server.ts +++ b/apps/api/src/server.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ import cors from '@koa/cors' import grant from 'grant' import Koa from 'koa' @@ -7,13 +9,13 @@ import session from 'koa-session' import serve from 'koa-static' import { createKoaMiddleware } from 'trpc-koa-adapter' -import config from './config' -import { createContext } from './context' -import { logger } from './logger' -import cacheControl from './middleware/cache-control' -import router from './routes' +import config from './config.js' +import { createContext } from './context.js' +import { logger } from './logger.js' +import cacheControl from './middleware/cache-control.js' +import router from './routes/index.js' -import { appRouter } from './index' +import { appRouter } from './index.js' export const app = new Koa() @@ -36,7 +38,8 @@ app.use(session({}, app)) // grant is used for oauth app.use( - grant.koa()({ + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access + (grant as any).koa()({ defaults: { origin: `${config.clientUrl}/api`, transport: 'session', @@ -75,4 +78,4 @@ app.use(async (ctx, next) => { }) app.listen(config.server.port, config.server.host) -logger.info(`app listening on http://0.0.0.0:${config.server.port}`) +logger.info(`app listening on http://${config.server.host}:${config.server.port}`) diff --git a/api/src/services/account/account.router.ts b/apps/api/src/services/account/account.router.ts similarity index 68% rename from api/src/services/account/account.router.ts rename to apps/api/src/services/account/account.router.ts index c807ce41..55be6a9a 100644 --- a/api/src/services/account/account.router.ts +++ b/apps/api/src/services/account/account.router.ts @@ -1,17 +1,17 @@ -/* eslint-disable prettier/prettier */ // Prettier ignored is because this file is generated -import { mergeRouters } from '../../trpc' +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' -import { accountActivateProcedure } from './accountActivate' -import { accountChangePasswordProcedure } from './accountChangePassword' -import { accountEmailConfirmProcedure } from './accountEmailConfirm' -import { accountEmailConfirmRequestProcedure } from './accountEmailConfirmRequest' -import { accountGliederungAdminCreateProcedure } from './accountGliederungAdminCreate' -import { accountPasswordResetProcedure } from './accountPasswordReset' -import { accountVerwaltungCreateProcedure } from './accountVerwaltungCreate' -import { accountVerwaltungGetProcedure } from './accountVerwaltungGet' -import { accountVerwaltungListProcedure } from './accountVerwaltungList' -import { accountVerwaltungPatchProcedure } from './accountVerwaltungPatch' -import {accountVerwaltungRemoveProcedure} from './accountVerwaltungRemove' +import { accountActivateProcedure } from './accountActivate.js' +import { accountChangePasswordProcedure } from './accountChangePassword.js' +import { accountEmailConfirmProcedure } from './accountEmailConfirm.js' +import { accountEmailConfirmRequestProcedure } from './accountEmailConfirmRequest.js' +import { accountGliederungAdminCreateProcedure } from './accountGliederungAdminCreate.js' +import { accountPasswordResetProcedure } from './accountPasswordReset.js' +import { accountVerwaltungCreateProcedure } from './accountVerwaltungCreate.js' +import { accountVerwaltungGetProcedure } from './accountVerwaltungGet.js' +import { accountVerwaltungCountProcedure, accountVerwaltungListProcedure } from './accountVerwaltungList.js' +import { accountVerwaltungPatchProcedure } from './accountVerwaltungPatch.js' +import { accountVerwaltungRemoveProcedure } from './accountVerwaltungRemove.js' // Import Routes here - do not delete this line export const accountRouter = mergeRouters( @@ -21,6 +21,7 @@ export const accountRouter = mergeRouters( accountVerwaltungCreateProcedure.router, accountVerwaltungGetProcedure.router, accountVerwaltungListProcedure.router, + accountVerwaltungCountProcedure.router, accountVerwaltungPatchProcedure.router, accountEmailConfirmRequestProcedure.router, accountEmailConfirmProcedure.router, diff --git a/apps/api/src/services/account/accountActivate.ts b/apps/api/src/services/account/accountActivate.ts new file mode 100644 index 00000000..2becb8f1 --- /dev/null +++ b/apps/api/src/services/account/accountActivate.ts @@ -0,0 +1,62 @@ +import { Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import logActivity from '../../util/activity.js' +import { sendMail } from '../../util/mail.js' + +export const accountActivateProcedure = defineProtectedMutateProcedure({ + key: 'activate', + roleIds: [Role.ADMIN], + inputSchema: z.strictObject({ + accountId: z.number().int(), + }), + async handler(options) { + const account = await prisma.account.update({ + where: { + id: options.input.accountId, + }, + data: { + activatedAt: new Date(), + }, + select: { + email: true, + person: { + select: { + firstname: true, + lastname: true, + gliederung: { + select: { + name: true, + }, + }, + }, + }, + }, + }) + + await logActivity({ + type: 'UPDATE', + description: `account was activated`, + subjectType: 'account', + subjectId: options.input.accountId, + causerId: options.ctx.accountId, + }) + + await sendMail({ + to: account.email, + subject: 'Account aktiviert', + categories: ['account', 'activate'], + template: 'account-activated', + variables: { + gliederung: account.person.gliederung!.name, + name: `${account.person.firstname} ${account.person.lastname}`, + hostname: 'brahmsee.digital', + veranstaltung: 'brahmsee.digital', + }, + }) + + return 'activated' + }, +}) diff --git a/api/src/services/account/accountChangePassword.ts b/apps/api/src/services/account/accountChangePassword.ts similarity index 77% rename from api/src/services/account/accountChangePassword.ts rename to apps/api/src/services/account/accountChangePassword.ts index 16a1fd7e..8c4d5e48 100644 --- a/api/src/services/account/accountChangePassword.ts +++ b/apps/api/src/services/account/accountChangePassword.ts @@ -1,17 +1,17 @@ +import { Role } from '@prisma/client' import { TRPCError } from '@trpc/server' import { z } from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import logActivity from '../../util/activity' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import logActivity from '../../util/activity.js' import { hashPassword, passwordMatches } from '@codeanker/authentication' import { isStrongPassword } from '@codeanker/helpers' -export const accountChangePasswordProcedure = defineProcedure({ +export const accountChangePasswordProcedure = defineProtectedMutateProcedure({ key: 'changePassword', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'] }, + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ id: z.number().int(), password_old: z.string(), @@ -27,11 +27,6 @@ export const accountChangePasswordProcedure = defineProcedure({ password: true, }, }) - if (password === null) - throw new TRPCError({ - code: 'INTERNAL_SERVER_ERROR', - message: 'Account has no password', - }) if (options.ctx.accountId === options.input.id && !(await passwordMatches(password, options.input.password_old))) { throw new TRPCError({ diff --git a/api/src/services/account/accountEmailConfirm.ts b/apps/api/src/services/account/accountEmailConfirm.ts similarity index 75% rename from api/src/services/account/accountEmailConfirm.ts rename to apps/api/src/services/account/accountEmailConfirm.ts index ec47ac70..c5bff564 100644 --- a/api/src/services/account/accountEmailConfirm.ts +++ b/apps/api/src/services/account/accountEmailConfirm.ts @@ -1,12 +1,10 @@ import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { definePublicMutateProcedure } from '../../types/defineProcedure.js' -export const accountEmailConfirmProcedure = defineProcedure({ +export const accountEmailConfirmProcedure = definePublicMutateProcedure({ key: 'emailConfirm', - method: 'mutation', - protection: { type: 'public' }, inputSchema: z.strictObject({ activationToken: z.string(), }), diff --git a/api/src/services/account/accountEmailConfirmRequest.ts b/apps/api/src/services/account/accountEmailConfirmRequest.ts similarity index 68% rename from api/src/services/account/accountEmailConfirmRequest.ts rename to apps/api/src/services/account/accountEmailConfirmRequest.ts index d392e2a7..42c89446 100644 --- a/api/src/services/account/accountEmailConfirmRequest.ts +++ b/apps/api/src/services/account/accountEmailConfirmRequest.ts @@ -1,15 +1,15 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import logActivity from '../../util/activity' +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import logActivity from '../../util/activity.js' -import { sendMailConfirmEmailRequest } from './helpers/sendMailConfirmEmailRequest' +import { sendMailConfirmEmailRequest } from './helpers/sendMailConfirmEmailRequest.js' -export const accountEmailConfirmRequestProcedure = defineProcedure({ +export const accountEmailConfirmRequestProcedure = defineProtectedQueryProcedure({ key: 'emailConfirmRequest', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ accountId: z.number().int(), }), @@ -44,10 +44,7 @@ export const accountEmailConfirmRequestProcedure = defineProcedure({ description: 'email confirmation requested', }) - await sendMailConfirmEmailRequest({ - email: account.email, - activationToken: account.activationToken, - }) + await sendMailConfirmEmailRequest(account.email, account.activationToken) return { success: true, diff --git a/api/src/services/account/accountGliederungAdminCreate.ts b/apps/api/src/services/account/accountGliederungAdminCreate.ts similarity index 73% rename from api/src/services/account/accountGliederungAdminCreate.ts rename to apps/api/src/services/account/accountGliederungAdminCreate.ts index 20ccf27f..adf06b8a 100644 --- a/api/src/services/account/accountGliederungAdminCreate.ts +++ b/apps/api/src/services/account/accountGliederungAdminCreate.ts @@ -3,13 +3,13 @@ import { TRPCError } from '@trpc/server' import jwt from 'jsonwebtoken' import z from 'zod' -import config from '../../config' -import prisma from '../../prisma' -import { ZOauthRegisterJwtPayloadSchema } from '../../routes/connect' -import { defineProcedure } from '../../types/defineProcedure' +import config from '../../config.js' +import prisma from '../../prisma.js' +import { ZOauthRegisterJwtPayloadSchema } from '../../routes/connect.js' +import { definePublicMutateProcedure } from '../../types/defineProcedure.js' -import { sendMailConfirmEmailRequest } from './helpers/sendMailConfirmEmailRequest' -import { getAccountCreateData } from './schema/account.schema' +import { sendMailConfirmEmailRequest } from './helpers/sendMailConfirmEmailRequest.js' +import { getAccountCreateData } from './schema/account.schema.js' const ZAccountGliederungAdminCreateInput = z.strictObject({ data: z.strictObject({ @@ -19,20 +19,18 @@ const ZAccountGliederungAdminCreateInput = z.strictObject({ birthday: z.date(), email: z.string().email().optional(), // email is required, because oauth login does not have an email password: z.string().optional(), // optional, because oauth login does not have a password - adminInGliederungId: z.number().int(), + gliederungId: z.number().int(), jwtOAuthToken: z.string().optional(), // optional, becaus normal registration does not have a jwtOAuthToken }), }) -export const accountGliederungAdminCreateProcedure = defineProcedure({ +export const accountGliederungAdminCreateProcedure = definePublicMutateProcedure({ key: 'gliederungAdminCreate', - method: 'mutation', - protection: { type: 'public' }, inputSchema: ZAccountGliederungAdminCreateInput, async handler(options) { let dlrgOauthId: undefined | string = undefined // check if jwtOAuthToken set and if so, check if it is valid - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (options.input.data.jwtOAuthToken) { const jwtOAuthTokenPayload = ZOauthRegisterJwtPayloadSchema.parse( jwt.verify(options.input.data.jwtOAuthToken, `${config.authentication.secret}-oauth`) @@ -41,7 +39,6 @@ export const accountGliederungAdminCreateProcedure = defineProcedure({ dlrgOauthId = jwtOAuthTokenPayload.sub } - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (!options.input.data.email) { throw new TRPCError({ code: 'BAD_REQUEST', @@ -57,7 +54,8 @@ export const accountGliederungAdminCreateProcedure = defineProcedure({ gender: options.input.data.gender, roleId: 'GLIEDERUNG_ADMIN', isActiv: false, - adminInGliederungId: options.input.data.adminInGliederungId, + gliederungId: options.input.data.gliederungId, + adminInGliederungId: options.input.data.gliederungId, }) const res = await prisma.account.create({ data: { @@ -69,10 +67,8 @@ export const accountGliederungAdminCreateProcedure = defineProcedure({ }, }) - await sendMailConfirmEmailRequest({ - email: accountData.email, - activationToken: accountData.activationToken, - }) + if (!accountData.activationToken) throw new Error('No activation token generated') + await sendMailConfirmEmailRequest(accountData.email, accountData.activationToken) return res }, diff --git a/api/src/services/account/accountPasswordReset.ts b/apps/api/src/services/account/accountPasswordReset.ts similarity index 68% rename from api/src/services/account/accountPasswordReset.ts rename to apps/api/src/services/account/accountPasswordReset.ts index e505c9eb..80cafc15 100644 --- a/api/src/services/account/accountPasswordReset.ts +++ b/apps/api/src/services/account/accountPasswordReset.ts @@ -1,17 +1,15 @@ import { v4 as uuidv4 } from 'uuid' import z from 'zod' -import config from '../../config' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { sendMail } from '../../util/mail' +import config from '../../config.js' +import prisma from '../../prisma.js' +import { definePublicMutateProcedure } from '../../types/defineProcedure.js' +import { sendMail } from '../../util/mail.js' import { hashPassword } from '@codeanker/authentication' -export const accountPasswordResetProcedure = defineProcedure({ +export const accountPasswordResetProcedure = definePublicMutateProcedure({ key: 'resetPassword', - method: 'mutation', - protection: { type: 'public' }, inputSchema: z.strictObject({ email: z.string().optional(), passwordResetToken: z.string().optional(), @@ -27,14 +25,26 @@ export const accountPasswordResetProcedure = defineProcedure({ select: { email: true, passwordResetToken: true, + person: { + select: { + firstname: true, + lastname: true, + gliederung: { + select: { + name: true, + }, + }, + }, + }, }, }) - if (findRes == null) + if (findRes === null) { return { status: true, } + } - let resetToken + let resetToken: string | null = null const email = findRes.email if (findRes.passwordResetToken != null) { @@ -51,13 +61,20 @@ export const accountPasswordResetProcedure = defineProcedure({ resetToken = res.passwordResetToken } - if (resetToken) { + if (resetToken !== null) { const resetUrl = `${config.clientUrl}/password-reset/${resetToken}` - sendMail({ + await sendMail({ to: email, - subject: 'Passwort vergessen', + subject: 'Passwort zurücksetzen', categories: ['account', 'passwordReset'], - html: `Du kannst dein Passwort mit folgendem Link zurück setzen ${resetUrl}`, + template: 'account-password-reset', + variables: { + gliederung: findRes.person.gliederung!.name, + name: `${findRes.person.firstname} ${findRes.person.lastname}`, + hostname: 'brahmsee.digital', + veranstaltung: 'brahmsee.digital', + resetUrl, + }, }) } return { diff --git a/apps/api/src/services/account/accountVerwaltungCreate.ts b/apps/api/src/services/account/accountVerwaltungCreate.ts new file mode 100644 index 00000000..681367cb --- /dev/null +++ b/apps/api/src/services/account/accountVerwaltungCreate.ts @@ -0,0 +1,33 @@ +import { Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' + +import { sendMailConfirmEmailRequest } from './helpers/sendMailConfirmEmailRequest.js' +import { accountSchema, getAccountCreateData } from './schema/account.schema.js' + +export const accountVerwaltungCreateProcedure = defineProtectedMutateProcedure({ + key: 'verwaltungCreate', + roleIds: [Role.ADMIN], + inputSchema: z.strictObject({ + data: accountSchema, + }), + async handler(options) { + const accountData = await getAccountCreateData(options.input.data) + if (typeof accountData.activationToken !== 'string') { + throw new Error('no activation token found!') + } + + const res = prisma.account.create({ + data: accountData, + select: { + id: true, + }, + }) + + await sendMailConfirmEmailRequest(accountData.email, accountData.activationToken) + + return res + }, +}) diff --git a/api/src/services/account/accountVerwaltungGet.ts b/apps/api/src/services/account/accountVerwaltungGet.ts similarity index 73% rename from api/src/services/account/accountVerwaltungGet.ts rename to apps/api/src/services/account/accountVerwaltungGet.ts index d80f8dfb..c64bf308 100644 --- a/api/src/services/account/accountVerwaltungGet.ts +++ b/apps/api/src/services/account/accountVerwaltungGet.ts @@ -1,12 +1,12 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' -export const accountVerwaltungGetProcedure = defineProcedure({ +export const accountVerwaltungGetProcedure = defineProtectedQueryProcedure({ key: 'verwaltungGet', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ id: z.number().int(), }), diff --git a/apps/api/src/services/account/accountVerwaltungList.ts b/apps/api/src/services/account/accountVerwaltungList.ts new file mode 100644 index 00000000..89856490 --- /dev/null +++ b/apps/api/src/services/account/accountVerwaltungList.ts @@ -0,0 +1,94 @@ +import { Prisma, Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import { defineQuery, getOrderBy } from '../../types/defineQuery.js' + +const inputSchema = defineQuery({ + filter: z.strictObject({ + personName: z.string().optional(), + email: z.string().optional(), + status: z.enum(['AKTIV', 'DEAKTIVIERT', 'OFFEN']).optional(), + }), + + orderBy: z.array( + z.tuple([ + z.union([z.literal('person.firstname'), z.literal('email'), z.literal('role'), z.literal('status')]), + z.union([z.literal('asc'), z.literal('desc')]), + ]) + ), +}) + +export const accountVerwaltungListProcedure = defineProtectedQueryProcedure({ + key: 'verwaltungList', + roleIds: [Role.ADMIN], + inputSchema, + async handler(options) { + const { skip, take } = options.input.pagination + const list = await prisma.account.findMany({ + skip, + take, + where: await getWhere(options.input.filter, options.ctx.account), + orderBy: getOrderBy(options.input.orderBy), + select: { + id: true, + email: true, + activatedAt: true, + status: true, + role: true, + personId: true, + person: { + select: { + firstname: true, + lastname: true, + photoId: true, + }, + }, + GliederungToAccount: { + select: { + role: true, + gliederung: { + select: { + name: true, + }, + }, + }, + }, + }, + }) + return list + }, +}) + +export const accountVerwaltungCountProcedure = defineProtectedQueryProcedure({ + key: 'verwaltungCount', + roleIds: [Role.ADMIN], + inputSchema: inputSchema.pick({ filter: true }), + async handler(options) { + const list = await prisma.account.count({ + where: await getWhere(options.input.filter, options.ctx.account), + }) + return list + }, +}) + +// eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars +async function getWhere(filter: z.infer['filter'], _account: { id: number; role: Role }) { + const where: Prisma.AccountWhereInput = {} + + if (filter.email != null && filter.email != '') + where.email = { + contains: filter.email, + } + + if (filter.personName != null && filter.personName != '') + where.OR = [ + { person: { firstname: { contains: filter.personName } } }, + { person: { lastname: { contains: filter.personName } } }, + ] + + if (filter.status != null) where.status = filter.status + + return where +} diff --git a/api/src/services/account/accountVerwaltungPatch.ts b/apps/api/src/services/account/accountVerwaltungPatch.ts similarity index 56% rename from api/src/services/account/accountVerwaltungPatch.ts rename to apps/api/src/services/account/accountVerwaltungPatch.ts index 45c1b57a..5d714fdd 100644 --- a/api/src/services/account/accountVerwaltungPatch.ts +++ b/apps/api/src/services/account/accountVerwaltungPatch.ts @@ -1,15 +1,15 @@ -import { Role, AccountStatus } from '@prisma/client' +import { AccountStatus, Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import logActivity from '../../util/activity' -import { sendMail } from '../../util/mail' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import logActivity from '../../util/activity.js' +import { sendMail } from '../../util/mail.js' +import { AccountStatusMapping } from '../../types/enums/mappings/AccountStatus.js' -export const accountVerwaltungPatchProcedure = defineProcedure({ +export const accountVerwaltungPatchProcedure = defineProtectedMutateProcedure({ key: 'verwaltungPatch', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ id: z.number().int(), data: z.strictObject({ @@ -41,6 +41,11 @@ export const accountVerwaltungPatchProcedure = defineProcedure({ select: { firstname: true, lastname: true, + gliederung: { + select: { + name: true, + }, + }, }, }, }, @@ -54,11 +59,20 @@ export const accountVerwaltungPatchProcedure = defineProcedure({ subjectId: account.id, causerId: options.ctx.accountId, }) + await sendMail({ to: account.email, subject: `Account ${account.status}`, categories: ['account', 'status'], - html: `Hallo ${account.person.firstname} ${account.person.lastname},\n\n\nDein Accountstatus wurde auf ${account.status} geändert.\n\nViele Grüße,\nDein Orga-Team`, + template: 'account-status-changed', + variables: { + name: `${account.person.firstname} ${account.person.lastname}`, + gliederung: account.person.gliederung!.name, + hostname: 'brahmsee.digital', + veranstaltung: 'brahmsee.digital', + status: AccountStatusMapping[account.status].human, + isActive: account.status === AccountStatus.AKTIV, + }, }) } diff --git a/api/src/services/account/accountVerwaltungRemove.ts b/apps/api/src/services/account/accountVerwaltungRemove.ts similarity index 54% rename from api/src/services/account/accountVerwaltungRemove.ts rename to apps/api/src/services/account/accountVerwaltungRemove.ts index fe125dc4..c7cebae2 100644 --- a/api/src/services/account/accountVerwaltungRemove.ts +++ b/apps/api/src/services/account/accountVerwaltungRemove.ts @@ -1,13 +1,13 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import logActivity from '../../util/activity' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import logActivity from '../../util/activity.js' -export const accountVerwaltungRemoveProcedure = defineProcedure({ +export const accountVerwaltungRemoveProcedure = defineProtectedMutateProcedure({ key: 'verwaltungRemove', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ id: z.number().int(), }), @@ -18,8 +18,8 @@ export const accountVerwaltungRemoveProcedure = defineProcedure({ id: options.input.id, }, }) - .then((account) => { - logActivity({ + .then(async (account) => { + await logActivity({ type: 'DELETE', subjectType: 'account', subjectId: account.id, diff --git a/apps/api/src/services/account/helpers/sendMailConfirmEmailRequest.ts b/apps/api/src/services/account/helpers/sendMailConfirmEmailRequest.ts new file mode 100644 index 00000000..b95f672c --- /dev/null +++ b/apps/api/src/services/account/helpers/sendMailConfirmEmailRequest.ts @@ -0,0 +1,40 @@ +import config from '../../../config.js' +import client from '../../../prisma.js' +import { sendMail } from '../../../util/mail.js' + +export async function sendMailConfirmEmailRequest(email: string, activationToken: string) { + const activationUrl = `${config.clientUrl}/registrierung/confirm/${activationToken}` + + const account = await client.account.findUniqueOrThrow({ + where: { + email, + }, + select: { + person: { + select: { + firstname: true, + lastname: true, + gliederung: { + select: { + name: true, + }, + }, + }, + }, + }, + }) + + await sendMail({ + to: email, + subject: 'Bestätige deine E-Mail Adresse', + categories: ['account', 'confirm'], + template: 'account-email-confirm', + variables: { + name: `${account.person.firstname} ${account.person.lastname}`, + gliederung: account.person.gliederung!.name, + hostname: 'brahmsee.digital', + veranstaltung: 'brahmsee.digital', + activationUrl, + }, + }) +} diff --git a/api/src/services/account/schema/account.schema.ts b/apps/api/src/services/account/schema/account.schema.ts similarity index 78% rename from api/src/services/account/schema/account.schema.ts rename to apps/api/src/services/account/schema/account.schema.ts index 734b5dc6..93808a4c 100644 --- a/api/src/services/account/schema/account.schema.ts +++ b/apps/api/src/services/account/schema/account.schema.ts @@ -1,4 +1,4 @@ -import { Gender, Role, AccountStatus } from '@prisma/client' +import { Gender, Role, AccountStatus, Prisma, GliederungAccountRole } from '@prisma/client' import { v4 as uuidv4 } from 'uuid' import { z } from 'zod' @@ -14,6 +14,7 @@ export const accountSchema = z.strictObject({ roleId: z.nativeEnum(Role), isActiv: z.boolean().optional(), status: z.nativeEnum(AccountStatus).optional(), + gliederungId: z.number().int(), adminInGliederungId: z.number().int().optional(), activationToken: z.string().optional(), passwordResetToken: z.string().optional(), @@ -21,16 +22,16 @@ export const accountSchema = z.strictObject({ export type TAccountSchema = z.infer -const ZGetAccountCreateDataSchema = accountSchema.extend({ +export const ZGetAccountCreateDataSchema = accountSchema.extend({ password: z.string().optional(), // optional, because oauth login does not have a password }) type TGetAccountCreateDataSchema = z.infer -export async function getAccountCreateData(data: TGetAccountCreateDataSchema) { +export async function getAccountCreateData(data: TGetAccountCreateDataSchema): Promise { return { email: data.email, - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + password: data.password ? await hashPassword(data.password) : undefined, role: data.roleId, person: { @@ -41,6 +42,11 @@ export async function getAccountCreateData(data: TGetAccountCreateDataSchema) { birthday: data.birthday, email: data.email, telefon: '', + gliederung: { + connect: { + id: data.gliederungId, + }, + }, }, }, activatedAt: data.isActiv ? new Date() : null, @@ -49,7 +55,7 @@ export async function getAccountCreateData(data: TGetAccountCreateDataSchema) { ? { create: { gliederungId: data.adminInGliederungId, - role: 'DELEGATIONSLEITER' as const, + role: GliederungAccountRole.DELEGATIONSLEITER, }, } : undefined, diff --git a/apps/api/src/services/activity/activity.routes.ts b/apps/api/src/services/activity/activity.routes.ts new file mode 100644 index 00000000..92b1fe3f --- /dev/null +++ b/apps/api/src/services/activity/activity.routes.ts @@ -0,0 +1,11 @@ +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' + +import { activityListProcedure, activityCountProcedure } from './activityList.js' +// Import Routes here - do not delete this line + +export const activityRouter = mergeRouters( + activityListProcedure.router, + activityCountProcedure.router + // Add Routes here - do not delete this line +) diff --git a/apps/api/src/services/activity/activityList.ts b/apps/api/src/services/activity/activityList.ts new file mode 100644 index 00000000..d2070dcd --- /dev/null +++ b/apps/api/src/services/activity/activityList.ts @@ -0,0 +1,73 @@ +import { type Prisma, Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import { defineQuery, getOrderBy } from '../../types/defineQuery.js' + +const inputSchema = defineQuery({ + filter: z.strictObject({ + veranstaltungId: z.string().optional(), + }), + orderBy: z.array( + z.tuple([z.union([z.literal('id'), z.literal('createdAt')]), z.union([z.literal('asc'), z.literal('desc')])]) + ), +}) + +type Input = z.infer + +export const activityListProcedure = defineProtectedQueryProcedure({ + key: 'list', + roleIds: [Role.ADMIN], + inputSchema, + async handler({ input, ctx }) { + const { skip, take } = input.pagination + + const activities = await prisma.activity.findMany({ + skip, + take, + orderBy: getOrderBy(input.orderBy), + where: await getWhere(input.filter, ctx.account), + include: { + causer: { + select: { + person: { + select: { + firstname: true, + lastname: true, + }, + }, + }, + }, + }, + }) + + return activities + }, +}) + +export const activityCountProcedure = defineProtectedQueryProcedure({ + key: 'count', + roleIds: [Role.ADMIN], + inputSchema: inputSchema.pick({ filter: true }), + async handler({ input, ctx }) { + const activities = await prisma.activity.count({ + where: await getWhere(input.filter, ctx.account), + }) + + return activities + }, +}) + +// eslint-disable-next-line @typescript-eslint/require-await +async function getWhere( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + filter: Input['filter'], + // eslint-disable-next-line @typescript-eslint/no-unused-vars + account: { + id: number + role: Role + } +): Promise { + return {} +} diff --git a/api/src/services/activity/schema/activity.schema.ts b/apps/api/src/services/activity/schema/activity.schema.ts similarity index 100% rename from api/src/services/activity/schema/activity.schema.ts rename to apps/api/src/services/activity/schema/activity.schema.ts diff --git a/apps/api/src/services/address/address.router.ts b/apps/api/src/services/address/address.router.ts new file mode 100644 index 00000000..9a9ec4a2 --- /dev/null +++ b/apps/api/src/services/address/address.router.ts @@ -0,0 +1,10 @@ +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' + +import { addressFindActionProcedure } from './addressFindAddress.js' +// Import Routes here - do not delete this line + +export const addressRouter = mergeRouters( + addressFindActionProcedure.router + // Add Routes here - do not delete this line +) diff --git a/apps/api/src/services/address/addressFindAddress.ts b/apps/api/src/services/address/addressFindAddress.ts new file mode 100644 index 00000000..29794add --- /dev/null +++ b/apps/api/src/services/address/addressFindAddress.ts @@ -0,0 +1,91 @@ +import axios from 'axios' +import z from 'zod' + +import config from '../../config.js' +import { definePublicQueryProcedure } from '../../types/defineProcedure.js' + +export const addressFindActionProcedure = definePublicQueryProcedure({ + key: 'findAddress', + inputSchema: z.object({ + query: z.string().optional(), + zip: z.string().optional(), + city: z.string().optional(), + street: z.string().optional(), + streetNumber: z.string().optional(), + country: z.string().optional(), + }), + + async handler(options) { + let searchText = '' + if (options.input.query != null) { + searchText = options.input.query + } else { + if (options.input.zip != null) { + searchText += options.input.zip + } + if (options.input.city != null) { + searchText += options.input.city + } + if (options.input.street != null) { + searchText += options.input.street + } + if (options.input.streetNumber != null) { + searchText += options.input.streetNumber + } + } + const language = 'NGT' + + const token = config.tomtom.apiKey + if (!token) { + console.error('No TomTom API key found') + return [] + } + const country = options?.input?.country != null ? options.input.country.toUpperCase() : 'DE' + + try { + const query = await axios.get( + `https://api.tomtom.com/search/2/search/${encodeURIComponent( + searchText + )}.json?typeahead=true&limit=5&countrySet=${country}&language=${language}&idxSet=PAD&minFuzzyLevel=1&maxFuzzyLevel=2&view=Unified&key=${token}` + ) + + const ZTomTomSearchResponse = z.object({ + summary: z.object({ + numResults: z.number(), + }), + results: z.array( + z.object({ + address: z.object({ + streetName: z.string(), + streetNumber: z.string(), + postalCode: z.string(), + municipality: z.string(), + countryCode: z.string(), + }), + position: z.object({ + lat: z.number(), + lon: z.number(), + }), + }) + ), + }) + + const parsedSearchResponse = ZTomTomSearchResponse.parse(query.data) + + if (parsedSearchResponse.summary.numResults < 1) return [] + return parsedSearchResponse.results.map((result) => { + return { + street: result.address.streetName, + streetNumber: result.address.streetNumber, + zip: result.address.postalCode, + city: result.address.municipality, + country: result.address.countryCode, + position: result.position, + } + }) + } catch (e) { + console.error(e) + return [] + } + }, +}) diff --git a/api/src/services/address/schema/address.schema.ts b/apps/api/src/services/address/schema/address.schema.ts similarity index 67% rename from api/src/services/address/schema/address.schema.ts rename to apps/api/src/services/address/schema/address.schema.ts index cca2cc04..475c4f17 100644 --- a/api/src/services/address/schema/address.schema.ts +++ b/apps/api/src/services/address/schema/address.schema.ts @@ -1,13 +1,21 @@ import { z } from 'zod' -import prisma from '../../../prisma' -import { isAllUndefined } from '../../../util/object' +import prisma from '../../../prisma.js' +import { isAllUndefined } from '../../../util/object.js' export const addressSchema = z.strictObject({ street: z.string().optional(), - number: z.string().optional(), + streetNumber: z.string().optional(), zip: z.string().optional(), city: z.string().optional(), + country: z.string().optional(), + valid: z.boolean().optional(), + position: z + .strictObject({ + lat: z.number(), + lon: z.number(), + }) + .optional(), }) export async function findAddress(input: z.infer) { @@ -16,7 +24,7 @@ export async function findAddress(input: z.infer) { city: input.city, zip: input.zip, street: input.street, - number: input.number, + streetNumber: input.streetNumber, }, }) } @@ -30,7 +38,11 @@ export async function createOrUpdateAddress(input: z.infer zip: input.zip!, city: input.city!, street: input.street!, - number: input.number!, + streetNumber: input.streetNumber!, + country: input.country!, + valid: input.valid, + lat: input.position?.lat, + lon: input.position?.lon, } const existing = await findAddress(data) diff --git a/api/src/services/anmeldung/anmeldung.router.ts b/apps/api/src/services/anmeldung/anmeldung.router.ts similarity index 50% rename from api/src/services/anmeldung/anmeldung.router.ts rename to apps/api/src/services/anmeldung/anmeldung.router.ts index 715e6549..72465e36 100644 --- a/api/src/services/anmeldung/anmeldung.router.ts +++ b/apps/api/src/services/anmeldung/anmeldung.router.ts @@ -1,18 +1,17 @@ -/* eslint-disable prettier/prettier */ // Prettier ignored is because this file is generated -import { mergeRouters } from '../../trpc' +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' -import { anmeldungGliederungGetProcedure } from './anmeldungGliederungGet' -import { anmeldungGliederungCountProcedure, anmeldungGliederungdListProcedure } from './anmeldungGliederungList' -import { anmeldungGliederungPatchProcedure } from './anmeldungGliederungPatch' -import { anmeldungPublicCreateProcedure } from './anmeldungPublicCreate' -import { anmeldungTeilnehmerStornoProcedure } from './anmeldungTeilnehmerStorno' -import { anmeldungVerwaltungAblehnenProcedure } from './anmeldungVerwaltungAblehnen' -import { anmeldungVerwaltungAnnehmenProcedure } from './anmeldungVerwaltungAnnehmen' -import { anmeldungVerwaltungCreateProcedure } from './anmeldungVerwaltungCreate' -import { anmeldungVerwaltungGetProcedure } from './anmeldungVerwaltungGet' -import { anmeldungVerwaltungCountProcedure, anmeldungVerwaltungListProcedure } from './anmeldungVerwaltungList' -import { anmeldungVerwaltungPatchProcedure } from './anmeldungVerwaltungPatch' -import { anmeldungVerwaltungStornoProcedure } from './anmeldungVerwaltungStorno' +import { anmeldungGetProcedure } from './anmeldungGet.js' +import { anmeldungGliederungPatchProcedure } from './anmeldungGliederungPatch.js' +import { anmeldungCountProcedure, anmeldungListProcedure } from './anmeldungList.js' +import { anmeldungPublicCreateProcedure } from './anmeldungPublicCreate.js' +import { anmeldungTeilnehmerStornoProcedure } from './anmeldungTeilnehmerStorno.js' +import { anmeldungVerwaltungAblehnenProcedure } from './anmeldungVerwaltungAblehnen.js' +import { anmeldungVerwaltungAnnehmenProcedure } from './anmeldungVerwaltungAnnehmen.js' +import { anmeldungVerwaltungCreateProcedure } from './anmeldungVerwaltungCreate.js' +import { anmeldungVerwaltungPatchProcedure } from './anmeldungVerwaltungPatch.js' +import { anmeldungVerwaltungStornoProcedure } from './anmeldungVerwaltungStorno.js' +import { anmeldungZuordnenProcedure } from './anmeldungZuordnen.js' // Import Routes here - do not delete this line export const anmeldungRouter = mergeRouters( @@ -21,14 +20,12 @@ export const anmeldungRouter = mergeRouters( anmeldungVerwaltungAblehnenProcedure.router, anmeldungVerwaltungAnnehmenProcedure.router, anmeldungVerwaltungStornoProcedure.router, - anmeldungVerwaltungListProcedure.router, - anmeldungVerwaltungCountProcedure.router, anmeldungVerwaltungCreateProcedure.router, - anmeldungVerwaltungGetProcedure.router, anmeldungVerwaltungPatchProcedure.router, - anmeldungGliederungdListProcedure.router, - anmeldungGliederungCountProcedure.router, - anmeldungGliederungGetProcedure.router, - anmeldungGliederungPatchProcedure.router + anmeldungGliederungPatchProcedure.router, + anmeldungCountProcedure.router, + anmeldungListProcedure.router, + anmeldungGetProcedure.router, + anmeldungZuordnenProcedure.router // Add Routes here - do not delete this line ) diff --git a/apps/api/src/services/anmeldung/anmeldungGet.ts b/apps/api/src/services/anmeldung/anmeldungGet.ts new file mode 100644 index 00000000..d7866b4f --- /dev/null +++ b/apps/api/src/services/anmeldung/anmeldungGet.ts @@ -0,0 +1,95 @@ +import { Prisma, Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import type { AuthenticatedContext } from '../../trpc.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' + +const select = { + id: true, + status: true, + mahlzeiten: true, + uebernachtungsTage: true, + createdAt: true, + comment: true, + customFieldValues: { + select: { + id: true, + value: true, + field: true, + }, + }, + person: { + select: { + id: true, + firstname: true, + lastname: true, + birthday: true, + gender: true, + email: true, + telefon: true, + gliederung: { + select: { + id: true, + name: true, + edv: true, + }, + }, + essgewohnheit: true, + nahrungsmittelIntoleranzen: true, + weitereIntoleranzen: true, + notfallkontakte: true, + address: true, + photoId: true, + }, + }, + unterveranstaltung: { + select: { + id: true, + veranstaltung: { + select: { + id: true, + name: true, + meldeschluss: true, + }, + }, + }, + }, +} satisfies Prisma.AnmeldungSelect + +const inputSchema = z.strictObject({ + anmeldungId: z.number().optional(), + personId: z.number().optional(), +}) + +type InputSchema = z.infer + +function getWhere({ ctx, input }: { ctx: AuthenticatedContext; input: InputSchema }): Prisma.AnmeldungWhereInput { + const where: Prisma.AnmeldungWhereInput = { + OR: [ + { + personId: input.personId, + }, + { + id: input.anmeldungId, + }, + ], + } + + if (ctx.account?.role === 'USER') { + where.accountId = ctx.accountId + } + + return where +} + +export const anmeldungGetProcedure = defineProtectedQueryProcedure({ + key: 'get', + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN, Role.USER], + inputSchema, + handler: ({ ctx, input }) => + prisma.anmeldung.findMany({ + where: getWhere({ ctx, input }), + select, + }), +}) diff --git a/api/src/services/anmeldung/anmeldungGliederungList.ts b/apps/api/src/services/anmeldung/anmeldungGliederungList.ts similarity index 79% rename from api/src/services/anmeldung/anmeldungGliederungList.ts rename to apps/api/src/services/anmeldung/anmeldungGliederungList.ts index 92af87d6..5f03949f 100644 --- a/api/src/services/anmeldung/anmeldungGliederungList.ts +++ b/apps/api/src/services/anmeldung/anmeldungGliederungList.ts @@ -1,14 +1,14 @@ -import { AnmeldungStatus } from '@prisma/client' +import { AnmeldungStatus, Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { ZPaginationSchema } from '../../types/defineQuery' -import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin' +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import { ZPaginationSchema } from '../../types/defineQuery.js' +import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin.js' const filter = z.strictObject({ - unterveranstaltungId: z.number().optional(), veranstaltungId: z.number().optional(), + unterveranstaltungId: z.number().optional(), }) const where = (filter: { gliederungId: number; unterveranstaltungId?: number; veranstaltungId?: number }) => { @@ -29,10 +29,9 @@ const where = (filter: { gliederungId: number; unterveranstaltungId?: number; ve } } -export const anmeldungGliederungdListProcedure = defineProcedure({ +export const anmeldungGliederungdListProcedure = defineProtectedQueryProcedure({ key: 'gliederungList', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'] }, + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ pagination: ZPaginationSchema, filter: filter, @@ -64,8 +63,8 @@ export const anmeldungGliederungdListProcedure = defineProcedure({ id: true, firstname: true, lastname: true, + photoId: true, birthday: true, - konfektionsgroesse: true, gliederung: { select: { id: true, @@ -75,7 +74,6 @@ export const anmeldungGliederungdListProcedure = defineProcedure({ }, }, status: true, - tshirtBestellt: true, unterveranstaltung: { select: { veranstaltung: { @@ -95,10 +93,9 @@ export const anmeldungGliederungdListProcedure = defineProcedure({ }, }) -export const anmeldungGliederungCountProcedure = defineProcedure({ +export const anmeldungGliederungCountProcedure = defineProtectedQueryProcedure({ key: 'gliederungCount', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'] }, + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ filter: filter, }), @@ -118,6 +115,6 @@ export const anmeldungGliederungCountProcedure = defineProcedure({ }) ) const total = countEntries.reduce((acc, [, count]) => acc + Number(count), 0) - return { total, ...Object.fromEntries(countEntries) } + return { total, ...Object.fromEntries(countEntries) } as Record & { total: number } }, }) diff --git a/api/src/services/anmeldung/anmeldungGliederungPatch.ts b/apps/api/src/services/anmeldung/anmeldungGliederungPatch.ts similarity index 60% rename from api/src/services/anmeldung/anmeldungGliederungPatch.ts rename to apps/api/src/services/anmeldung/anmeldungGliederungPatch.ts index 5f4924b2..ccd87ea2 100644 --- a/api/src/services/anmeldung/anmeldungGliederungPatch.ts +++ b/apps/api/src/services/anmeldung/anmeldungGliederungPatch.ts @@ -1,13 +1,12 @@ import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' -export const anmeldungGliederungPatchProcedure = defineProcedure({ +export const anmeldungGliederungPatchProcedure = defineProtectedMutateProcedure({ key: 'gliederungPatch', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: [Role.GLIEDERUNG_ADMIN, Role.ADMIN] }, + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ id: z.number().int(), data: z.strictObject({ diff --git a/apps/api/src/services/anmeldung/anmeldungList.ts b/apps/api/src/services/anmeldung/anmeldungList.ts new file mode 100644 index 00000000..4ca5e8ac --- /dev/null +++ b/apps/api/src/services/anmeldung/anmeldungList.ts @@ -0,0 +1,120 @@ +import { AnmeldungStatus, Prisma, Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineOrderBy } from '../../types/defineOrderBy.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import { ZPaginationSchema } from '../../types/defineQuery.js' +import type { Context } from '../../context.js' + +const filterSchema = z.discriminatedUnion('type', [ + z.strictObject({ + type: z.literal('veranstaltung'), + veranstaltungId: z.number(), + }), + z.strictObject({ + type: z.literal('unterveranstaltung'), + unterveranstaltungId: z.number(), + }), + z.strictObject({ + type: z.literal('own'), + }) +]) + +type FilterSchema = z.infer + +function getWhere(ctx: Context, filter: FilterSchema): Prisma.AnmeldungWhereInput { + if (filter.type === 'veranstaltung') { + return { + unterveranstaltung: { + veranstaltungId: filter.veranstaltungId, + }, + } + } + else if (filter.type === 'unterveranstaltung') { + return { + unterveranstaltungId: filter.unterveranstaltungId, + } + } + + return { + accountId: ctx.accountId, + } +} + +export const anmeldungListProcedure = defineProtectedQueryProcedure({ + key: 'list', + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN, Role.USER], + inputSchema: z.strictObject({ + pagination: ZPaginationSchema, + filter: filterSchema, + orderBy: defineOrderBy(['status', 'createdBy']), + }), + async handler({ ctx, input }) { + const { skip, take } = input.pagination + const anmeldungen = await prisma.anmeldung.findMany({ + skip, + take, + where: getWhere(ctx, input.filter), + select: { + id: true, + person: { + select: { + id: true, + firstname: true, + lastname: true, + photoId: true, + birthday: true, + gliederung: { + select: { + id: true, + name: true, + }, + }, + }, + }, + status: true, + unterveranstaltung: { + select: { + veranstaltung: { + select: { + meldeschluss: true, + }, + }, + }, + }, + }, + orderBy: { + createdAt: 'desc', + }, + }) + + return anmeldungen + }, +}) + +export const anmeldungCountProcedure = defineProtectedQueryProcedure({ + key: 'count', + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN, Role.USER], + inputSchema: z.strictObject({ + filter: filterSchema, + }), + async handler({ ctx, input }) { + const countEntries = await Promise.all( + Object.values(AnmeldungStatus).map(async (status) => { + return [ + status, + await prisma.anmeldung.count({ + where: { + ...getWhere(ctx, input.filter), + status: status, + }, + }), + ] + }) + ) + + const total = countEntries.reduce((acc, [, count]) => acc + Number(count), 0) + return { total, ...Object.fromEntries(countEntries) } as Record & { total: number } + }, +}) diff --git a/apps/api/src/services/anmeldung/anmeldungProtectedGet.ts b/apps/api/src/services/anmeldung/anmeldungProtectedGet.ts new file mode 100644 index 00000000..d6435572 --- /dev/null +++ b/apps/api/src/services/anmeldung/anmeldungProtectedGet.ts @@ -0,0 +1,44 @@ +import { Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' + +const inputSchema = z.strictObject({ + anmeldungId: z.number().optional(), + personId: z.number().optional(), +}) + +export type AnmeldungProtectedGetSchema = z.infer + +export const anmeldungProtectedGetProcedure = defineProtectedQueryProcedure({ + key: 'gliederungGet', + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], + inputSchema, + handler: ({ input }) => + prisma.anmeldung.findMany({ + where: { + OR: [ + { + personId: input.personId, + }, + { + id: input.anmeldungId, + }, + ], + }, + include: { + customFieldValues: true, + person: { + include: { + gliederung: true, + }, + }, + unterveranstaltung: { + include: { + veranstaltung: true, + }, + }, + }, + }), +}) diff --git a/api/src/services/anmeldung/anmeldungPublicCreate.ts b/apps/api/src/services/anmeldung/anmeldungPublicCreate.ts similarity index 50% rename from api/src/services/anmeldung/anmeldungPublicCreate.ts rename to apps/api/src/services/anmeldung/anmeldungPublicCreate.ts index f271a979..43d8a76a 100644 --- a/api/src/services/anmeldung/anmeldungPublicCreate.ts +++ b/apps/api/src/services/anmeldung/anmeldungPublicCreate.ts @@ -2,12 +2,14 @@ import { TRPCError } from '@trpc/server' import dayjs from 'dayjs' import { z } from 'zod' -import prisma from '../../prisma' -import { customFieldValuesCreateMany, defineCustomFieldValues } from '../../types/defineCustomFieldValues' -import { defineProcedure } from '../../types/defineProcedure' -import logActivity from '../../util/activity' -import { sendMail } from '../../util/mail' -import { personSchema, getPersonCreateData } from '../person/schema/person.schema' +import prisma from '../../prisma.js' +import { customFieldValuesCreateMany, defineCustomFieldValues } from '../../types/defineCustomFieldValues.js' +import { definePublicMutateProcedure } from '../../types/defineProcedure.js' +import logActivity from '../../util/activity.js' +import { sendMail } from '../../util/mail.js' +import { getPersonCreateData, personSchema } from '../person/schema/person.schema.js' +import type { Context } from '../../context.js' +import { randomUUID } from 'node:crypto' export const inputSchema = z.strictObject({ data: personSchema.extend({ @@ -21,7 +23,13 @@ export const inputSchema = z.strictObject({ customFieldValues: defineCustomFieldValues(), }) -export async function handle(input: z.infer, isPublic: boolean) { +type HandleProps = { + ctx: Context + input: z.infer + isPublic: boolean +} + +export async function handle({ ctx, input, isPublic }: HandleProps) { const unterveranstaltung = await prisma.unterveranstaltung.findUniqueOrThrow({ where: { id: input.data.unterveranstaltungId, @@ -40,6 +48,11 @@ export async function handle(input: z.infer, isPublic: boole }, }, }, + gliederung: { + select: { + name: true, + }, + }, }, }) @@ -51,38 +64,33 @@ export async function handle(input: z.infer, isPublic: boole } const personData = await getPersonCreateData(input.data) - const person = await prisma.person.create({ - data: { - ...personData, - anmeldungen: { - create: { - unterveranstaltungId: unterveranstaltung.id, - mahlzeiten: input.data.mahlzeitenIds - ? { - connect: input.data.mahlzeitenIds.map((id) => ({ - id, - })), - } - : undefined, - uebernachtungsTage: input.data.uebernachtungsTage, - tshirtBestellt: input.data.tshirtBestellt, - comment: input.data.comment, - createdAt: new Date(), - customFieldValues: { - createMany: customFieldValuesCreateMany(input.customFieldValues), - }, - }, - }, - }, + data: personData, select: { id: true, - anmeldungen: { - take: 1, - orderBy: { - createdAt: 'desc', - }, + }, + }) + + const assignmentCode = ctx.authenticated ? null : randomUUID() + const anmeldung = await prisma.anmeldung.create({ + data: { + unterveranstaltungId: unterveranstaltung.id, + accountId: ctx.accountId, + personId: person.id, + mahlzeiten: input.data.mahlzeitenIds + ? { + connect: input.data.mahlzeitenIds.map((id) => ({ + id, + })), + } + : undefined, + uebernachtungsTage: input.data.uebernachtungsTage, + comment: input.data.comment, + createdAt: new Date(), + customFieldValues: { + createMany: customFieldValuesCreateMany(input.customFieldValues), }, + assignmentCode, }, }) @@ -97,7 +105,7 @@ export async function handle(input: z.infer, isPublic: boole type: 'CREATE', description: 'new public registration', subjectType: 'anmeldung', - subjectId: person.anmeldungen[0].id, + subjectId: anmeldung.id, }), ]) @@ -105,18 +113,21 @@ export async function handle(input: z.infer, isPublic: boole to: input.data.email, subject: `${unterveranstaltung?.veranstaltung?.hostname?.hostname} Anmeldung erfolgreich`, categories: ['anmeldung', 'create'], - html: `Vielen Dank für deine Anmeldung zur Veranstaltung ${unterveranstaltung.veranstaltung.name} .`, + template: 'registration-successful', + variables: { + name: `${personData.firstname} ${personData.lastname}`, + gliederung: unterveranstaltung.gliederung.name, + veranstaltung: unterveranstaltung.veranstaltung.name, + hostname: unterveranstaltung.veranstaltung.hostname!.hostname, + assignmentCode, + }, }) - - return person } -export const anmeldungPublicCreateProcedure = defineProcedure({ +export const anmeldungPublicCreateProcedure = definePublicMutateProcedure({ key: 'publicCreate', - method: 'mutation', - protection: { type: 'public' }, inputSchema: inputSchema, - async handler(options) { - await handle(options.input, true) + async handler({ ctx, input }) { + await handle({ ctx, input, isPublic: true }) }, }) diff --git a/api/src/services/anmeldung/anmeldungTeilnehmerStorno.ts b/apps/api/src/services/anmeldung/anmeldungTeilnehmerStorno.ts similarity index 70% rename from api/src/services/anmeldung/anmeldungTeilnehmerStorno.ts rename to apps/api/src/services/anmeldung/anmeldungTeilnehmerStorno.ts index 1c266a50..d305365c 100644 --- a/api/src/services/anmeldung/anmeldungTeilnehmerStorno.ts +++ b/apps/api/src/services/anmeldung/anmeldungTeilnehmerStorno.ts @@ -1,13 +1,13 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import logActivity from '../../util/activity' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import logActivity from '../../util/activity.js' -export const anmeldungTeilnehmerStornoProcedure = defineProcedure({ +export const anmeldungTeilnehmerStornoProcedure = defineProtectedMutateProcedure({ key: 'teilnehmerStorno', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['GLIEDERUNG_ADMIN'] }, + roleIds: [Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ data: z.strictObject({ anmeldungId: z.number().int(), diff --git a/api/src/services/anmeldung/anmeldungVerwaltungAblehnen.ts b/apps/api/src/services/anmeldung/anmeldungVerwaltungAblehnen.ts similarity index 67% rename from api/src/services/anmeldung/anmeldungVerwaltungAblehnen.ts rename to apps/api/src/services/anmeldung/anmeldungVerwaltungAblehnen.ts index d6d502f4..eef0de0f 100644 --- a/api/src/services/anmeldung/anmeldungVerwaltungAblehnen.ts +++ b/apps/api/src/services/anmeldung/anmeldungVerwaltungAblehnen.ts @@ -1,16 +1,15 @@ import { AnmeldungStatus, Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import logActivity from '../../util/activity' -import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin' -import { sendMail } from '../../util/mail' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import logActivity from '../../util/activity.js' +import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin.js' +import { sendMail } from '../../util/mail.js' -export const anmeldungVerwaltungAblehnenProcedure = defineProcedure({ +export const anmeldungVerwaltungAblehnenProcedure = defineProtectedMutateProcedure({ key: 'verwaltungAblehnen', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN] }, + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ anmeldungId: z.number().int(), }), @@ -27,6 +26,11 @@ export const anmeldungVerwaltungAblehnenProcedure = defineProcedure({ veranstaltung: { select: { name: true, + hostname: { + select: { + hostname: true, + }, + }, }, }, }, @@ -68,6 +72,11 @@ export const anmeldungVerwaltungAblehnenProcedure = defineProcedure({ firstname: true, lastname: true, email: true, + gliederung: { + select: { + name: true, + }, + }, }, }) if (!person) { @@ -77,7 +86,13 @@ export const anmeldungVerwaltungAblehnenProcedure = defineProcedure({ to: person.email, subject: 'Anmeldung abgelehnt', categories: ['anemldung', 'abgelehnt'], - html: `Hallo ${person.firstname} ${person.lastname},\n\n\nDeine Anmeldung für ${anmeldung?.unterveranstaltung.veranstaltung.name} wurde abgelehnt.\n\nViele Grüße,\nDein Orga-Team`, + template: 'registration-rejected', + variables: { + name: `${person.firstname} ${person.lastname}`, + gliederung: person.gliederung!.name, + veranstaltung: anmeldung!.unterveranstaltung.veranstaltung.name, + hostname: anmeldung!.unterveranstaltung.veranstaltung.hostname!.hostname, + }, }) return { success: true, diff --git a/api/src/services/anmeldung/anmeldungVerwaltungAnnehmen.ts b/apps/api/src/services/anmeldung/anmeldungVerwaltungAnnehmen.ts similarity index 67% rename from api/src/services/anmeldung/anmeldungVerwaltungAnnehmen.ts rename to apps/api/src/services/anmeldung/anmeldungVerwaltungAnnehmen.ts index 4bf0914e..b8991cb6 100644 --- a/api/src/services/anmeldung/anmeldungVerwaltungAnnehmen.ts +++ b/apps/api/src/services/anmeldung/anmeldungVerwaltungAnnehmen.ts @@ -1,16 +1,15 @@ import { AnmeldungStatus, Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import logActivity from '../../util/activity' -import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin' -import { sendMail } from '../../util/mail' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import logActivity from '../../util/activity.js' +import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin.js' +import { sendMail } from '../../util/mail.js' -export const anmeldungVerwaltungAnnehmenProcedure = defineProcedure({ +export const anmeldungVerwaltungAnnehmenProcedure = defineProtectedMutateProcedure({ key: 'verwaltungAnnehmen', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN] }, + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ anmeldungId: z.number().int(), }), @@ -27,6 +26,11 @@ export const anmeldungVerwaltungAnnehmenProcedure = defineProcedure({ veranstaltung: { select: { name: true, + hostname: { + select: { + hostname: true, + }, + }, }, }, }, @@ -68,6 +72,11 @@ export const anmeldungVerwaltungAnnehmenProcedure = defineProcedure({ firstname: true, lastname: true, email: true, + gliederung: { + select: { + name: true, + }, + }, }, }) if (!person) { @@ -77,7 +86,13 @@ export const anmeldungVerwaltungAnnehmenProcedure = defineProcedure({ to: person.email, subject: 'Anmeldung bestätigt', categories: ['anemldung', 'bestaetigung'], - html: `Hallo ${person.firstname} ${person.lastname},\n\n\nDeine Anmeldung für ${anmeldung?.unterveranstaltung.veranstaltung.name} wurde bestätigt.\n\nViele Grüße,\nDein Orga-Team`, + template: 'registration-confirmed', + variables: { + name: `${person.firstname} ${person.lastname}`, + gliederung: person.gliederung!.name, + veranstaltung: anmeldung!.unterveranstaltung.veranstaltung.name, + hostname: anmeldung!.unterveranstaltung.veranstaltung.hostname!.hostname, + }, }) return { success: true, diff --git a/apps/api/src/services/anmeldung/anmeldungVerwaltungCreate.ts b/apps/api/src/services/anmeldung/anmeldungVerwaltungCreate.ts new file mode 100644 index 00000000..716cce93 --- /dev/null +++ b/apps/api/src/services/anmeldung/anmeldungVerwaltungCreate.ts @@ -0,0 +1,13 @@ +import { Role } from '@prisma/client' + +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import { handle, inputSchema } from './anmeldungPublicCreate.js' + +export const anmeldungVerwaltungCreateProcedure = defineProtectedMutateProcedure({ + key: 'verwaltungCreate', + inputSchema: inputSchema, + async handler({ ctx, input }) { + await handle({ ctx, input, isPublic: false }) + }, + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], +}) diff --git a/api/src/services/anmeldung/anmeldungVerwaltungPatch.ts b/apps/api/src/services/anmeldung/anmeldungVerwaltungPatch.ts similarity index 63% rename from api/src/services/anmeldung/anmeldungVerwaltungPatch.ts rename to apps/api/src/services/anmeldung/anmeldungVerwaltungPatch.ts index a5a94f41..559fd362 100644 --- a/api/src/services/anmeldung/anmeldungVerwaltungPatch.ts +++ b/apps/api/src/services/anmeldung/anmeldungVerwaltungPatch.ts @@ -1,13 +1,12 @@ import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' -export const anmeldungVerwaltungPatchProcedure = defineProcedure({ +export const anmeldungVerwaltungPatchProcedure = defineProtectedMutateProcedure({ key: 'verwaltungPatch', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: [Role.GLIEDERUNG_ADMIN, Role.ADMIN] }, + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ id: z.number().int(), data: z.strictObject({ diff --git a/api/src/services/anmeldung/anmeldungVerwaltungStorno.ts b/apps/api/src/services/anmeldung/anmeldungVerwaltungStorno.ts similarity index 67% rename from api/src/services/anmeldung/anmeldungVerwaltungStorno.ts rename to apps/api/src/services/anmeldung/anmeldungVerwaltungStorno.ts index 7c019a62..ce10626a 100644 --- a/api/src/services/anmeldung/anmeldungVerwaltungStorno.ts +++ b/apps/api/src/services/anmeldung/anmeldungVerwaltungStorno.ts @@ -1,16 +1,15 @@ import { AnmeldungStatus, Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import logActivity from '../../util/activity' -import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin' -import { sendMail } from '../../util/mail' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import logActivity from '../../util/activity.js' +import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin.js' +import { sendMail } from '../../util/mail.js' -export const anmeldungVerwaltungStornoProcedure = defineProcedure({ +export const anmeldungVerwaltungStornoProcedure = defineProtectedMutateProcedure({ key: 'verwaltungStorno', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN] }, + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ anmeldungId: z.number().int(), }), @@ -27,6 +26,11 @@ export const anmeldungVerwaltungStornoProcedure = defineProcedure({ veranstaltung: { select: { name: true, + hostname: { + select: { + hostname: true, + }, + }, }, }, }, @@ -69,6 +73,11 @@ export const anmeldungVerwaltungStornoProcedure = defineProcedure({ firstname: true, lastname: true, email: true, + gliederung: { + select: { + name: true, + }, + }, }, }) if (!person) { @@ -78,7 +87,13 @@ export const anmeldungVerwaltungStornoProcedure = defineProcedure({ to: person.email, subject: 'Anmeldung storniert', categories: ['anemldung', 'storno'], - html: `Hallo ${person.firstname} ${person.lastname},\n\n\nDeine Anmeldung für ${anmeldung?.unterveranstaltung.veranstaltung.name} wurde storniert.\n\nViele Grüße,\nDein Orga-Team`, + template: 'registration-canceled', + variables: { + name: `${person.firstname} ${person.lastname}`, + gliederung: person.gliederung!.name, + veranstaltung: anmeldung!.unterveranstaltung.veranstaltung.name, + hostname: anmeldung!.unterveranstaltung.veranstaltung.hostname!.hostname, + }, }) return { success: true, diff --git a/apps/api/src/services/anmeldung/anmeldungZuordnen.ts b/apps/api/src/services/anmeldung/anmeldungZuordnen.ts new file mode 100644 index 00000000..1c0c6839 --- /dev/null +++ b/apps/api/src/services/anmeldung/anmeldungZuordnen.ts @@ -0,0 +1,37 @@ +import { Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import { TRPCError } from '@trpc/server' + +export const anmeldungZuordnenProcedure = defineProtectedMutateProcedure({ + key: 'zuordnen', + roleIds: [Role.USER, Role.GLIEDERUNG_ADMIN, Role.ADMIN], + inputSchema: z.string().uuid(), + handler: async ({ ctx, input }) => { + const anmeldung = await prisma.anmeldung.findFirst({ + where: { + assignmentCode: input, + }, + select: { + id: true, + }, + }) + if (anmeldung === null) { + throw new TRPCError({ + code: 'NOT_FOUND', + }) + } + + await prisma.anmeldung.update({ + where: { + id: anmeldung.id, + }, + data: { + accountId: ctx.accountId, + assignmentCode: null, + }, + }) + }, +}) diff --git a/api/src/services/anmeldung/schema/anmeldung.schema.ts b/apps/api/src/services/anmeldung/schema/anmeldung.schema.ts similarity index 100% rename from api/src/services/anmeldung/schema/anmeldung.schema.ts rename to apps/api/src/services/anmeldung/schema/anmeldung.schema.ts diff --git a/api/src/services/authentication/authentication.router.ts b/apps/api/src/services/authentication/authentication.router.ts similarity index 54% rename from api/src/services/authentication/authentication.router.ts rename to apps/api/src/services/authentication/authentication.router.ts index 85cb77fa..cecf9ed5 100644 --- a/api/src/services/authentication/authentication.router.ts +++ b/apps/api/src/services/authentication/authentication.router.ts @@ -1,10 +1,10 @@ -/* eslint-disable prettier/prettier */ // Prettier ignored is because this file is generated -import { mergeRouters } from '../../trpc' +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' -import { authenticationLoginProcedure } from './authenticationLogin' +import { authenticationLoginProcedure } from './authenticationLogin.js' // Import Routes here - do not delete this line export const authenticationRouter = mergeRouters( - authenticationLoginProcedure.router, + authenticationLoginProcedure.router // Add Routes here - do not delete this line ) diff --git a/apps/api/src/services/authentication/authenticationLogin.ts b/apps/api/src/services/authentication/authenticationLogin.ts new file mode 100644 index 00000000..da9d25f9 --- /dev/null +++ b/apps/api/src/services/authentication/authenticationLogin.ts @@ -0,0 +1,28 @@ +import { z } from 'zod' + +import { authenticationLogin } from '../../authentication.js' +import prisma from '../../prisma.js' +import { definePublicMutateProcedure } from '../../types/defineProcedure.js' +import { personSelfSelect } from '../person/personAuthenticatedGet.js' + +export const authenticationLoginProcedure = definePublicMutateProcedure({ + key: 'login', + inputSchema: z.strictObject({ + email: z.string(), + password: z.string(), + }), + async handler({ input }) { + const { accessToken, user } = await authenticationLogin(input) + const account = await prisma.account.findUniqueOrThrow({ + where: { + id: user.id, + }, + select: personSelfSelect, + }) + + return { + accessToken, + account, + } + }, +}) diff --git a/api/src/services/customFields/customFieldValuesUpdate.ts b/apps/api/src/services/customFields/customFieldValuesUpdate.ts similarity index 70% rename from api/src/services/customFields/customFieldValuesUpdate.ts rename to apps/api/src/services/customFields/customFieldValuesUpdate.ts index afb7d479..7ef4dd84 100644 --- a/api/src/services/customFields/customFieldValuesUpdate.ts +++ b/apps/api/src/services/customFields/customFieldValuesUpdate.ts @@ -1,15 +1,14 @@ import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import logActivity from '../../util/activity' -import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import logActivity from '../../util/activity.js' +import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin.js' -export const customFieldValuesUpdate = defineProcedure({ +export const customFieldValuesUpdate = defineProtectedMutateProcedure({ key: 'valuesUpdate', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN] }, + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN, Role.USER], inputSchema: z.strictObject({ data: z.array( z.strictObject({ @@ -19,12 +18,14 @@ export const customFieldValuesUpdate = defineProcedure({ ), anmeldungId: z.number().int(), }), - async handler(options) { + async handler({ ctx: { account, accountId }, input }) { type AnmeldungWhereUniqueInput = Parameters[0]['where'] - const where: AnmeldungWhereUniqueInput = { id: options.input.anmeldungId } + const where: AnmeldungWhereUniqueInput = { id: input.anmeldungId } - if (options.ctx.account.role !== Role.ADMIN) { - const gliederung = await getGliederungRequireAdmin(options.ctx.accountId) + if (account.role === Role.USER) { + where.accountId = accountId + } else if (account.role === Role.GLIEDERUNG_ADMIN) { + const gliederung = await getGliederungRequireAdmin(accountId) where.unterveranstaltung = { gliederungId: gliederung.id, } @@ -51,12 +52,12 @@ export const customFieldValuesUpdate = defineProcedure({ } const res = await Promise.all( - options.input.data.map(async (element) => { + input.data.map(async (element) => { const existingValues = ( await prisma.customFieldValue.findMany({ where: { fieldId: element.id, - anmeldungId: options.input.anmeldungId, + anmeldungId: input.anmeldungId, }, select: { id: true, @@ -74,17 +75,17 @@ export const customFieldValuesUpdate = defineProcedure({ value: element.value, }, }) - logActivity({ + await logActivity({ type: 'UPDATE', description: 'Benutzerdefinierten Wert aktualisiert', subjectType: 'customFieldValues', subjectId: existingValues.id, - causerId: options.ctx.accountId, + causerId: accountId, metadata: { oldValue: existingValues.value, value: element.value, fieldId: element.id, - anmeldungId: options.input.anmeldungId, + anmeldungId: input.anmeldungId, }, }) } else { @@ -92,19 +93,19 @@ export const customFieldValuesUpdate = defineProcedure({ data: { value: element.value, fieldId: element.id, - anmeldungId: options.input.anmeldungId, + anmeldungId: input.anmeldungId, }, }) - logActivity({ + await logActivity({ type: 'CREATE', description: 'Benutzerdefinierten Wert hinzugefügt', subjectType: 'customFieldValues', subjectId: res.id, - causerId: options.ctx.accountId, + causerId: accountId, metadata: { value: element.value, fieldId: element.id, - anmeldungId: options.input.anmeldungId, + anmeldungId: input.anmeldungId, }, }) } diff --git a/api/src/services/customFields/customFields.router.ts b/apps/api/src/services/customFields/customFields.router.ts similarity index 53% rename from api/src/services/customFields/customFields.router.ts rename to apps/api/src/services/customFields/customFields.router.ts index 2731ff41..f7d75c3d 100644 --- a/api/src/services/customFields/customFields.router.ts +++ b/apps/api/src/services/customFields/customFields.router.ts @@ -1,12 +1,14 @@ -/* eslint-disable prettier/prettier */ // Prettier ignored is because this file is generated -import { mergeRouters } from '../../trpc' +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' -import { customFieldsGet } from './customFieldsGet' -import { customFieldsList } from './customFieldsList' -import { customFieldsVeranstaltungCreate } from './customFieldsVeranstaltungCreate' -import { customFieldsVeranstaltungDelete } from './customFieldsVeranstaltungDelete' -import { customFieldsVeranstaltungUpdate } from './customFieldsVeranstaltungUpdate' -import { customFieldValuesUpdate } from './customFieldValuesUpdate' +import { customFieldsGet } from './customFieldsGet.js' +import { customFieldsList } from './customFieldsList.js' +import { customFieldsTemplates } from './customFieldsTemplates.js' +import { customFieldsUnterveranstaltungCreate } from './customFieldsUnterveranstaltungCreate.js' +import { customFieldsVeranstaltungCreate } from './customFieldsVeranstaltungCreate.js' +import { customFieldsVeranstaltungDelete } from './customFieldsVeranstaltungDelete.js' +import { customFieldsVeranstaltungUpdate } from './customFieldsVeranstaltungUpdate.js' +import { customFieldValuesUpdate } from './customFieldValuesUpdate.js' // Import Routes here - do not delete this line export const customFieldsRouter = mergeRouters( @@ -15,6 +17,8 @@ export const customFieldsRouter = mergeRouters( customFieldsVeranstaltungCreate.router, customFieldsVeranstaltungUpdate.router, customFieldsVeranstaltungDelete.router, + customFieldsUnterveranstaltungCreate.router, customFieldValuesUpdate.router, + customFieldsTemplates.router // Add Routes here - do not delete this line ) diff --git a/api/src/services/customFields/customFieldsGet.ts b/apps/api/src/services/customFields/customFieldsGet.ts similarity index 60% rename from api/src/services/customFields/customFieldsGet.ts rename to apps/api/src/services/customFields/customFieldsGet.ts index a0c2e3bc..fa2b90f7 100644 --- a/api/src/services/customFields/customFieldsGet.ts +++ b/apps/api/src/services/customFields/customFieldsGet.ts @@ -1,13 +1,13 @@ +import { Role } from '@prisma/client' import { TRPCError } from '@trpc/server' import { z } from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' -export const customFieldsGet = defineProcedure({ +export const customFieldsGet = defineProtectedQueryProcedure({ key: 'get', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'] }, + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ id: z.number(), }), diff --git a/apps/api/src/services/customFields/customFieldsList.ts b/apps/api/src/services/customFields/customFieldsList.ts new file mode 100644 index 00000000..01c81d2a --- /dev/null +++ b/apps/api/src/services/customFields/customFieldsList.ts @@ -0,0 +1,57 @@ +import { z } from 'zod' + +import prisma from '../../prisma.js' +import { definePublicQueryProcedure } from '../../types/defineProcedure.js' +import { CustomFieldPosition, Prisma } from '@prisma/client' + +export const customFieldsList = definePublicQueryProcedure({ + key: 'list', + inputSchema: z.strictObject({ + entity: z.enum(['veranstaltung', 'unterveranstaltung']), + entityId: z.number(), + position: z.nativeEnum(CustomFieldPosition).optional(), + }), + async handler({ input }) { + const customFieldsFilter: Prisma.CustomFieldWhereInput = {} + if (input.position !== undefined) { + customFieldsFilter.positions = { + hasSome: [input.position] + } + } + + if (input.entity === 'veranstaltung') { + const veranstaltung = await prisma.veranstaltung.findUniqueOrThrow({ + where: { + id: input.entityId, + }, + include: { + customFields: { + where: customFieldsFilter, + }, + }, + }) + + return veranstaltung.customFields + } else if (input.entity === 'unterveranstaltung') { + const ausschreibung = await prisma.unterveranstaltung.findUniqueOrThrow({ + where: { + id: input.entityId, + }, + include: { + customFields: true, + veranstaltung: { + include: { + customFields: { + where: customFieldsFilter, + }, + }, + }, + }, + }) + + return [...ausschreibung.veranstaltung.customFields, ...ausschreibung.customFields] + } + + return [] + }, +}) diff --git a/apps/api/src/services/customFields/customFieldsTemplates.ts b/apps/api/src/services/customFields/customFieldsTemplates.ts new file mode 100644 index 00000000..4514d438 --- /dev/null +++ b/apps/api/src/services/customFields/customFieldsTemplates.ts @@ -0,0 +1,12 @@ +import { Role } from '@prisma/client' +import { z } from 'zod' + +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import templates from './schema/templates.js' + +export const customFieldsTemplates = defineProtectedQueryProcedure({ + key: 'listTemplates', + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], + inputSchema: z.void(), + handler: () => templates, +}) diff --git a/apps/api/src/services/customFields/customFieldsUnterveranstaltungCreate.ts b/apps/api/src/services/customFields/customFieldsUnterveranstaltungCreate.ts new file mode 100644 index 00000000..9b25f1db --- /dev/null +++ b/apps/api/src/services/customFields/customFieldsUnterveranstaltungCreate.ts @@ -0,0 +1,23 @@ +import { Role } from '@prisma/client' +import { z } from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' + +import { customFieldSchema } from './schema/customField.schema.js' + +export const customFieldsUnterveranstaltungCreate = defineProtectedMutateProcedure({ + key: 'unterveranstaltungCreate', + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], + inputSchema: z.strictObject({ + unterveranstaltungId: z.number(), + data: customFieldSchema, + }), + handler: ({ input }) => + prisma.customField.create({ + data: { + ...input.data, + unterveranstaltungId: input.unterveranstaltungId, + }, + }), +}) diff --git a/apps/api/src/services/customFields/customFieldsVeranstaltungCreate.ts b/apps/api/src/services/customFields/customFieldsVeranstaltungCreate.ts new file mode 100644 index 00000000..5e1370f3 --- /dev/null +++ b/apps/api/src/services/customFields/customFieldsVeranstaltungCreate.ts @@ -0,0 +1,55 @@ +import { Role } from '@prisma/client' +import { z } from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' + +import { customFieldSchema } from './schema/customField.schema.js' +import templates from './schema/templates.js' +import { TRPCError } from '@trpc/server' + +export const customFieldsVeranstaltungCreate = defineProtectedMutateProcedure({ + key: 'veranstaltungCreate', + roleIds: [Role.ADMIN], + inputSchema: z.discriminatedUnion('type', [ + z.strictObject({ + type: z.literal('new'), + veranstaltungId: z.number(), + data: customFieldSchema, + }), + z.strictObject({ + type: z.literal('fromTemplate'), + veranstaltungId: z.number(), + template: z.string(), + }), + ]), + handler: ({ input }) => { + if (input.type === 'new') { + return prisma.customField.create({ + data: { + ...input.data, + veranstaltungId: input.veranstaltungId, + }, + }) + } + + const template = templates[input.template] + if (template === undefined) { + throw new TRPCError({ + code: 'NOT_FOUND', + message: `No template named ${input.template}`, + }) + } + + return prisma.customField.create({ + data: { + ...template, + veranstaltung: { + connect: { + id: input.veranstaltungId, + }, + }, + }, + }) + }, +}) diff --git a/api/src/services/customFields/customFieldsVeranstaltungDelete.ts b/apps/api/src/services/customFields/customFieldsVeranstaltungDelete.ts similarity index 67% rename from api/src/services/customFields/customFieldsVeranstaltungDelete.ts rename to apps/api/src/services/customFields/customFieldsVeranstaltungDelete.ts index 9981c4b8..f6876733 100644 --- a/api/src/services/customFields/customFieldsVeranstaltungDelete.ts +++ b/apps/api/src/services/customFields/customFieldsVeranstaltungDelete.ts @@ -1,13 +1,13 @@ +import { Role } from '@prisma/client' import { TRPCError } from '@trpc/server' import { z } from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' -export const customFieldsVeranstaltungDelete = defineProcedure({ - key: 'verwaltungDelete', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, +export const customFieldsVeranstaltungDelete = defineProtectedMutateProcedure({ + key: 'veranstaltungDelete', + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ veranstaltungId: z.number(), fieldId: z.number(), diff --git a/apps/api/src/services/customFields/customFieldsVeranstaltungUpdate.ts b/apps/api/src/services/customFields/customFieldsVeranstaltungUpdate.ts new file mode 100644 index 00000000..d8e0fd73 --- /dev/null +++ b/apps/api/src/services/customFields/customFieldsVeranstaltungUpdate.ts @@ -0,0 +1,26 @@ +import { Role } from '@prisma/client' +import { z } from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' + +import { customFieldSchema } from './schema/customField.schema.js' + +export const customFieldsVeranstaltungUpdate = defineProtectedMutateProcedure({ + key: 'veranstaltungUpdate', + roleIds: [Role.ADMIN], + inputSchema: z.strictObject({ + fieldId: z.number(), + data: customFieldSchema, + }), + async handler({ input }) { + return await prisma.customField.update({ + where: { + id: input.fieldId, + }, + data: { + ...input.data, + }, + }) + }, +}) diff --git a/apps/api/src/services/customFields/schema/customField.schema.ts b/apps/api/src/services/customFields/schema/customField.schema.ts new file mode 100644 index 00000000..8172e451 --- /dev/null +++ b/apps/api/src/services/customFields/schema/customField.schema.ts @@ -0,0 +1,11 @@ +import { CustomFieldPosition, CustomFieldType } from '@prisma/client' +import { z } from 'zod' + +export const customFieldSchema = z.strictObject({ + name: z.string().min(1), + description: z.string().nullable(), + type: z.nativeEnum(CustomFieldType), + required: z.boolean(), + options: z.array(z.string()), + positions: z.nativeEnum(CustomFieldPosition).array(), +}) diff --git a/apps/api/src/services/customFields/schema/templates.ts b/apps/api/src/services/customFields/schema/templates.ts new file mode 100644 index 00000000..4acf1a9e --- /dev/null +++ b/apps/api/src/services/customFields/schema/templates.ts @@ -0,0 +1,87 @@ +import type { Prisma } from '@prisma/client' + +type CustomField = Prisma.CustomFieldCreateInput + +const customFieldKonfektionsgroesse: CustomField = { + name: 'Konfektionsgröße', + type: 'BASIC_DROPDOWN', + description: 'Gib hier deine gewünschte T-Shirt Größe an. Es gelten Standardmaße.', + options: [ + 'JUNIOR_98_104', + 'JUNIOR_110_116', + 'JUNIOR_122_128', + 'JUNIOR_134_140', + 'JUNIOR_146_152', + 'JUNIOR_158_164', + 'XS', + 'S', + 'M', + 'L', + 'XL', + 'XXL', + 'XXXL', + ], +} + +const customFieldFahrerlaubnis: CustomField = { + name: 'Fahrerlaubnis', + type: 'BASIC_DROPDOWN', + description: 'Wenn du einen Führerschein hast, gib hier bitte deine Fahrerlaubnisklasse an.', + options: ['B', 'BE', 'C', 'CE', 'D1', 'D', 'D1E', 'DE', 'T', 'L'], +} + +const customFieldSchwimmabzeichen: CustomField = { + name: 'Schwimmabzeichen', + type: 'BASIC_DROPDOWN', + description: 'Gib hier bitte dein aktuellstes Schwimmabzeichen an.', + options: ['DSA Bronze', 'DSA Silber', 'DSA Gold', 'Juniorretter', 'DRSA Bronze', 'DRSA Silber', 'DRSA Gold'], +} + +const customFieldFunk: CustomField = { + name: 'Funkausbildung', + type: 'BASIC_SELECT', + options: [ + 'DLRG_SPRECHFUNKER', + 'BOS_SPRECHFUNKER_ANALOG', + 'BOS_SPRECHFUNKER_DIGITAL', + 'AUSBILDER_SPRECHFUNK', + 'AUSBILDER_BOS_SPRECHFUNK', + 'MULTIPLIKATOR_SPRECHFUNK', + 'MULTIPLIKATOR_BOS_SPRECHFUNK', + 'EINSATZFAEHIGKEIT', + ], +} + +const customFieldErsteHilfeAusbildung: CustomField = { + name: 'Erste Hilfe Ausbildung', + type: 'BASIC_SELECT', + options: [ + 'EINWEISER_EHSH', + 'AUSBILDER_EHSH_MODUL_1_2', + 'AUSBILDER_EHSH_MODUL_3', + 'MODULE_1', + 'MODULE_2', + 'MODULE_3', + 'AUSBILDUNG', + 'KINDERNOTFAELLE', + 'BILDUNGS_UND_BETREUUNGSEINRICHTUNGEN_KINDER', + 'AUSBILDER', + ], +} + +const customFieldSanAusbildung: CustomField = { + name: 'Sanitätsausbildung', + type: 'BASIC_SELECT', + options: ['SAN_A', 'SAN_B', 'FORTBILDUNG', 'AUSBILDER'], +} + +const templates: Record = [ + customFieldKonfektionsgroesse, + customFieldFahrerlaubnis, + customFieldSchwimmabzeichen, + customFieldFunk, + customFieldErsteHilfeAusbildung, + customFieldSanAusbildung, +].reduce((prev, curr) => ({ ...prev, [curr.name]: curr }), {}) + +export default templates diff --git a/apps/api/src/services/faqs/faqCreateProcedure.ts b/apps/api/src/services/faqs/faqCreateProcedure.ts new file mode 100644 index 00000000..2f682493 --- /dev/null +++ b/apps/api/src/services/faqs/faqCreateProcedure.ts @@ -0,0 +1,41 @@ +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import { faqSchema } from './faqs.schema.js' + +export const faqCreateProcedure = defineProtectedMutateProcedure({ + key: 'create', + roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'], + inputSchema: z.strictObject({ + unterveranstaltungId: z.number().int(), + faq: faqSchema, + }), + handler: async ({ input: { faq, unterveranstaltungId } }) => { + await prisma.faq.create({ + data: { + question: faq.question, + answer: faq.answer, + unterveranstaltung: { + connect: { + id: unterveranstaltungId, + }, + }, + category: { + connectOrCreate: { + create: { + name: faq.category, + unterveranstaltungId, + }, + where: { + name_unterveranstaltungId: { + name: faq.category, + unterveranstaltungId, + }, + }, + }, + }, + }, + }) + }, +}) diff --git a/apps/api/src/services/faqs/faqDeleteProcecure.ts b/apps/api/src/services/faqs/faqDeleteProcecure.ts new file mode 100644 index 00000000..87052556 --- /dev/null +++ b/apps/api/src/services/faqs/faqDeleteProcecure.ts @@ -0,0 +1,28 @@ +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' + +export const faqDeleteProcedure = defineProtectedMutateProcedure({ + key: 'delete', + roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'], + inputSchema: z.number().int(), + handler: async ({ input }) => { + await prisma.faq.delete({ + where: { + id: input, + }, + }) + + // delete empty categories + await prisma.faqCategory.deleteMany({ + where: { + faqs: { + every: { + id: input, + }, + }, + }, + }) + }, +}) diff --git a/apps/api/src/services/faqs/faqListProcedure.ts b/apps/api/src/services/faqs/faqListProcedure.ts new file mode 100644 index 00000000..0008574a --- /dev/null +++ b/apps/api/src/services/faqs/faqListProcedure.ts @@ -0,0 +1,69 @@ +import z from 'zod' + +import { groupBy } from '@codeanker/helpers' +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' + +export async function listFaqs(unterveranstaltungId: number) { + const list = await prisma.faq.findMany({ + where: { + unterveranstaltung: { + every: { + id: unterveranstaltungId, + }, + }, + }, + select: { + id: true, + question: true, + answer: true, + category: { + select: { + name: true, + }, + }, + }, + }) + + const groups = groupBy( + list.map((v) => ({ ...v, category: v.category.name })), + ({ category }) => category + ) + + return Object.fromEntries(Object.entries(groups).sort(([a], [b]) => a.localeCompare(b))) +} + +export const faqListProcedure = defineProtectedQueryProcedure({ + key: 'list', + roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'], + inputSchema: z.strictObject({ + unterveranstaltungId: z.number().int(), + }), + handler: ({ input }) => listFaqs(input.unterveranstaltungId), +}) + +export const faqCategorySearchProcedure = defineProtectedQueryProcedure({ + key: 'searchCategory', + roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'], + inputSchema: z.strictObject({ + term: z.string().optional(), + }), + handler: async ({ input: { term } }) => { + const result = await prisma.faqCategory.findMany({ + take: 10, + orderBy: { + name: 'asc', + }, + where: { + name: { + contains: term, + }, + }, + select: { + name: true, + }, + }) + + return result.map((c) => c.name) + }, +}) diff --git a/apps/api/src/services/faqs/faqUpdateProcedure.ts b/apps/api/src/services/faqs/faqUpdateProcedure.ts new file mode 100644 index 00000000..9b57f73e --- /dev/null +++ b/apps/api/src/services/faqs/faqUpdateProcedure.ts @@ -0,0 +1,40 @@ +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import { faqSchema } from './faqs.schema.js' + +export const faqUpdateProcedure = defineProtectedMutateProcedure({ + key: 'update', + roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'], + inputSchema: z.strictObject({ + id: z.number().int(), + unterveranstaltungId: z.number().int(), + faq: faqSchema, + }), + handler: async ({ input: { id, unterveranstaltungId, faq } }) => { + await prisma.faq.update({ + where: { + id, + }, + data: { + question: faq.question, + answer: faq.answer, + category: { + connectOrCreate: { + create: { + name: faq.category, + unterveranstaltungId, + }, + where: { + name_unterveranstaltungId: { + name: faq.category, + unterveranstaltungId, + }, + }, + }, + }, + }, + }) + }, +}) diff --git a/apps/api/src/services/faqs/faqs.router.ts b/apps/api/src/services/faqs/faqs.router.ts new file mode 100644 index 00000000..29e6dd1e --- /dev/null +++ b/apps/api/src/services/faqs/faqs.router.ts @@ -0,0 +1,17 @@ +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' +import { faqCreateProcedure } from './faqCreateProcedure.js' +import { faqDeleteProcedure } from './faqDeleteProcecure.js' +import { faqCategorySearchProcedure, faqListProcedure } from './faqListProcedure.js' +import { faqUpdateProcedure } from './faqUpdateProcedure.js' + +// Import Routes here - do not delete this line + +export const faqsRouter = mergeRouters( + faqListProcedure.router, + faqCategorySearchProcedure.router, + faqCreateProcedure.router, + faqUpdateProcedure.router, + faqDeleteProcedure.router + // Add Routes here - do not delete this line +) diff --git a/apps/api/src/services/faqs/faqs.schema.ts b/apps/api/src/services/faqs/faqs.schema.ts new file mode 100644 index 00000000..5a5fada5 --- /dev/null +++ b/apps/api/src/services/faqs/faqs.schema.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' + +export const faqSchema = z.strictObject({ + question: z.string(), + answer: z.string(), + category: z.string(), +}) + +export const faqUpdateSchema = faqSchema.partial() diff --git a/apps/api/src/services/file/file.router.ts b/apps/api/src/services/file/file.router.ts new file mode 100644 index 00000000..dd53cb58 --- /dev/null +++ b/apps/api/src/services/file/file.router.ts @@ -0,0 +1,12 @@ +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' + +import { fileCreateProcedure } from './fileCreate.js' +import { fileGetUrlActionProcedure } from './fileGetUrl.js' +// Import Routes here - do not delete this line + +export const fileRouter = mergeRouters( + fileCreateProcedure.router, + fileGetUrlActionProcedure.router + // Add Routes here - do not delete this line +) diff --git a/apps/api/src/services/file/fileCreate.ts b/apps/api/src/services/file/fileCreate.ts new file mode 100644 index 00000000..d55ea7ad --- /dev/null +++ b/apps/api/src/services/file/fileCreate.ts @@ -0,0 +1,56 @@ +import { randomUUID } from 'crypto' + +import { BlobSASPermissions } from '@azure/storage-blob' +import dayjs from 'dayjs' +import z from 'zod' + +import { azureStorage } from '../../azureStorage.js' +import config from '../../config.js' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' + +export const fileCreateProcedure = defineProtectedMutateProcedure({ + key: 'fileCreate', + roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'], + inputSchema: z.strictObject({ + mimetype: z.string(), + }), + async handler(options) { + const provider = config.fileDefaultProvider + let key: string = randomUUID() + if (provider === 'AZURE') { + key = `${config.fileProviders.AZURE.folder}/${key}` + } + + const file = await prisma.file.create({ + data: { + provider: provider, + key: key, + mimetype: options.input.mimetype, + }, + select: { + id: true, + provider: true, + uploaded: true, + }, + }) + + let azureUploadUrl: string | null = null + if (file.provider === 'AZURE' && azureStorage !== null) { + const containerClient = azureStorage.getContainerClient(config.fileProviders.AZURE.container) + const blockBlobClient = containerClient.getBlockBlobClient(key) + const permissions = BlobSASPermissions.from({ read: true, write: true, add: true, create: true }) + azureUploadUrl = await blockBlobClient.generateSasUrl({ + startsOn: dayjs().subtract(5, 'minute').toDate(), + expiresOn: dayjs().add(20, 'minute').toDate(), + permissions: permissions, + contentType: options.input.mimetype, + }) + } + + return { + ...file, + azureUploadUrl, + } + }, +}) diff --git a/apps/api/src/services/file/fileGetUrl.ts b/apps/api/src/services/file/fileGetUrl.ts new file mode 100644 index 00000000..1ae7dac4 --- /dev/null +++ b/apps/api/src/services/file/fileGetUrl.ts @@ -0,0 +1,45 @@ +import z from 'zod' + +import prisma from '../../prisma.js' +import { definePublicQueryProcedure } from '../../types/defineProcedure.js' +import { getFileUrl } from './helpers/getFileUrl.js' + +export const fileGetUrlActionProcedure = definePublicQueryProcedure({ + key: 'fileGetUrl', + inputSchema: z.strictObject({ + id: z.string().uuid().nullable(), + personId: z.number().int().optional(), + }), + async handler({ input }) { + if (typeof input.id !== 'string') { + return null + } + + type FileWhereUniqueInput = Parameters[0]['where'] + const where: FileWhereUniqueInput = { id: input.id } + + if (input.personId) { + where.Persons = { + some: { + id: input.personId, + }, + } + } + + const file = await prisma.file.findUnique({ + where, + select: { + id: true, + provider: true, + uploaded: true, + key: true, + }, + }) + + if (file === null) { + return null + } + + return await getFileUrl(file) + }, +}) diff --git a/apps/api/src/services/file/helpers/getFileUrl.ts b/apps/api/src/services/file/helpers/getFileUrl.ts new file mode 100644 index 00000000..3d4dc647 --- /dev/null +++ b/apps/api/src/services/file/helpers/getFileUrl.ts @@ -0,0 +1,30 @@ +import config from '../../../config.js' +import { azureStorage } from '../../../azureStorage.js' +import dayjs from 'dayjs' +import { BlobSASPermissions } from '@azure/storage-blob' + +export type File = { + id: string + provider: 'LOCAL' | 'AZURE' + uploaded: boolean + key: string +} + +const downloadUrlLifespan = 60 * 60 // 1 hour + +export async function getFileUrl(file: File) { + if (file.provider === 'LOCAL') { + if (!file.uploaded) throw new Error('File is not uploaded') + return new URL(`/api/download/file/${file.provider}/${file.id}`, config.clientUrl).href + } + if (file.provider === 'AZURE' && azureStorage !== null) { + const containerClient = azureStorage.getContainerClient(config.fileProviders.AZURE.container) + const blockBlobClient = containerClient.getBlockBlobClient(file.key) + return await blockBlobClient.generateSasUrl({ + startsOn: dayjs().subtract(5, 'minute').toDate(), + expiresOn: dayjs().add(downloadUrlLifespan, 'seconds').toDate(), + permissions: BlobSASPermissions.from({ read: true }), + }) + } + throw new Error('Unknown file provider') +} diff --git a/api/src/services/gliederung/gliederung.router.ts b/apps/api/src/services/gliederung/gliederung.router.ts similarity index 67% rename from api/src/services/gliederung/gliederung.router.ts rename to apps/api/src/services/gliederung/gliederung.router.ts index b326f9f4..69c78f02 100644 --- a/api/src/services/gliederung/gliederung.router.ts +++ b/apps/api/src/services/gliederung/gliederung.router.ts @@ -1,12 +1,12 @@ -/* eslint-disable prettier/prettier */ // Prettier ignored is because this file is generated -import { mergeRouters } from '../../trpc' +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' -import { gliederungPublicGetProcedure } from './gliederungPublicGet' -import { gliederungPublicListProcedure } from './gliederungPublicList' -import { gliederungVerwaltungCreateProcedure } from './gliederungVerwaltungCreate' -import { gliederungVerwaltungGetProcedure } from './gliederungVerwaltungGet' -import { gliederungVerwaltungListProcedure } from './gliederungVerwaltungList' -import { gliederungVerwaltungPatchProcedure } from './gliederungVerwaltungPatch' +import { gliederungListProcedure, gliederungCountProcedure } from './gliederungList.js' +import { gliederungPublicGetProcedure } from './gliederungPublicGet.js' +import { gliederungPublicListProcedure } from './gliederungPublicList.js' +import { gliederungVerwaltungCreateProcedure } from './gliederungVerwaltungCreate.js' +import { gliederungVerwaltungGetProcedure } from './gliederungVerwaltungGet.js' +import { gliederungVerwaltungPatchProcedure } from './gliederungVerwaltungPatch.js' // Import Routes here - do not delete this line export const gliederungRouter = mergeRouters( @@ -14,7 +14,8 @@ export const gliederungRouter = mergeRouters( gliederungPublicListProcedure.router, gliederungVerwaltungCreateProcedure.router, gliederungVerwaltungGetProcedure.router, - gliederungVerwaltungListProcedure.router, + gliederungListProcedure.router, + gliederungCountProcedure.router, gliederungVerwaltungPatchProcedure.router // Add Routes here - do not delete this line ) diff --git a/apps/api/src/services/gliederung/gliederungList.ts b/apps/api/src/services/gliederung/gliederungList.ts new file mode 100644 index 00000000..66efb86c --- /dev/null +++ b/apps/api/src/services/gliederung/gliederungList.ts @@ -0,0 +1,78 @@ +import { Prisma, Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import { defineQuery, getOrderBy } from '../../types/defineQuery.js' + +const inputSchema = defineQuery({ + filter: z.strictObject({ + edv: z.string().optional(), + name: z.string().optional(), + }), + orderBy: z.array( + z.tuple([z.union([z.literal('edv'), z.literal('name')]), z.union([z.literal('asc'), z.literal('desc')])]) + ), +}) +type TInput = z.infer + +export const gliederungListProcedure = defineProtectedQueryProcedure({ + key: 'list', + roleIds: [Role.ADMIN], + inputSchema, + async handler(options) { + const { skip, take } = options.input.pagination + const list = await prisma.gliederung.findMany({ + skip, + take, + orderBy: getOrderBy(options.input.orderBy), + where: await getWhere(options.input.filter, options.ctx.account), + select: { + id: true, + name: true, + edv: true, + }, + }) + + return list + }, +}) + +export const gliederungCountProcedure = defineProtectedQueryProcedure({ + key: 'count', + roleIds: [Role.ADMIN], + inputSchema: inputSchema.pick({ filter: true }), + async handler(options) { + const list = await prisma.gliederung.count({ + where: await getWhere(options.input.filter, options.ctx.account), + }) + + return list + }, +}) + +// eslint-disable-next-line @typescript-eslint/require-await +async function getWhere( + filter: TInput['filter'], + // eslint-disable-next-line @typescript-eslint/no-unused-vars + account: { + id: number + role: Role + } +): Promise { + const where: Prisma.GliederungWhereInput = {} + + if (filter.name != undefined && filter.name !== '') + where.name = { + contains: filter.name, + mode: 'insensitive', + } + + if (filter.edv != undefined && filter.edv !== '') + where.edv = { + contains: filter.edv, + mode: 'insensitive', + } + + return where +} diff --git a/api/src/services/gliederung/gliederungPublicGet.ts b/apps/api/src/services/gliederung/gliederungPublicGet.ts similarity index 59% rename from api/src/services/gliederung/gliederungPublicGet.ts rename to apps/api/src/services/gliederung/gliederungPublicGet.ts index 73ef5bbb..86c834e8 100644 --- a/api/src/services/gliederung/gliederungPublicGet.ts +++ b/apps/api/src/services/gliederung/gliederungPublicGet.ts @@ -1,12 +1,10 @@ import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { definePublicQueryProcedure } from '../../types/defineProcedure.js' -export const gliederungPublicGetProcedure = defineProcedure({ +export const gliederungPublicGetProcedure = definePublicQueryProcedure({ key: 'publicGet', - method: 'query', - protection: { type: 'public' }, inputSchema: z.strictObject({ gliederungId: z.number().int(), }), diff --git a/api/src/services/gliederung/gliederungPublicList.ts b/apps/api/src/services/gliederung/gliederungPublicList.ts similarity index 55% rename from api/src/services/gliederung/gliederungPublicList.ts rename to apps/api/src/services/gliederung/gliederungPublicList.ts index dc20959c..e576968d 100644 --- a/api/src/services/gliederung/gliederungPublicList.ts +++ b/apps/api/src/services/gliederung/gliederungPublicList.ts @@ -1,17 +1,18 @@ import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { defineQuery } from '../../types/defineQuery' +import prisma from '../../prisma.js' +import { definePublicQueryProcedure } from '../../types/defineProcedure.js' +import { defineQuery, getOrderBy } from '../../types/defineQuery.js' -export const gliederungPublicListProcedure = defineProcedure({ +export const gliederungPublicListProcedure = definePublicQueryProcedure({ key: 'publicList', - method: 'query', - protection: { type: 'public' }, inputSchema: defineQuery({ filter: z.strictObject({ name: z.string().optional(), }), + orderBy: z.array( + z.tuple([z.union([z.literal('id'), z.literal('name')]), z.union([z.literal('asc'), z.literal('desc')])]) + ), }), async handler(options) { const { skip, take } = options.input.pagination @@ -24,6 +25,7 @@ export const gliederungPublicListProcedure = defineProcedure({ mode: 'insensitive', }, }, + orderBy: getOrderBy(options.input.orderBy), select: { id: true, name: true, diff --git a/api/src/services/gliederung/gliederungVerwaltungCreate.ts b/apps/api/src/services/gliederung/gliederungVerwaltungCreate.ts similarity index 69% rename from api/src/services/gliederung/gliederungVerwaltungCreate.ts rename to apps/api/src/services/gliederung/gliederungVerwaltungCreate.ts index dc441762..ba390d60 100644 --- a/api/src/services/gliederung/gliederungVerwaltungCreate.ts +++ b/apps/api/src/services/gliederung/gliederungVerwaltungCreate.ts @@ -1,8 +1,9 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import type { AuthenticatedContext } from '../../trpc' -import { defineProcedure } from '../../types/defineProcedure' +import type { Context } from '../../context.js' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' export const ZGliederungVerwaltungCreateInputSchema = z.strictObject({ data: z.strictObject({ @@ -13,7 +14,7 @@ export const ZGliederungVerwaltungCreateInputSchema = z.strictObject({ export type TGliederungVerwaltungCreateInputSchema = z.infer -type GliederungVerwaltungCreateOptions = AuthenticatedContext & { +type GliederungVerwaltungCreateOptions = Context & { input: TGliederungVerwaltungCreateInputSchema } @@ -26,10 +27,9 @@ export async function gliederungVerwaltungCreate(options: GliederungVerwaltungCr }) } -export const gliederungVerwaltungCreateProcedure = defineProcedure({ +export const gliederungVerwaltungCreateProcedure = defineProtectedMutateProcedure({ key: 'verwaltungCreate', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ data: z.strictObject({ name: z.string(), diff --git a/api/src/services/gliederung/gliederungVerwaltungGet.ts b/apps/api/src/services/gliederung/gliederungVerwaltungGet.ts similarity index 56% rename from api/src/services/gliederung/gliederungVerwaltungGet.ts rename to apps/api/src/services/gliederung/gliederungVerwaltungGet.ts index 9e200dd2..7155731e 100644 --- a/api/src/services/gliederung/gliederungVerwaltungGet.ts +++ b/apps/api/src/services/gliederung/gliederungVerwaltungGet.ts @@ -1,12 +1,12 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' -export const gliederungVerwaltungGetProcedure = defineProcedure({ +export const gliederungVerwaltungGetProcedure = defineProtectedQueryProcedure({ key: 'verwaltungGet', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ id: z.number(), }), diff --git a/api/src/services/gliederung/gliederungVerwaltungPatch.ts b/apps/api/src/services/gliederung/gliederungVerwaltungPatch.ts similarity index 64% rename from api/src/services/gliederung/gliederungVerwaltungPatch.ts rename to apps/api/src/services/gliederung/gliederungVerwaltungPatch.ts index ef7150f9..f2e6572f 100644 --- a/api/src/services/gliederung/gliederungVerwaltungPatch.ts +++ b/apps/api/src/services/gliederung/gliederungVerwaltungPatch.ts @@ -1,12 +1,12 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' -export const gliederungVerwaltungPatchProcedure = defineProcedure({ +export const gliederungVerwaltungPatchProcedure = defineProtectedMutateProcedure({ key: 'verwaltungPatch', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ id: z.number().int(), data: z.strictObject({ diff --git a/api/src/services/gliederung/schema/gliederung.schema.ts b/apps/api/src/services/gliederung/schema/gliederung.schema.ts similarity index 100% rename from api/src/services/gliederung/schema/gliederung.schema.ts rename to apps/api/src/services/gliederung/schema/gliederung.schema.ts diff --git a/api/src/services/index.ts b/apps/api/src/services/index.ts similarity index 52% rename from api/src/services/index.ts rename to apps/api/src/services/index.ts index 1e226320..b8184e22 100644 --- a/api/src/services/index.ts +++ b/apps/api/src/services/index.ts @@ -1,17 +1,20 @@ -import { router } from '../trpc' +import { router } from '../trpc.js' -import { accountRouter } from './account/account.router' -import { activityRouter } from './activity/activity.routes' -import { anmeldungRouter } from './anmeldung/anmeldung.router' -import { authenticationRouter } from './authentication/authentication.router' -import { customFieldsRouter } from './customFields/customFields.router' -import { gliederungRouter } from './gliederung/gliederung.router' -import { ortRouter } from './ort/ort.router' -import { personRouter } from './person/person.router' -import { searchRouter } from './search/search.router' -import { systemRouter } from './system/system.router' -import { unterveranstaltungRouter } from './unterveranstaltung/unterveranstaltung.router' -import { veranstaltungRouter } from './veranstaltung/veranstaltung.router' +import { accountRouter } from './account/account.router.js' +import { activityRouter } from './activity/activity.routes.js' +import { addressRouter } from './address/address.router.js' +import { anmeldungRouter } from './anmeldung/anmeldung.router.js' +import { authenticationRouter } from './authentication/authentication.router.js' +import { customFieldsRouter } from './customFields/customFields.router.js' +import { faqsRouter } from './faqs/faqs.router.js' +import { fileRouter } from './file/file.router.js' +import { gliederungRouter } from './gliederung/gliederung.router.js' +import { ortRouter } from './ort/ort.router.js' +import { personRouter } from './person/person.router.js' +import { searchRouter } from './search/search.router.js' +import { systemRouter } from './system/system.router.js' +import { unterveranstaltungRouter } from './unterveranstaltung/unterveranstaltung.router.js' +import { veranstaltungRouter } from './veranstaltung/veranstaltung.router.js' // Add Imports here - do not delete this line export const serviceRouter = router({ @@ -27,5 +30,8 @@ export const serviceRouter = router({ search: searchRouter, system: systemRouter, customFields: customFieldsRouter, + file: fileRouter, + address: addressRouter, + faq: faqsRouter, // Add Routers here - do not delete this line }) diff --git a/api/src/services/kontakt/schema/kontakt.schema.ts b/apps/api/src/services/kontakt/schema/kontakt.schema.ts similarity index 100% rename from api/src/services/kontakt/schema/kontakt.schema.ts rename to apps/api/src/services/kontakt/schema/kontakt.schema.ts diff --git a/api/src/services/ort/ort.router.ts b/apps/api/src/services/ort/ort.router.ts similarity index 55% rename from api/src/services/ort/ort.router.ts rename to apps/api/src/services/ort/ort.router.ts index a301252b..713b6aa9 100644 --- a/api/src/services/ort/ort.router.ts +++ b/apps/api/src/services/ort/ort.router.ts @@ -1,18 +1,19 @@ -/* eslint-disable prettier/prettier */ // Prettier ignored is because this file is generated -import { mergeRouters } from '../../trpc' +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' -import { ortVerwaltungCreateProcedure } from './ortVerwaltungCreate' -import { ortVerwaltungGetProcedure } from './ortVerwaltungGet' -import { ortVerwaltungListProcedure } from './ortVerwaltungList' -import { ortVerwaltungPatchProcedure } from './ortVerwaltungPatch' -import { ortVerwaltungRemoveProcedure } from './ortVerwaltungRemove' +import { ortListProcedure, ortCountProcedure } from './ortList.js' +import { ortVerwaltungCreateProcedure } from './ortVerwaltungCreate.js' +import { ortVerwaltungGetProcedure } from './ortVerwaltungGet.js' +import { ortVerwaltungPatchProcedure } from './ortVerwaltungPatch.js' +import { ortVerwaltungRemoveProcedure } from './ortVerwaltungRemove.js' // Import Routes here - do not delete this line export const ortRouter = mergeRouters( ortVerwaltungCreateProcedure.router, ortVerwaltungGetProcedure.router, - ortVerwaltungListProcedure.router, + ortListProcedure.router, + ortCountProcedure.router, ortVerwaltungPatchProcedure.router, - ortVerwaltungRemoveProcedure.router, + ortVerwaltungRemoveProcedure.router // Add Routes here - do not delete this line ) diff --git a/apps/api/src/services/ort/ortList.ts b/apps/api/src/services/ort/ortList.ts new file mode 100644 index 00000000..3fde11f6 --- /dev/null +++ b/apps/api/src/services/ort/ortList.ts @@ -0,0 +1,82 @@ +import { Role, type Prisma } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import { defineQuery } from '../../types/defineQuery.js' + +const inputSchema = defineQuery({ + filter: z.strictObject({ + name: z.string().optional(), + city: z.string().optional(), + }), + orderBy: z.array( + z.tuple([ + z.union([z.literal('id'), z.literal('name'), z.literal('address.city')]), + z.union([z.literal('asc'), z.literal('desc')]), + ]) + ), +}) +type TInput = z.infer + +// eslint-disable-next-line @typescript-eslint/require-await +async function getWhere( + filter: TInput['filter'], + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _account: { + id: number + role: Role + } +): Promise { + const where: Prisma.OrtWhereInput = {} + + // Input filter + if (filter.name != undefined && filter.name != '') { + where.name = { + contains: filter.name, + } + } + if (filter.city != undefined && filter.city != '') { + where.address = { + city: { + contains: filter.city, + }, + } + } + + // Role filter + // -- + + return where +} + +export const ortListProcedure = defineProtectedQueryProcedure({ + key: 'list', + roleIds: [Role.ADMIN], + inputSchema, + async handler(options) { + const { skip, take } = options.input.pagination + + return await prisma.ort.findMany({ + skip, + take, + where: await getWhere(options.input.filter, options.ctx.account), + select: { + id: true, + name: true, + address: true, + }, + }) + }, +}) + +export const ortCountProcedure = defineProtectedQueryProcedure({ + key: 'count', + roleIds: [Role.ADMIN], + inputSchema: inputSchema.pick({ filter: true }), + async handler(options) { + return await prisma.ort.count({ + where: await getWhere(options.input.filter, options.ctx.account), + }) + }, +}) diff --git a/api/src/services/ort/ortVerwaltungCreate.ts b/apps/api/src/services/ort/ortVerwaltungCreate.ts similarity index 64% rename from api/src/services/ort/ortVerwaltungCreate.ts rename to apps/api/src/services/ort/ortVerwaltungCreate.ts index 114a7d25..2cf8f1b9 100644 --- a/api/src/services/ort/ortVerwaltungCreate.ts +++ b/apps/api/src/services/ort/ortVerwaltungCreate.ts @@ -1,13 +1,13 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { addressSchema, createOrUpdateAddress } from '../address/schema/address.schema' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import { addressSchema, createOrUpdateAddress } from '../address/schema/address.schema.js' -export const ortVerwaltungCreateProcedure = defineProcedure({ +export const ortVerwaltungCreateProcedure = defineProtectedMutateProcedure({ key: 'verwaltungCreate', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ data: z.strictObject({ name: z.string(), diff --git a/api/src/services/ort/ortVerwaltungGet.ts b/apps/api/src/services/ort/ortVerwaltungGet.ts similarity index 55% rename from api/src/services/ort/ortVerwaltungGet.ts rename to apps/api/src/services/ort/ortVerwaltungGet.ts index d4fafe97..6b7cec8e 100644 --- a/api/src/services/ort/ortVerwaltungGet.ts +++ b/apps/api/src/services/ort/ortVerwaltungGet.ts @@ -1,12 +1,12 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' -export const ortVerwaltungGetProcedure = defineProcedure({ +export const ortVerwaltungGetProcedure = defineProtectedQueryProcedure({ key: 'verwaltungGet', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ id: z.number().int(), }), @@ -23,7 +23,10 @@ export const ortVerwaltungGetProcedure = defineProcedure({ zip: true, city: true, street: true, - number: true, + streetNumber: true, + country: true, + lon: true, + lat: true, }, }, }, diff --git a/api/src/services/ort/ortVerwaltungPatch.ts b/apps/api/src/services/ort/ortVerwaltungPatch.ts similarity index 67% rename from api/src/services/ort/ortVerwaltungPatch.ts rename to apps/api/src/services/ort/ortVerwaltungPatch.ts index a7f2f323..20ff6e71 100644 --- a/api/src/services/ort/ortVerwaltungPatch.ts +++ b/apps/api/src/services/ort/ortVerwaltungPatch.ts @@ -1,13 +1,13 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { addressSchema, createOrUpdateAddress } from '../address/schema/address.schema' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import { addressSchema, createOrUpdateAddress } from '../address/schema/address.schema.js' -export const ortVerwaltungPatchProcedure = defineProcedure({ +export const ortVerwaltungPatchProcedure = defineProtectedMutateProcedure({ key: 'verwaltungPatch', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ id: z.number().int(), data: z.strictObject({ diff --git a/api/src/services/ort/ortVerwaltungRemove.ts b/apps/api/src/services/ort/ortVerwaltungRemove.ts similarity index 51% rename from api/src/services/ort/ortVerwaltungRemove.ts rename to apps/api/src/services/ort/ortVerwaltungRemove.ts index 6281930d..96394df0 100644 --- a/api/src/services/ort/ortVerwaltungRemove.ts +++ b/apps/api/src/services/ort/ortVerwaltungRemove.ts @@ -1,12 +1,12 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' -export const ortVerwaltungRemoveProcedure = defineProcedure({ +export const ortVerwaltungRemoveProcedure = defineProtectedMutateProcedure({ key: 'verwaltungRemove', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ id: z.number().int(), }), diff --git a/apps/api/src/services/person/person.router.ts b/apps/api/src/services/person/person.router.ts new file mode 100644 index 00000000..31f0aace --- /dev/null +++ b/apps/api/src/services/person/person.router.ts @@ -0,0 +1,22 @@ +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' +import { personAuthenticatedGetProcedure } from './personAuthenticatedGet.js' +import { personGetProcedure } from './personGet.js' +import { personGliederungPatchProcedure } from './personGliederungPatch.js' +import { personCountProcedure, personListProcedure } from './personList.js' +import { personVerwaltungCreateProcedure } from './personVerwaltungCreate.js' +import { personVerwaltungPatchProcedure } from './personVerwaltungPatch.js' +import { personVerwaltungRemoveProcedure } from './personVerwaltungRemove.js' +// Import Routes here - do not delete this line + +export const personRouter = mergeRouters( + personAuthenticatedGetProcedure.router, + personVerwaltungCreateProcedure.router, + personListProcedure.router, + personCountProcedure.router, + personGetProcedure.router, + personVerwaltungPatchProcedure.router, + personVerwaltungRemoveProcedure.router, + personGliederungPatchProcedure.router + // Add Routes here - do not delete this line +) diff --git a/apps/api/src/services/person/personAuthenticatedGet.ts b/apps/api/src/services/person/personAuthenticatedGet.ts new file mode 100644 index 00000000..90c48e5c --- /dev/null +++ b/apps/api/src/services/person/personAuthenticatedGet.ts @@ -0,0 +1,33 @@ +import { Prisma, Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' + +export const personSelfSelect = { + id: true, + role: true, + status: true, + person: { + select: { + id: true, + firstname: true, + lastname: true, + gliederungId: true, + photoId: true, + }, + }, +} satisfies Prisma.AccountSelect + +export const personAuthenticatedGetProcedure = defineProtectedQueryProcedure({ + key: 'authenticatedGet', + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN, Role.USER], + inputSchema: z.void(), + handler: (options) => + prisma.account.findUniqueOrThrow({ + where: { + id: options.ctx.accountId, + }, + select: personSelfSelect, + }), +}) diff --git a/api/src/services/person/personVerwaltungGet.ts b/apps/api/src/services/person/personGet.ts similarity index 50% rename from api/src/services/person/personVerwaltungGet.ts rename to apps/api/src/services/person/personGet.ts index f8a77c98..11a645d0 100644 --- a/api/src/services/person/personVerwaltungGet.ts +++ b/apps/api/src/services/person/personGet.ts @@ -1,20 +1,26 @@ +import { Prisma, Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import { getPersonProtectionFilter } from './personList.js' -export const personVerwaltungGetProcedure = defineProcedure({ - key: 'verwaltungGet', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, +export const personGetProcedure = defineProtectedQueryProcedure({ + key: 'get', + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN, Role.USER], inputSchema: z.strictObject({ id: z.number().int(), }), - async handler(options) { - return await prisma.person.findUniqueOrThrow({ - where: { - id: options.input.id, - }, + handler: ({ ctx, input }) => { + const where: Prisma.PersonWhereUniqueInput = { + ...getPersonProtectionFilter(ctx), + id: input.id, + } + + console.log(where) + + return prisma.person.findUniqueOrThrow({ + where, select: { id: true, firstname: true, @@ -28,12 +34,15 @@ export const personVerwaltungGetProcedure = defineProcedure({ zip: true, city: true, street: true, - number: true, + streetNumber: true, + country: true, + valid: true, + lat: true, + lon: true, }, }, nahrungsmittelIntoleranzen: true, weitereIntoleranzen: true, - konfektionsgroesse: true, essgewohnheit: true, notfallkontakte: { select: { @@ -44,6 +53,7 @@ export const personVerwaltungGetProcedure = defineProcedure({ }, }, gliederung: true, + photoId: true, }, }) }, diff --git a/api/src/services/person/personGliederungPatch.ts b/apps/api/src/services/person/personGliederungPatch.ts similarity index 64% rename from api/src/services/person/personGliederungPatch.ts rename to apps/api/src/services/person/personGliederungPatch.ts index a7822386..11f669de 100644 --- a/api/src/services/person/personGliederungPatch.ts +++ b/apps/api/src/services/person/personGliederungPatch.ts @@ -1,14 +1,14 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' -import { getPersonCreateData, personSchema } from './schema/person.schema' +import { getPersonCreateData, personSchema } from './schema/person.schema.js' -export const personGliederungPatchProcedure = defineProcedure({ +export const personGliederungPatchProcedure = defineProtectedMutateProcedure({ key: 'gliederungPatch', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['GLIEDERUNG_ADMIN'] }, + roleIds: [Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ id: z.number().int(), data: personSchema, diff --git a/apps/api/src/services/person/personList.ts b/apps/api/src/services/person/personList.ts new file mode 100644 index 00000000..c577ef38 --- /dev/null +++ b/apps/api/src/services/person/personList.ts @@ -0,0 +1,117 @@ +import { Prisma, Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import { defineQuery, getOrderBy } from '../../types/defineQuery.js' +import type { AuthenticatedContext } from '../../trpc.js' + +const inputSchema = defineQuery({ + filter: z.strictObject({ + name: z.string().optional(), + gliederungName: z.string().optional(), + }), + orderBy: z.array( + z.tuple([ + z.union([z.literal('firstname'), z.literal('birthday'), z.literal('gliederung.name')]), + z.union([z.literal('asc'), z.literal('desc')]), + ]) + ), +}) + +type TInput = z.infer + +export const personListProcedure = defineProtectedQueryProcedure({ + key: 'list', + roleIds: [Role.ADMIN, Role.USER], + inputSchema, + handler: ({ ctx, input }) => { + const { skip, take } = input.pagination + + return prisma.person.findMany({ + skip, + take, + where: getWhere(input.filter, ctx), + orderBy: getOrderBy(input.orderBy), + select: { + id: true, + firstname: true, + lastname: true, + birthday: true, + photoId: true, + gliederung: { + select: { + id: true, + name: true, + }, + }, + account: { + select: { + id: true, + activatedAt: true, + role: true, + status: true, + GliederungToAccount: { + select: { + role: true, + gliederung: { + select: { + name: true, + }, + }, + }, + }, + }, + }, + }, + }) + }, +}) + +export const personCountProcedure = defineProtectedQueryProcedure({ + key: 'count', + roleIds: [Role.ADMIN, Role.USER], + inputSchema: inputSchema.pick({ filter: true }), + async handler(options) { + const total = await prisma.person.count({ + where: getWhere(options.input.filter, options.ctx), + }) + return total + }, +}) + +export function getPersonProtectionFilter({ + account, +}: AuthenticatedContext): Prisma.PersonWhereInput | Prisma.PersonWhereUniqueInput { + const where: Prisma.PersonWhereInput = {} + + if (account.role === Role.USER) { + where.anmeldungen = { + every: { + accountId: account.id, + }, + } + } + + return where +} + +function getWhere(filter: TInput['filter'], ctx: AuthenticatedContext): Prisma.PersonWhereInput { + const where: Prisma.PersonWhereInput = {} + + if (filter.name != null && filter.name != '') { + where.OR = [{ firstname: { contains: filter.name } }, { lastname: { contains: filter.name } }] + } + if (filter.gliederungName != null && filter.gliederungName != '') { + where.gliederung = { + name: { + contains: filter.gliederungName, + }, + } + } + + return { + ...where, + ...getPersonProtectionFilter(ctx), + } +} diff --git a/api/src/services/person/personVerwaltungCreate.ts b/apps/api/src/services/person/personVerwaltungCreate.ts similarity index 70% rename from api/src/services/person/personVerwaltungCreate.ts rename to apps/api/src/services/person/personVerwaltungCreate.ts index 06a04533..1261526c 100644 --- a/api/src/services/person/personVerwaltungCreate.ts +++ b/apps/api/src/services/person/personVerwaltungCreate.ts @@ -1,15 +1,15 @@ +import { Role } from '@prisma/client' import z from 'zod' -import { updateMeiliPerson } from '../../meilisearch/person' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import { updateMeiliPerson } from '../../meilisearch/person.js' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' -import { personSchema, getPersonCreateData } from './schema/person.schema' +import { personSchema, getPersonCreateData } from './schema/person.schema.js' -export const personVerwaltungCreateProcedure = defineProcedure({ +export const personVerwaltungCreateProcedure = defineProtectedMutateProcedure({ key: 'verwaltungCreate', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ data: personSchema, }), diff --git a/api/src/services/person/personVerwaltungPatch.ts b/apps/api/src/services/person/personVerwaltungPatch.ts similarity index 53% rename from api/src/services/person/personVerwaltungPatch.ts rename to apps/api/src/services/person/personVerwaltungPatch.ts index 604e31c8..ecf3fa2a 100644 --- a/api/src/services/person/personVerwaltungPatch.ts +++ b/apps/api/src/services/person/personVerwaltungPatch.ts @@ -1,17 +1,17 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' -import { getPersonCreateData, personSchema } from './schema/person.schema' +import { getPersonCreateData, personSchemaOptional } from './schema/person.schema.js' -export const personVerwaltungPatchProcedure = defineProcedure({ +export const personVerwaltungPatchProcedure = defineProtectedMutateProcedure({ key: 'verwaltungPatch', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ id: z.number().int(), - data: personSchema, + data: personSchemaOptional, }), async handler({ input }) { await prisma.notfallkontakt.deleteMany({ diff --git a/apps/api/src/services/person/personVerwaltungRemove.ts b/apps/api/src/services/person/personVerwaltungRemove.ts new file mode 100644 index 00000000..34fb1a7a --- /dev/null +++ b/apps/api/src/services/person/personVerwaltungRemove.ts @@ -0,0 +1,20 @@ +import { Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' + +export const personVerwaltungRemoveProcedure = defineProtectedMutateProcedure({ + key: 'verwaltungRemove', + roleIds: [Role.ADMIN], + inputSchema: z.strictObject({ + id: z.number().int(), + }), + async handler(options) { + return prisma.person.delete({ + where: { + id: options.input.id, + }, + }) + }, +}) diff --git a/api/src/services/person/schema/person.schema.ts b/apps/api/src/services/person/schema/person.schema.ts similarity index 54% rename from api/src/services/person/schema/person.schema.ts rename to apps/api/src/services/person/schema/person.schema.ts index 01d26402..1e061fe0 100644 --- a/api/src/services/person/schema/person.schema.ts +++ b/apps/api/src/services/person/schema/person.schema.ts @@ -1,19 +1,8 @@ -import { - Essgewohnheit, - Gender, - Konfektionsgroesse, - NahrungsmittelIntoleranz, - Prisma, - QualificationErsteHilfe, - QualificationFahrerlaubnis, - QualificationFunk, - QualificationSanitaeter, - QualificationSchwimmer, -} from '@prisma/client' +import { Essgewohnheit, Gender, NahrungsmittelIntoleranz, Prisma } from '@prisma/client' import { z } from 'zod' -import { addressSchema, createOrUpdateAddress } from '../../address/schema/address.schema' -import { kontaktSchema, type TKontaktSchema } from '../../kontakt/schema/kontakt.schema' +import { addressSchema, createOrUpdateAddress } from '../../address/schema/address.schema.js' +import { kontaktSchema, type TKontaktSchema } from '../../kontakt/schema/kontakt.schema.js' export type TPersonSchema = z.infer export const personSchema = z.strictObject({ @@ -28,42 +17,37 @@ export const personSchema = z.strictObject({ essgewohnheit: z.nativeEnum(Essgewohnheit), nahrungsmittelIntoleranzen: z.array(z.nativeEnum(NahrungsmittelIntoleranz)), weitereIntoleranzen: z.array(z.string()).optional(), - qualifikationenFahrerlaubnis: z.array(z.nativeEnum(QualificationFahrerlaubnis)).optional(), - qualifikationenSchwimmer: z.array(z.nativeEnum(QualificationSchwimmer)).optional(), - qualifikationenErsteHilfe: z.array(z.nativeEnum(QualificationErsteHilfe)).optional(), - qualifikationenSanitaeter: z.array(z.nativeEnum(QualificationSanitaeter)).optional(), - qualifikationenFunk: z.array(z.nativeEnum(QualificationFunk)).optional(), - konfektionsgroesse: z.nativeEnum(Konfektionsgroesse).optional(), erziehungsberechtigtePersonen: z.array(kontaktSchema).optional(), notfallkontaktPersonen: z.array(kontaktSchema), + photoId: z.string().nullish(), }) +export const personSchemaOptional = personSchema.partial() + export async function getPersonCreateData( - input: z.infer + input: z.infer ): Promise { - const addressId = await createOrUpdateAddress(input.address) + let addressId: number | undefined = undefined + if (input.address) { + addressId = await createOrUpdateAddress(input.address) + } return { - firstname: input.firstname, - lastname: input.lastname, + firstname: input.firstname!, + lastname: input.lastname!, birthday: input.birthday, gender: input.gender, - email: input.email, - telefon: input.telefon, + email: input.email!, + telefon: input.telefon!, gliederungId: input.gliederungId, essgewohnheit: input.essgewohnheit, nahrungsmittelIntoleranzen: input.nahrungsmittelIntoleranzen, weitereIntoleranzen: input.weitereIntoleranzen, - qualifikationenFahrerlaubnis: input.qualifikationenFahrerlaubnis, - qualifikationenSchwimmer: input.qualifikationenSchwimmer, - qualifikationenErsteHilfe: input.qualifikationenErsteHilfe, - qualifikationenSanitaeter: input.qualifikationenSanitaeter, - qualifikationenFunk: input.qualifikationenFunk, - konfektionsgroesse: input.konfektionsgroesse, addressId: addressId, notfallkontakte: { create: input.notfallkontaktPersonen, }, + photoId: input.photoId, } } diff --git a/apps/api/src/services/search/search.router.ts b/apps/api/src/services/search/search.router.ts new file mode 100644 index 00000000..aa87154f --- /dev/null +++ b/apps/api/src/services/search/search.router.ts @@ -0,0 +1,11 @@ +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' + +import { searchProcedure } from './search.js' + +// Import Routes here - do not delete this line + +export const searchRouter = mergeRouters( + searchProcedure.router + // Add Routes here - do not delete this line +) diff --git a/api/src/services/search/search.ts b/apps/api/src/services/search/search.ts similarity index 79% rename from api/src/services/search/search.ts rename to apps/api/src/services/search/search.ts index d82ac0a0..2927d9a1 100644 --- a/api/src/services/search/search.ts +++ b/apps/api/src/services/search/search.ts @@ -2,14 +2,13 @@ import { Role } from '@prisma/client' import type { MultiSearchQuery } from 'meilisearch' import z from 'zod' -import { meilisearchClient } from '../../meilisearch' -import { defineProcedure } from '../../types/defineProcedure' -import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin' +import { meilisearchClient } from '../../meilisearch/index.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin.js' -export const searchProcedure = defineProcedure({ +export const searchProcedure = defineProtectedQueryProcedure({ key: 'search', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'] }, + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ term: z.string(), }), diff --git a/api/src/services/system/system.router.ts b/apps/api/src/services/system/system.router.ts similarity index 54% rename from api/src/services/system/system.router.ts rename to apps/api/src/services/system/system.router.ts index 3c2ab368..21ace667 100644 --- a/api/src/services/system/system.router.ts +++ b/apps/api/src/services/system/system.router.ts @@ -1,12 +1,12 @@ -/* eslint-disable prettier/prettier */ // Prettier ignored is because this file is generated -import { mergeRouters } from '../../trpc' +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' -import { systemHostnamesGetProcedure } from './systemHostnamesGet' +import { systemHostnamesGetProcedure } from './systemHostnamesGet.js' // Import Routes here - do not delete this line export const systemRouter = mergeRouters( - systemHostnamesGetProcedure.router, + systemHostnamesGetProcedure.router // Add Routes here - do not delete this line ) diff --git a/apps/api/src/services/system/systemHostnamesGet.ts b/apps/api/src/services/system/systemHostnamesGet.ts new file mode 100644 index 00000000..37ffde71 --- /dev/null +++ b/apps/api/src/services/system/systemHostnamesGet.ts @@ -0,0 +1,18 @@ +import { Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' + +export const systemHostnamesGetProcedure = defineProtectedQueryProcedure({ + key: 'hostnamesGet', + roleIds: [Role.ADMIN], + inputSchema: z.strictObject({}), + async handler() { + return await prisma.hostname.findMany({ + orderBy: { + hostname: 'asc', + }, + }) + }, +}) diff --git a/apps/api/src/services/unterveranstaltung/schema/crudFiles.ts b/apps/api/src/services/unterveranstaltung/schema/crudFiles.ts new file mode 100644 index 00000000..3292a169 --- /dev/null +++ b/apps/api/src/services/unterveranstaltung/schema/crudFiles.ts @@ -0,0 +1,34 @@ +/** + * Create, update and delete documents + * @param addFiles + * @param updateFiles + * @param deleteFilesIds + */ +export function crudFiles( + addFiles?: { name: string; fileId: string }[], + updateFiles?: { id: number; name: string }[], + deleteFilesIds?: number[] +) { + // Documents create, update, delete + const files: { + createMany?: { data: { name: string; fileId: string }[] } + updateMany?: { where: { id: number }; data: { name: string } }[] + deleteMany?: { id: number }[] + } = {} + if (addFiles) { + files.createMany = { + data: addFiles.map((doc) => ({ ...doc, fileId: doc.fileId })), + } + } + if (updateFiles) { + files.updateMany = updateFiles.map((doc) => ({ + where: { id: doc.id }, + data: { name: doc.name }, + })) + } + if (deleteFilesIds) { + files.deleteMany = deleteFilesIds.map((id) => ({ id })) + } + + return files +} diff --git a/apps/api/src/services/unterveranstaltung/schema/crudMiscellaneousItems.ts b/apps/api/src/services/unterveranstaltung/schema/crudMiscellaneousItems.ts new file mode 100644 index 00000000..a12575e7 --- /dev/null +++ b/apps/api/src/services/unterveranstaltung/schema/crudMiscellaneousItems.ts @@ -0,0 +1,28 @@ +export function crudMiscellaneousItems( + addMiscellaneousItems?: { title: string; content: string }[], + updateMiscellaneousItems?: { id: number; title: string; content: string }[], + deleteMiscellaneousItemIds?: number[] +) { + // Miscellaneous create, update, delete + const miscellaneousItems: { + createMany?: { data: { title: string; content: string }[] } + updateMany?: { where: { id: number }; data: { title: string; content: string } }[] + deleteMany?: { id: number }[] + } = {} + if (addMiscellaneousItems) { + miscellaneousItems.createMany = { + data: addMiscellaneousItems.map((item) => ({ ...item })), + } + } + if (updateMiscellaneousItems) { + miscellaneousItems.updateMany = updateMiscellaneousItems.map((item) => ({ + where: { id: item.id }, + data: { title: item.title, content: item.content }, + })) + } + if (deleteMiscellaneousItemIds) { + miscellaneousItems.deleteMany = deleteMiscellaneousItemIds.map((id) => ({ id })) + } + + return miscellaneousItems +} diff --git a/apps/api/src/services/unterveranstaltung/schema/unterveranstaltung.schema.ts b/apps/api/src/services/unterveranstaltung/schema/unterveranstaltung.schema.ts new file mode 100644 index 00000000..b40a6bde --- /dev/null +++ b/apps/api/src/services/unterveranstaltung/schema/unterveranstaltung.schema.ts @@ -0,0 +1,72 @@ +import { z } from 'zod' + +export const unterveranstaltungCreateSchema = z.strictObject({ + veranstaltungId: z.number().int(), + maxTeilnehmende: z.number().int(), + teilnahmegebuehr: z.number({ description: 'In Cent' }).int(), + meldebeginn: z.date(), + meldeschluss: z.date(), + beschreibung: z.string().optional(), + bedingungen: z.string().optional(), +}) + +export type TUnterveranstaltungCreateSchema = z.infer + +export const unterveranstaltungLandingSchema = z.strictObject({ + heroTitle: z.string(), + heroSubtitle: z.string(), + addHeroImages: z + .array( + z.strictObject({ + name: z.string(), + fileId: z.string().uuid(), + }) + ) + .optional(), + updateHeroImages: z.array(z.strictObject({ id: z.number().int(), name: z.string() })).optional(), + deleteHeroImageIds: z.array(z.number().int()).optional(), + + eventDetailsTitle: z.string(), + eventDetailsContent: z.string(), + + miscellaneousVisible: z.boolean().optional(), + miscellaneousTitle: z.string().optional(), + miscellaneousSubtitle: z.string().optional(), + + addMiscellaneousItems: z + .array( + z.strictObject({ + title: z.string(), + content: z.string(), + }) + ) + .optional(), + updateMiscellaneousItems: z + .array(z.strictObject({ id: z.number().int(), title: z.string(), content: z.string() })) + .optional(), + deleteMiscellaneousItemIds: z.array(z.number().int()).optional(), + + faqVisible: z.boolean().optional(), + faqEmail: z.string().optional(), + + instagramVisible: z.boolean().optional(), + instagramUrl: z.string().optional(), + + facebookVisible: z.boolean().optional(), + facebookUrl: z.string().optional(), +}) + +export const unterveranstaltungUpdateSchema = unterveranstaltungCreateSchema.omit({ veranstaltungId: true }).extend({ + addDocuments: z + .array( + z.strictObject({ + name: z.string(), + fileId: z.string().uuid(), + }) + ) + .optional(), + updateDocuments: z.array(z.strictObject({ id: z.number().int(), name: z.string() })).optional(), + deleteDocumentIds: z.array(z.number().int()).optional(), +}) + +export type TUnterveranstaltungLandingSchema = z.infer diff --git a/api/src/services/unterveranstaltung/unterveranstaltung.router.ts b/apps/api/src/services/unterveranstaltung/unterveranstaltung.router.ts similarity index 60% rename from api/src/services/unterveranstaltung/unterveranstaltung.router.ts rename to apps/api/src/services/unterveranstaltung/unterveranstaltung.router.ts index e47f019f..acb0b9f8 100644 --- a/api/src/services/unterveranstaltung/unterveranstaltung.router.ts +++ b/apps/api/src/services/unterveranstaltung/unterveranstaltung.router.ts @@ -1,28 +1,27 @@ -/* eslint-disable prettier/prettier */ // Prettier ignored is because this file is generated -import { mergeRouters } from '../../trpc' +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' -import { unterveranstaltungGliederungCreateProcedure } from './unterveranstaltungGliederungCreate' -import { unterveranstaltungGliederungGetProcedure } from './unterveranstaltungGliederungGet' -import { unterveranstaltungGliederungListProcedure } from './unterveranstaltungGliederungList' -import { unterveranstaltungGliederungPatchProcedure } from './unterveranstaltungGliederungPatch' -import { unterveranstaltungPublicGetProcedure } from './unterveranstaltungPublicGet' -import { unterveranstaltungVerwaltungCreateProcedure } from './unterveranstaltungVerwaltungCreate' -import { unterveranstaltungVerwaltungGetProcedure } from './unterveranstaltungVerwaltungGet' -import { unterveranstaltungVerwaltungListProcedure } from './unterveranstaltungVerwaltungList' -import { unterveranstaltungVerwaltungPatchProcedure } from './unterveranstaltungVerwaltungPatch' +import { unterveranstaltungGliederungCreateProcedure } from './unterveranstaltungGliederungCreate.js' +import { unterveranstaltungGliederungGetProcedure } from './unterveranstaltungGliederungGet.js' +import { unterveranstaltungGliederungPatchProcedure } from './unterveranstaltungGliederungPatch.js' +import { unterveranstaltungListProcedure, unterveranstaltungCountProcedure } from './unterveranstaltungList.js' +import { unterveranstaltungPublicGetProcedure } from './unterveranstaltungPublicGet.js' +import { unterveranstaltungVerwaltungCreateProcedure } from './unterveranstaltungVerwaltungCreate.js' +import { unterveranstaltungVerwaltungGetProcedure } from './unterveranstaltungVerwaltungGet.js' +import { unterveranstaltungVerwaltungPatchProcedure } from './unterveranstaltungVerwaltungPatch.js' // Import Routes here - do not delete this line export const unterveranstaltungRouter = mergeRouters( unterveranstaltungGliederungCreateProcedure.router, - unterveranstaltungGliederungListProcedure.router, unterveranstaltungGliederungPatchProcedure.router, unterveranstaltungGliederungGetProcedure.router, unterveranstaltungPublicGetProcedure.router, unterveranstaltungVerwaltungCreateProcedure.router, - unterveranstaltungVerwaltungListProcedure.router, unterveranstaltungVerwaltungPatchProcedure.router, unterveranstaltungVerwaltungGetProcedure.router, + unterveranstaltungListProcedure.router, + unterveranstaltungCountProcedure.router // Add Routes here - do not delete this line ) diff --git a/apps/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts b/apps/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts new file mode 100644 index 00000000..212d9ed0 --- /dev/null +++ b/apps/api/src/services/unterveranstaltung/unterveranstaltungGliederungCreate.ts @@ -0,0 +1,78 @@ +import { Role } from '@prisma/client' +import { TRPCError } from '@trpc/server' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin.js' +import { unterveranstaltungCreateSchema } from './schema/unterveranstaltung.schema.js' + +export const unterveranstaltungGliederungCreateProcedure = defineProtectedMutateProcedure({ + key: 'gliederungCreate', + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], + inputSchema: z.strictObject({ + data: unterveranstaltungCreateSchema, + }), + async handler({ ctx, input }) { + // check logged in user is admin of gliederung + const gliederung = await getGliederungRequireAdmin(ctx.accountId) + const veranstaltung = await prisma.veranstaltung.findUniqueOrThrow({ + where: { + id: input.data.veranstaltungId, + }, + select: { + meldebeginn: true, + meldeschluss: true, + }, + }) + // check meldebegin is after parent meldebeginn + if (new Date(input.data.meldebeginn) < veranstaltung.meldebeginn) { + throw new TRPCError({ + message: 'Der Meldebeginn darf nicht vor dem Meldebeginn der übergeordneten Veranstaltung liegen', + code: 'BAD_REQUEST', + }) + } + // check meldeschluss is before parent meldeschluss + if (new Date(input.data.meldeschluss) > veranstaltung.meldeschluss) { + throw new TRPCError({ + message: 'Der Meldeschluss darf nicht nach dem Meldeschluss der übergeordneten Veranstaltung liegen', + code: 'BAD_REQUEST', + }) + } + + return await prisma.unterveranstaltung.create({ + data: { + veranstaltung: { + connect: { + id: input.data.veranstaltungId, + }, + }, + maxTeilnehmende: input.data.maxTeilnehmende, + teilnahmegebuehr: input.data.teilnahmegebuehr, + meldebeginn: input.data.meldebeginn, + meldeschluss: input.data.meldeschluss, + beschreibung: input.data.beschreibung, + bedingungen: input.data.bedingungen, + type: 'GLIEDERUNG', + gliederung: { + connect: { + id: gliederung.id, + }, + }, + landingSettings: { + create: { + heroTitle: 'Es ist die Veranstaltung des Jahres', + heroSubtitle: 'Pflege hier einen kurzen beschreibenden Untertitel ein.', + eventDetailsTitle: 'Alle Details auf einen Blick', + eventDetailsContent: 'Hier kannst du alle wichtigen Informationen zur Veranstaltung einsehen.', + miscellaneousVisible: false, + faqVisible: false, + }, + }, + }, + select: { + id: true, + }, + }) + }, +}) diff --git a/apps/api/src/services/unterveranstaltung/unterveranstaltungGliederungGet.ts b/apps/api/src/services/unterveranstaltung/unterveranstaltungGliederungGet.ts new file mode 100644 index 00000000..bd196074 --- /dev/null +++ b/apps/api/src/services/unterveranstaltung/unterveranstaltungGliederungGet.ts @@ -0,0 +1,97 @@ +import { Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' + +export const unterveranstaltungGliederungGetProcedure = defineProtectedQueryProcedure({ + key: 'gliederungGet', + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], + inputSchema: z.strictObject({ + id: z.number().int(), + }), + handler: (options) => + prisma.unterveranstaltung.findUniqueOrThrow({ + where: { + id: options.input.id, + }, + select: { + id: true, + beschreibung: true, + bedingungen: true, + maxTeilnehmende: true, + teilnahmegebuehr: true, + meldebeginn: true, + meldeschluss: true, + veranstaltung: { + select: { + id: true, + name: true, + beginn: true, + ende: true, + ort: true, + meldebeginn: true, + meldeschluss: true, + maxTeilnehmende: true, + teilnahmegebuehr: true, + beschreibung: true, + zielgruppe: true, + teilnahmeBedingungen: true, + teilnahmeBedingungenPublic: true, + datenschutz: true, + hostname: true, + }, + }, + gliederung: { + select: { + id: true, + name: true, + edv: true, + }, + }, + documents: { + select: { + id: true, + name: true, + description: true, + fileId: true, + file: { + select: { + id: true, + filename: true, + mimetype: true, + createdAt: true, + }, + }, + }, + }, + landingSettings: { + select: { + eventDetailsContent: true, + eventDetailsTitle: true, + faqEmail: true, + faqVisible: true, + heroImages: { + select: { + id: true, + name: true, + file: true, + }, + }, + heroSubtitle: true, + heroTitle: true, + miscellaneousItems: { + select: { + id: true, + title: true, + content: true, + }, + }, + miscellaneousTitle: true, + miscellaneousSubtitle: true, + miscellaneousVisible: true, + }, + }, + }, + }), +}) diff --git a/apps/api/src/services/unterveranstaltung/unterveranstaltungGliederungPatch.ts b/apps/api/src/services/unterveranstaltung/unterveranstaltungGliederungPatch.ts new file mode 100644 index 00000000..39b5aa99 --- /dev/null +++ b/apps/api/src/services/unterveranstaltung/unterveranstaltungGliederungPatch.ts @@ -0,0 +1,81 @@ +import { Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin.js' +import { unterveranstaltungLandingSchema, unterveranstaltungUpdateSchema } from './schema/unterveranstaltung.schema.js' +import { crudFiles } from './schema/crudFiles.js' +import { crudMiscellaneousItems } from './schema/crudMiscellaneousItems.js' + +export const unterveranstaltungGliederungPatchProcedure = defineProtectedMutateProcedure({ + key: 'gliederungPatch', + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], + inputSchema: z.strictObject({ + id: z.number().int(), + data: unterveranstaltungUpdateSchema.partial().optional(), + landingSettings: unterveranstaltungLandingSchema.partial().optional(), + }), + async handler(options) { + await getGliederungRequireAdmin(options.ctx.accountId) + + // Documents create, update, delete + const documents = crudFiles( + options.input.data?.addDocuments, + options.input.data?.updateDocuments, + options.input.data?.deleteDocumentIds + ) + + const heroImages = crudFiles( + options.input.landingSettings?.addHeroImages, + options.input.landingSettings?.updateHeroImages, + options.input.landingSettings?.deleteHeroImageIds + ) + + const miscellaneousItems = crudMiscellaneousItems( + options.input.landingSettings?.addMiscellaneousItems, + options.input.landingSettings?.updateMiscellaneousItems, + options.input.landingSettings?.deleteMiscellaneousItemIds + ) + + if (options.input.data) { + await prisma.unterveranstaltung.update({ + where: { + id: options.input.id, + }, + data: { + maxTeilnehmende: options.input.data.maxTeilnehmende, + teilnahmegebuehr: options.input.data.teilnahmegebuehr, + meldebeginn: options.input.data.meldebeginn, + meldeschluss: options.input.data.meldeschluss, + beschreibung: options.input.data.beschreibung, + bedingungen: options.input.data.bedingungen, + documents: documents, + }, + }) + } + + if (options.input.landingSettings) { + await prisma.unterveranstaltungLandingSettings.update({ + data: { + heroTitle: options.input.landingSettings.heroTitle, + heroSubtitle: options.input.landingSettings.heroSubtitle, + eventDetailsTitle: options.input.landingSettings.eventDetailsTitle, + eventDetailsContent: options.input.landingSettings.eventDetailsContent, + miscellaneousVisible: options.input.landingSettings.miscellaneousVisible, + miscellaneousTitle: options.input.landingSettings.miscellaneousTitle, + miscellaneousSubtitle: options.input.landingSettings.miscellaneousSubtitle, + faqVisible: options.input.landingSettings.faqVisible, + faqEmail: options.input.landingSettings.faqEmail, + heroImages: heroImages, + miscellaneousItems: miscellaneousItems, + facebookUrl: options.input.landingSettings.facebookUrl, + instagramUrl: options.input.landingSettings.instagramUrl, + }, + where: { + unterveranstaltungId: options.input.id, + }, + }) + } + }, +}) diff --git a/apps/api/src/services/unterveranstaltung/unterveranstaltungList.ts b/apps/api/src/services/unterveranstaltung/unterveranstaltungList.ts new file mode 100644 index 00000000..c7753c99 --- /dev/null +++ b/apps/api/src/services/unterveranstaltung/unterveranstaltungList.ts @@ -0,0 +1,112 @@ +import { Role, type Prisma } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import { defineQuery, getOrderBy } from '../../types/defineQuery.js' +import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin.js' + +const inputSchema = defineQuery({ + filter: z.strictObject({ + veranstaltungId: z.number().optional(), + gliederungId: z.number().optional(), + gliederungName: z.string().optional(), + }), + orderBy: z.array( + z.tuple([ + z.union([ + z.literal('id'), + z.literal('veranstaltung.name'), + z.literal('gliederung.name'), + z.literal('meldeschluss'), + z.literal('teilnahmegebuehr'), + z.literal('type'), + ]), + z.union([z.literal('asc'), z.literal('desc')]), + ]) + ), +}) + +type Input = z.infer + +export const unterveranstaltungListProcedure = defineProtectedQueryProcedure({ + key: 'list', + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], + inputSchema: inputSchema, + async handler(options) { + const { skip, take } = options.input.pagination + const veranstaltungen = await prisma.unterveranstaltung.findMany({ + skip, + take, + where: await getWhere(options.input.filter, options.ctx.account), + orderBy: getOrderBy(options.input.orderBy), + select: { + id: true, + type: true, + gliederung: { + select: { + id: true, + name: true, + }, + }, + veranstaltung: { + select: { + id: true, + name: true, + hostname: true, + }, + }, + _count: { + select: { Anmeldung: true }, + }, + meldebeginn: true, + meldeschluss: true, + maxTeilnehmende: true, + teilnahmegebuehr: true, + }, + }) + + return veranstaltungen + }, +}) + +export const unterveranstaltungCountProcedure = defineProtectedQueryProcedure({ + key: 'count', + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], + inputSchema: inputSchema.pick({ filter: true }), + async handler(options) { + const unterveranstaltungenCount = await prisma.unterveranstaltung.count({ + where: await getWhere(options.input.filter, options.ctx.account), + }) + + return unterveranstaltungenCount + }, +}) + +async function getWhere( + filter: Input['filter'], + account: { + id: number + role: Role + } +): Promise { + const where: Prisma.UnterveranstaltungWhereInput = {} + + // Input-Filter + if (filter.gliederungId !== undefined) where.gliederungId = filter.gliederungId + if (filter.veranstaltungId !== undefined) where.veranstaltungId = filter.veranstaltungId + if (filter.gliederungName !== undefined) + where.gliederung = { + name: { + contains: filter.gliederungName, + }, + } + + // Role-based Filter + if (account.role !== Role.ADMIN) { + const gliederung = await getGliederungRequireAdmin(account.id) + where.gliederungId = gliederung.id + } + + return where +} diff --git a/apps/api/src/services/unterveranstaltung/unterveranstaltungPublicGet.ts b/apps/api/src/services/unterveranstaltung/unterveranstaltungPublicGet.ts new file mode 100644 index 00000000..47c11d5b --- /dev/null +++ b/apps/api/src/services/unterveranstaltung/unterveranstaltungPublicGet.ts @@ -0,0 +1,123 @@ +import z from 'zod' + +import prisma from '../../prisma.js' +import { definePublicQueryProcedure } from '../../types/defineProcedure.js' +import { listFaqs } from '../faqs/faqListProcedure.js' +import { getFileUrl } from '../file/helpers/getFileUrl.js' + +export const unterveranstaltungPublicGetProcedure = definePublicQueryProcedure({ + key: 'publicGet', + inputSchema: z.strictObject({ + id: z.number(), + }), + async handler({ input }) { + const unterveranstaltung = await prisma.unterveranstaltung.findUniqueOrThrow({ + where: { + id: input.id, + }, + select: { + id: true, + veranstaltungId: true, + meldebeginn: true, + meldeschluss: true, + maxTeilnehmende: true, + teilnahmegebuehr: true, + type: true, + veranstaltung: { + select: { + name: true, + beginn: true, + ende: true, + ort: { + select: { + name: true, + }, + }, + datenschutz: true, + teilnahmeBedingungen: true, + teilnahmeBedingungenPublic: true, + zielgruppe: true, + hostname: true, + }, + }, + gliederung: { + select: { + id: true, + name: true, + }, + }, + landingSettings: { + select: { + heroTitle: true, + heroSubtitle: true, + heroImages: { + select: { + fileId: true, + name: true, + file: { + select: { + id: true, + provider: true, + uploaded: true, + key: true, + }, + }, + }, + }, + eventDetailsTitle: true, + eventDetailsContent: true, + miscellaneousVisible: true, + miscellaneousTitle: true, + miscellaneousSubtitle: true, + miscellaneousItems: { + select: { + title: true, + content: true, + }, + }, + faqVisible: true, + faqEmail: true, + instagramVisible: true, + instagramUrl: true, + facebookVisible: true, + facebookUrl: true, + }, + }, + _count: { + select: { + Anmeldung: { + where: { + status: 'BESTAETIGT', + }, + }, + }, + }, + beschreibung: true, + bedingungen: true, + documents: { + select: { + name: true, + fileId: true, + }, + }, + }, + }) + + const heroImages = await Promise.all( + unterveranstaltung.landingSettings?.heroImages.map(async (image) => { + return { + ...image.file, + url: await getFileUrl(image.file), + } + }) ?? [] + ) + + const faqs = await listFaqs(input.id) + + return { + ...unterveranstaltung, + heroImages, + faqs, + } + }, +}) diff --git a/apps/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts b/apps/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts new file mode 100644 index 00000000..2ff3405a --- /dev/null +++ b/apps/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungCreate.ts @@ -0,0 +1,57 @@ +import { Role, UnterveranstaltungType } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import { unterveranstaltungCreateSchema } from './schema/unterveranstaltung.schema.js' + +const unterveranstaltungVerwaltungCreateSchema = unterveranstaltungCreateSchema.extend({ + gliederungId: z.number().int(), + type: z.nativeEnum(UnterveranstaltungType), +}) + +export const unterveranstaltungVerwaltungCreateProcedure = defineProtectedMutateProcedure({ + key: 'verwaltungCreate', + roleIds: [Role.ADMIN], + inputSchema: z.strictObject({ + data: unterveranstaltungVerwaltungCreateSchema, + }), + async handler({ input }) { + const unterveranstaltung = await prisma.unterveranstaltung.create({ + data: { + veranstaltung: { + connect: { + id: input.data.veranstaltungId, + }, + }, + maxTeilnehmende: input.data.maxTeilnehmende, + teilnahmegebuehr: input.data.teilnahmegebuehr, + meldebeginn: input.data.meldebeginn, + meldeschluss: input.data.meldeschluss, + beschreibung: input.data.beschreibung, + bedingungen: input.data.bedingungen, + type: input.data.type, + gliederung: { + connect: { + id: input.data.gliederungId, + }, + }, + landingSettings: { + create: { + heroTitle: 'Es ist die Veranstaltung des Jahres', + heroSubtitle: 'Pflege hier einen kurzen beschreibenden Untertitel ein.', + eventDetailsTitle: 'Alle Details auf einen Blick', + eventDetailsContent: 'Hier kannst du alle wichtigen Informationen zur Veranstaltung einsehen.', + miscellaneousVisible: false, + faqVisible: false, + }, + }, + }, + select: { + id: true, + }, + }) + + return unterveranstaltung + }, +}) diff --git a/apps/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungGet.ts b/apps/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungGet.ts new file mode 100644 index 00000000..09f1b0ad --- /dev/null +++ b/apps/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungGet.ts @@ -0,0 +1,102 @@ +import { Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' + +export const unterveranstaltungVerwaltungGetProcedure = defineProtectedQueryProcedure({ + key: 'verwaltungGet', + roleIds: [Role.ADMIN], + inputSchema: z.strictObject({ + id: z.number().int(), + }), + async handler(options) { + return await prisma.unterveranstaltung.findUniqueOrThrow({ + where: { + id: options.input.id, + }, + select: { + id: true, + beschreibung: true, + bedingungen: true, + maxTeilnehmende: true, + teilnahmegebuehr: true, + meldebeginn: true, + meldeschluss: true, + type: true, + gliederung: { + select: { + id: true, + name: true, + edv: true, + }, + }, + veranstaltung: { + select: { + id: true, + name: true, + beginn: true, + ende: true, + ort: true, + meldebeginn: true, + meldeschluss: true, + maxTeilnehmende: true, + teilnahmegebuehr: true, + beschreibung: true, + zielgruppe: true, + teilnahmeBedingungen: true, + teilnahmeBedingungenPublic: true, + datenschutz: true, + hostname: true, + }, + }, + documents: { + select: { + id: true, + name: true, + description: true, + fileId: true, + file: { + select: { + id: true, + filename: true, + mimetype: true, + createdAt: true, + }, + }, + }, + orderBy: { + id: 'asc', + }, + }, + landingSettings: { + select: { + eventDetailsContent: true, + eventDetailsTitle: true, + faqEmail: true, + faqVisible: true, + heroImages: { + select: { + id: true, + name: true, + file: true, + }, + }, + heroSubtitle: true, + heroTitle: true, + miscellaneousItems: { + select: { + id: true, + title: true, + content: true, + }, + }, + miscellaneousTitle: true, + miscellaneousSubtitle: true, + miscellaneousVisible: true, + }, + }, + }, + }) + }, +}) diff --git a/apps/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungPatch.ts b/apps/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungPatch.ts new file mode 100644 index 00000000..6787b198 --- /dev/null +++ b/apps/api/src/services/unterveranstaltung/unterveranstaltungVerwaltungPatch.ts @@ -0,0 +1,78 @@ +import { Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' +import { crudFiles } from './schema/crudFiles.js' +import { crudMiscellaneousItems } from './schema/crudMiscellaneousItems.js' +import { unterveranstaltungLandingSchema, unterveranstaltungUpdateSchema } from './schema/unterveranstaltung.schema.js' + +export const unterveranstaltungVerwaltungPatchProcedure = defineProtectedMutateProcedure({ + key: 'verwaltungPatch', + roleIds: [Role.ADMIN], + inputSchema: z.strictObject({ + id: z.number().int(), + data: unterveranstaltungUpdateSchema.partial().optional(), + landingSettings: unterveranstaltungLandingSchema.partial().optional(), + }), + async handler(options) { + // Documents create, update, delete + const documents = crudFiles( + options.input.data?.addDocuments, + options.input.data?.updateDocuments, + options.input.data?.deleteDocumentIds + ) + + const heroImages = crudFiles( + options.input.landingSettings?.addHeroImages, + options.input.landingSettings?.updateHeroImages, + options.input.landingSettings?.deleteHeroImageIds + ) + + const miscellaneousItems = crudMiscellaneousItems( + options.input.landingSettings?.addMiscellaneousItems, + options.input.landingSettings?.updateMiscellaneousItems, + options.input.landingSettings?.deleteMiscellaneousItemIds + ) + + if (options.input.data) { + await prisma.unterveranstaltung.update({ + where: { + id: options.input.id, + }, + data: { + maxTeilnehmende: options.input.data.maxTeilnehmende, + teilnahmegebuehr: options.input.data.teilnahmegebuehr, + meldebeginn: options.input.data.meldebeginn, + meldeschluss: options.input.data.meldeschluss, + beschreibung: options.input.data.beschreibung, + bedingungen: options.input.data.bedingungen, + documents: documents, + }, + }) + } + + if (options.input.landingSettings) { + await prisma.unterveranstaltungLandingSettings.update({ + data: { + heroTitle: options.input.landingSettings.heroTitle, + heroSubtitle: options.input.landingSettings.heroSubtitle, + eventDetailsTitle: options.input.landingSettings.eventDetailsTitle, + eventDetailsContent: options.input.landingSettings.eventDetailsContent, + miscellaneousVisible: options.input.landingSettings.miscellaneousVisible, + miscellaneousTitle: options.input.landingSettings.miscellaneousTitle, + miscellaneousSubtitle: options.input.landingSettings.miscellaneousSubtitle, + faqVisible: options.input.landingSettings.faqVisible, + faqEmail: options.input.landingSettings.faqEmail, + heroImages: heroImages, + miscellaneousItems: miscellaneousItems, + facebookUrl: options.input.landingSettings.facebookUrl, + instagramUrl: options.input.landingSettings.instagramUrl, + }, + where: { + unterveranstaltungId: options.input.id, + }, + }) + } + }, +}) diff --git a/api/src/services/veranstaltung/veranstaltung.router.ts b/apps/api/src/services/veranstaltung/veranstaltung.router.ts similarity index 58% rename from api/src/services/veranstaltung/veranstaltung.router.ts rename to apps/api/src/services/veranstaltung/veranstaltung.router.ts index 6d4a8a1a..7c0861a5 100644 --- a/api/src/services/veranstaltung/veranstaltung.router.ts +++ b/apps/api/src/services/veranstaltung/veranstaltung.router.ts @@ -1,18 +1,22 @@ -/* eslint-disable prettier/prettier */ // Prettier ignored is because this file is generated -import { mergeRouters } from '../../trpc' +// Prettier ignored is because this file is generated +import { mergeRouters } from '../../trpc.js' -import { veranstaltungGliederungListProcedure } from './veranstaltungGliederungList' -import { veranstaltungVerwaltungCreateProcedure } from './veranstaltungVerwaltungCreate' -import { veranstaltungVerwaltungGetProcedure } from './veranstaltungVerwaltungGet' -import { veranstaltungVerwaltungListProcedure } from './veranstaltungVerwaltungList' -import { veranstaltungVerwaltungPatchProcedure } from './veranstaltungVerwaltungPatch' +import { veranstaltungGliederungListProcedure } from './veranstaltungGliederungList.js' +import { veranstaltungVerwaltungCreateProcedure } from './veranstaltungVerwaltungCreate.js' +import { veranstaltungVerwaltungGetProcedure } from './veranstaltungVerwaltungGet.js' +import { + veranstaltungVerwaltungListProcedure, + veranstaltungVerwaltungCountProcedure, +} from './veranstaltungVerwaltungList.js' +import { veranstaltungVerwaltungPatchProcedure } from './veranstaltungVerwaltungPatch.js' // Import Routes here - do not delete this line export const veranstaltungRouter = mergeRouters( veranstaltungVerwaltungCreateProcedure.router, veranstaltungVerwaltungGetProcedure.router, veranstaltungVerwaltungListProcedure.router, + veranstaltungVerwaltungCountProcedure.router, veranstaltungVerwaltungPatchProcedure.router, - veranstaltungGliederungListProcedure.router, + veranstaltungGliederungListProcedure.router // Add Routes here - do not delete this line ) diff --git a/api/src/services/veranstaltung/veranstaltungGliederungList.ts b/apps/api/src/services/veranstaltung/veranstaltungGliederungList.ts similarity index 65% rename from api/src/services/veranstaltung/veranstaltungGliederungList.ts rename to apps/api/src/services/veranstaltung/veranstaltungGliederungList.ts index 4232c72c..2969d8cb 100644 --- a/api/src/services/veranstaltung/veranstaltungGliederungList.ts +++ b/apps/api/src/services/veranstaltung/veranstaltungGliederungList.ts @@ -1,16 +1,19 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' -import { defineQuery } from '../../types/defineQuery' -import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin' +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import { defineQuery } from '../../types/defineQuery.js' +import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin.js' -export const veranstaltungGliederungListProcedure = defineProcedure({ +export const veranstaltungGliederungListProcedure = defineProtectedQueryProcedure({ key: 'gliederungList', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['GLIEDERUNG_ADMIN'] }, + roleIds: [Role.GLIEDERUNG_ADMIN], inputSchema: defineQuery({ filter: z.strictObject({}), + orderBy: z.array( + z.tuple([z.union([z.literal('id'), z.literal('name')]), z.union([z.literal('asc'), z.literal('desc')])]) + ), }), async handler(options) { const { skip, take } = options.input.pagination @@ -59,6 +62,17 @@ export const veranstaltungGliederungListProcedure = defineProcedure({ }, select: { id: true, + _count: { + select: { + Anmeldung: { + where: { + status: { + equals: 'BESTAETIGT', + }, + }, + }, + }, + }, }, }, hostname: { diff --git a/api/src/services/veranstaltung/veranstaltungVerwaltungCreate.ts b/apps/api/src/services/veranstaltung/veranstaltungVerwaltungCreate.ts similarity index 75% rename from api/src/services/veranstaltung/veranstaltungVerwaltungCreate.ts rename to apps/api/src/services/veranstaltung/veranstaltungVerwaltungCreate.ts index c7a2efcc..d1e2149c 100644 --- a/api/src/services/veranstaltung/veranstaltungVerwaltungCreate.ts +++ b/apps/api/src/services/veranstaltung/veranstaltungVerwaltungCreate.ts @@ -1,12 +1,12 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' -export const veranstaltungVerwaltungCreateProcedure = defineProcedure({ +export const veranstaltungVerwaltungCreateProcedure = defineProtectedMutateProcedure({ key: 'verwaltungCreate', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ data: z.strictObject({ name: z.string(), diff --git a/api/src/services/veranstaltung/veranstaltungVerwaltungGet.ts b/apps/api/src/services/veranstaltung/veranstaltungVerwaltungGet.ts similarity index 76% rename from api/src/services/veranstaltung/veranstaltungVerwaltungGet.ts rename to apps/api/src/services/veranstaltung/veranstaltungVerwaltungGet.ts index ed46b5f0..3847c878 100644 --- a/api/src/services/veranstaltung/veranstaltungVerwaltungGet.ts +++ b/apps/api/src/services/veranstaltung/veranstaltungVerwaltungGet.ts @@ -1,12 +1,12 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' -export const veranstaltungVerwaltungGetProcedure = defineProcedure({ +export const veranstaltungVerwaltungGetProcedure = defineProtectedQueryProcedure({ key: 'verwaltungGet', - method: 'query', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'] }, + roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN], inputSchema: z.strictObject({ id: z.number(), }), diff --git a/apps/api/src/services/veranstaltung/veranstaltungVerwaltungList.ts b/apps/api/src/services/veranstaltung/veranstaltungVerwaltungList.ts new file mode 100644 index 00000000..47f34615 --- /dev/null +++ b/apps/api/src/services/veranstaltung/veranstaltungVerwaltungList.ts @@ -0,0 +1,118 @@ +import { Prisma, Role } from '@prisma/client' +import z from 'zod' + +import prisma from '../../prisma.js' +import { defineProtectedQueryProcedure } from '../../types/defineProcedure.js' +import { defineQuery, getOrderBy } from '../../types/defineQuery.js' + +const inputSchema = defineQuery({ + filter: z.strictObject({ + name: z.string().optional(), + }), + orderBy: z.array( + z.tuple([ + z.union([ + z.literal('id'), + z.literal('name'), + z.literal('maxTeilnehmende'), + z.literal('teilnahmegebuehr'), + z.literal('beginn'), + z.literal('meldeschluss'), + ]), + z.union([z.literal('asc'), z.literal('desc')]), + ]) + ), +}) + +type TInput = z.infer + +export const veranstaltungVerwaltungListProcedure = defineProtectedQueryProcedure({ + key: 'verwaltungList', + roleIds: [Role.ADMIN], + inputSchema, + async handler(options) { + const { skip, take } = options.input.pagination + const veranstaltungen = await prisma.veranstaltung.findMany({ + skip, + take, + where: await getWhere(options.input.filter, options.ctx.account), + orderBy: getOrderBy(options.input.orderBy), + select: { + id: true, + name: true, + beginn: true, + ende: true, + ort: { + select: { + name: true, + id: true, + }, + }, + meldebeginn: true, + meldeschluss: true, + maxTeilnehmende: true, + teilnahmegebuehr: true, + unterveranstaltungen: { + select: { + id: true, + maxTeilnehmende: true, + teilnahmegebuehr: true, + meldebeginn: true, + meldeschluss: true, + gliederungId: true, + _count: { + select: { + Anmeldung: { + where: { + status: { + equals: 'BESTAETIGT', + }, + }, + }, + }, + }, + }, + }, + hostname: { + select: { + id: true, + hostname: true, + }, + }, + }, + }) + + return veranstaltungen + }, +}) + +export const veranstaltungVerwaltungCountProcedure = defineProtectedQueryProcedure({ + key: 'verwaltungCount', + roleIds: [Role.ADMIN], + inputSchema: inputSchema.pick({ filter: true }), + async handler(options) { + return await prisma.veranstaltung.count({ + where: await getWhere(options.input.filter, options.ctx.account), + }) + }, +}) + +// eslint-disable-next-line @typescript-eslint/require-await +async function getWhere( + filter: TInput['filter'], + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _account: { + id: number + role: Role + } +): Promise { + const where: Prisma.VeranstaltungWhereInput = {} + + if (filter.name != undefined && filter.name != '') { + where.name = { + contains: filter.name, + } + } + + return where +} diff --git a/api/src/services/veranstaltung/veranstaltungVerwaltungPatch.ts b/apps/api/src/services/veranstaltung/veranstaltungVerwaltungPatch.ts similarity index 86% rename from api/src/services/veranstaltung/veranstaltungVerwaltungPatch.ts rename to apps/api/src/services/veranstaltung/veranstaltungVerwaltungPatch.ts index bf970b44..df14b5b0 100644 --- a/api/src/services/veranstaltung/veranstaltungVerwaltungPatch.ts +++ b/apps/api/src/services/veranstaltung/veranstaltungVerwaltungPatch.ts @@ -1,12 +1,12 @@ +import { Role } from '@prisma/client' import z from 'zod' -import prisma from '../../prisma' -import { defineProcedure } from '../../types/defineProcedure' +import prisma from '../../prisma.js' +import { defineProtectedMutateProcedure } from '../../types/defineProcedure.js' -export const veranstaltungVerwaltungPatchProcedure = defineProcedure({ +export const veranstaltungVerwaltungPatchProcedure = defineProtectedMutateProcedure({ key: 'verwaltungPatch', - method: 'mutation', - protection: { type: 'restrictToRoleIds', roleIds: ['ADMIN'] }, + roleIds: [Role.ADMIN], inputSchema: z.strictObject({ id: z.number(), data: z.strictObject({ diff --git a/api/src/trpc.ts b/apps/api/src/trpc.ts similarity index 56% rename from api/src/trpc.ts rename to apps/api/src/trpc.ts index 42318c46..6d493551 100644 --- a/api/src/trpc.ts +++ b/apps/api/src/trpc.ts @@ -2,12 +2,11 @@ import type { ActivityType, Role } from '@prisma/client' import { TRPCError, initTRPC } from '@trpc/server' import superjson from 'superjson' -import config from './config' -import { type Context } from './context' -import { logger } from './logger' -import { trpc_call_duration } from './metrics' -import prisma from './prisma' -import logActivity from './util/activity' +import config from './config.js' +import { type Context } from './context.js' +import { logger } from './logger.js' +import { trpc_call_duration } from './metrics.js' +import logActivity from './util/activity.js' const t = initTRPC.context().create({ transformer: superjson, @@ -27,11 +26,13 @@ const loggerMiddleware = middleware(async (opts) => { durationMs ) - if (result.ok) logger.info(`[${opts.ctx.accountId ?? 'public'}] ${meta.path}.${meta.type} [${durationMs}ms]`) + const causer = opts.ctx.authenticated ? opts.ctx.accountId : 'public' + + if (result.ok) logger.info(`[${causer}] ${meta.path}.${meta.type} [${durationMs}ms]`) else { const stack = config.loggingLevel === 'debug' ? result.error.stack : result.error.message // maybe dont log all errors - logger.error(`[${opts.ctx.accountId ?? 'public'}] ${meta.path}.${meta.type} [${durationMs}ms] ${stack}`) + logger.error(`[${causer}] ${meta.path}.${meta.type} [${durationMs}ms] ${stack}`) } return result }) @@ -43,7 +44,9 @@ const logActivityMiddleware = middleware(async (opts) => { let type: ActivityType | undefined = undefined const [subject, operation] = opts.path.split('.') - if (operation.endsWith('Create')) { + if (operation == undefined) { + type = 'OTHER' + } else if (operation.endsWith('Create')) { type = 'CREATE' } else if (operation.endsWith('Patch')) { type = 'UPDATE' @@ -52,13 +55,15 @@ const logActivityMiddleware = middleware(async (opts) => { } if (type !== undefined) { + // const rawInput = opts.getRawInput() as { id: number } + const resultData = result.data as { id?: number } logger.verbose(`Recording activity ${opts.path} of type ${type}`) + const subjectId = type === 'CREATE' ? resultData?.id : 9999 + if (!subjectId) throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'No subjectId found' }) await logActivity({ - // @ts-expect-error ist unknowmn - subjectId: type === 'CREATE' ? result.data?.id : opts.rawInput?.id, - subjectType: subject, - causerId: opts.ctx.accountId, - metadata: opts.rawInput, + subjectId, + subjectType: subject || 'NO_SUBJECT', + causerId: opts.ctx.authenticated ? opts.ctx.accountId : undefined, type, }) } @@ -67,44 +72,30 @@ const logActivityMiddleware = middleware(async (opts) => { return result }) -async function getAuthContext(accountId: number | undefined, roles: Role[]) { - if (accountId === undefined) { - throw new TRPCError({ code: 'UNAUTHORIZED' }) - } - const account = await prisma.account.findUniqueOrThrow({ - where: { id: accountId }, - select: { - id: true, - role: true, - person: { - select: { - gliederungId: true, - }, - }, - }, - }) - if (!roles.includes(account.role) && roles.length > 0) { - // if roles is empty, the resource is public - throw new TRPCError({ - code: 'FORBIDDEN', - message: `You are not allowed to access this resource "${roles}" with "${account.role}"`, - }) - } - return { - ctx: { - account: account, - accountId: account.id, - }, - } -} - const isAuthed = (roles: Role[]) => - t.middleware(async (opts) => { - const context = await getAuthContext(opts.ctx.accountId, roles) - return opts.next(context) + t.middleware((opts) => { + if (!opts.ctx.authenticated) { + throw new TRPCError({ + code: 'FORBIDDEN', + message: `You are not allowed to access this resource`, + }) + } + + if (!roles.includes(opts.ctx.account.role) && roles.length > 0) { + // if roles is empty, the resource is public + throw new TRPCError({ + code: 'FORBIDDEN', + message: `You are not allowed to access this resource with "${opts.ctx.account.role}"`, + }) + } + + return opts.next({ + ...opts, + ctx: opts.ctx, + }) }) -export type AuthenticatedContext = Awaited> +export type AuthenticatedContext = Extract export const router = t.router export const mergeRouters = t.mergeRouters diff --git a/api/src/types/defineCustomFieldValues.ts b/apps/api/src/types/defineCustomFieldValues.ts similarity index 72% rename from api/src/types/defineCustomFieldValues.ts rename to apps/api/src/types/defineCustomFieldValues.ts index 9f7615cb..618a5aa7 100644 --- a/api/src/types/defineCustomFieldValues.ts +++ b/apps/api/src/types/defineCustomFieldValues.ts @@ -10,12 +10,14 @@ export function defineCustomFieldValues() { ) } -export function customFieldValuesCreateMany(data) { +export function customFieldValuesCreateMany( + data: { fieldId: number; value?: string | number | boolean | undefined }[] +) { const customField: Prisma.CustomFieldValueCreateManyArgs = { data: data.map((field) => { return { fieldId: field.fieldId, - value: field.value.toString(), + value: field.value?.toString(), } }), } diff --git a/apps/api/src/types/defineOrderBy.ts b/apps/api/src/types/defineOrderBy.ts new file mode 100644 index 00000000..98e05fe4 --- /dev/null +++ b/apps/api/src/types/defineOrderBy.ts @@ -0,0 +1,6 @@ +import { z } from 'zod' +import { ZOrderbySchema } from './defineQuery.js' + +export function defineOrderBy>(orderByConfig: TKeys) { + return z.array(z.record(z.enum(orderByConfig), ZOrderbySchema)).optional() +} diff --git a/apps/api/src/types/defineProcedure.ts b/apps/api/src/types/defineProcedure.ts new file mode 100644 index 00000000..2d0a2a45 --- /dev/null +++ b/apps/api/src/types/defineProcedure.ts @@ -0,0 +1,95 @@ +import type { Role } from '@prisma/client' +import type { z } from 'zod' + +import { protectedProcedure, publicProcedure, router, type AuthenticatedContext } from '../trpc.js' +import type { Context } from '../context.js' + +export type MaybePromise = Promise | TType + +export function defineProtectedQueryProcedure< + const TProcedureKey extends string, + TInputSchema extends z.ZodSchema, + TProcedureResult, +>(config: { + /** Der Key unter dem der Endpunkt aufgerufen werden kann */ + key: TProcedureKey + /** Das Schema der Eingabedaten */ + inputSchema: TInputSchema + /** Der Handler der die Daten verarbeitet */ + handler: (options: { ctx: AuthenticatedContext; input: z.infer }) => MaybePromise + /** roles */ + roleIds: Role[] +}) { + const procedure = protectedProcedure(config.roleIds) + .input(config.inputSchema) + .query((opts) => config.handler(opts)) + return { + router: router({ + [config.key]: procedure, + } as { [k in TProcedureKey]: typeof procedure }), + } +} + +export function defineProtectedMutateProcedure< + const TProcedureKey extends string, + TInputSchema extends z.ZodSchema, + TProcedureResult, +>(config: { + /** Der Key unter dem der Endpunkt aufgerufen werden kann */ + key: TProcedureKey + /** Das Schema der Eingabedaten */ + inputSchema: TInputSchema + /** Der Handler der die Daten verarbeitet */ + handler: (options: { ctx: AuthenticatedContext; input: z.infer }) => MaybePromise + /** roles */ + roleIds: Role[] +}) { + const procedure = protectedProcedure(config.roleIds) + .input(config.inputSchema) + .mutation((opts) => config.handler(opts)) + return { + router: router({ + [config.key]: procedure, + } as { [k in TProcedureKey]: typeof procedure }), + } +} + +export function definePublicQueryProcedure< + const TProcedureKey extends string, + TInputSchema extends z.ZodSchema, + TProcedureResult, +>(config: { + /** Der Key unter dem der Endpunkt aufgerufen werden kann */ + key: TProcedureKey + /** Das Schema der Eingabedaten */ + inputSchema: TInputSchema + /** Der Handler der die Daten verarbeitet */ + handler: (options: { ctx: Context; input: z.infer }) => MaybePromise +}) { + const procedure = publicProcedure.input(config.inputSchema).query((opts) => config.handler(opts)) + return { + router: router({ + [config.key]: procedure, + } as { [k in TProcedureKey]: typeof procedure }), + } +} + +export function definePublicMutateProcedure< + const TProcedureKey extends string, + TInputSchema extends z.ZodSchema, + TProcedureResult, +>(config: { + /** Der Key unter dem der Endpunkt aufgerufen werden kann */ + key: TProcedureKey + /** Das Schema der Eingabedaten */ + inputSchema: TInputSchema + /** Der Handler der die Daten verarbeitet */ + handler: (options: { ctx: Context; input: z.infer }) => MaybePromise +}) { + const procedure = publicProcedure.input(config.inputSchema).mutation((opts) => config.handler(opts)) + return { + router: router({ + [config.key]: procedure, + } as { [k in TProcedureKey]: typeof procedure }), + } +} diff --git a/apps/api/src/types/defineQuery.ts b/apps/api/src/types/defineQuery.ts new file mode 100644 index 00000000..264baaa7 --- /dev/null +++ b/apps/api/src/types/defineQuery.ts @@ -0,0 +1,46 @@ +import z from 'zod' +import { set } from 'lodash-es' + +export const ZPaginationSchema = z.strictObject({ + skip: z.number().optional(), + take: z.number().max(100).optional(), +}) + +export const ZOrderbySchema = z.union([z.literal('asc'), z.literal('desc')]) + +export type TQueryPagination = z.infer + +export const defineQuery = >({ + filter, + orderBy, +}: { + filter: TFilter + orderBy: TOrderBy +}) => { + return z.strictObject({ + pagination: ZPaginationSchema, + filter: filter, + orderBy: orderBy, + }) +} + +export interface TQuery { + pagination: { + take: number + skip: number + } + groupBy?: Record + sortBy?: Record + filter: Record +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function getOrderBy(orderBy: Array<[T, 'asc' | 'desc']>): Record { + return orderBy.map(([field, order]) => { + const result = {} as { [key in T]: 'asc' | 'desc' } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + set(result, field, order) + return result + }) +} diff --git a/api/src/enumMappings/defineEnumMapping.ts b/apps/api/src/types/enums/defineEnumMapping.ts similarity index 83% rename from api/src/enumMappings/defineEnumMapping.ts rename to apps/api/src/types/enums/defineEnumMapping.ts index b6a69ac3..77c2668a 100644 --- a/api/src/enumMappings/defineEnumMapping.ts +++ b/apps/api/src/types/enums/defineEnumMapping.ts @@ -1,6 +1,5 @@ export type Prettify = { [K in keyof T]: T[K] - // eslint-disable-next-line @typescript-eslint/ban-types } & {} export type EnumMapping = { [K in T]: { human: string; description?: string } } diff --git a/api/src/enumMappings/getEnumOptions.ts b/apps/api/src/types/enums/getEnumOptions.ts similarity index 82% rename from api/src/enumMappings/getEnumOptions.ts rename to apps/api/src/types/enums/getEnumOptions.ts index 9497f676..198b5021 100644 --- a/api/src/enumMappings/getEnumOptions.ts +++ b/apps/api/src/types/enums/getEnumOptions.ts @@ -1,4 +1,4 @@ -import { type EnumMapping } from './defineEnumMapping' +import { type EnumMapping } from './defineEnumMapping.js' export function getEnumOptions(enumMapping: T) { return Object.entries(enumMapping).map(([apiKey, mapping]) => { diff --git a/apps/api/src/types/enums/index.ts b/apps/api/src/types/enums/index.ts new file mode 100644 index 00000000..b50034d7 --- /dev/null +++ b/apps/api/src/types/enums/index.ts @@ -0,0 +1,2 @@ +export * from './defineEnumMapping.js' +export * from './getEnumOptions.js' diff --git a/api/src/enumMappings/AccountStatus.ts b/apps/api/src/types/enums/mappings/AccountStatus.ts similarity index 80% rename from api/src/enumMappings/AccountStatus.ts rename to apps/api/src/types/enums/mappings/AccountStatus.ts index 3659bbb5..e05e46fb 100644 --- a/api/src/enumMappings/AccountStatus.ts +++ b/apps/api/src/types/enums/mappings/AccountStatus.ts @@ -1,6 +1,6 @@ import { AccountStatus } from '@prisma/client' -import { defineEnumMapping } from './defineEnumMapping' +import { defineEnumMapping } from '../defineEnumMapping.js' export const AccountStatusMapping = defineEnumMapping({ OFFEN: { human: 'Offen' }, diff --git a/api/src/enumMappings/Activity.ts b/apps/api/src/types/enums/mappings/Activity.ts similarity index 85% rename from api/src/enumMappings/Activity.ts rename to apps/api/src/types/enums/mappings/Activity.ts index b11d9aca..5f43321b 100644 --- a/api/src/enumMappings/Activity.ts +++ b/apps/api/src/types/enums/mappings/Activity.ts @@ -1,6 +1,6 @@ import type { Activity, ActivityType } from '@prisma/client' -import { defineEnumMapping } from './defineEnumMapping' +import { defineEnumMapping } from '../defineEnumMapping.js' export const ActivityTypeMapping = defineEnumMapping({ CREATE: { human: 'Erstellt' }, diff --git a/api/src/enumMappings/AnmeldungStatus.ts b/apps/api/src/types/enums/mappings/AnmeldungStatus.ts similarity index 81% rename from api/src/enumMappings/AnmeldungStatus.ts rename to apps/api/src/types/enums/mappings/AnmeldungStatus.ts index 1af32df4..410025e4 100644 --- a/api/src/enumMappings/AnmeldungStatus.ts +++ b/apps/api/src/types/enums/mappings/AnmeldungStatus.ts @@ -1,6 +1,6 @@ -import { type AnmeldungStatus } from '@prisma/client' +import type { AnmeldungStatus } from '@prisma/client' -import { defineEnumMapping } from './defineEnumMapping' +import { defineEnumMapping } from '../defineEnumMapping.js' export const AnmeldungStatusMapping = defineEnumMapping({ OFFEN: { human: 'Offen', description: 'Anmeldung ist noch nicht bearbeitet.' }, diff --git a/api/src/enumMappings/CustomFields.ts b/apps/api/src/types/enums/mappings/CustomFields.ts similarity index 96% rename from api/src/enumMappings/CustomFields.ts rename to apps/api/src/types/enums/mappings/CustomFields.ts index fcfb7c46..d84274d8 100644 --- a/api/src/enumMappings/CustomFields.ts +++ b/apps/api/src/types/enums/mappings/CustomFields.ts @@ -1,6 +1,6 @@ import { CustomFieldPosition, CustomFieldType, type CustomField } from '@prisma/client' -import { defineEnumMapping } from './defineEnumMapping' +import { defineEnumMapping } from '../defineEnumMapping.js' export { type CustomField } export const CustomFieldTypeMapping = defineEnumMapping({ diff --git a/api/src/enumMappings/Essgewohnheit.ts b/apps/api/src/types/enums/mappings/Essgewohnheit.ts similarity index 79% rename from api/src/enumMappings/Essgewohnheit.ts rename to apps/api/src/types/enums/mappings/Essgewohnheit.ts index 9edab853..547916c4 100644 --- a/api/src/enumMappings/Essgewohnheit.ts +++ b/apps/api/src/types/enums/mappings/Essgewohnheit.ts @@ -1,6 +1,6 @@ import { Essgewohnheit } from '@prisma/client' -import { defineEnumMapping } from './defineEnumMapping' +import { defineEnumMapping } from '../defineEnumMapping.js' export const EssgewohnheitMapping = defineEnumMapping({ OMNIVOR: { human: 'Omnivor' }, diff --git a/api/src/enumMappings/Gender.ts b/apps/api/src/types/enums/mappings/Gender.ts similarity index 79% rename from api/src/enumMappings/Gender.ts rename to apps/api/src/types/enums/mappings/Gender.ts index 741c5171..8c3f12c8 100644 --- a/api/src/enumMappings/Gender.ts +++ b/apps/api/src/types/enums/mappings/Gender.ts @@ -1,6 +1,6 @@ import { Gender } from '@prisma/client' -import { defineEnumMapping } from './defineEnumMapping' +import { defineEnumMapping } from '../defineEnumMapping.js' export const GenderMapping = defineEnumMapping({ MALE: { human: 'Männlich' }, diff --git a/api/src/enumMappings/NahrungsmittelIntoleranz.ts b/apps/api/src/types/enums/mappings/NahrungsmittelIntoleranz.ts similarity index 84% rename from api/src/enumMappings/NahrungsmittelIntoleranz.ts rename to apps/api/src/types/enums/mappings/NahrungsmittelIntoleranz.ts index 12a26d3d..9d703412 100644 --- a/api/src/enumMappings/NahrungsmittelIntoleranz.ts +++ b/apps/api/src/types/enums/mappings/NahrungsmittelIntoleranz.ts @@ -1,6 +1,6 @@ import { NahrungsmittelIntoleranz } from '@prisma/client' -import { defineEnumMapping } from './defineEnumMapping' +import { defineEnumMapping } from '../defineEnumMapping.js' export const NahrungsmittelIntoleranzMapping = defineEnumMapping({ FRUCTOSE: { human: 'Fructose' }, diff --git a/api/src/enumMappings/role.ts b/apps/api/src/types/enums/mappings/Role.ts similarity index 72% rename from api/src/enumMappings/role.ts rename to apps/api/src/types/enums/mappings/Role.ts index 2de4e9d5..3791febf 100644 --- a/api/src/enumMappings/role.ts +++ b/apps/api/src/types/enums/mappings/Role.ts @@ -1,8 +1,9 @@ import { Prisma, Role } from '@prisma/client' -import { defineEnumMapping } from './defineEnumMapping' +import { defineEnumMapping } from '../defineEnumMapping.js' export const roleMapping = defineEnumMapping({ + USER: { human: 'Benutzer' }, GLIEDERUNG_ADMIN: { human: 'Gliederung Admin' }, ADMIN: { human: 'System Admin' }, }) diff --git a/api/src/enumMappings/UnterveranstaltungType.ts b/apps/api/src/types/enums/mappings/UnterveranstaltungType.ts similarity index 81% rename from api/src/enumMappings/UnterveranstaltungType.ts rename to apps/api/src/types/enums/mappings/UnterveranstaltungType.ts index ab5afb38..25a9783c 100644 --- a/api/src/enumMappings/UnterveranstaltungType.ts +++ b/apps/api/src/types/enums/mappings/UnterveranstaltungType.ts @@ -1,6 +1,6 @@ import { UnterveranstaltungType } from '@prisma/client' -import { defineEnumMapping } from './defineEnumMapping' +import { defineEnumMapping } from '../defineEnumMapping.js' export const UnterveranstaltungTypeMapping = defineEnumMapping({ CREW: { human: 'CREW' }, diff --git a/apps/api/src/types/enums/mappings/index.ts b/apps/api/src/types/enums/mappings/index.ts new file mode 100644 index 00000000..c8b0933c --- /dev/null +++ b/apps/api/src/types/enums/mappings/index.ts @@ -0,0 +1,9 @@ +export * from './AccountStatus.js' +export * from './Activity.js' +export * from './AnmeldungStatus.js' +export * from './CustomFields.js' +export * from './Essgewohnheit.js' +export * from './Gender.js' +export * from './NahrungsmittelIntoleranz.js' +export * from './Role.js' +export * from './UnterveranstaltungType.js' diff --git a/apps/api/src/types/services/Anmeldung.ts b/apps/api/src/types/services/Anmeldung.ts new file mode 100644 index 00000000..aa903012 --- /dev/null +++ b/apps/api/src/types/services/Anmeldung.ts @@ -0,0 +1 @@ +export type { AnmeldungProtectedGetSchema } from '../../services/anmeldung/anmeldungProtectedGet.js' diff --git a/apps/api/src/types/services/index.ts b/apps/api/src/types/services/index.ts new file mode 100644 index 00000000..af819dcf --- /dev/null +++ b/apps/api/src/types/services/index.ts @@ -0,0 +1 @@ +export * from './Anmeldung.js' diff --git a/api/src/util/activity.ts b/apps/api/src/util/activity.ts similarity index 68% rename from api/src/util/activity.ts rename to apps/api/src/util/activity.ts index 4f28fd80..ebf827cb 100644 --- a/api/src/util/activity.ts +++ b/apps/api/src/util/activity.ts @@ -1,15 +1,15 @@ import { ActivityType } from '@prisma/client' -import { logger } from '../logger' -import prisma from '../prisma' +import { logger } from '../logger.js' +import prisma from '../prisma.js' interface Opts { type: ActivityType description?: string causerId?: number - metadata?: any + metadata?: unknown subjectType: string - subjectId?: number + subjectId?: number | string } export default async function logActivity(opts: Opts) { @@ -17,10 +17,11 @@ export default async function logActivity(opts: Opts) { await prisma.activity.create({ data: { ...opts, + subjectId: opts.subjectId?.toString(), metadata: opts.metadata ?? {}, }, }) - } catch (error: any) { + } catch (error) { logger.warn('Failed to insert activity record!', error) } } diff --git a/api/src/util/casing.ts b/apps/api/src/util/casing.ts similarity index 80% rename from api/src/util/casing.ts rename to apps/api/src/util/casing.ts index 453c504c..e70191fe 100644 --- a/api/src/util/casing.ts +++ b/apps/api/src/util/casing.ts @@ -1,4 +1,5 @@ export function pascalToCamelCase(str: string) { + if (str.length === 0 || !str[0]) return str return str[0].toLowerCase() + str.slice(1) } diff --git a/api/src/util/files.ts b/apps/api/src/util/files.ts similarity index 80% rename from api/src/util/files.ts rename to apps/api/src/util/files.ts index 9653ac5e..0a9275f0 100644 --- a/api/src/util/files.ts +++ b/apps/api/src/util/files.ts @@ -1,3 +1,4 @@ +import type { PathLike } from 'fs' import { access, constants, lstat, readdir } from 'fs/promises' import path from 'path' @@ -13,9 +14,9 @@ export async function getDirectories(source: string) { return (await isDirectory(filePath)) ? file : null }) ) - return dirs.filter((dir) => dir !== null) as string[] + return dirs.filter((dir) => dir !== null) } -export function checkFileExists(file) { +export function checkFileExists(file: PathLike) { return access(file, constants.F_OK) .then(() => true) .catch(() => false) diff --git a/api/src/util/getGliederungRequireAdmin.ts b/apps/api/src/util/getGliederungRequireAdmin.ts similarity index 94% rename from api/src/util/getGliederungRequireAdmin.ts rename to apps/api/src/util/getGliederungRequireAdmin.ts index 23e73984..239bfe4f 100644 --- a/api/src/util/getGliederungRequireAdmin.ts +++ b/apps/api/src/util/getGliederungRequireAdmin.ts @@ -1,6 +1,6 @@ import { GliederungAccountRole } from '@prisma/client' -import prisma from '../prisma' +import prisma from '../prisma.js' /** * get gliederung where `accountId` is admin * diff --git a/api/src/util/is-production.ts b/apps/api/src/util/is-production.ts similarity index 100% rename from api/src/util/is-production.ts rename to apps/api/src/util/is-production.ts diff --git a/api/src/util/mail.ts b/apps/api/src/util/mail.ts similarity index 50% rename from api/src/util/mail.ts rename to apps/api/src/util/mail.ts index 65aaf7ff..81671ed5 100644 --- a/api/src/util/mail.ts +++ b/apps/api/src/util/mail.ts @@ -1,18 +1,26 @@ -/* eslint-disable no-unused-vars */ +import { readFile } from 'fs/promises' +import { join } from 'path' + import sgMail from '@sendgrid/mail' +import { getProperty } from 'dot-prop' +import Handlebars from 'handlebars' +import mjml2html from 'mjml' -import config from '.././config' +import config from '.././config.js' +import { logger } from '../logger.js' -import logActivity from './activity' +import logActivity from './activity.js' sgMail.setApiKey(config.mail.sendgridApiKey) -type EMailTemplateConfig = { - html: string - template?: undefined +type Variables = Record & { + name: string + gliederung: string + veranstaltung: string + hostname: string } -/** Die EMail wie sie an den Service übergeben wird */ -export type EMailParams = EMailTemplateConfig & { + +type EMailParams = { to: string | string[] bcc?: string | string[] subject: string @@ -23,10 +31,11 @@ export type EMailParams = EMailTemplateConfig & { type: string }[] skipHtmlEncode?: boolean + template: string + variables: Variables } -/** Die EMail wie sie an den Sendgrid Service übergeben wird */ -export type EMail = { +type EMail = { from: string to: string[] bcc?: string[] @@ -40,14 +49,53 @@ export type EMail = { }[] } +async function readTemplate(name: string) { + const abs = `${join(templateDirectory, name)}.mjml` + const buffer = await readFile(abs) + const contents = buffer.toString('utf8') + + return { abs, contents } +} + +Handlebars.registerHelper('year', () => new Date().getFullYear()) +Handlebars.registerHelper('config', (key) => getProperty(config, key)) +Handlebars.registerHelper('url', (path) => `${config.clientUrl}/${path}`) + +const templateDirectory = join(process.cwd(), 'email') +const { contents: layout } = await readTemplate('_layout') +Handlebars.registerPartial('layout', layout) + +/** + * Triggers the email template compilation pipeline consisting of the following: + * + * - Use `ejs` as a templating engine for enabling variable support. + * - Use `mjml` to generate the raw html code to embed in the email. + */ +async function compile(templateName: string, variables: Variables): Promise { + const { abs, contents } = await readTemplate(templateName) + const template = Handlebars.compile(contents, { + strict: true, + }) + const mjml = template(variables) + const result = mjml2html(mjml, { + filePath: abs, + }) + + const { errors, html } = result + + if (errors.length > 0) { + logger.error('Failed compiling mjml email template!', { errors }) + return null + } + + return html +} + export async function sendMail(mailParams: EMailParams) { try { - let html let subject = mailParams.subject const sendWithTemplate = 'no-template' - if (mailParams.html) { - html = mailParams.html - } + // set mail categories for sendgrid const categories = [ process.env.NODE_ENV ?? '', @@ -56,20 +104,30 @@ export async function sendMail(mailParams: EMailParams) { ...mailParams.categories, ] - if (process.env.NODE_ENV != 'production') { - subject += ' [DEV]' - html += `

- Testnachricht, versendet aus folgendem System: ${process.env.NODE_ENV}` + let html = await compile(mailParams.template, { + ...mailParams.variables, + subject, + }) + if (html === null) { + return } + // convert umlaute to html entires - if (!mailParams.skipHtmlEncode) html = encodeHtmlEntries(html) + if (!mailParams.skipHtmlEncode) { + html = encodeHtmlEntries(html) + } + + if (process.env.NODE_ENV != 'production') { + subject += ` [${process.env.NODE_ENV}]` + } + const mailToSend: EMail = { from: 'brahmsee.digital', to: parseMaybeArray(mailParams.to), + attachments: formatAttachments(mailParams.attachments), subject, categories, - html: html, - attachments: formatAttachments(mailParams.attachments), + html, } await logActivity({ @@ -79,27 +137,37 @@ export async function sendMail(mailParams: EMailParams) { metadata: mailParams, }) + const to = Array.isArray(mailToSend.to) ? mailToSend.to.join(', ') : mailToSend.to + const bcc = mailToSend.bcc ? (Array.isArray(mailToSend.bcc) ? mailToSend.bcc.join(', ') : mailToSend.bcc) : '' // send mail if (config.mail.sendMails === 'true' && config.mail.sendgridApiKey) { - // eslint-disable-next-line no-console - console.log(`sending mail (${sendWithTemplate}) to "${mailParams.to}" with subject "${mailParams.subject}"`) + console.log(`sending mail (${sendWithTemplate}) to "${to}" with subject "${mailParams.subject}"`) return await sgMail.sendMultiple(mailToSend) } else { - /* eslint-disable no-console */ console.log('///////////////////////////////////////') console.log('Sending Email') console.log(`from: ${mailToSend.from}`) - console.log(`to: ${mailToSend.to}`) - if (mailToSend.bcc) console.log(`bcc: ${mailToSend.bcc}`) + console.log(`to: ${to}`) + if (mailToSend.bcc) console.log(`bcc: ${bcc}`) console.log(`subject: ${mailToSend.subject}`) console.log(mailToSend.html) console.log('////////////////////////////////////////') - /* eslint-enable no-console */ + return mailToSend } - } catch (error: any) { + } catch (error: unknown) { console.error(error) - if (error.response?.body) console.error(error.response.body) + if (error instanceof Error) { + logger.error('Failed sending email!', { + message: error.message, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any + response: (error as any)?.response?.body, + }) + } else { + logger.error('Failed sending email!', { + message: 'Unknown error', + }) + } } } diff --git a/api/src/util/object.ts b/apps/api/src/util/object.ts similarity index 100% rename from api/src/util/object.ts rename to apps/api/src/util/object.ts diff --git a/api/src/util/prisma-exclude.ts b/apps/api/src/util/prisma-exclude.ts similarity index 100% rename from api/src/util/prisma-exclude.ts rename to apps/api/src/util/prisma-exclude.ts diff --git a/api/src/util/test.ts b/apps/api/src/util/test.ts similarity index 86% rename from api/src/util/test.ts rename to apps/api/src/util/test.ts index fa6a5cd7..285793b4 100644 --- a/api/src/util/test.ts +++ b/apps/api/src/util/test.ts @@ -1,7 +1,7 @@ -import { AccountStatus } from '@prisma/client' +import { AccountStatus, Role } from '@prisma/client' -import { authenticationLogin } from '../authentication' -import prisma from '../prisma' +import { authenticationLogin } from '../authentication.js' +import prisma from '../prisma.js' import { hashPassword } from '@codeanker/authentication' @@ -13,7 +13,7 @@ export async function createMock(runId: string) { email, password: await hashPassword(accountPassword), activatedAt: new Date(), - role: 'ADMIN', + role: Role.ADMIN, person: { create: { firstname: 'Test', diff --git a/apps/api/src/util/zod.ts b/apps/api/src/util/zod.ts new file mode 100644 index 00000000..e0258cdc --- /dev/null +++ b/apps/api/src/util/zod.ts @@ -0,0 +1,20 @@ +import type { ZodError, ZodSchema } from 'zod' + +type ZodSafeResult = [true, O] | [false, ZodError] + +/** + * Runs a zod schema validation and returns the result data as a tuple. + * + * @param schema The zod schema to use for validation. + * @param payload The payload to validate. + * @returns The validation result. + */ +export async function zodSafe(schema: ZodSchema, payload: I): Promise> { + const { success, data, error } = await schema.safeParseAsync(payload) + + if (success) { + return [true, data] + } + + return [false, error] +} diff --git a/api/static/favicon.ico b/apps/api/static/favicon.ico similarity index 100% rename from api/static/favicon.ico rename to apps/api/static/favicon.ico diff --git a/apps/api/static/index.html b/apps/api/static/index.html new file mode 100644 index 00000000..00e99e2f --- /dev/null +++ b/apps/api/static/index.html @@ -0,0 +1,10 @@ + + + + + API + + +

API

+ + diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 00000000..88543912 --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@codeanker/typescript-config/tsconfig-node-tsc", + "compilerOptions": { + "lib": ["ES2022"], + "outDir": "./dist" + }, + "include": ["src", "prisma", "eslint.config.js"] +} diff --git a/docs/.gitignore b/apps/docs/.gitignore similarity index 100% rename from docs/.gitignore rename to apps/docs/.gitignore diff --git a/docs/.vitepress/config.mts b/apps/docs/.vitepress/config.mts similarity index 64% rename from docs/.vitepress/config.mts rename to apps/docs/.vitepress/config.mts index e70b2dbc..ab8b5ea2 100644 --- a/docs/.vitepress/config.mts +++ b/apps/docs/.vitepress/config.mts @@ -2,14 +2,14 @@ import { defineConfig } from 'vitepress' // https://vitepress.dev/reference/site-config export default defineConfig({ - title: "brahmsee.digital", - description: "Documentation for brahmsee.digital", + title: 'brahmsee.digital', + description: 'Documentation for brahmsee.digital', srcDir: './src', themeConfig: { // https://vitepress.dev/reference/default-theme-config nav: [ { text: 'Home', link: '/' }, - { text: 'Examples', link: '/markdown-examples' } + { text: 'Examples', link: '/markdown-examples' }, ], sidebar: [ @@ -17,13 +17,11 @@ export default defineConfig({ text: 'Examples', items: [ { text: 'Markdown Examples', link: '/markdown-examples' }, - { text: 'Runtime API Examples', link: '/api-examples' } - ] - } + { text: 'Runtime API Examples', link: '/api-examples' }, + ], + }, ], - socialLinks: [ - { icon: 'github', link: 'https://github.com/codeanker/brahmsee.digital' } - ] - } + socialLinks: [{ icon: 'github', link: 'https://github.com/codeanker/brahmsee.digital' }], + }, }) diff --git a/docs/package.json b/apps/docs/package.json similarity index 100% rename from docs/package.json rename to apps/docs/package.json diff --git a/docs/src/anmelde-entity.md b/apps/docs/src/anmelde-entity.md similarity index 97% rename from docs/src/anmelde-entity.md rename to apps/docs/src/anmelde-entity.md index 752db847..abf3157d 100644 --- a/docs/src/anmelde-entity.md +++ b/apps/docs/src/anmelde-entity.md @@ -1,7 +1,9 @@ # Felder die zur Anmeldung / Registrierung benötigt werden # CREW + Stammdaten + - Identifikation (w,m,d) - Vorname - Nachname @@ -10,16 +12,17 @@ Stammdaten - Adresse Kontaktdaten + - Email - Handynummer Notfallkontakte Verpflegung + - Omnivor - Vegetarisch -- Vegan -==== +- # Vegan - kein Schweinefleisch - kein Gluten - kein Lactose @@ -28,6 +31,7 @@ Verpflegung - Freitext Feld Anwesenheit + - Tag - Vormittag - Mittag @@ -35,19 +39,23 @@ Anwesenheit - Übernachtung DLRG Mitglied + - Ja - Gliederung - Nein Qualifikation + - Einweisung Fahrzeuge (aus ENUM, können sich ändern) - T5, T6, ... - siehe Datei Qualifikation Metadaten + - Konfektionsgröße Arbeitsbereich + - Programm (KiGA) - ORGA - SAN @@ -58,7 +66,9 @@ T-Shirt Bestellung Bemerkung # Teilnehmende + Stammdaten + - Identifikation (w,m,d) - Vorname - Nachname @@ -67,21 +77,24 @@ Stammdaten - Adresse Kontaktdaten + - Email - Handynummer Notfallkontakte Rolle + - Delegationsleitung - Betreuer:in - Teilnehmer:in T-Shirt Bestellung - # Gliederungsadministratoren + Stammdaten + - Identifikation (w,m,d) - Vorname - Nachname @@ -90,12 +103,14 @@ Stammdaten - Adresse Kontaktdaten + - Email - Handynummer Notfallkontakte Rolle + - Delegationsleitung - Betreuer:in - Teilnehmer:in diff --git a/docs/src/api-examples.md b/apps/docs/src/api-examples.md similarity index 99% rename from docs/src/api-examples.md rename to apps/docs/src/api-examples.md index 6bd8bb5c..691df9cc 100644 --- a/docs/src/api-examples.md +++ b/apps/docs/src/api-examples.md @@ -18,12 +18,15 @@ const { theme, page, frontmatter } = useData() ## Results ### Theme Data +
{{ theme }}
### Page Data +
{{ page }}
### Page Frontmatter +
{{ frontmatter }}
``` @@ -36,12 +39,15 @@ const { site, theme, page, frontmatter } = useData() ## Results ### Theme Data +
{{ theme }}
### Page Data +
{{ page }}
### Page Frontmatter +
{{ frontmatter }}
## More diff --git a/apps/docs/src/app-structure.md b/apps/docs/src/app-structure.md new file mode 100644 index 00000000..49ef1c66 --- /dev/null +++ b/apps/docs/src/app-structure.md @@ -0,0 +1,36 @@ +# Struktur für Routen + +- 🟢Veranstaltung (im Scope) + + - 🟢Dashboard / Home Übersicht... `veranstaltung/:id/dashboard` + - 🟢Auswertung `veranstaltung/:id/auswertung` + - 🟢Verpflegung `veranstaltung/:id/auswertung/verpflegung` + - 🟢Anmeldungen / Kosten, Personen ... `veranstaltung/:id/auswertung/anmeldungen` + - 🟢Anmeldungen `veranstaltung/:id/anmeldungen` + - 🟢Crew `veranstaltung/:id/anmeldungen/crew` + - 🟢Gliederung `veranstaltung/:id/anmeldungen/gliederungen` + - 🟢Teilnehmende `veranstaltung/:id/anmeldungen/gliederungen/:gliederungId/teilnehmende` + - 🟢Programm `veranstaltung/:id/programm` + - 🟢List `veranstaltung/:id/programm` + - 🟢Detail `veranstaltung/:id/programm/:programmId` + - 🟢Create `veranstaltung/:id/programm/erstellen` + - 🟢Lageplan `veranstaltung/:id/lageplan` + +- 🟢Einstellungen / Verwaltung `verwaltung` + - 🟢Gliederungsaccount Anfrage `verwaltung/gliederungen/anfragen` + - 🟢Gliederungen `verwaltung/gliederungen` + - 🟢List `verwaltung/gliederungen` + - 🟢Detail `verwaltung/gliederungen/:id` + - 🟢Create `verwaltung/gliederungen/erstellen` + - 🟢Veranstaltungen `verwaltung/veranstaltung` + - 🟢List `verwaltung/veranstaltung` + - 🟢Detail `verwaltung/veranstaltung/:id` + - 🟢Create `verwaltung/veranstaltung/erstellen` + - 🟢Benutzer `verwaltung/benutzer` + - 🟢List `verwaltung/benutzer` + - 🟢Detail `verwaltung/benutzer/:id` + - 🟢Create `verwaltung/benutzer/erstellen` + - 🟢 Orte (Häuser, Zeltplätze, Räume) `verwaltung/orte` + - 🟢List `verwaltung/orte` + - 🟢Detail `verwaltung/orte/:id` + - 🟢Create `verwaltung/orte/erstellen` diff --git a/docs/src/bugs-and-workarounds.md b/apps/docs/src/bugs-and-workarounds.md similarity index 100% rename from docs/src/bugs-and-workarounds.md rename to apps/docs/src/bugs-and-workarounds.md diff --git a/docs/src/index.md b/apps/docs/src/index.md similarity index 91% rename from docs/src/index.md rename to apps/docs/src/index.md index 8ec35ece..b85e3065 100644 --- a/docs/src/index.md +++ b/apps/docs/src/index.md @@ -3,8 +3,8 @@ layout: home hero: - name: "brahmsee.digital" - text: "documentation" + name: 'brahmsee.digital' + text: 'documentation' tagline: Structure and Hints actions: - theme: brand diff --git a/docs/src/markdown-examples.md b/apps/docs/src/markdown-examples.md similarity index 100% rename from docs/src/markdown-examples.md rename to apps/docs/src/markdown-examples.md diff --git a/docs/src/qualifikationen.md b/apps/docs/src/qualifikationen.md similarity index 100% rename from docs/src/qualifikationen.md rename to apps/docs/src/qualifikationen.md diff --git a/frontend/.dockerignore b/apps/frontend/.dockerignore similarity index 100% rename from frontend/.dockerignore rename to apps/frontend/.dockerignore diff --git a/frontend/.gitignore b/apps/frontend/.gitignore similarity index 100% rename from frontend/.gitignore rename to apps/frontend/.gitignore diff --git a/frontend/.vscode/extensions.json b/apps/frontend/.vscode/extensions.json similarity index 100% rename from frontend/.vscode/extensions.json rename to apps/frontend/.vscode/extensions.json diff --git a/frontend/README.md b/apps/frontend/README.md similarity index 100% rename from frontend/README.md rename to apps/frontend/README.md diff --git a/apps/frontend/components.d.ts b/apps/frontend/components.d.ts new file mode 100644 index 00000000..a4125160 --- /dev/null +++ b/apps/frontend/components.d.ts @@ -0,0 +1,101 @@ +/* eslint-disable */ +// @ts-nocheck +// Generated by unplugin-vue-components +// Read more: https://github.com/vuejs/core/pull/3399 +export {} + +/* prettier-ignore */ +declare module 'vue' { + export interface GlobalComponents { + Address: typeof import('./src/components/forms/anmeldung/Address.vue')['default'] + AnmeldungenTable: typeof import('./src/components/AnmeldungenTable.vue')['default'] + AnmeldungStatusSelect: typeof import('./src/components/AnmeldungStatusSelect.vue')['default'] + AnmeldungTshirtSelect: typeof import('./src/components/AnmeldungTshirtSelect.vue')['default'] + AppLink: typeof import('./src/components/AppLink.vue')['default'] + Badge: typeof import('./src/components/UIComponents/Badge.vue')['default'] + BasicAddressPicker: typeof import('./src/components/BasicInputs/BasicAddressPicker.vue')['default'] + BasicCheckbox: typeof import('./src/components/BasicInputs/BasicCheckbox.vue')['default'] + BasicDatepicker: typeof import('./src/components/BasicInputs/BasicDatepicker.vue')['default'] + BasicDropdown: typeof import('./src/components/BasicInputs/BasicDropdown.vue')['default'] + BasicEditor: typeof import('./src/components/BasicInputs/BasicEditor.vue')['default'] + BasicEditorMenu: typeof import('./src/components/BasicInputs/components/BasicEditorMenu.vue')['default'] + BasicEditorMenuItem: typeof import('./src/components/BasicInputs/components/BasicEditorMenuItem.vue')['default'] + BasicFormGroup: typeof import('./src/components/BasicInputs/components/BasicFormGroup.vue')['default'] + BasicGrid: typeof import('./src/components/BasicGrid.vue')['default'] + BasicInput: typeof import('./src/components/BasicInputs/BasicInput.vue')['default'] + BasicInputNumber: typeof import('./src/components/BasicInputs/BasicInputNumber.vue')['default'] + BasicInputPhoneNumber: typeof import('./src/components/BasicInputs/BasicInputPhoneNumber.vue')['default'] + BasicPassword: typeof import('./src/components/BasicInputs/BasicPassword.vue')['default'] + BasicRadio: typeof import('./src/components/BasicInputs/BasicRadio.vue')['default'] + BasicSelect: typeof import('./src/components/BasicInputs/BasicSelect.vue')['default'] + BasicSwitch: typeof import('./src/components/BasicInputs/BasicSwitch.vue')['default'] + BasicTextArea: typeof import('./src/components/BasicInputs/BasicTextArea.vue')['default'] + BasicTypeahead: typeof import('./src/components/BasicInputs/BasicTypeahead.vue')['default'] + BasicValidationFeedback: typeof import('./src/components/BasicInputs/components/BasicValidationFeedback.vue')['default'] + Beispiel: typeof import('./src/components/Beispiel.vue')['default'] + Breadcrumbs: typeof import('./src/components/UIComponents/Breadcrumbs.vue')['default'] + Button: typeof import('./src/components/UIComponents/Button.vue')['default'] + CircularProgress: typeof import('./src/components/UIComponents/CircularProgress.vue')['default'] + CustomField: typeof import('./src/components/CustomFields/CustomField.vue')['default'] + CustomFieldsForm: typeof import('./src/components/CustomFields/CustomFieldsForm.vue')['default'] + CustomFieldsFormCreate: typeof import('./src/components/CustomFields/CustomFieldsFormCreate.vue')['default'] + CustomFieldsFormEdit: typeof import('./src/components/CustomFields/CustomFieldsFormEdit.vue')['default'] + CustomFieldsFormGeneral: typeof import('./src/components/CustomFields/CustomFieldsFormGeneral.vue')['default'] + CustomFieldsFormUser: typeof import('./src/components/CustomFields/CustomFieldsFormUser.vue')['default'] + CustomFieldsSettingsForm: typeof import('./src/components/CustomFields/CustomFieldsSettingsForm.vue')['default'] + CustomFieldsTable: typeof import('./src/components/CustomFields/CustomFieldsTable.vue')['default'] + DarkModeSwitch: typeof import('./src/components/DarkModeSwitch.vue')['default'] + DataGridDoubleLineCell: typeof import('./src/components/DataGridDoubleLineCell.vue')['default'] + DataGridFilterInput: typeof import('./src/components/DataGridFilterInput.vue')['default'] + DataGridHeader: typeof import('./src/components/DataGrid/DataGridHeader.vue')['default'] + DataGridHeaderCell: typeof import('./src/components/DataGrid/DataGridHeaderCell.vue')['default'] + DataGridPaginatedList: typeof import('./src/components/DataGrid/DataGridPaginatedList.vue')['default'] + DataGridPagination: typeof import('./src/components/DataGrid/DataGridPagination.vue')['default'] + DataGridRow: typeof import('./src/components/DataGrid/DataGridRow.vue')['default'] + DataGridRowCell: typeof import('./src/components/DataGrid/DataGridRowCell.vue')['default'] + DataGridRowPending: typeof import('./src/components/DataGrid/DataGridRowPending.vue')['default'] + DataGridRowSubheader: typeof import('./src/components/DataGrid/DataGridRowSubheader.vue')['default'] + DataGridVirtualList: typeof import('./src/components/DataGrid/DataGridVirtualList.vue')['default'] + DownloadLink: typeof import('./src/components/DownloadLink.vue')['default'] + Drawer: typeof import('./src/components/LayoutComponents/Drawer.vue')['default'] + FilesExport: typeof import('./src/components/FilesExport.vue')['default'] + Footer: typeof import('./src/components/LayoutComponents/Footer.vue')['default'] + FormAccountGeneral: typeof import('./src/components/forms/account/FormAccountGeneral.vue')['default'] + FormAnmeldungGeneral: typeof import('./src/components/forms/anmeldung/FormAnmeldungGeneral.vue')['default'] + FormContactGeneral: typeof import('./src/components/forms/person/FormContactGeneral.vue')['default'] + FormEssgewohnheitGeneral: typeof import('./src/components/forms/person/FormEssgewohnheitGeneral.vue')['default'] + FormGliederungGeneral: typeof import('./src/components/forms/gliederung/FormGliederungGeneral.vue')['default'] + FormNotfallkontakteGeneral: typeof import('./src/components/forms/person/FormNotfallkontakteGeneral.vue')['default'] + FormOrtGeneral: typeof import('./src/components/forms/ort/FormOrtGeneral.vue')['default'] + FormPersonGeneral: typeof import('./src/components/forms/person/FormPersonGeneral.vue')['default'] + FormTShirtBestellungGeneral: typeof import('./src/components/forms/person/FormTShirtBestellungGeneral.vue')['default'] + FormUnterveranstaltungGeneral: typeof import('./src/components/forms/unterveranstaltung/FormUnterveranstaltungGeneral.vue')['default'] + FormVeranstaltungGeneral: typeof import('./src/components/forms/veranstaltung/FormVeranstaltungGeneral.vue')['default'] + GenericDataGrid: typeof import('./src/components/GenericDataGrid.vue')['default'] + GliederungLogo: typeof import('./src/components/UIComponents/GliederungLogo.vue')['default'] + GlobalSearch: typeof import('./src/components/GlobalSearch.vue')['default'] + InfoList: typeof import('./src/components/UIComponents/InfoList.vue')['default'] + KeyValue: typeof import('./src/components/UIComponents/KeyValue.vue')['default'] + KontaktItem: typeof import('./src/components/UIComponents/KontaktItem.vue')['default'] + Loading: typeof import('./src/components/UIComponents/Loading.vue')['default'] + MobileMenuButton: typeof import('./src/components/UIComponents/MobileMenuButton.vue')['default'] + Notifications: typeof import('./src/components/LayoutComponents/Notifications.vue')['default'] + PasswordStrength: typeof import('./src/components/PasswordStrength.vue')['default'] + PersonPhotoUpload: typeof import('./src/components/forms/person/PersonPhotoUpload.vue')['default'] + PublicFooter: typeof import('./src/components/LayoutComponents/PublicFooter.vue')['default'] + PublicHeader: typeof import('./src/components/LayoutComponents/PublicHeader.vue')['default'] + PublicHeaderBg: typeof import('./src/components/LayoutComponents/PublicHeaderBg.vue')['default'] + RouterLink: typeof import('vue-router')['RouterLink'] + RouterView: typeof import('vue-router')['RouterView'] + Sidebar: typeof import('./src/components/LayoutComponents/Sidebar/Sidebar.vue')['default'] + SidebarItems: typeof import('./src/components/LayoutComponents/Sidebar/SidebarItems.vue')['default'] + SidebarVeranstaltungSwitcher: typeof import('./src/components/LayoutComponents/Sidebar/SidebarVeranstaltungSwitcher.vue')['default'] + Stammdaten: typeof import('./src/components/forms/anmeldung/Stammdaten.vue')['default'] + Tab: typeof import('./src/components/UIComponents/components/Tab.vue')['default'] + Tabs: typeof import('./src/components/UIComponents/Tabs.vue')['default'] + Typeahead: typeof import('./src/components/BasicInputs/components/Typeahead.vue')['default'] + UnterveranstaltungenTable: typeof import('./src/components/UnterveranstaltungenTable.vue')['default'] + UserLogo: typeof import('./src/components/UIComponents/UserLogo.vue')['default'] + VeranstaltungCard: typeof import('./src/components/UIComponents/VeranstaltungCard.vue')['default'] + } +} diff --git a/apps/frontend/eslint.config.js b/apps/frontend/eslint.config.js new file mode 100644 index 00000000..9e276c2c --- /dev/null +++ b/apps/frontend/eslint.config.js @@ -0,0 +1,3 @@ +import eslintConfig from '@codeanker/eslint-config/eslint-vue-config' + +export default eslintConfig diff --git a/apps/frontend/index.html b/apps/frontend/index.html new file mode 100644 index 00000000..ecad54d7 --- /dev/null +++ b/apps/frontend/index.html @@ -0,0 +1,28 @@ + + + + + + + brahmsee.digital + + + +
+ + + diff --git a/frontend/nginx.conf b/apps/frontend/nginx.conf similarity index 100% rename from frontend/nginx.conf rename to apps/frontend/nginx.conf diff --git a/apps/frontend/package.json b/apps/frontend/package.json new file mode 100644 index 00000000..08631cdf --- /dev/null +++ b/apps/frontend/package.json @@ -0,0 +1,64 @@ +{ + "name": "@codeanker/frontend", + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --host", + "build": "vite build", + "serve": "vite preview", + "typecheck": "vue-tsc --noEmit", + "lint": "eslint .", + "lint:fix": "eslint . --fix" + }, + "dependencies": { + "@azure/storage-blob": "^12.17.0", + "@codeanker/api": "workspace:*", + "@codeanker/cookies": "workspace:*", + "@codeanker/datagrid": "file:../../vendor/codeanker-datagrid-2.7.1-trimmed.tgz", + "@codeanker/helpers": "workspace:*", + "@codeanker/interfaces": "workspace:*", + "@codeanker/validation": "workspace:*", + "@faker-js/faker": "^9.4.0", + "@headlessui/vue": "^1.7.16", + "@heroicons/vue": "^2.0.18", + "@tailwindcss/forms": "^0.5.7", + "@tailwindcss/typography": "^0.5.16", + "@tiptap/extension-document": "catalog:", + "@tiptap/extension-highlight": "catalog:", + "@tiptap/extension-link": "catalog:", + "@tiptap/extension-paragraph": "catalog:", + "@tiptap/extension-text": "catalog:", + "@tiptap/pm": "catalog:", + "@tiptap/starter-kit": "catalog:", + "@tiptap/vue-3": "catalog:", + "@trpc/client": "catalog:", + "@vuepic/vue-datepicker": "^4.5.1", + "@vueuse/core": "^12.5.0", + "@vueuse/router": "^12.3.0", + "clsx": "^2.1.1", + "http2-proxy": "^5.0.53", + "intl-tel-input": "^24.4.0", + "radix-vue": "^1.9.5", + "remixicon": "^3.5.0", + "simple-syntax-highlighter": "^3.1.1", + "superjson": "catalog:", + "tailwind-merge": "^2.6.0", + "vue": "catalog:", + "vue-router": "^4.2.5" + }, + "devDependencies": { + "@codeanker/eslint-config": "workspace:*", + "@codeanker/typescript-config": "workspace:*", + "@types/node": "catalog:", + "@vitejs/plugin-basic-ssl": "^1.2.0", + "@vitejs/plugin-vue": "^5.2.1", + "autoprefixer": "^10.4.16", + "eslint": "catalog:", + "postcss": "^8.4.31", + "sass": "^1.69.5", + "tailwindcss": "^3.3.5", + "typescript": "catalog:", + "vite": "^6.1.0", + "vue-tsc": "catalog:" + } +} diff --git a/frontend/pluginHttp2Proxy.ts b/apps/frontend/pluginHttp2Proxy.ts similarity index 100% rename from frontend/pluginHttp2Proxy.ts rename to apps/frontend/pluginHttp2Proxy.ts diff --git a/frontend/postcss.config.js b/apps/frontend/postcss.config.js similarity index 76% rename from frontend/postcss.config.js rename to apps/frontend/postcss.config.js index 33ad091d..2e7af2b7 100644 --- a/frontend/postcss.config.js +++ b/apps/frontend/postcss.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { plugins: { tailwindcss: {}, autoprefixer: {}, diff --git a/frontend/public/codeanker.png b/apps/frontend/public/codeanker.png similarity index 100% rename from frontend/public/codeanker.png rename to apps/frontend/public/codeanker.png diff --git a/frontend/public/favicon.ico b/apps/frontend/public/favicon.ico similarity index 100% rename from frontend/public/favicon.ico rename to apps/frontend/public/favicon.ico diff --git a/frontend/src/assets/images/gliederung_sh.png b/apps/frontend/public/img/gliederung_sh.png similarity index 100% rename from frontend/src/assets/images/gliederung_sh.png rename to apps/frontend/public/img/gliederung_sh.png diff --git a/apps/frontend/public/opengraph.gif b/apps/frontend/public/opengraph.gif new file mode 100644 index 00000000..39ab9ad7 Binary files /dev/null and b/apps/frontend/public/opengraph.gif differ diff --git a/frontend/src/App.vue b/apps/frontend/src/App.vue similarity index 100% rename from frontend/src/App.vue rename to apps/frontend/src/App.vue diff --git a/frontend/src/api.ts b/apps/frontend/src/api.ts similarity index 78% rename from frontend/src/api.ts rename to apps/frontend/src/api.ts index 3d3a2538..bf35e5a6 100644 --- a/frontend/src/api.ts +++ b/apps/frontend/src/api.ts @@ -1,4 +1,4 @@ -import { createTRPCProxyClient, httpBatchLink } from '@trpc/client' +import { createTRPCClient, httpBatchLink } from '@trpc/client' import superjson from 'superjson' import type { AppRouter } from '@codeanker/api' @@ -15,10 +15,10 @@ BigInt.prototype.toJSON = function (): string { return this.toString() } -export const apiClient = createTRPCProxyClient({ - transformer: superjson, +export const apiClient = createTRPCClient({ links: [ httpBatchLink({ + transformer: superjson, url: (host !== '/' ? host : '') + '/api/trpc', async headers() { const jwt = localStorage.getItem('jwt') diff --git a/frontend/src/assets/animations.scss b/apps/frontend/src/assets/animations.scss similarity index 79% rename from frontend/src/assets/animations.scss rename to apps/frontend/src/assets/animations.scss index 824ca29c..bcb7cd41 100755 --- a/frontend/src/assets/animations.scss +++ b/apps/frontend/src/assets/animations.scss @@ -3,11 +3,13 @@ } @keyframes shake { - 8%, 41% { + 8%, + 41% { -webkit-transform: translateX(-10px); } - 25%, 58% { + 25%, + 58% { -webkit-transform: translateX(10px); } @@ -19,12 +21,14 @@ -webkit-transform: translateX(5px); } - 0%, 100% { + 0%, + 100% { -webkit-transform: translateX(0); } } -.fade-enter-active, .fade-leave-active { +.fade-enter-active, +.fade-leave-active { transition: all 0.3s ease-in-out; } @@ -33,7 +37,8 @@ opacity: 0; } -.slide-fade-enter-active, .slide-fade-leave-active { +.slide-fade-enter-active, +.slide-fade-leave-active { transition: all 0.3s ease-in-out; } diff --git a/frontend/src/assets/baseFormStyle.scss b/apps/frontend/src/assets/baseFormStyle.scss similarity index 78% rename from frontend/src/assets/baseFormStyle.scss rename to apps/frontend/src/assets/baseFormStyle.scss index 3aaff61e..fa620c77 100644 --- a/frontend/src/assets/baseFormStyle.scss +++ b/apps/frontend/src/assets/baseFormStyle.scss @@ -1,11 +1,25 @@ - -[type='text'], [type='email'], [type='url'], [type='password'], [type='number'], [type='date'], [type='datetime-local'], [type='month'], [type='search'], [type='tel'], [type='time'], [type='week'], [multiple], textarea, select, .input-style { +[type='text'], +[type='email'], +[type='url'], +[type='password'], +[type='number'], +[type='date'], +[type='datetime-local'], +[type='month'], +[type='search'], +[type='tel'], +[type='time'], +[type='week'], +[multiple], +textarea, +select, +.input-style { @apply block w-full py-2 px-4 text-gray-900 dark:text-gray-100 bg-gray-100 dark:bg-dark-secondary border-gray-100 dark:border-gray-700 border rounded-lg transition focus:border-primary-700 ring-0 focus:ring-0 leading-5 focus:outline-none dark:placeholder:text-gray-500; - &:disabled, &.disabled { - @apply bg-gray-50 dark:bg-gray-700 text-gray-50 cursor-not-allowed; + &:disabled, + &.disabled { + @apply bg-gray-50 dark:bg-gray-700 text-gray-400 cursor-not-allowed; } - } [type='checkbox'] { @@ -40,31 +54,30 @@ @apply w-4 h-4 bg-white border-gray-400 rounded-full transition text-primary-700 checked:bg-primary-700 checked:border-primary-700 focus:ring-primary-700; } -.btn-primary{ +.btn-primary { @apply border bg-primary-700 border-primary-700 active:bg-primary-800 text-white rounded-lg; } -.btn-secondary{ +.btn-secondary { @apply border bg-gray-100 border-gray-100 active:bg-gray-300 text-gray-900; } -.btn-link{ +.btn-link { @apply active:bg-gray-100 border border-transparent; } - //* BasicEditor */ -mark{ - &.highlight{ +mark { + &.highlight { @apply bg-primary-100 p-0.5 rounded-sm text-primary-800; } } -.prose{ +.prose { --tw-prose-counters: #000 !important; --tw-prose-links: rgb(22 163 74); } -hr{ +hr { @apply border-t border-gray-200 dark:border-gray-700 my-4; } diff --git a/frontend/src/assets/datepicker.scss b/apps/frontend/src/assets/datepicker.scss similarity index 73% rename from frontend/src/assets/datepicker.scss rename to apps/frontend/src/assets/datepicker.scss index 629b851e..359c3065 100644 --- a/frontend/src/assets/datepicker.scss +++ b/apps/frontend/src/assets/datepicker.scss @@ -1,44 +1,45 @@ -.dp__main{ +.dp__main { @apply relative w-full; - .dp__input_wrap{ - input{ + .dp__input_wrap { + input { @apply cursor-pointer; } @apply relative w-full; - .dp__icon.dp__input_icon{ + .dp__icon.dp__input_icon { @apply cursor-pointer absolute top-1/2 right-0 -translate-y-1/2 text-gray-500 w-4 h-4 mr-3 inline-block text-base; } - .dp__icon.dp__clear_icon{ + .dp__icon.dp__clear_icon { @apply cursor-pointer absolute top-1/2 right-6 -translate-y-1/2 text-gray-500 w-4 h-4 mr-3 inline-block text-base; } } - .dp__menu_index{ + .dp__menu_index { @apply z-dropdown; } // Ausgeklapptes Menü - .dp__menu{ + .dp__menu { @apply shadow rounded-lg bg-white absolute min-w-[260px] z-dropdown; - .dp__menu_content_wrapper{ + .dp__menu_content_wrapper { @apply flex; } - .dp__flex_display{ + .dp__flex_display { @apply flex w-full; } - .dp__preset_ranges{ + .dp__preset_ranges { @apply border-r p-4 px-3; - .dp__preset_range{ + .dp__preset_range { @apply hover:bg-gray-100 rounded-lg flex items-center justify-center cursor-pointer py-2 px-3 whitespace-nowrap; } } - .dp__instance_calendar{ + .dp__instance_calendar { @apply w-full relative pt-0 px-0; } - .dp__instance_calendar:not(:has(.dp__instance_calendar)){ + .dp__instance_calendar:not(:has(.dp__instance_calendar)) { @apply w-full relative pt-4 px-4; } - .dp__overlay{ + .dp__overlay { @apply absolute w-full h-full bg-white rounded-lg transition-opacity top-0 left-0 z-dropdown px-4; - .dp__time_picker_overlay_container, .dp__overlay_container{ + .dp__time_picker_overlay_container, + .dp__overlay_container { @apply h-full flex flex-col overflow-y-auto pt-4; &::-webkit-scrollbar { @apply w-1 bg-white; @@ -46,21 +47,21 @@ &::-webkit-scrollbar-thumb { @apply rounded-lg bg-gray-300; } - .dp__overlay_row{ + .dp__overlay_row { @apply max-w-full w-full flex items-center justify-center flex-1 flex-wrap; - .dp__time_input{ + .dp__time_input { @apply flex justify-center max-w-full w-full items-center; - .dp__time_col.dp__time_col_reg{ + .dp__time_col.dp__time_col_reg { @apply text-3xl flex flex-col items-center justify-center px-4; - .dp__inc_dec_button{ + .dp__inc_dec_button { @apply w-8 hover:bg-gray-100 cursor-pointer rounded-lg p-2; } - .dp__time_display{ + .dp__time_display { @apply hover:bg-gray-100 cursor-pointer rounded-lg p-2; } } } - .dp__overlay_col{ + .dp__overlay_col { @apply w-1/3 text-center; .dp__overlay_cell { @apply py-2 hover:bg-gray-100 cursor-pointer rounded-lg; @@ -70,38 +71,39 @@ } } // Obere Leiste mit Jahren und Buttons - .dp__month_year_row, .dp__month_picker_header{ + .dp__month_year_row, + .dp__month_picker_header { @apply flex items-center w-full mb-4; //Navigation der Monate - .dp__month_year_col_nav{ - @apply order-2; - .dp__inner_nav{ + .dp__month_year_col_nav { + @apply order-2; + .dp__inner_nav { @apply flex items-center justify-center cursor-pointer rounded-md transition-all hover:bg-gray-100 p-2; - .dp__icon{ + .dp__icon { @apply h-6 text-gray-400; } } } - .dp__month_picker_header{ + .dp__month_picker_header { @apply justify-center; } // Monat und Jahr - .dp__month_year_wrap{ + .dp__month_year_wrap { @apply flex w-full order-1; } - .dp__month_year_select{ + .dp__month_year_select { @apply text-lg px-0 hover:px-3 py-1 transition-all hover:bg-gray-100 rounded-md; } - .dp__month_year_select:nth-of-type(1){ + .dp__month_year_select:nth-of-type(1) { @apply font-bold text-gray-900 mr-2; } - .dp__month_year_select:nth-of-type(2){ + .dp__month_year_select:nth-of-type(2) { @apply text-gray-400; } // Monat / Jahr Select - .dp__overlay{ + .dp__overlay { @apply absolute w-full h-full bg-white rounded-lg transition-opacity top-0 left-0 z-dropdown px-4; - .dp__overlay_container{ + .dp__overlay_container { @apply flex flex-col overflow-y-auto pt-4; &::-webkit-scrollbar { @apply w-1 bg-white; @@ -109,14 +111,14 @@ &::-webkit-scrollbar-thumb { @apply rounded-lg bg-gray-300; } - .dp__overlay_row{ + .dp__overlay_row { @apply grid grid-cols-3 max-w-full w-full items-center; - .dp__overlay_col{ + .dp__overlay_col { @apply w-full mb-2; - .dp__overlay_cell{ + .dp__overlay_cell { @apply cursor-pointer rounded-md text-center hover:bg-gray-100 transition-colors py-2; } - .dp__overlay_cell_active{ + .dp__overlay_cell_active { @apply cursor-pointer rounded-md text-center bg-primary-500 text-white py-2; } } @@ -125,72 +127,72 @@ } } // Kalender - .dp__calendar{ + .dp__calendar { @apply w-full mb-2; - &>div{ + & > div { @apply w-full; } - .dp__calendar_wrap{ + .dp__calendar_wrap { @apply w-full; // Wochentage - .dp__calendar_header{ + .dp__calendar_header { @apply flex relative whitespace-nowrap text-xs font-bold w-full; - .dp__calendar_header_item{ + .dp__calendar_header_item { @apply w-full flex items-center justify-center rounded-full aspect-square text-gray-700 p-2; } } - .dp__calendar_header_separator{ - + .dp__calendar_header_separator { } // Tage - .dp__calendar{ + .dp__calendar { @apply relative; - .dp__calendar_row{ + .dp__calendar_row { @apply grid grid-cols-7 relative whitespace-nowrap; - .dp__calendar_item{ + .dp__calendar_item { @apply w-full flex items-center justify-center aspect-square text-base; - .dp__cell_inner{ + .dp__cell_inner { @apply text-gray-900 relative w-full h-full flex items-center justify-center hover:bg-gray-100 transition-colors cursor-pointer rounded-lg; - &.dp__cell_offset{ + &.dp__cell_offset { @apply text-gray-400; } - &.dp__today{ + &.dp__today { @apply bg-primary-100 rounded-lg; } - &.dp__date_hover_start{ + &.dp__date_hover_start { @apply rounded-r-none; } - &.dp__date_hover_end{ + &.dp__date_hover_end { @apply rounded-l-none; } - &.dp__cell_auto_range{ + &.dp__cell_auto_range { @apply border-gray-500 border-dashed rounded-none border-y; } - &.dp__cell_auto_range_start{ + &.dp__cell_auto_range_start { @apply border-gray-500 border-dashed border-y border-l rounded-r-none; } - &.dp__cell_auto_range_end{ + &.dp__cell_auto_range_end { @apply border-gray-500 border-dashed border-y border-r rounded-l-none; } - &.dp__range_between, &.dp__range_between_week{ + &.dp__range_between, + &.dp__range_between_week { @apply bg-gray-100 rounded-none; } - &.dp__range_start{ + &.dp__range_start { @apply bg-primary-500 text-white rounded-r-none; } - &.dp__range_end{ + &.dp__range_end { @apply bg-primary-500 text-white rounded-l-none; } - &.dp__active_date{ + &.dp__active_date { @apply bg-primary-500 text-white; } - &.dp__cell_disabled{ + &.dp__cell_disabled { @apply text-gray-400 cursor-not-allowed; } - .dp__marker_dot{ + .dp__marker_dot { @apply w-1.5 h-1.5 rounded-full bottom-0 absolute left-1/2 -translate-x-1/2 bg-danger-400; } - .dp__marker_line{ + .dp__marker_line { @apply w-full h-1.5 rounded-full bottom-0 absolute left-1/2 -translate-x-1/2 bg-danger-400; } .dp__marker_tooltip { @@ -212,24 +214,24 @@ } } // Zeit Button - .dp__button{ + .dp__button { @apply hover:bg-gray-100 rounded-lg flex items-center justify-center p-2; - .dp__icon{ + .dp__icon { @apply h-6 fill-gray-400; } } // select und cancel - .dp__action_row{ + .dp__action_row { @apply pb-4 px-4; - .dp__selection_preview{ + .dp__selection_preview { @apply font-bold text-center; } - .dp__action_buttons{ + .dp__action_buttons { @apply flex justify-end; - .dp__action.dp__cancel{ + .dp__action.dp__cancel { @apply px-2 py-1 cursor-pointer transition-colors hover:bg-gray-100 rounded-md text-gray-500; } - .dp__action.dp__select{ + .dp__action.dp__select { @apply px-2 py-1 cursor-pointer transition-colors hover:bg-gray-100 rounded-md text-primary-500; } } diff --git a/apps/frontend/src/assets/fonts.scss b/apps/frontend/src/assets/fonts.scss new file mode 100644 index 00000000..a68a6187 --- /dev/null +++ b/apps/frontend/src/assets/fonts.scss @@ -0,0 +1,12 @@ +@font-face { + font-family: 'Inter var'; + font-style: normal; + font-display: block; + src: url(./fonts/InterVariable.woff2) format('woff2'); +} +@font-face { + font-family: 'Inter var'; + font-style: italic; + font-display: block; + src: url(./fonts/InterVariable-Italic.woff2) format('woff2'); +} diff --git a/apps/frontend/src/assets/fonts/InterVariable-Italic.woff2 b/apps/frontend/src/assets/fonts/InterVariable-Italic.woff2 new file mode 100644 index 00000000..f22ec255 Binary files /dev/null and b/apps/frontend/src/assets/fonts/InterVariable-Italic.woff2 differ diff --git a/apps/frontend/src/assets/fonts/InterVariable.woff2 b/apps/frontend/src/assets/fonts/InterVariable.woff2 new file mode 100644 index 00000000..22a12b04 Binary files /dev/null and b/apps/frontend/src/assets/fonts/InterVariable.woff2 differ diff --git a/frontend/src/assets/images/cookies.webp b/apps/frontend/src/assets/images/cookies.webp similarity index 100% rename from frontend/src/assets/images/cookies.webp rename to apps/frontend/src/assets/images/cookies.webp diff --git a/frontend/src/assets/images/dlrg-isc-logo.svg b/apps/frontend/src/assets/images/dlrg-isc-logo.svg similarity index 100% rename from frontend/src/assets/images/dlrg-isc-logo.svg rename to apps/frontend/src/assets/images/dlrg-isc-logo.svg diff --git a/frontend/src/assets/images/dlrg-jugend-vollfarbe.svg b/apps/frontend/src/assets/images/dlrg-jugend-vollfarbe.svg similarity index 100% rename from frontend/src/assets/images/dlrg-jugend-vollfarbe.svg rename to apps/frontend/src/assets/images/dlrg-jugend-vollfarbe.svg diff --git a/apps/frontend/src/assets/images/gliederung_sh.png b/apps/frontend/src/assets/images/gliederung_sh.png new file mode 100644 index 00000000..773e1851 Binary files /dev/null and b/apps/frontend/src/assets/images/gliederung_sh.png differ diff --git a/frontend/src/assets/images/gliederung_test.png b/apps/frontend/src/assets/images/gliederung_test.png similarity index 100% rename from frontend/src/assets/images/gliederung_test.png rename to apps/frontend/src/assets/images/gliederung_test.png diff --git a/frontend/src/assets/images/icons/CustomFields/align-left-regular.svg b/apps/frontend/src/assets/images/icons/CustomFields/align-left-regular.svg similarity index 100% rename from frontend/src/assets/images/icons/CustomFields/align-left-regular.svg rename to apps/frontend/src/assets/images/icons/CustomFields/align-left-regular.svg diff --git a/frontend/src/assets/images/icons/CustomFields/circle-dot-regular.svg b/apps/frontend/src/assets/images/icons/CustomFields/circle-dot-regular.svg similarity index 100% rename from frontend/src/assets/images/icons/CustomFields/circle-dot-regular.svg rename to apps/frontend/src/assets/images/icons/CustomFields/circle-dot-regular.svg diff --git a/frontend/src/assets/images/icons/CustomFields/input-numeric-regular.svg b/apps/frontend/src/assets/images/icons/CustomFields/input-numeric-regular.svg similarity index 100% rename from frontend/src/assets/images/icons/CustomFields/input-numeric-regular.svg rename to apps/frontend/src/assets/images/icons/CustomFields/input-numeric-regular.svg diff --git a/frontend/src/assets/images/icons/CustomFields/input-pipe-regular.svg b/apps/frontend/src/assets/images/icons/CustomFields/input-pipe-regular.svg similarity index 100% rename from frontend/src/assets/images/icons/CustomFields/input-pipe-regular.svg rename to apps/frontend/src/assets/images/icons/CustomFields/input-pipe-regular.svg diff --git a/frontend/src/assets/images/icons/CustomFields/input-text-regular.svg b/apps/frontend/src/assets/images/icons/CustomFields/input-text-regular.svg similarity index 100% rename from frontend/src/assets/images/icons/CustomFields/input-text-regular.svg rename to apps/frontend/src/assets/images/icons/CustomFields/input-text-regular.svg diff --git a/frontend/src/assets/images/icons/CustomFields/square-check-regular.svg b/apps/frontend/src/assets/images/icons/CustomFields/square-check-regular.svg similarity index 100% rename from frontend/src/assets/images/icons/CustomFields/square-check-regular.svg rename to apps/frontend/src/assets/images/icons/CustomFields/square-check-regular.svg diff --git a/frontend/src/assets/images/icons/CustomFields/square-chevron-down-regular.svg b/apps/frontend/src/assets/images/icons/CustomFields/square-chevron-down-regular.svg similarity index 100% rename from frontend/src/assets/images/icons/CustomFields/square-chevron-down-regular.svg rename to apps/frontend/src/assets/images/icons/CustomFields/square-chevron-down-regular.svg diff --git a/frontend/src/assets/images/icons/CustomFields/toggle-on-regular.svg b/apps/frontend/src/assets/images/icons/CustomFields/toggle-on-regular.svg similarity index 100% rename from frontend/src/assets/images/icons/CustomFields/toggle-on-regular.svg rename to apps/frontend/src/assets/images/icons/CustomFields/toggle-on-regular.svg diff --git a/apps/frontend/src/assets/images/icons/Files/file-csv-duotone-solid.svg b/apps/frontend/src/assets/images/icons/Files/file-csv-duotone-solid.svg new file mode 100644 index 00000000..7943aca0 --- /dev/null +++ b/apps/frontend/src/assets/images/icons/Files/file-csv-duotone-solid.svg @@ -0,0 +1 @@ + diff --git a/apps/frontend/src/assets/images/icons/Files/file-doc-duotone-solid.svg b/apps/frontend/src/assets/images/icons/Files/file-doc-duotone-solid.svg new file mode 100644 index 00000000..e4e56774 --- /dev/null +++ b/apps/frontend/src/assets/images/icons/Files/file-doc-duotone-solid.svg @@ -0,0 +1 @@ + diff --git a/apps/frontend/src/assets/images/icons/Files/file-duotone-solid.svg b/apps/frontend/src/assets/images/icons/Files/file-duotone-solid.svg new file mode 100644 index 00000000..506d6cd7 --- /dev/null +++ b/apps/frontend/src/assets/images/icons/Files/file-duotone-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/frontend/src/assets/images/icons/Files/file-jpg-duotone-solid.svg b/apps/frontend/src/assets/images/icons/Files/file-jpg-duotone-solid.svg new file mode 100644 index 00000000..9771a152 --- /dev/null +++ b/apps/frontend/src/assets/images/icons/Files/file-jpg-duotone-solid.svg @@ -0,0 +1 @@ + diff --git a/apps/frontend/src/assets/images/icons/Files/file-pdf-duotone-solid.svg b/apps/frontend/src/assets/images/icons/Files/file-pdf-duotone-solid.svg new file mode 100644 index 00000000..e5127e22 --- /dev/null +++ b/apps/frontend/src/assets/images/icons/Files/file-pdf-duotone-solid.svg @@ -0,0 +1 @@ + diff --git a/apps/frontend/src/assets/images/icons/Files/file-png-duotone-solid.svg b/apps/frontend/src/assets/images/icons/Files/file-png-duotone-solid.svg new file mode 100644 index 00000000..f8f75cae --- /dev/null +++ b/apps/frontend/src/assets/images/icons/Files/file-png-duotone-solid.svg @@ -0,0 +1 @@ + diff --git a/apps/frontend/src/assets/images/icons/Files/file-ppt-duotone-solid.svg b/apps/frontend/src/assets/images/icons/Files/file-ppt-duotone-solid.svg new file mode 100644 index 00000000..2994a9a2 --- /dev/null +++ b/apps/frontend/src/assets/images/icons/Files/file-ppt-duotone-solid.svg @@ -0,0 +1 @@ + diff --git a/apps/frontend/src/assets/images/icons/Files/file-svg-duotone-solid.svg b/apps/frontend/src/assets/images/icons/Files/file-svg-duotone-solid.svg new file mode 100644 index 00000000..fc231288 --- /dev/null +++ b/apps/frontend/src/assets/images/icons/Files/file-svg-duotone-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/frontend/src/assets/images/icons/Files/file-xls-duotone-solid.svg b/apps/frontend/src/assets/images/icons/Files/file-xls-duotone-solid.svg new file mode 100644 index 00000000..ceff4e05 --- /dev/null +++ b/apps/frontend/src/assets/images/icons/Files/file-xls-duotone-solid.svg @@ -0,0 +1 @@ + diff --git a/apps/frontend/src/assets/images/icons/Files/file-zip-duotone-solid.svg b/apps/frontend/src/assets/images/icons/Files/file-zip-duotone-solid.svg new file mode 100644 index 00000000..1cd4308b --- /dev/null +++ b/apps/frontend/src/assets/images/icons/Files/file-zip-duotone-solid.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/assets/images/icons/carrot-regular.svg b/apps/frontend/src/assets/images/icons/carrot-regular.svg similarity index 100% rename from frontend/src/assets/images/icons/carrot-regular.svg rename to apps/frontend/src/assets/images/icons/carrot-regular.svg diff --git a/frontend/src/assets/images/icons/seedling-regular.svg b/apps/frontend/src/assets/images/icons/seedling-regular.svg similarity index 100% rename from frontend/src/assets/images/icons/seedling-regular.svg rename to apps/frontend/src/assets/images/icons/seedling-regular.svg diff --git a/frontend/src/assets/images/icons/steak-regular.svg b/apps/frontend/src/assets/images/icons/steak-regular.svg similarity index 100% rename from frontend/src/assets/images/icons/steak-regular.svg rename to apps/frontend/src/assets/images/icons/steak-regular.svg diff --git a/frontend/src/assets/images/login.png b/apps/frontend/src/assets/images/login.png similarity index 100% rename from frontend/src/assets/images/login.png rename to apps/frontend/src/assets/images/login.png diff --git a/frontend/src/assets/images/logo.svg b/apps/frontend/src/assets/images/logo.svg similarity index 100% rename from frontend/src/assets/images/logo.svg rename to apps/frontend/src/assets/images/logo.svg diff --git a/frontend/src/assets/images/logo_white.svg b/apps/frontend/src/assets/images/logo_white.svg similarity index 100% rename from frontend/src/assets/images/logo_white.svg rename to apps/frontend/src/assets/images/logo_white.svg diff --git a/frontend/src/assets/images/publicBg.webp b/apps/frontend/src/assets/images/publicBg.webp similarity index 100% rename from frontend/src/assets/images/publicBg.webp rename to apps/frontend/src/assets/images/publicBg.webp diff --git a/frontend/src/assets/images/sponsoren/codeanker.png b/apps/frontend/src/assets/images/sponsoren/codeanker.png similarity index 100% rename from frontend/src/assets/images/sponsoren/codeanker.png rename to apps/frontend/src/assets/images/sponsoren/codeanker.png diff --git a/frontend/src/assets/images/sponsoren/provinzial.png b/apps/frontend/src/assets/images/sponsoren/provinzial.png similarity index 100% rename from frontend/src/assets/images/sponsoren/provinzial.png rename to apps/frontend/src/assets/images/sponsoren/provinzial.png diff --git a/frontend/src/assets/images/svg/github-mark.svg b/apps/frontend/src/assets/images/svg/github-mark.svg similarity index 100% rename from frontend/src/assets/images/svg/github-mark.svg rename to apps/frontend/src/assets/images/svg/github-mark.svg diff --git a/frontend/src/assets/images/svg/shirt-light.svg b/apps/frontend/src/assets/images/svg/shirt-light.svg similarity index 100% rename from frontend/src/assets/images/svg/shirt-light.svg rename to apps/frontend/src/assets/images/svg/shirt-light.svg diff --git a/frontend/src/assets/main.scss b/apps/frontend/src/assets/main.scss similarity index 50% rename from frontend/src/assets/main.scss rename to apps/frontend/src/assets/main.scss index d6bd1ad9..9ac61135 100644 --- a/frontend/src/assets/main.scss +++ b/apps/frontend/src/assets/main.scss @@ -1,21 +1,29 @@ -@import "./animations.scss"; -@import "./tailwind.scss"; +@use './animations.scss'; +@use './tailwind.scss'; // @import "@vuepic/vue-datepicker/dist/main.css"; -@import "./baseFormStyle.scss"; -@import "./datepicker.scss"; -@import "./fonts.scss"; +@use './baseFormStyle.scss'; +@use './datepicker.scss'; +@use './fonts.scss'; -html, body, #app { - height: 100vh; +html { + font-size: 14px; +} + +html, +body, +#app { width: 100vw; overflow: hidden; overflow-y: overlay; -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none; scrollbar-gutter: stable; - @apply bg-gray-50 dark:bg-dark-secondary text-primary-950 dark:text-gray-200; } body { @apply transition-colors duration-200; } + +abbr { + cursor: help; +} diff --git a/apps/frontend/src/assets/tailwind.scss b/apps/frontend/src/assets/tailwind.scss new file mode 100644 index 00000000..b0acb187 --- /dev/null +++ b/apps/frontend/src/assets/tailwind.scss @@ -0,0 +1,51 @@ +/*! @import */ +@tailwind base; + +@layer base { + h1, + h2, + h3, + h4, + h5, + h6 { + @apply font-bold mt-0; + } + h1 { + @apply text-6xl mb-4; + } + h2 { + @apply text-4xl mb-4; + } + h3 { + @apply text-3xl leading-9 mb-3; + } + h4 { + @apply text-2xl leading-8 mb-2; + } + h5 { + @apply text-lg leading-6 mb-2; + } + h6 { + @apply text-base leading-5 mb-2; + } + p { + @apply mb-2; + } + pre { + @apply overflow-auto relative block bg-gray-800 rounded-lg py-8 px-4 text-white my-2; + &:before { + @apply absolute top-2 left-4 text-success-400; + content: '
';
+    }
+    &:after {
+      @apply absolute bottom-2 left-4 text-success-400;
+      content: '
'; + } + } + .container { + max-width: 100vw; + } +} + +@tailwind components; +@tailwind utilities; diff --git a/apps/frontend/src/components/Abbr.vue b/apps/frontend/src/components/Abbr.vue new file mode 100644 index 00000000..91f5d0ee --- /dev/null +++ b/apps/frontend/src/components/Abbr.vue @@ -0,0 +1,27 @@ + + + diff --git a/frontend/src/components/AnmeldungStatusSelect.vue b/apps/frontend/src/components/AnmeldungStatusSelect.vue similarity index 72% rename from frontend/src/components/AnmeldungStatusSelect.vue rename to apps/frontend/src/components/AnmeldungStatusSelect.vue index 342d5a4f..f3163b4e 100644 --- a/frontend/src/components/AnmeldungStatusSelect.vue +++ b/apps/frontend/src/components/AnmeldungStatusSelect.vue @@ -19,23 +19,35 @@ const props = withDefaults( ) const emit = defineEmits<{ - (event: 'changed'): void + changed: [] }>() const currentStatus = ref(props.status) const statusOptions = getEnumOptions(AnmeldungStatusMapping) -const availableOptions = statusOptions.filter( - (status) => status.value == 'ABGELEHNT' || status.value == 'STORNIERT' || status.value == 'BESTAETIGT' -) +const statusOptionsVisibility: Record boolean> = { + OFFEN: () => false, + BESTAETIGT: () => loggedInAccount.value?.role === 'ADMIN' || loggedInAccount.value?.role === 'GLIEDERUNG_ADMIN', + ABGELEHNT: () => loggedInAccount.value?.role === 'ADMIN' || loggedInAccount.value?.role === 'GLIEDERUNG_ADMIN', + STORNIERT: () => loggedInAccount.value?.role === 'USER', +} + +const availableOptions = computed(() => statusOptions.filter((o) => statusOptionsVisibility[o.value]())) const getStatusHuman = computed(() => (anmeldungStatus) => { return statusOptions.find((status) => status.value === anmeldungStatus)?.label }) const isStatusChangeAvailable = computed(() => { - if (loggedInAccount.value?.role === 'ADMIN' || props.meldeschluss > new Date()) { + if (loggedInAccount.value?.role === 'ADMIN') { return true } + if (props.status === 'ABGELEHNT') { + return false + } + if (props.meldeschluss > new Date()) { + return true + } + return false }) @@ -72,21 +84,21 @@ const setStatus = async (status: AnmeldungStatus) => { @@ -105,10 +117,14 @@ const setStatus = async (status: AnmeldungStatus) => {
+ />
-
{{ statusOption.label }}
-
{{ statusOption.description }}
+
+ {{ statusOption.label }} +
+
+ {{ statusOption.description }} +
diff --git a/frontend/src/components/AppLink.vue b/apps/frontend/src/components/AppLink.vue similarity index 100% rename from frontend/src/components/AppLink.vue rename to apps/frontend/src/components/AppLink.vue diff --git a/apps/frontend/src/components/BasicInputs/BasicAddressPicker.vue b/apps/frontend/src/components/BasicInputs/BasicAddressPicker.vue new file mode 100644 index 00000000..748d0ea1 --- /dev/null +++ b/apps/frontend/src/components/BasicInputs/BasicAddressPicker.vue @@ -0,0 +1,353 @@ + + + diff --git a/frontend/src/components/BasicInputs/BasicCheckbox.vue b/apps/frontend/src/components/BasicInputs/BasicCheckbox.vue similarity index 86% rename from frontend/src/components/BasicInputs/BasicCheckbox.vue rename to apps/frontend/src/components/BasicInputs/BasicCheckbox.vue index 9395acf5..4033d62b 100644 --- a/frontend/src/components/BasicInputs/BasicCheckbox.vue +++ b/apps/frontend/src/components/BasicInputs/BasicCheckbox.vue @@ -1,18 +1,19 @@ diff --git a/frontend/src/components/UIComponents/Tabs.vue b/apps/frontend/src/components/UIComponents/Tabs.vue similarity index 87% rename from frontend/src/components/UIComponents/Tabs.vue rename to apps/frontend/src/components/UIComponents/Tabs.vue index de709433..8ffbaf68 100644 --- a/frontend/src/components/UIComponents/Tabs.vue +++ b/apps/frontend/src/components/UIComponents/Tabs.vue @@ -23,6 +23,10 @@ const props = withDefaults( } ) +const emit = defineEmits<{ + changeTabIndex: [number] +}>() + const route = useRoute() const router = useRouter() @@ -41,6 +45,7 @@ const selectedTab = ref() function changeTab(index) { selectedTab.value = index router.push({ query: { tab: index.toString() } }) + emit('changeTabIndex', index) } @@ -48,13 +53,13 @@ function changeTab(index) { diff --git a/apps/frontend/src/components/UIComponents/UserLogo.vue b/apps/frontend/src/components/UIComponents/UserLogo.vue new file mode 100644 index 00000000..64076127 --- /dev/null +++ b/apps/frontend/src/components/UIComponents/UserLogo.vue @@ -0,0 +1,148 @@ + + + diff --git a/apps/frontend/src/components/UIComponents/VeranstaltungCard.vue b/apps/frontend/src/components/UIComponents/VeranstaltungCard.vue new file mode 100644 index 00000000..afff2cd6 --- /dev/null +++ b/apps/frontend/src/components/UIComponents/VeranstaltungCard.vue @@ -0,0 +1,69 @@ + + + diff --git a/frontend/src/components/UIComponents/components/Tab.vue b/apps/frontend/src/components/UIComponents/components/Tab.vue similarity index 73% rename from frontend/src/components/UIComponents/components/Tab.vue rename to apps/frontend/src/components/UIComponents/components/Tab.vue index 2d6b641a..0efcf6ea 100644 --- a/frontend/src/components/UIComponents/components/Tab.vue +++ b/apps/frontend/src/components/UIComponents/components/Tab.vue @@ -3,5 +3,5 @@ import { TabPanel } from '@headlessui/vue' diff --git a/apps/frontend/src/components/UnterveranstaltungenTable.vue b/apps/frontend/src/components/UnterveranstaltungenTable.vue new file mode 100644 index 00000000..3a777639 --- /dev/null +++ b/apps/frontend/src/components/UnterveranstaltungenTable.vue @@ -0,0 +1,117 @@ + + + diff --git a/apps/frontend/src/components/data/AnmeldungenTable.vue b/apps/frontend/src/components/data/AnmeldungenTable.vue new file mode 100644 index 00000000..c916b2a2 --- /dev/null +++ b/apps/frontend/src/components/data/AnmeldungenTable.vue @@ -0,0 +1,457 @@ + + + + + diff --git a/apps/frontend/src/components/data/PersonTable.vue b/apps/frontend/src/components/data/PersonTable.vue new file mode 100644 index 00000000..6794f671 --- /dev/null +++ b/apps/frontend/src/components/data/PersonTable.vue @@ -0,0 +1,108 @@ + + + diff --git a/frontend/src/components/forms/account/FormAccountGeneral.vue b/apps/frontend/src/components/forms/account/FormAccountGeneral.vue similarity index 86% rename from frontend/src/components/forms/account/FormAccountGeneral.vue rename to apps/frontend/src/components/forms/account/FormAccountGeneral.vue index 0020c0ec..0ae1ebdc 100644 --- a/frontend/src/components/forms/account/FormAccountGeneral.vue +++ b/apps/frontend/src/components/forms/account/FormAccountGeneral.vue @@ -1,6 +1,13 @@ + +
Entwickler:innen
+

Informationen für Entwickler:innen

+
{{ stammdatenForm }}
+
diff --git a/frontend/src/components/forms/anmeldung/FormAnmeldungGeneral.vue b/apps/frontend/src/components/forms/anmeldung/FormAnmeldungGeneral.vue similarity index 72% rename from frontend/src/components/forms/anmeldung/FormAnmeldungGeneral.vue rename to apps/frontend/src/components/forms/anmeldung/FormAnmeldungGeneral.vue index 084f82b9..70960028 100644 --- a/frontend/src/components/forms/anmeldung/FormAnmeldungGeneral.vue +++ b/apps/frontend/src/components/forms/anmeldung/FormAnmeldungGeneral.vue @@ -1,5 +1,5 @@ + + diff --git a/frontend/src/components/forms/person/FormContactGeneral.vue b/apps/frontend/src/components/forms/person/FormContactGeneral.vue similarity index 83% rename from frontend/src/components/forms/person/FormContactGeneral.vue rename to apps/frontend/src/components/forms/person/FormContactGeneral.vue index c7bf9fb8..1672fc83 100644 --- a/frontend/src/components/forms/person/FormContactGeneral.vue +++ b/apps/frontend/src/components/forms/person/FormContactGeneral.vue @@ -2,6 +2,7 @@ import { computed } from 'vue' import BasicInput from '@/components/BasicInputs/BasicInput.vue' +import BasicInputPhoneNumber from '@/components/BasicInputs/BasicInputPhoneNumber.vue' export interface IContact { email: string @@ -15,7 +16,7 @@ const props = withDefaults( {} ) const emit = defineEmits<{ - (event: 'update:modelValue', eventArgs: IContact): void + 'update:modelValue': [IContact] }>() const model = computed({ @@ -37,9 +38,8 @@ const model = computed({ placeholder="Email-Adresse" required /> - () const model = computed({ @@ -89,13 +89,15 @@ const addWeitereIntoleranzen = () => { class="input-style w-full text-left flex justify-between items-center" > -
+
+
+ + {{ getEssgewohnheitHuman }} +
- - {{ getEssgewohnheitHuman }}
@@ -122,7 +124,7 @@ const addWeitereIntoleranzen = () => { -

Nahrungsmittelintoleranzen

+

Nahrungsmittelintoleranzen

() const model = computed({ get() { - let model = props.modelValue + const model = props.modelValue if (props.modelValue.personen.length === 0) { model.personen.push(personTemplate.value) } diff --git a/apps/frontend/src/components/forms/person/FormPersonGeneral.vue b/apps/frontend/src/components/forms/person/FormPersonGeneral.vue new file mode 100644 index 00000000..6a278165 --- /dev/null +++ b/apps/frontend/src/components/forms/person/FormPersonGeneral.vue @@ -0,0 +1,270 @@ + + + diff --git a/apps/frontend/src/components/forms/person/PersonPhotoUpload.vue b/apps/frontend/src/components/forms/person/PersonPhotoUpload.vue new file mode 100644 index 00000000..a7312edf --- /dev/null +++ b/apps/frontend/src/components/forms/person/PersonPhotoUpload.vue @@ -0,0 +1,82 @@ + + + diff --git a/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungGeneral.vue b/apps/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungGeneral.vue similarity index 89% rename from frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungGeneral.vue rename to apps/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungGeneral.vue index a0333616..01185bfa 100644 --- a/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungGeneral.vue +++ b/apps/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungGeneral.vue @@ -17,14 +17,15 @@ import { UnterveranstaltungTypeMapping, getEnumOptions } from '@codeanker/api' import { ValidateForm } from '@codeanker/validation' const props = defineProps<{ - // eslint-disable-next-line @typescript-eslint/no-explicit-any unterveranstaltung?: any - // eslint-disable-next-line @typescript-eslint/no-explicit-any veranstaltungId?: any mode: 'create' | 'update' onUpdate?: () => void }>() +const unterveranstaltungId = props.unterveranstaltung?.id +const gliederung = ref(props.unterveranstaltung?.gliederung) + const unterveranstaltungCopy = ref({ beschreibung: props.unterveranstaltung?.beschreibung, bedingungen: props.unterveranstaltung?.bedingungen, @@ -37,9 +38,6 @@ const unterveranstaltungCopy = ref({ type: props.unterveranstaltung?.type, }) -const unterveranstaltungId = props.unterveranstaltung?.id -const gliederung = ref(props.unterveranstaltung?.gliederung) - // Wird benötig damit man direkt von einer Veranstaltung eine Unterveranstaltung anlegen kann ohne diese extra auswählen zu müssen if (props.mode === 'create') { unterveranstaltungCopy.value.veranstaltungId = props?.veranstaltungId @@ -48,12 +46,20 @@ if (props.mode === 'create') { function useVeranstaltungList(isAdmin: boolean) { if (isAdmin) { const { state } = useAsyncState(async () => { - return apiClient.veranstaltung.verwaltungList.query({ filter: {}, pagination: { take: 100, skip: 0 } }) + return apiClient.veranstaltung.verwaltungList.query({ + filter: {}, + orderBy: [], + pagination: { take: 100, skip: 0 }, + }) }, []) return state } else { const { state } = useAsyncState(async () => { - return apiClient.veranstaltung.gliederungList.query({ filter: {}, pagination: { take: 100, skip: 0 } }) + return apiClient.veranstaltung.gliederungList.query({ + filter: {}, + orderBy: [], + pagination: { take: 100, skip: 0 }, + }) }, []) return state } @@ -100,7 +106,9 @@ const { } await apiClient.unterveranstaltung.verwaltungPatch.mutate({ id: unterveranstaltungId, - data: unterveranstaltungCopy.value as unknown as RouterInput['unterveranstaltung']['verwaltungPatch']['data'], + data: { + ...unterveranstaltungCopy.value, + } as RouterInput['unterveranstaltung']['verwaltungPatch']['data'], }) } else { delete unterveranstaltungCopy.value.gliederungId @@ -111,7 +119,9 @@ const { } await apiClient.unterveranstaltung.gliederungPatch.mutate({ id: unterveranstaltungId, - data: unterveranstaltungCopy.value as unknown as RouterInput['unterveranstaltung']['gliederungPatch']['data'], + data: { + ...unterveranstaltungCopy.value, + } as RouterInput['unterveranstaltung']['gliederungPatch']['data'], }) } @@ -134,7 +144,11 @@ const handle = async () => { } async function queryObjectGliederungen(searchTerm) { - return apiClient.gliederung.publicList.query({ filter: { name: searchTerm }, pagination: { take: 100, skip: 0 } }) + return apiClient.gliederung.publicList.query({ + filter: { name: searchTerm }, + orderBy: [], + pagination: { take: 100, skip: 0 }, + }) } const veranstaltung = computed(() => { @@ -151,7 +165,7 @@ const veranstaltung = computed(() => { }) const disableddates = computed(() => { - let obj = { + const obj = { to: undefined, from: undefined, } @@ -164,14 +178,12 @@ const disableddates = computed(() => { diff --git a/apps/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungLandingSettings.vue b/apps/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungLandingSettings.vue new file mode 100644 index 00000000..1c6b6f44 --- /dev/null +++ b/apps/frontend/src/components/forms/unterveranstaltung/FormUnterveranstaltungLandingSettings.vue @@ -0,0 +1,523 @@ + + + diff --git a/frontend/src/components/forms/veranstaltung/FormVeranstaltungGeneral.vue b/apps/frontend/src/components/forms/veranstaltung/FormVeranstaltungGeneral.vue similarity index 95% rename from frontend/src/components/forms/veranstaltung/FormVeranstaltungGeneral.vue rename to apps/frontend/src/components/forms/veranstaltung/FormVeranstaltungGeneral.vue index b803e6d3..82840dd0 100644 --- a/frontend/src/components/forms/veranstaltung/FormVeranstaltungGeneral.vue +++ b/apps/frontend/src/components/forms/veranstaltung/FormVeranstaltungGeneral.vue @@ -14,7 +14,6 @@ import type { RouterInput } from '@codeanker/api' import { ValidateForm } from '@codeanker/validation' const props = defineProps<{ - // eslint-disable-next-line @typescript-eslint/no-explicit-any veranstaltung?: any mode: 'create' | 'update' onUpdate?: () => void @@ -89,7 +88,7 @@ const handle = async () => { } const { state: orte } = useAsyncState(async () => { - return apiClient.ort.verwaltungList.query({ filter: {}, pagination: { take: 100, skip: 0 } }) + return apiClient.ort.list.query({ filter: {}, orderBy: [], pagination: { take: 100, skip: 0 } }) }, []) const { state: hostnames } = useAsyncState(async () => { @@ -98,13 +97,7 @@ const { state: hostnames } = useAsyncState(async () => {