diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index b9e6f17b2..a0f3a375b 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -25,13 +25,12 @@ on: jobs: build: + runs-on: docker - container: - image: pazof/yavsc-build-env:debian13-dotnet10-android36-jdk21-v1 + steps: - name: Clone yavsc run: | - set -e cd /src git clone https://forgejo.pschneider.fr/notazof/yavsc.git _src cd _src @@ -40,24 +39,10 @@ jobs: git checkout FETCH_HEAD fi git submodule update --init --recursive - echo "✅ Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" - - name: Secret scan - run: | - echo "🔍 Scanning for secrets..." - cd /src/_src && dotnet tool restore && dotnet picket git --verbose --redact --exit-code 1 --log-opts -n10 \ + echo "Checked out at $(git rev-parse HEAD) on $(git branch --show-current 2>/dev/null || echo detached HEAD)" + - name: Restore dependencies + run: cd /src/_src && dotnet restore - name: Build - run: | - echo "🏗️ Building the solution..." - cd /src/_src && dotnet build --verbosity normal - - name: Install SkiaSharp native dependencies - run: | - echo "📦 Installing libfontconfig/libfreetype for SkiaSharp (Avalonia.Headless)..." - apt-get update && apt-get install -y --no-install-recommends \ - libfontconfig1 libfreetype6 libexpat1 zlib1g libbz2-1.0 libpng16-16 libbrotli1 \ - && rm -rf /var/lib/apt/lists/* + run: cd /src/_src && dotnet build --no-restore - name: Test - run: | - echo "🚀 Lancement des tests..." - cd /src/_src && dotnet test \ - --verbosity normal \ - --filter="Category!=Platform-Android" + run: cd /src/_src && dotnet test --no-build --verbosity normal diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 911fc831d..f7173b3a0 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -42,6 +42,11 @@ on: description: 'Tag à publier (requis en dispatch, ex. 1.0.6 ou 1.0.7-rc1).' required: true type: string + force_unstable: + description: 'Publier une release avec suffixe (ex. 1.0.0-rc1) malgré le fail-fast par défaut.' + required: false + type: boolean + default: false permissions: contents: write @@ -51,14 +56,13 @@ jobs: # via l'API REST Forgejo (pas d'actions tierces Node). release: runs-on: docker - container: - image: pazof/yavsc-build-env:debian13-dotnet10-android36-jdk21-v1 steps: - name: Clone du repo au tag demandé env: # En push tag : github.ref_name est le tag. # En workflow_dispatch : on lit l'input 'tag'. TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }} + FORCE_UNSTABLE: ${{ inputs.force_unstable || 'false' }} run: | if [[ -z "$TAG" ]]; then echo "::error::No tag provided. In workflow_dispatch, set the 'tag' input." @@ -68,8 +72,10 @@ jobs: # WORKDIR de l'image (cf. dotnet-android-build-image/Dockerfile). cd /src + # Clone unshallow pour que GitVersion.MsBuild ait l'historique + # et les tags (sinon MSB3073 sur la cible Android cf. PR #21). if [[ ! -d _src/.git ]]; then - git clone --depth=1 https://forgejo.pschneider.fr/notazof/yavsc.git _src + git clone https://forgejo.pschneider.fr/notazof/yavsc.git _src fi cd _src @@ -109,16 +115,11 @@ jobs: echo "Tag $TAG classifié comme channel=$CHANNEL" - # Seuls les suffixes explicitement autorisés déclenchent un - # release : -rcN et -betaN. Les autres suffixes (-alpha*, - # -dev*, -preview*, etc.) restent refusés — ils sont - # utilisables localement pour itérer, mais ne doivent pas - # être publiés comme release publique. - if [[ "$CHANNEL" == "unstable" ]]; then - if [[ ! "$SUFFIX" =~ ^-(rc|beta)([0-9]+)?$ ]]; then - echo "::error::Tag '$TAG' has suffix '$SUFFIX' which is not in the allowed release suffixes (-rcN, -betaN). Refusing to publish." - exit 1 - fi + # Fail-fast sur instable sauf opt-in explicite. + if [[ "$CHANNEL" == "unstable" && "${FORCE_UNSTABLE:-false}" != "true" ]]; then + echo "::error::Tag '$TAG' is unstable (suffix '$SUFFIX'). Refusing to publish." + echo "Set force_unstable=true via workflow_dispatch to override." + exit 1 fi # Lecture du CHANGELOG.md (doit exister à la racine du repo). @@ -171,28 +172,37 @@ jobs: echo "EOF" >> "$GITHUB_ENV" echo "IS_PRERELEASE=$([ "$CHANNEL" = "stable" ] && echo false || echo true)" >> "$GITHUB_ENV" - - name: Restore + - name: Build des projets .NET (sans docker) + # L'image runner (pazof/yavsc-build-env) a le SDK .NET 10 + le + # workload Android, mais PAS le binaire `docker` ni de daemon + # Docker. On exécute donc les commandes dotnet directement + # au lieu de passer par `docker build`. + # Equivalent des stages build-env du Dockerfile (lignes + # restore + build Yavsc.Org + build Yavsc.Api + build + # Yavsc.Blogs + build PostIt.Android -r android-arm64). run: | cd /src/_src dotnet restore - - name: Test - run: | - cd /src/_src && dotnet test \ - --verbosity normal \ - --filter="Category!=Platform-Android" \ - --logger "xunit;LogFileName=test-results.xml" + dotnet build src/Yavsc.Org/Yavsc.Org.csproj -c Release --no-restore -clp:ErrorsOnly + dotnet build src/Yavsc.Api/Yavsc.Api.csproj -c Release --no-restore -clp:ErrorsOnly + dotnet build src/Yavsc.Blogs/Yavsc.Blogs.csproj -c Release --no-restore -clp:ErrorsOnly + dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ + -c Release --no-restore -clp:ErrorsOnly -r android-arm64 - - name: Build de PostIt.Android ARM64 + - name: Copier l'APK signé vers un emplacement connu + # Le build Android avec -r android-arm64 produit l'APK dans + # bin/Release/net10.0-android/android-arm64/. On le copie à + # la racine du checkout pour que l'étape d'upload le trouve. run: | cd /src/_src - dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ - -c Release -r android-arm64 --no-restore -clp:ErrorsOnly - - - name: Build de PostIt.Android x64 - run: | - cd /src/_src - dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj \ - -c Release -r android-x64 --no-restore -clp:ErrorsOnly + APK=src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64/com.CompanyName.PostIt-Signed.apk + if [[ ! -f "$APK" ]]; then + echo "::error::APK not found at $APK" + ls -la src/PostIt/PostIt.Android/bin/Release/net10.0-android/ 2>/dev/null || true + exit 1 + fi + cp "$APK" /src/_src/PostIt.Android.apk + ls -la /src/_src/PostIt.Android.apk - name: Publier la release Forgejo via l'API REST # Pas d'action tierce (pas de Node dans l'image runner). @@ -206,7 +216,6 @@ jobs: RELEASE_BODY: ${{ env.RELEASE_BODY }} IS_PRERELEASE: ${{ env.IS_PRERELEASE }} run: | - set -e if [[ -z "$TAG" ]]; then echo "::error::No tag resolved for the API call." exit 1 @@ -302,22 +311,21 @@ jobs: # sinon curl l'interprète comme un second fichier d'input # (un fichier nommé '?name=PostIt.Android.apk') et l'API # Forgejo renvoie 400 "Missing 'name' parameter". - echo "::group::Upload PostIt APK assets" - for MARCH in arm64 x64; do - HTTP=$(curl -sS -o /tmp/asset.json -w '%{http_code}' \ - -X POST \ - -H "Authorization: token $GITHUB_TOKEN" \ - -H "Content-Type: application/octet-stream" \ - -H "Accept: application/json" \ - --data-binary "@/src/_src/src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-$MARCH/fr.pschneider.postit-Signed.apk" \ - "$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=PostIt.Android-$MARCH.apk") - echo "POST asset -> HTTP $HTTP" - if [[ "$HTTP" != "201" ]]; then - echo "::error::Asset upload failed (HTTP $HTTP):" - cat /tmp/asset.json - exit 1 - fi - done + echo "::group::Upload APK asset" + HTTP=$(curl -sS -o /tmp/asset.json -w '%{http_code}' \ + -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Content-Type: application/octet-stream" \ + -H "Accept: application/json" \ + --data-binary "@/src/_src/PostIt.Android.apk" \ + "$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=PostIt.Android.apk") + echo "POST asset -> HTTP $HTTP" echo "::endgroup::" - echo "✅ Release publiée: $API_BASE/$GITHUB_REPOSITORY/releases/tag/$TAG" + if [[ "$HTTP" != "201" ]]; then + echo "::error::Asset upload failed (HTTP $HTTP):" + cat /tmp/asset.json + exit 1 + fi + + echo "Release publiée: $API_BASE/$GITHUB_REPOSITORY/releases/tag/$TAG" diff --git a/.github/workflows/docker-publish-android.yml b/.github/workflows/docker-publish-android.yml new file mode 100644 index 000000000..b9ee364cc --- /dev/null +++ b/.github/workflows/docker-publish-android.yml @@ -0,0 +1,183 @@ +name: Build and Push Yavsc Apk + +on: + push: + branches: + - main + tags: + - '*' + workflow_dispatch: + inputs: + force_unstable: + description: 'Publier une release avec suffixe (ex. 1.0.0-rc1) malgré le fail-fast par défaut.' + required: false + type: boolean + default: false + +# softprops/action-gh-release a besoin de contents: write +# pour publier une release + uploader un asset. +permissions: + contents: write + +jobs: + apk-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout du code + uses: actions/checkout@v7 + with: + fetch-depth: 0 + fetch-tags: true + + # 1. Votre étape de build actuelle (on nomme l'image "postit-android") + # --target build-env : on ne veut que le stage de build (qui + # contient les artefacts .apk). Sans --target, Docker ciblerait + # le DERNIER stage du Dockerfile (blogs-runtime, qui est une + # image ASP.NET runtime sans aucun APK à extraire). + - name: Build de l'image Docker + run: docker build --build-arg ANDROID_TARGET_RID=android-arm64 --target build-env -t postit-android . + # 2. EXTRACTION : Créer un conteneur éphémère pour copier l'APK vers l'hôte GitHub + - name: Extraire l'APK du conteneur Docker + run: | + docker create --name extractor postit-android + docker cp extractor:/src/src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64/com.CompanyName.PostIt-Signed.apk ./PostIt.Android.apk + docker rm extractor + + - name: Téléverser l'APK en tant qu'Artéfact GitHub + uses: actions/upload-artifact@v7 + with: + name: application-apk-release + path: ./PostIt.Android.apk + retention-days: 7 + + # Job de validation : parse le tag, vérifie le format, applique la règle + # de parité du patch (pair=stable / impair=preview / suffixe=instable), + # et s'assure que CHANGELOG.md contient une section cohérente. + # Sans ce job, le job publish-release peut être bypassé (un attaquant + # qui contrôle un tag ne peut pas publier de release sans une section + # changelog cohérente). + validate-release: + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout du code + uses: actions/checkout@v7 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Valider le tag et la section CHANGELOG + env: + FORCE_UNSTABLE: ${{ inputs.force_unstable || github.event.inputs.force_unstable || 'false' }} + run: | + TAG="${GITHUB_REF_NAME}" + + # Parse semver : MAJOR.MINOR.PATCH[-SUFFIX] + if [[ ! "$TAG" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?$ ]]; then + echo "::error::Tag '$TAG' does not match MAJOR.MINOR.PATCH[-SUFFIX] format." + exit 1 + fi + + MAJOR="${BASH_REMATCH[1]}" + MINOR="${BASH_REMATCH[2]}" + PATCH="${BASH_REMATCH[3]}" + SUFFIX="${BASH_REMATCH[4]}" + + # Classification du canal par parité du patch. + # Patch pair + pas de suffixe -> stable. + # Patch impair + pas de suffixe -> preview. + # Suffixe présent -> instable. + if [[ -n "$SUFFIX" ]]; then + CHANNEL="unstable" + elif (( PATCH % 2 == 0 )); then + CHANNEL="stable" + else + CHANNEL="preview" + fi + + echo "Tag $TAG classifié comme channel=$CHANNEL" + + # Fail-fast sur instable sauf opt-in explicite via workflow_dispatch. + if [[ "$CHANNEL" == "unstable" && "$FORCE_UNSTABLE" != "true" ]]; then + echo "::error::Tag '$TAG' is unstable (suffix '$SUFFIX'). Refusing to publish." + echo "Set force_unstable=true via workflow_dispatch to override." + exit 1 + fi + + # Lecture du CHANGELOG.md (doit exister à la racine du repo). + if [[ ! -f CHANGELOG.md ]]; then + echo "::error::CHANGELOG.md not found at repo root." + exit 1 + fi + + # Extraction de la section [TAG]. On cherche la première ligne + # commençant par '## [' qui contient '[TAG]' (entre '## [' et + # la prochaine ligne '## [' ou fin de fichier). awk en mode + # paragraphe suffit et reste POSIX. + BODY=$(awk -v tag="[$TAG]" ' + /^## \[/ { + if (in_section) exit + if (index($0, tag) > 0) in_section=1 + next + } + in_section { print } + ' CHANGELOG.md) + + if [[ -z "$BODY" ]]; then + echo "::error::No section matching '## [$TAG]' found in CHANGELOG.md." + echo "Add a '## [$TAG] - $CHANNEL' section before tagging." + exit 1 + fi + + # Vérification cohérence du canal déclaré dans le titre de section. + # Format attendu : "## [TAG] - stable" / "- preview" / "- unstable". + HEADER=$(grep -m1 "^## \[$TAG\]" CHANGELOG.md) + if [[ "$HEADER" != *" - $CHANNEL"* ]]; then + echo "::error::Section '## [$TAG]' must declare suffix '- $CHANNEL' to match tag parity." + echo "Current section header: $HEADER" + exit 1 + fi + + echo "Section CHANGELOG validée pour [$TAG] - $CHANNEL" + + # Exposition aux étapes suivantes via $GITHUB_ENV. + # heredoc <> "$GITHUB_ENV" + + publish-release: + # Déclenché uniquement par un push de tag. Le job apk-deploy produit + # l'artefact ; validate-release garantit la cohérence du tag et du + # changelog avant publication. + if: startsWith(github.ref, 'refs/tags/') + needs: [apk-deploy, validate-release] + runs-on: ubuntu-latest + steps: + - name: Récupérer l'APK depuis l'artefact + uses: actions/download-artifact@v7 + with: + name: application-apk-release + path: ./ + + - name: Publier la release GitHub et uploader l'APK + uses: softprops/action-gh-release@v2 + with: + # Le nom de fichier final dans la release. C'est ce qui + # apparaîtra dans l'asset et donc dans le permalink : + # https://github.com///releases/latest/download/PostIt.Android.apk + files: ./PostIt.Android.apk + # Le body est extrait de la section CHANGELOG.md correspondant + # au tag, exposée par validate-release via $GITHUB_ENV. + body: ${{ env.RELEASE_BODY }} + # stable -> false (marque comme Latest). + # preview / unstable -> true (visible mais pas Latest). + prerelease: ${{ env.IS_PRERELEASE }} diff --git a/.github/workflows/docker-publish-backend.yml b/.github/workflows/docker-publish-backend.yml index d8466bdb8..6c2431aed 100644 --- a/.github/workflows/docker-publish-backend.yml +++ b/.github/workflows/docker-publish-backend.yml @@ -26,7 +26,7 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Test - run: dotnet test --no-build --verbosity normal --filter="Category!=Platform-Android" + run: dotnet test --no-build --verbosity normal # 4. Build et Push de l'image de production finale - name: Build and push production image uses: docker/build-push-action@v7 diff --git a/.gitignore b/.gitignore index 056cf69d2..a94475e3c 100644 --- a/.gitignore +++ b/.gitignore @@ -35,11 +35,7 @@ appsettings-*.*.json generated/ *.tmp -tmp/ DataDir/ *.tests.trx *.tests.html - -*.log - diff --git a/.gitleaksignore b/.gitleaksignore deleted file mode 100644 index 056cf69d2..000000000 --- a/.gitleaksignore +++ /dev/null @@ -1,45 +0,0 @@ -# Exclure uniquement les dossiers de sortie de compilation -bin/ -obj/ -src/*/bin/ -src/*/obj/ -test/*/bin/ -test/*/obj/ - -# Toolchain front (Node / esbuild) -node_modules/ -build/ -package-lock.json - -# Exclure les caches lourds -.git/ -.vs/ - -.env - -.*.env - -*.csproj.lscache -data/ -appsettings.*.json -appsettings-*.*.json - -# Exception: the Testing-environment override for Yavsc.Org is a tracked -# configuration source, not a secrets file. TestWebApplicationFactory -# (Yavsc.Org.Tests) flips ASPNETCORE_ENVIRONMENT to "Testing" so -# AddConfiguration("org") in Program.Main loads this file as the -# last in the chain (it is optional). It overrides the connection -# string and SMTP section for the in-memory test host and contains -# no production secrets. -!src/Yavsc.Org/appsettings-org.Testing.json - -generated/ -*.tmp -tmp/ -DataDir/ - -*.tests.trx -*.tests.html - -*.log - diff --git a/.vscode/launch.json b/.vscode/launch.json index d5088f01d..76dc08d50 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,68 +1,33 @@ { - // Utilisez IntelliSense pour en savoir plus sur les attributs possibles. - // Pointez pour afficher la description des attributs existants. - // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "PostIt Desktop", - "type": "dotnet", - "request": "launch", - "projectPath": "${workspaceFolder}/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj" - }, - { - "name": "PostIt Desktop local", - "type": "coreclr", - "request": "launch", - "program": "${workspaceFolder}/src/PostIt/PostIt.Desktop/bin/Debug/net10.0/PostIt.Desktop.dll", - "env": { - "POSTIT_SETTINGS_JSON": "/home/paul/Workspace/yavsc/src/PostIt/PostIt/postit-settings.json" - }, - "preLaunchTask": "dotnet: build-postit-desktop" - }, - { - "name": "Android Debug", - "type": "mono", - "preLaunchTask": "run-debug-android", - "request": "attach", - "address": "localhost", - "port": 55555 - }, - { - "name": "Android Attach - Debug", - "type": "mono", - "request": "attach", - "address": "localhost", - "port": 55555 - }, - { - "name": "Yavsc API", - "type": "dotnet", - "request": "launch", - "projectPath": "${workspaceFolder}/src/Yavsc.Api/Yavsc.Api.csproj" - }, - { - "name": "Yavsc Org", - "type": "dotnet", - "request": "launch", - "projectPath": "${workspaceFolder}/src/Yavsc.Org/Yavsc.Org.csproj", - }, - { - "name": "Yavsc Blogs", - "type": "dotnet", - "request": "launch", - "projectPath": "${workspaceFolder}/src/Yavsc.Blogs/Yavsc.Blogs.csproj" - }, + // Utilisez IntelliSense pour en savoir plus sur les attributs possibles. + // Pointez pour afficher la description des attributs existants. + // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "API", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/src/Api/Api.csproj" + }, + { + "name": "Yavsc.Org", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/src/Yavsc.Org/Yavsc.Org.csproj", + }, + { + "name": "Yavsc.Blogs", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/src/Yavsc.Blogs/Yavsc.Blogs.csproj" + }, + { + "name": "PostIt", + "type": "dotnet", + "request": "launch", + "projectPath": "${workspaceFolder}/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj", - { - "name": "Test PostIt.Android launch (Xamarin.UITest)", - "type": "coreclr", - "request": "launch", - "program": "${workspaceFolder}/src/PostIt/PostIt.Tests/bin/Debug/net10.0/PostIt.Tests.dll", - "args": [], - "cwd": "${workspaceFolder}/src/PostIt/PostIt.Tests", - "console": "integratedTerminal", - "stopAtEntry": false - } - ] + } + ] } diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 000000000..7ca6ed4be --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,11 @@ +{ + "servers": { + "openclaw": { + "type": "stdio", + "command": "/home/paul/.nvm/versions/node/v22.23.0/bin/node", + "args": [ + "/home/paul/Workspace/tools/openclaw-mcp-server.js" + ] + } + } +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 83a17ae38..16bbe4835 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,31 +2,27 @@ "dotnet-test-explorer.testProjectPath": "test/**/*Tests.csproj", "cSpell.words": [ - "appsettings", - "asciidoctor", - "ASPNETCORE", - "Avalonia", - "blogspot", - "Configurabilité", - "Cratie", - "DESTDIR", - "dotnet", - "DOTNET", - "ecdsa", - "envsubst", - "Forgejo", - "Hsts", - "Newtonsoft", - "Npgsql", - "Oidc", - "PKCE", - "postit", - "pschneider", - "SLNDIR", - "validable", - "www-data", - "yavsc", - "Yavsc" + "appsettings", + "asciidoctor", + "ASPNETCORE", + "Configurabilité", + "Cratie", + "DESTDIR", + "dotnet", + "DOTNET", + "ecdsa", + "envsubst", + "Hsts", + "Newtonsoft", + "Npgsql", + "PKCE", + "postit", + "pschneider", + "SLNDIR", + "validable", + "www-data", + "yavsc", + "Yavsc" ], "cSpell.reportUnknownWords": true, "cSpell.language": "fr,en", @@ -44,6 +40,5 @@ "copilotcli/gpt-5.3-codex" ] } - }, - "dotnet.defaultSolution": "yavsc.sln" + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 71384cd8c..e45a9921e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,44 +1,6 @@ { "version": "2.0.0", - "isRoot": true, - "problemMatcher": [ - { - "owner": "dotnet", - "fileLocation": ["relative", "${workspaceFolder}"], - "source": "dotnet", - "pattern": { - "regexp": "^\\s*(.*)\\((\\d+),(\\d+)\\):\\s+(error|warning) (.*)$", - "file": 1, - "line": 2, - "column": 3, - "severity": 4, - "code": 5, - "message": 6 - } - } - ], "tasks": [ - { - "label": "run-debug-android", - "command": "dotnet", - "type": "shell", - "options": { - "cwd": "${workspaceFolder}/src/PostIt/PostIt.Android", - "env": { - "DOTNET_HOST_PATH": "/usr/share/dotnet", - "ANDROID_HOME": "/opt/android-sdk", - "JAVA_HOME": "/usr/lib/jvm/java-1.25.0-openjdk-amd64" - } - }, - "args": [ - "run", - "-p:TargetFramework=net10.0-android", - "-p:Configuration=Debug", - "-p:AndroidAttachDebugger=true", - "-p:AndroidSdbHostPort=55555", - "-p:AndroidSdbTargetPort=55555" - ] - }, { "label": "build", "command": "dotnet", @@ -47,10 +9,8 @@ "group": "build", "isBuildCommand": true, "isTestCommand": false, - "isBackground": true, - "options": { - "cwd": "${workspaceFolder}" - } + "problemMatcher": ["$msCompile"], + "isBackground": true }, { "label": "test blogs backend", @@ -66,28 +26,6 @@ "isDefault": false } }, - { - "label": "test api backend (npgsql)", - "type": "process", - "problemMatcher": "$msCompile", - "command": "dotnet", - "args": [ - "test", - "Yavsc.Api.Test.csproj", - "-v", - "minimal" - ], - "options": { - "cwd": "src/Yavsc.Api.Test", - "env": { - "YAVSC_API_TEST_DB_PROVIDER": "npgsql" - } - }, - "group": { - "kind": "test", - "isDefault": false - } - }, { "label": "build-webapi", "type": "process", @@ -103,19 +41,57 @@ "isBackground": true }, { - "label": "dotnet: build-postit-desktop", + "label": "test blogs", "type": "process", - "isBuildCommand": true, - "isTestCommand": false, - "isBackground": true, + "problemMatcher": ["$msCompile"], "command": "dotnet", - "args": ["build", "/property:GenerateFullPaths=true"], + "args": ["test"], + "runOptions": { + "instanceLimit": 1 + }, "options": { - "cwd": "src/PostIt/PostIt.Desktop" + "cwd": "src/Yavsc.Blogs", + "env": { + "DOTNET_CLI_UI_LANGUAGE": "en-US", + "ASPNETCORE_ENVIRONMENT": "Development" + } }, "group": { - "kind": "build" + "kind": "test" + }, + "isBackground": true, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": true, + "clear": false } + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": ["watch", "--project", + "src/Yavsc.Org/Yavsc.Org.csproj" + ], + "problemMatcher": "$msCompile", + "runOptions": { + + } + } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 111c1491a..ac258ff6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,230 +1,20 @@ # Changelog -## [1.0.8-rc14] - unstable +Toutes les modifications notables de PostIt et de la plateforme Yavsc +sont documentées dans ce fichier. -### Added +Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/), +et ce projet adhère au [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -* [PostIt] Ajout d'un `BillingQueryDetailsPageViewModel` et de sa page associee pour afficher le detail d'une commande billing depuis l'historique. -* [PostIt] Ajout d'un mode detail avec section metier (statut, date, description, motif, infos) et section technique repliable (code, client, provision, lieu, prestations). -* [PostIt] Ajout d'un badge de statut enrichi (couleur + pictogramme) sur le detail d'une commande pour visualiser l'etat en un coup d'oeil. -* [PostIt] Ajout d'un bloc d'actions rapide en tete du detail (`Retour`, `Ouvrir en edition`) pour eviter le scroll jusqu'au bas de page. -* [PostIt] Ajout d'un style monospace sur les metadonnees techniques (code billing, client, provision, lieu, prestations) pour faciliter la lecture des identifiants et valeurs brutes. +À noter : la **parité du numéro de patch** porte une signification de canal : -### Changed +- **patch pair** (ex. `1.0.0`, `1.0.2`) → **stable** +- **patch impair** (ex. `1.0.1`, `1.0.3`) → **preview** +- **suffixe** (ex. `1.0.0-rc1`, `1.0.0-alpha`) → **instable** -* [PostIt] Le bouton d'ouverture depuis la liste billing ouvre maintenant une page de detail dediee avant l'eventuelle edition. -* [PostIt] Amelioration UX des pages billing: badges de statut colores, actions remontees en haut de page, et typographie monospace sur les metadonnees techniques. - -### Fixed - -* [Yavsc.Api] Correction d'un 500 sur le refresh du catalogue d'activites lorsque `Activity.Description` est `NULL` en base (nullabilite explicite + projection null-safe + gardes sur codes vides). -* [Yavsc.Api] Correction des erreurs 400/500 sur les routes billing (`Rdv`, `Brush`, `MBrush`) en imposant `ClientId` depuis l'utilisateur authentifie et en ignorant les champs server-owned lors de la validation modele. -* [Yavsc.Api] Correction du `PUT /api/v1/billing/Rdv/{id}`: mise a jour controlee de l'entite existante (et non remplacement brut du graphe JSON), ce qui supprime les `BadRequest` parasites. -* [Yavsc.Api] Correction PostgreSQL `timestamptz` sur RDV: normalisation UTC de `EventDate` sur `POST/PUT /api/v1/billing/Rdv` pour eviter l'erreur `Cannot write DateTime with Kind=Local`. -* [Yavsc.Api] Correction du flux FrontOffice accept/reject de query: sauvegarde avec contexte utilisateur et fallback d'injection pour `IBillingService` afin d'eviter les erreurs serveur en environnement de test. -* [Yavsc.Blogs] Correction des `BadRequest` sur `POST/PUT /api/v1/blogspot` avec payload JSON (PostIt): les proprietes de navigation/serveur (`Author`, `Tags`, `Comments`, audit) ne bloquent plus la validation. -* [Yavsc.Org] Correction du flux MVC de creation de commentaire: `SaveChangesAsync(userId)` est utilise pour renseigner les champs d'audit requis (`UserCreated`/`UserModified`). -* [Yavsc.Api.Test] Stabilisation des fixtures de seed billing: remplissage des metadonnees d'audit (`UserCreated`, `UserModified`, dates) pour eviter les echecs SQLite `NOT NULL`. - -## [1.0.8-rc13] - unstable - -### Added - -* [PostIt] Integration d'un selecteur de lieu RDV base sur Mapsui (carte interactive dans le formulaire `Rdv`). -* [PostIt] Ajout d'un marqueur de position et d'une action de recentrage sur la carte RDV. -* [PostIt] Ajout d'un service de reverse geocoding pour suggerer une adresse a partir des coordonnees carte. -* [PostIt] Cache et debounce des resolutions d'adresse RDV pour limiter les appels reseau et lisser l'UX. -* [PostIt.Tests] Nouvelles non-regressions sur le panneau d'adresse suggeree RDV et le comportement de la carte. -* [Yavsc.Abstract] Activation de `#nullable enable annotations` sur les fichiers legacy avec annotations nullable. -* [Yavsc.Server] Activation de `#nullable enable annotations` sur les fichiers legacy avec annotations nullable. - -### Changed - -* [PostIt] Generalisation de la barre de statut d'action (severite explicite) sur pages principales, dialogues et formulaires billing. -* [PostIt] Harmonisation des messages de statut utilisateur en francais. -* [PostIt] Renforcement des gardes de navigation dans les flux de gestion des membres de cercle. -* [PostIt] Le flux RDV conserve l'adresse saisie manuellement et propose l'adresse resolue comme suggestion explicite. -* [PostIt] Le flux de geolocalisation RDV tolere les positions proches dans le cache de suggestion d'adresse. - -### Fixed - -* [PostIt.Desktop] Correction d'un crash au demarrage OIDC (`No authority specified`) via durcissement des valeurs par defaut de configuration d'authentification. -* [PostIt] Correction de la persistance des settings: l'etat runtime de statut n'est plus serialize dans le JSON utilisateur. -* [PostIt.Tests] Ajout d'un verrou de non-regression sur le premier chargement des settings. -* [PostIt] Correction du binding de la date RDV: `DatePicker.SelectedDate` est aligne sur un proxy `DateTimeOffset?` (`EventDateSelection`). - -## [1.0.8-rc12] - unstable - -### Added - -* [PostIt] Nouveau helper d'image `ImageHelper` pour charger des bitmaps depuis les ressources et depuis le web. -* [PostIt] Affichage de l'avatar XS dans la liste des performers d'activites, avec fallback visuel (initiale utilisateur). -* [PostIt.Tests] Nouveaux tests autour des URLs avatar et de la source d'autorite. -* [contrib] Ajout d'un `README.md` utilitaire pour les symboles/icones. - -### Changed - -* [PostIt] Les avatars ne sont plus relies en string sur `Image.Source`: ils sont telecharges et lies en `Bitmap`. -* [Yavsc.Api.Client] `ActivityApiClient` accepte une base d'avatar dediee et construit les URLs avatar depuis l'autorite d'identification. -* [PostIt] Les clients Activites/Billing utilisent maintenant `ApiUrl` en lecture dynamique: un changement via Parametres prend effet sans redemarrer l'application (apres sauvegarde et rafraichissement de la page). -* [PostIt] Le header de `MainPage` n'utilise plus `ScrollViewer`; remplacement par une barre de commandes basee sur `WrapPanel`. -* [PostIt] Alignement de la navigation blogs: renommage `PushMainPageAsync` -> `PushBlogsPageAsync` et ajustement de `HomePageViewModel`. - -### Fixed - -* [PostIt.Android] Correction d'un 404 sur la page Activites au premier lancement: la configuration embarquee pointait `ApiUrl` vers le host Blogs au lieu de l'API metier. -* [PostIt] Correction du bouton Sauver de la page Parametres: binding vers `SaveCommand` pour persister correctement `ApiUrl`/`BlogsApiUrl`. - -## [1.0.8-rc11] - unstable - -### Added - -nothing - -### Changed - -* [Yavsc.Api.Test] Mise a jour de `Microsoft.EntityFrameworkCore.Sqlite` vers `10.0.11` afin de supprimer l'alerte NU1903 liee a `SQLitePCLRaw.lib.e_sqlite3` 2.1.11. -* [Yavsc.Org] Nettoyage de la configuration NuGet pour le restore: suppression du fichier local `Directory.Packages.props` au profit du fichier racine centralise. -* [Yavsc.Org] Suppression de references de packages redondantes dans le projet, sans impact fonctionnel attendu. - -### Fixed - -* [Yavsc.Api.Test] Le restore n'emet plus le warning de vulnerabilite `NU1903` sur `SQLitePCLRaw.lib.e_sqlite3`. -* [Yavsc.Org] Suppression d'une vulnerabilite de severite elevee sur AutoMapper apres publication et consommation de la nouvelle version candidate de `HigginsSoft.IdentityServer8`. - -## [1.0.8-rc10] - unstable - -### Added - -* [PostIt] Une page d'historique des commandes billing permet maintenant d'ouvrir une commande existante. -* [PostIt] Une vue "Demandes en cours" en lecture seule est disponible pour le performer, filtrée sur les statuts actifs (Inserted, Accepted, InProgress). -* [Yavsc.Org] Nouvelles entités `Country` et `PerformerCodeInputValidation` pour piloter la validation du code entreprise performer par pays. - -### Changed - -* [PostIt] La page détail billing se préremplit depuis une commande existante (Rdv, Brush, MBrush) et passe en mode mise à jour. -* [Yavsc.Org] Le formulaire `Manage/SetActivity` inclut désormais le pays d'exercice (`fr`, `en`, `pt`) et applique la regex associée au champ `SIREN`. -* [Yavsc.Org] La vérification externe du numéro d'entreprise est conservée uniquement pour le pays `fr`. - -### Fixed - -* [PostIt] Le flux historique n'est plus limité à une simple liste: l'action d'ouverture charge la commande cible puis navigue vers la page détail. -* [Yavsc.Org] Le champ `SIREN` n'est plus validé avec une règle unique indépendante du pays d'exercice. - -## [1.0.8-rc9] - unstable - -### Added - -nothing - -### Changed - -masquage non-owner côté backend de l'ACL du billet - -### Fixed - -On a maintenant le comportement attendu bout en bout: - -ACL chargée depuis le BlogPostDto -noms de cercles affichés dans le dialogue ACL côté PostIt - -## [1.0.8-rc8] - unstable - -### Added - -nothing - -### Changed - -nothing - -### Fixed - -The PostIt publish toggle button - -## [1.0.8-rc7] - unstable - -### Added - -* [PostIt] The search pattern now persists - -### Changed - -* The blog spot path is now `/api/v1/blogspot` (yet in last release) - -### Fixed - -* [Yavsc.Org] (Ticket #45) La forme de l'email de l'utilisateur est maintenant validée avant l'envoi du formulaire d'enregistrement - -## [1.0.8-rc6] - unstable - -### Added - -* a code cleanup, -* a first Xamarin.UITest is successful, but disabled, because breaking the actual CI process, -* Android app starts, the login process succeeds - -### Changed - -L'identifiant de l'application client Android a changé, il passe en minuscules : -`fr.pschneider.postit` - -### Fixed - -a bug posting and retrieving ACL from the backend, -the ACL now comes along with the article, -[TODO][PostIt] keep ACL along with the article - -## [1.0.8-rc1] - unstable - -### Added -- `BlogAclApiTests.PostCircleAuthorization_returns_201_when_payload_mirrors_PostIt_shape_against_existing_circle_named_test` - : test de non-régression qui épingle la forme exacte du payload - que PostIt envoie à `POST /api/v1/blogacl` (un objet - `PostAccessControlRulePayload` avec `CircleId` et `BlogPostId`). - C'est le verrou côté test du fix applicatif PostIt + serveur. -- `BlogAclApiTests.PostCircleAuthorization_never_returns_500` : une - `[Theory]` couvrant quatre shapes de payload (`{ circleId }`, - corps vide, `{ blogPostId }` seul, `{ circleId, blogPostId: 0 }`) - qui doivent tous retourner un statut différent de 500. Toute - réintroduction d'un chemin 500 dans le futur fera rougir ce test. -- `BlogAclApiTests.PostCircleAuthorization_dosent_return_500` et - `..._dosent_return_500_on_success` : entry points `[Fact]` qui - appellent la `[Theory]` ci-dessus avec un payload spécifique - chacun, pour pouvoir filtrer en isolation depuis la ligne de - commande ou le CI. -- Règle « Pas de `object` dans le code source applicatif » ajoutée - à `CONTRIBUTING.md` : types de retour, paramètres, champs, - propriétés, variables locales doivent être typés statiquement. - `dynamic` est interdit pour les mêmes raisons. - -### Changed -- `BlogAclApiController.CheckOwner` devient `CheckOwnerAsync` et - utilise `FirstOrDefaultAsync` au lieu de `First`, supprimant - l'appel LINQ synchrone sur le fil de la requête et retournant - `false` sur cercle manquant (le contrôleur mappe déjà cela vers - `ChallengeResult`). -- `BlogsWebServerFixture` seed `alice`, son `Circle` et son - `BlogPost` une seule fois au démarrage du host, sur la - `SqliteConnection` partagée (`Cache=Shared`). Le précédent - `EnsureDeleted` au début de chaque test fermait la connexion - statique et détruisait le store `:memory:` pour tous les autres - `DbContext` ; il est retiré au profit d'un `EnsureCreated` - idempotent. - -### Fixed -- `POST /api/v1/blogacl` ne retourne plus 500 sur les payloads - dont `BlogPostId` est absent ou à zéro. Le contrôleur rejette - `BlogPostId <= 0` avec `400 BadRequest` avant que la requête - n'atteigne `SaveChangesAsync`. L'incident de prod du 2026-08-21 - sur mercure (PostIt envoyant seulement `circleId`, le serveur - voyant `BlogPostId = default(long) = 0` et EF Core levant - `InvalidOperationException` sur l'INSERT) n'est plus atteignable. -- PostIt `PostAclDialogViewModel.AddAsync` envoie désormais le - payload explicite `PostAccessControlRulePayload { CircleId, - BlogPostId }` au lieu de l'ancien `CircleAuthorization { - CircleId }`. Le DTO serveur `PostAccessControlRulePayload` est - introduit dans `Yavsc.Abstract` pour porter le contrat. +Cette convention est partagée avec le dépôt +[`postit-debian`](https://forgejo.pschneider.fr/notazof/postit-debian) +pour la production des paquets `.deb`. ## [1.0.7] - preview @@ -328,10 +118,9 @@ the ACL now comes along with the article, migration, reverted in this release. The publish toggle covers the same user-visible switch without a schema change. -[Unreleased]: https://forgejo.pschneider.fr/notazof/yavsc/compare/HEAD -[1.0.8-rc1]: https://forgejo.pschneider.fr/notazof/yavsc/compare/1.0.7...1.0.8-rc1 -[1.0.7]: https://forgejo.pschneider.fr/notazof/yavsc/compare/1.0.6...1.0.7 -[1.0.6]: https://forgejo.pschneider.fr/notazof/yavsc/compare/1.0.5...1.0.6 +[Unreleased]: https://github.com/pazof/yavsc/compare/HEAD +[1.0.7]: https://github.com/pazof/yavsc/compare/1.0.6...1.0.7 +[1.0.6]: https://github.com/pazof/yavsc/compare/1.0.5...1.0.6 ## [1.0.6] - stable @@ -365,4 +154,4 @@ the ACL now comes along with the article, actual release id. Switched to `jq` for both body construction and field extraction. -[1.0.6]: https://forgejo.pschneider.fr/notazof/yavsc/compare/1.0.5...1.0.6 +[1.0.6]: https://github.com/pazof/yavsc/compare/1.0.5...1.0.6 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8aa0567d3..4730e18c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ ## Premier build ```bash -git clone https://forgejo.pschneider.fr/notazof/yavsc.git +git clone https://github.com/pazof/yavsc.git cd yavsc dotnet restore dotnet build @@ -49,90 +49,6 @@ Les tests sont répartis en : item « Tests d'intégration smoke par BC ». - `src/PostIt.Tests/` — tests unitaires du client desktop PostIt. -## Onboarding assiste par agents IA - -Pour accelerer la prise en main du depot avec Copilot/Plan/Explore : - -- Parcours pas-a-pas : [doc/onboarding-agents.md](./doc/onboarding-agents.md) -- Playbook d'usage des agents : [doc/agent-playbook.md](./doc/agent-playbook.md) -- Matrice intentions -> agent -> preuves : [doc/agent-intent-matrix.md](./doc/agent-intent-matrix.md) - -Regle minimale en contribution assistee par agent : -- expliciter l'impact architecture, -- justifier le niveau de tests execute, -- documenter les risques residuels. - -## Le CHANGELOG.md - -Le `CHANGELOG.md` est un document de changement de version - -Toutes les modifications notables de PostIt et de la plateforme Yavsc -sont documentées dans ce fichier. - -Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/), -et ce projet adhère au [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -À noter : la **parité du numéro de patch** porte une signification de canal : - -- **patch pair** (ex. `1.0.0`, `1.0.2`) → **preview** -- **patch impair** (ex. `1.0.1`, `1.0.3`) → **stable** -- **suffixe** (ex. `1.0.0-rc1`, `1.0.0-alpha`) → **instable** - -Cette convention est partagée avec le dépôt -[`postit-debian`](https://forgejo.pschneider.fr/notazof/postit-debian) -pour la production des paquets `.deb`. - -## Navigation (PostIt) - -La navigation est centralisée dans -`App.PushPageAsync(ViewModelBase vm)` (`src/PostIt/PostIt/App.axaml.cs`). -Pour ouvrir un écran, un ViewModel (généralement dans une -commande `[RelayCommand]`) appelle -`await ((App)App.Current!).PushPageAsync(targetVm).ConfigureAwait(true);`. -`PushPageAsync` résout la `Control` correspondante via le -`ViewLocator` (un `IDataTemplate` enregistré dans -`Application.DataTemplates` au boot), l'identifie comme -`Page`, lui assigne le VM comme `DataContext`, et appelle -`NavRoot.PushAsync(page)`. Une garde anti-empilement -compare par référence la nouvelle page au sommet courant -de la stack pour éviter un push doublon. - -Pour qu'une nouvelle page soit navigable, il faut *deux* -enregistrements : la page dans le DI (`AddTransient` -ou `AddSingleton`) **et** une case dans le `switch` -de `ViewLocator.Build`. Si l'un manque, l'app affiche -"No view for X" sans crash. - -Règles : - -- On n'instancie jamais une `View` à la main depuis un - ViewModel, on ne récupère jamais une `View` depuis la DI - directement dans un ViewModel. -- Le ViewModel qui déclenche la nav ne pousse pas lui-même - la page ; il appelle `App.PushPageAsync(vm)` et laisse - `App` orchestrer le `PushAsync` physique. -- Le ViewModel qui déclenche la nav ne capture pas de - référence à `MainWindow` ou `NavigationPage`. Il passe - par `App.Current` (l'app Avalonia est un singleton). - -Exemple canonique (depuis `MainPageViewModel`) : - -```csharp -[RelayCommand] -internal async Task OpenSettings() -{ - var settingsVm = ((App)App.Current!).ServiceProvider - .GetRequiredService(); - await ((App)App.Current!).PushPageAsync(settingsVm) - .ConfigureAwait(true); -} -``` - -Cf. [doc/architecture/postit.md](./doc/architecture/postit.md) -pour la topologie complète (host de navigation, -`SessionStatusViewModel`, signaux de cycle de vie vs nav -utilisateur). - ## Conventions de code Le repo applique `.editorconfig` (UTF-8, LF, `indent_size = 4` en @@ -148,13 +64,6 @@ Quelques règles non capturées par `.editorconfig` : - Préférer les types BCL (`int`, `string`) aux types framework (`Int32`, `String`). - Préférer les expressions de pattern matching aux casts explicites. -- **Pas de `object` dans le code source applicatif.** Types de retour, - paramètres, champs, propriétés, variables locales : tout doit être - typé statiquement. `dynamic` est interdit pour les mêmes raisons. - Un cast en `object` est presque toujours le symptôme d'un contrat - qu'on a laissé s'effriter (DTO, payload, handler) — refactore - le contrat (record typé, DTO dédié, méthode dédiée) au lieu de - shimer avec un cast. ## Branches & commits diff --git a/Directory.Build.props b/Directory.Build.props index 83d21579d..aec8c9908 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,16 @@ Yavsc - NU1701, NU1901, NU1902, NU1507 + + true + NU1701, NU1901, NU1902 diff --git a/Directory.Packages.props b/Directory.Packages.props index f1be93f91..e4b091593 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,31 +1,16 @@ true - 8.1.0-pazofrc007 - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + @@ -33,24 +18,13 @@ - - - - - - - - - - - - + \ No newline at end of file diff --git a/Makefile b/Makefile index d99fe5e5e..fa9d4ecf1 100644 --- a/Makefile +++ b/Makefile @@ -48,4 +48,77 @@ docker-build: docker-run: docker run -d -p 5000:5000 --name yavsc yavsc -.PHONY: test install docker-image docker-build docker-run +# Crée une branche release/ depuis main, met à jour les +# `` des .csproj via dotnet-gitversion, et la +# pousse sur origin. +# +# Usage : make release V=1.0.7-rc1 +# +# Pré-requis : être sur main, working tree clean. La cible +# vérifie les deux et refuse sinon — elle ne fait JAMAIS +# de checkout automatique, c'est à l'opérateur de s'être +# positionné sur la bonne branche au préalable (sinon le +# bump pourrait partir sur une branche tierce par accident). +# +# Notes : +# - Le nom de branche vient de l'argument V (ex: 1.0.7-rc1 +# donne release/1.0.7-rc1). C'est une étiquette d'intention, +# pas la version assembly. +# - La version dans les .csproj vient de GitVersion qui la +# calcule depuis l'historique git (tag le plus proche + +# nombre de commits). C'est la version assembly réelle. +# - L'ordre (fetch → branche → bump → push) garantit qu'on +# part d'un main synchro et qu'on ne pollue pas main avec +# le bump (qui vit sur la branche release). +# - Fail-fast si la branche existe déjà en local ou sur origin. +release: + @if [ -z "$(V)" ]; then \ + echo "Usage: make release V="; \ + echo " V : version semver (ex. 1.0.7-rc1) — sert à nommer la branche."; \ + exit 1; \ + fi + @CURRENT=$$(git branch --show-current); \ + if [ "$$CURRENT" != "main" ]; then \ + echo "Refus : la cible doit être lancée depuis main."; \ + echo " Branche courante : $$CURRENT"; \ + echo " Fais : git checkout main && git pull --ff-only origin main"; \ + exit 1; \ + fi + @if [ -n "$$(git status --porcelain)" ]; then \ + echo "Working tree sale, refus de créer une branche release."; \ + git status --short; \ + exit 1; \ + fi + @BRANCH="release/$(V)"; \ + if git show-ref --verify --quiet "refs/heads/$$BRANCH"; then \ + echo "La branche $$BRANCH existe déjà en local."; \ + echo " Pour la supprimer : git branch -D $$BRANCH"; \ + exit 1; \ + fi; \ + if git ls-remote --exit-code --heads origin "$$BRANCH" >/dev/null 2>&1; then \ + echo "La branche $$BRANCH existe déjà sur origin."; \ + exit 1; \ + fi; \ + echo "==> Fetch + vérification synchro main"; \ + git fetch origin main; \ + if ! git merge-base --is-ancestor origin/main HEAD; then \ + echo "main a avancé plus loin que HEAD. Fais :"; \ + echo " git pull --ff-only origin main"; \ + exit 1; \ + fi; \ + echo "==> Création de $$BRANCH depuis main"; \ + git checkout -b "$$BRANCH"; \ + echo "==> dotnet-gitversion /updateprojectfiles"; \ + dotnet-gitversion /updateprojectfiles; \ + echo "==> Commit du bump"; \ + git add .; \ + if git diff --cached --quiet; then \ + echo "Pas de changements à committer (gitversion n'a produit aucune diff)."; \ + else \ + git commit -m "chore(release): bump version via gitversion for $(V)"; \ + fi; \ + echo "==> Push de $$BRANCH sur origin"; \ + git push -u origin "$$BRANCH"; \ + echo "==> Terminé. Branche $$BRANCH live sur origin." + +.PHONY: test release diff --git a/README.md b/README.md index 549348f4a..8e6306125 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,11 @@ https://forgejo.pschneider.fr/notazof/yavsc/actions?workflow=release.yml # Statut actuel des actions GitHub -* [![CodeQL Advanced](https://github.com/pazof/yavsc/actions/workflows/codeql.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/codeql.yml) +* [![Build and Push Yavsc Apk](https://github.com/pazof/yavsc/actions/workflows/docker-publish-android.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/docker-publish-android.yml) * [![Build and Push Yavsc Production Image](https://github.com/pazof/yavsc/actions/workflows/docker-publish-backend.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/docker-publish-backend.yml) +* [![CodeQL Advanced](https://github.com/pazof/yavsc/actions/workflows/codeql.yml/badge.svg)](https://github.com/pazof/yavsc/actions/workflows/codeql.yml) # Documentation @@ -28,10 +29,6 @@ sous [`doc/`](./doc/). Voir l'[index de la documentation](./doc/README.md) pour le sommaire complet. La racine de l'architecture est [Architecture.md](./doc/Architecture.md). -Pour une prise en main guidee avec agents IA: -- parcours onboarding: [doc/onboarding-agents.md](./doc/onboarding-agents.md) -- playbook d'usage: [doc/agent-playbook.md](./doc/agent-playbook.md) - # Construction et déploiement diff --git a/ROADMAP.md b/ROADMAP.md index 364b98bda..4c00e629c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -68,7 +68,7 @@ Trois principes non négociables traversent tous les jalons : > > Chaque jalon a un **critère de sortie** vérifiable. -### Jalon 0 — Fondations techniques +### Jalon 0 — Fondations techniques *(en cours)* > Cible : pouvoir parler du domaine sans se battre avec le runtime. @@ -81,7 +81,7 @@ Trois principes non négociables traversent tous les jalons : --- -### Jalon 1 — Prestation signée de bout en bout *(en cours)* +### Jalon 1 — Prestation signée de bout en bout > Cible : un projet client/fournisseur aboutit à un **devis signé par les deux parties**, traçable, avec notifications. diff --git a/contrib/.env-sample b/contrib/.env-sample deleted file mode 100644 index fb01ede43..000000000 --- a/contrib/.env-sample +++ /dev/null @@ -1,24 +0,0 @@ -# parametres de déploiement au Makefile - -POSTGRES_HOST=localhost -POSTGRES_PORT=5432 -POSTGRES_DB=yavsc -POSTGRES_USER=yavsc -POSTGRES_PASSWORD= - -HTTP_HOST=localhost - -Org_PORT=83 -Blogs_PORT=85 -Api_PORT=87 - -PostIt_CLIENT_ID=postit - -ASPNETCORE_Smtp__Host="mercure.pschneider.fr" -ASPNETCORE_Smtp__Port=465 -ASPNETCORE_Smtp__SenderName="Paul Schneider" -ASPNETCORE_Smtp__SenderEmail="paul@pschneider.fr" -ASPNETCORE_Smtp__UserName="paul" -ASPNETCORE_Smtp__Password="" - -DESTDIR=/srv/www/yavsc diff --git a/contrib/Makefile b/contrib/Makefile index 4e4ef4df6..62e1e22d3 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -1,4 +1,4 @@ -APP_PROJECT_NAMES=Org Blogs Api +APP_PROJECT_NAMES=Api Org Blogs SLNDIR=.. include $(SLNDIR)/.env @@ -7,13 +7,12 @@ include .env generated/: @mkdir -p $@ +generated/yavscApi.service: generated/yavscOrg.service: generated/yavscBlogs.service: -generated/yavscApi.service: generated/yavsc%.service: generated/ template.service $(SLNDIR)/.env @cat template.service | APP_NAME="$*" \ - DESTDIR="$(DESTDIR)" \ HTTP_HOST="$(HTTP_HOST)" \ HTTP_PORT="$*_$(HTTP_PORT)" \ BASEAPPDIR="$(BASEAPPDIR)" \ @@ -35,12 +34,12 @@ generated/yavsc%.service: generated/ template.service $(SLNDIR)/.env @echo Created service file: $@ -copy-services: copy-service-Org copy-service-Blogs copy-service-Api +copy-services: copy-service-Org copy-service-Api copy-service-Blogs copy-service-Org: /etc/systemd/system/yavscOrg.service -copy-service-Blogs: /etc/systemd/system/yavscBlogs.service copy-service-Api: /etc/systemd/system/yavscApi.service +copy-service-Blogs: /etc/systemd/system/yavscBlogs.service -copy-binaries: build_publish_Org build_publish_Blogs build_publish_Api stop-services +copy-binaries: build_publish_Org build_publish_Api build_publish_Blogs stop-services @for project in $(APP_PROJECT_NAMES); \ do LCAPI=$$(echo $${project}|tr [:upper:] [:lower:]) ; \ echo "$${project} -> $${LCAPI}" ; \ @@ -56,26 +55,24 @@ copy-binaries: build_publish_Org build_publish_Blogs build_publish_Api stop-serv done @sudo chown -R $(USER_AND_GROUP) $(BASEAPPDIR) -/etc/systemd/system/yavsc%.service: generated/yavsc%.service +/etc/systemd/system/yavsc%.service: generated/yavsc%.service sudo cp $^ $@ sudo chown root:root $@ build_publish_%: clean_publish_dir_% @ASPNETCORE_ENV=$(CONFIGURATION) dotnet publish $(SLNDIR)/src/Yavsc.$*/Yavsc.$*.csproj -build_publish: build_publish_Org build_publish_Blogs build_publish_Api - clean_publish_dir_%: @rm -rf $(SLNDIR)/src/Yavsc.$*/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish -install: build_publish copy-binaries copy-services +install: build_publish copy-binaries copy-services @sudo systemctl daemon-reload @for project in $(APP_PROJECT_NAMES); \ do \ sudo systemctl enable yavsc$${project} ; \ sudo systemctl start yavsc$${project} ; \ done - + reinstall: copy-binaries @sync @for project in $(APP_PROJECT_NAMES); do \ @@ -91,19 +88,11 @@ $(SLNDIR)/src/Yavsc.Org/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish: build_ $(SLNDIR)/src/Yavsc.Blogs/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish: build_publish $(SLNDIR)/src/Yavsc.Api/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish: build_publish -showConfig: +showConfig: @echo CONFIGURATION: $(CONFIGURATION) @echo BASEAPPDIR: $(BASEAPPDIR) -showApiLogs: - @sudo journalctl -u yavscApi.service -S "2 min ago" | tee yavscApi.log - -showOrgLogs: - @sudo journalctl -u yavscOrg.service -S "2 min ago" | tee yavscOrg.log - -showBlogsLogs: - @sudo journalctl -u yavscBlogs.service -S "2 min ago" | tee yavscBlogs.log - clean: @rm -rf generated +.PHONY: build_publish mep showConfig copy-service-Api copy-service-Org copy-service-Blogs reinstall clean diff --git a/doc/README.md b/doc/README.md index fb9bea943..912a2e562 100644 --- a/doc/README.md +++ b/doc/README.md @@ -18,9 +18,6 @@ La racine de l'architecture est [Architecture.md](Architecture.md). | [architecture/postit.md](architecture/postit.md) | PostIt — topologie des projets, ViewLocator custo, navigation, DI, conventions de binding | | [architecture/decoupage-organisation.md](architecture/decoupage-organisation.md) | Découpage des projets .NET (Abstract, Server, Org, Api, Blogs, Web, Org.Tests) | | [testing.md](testing.md) | Stratégie de test : conventions des dossiers, EF Core in-memory, auth stubs, scaffold partagé | -| [onboarding-agents.md](onboarding-agents.md) | Parcours pas-à-pas pour prise en main agents IA + architecture + tests | -| [agent-playbook.md](agent-playbook.md) | Playbook d'usage de Copilot, Plan, Explore avec scénarios et anti-patterns | -| [agent-intent-matrix.md](agent-intent-matrix.md) | Matrice intentions développeur -> agent -> preuves attendues | ## Roadmap & design exploration diff --git a/doc/agent-intent-matrix.md b/doc/agent-intent-matrix.md deleted file mode 100644 index 975cd7b79..000000000 --- a/doc/agent-intent-matrix.md +++ /dev/null @@ -1,20 +0,0 @@ -# Matrice intentions -> agent -> preuves - -Cette matrice aide a choisir rapidement l'agent adapte et a exiger -une sortie verifiable. - -| Intention developpeur | Agent principal | Entrees minimales | Sortie minimale attendue | Verification | -|---|---|---|---|---| -| Comprendre un BC avant changement | Explore | BC cible, profondeur, contrainte de perimetre | Composants, points d'entree, tests relies, risques | Lire les fichiers cites + confirmer tests proposes | -| Decomposer une tache transverse | Plan | Objectif, contraintes, definition of done | Etapes ordonnees, dependances, criteres de verif | Verifier que chaque etape a une preuve observable | -| Implementer une modif locale | Copilot | Fichier cible, comportement attendu, conventions | Patch minimal, justification courte | Build/test du projet impacte | -| Ajouter un test smoke | Copilot (+Explore) | Route/endpoint, projet de test cible | Test + commande cible | Execution test cible | -| Corriger une regression | Plan + Copilot | Symptome, zone suspecte, test attendu | Fix + test NonRegression | Test rouge avant, vert apres | -| Diagnostiquer flux PostIt/OIDC | Explore + Plan | Flux, symptome, plateforme | Carte du flux + hypotheses testables | Verification manuelle + tests existants | - -## Regles d'arbitrage - -- Si l'intention est "comprendre": commencer par Explore. -- Si l'intention est "orchestrer": commencer par Plan. -- Si l'intention est "produire": utiliser Copilot apres cadrage. -- Si une sortie n'inclut pas de preuve, elle est incomplete. diff --git a/doc/agent-playbook.md b/doc/agent-playbook.md deleted file mode 100644 index ecc14f1d2..000000000 --- a/doc/agent-playbook.md +++ /dev/null @@ -1,101 +0,0 @@ -# Playbook d'usage des agents IA (Yavsc) - -Ce playbook normalise l'usage de Copilot, Plan et Explore dans le depot. -Il privilegie des sorties verifiables: fichiers, commandes tests, risques. - -## Quand utiliser quel agent - -- Plan: quand la tache est ambigue, transverse ou risquee. -- Explore: quand il faut cartographier rapidement des zones du code. -- Copilot: quand les specifications sont claires et localisees. - -## Prompt type (base) - -Utiliser ce squelette avant toute tache non triviale: - -```text -Contexte: -Objectif: -Contraintes: -Verification: -Sortie attendue: -``` - -## 4 scenarios de reference - -## 1) Explorer un bounded context - -Intention: -- Comprendre ou implementer un changement dans un BC sans regression laterale. - -Prompt minimal: -```text -Explore le BC avec profondeur medium. -Retour: composants touches, points d'entree, tests existants et risques. -``` - -Preuves attendues: -- Carte des fichiers a modifier. -- Test(s) smoke/mandatory proposes. - -## 2) Ajouter un smoke test - -Intention: -- Couvrir rapidement un endpoint ou une route publique. - -Prompt minimal: -```text -Propose un smoke test pour dans le projet de test approprie. -Respecte les conventions de doc/testing.md. -``` - -Preuves attendues: -- Fichier test cree/modifie. -- Commande precise pour executer le test cible. - -## 3) Corriger une regression backend API - -Intention: -- Corriger un bug sans casser un flux voisin. - -Prompt minimal: -```text -Planifie puis implemente un fix de dans . -Ajoute/ajuste un test NonRegression rouge puis vert. -``` - -Preuves attendues: -- Explication cause racine. -- Test non-regression associe. -- Commande d'execution et resultat attendu. - -## 4) Tracer un flux PostIt/OIDC - -Intention: -- Localiser une cassure d'authentification entre client et serveur. - -Prompt minimal: -```text -Cartographie le flux OIDC PostIt: entrypoints, callback, stockage token, -refresh. Donne points de rupture probables et tests/verification proposes. -``` - -Preuves attendues: -- Liste ordonnee des etapes du flux. -- Fichiers critiques. -- Hypotheses testables. - -## Anti-patterns a eviter - -- Prompt sans objectif verifiable. -- Demande trop large sans perimetre de fichiers. -- Validation basee uniquement sur "ca semble correct". -- Pas de lien entre changement et niveau de test. - -## Gate PR minimale (agent-assiste) - -Avant validation: -- Impact architecture explicite. -- Rationale de choix agent explicite. -- Test(s) executes et justifies. -- Risques residuels documentes. diff --git a/doc/architecture/postit.md b/doc/architecture/postit.md index 70f3f2fdc..77d0a2528 100644 --- a/doc/architecture/postit.md +++ b/doc/architecture/postit.md @@ -129,51 +129,30 @@ le DI est construit. Ordre, dans cet ordre : ## Navigation Le host de navigation est un `NavigationPage x:Name="NavRoot"` -posé sur `MainWindow.axaml`. La pile est gérée par deux -mécanismes distincts : +posé sur `MainWindow.axaml`. La pile est gérée par les +événements du `SessionStatusViewModel` : -1. **Nav utilisateur (VM-first)** : un ViewModel (souvent dans - une commande `[RelayCommand]`) appelle - `await ((App)App.Current!).PushPageAsync(targetVm).ConfigureAwait(true);`. - `App.PushPageAsync` (`src/PostIt/PostIt/App.axaml.cs`) - résout la `Control` correspondante via le `ViewLocator` - enregistré dans `Application.DataTemplates`, l'identifie - comme `Page`, lui assigne le VM comme `DataContext`, et - appelle `NavRoot.PushAsync(page)`. C'est le seul chemin - pour les boutons de la toolbar, les `OpenSettings` / - `OpenCircles` / `ManageAcl` / `OpenSignatureDev`, et - toute autre nav déclenchée par un ViewModel. - -2. **Signaux de cycle de vie** : le `SessionStatusViewModel` - lève des événements consommés dans - `App.OnFrameworkInitializationCompleted` pour orchestrer - la nav de boot : - - | Événement | Effet | - |---------------------|------------------------------------------------------------------| - | `LoginSucceeded` | `PushAsync(MainPage)` au-dessus de `HomePage` (post-login). | - | `LogoutCompleted` | `PopToRootAsync()` (revient à `HomePage`). | - - Ces events ne sont **pas** un canal de nav utilisateur ; ils - portent une transition d'état applicatif (authentification - établie / perdue) et c'est `App` qui choisit d'en faire une - transition de pile. +| Événement | Effet | +|---------------------------------|------------------------------------------------------------------------| +| `LoginSucceeded` | `PushAsync(MainPage)` au-dessus de `HomePage`. | +| `LogoutCompleted` | `PopToRootAsync()` (revient à `HomePage`). | +| `OpenSettingsRequested` | `PushAsync(SettingsPage)` au-dessus de la page courante. | ### Garde anti-empilement `NavigationPage.PushAsync` n'est pas idempotent : pousser deux fois la même instance l'empile deux fois, et l'utilisateur doit -taper **Retour** N fois pour sortir. La garde est implémentée -dans `App.PushPageAsync` (et consommée par tous les chemins -de nav utilisateur) : +taper **Retour** N fois pour sortir. Le handler +`OpenSettingsRequested` est gardé pour bloquer ce cas : ```csharp -var stack = window.NavRoot.NavigationStack; -if (stack.Count > 0 && ReferenceEquals(stack[stack.Count - 1], page)) +var settingsPage = provider.GetRequiredService(); +var stack = w.NavRoot.NavigationStack; +if (stack.Count > 0 && ReferenceEquals(stack[stack.Count - 1], settingsPage)) { - return Task.CompletedTask; // déjà au sommet, no-op silencieux + return; // déjà au sommet, no-op silencieux } -return window.NavRoot.PushAsync(page); +_ = w.NavRoot.PushAsync(settingsPage); ``` La comparaison est par référence, pas par type : on ne veut @@ -199,11 +178,9 @@ qui ne tiendrait plus). - `SessionStatusViewModel` est le seul VM avec une durée de vie **process-entière** (singleton). Il survit à toutes les navigations, expose `HasValidSession` en continu, et porte - les événements de cycle de vie consommés par `App` pour - orchestrer la nav de boot (`LoginSucceeded`, - `LogoutCompleted`). La nav utilisateur déclenchée par - l'utilisateur passe par `App.PushPageAsync(vm)`, pas par - un événement du `SessionStatusViewModel`. + les trois événements qui pilotent la navigation + (`LoginSucceeded`, `LogoutCompleted`, + `OpenSettingsRequested`). - `MainPageViewModel` / `HomePageViewModel` / `SignaturePageViewModel` sont `Transient` — une nouvelle @@ -256,14 +233,10 @@ pour `[RelayCommand]`". `ViewLocator.Build`. Oublier le `ViewLocator` est silencieux (juste un TextBlock "No view for X"), pas une exception. - **Ajouter un événement global de navigation** (par ex. - "Push après payment success") : ne pas capturer `MainWindow` - ni `NavigationPage` depuis le VM. La nav passe par - `App.PushPageAsync(vm)` dans tous les cas : soit le VM - appelle la méthode directement depuis une commande - (`[RelayCommand]`), soit un handler abonné à un événement - d'un singleton (cf. `SessionStatusViewModel`) l'appelle. - Garder les VMs découplés du - `IClassicDesktopStyleApplicationLifetime`. + "Push après payment success") : passer par un événement sur + un VM singleton (cf. `SessionStatusViewModel.OpenSettingsRequested`), + pas par une référence à `MainWindow` depuis le VM. Garder + les VMs découplés du `IClassicDesktopStyleApplicationLifetime`. - **Modifier l'OIDC** : la fiche à lire est [postit-oidc.md](postit-oidc.md), pas celle-ci. Cette fiche ne ré-explique ni le flow, ni le pipe, ni le custom scheme. diff --git a/doc/onboarding-agents.md b/doc/onboarding-agents.md deleted file mode 100644 index 14a727a17..000000000 --- a/doc/onboarding-agents.md +++ /dev/null @@ -1,73 +0,0 @@ -# Onboarding guide: agents IA + architecture + tests - -Ce guide est optimise pour accelerer la prise en main des agents IA -(Copilot, Plan, Explore) dans Yavsc, avec une verification rapide -par les tests. - -## Resultat attendu - -A la fin du parcours, un contributeur doit pouvoir: -- Identifier les projets impactes par une modification. -- Choisir l'agent adapte a l'intention de travail. -- Produire une proposition de changement verifiable par les tests. - -## Parcours en 3 modules - -## Module A - Comprendre le terrain (30-45 min) - -Objectif: acquerir une lecture fiable de l'architecture. - -1. Lire [README.md](../README.md) puis [Architecture.md](Architecture.md). -2. Lire [architecture/decoupage-organisation.md](architecture/decoupage-organisation.md). -3. Selon le domaine: - - Backend/API: [architecture/workflow-multi-parties.md](architecture/workflow-multi-parties.md) - - PostIt: [architecture/postit.md](architecture/postit.md) puis [architecture/postit-oidc.md](architecture/postit-oidc.md) - -Definition of done: -- Expliquer en 5 phrases quelles couches sont touchees. -- Citer le ou les points d'entree applicatifs a verifier. - -## Module B - Boucle tests rapide (20-30 min) - -Objectif: verifier rapidement sans lancer toute la suite. - -1. Lire [testing.md](testing.md). -2. Lancer les smoke tests d'abord, puis mandatory selon le projet. -3. N'elargir au test complet que si le scope depasse le BC touche. - -Definition of done: -- Fournir la commande test executee. -- Expliquer pourquoi ce niveau de test est suffisant. - -## Module C - Usage agentique en production (30-40 min) - -Objectif: utiliser les agents comme accelerateurs, pas comme boites noires. - -1. Plan: decomposer la tache en etapes verifiables. -2. Explore: collecter le contexte code/doc precise. -3. Copilot: implementer localement et verifier. - -Regles: -- Toujours donner un contexte explicite (fichier, but, contrainte). -- Demander des preuves observables (fichiers modifies, tests, risques). -- Refuser toute sortie non verifiable. - -Definition of done: -- Une tache simple est livree avec: - - Plan - - Changement local - - Preuve par test - -## Routine continue (sans echeance fixe) - -Rituels recommandes: -- Hebdo: revue des prompts qui ont bien fonctionne. -- Mensuel: mise a jour du present guide et du playbook. -- A chaque incident: ajouter un anti-pattern dans le playbook. - -## Check-list de validation - -- Le changement indique son impact architecture. -- Le choix de l'agent est justifie. -- La preuve test est incluse. -- Les risques residuels sont explicitement listes. diff --git a/dotnet-tools.json b/dotnet-tools.json index 1762a89de..b0e38abda 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -1,13 +1,5 @@ { "version": 1, "isRoot": true, - "tools": { - "picket": { - "version": "0.2.12", - "commands": [ - "picket" - ], - "rollForward": false - } - } + "tools": {} } \ No newline at end of file diff --git a/external/dotnet-android-build-image b/external/dotnet-android-build-image new file mode 160000 index 000000000..0695a6c1f --- /dev/null +++ b/external/dotnet-android-build-image @@ -0,0 +1 @@ +Subproject commit 0695a6c1fea6508f1a88f7ad0ad9cb93733aa52d diff --git a/src/PostIt/PostIt.Tests/BearerScopeTests.cs b/src/PostIt.Tests/BearerScopeTests.cs similarity index 97% rename from src/PostIt/PostIt.Tests/BearerScopeTests.cs rename to src/PostIt.Tests/BearerScopeTests.cs index 984483fc7..fbccb606e 100644 --- a/src/PostIt/PostIt.Tests/BearerScopeTests.cs +++ b/src/PostIt.Tests/BearerScopeTests.cs @@ -1,8 +1,18 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Net; +using System.Net.Http; using System.Text; using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Yavsc.Blogspot; using Yavsc.Api.Client; using PostIt.Services; +using PostIt.Services; +using Xunit; namespace PostIt.Tests; @@ -67,7 +77,7 @@ public class BearerScopeTests Scopes = userScopes, RedirectUri = "postit://callback", }, - ApiUrl = "https://example.invalid/api/v1/", + BusinessApiUrl = "https://example.invalid/api/v1/", }; var tokensPath = Path.Combine( @@ -266,7 +276,7 @@ public class BearerScopeTests // private HttpClient is independent, so we resolve the // absolute URI ourselves from Settings.BusinessApiUrl — // the same URL BlogApiClient would have set as BaseAddress. - var absolute = new Uri(new Uri(Settings.ApiUrl), path); + var absolute = new Uri(new Uri(Settings.BusinessApiUrl), path); using var req = new HttpRequestMessage(method, absolute); req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _accessToken); diff --git a/src/PostIt/PostIt.Tests/BlogApiTestFakes.cs b/src/PostIt.Tests/BlogApiTestFakes.cs similarity index 99% rename from src/PostIt/PostIt.Tests/BlogApiTestFakes.cs rename to src/PostIt.Tests/BlogApiTestFakes.cs index 4f102be20..4b541e428 100644 --- a/src/PostIt/PostIt.Tests/BlogApiTestFakes.cs +++ b/src/PostIt.Tests/BlogApiTestFakes.cs @@ -1,6 +1,7 @@ using Yavsc.Blogspot; using PostIt.Services; using PostIt.ViewModels; +using Yavsc.Models; namespace PostIt.Tests; diff --git a/src/PostIt.Tests/Directory.Packages.props b/src/PostIt.Tests/Directory.Packages.props new file mode 100644 index 000000000..15c4e24b0 --- /dev/null +++ b/src/PostIt.Tests/Directory.Packages.props @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/PostIt/PostIt.Tests/FakeAuthorizingBrowser.cs b/src/PostIt.Tests/FakeAuthorizingBrowser.cs similarity index 98% rename from src/PostIt/PostIt.Tests/FakeAuthorizingBrowser.cs rename to src/PostIt.Tests/FakeAuthorizingBrowser.cs index 88dd58564..4748425aa 100644 --- a/src/PostIt/PostIt.Tests/FakeAuthorizingBrowser.cs +++ b/src/PostIt.Tests/FakeAuthorizingBrowser.cs @@ -1,3 +1,6 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; using IdentityModel.OidcClient.Browser; namespace PostIt.Tests; diff --git a/src/PostIt/PostIt.Tests/MainPageSaveTests.cs b/src/PostIt.Tests/MainPageSaveTests.cs similarity index 88% rename from src/PostIt/PostIt.Tests/MainPageSaveTests.cs rename to src/PostIt.Tests/MainPageSaveTests.cs index 519cd141d..b6bf963af 100644 --- a/src/PostIt/PostIt.Tests/MainPageSaveTests.cs +++ b/src/PostIt.Tests/MainPageSaveTests.cs @@ -1,15 +1,16 @@ +using Avalonia; using Avalonia.Controls; using Avalonia.Headless.XUnit; using Avalonia.VisualTree; using Yavsc.Blogspot; using Yavsc.Api.Client; +using PostIt.Services; using PostIt.ViewModels; using PostIt.Views; -using PostIt.Views.Blogs; namespace PostIt.Tests; /// -/// Headless UI tests for the "Save" flow in . +/// Headless UI tests for the "Save" flow in . /// The pattern is the one SessionStatusBannerTests /// established: [AvaloniaFact], a /// hosting the page (via a because @@ -41,9 +42,9 @@ public class MainPageSaveTests var recorder = new CallRecorder(); var api = new RecordingYavscApiClient(recorder); var blog = new BlogApiClient(api, "http://localhost/"); - var viewModel = new BlogsViewModel(blog); + var viewModel = new MainPageViewModel(blog); - var page = new BlogsPage { DataContext = viewModel }; + var page = new MainPage { DataContext = viewModel }; // MainPage is a ContentPage (a Page, not a Control), so it // must be hosted in a navigation surface. The production // MainWindow.axaml uses NavigationPage, and the API is the @@ -64,7 +65,9 @@ public class MainPageSaveTests const string typed = "Mon premier billet"; titleBox.Text = typed; - var saveButton = page.SaveButton; + var saveButton = window.GetVisualDescendants() + .OfType [Fact] - public async Task Load_is_idempotent_under_concurrent_calls() + public void Load_is_idempotent_under_concurrent_calls() { var settings = new PostIt.ViewModels.Settings { @@ -145,79 +149,10 @@ public class SettingsLoadTests { barrier.SignalAndWait(); settings.Load(); - }, TestContext.Current.CancellationToken); + }); } - await Task.WhenAll(tasks); + Task.WaitAll(tasks); Assert.True(settings.Loaded); } - - [Fact] - public void SearchText_is_serialized_in_settings_and_round_trips() - { - var settings = new PostIt.ViewModels.Settings - { - Authentication = new AuthenticationSettings - { - Authority = "https://example.test/", - ClientId = "postit-tests", - Scopes = new[] { "openid" } - } - }; - - settings.SearchText = "bonjour"; - - var json = JsonSerializer.Serialize(settings); - var roundTrip = JsonSerializer.Deserialize(json); - - Assert.NotNull(roundTrip); - Assert.Equal("bonjour", roundTrip.SearchText); - } - - /// - /// First-start regression guard: older settings payloads can - /// still contain ActionStatus from a previous write. This - /// runtime-only UI state must not be persisted anymore and must - /// not break deserialization when present. - /// - [Fact] - public void ActionStatus_is_not_persisted_and_legacy_payload_with_it_still_deserializes() - { - var settings = new PostIt.ViewModels.Settings - { - Authentication = new AuthenticationSettings - { - Authority = "https://example.test/", - ClientId = "postit-tests", - Scopes = new[] { "openid" } - } - }; - - var serialized = JsonSerializer.Serialize(settings); - Assert.DoesNotContain("\"ActionStatus\"", serialized, StringComparison.Ordinal); - - const string legacyPayload = """ - { - "Authentication": { - "Authority": "https://example.test/", - "ClientId": "postit-tests", - "Scopes": ["openid"], - "RedirectUri": "postit://callback" - }, - "DarkMode": false, - "BlogsApiUrl": "https://blogs.example.test/api/v1/", - "ApiUrl": "https://api.example.test/api/v1/", - "SearchText": "hello", - "ActionStatus": { - "Message": "runtime only", - "Severity": "Error" - } - } - """; - - var roundTrip = JsonSerializer.Deserialize(legacyPayload); - - Assert.NotNull(roundTrip); - Assert.Equal("hello", roundTrip.SearchText); - } } diff --git a/src/PostIt/PostIt.Tests/SignaturePadControlTests.cs b/src/PostIt.Tests/SignaturePadControlTests.cs similarity index 90% rename from src/PostIt/PostIt.Tests/SignaturePadControlTests.cs rename to src/PostIt.Tests/SignaturePadControlTests.cs index ff1233e99..691ae5472 100644 --- a/src/PostIt/PostIt.Tests/SignaturePadControlTests.cs +++ b/src/PostIt.Tests/SignaturePadControlTests.cs @@ -1,5 +1,8 @@ +using System; +using System.Linq; using PostIt.Controls; using PostIt.Models; +using Xunit; namespace PostIt.Tests; @@ -94,26 +97,6 @@ public class SignaturePadControlTests Assert.NotEqual(first.Strokes, third.Strokes); } - [Fact] - public void PendingStroke_is_exposed_only_while_capturing() - { - var pad = new SignaturePadControl(); - - Assert.Empty(pad.PendingStroke); - - pad.BeginCaptureForTest(); - pad.AppendPointForTest(1_000, 2_000); - pad.AppendPointForTest(3_000, 4_000); - - Assert.Equal(new[] { 1_000, 2_000, 3_000, 4_000 }, pad.PendingStroke); - Assert.Equal(new[] { 1_000, 2_000, 3_000, 4_000 }, pad.Strokes); - - pad.SealStrokeForTest(); - - Assert.Empty(pad.PendingStroke); - Assert.Equal(new[] { 2, 1_000, 2_000, 3_000, 4_000 }, pad.Strokes); - } - [Fact] public void Clear_empties_buffer_and_raises_redraw() { diff --git a/src/PostIt/PostIt.Tests/SignaturePageViewModelTests.cs b/src/PostIt.Tests/SignaturePageViewModelTests.cs similarity index 98% rename from src/PostIt/PostIt.Tests/SignaturePageViewModelTests.cs rename to src/PostIt.Tests/SignaturePageViewModelTests.cs index 494df9ab4..37f17a58a 100644 --- a/src/PostIt/PostIt.Tests/SignaturePageViewModelTests.cs +++ b/src/PostIt.Tests/SignaturePageViewModelTests.cs @@ -1,6 +1,10 @@ +using System; +using System.IO; using System.Text.Json; +using System.Threading.Tasks; using PostIt.Controls; using PostIt.ViewModels; +using Xunit; namespace PostIt.Tests; diff --git a/src/PostIt/PostIt.Tests/TestApp.cs b/src/PostIt.Tests/TestApp.cs similarity index 100% rename from src/PostIt/PostIt.Tests/TestApp.cs rename to src/PostIt.Tests/TestApp.cs diff --git a/src/PostIt/PostIt.Tests/UnitTest1.cs b/src/PostIt.Tests/UnitTest1.cs similarity index 70% rename from src/PostIt/PostIt.Tests/UnitTest1.cs rename to src/PostIt.Tests/UnitTest1.cs index bc0d864c8..96990865a 100644 --- a/src/PostIt/PostIt.Tests/UnitTest1.cs +++ b/src/PostIt.Tests/UnitTest1.cs @@ -1,4 +1,5 @@ using Avalonia.Headless.XUnit; +using Avalonia.Controls; using PostIt.Views; namespace PostIt.Tests; @@ -8,7 +9,8 @@ public class MainPageTests [AvaloniaFact] public void MainPage_Should_Load() { - var window = new MainView(); + var window = new MainWindow(); + window.Show(); Assert.NotNull(window); } -} +} \ No newline at end of file diff --git a/src/PostIt/PostIt.Tests/YavscApiClientTests.cs b/src/PostIt.Tests/YavscApiClientTests.cs similarity index 97% rename from src/PostIt/PostIt.Tests/YavscApiClientTests.cs rename to src/PostIt.Tests/YavscApiClientTests.cs index b074de666..e54bc5411 100644 --- a/src/PostIt/PostIt.Tests/YavscApiClientTests.cs +++ b/src/PostIt.Tests/YavscApiClientTests.cs @@ -1,10 +1,22 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Net; +using System.Net.Http; using System.Net.Sockets; using System.Text; using System.Text.Json; +using System.Threading; +using Yavsc.Blogspot; +using Yavsc.Api.Client; using PostIt.Services; +using System.Threading.Tasks; +using IdentityModel.OidcClient; using IdentityModel.OidcClient.Browser; +using PostIt.Services; using PostIt.ViewModels; +using Xunit; namespace PostIt.Tests; @@ -58,7 +70,7 @@ public class YavscApiClientTests // calls CallAsync("posts", ...) directly (bypassing // BlogApiClient, which is the only thing that would set // it in production). Mirror prod here. - reloaded.Http.BaseAddress = new Uri(settings.ApiUrl); + reloaded.Http.BaseAddress = new Uri(settings.BusinessApiUrl); var posts = await reloaded.CallAsync>( HttpMethod.Get, "posts", TestContext.Current.CancellationToken); @@ -118,7 +130,7 @@ public class YavscApiClientTests RedirectUri = "postit://callback", Scopes = new[] { "openid" }, }, - ApiUrl = "https://127.0.0.1:5003/api/v1", + BusinessApiUrl = "https://127.0.0.1:5003/api/v1", }; var client = new YavscApiClient(settings, new TokenStore(Path.Combine( Path.GetTempPath(), $"postit-tests-noop-{Guid.NewGuid():N}.json"))); @@ -162,7 +174,7 @@ public class YavscApiClientTests RedirectUri = authority.LoopbackRedirectUri, Scopes = new[] { "openid", "profile", "blog" } }, - ApiUrl = apiBaseUrl + BusinessApiUrl = apiBaseUrl }; private static async Task LoginAndPersistAsync( @@ -175,7 +187,7 @@ public class YavscApiClientTests // directly (bypassing BlogApiClient) rely on the same // BaseAddress the production chain sets in BlogApiClient's // ctor. Mirror that here so "posts" resolves to the stub. - client.Http.BaseAddress = new Uri(settings.ApiUrl); + client.Http.BaseAddress = new Uri(settings.BusinessApiUrl); // Force the API client to use the test browser by routing the // LoginInteractiveAsync call through a small wrapper. diff --git a/src/PostIt/Directory.Packages.props b/src/PostIt/Directory.Packages.props index 2f27340e9..62b3a343f 100644 --- a/src/PostIt/Directory.Packages.props +++ b/src/PostIt/Directory.Packages.props @@ -1,35 +1,21 @@ - + + - - - true - 12.1.1 - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/PostIt/Makefile b/src/PostIt/Makefile deleted file mode 100644 index 1217976b7..000000000 --- a/src/PostIt/Makefile +++ /dev/null @@ -1,178 +0,0 @@ - -# Cibles pour installer PostIt.Android en Debug sur l'AVD qemu. -# -# Usage typique : -# make qemu # lance l'AVD, attend le boot, build l'APK, l'installe -# make android-install # (re)build l'APK et l'installe (AVD doit tourner) -# make android-build # build l'APK seul (sans install) -# make qemu-run # démarre l'AVD en background -# make qemu-stop # arrête l'émulateur -# make qemu-wait-boot # attend que l'AVD ait fini de booter -# -# Variables surchargeables (make VAR=valeur) : -# AVD_NAME default: postit_test_avd -# (l'AVD doit être listé par `avdmanager list avd`) -# ADB_SERIAL default: emulator-5554 -# (port standard du premier émulateur lancé) -# ANDROID_HOME default: /opt/android-sdk -# (le SDK Android local; doit contenir -# emulator/emulator et platform-tools/adb) -# POSTIT_RID default: android-x64 -# (doit matcher l'ABI de l'AVD; `avdmanager list avd` -# affiche la ligne Tag/ABI) -# EMU_HEADLESS default: 0 -# (1 = lancer l'émulateur sans fenêtre, pour scripter) -# CONFIG surcharge la variable CONFIG globale (Debug par -# défaut dans ce Makefile). Passer à Release pour -# un APK optimisé et signé release. -# LOGCAT_LINES default: 200 -# (nombre de lignes dumpées par `make qemu-logcat`) -# LOGCAT_FOLLOW default: 0 -# (1 = stream live via `make logcat`, -# sinon dump one-shot des N dernières lignes) -# LOGCAT_BOOT_WAIT default: 30 -# (secondes d'attente entre le clear du buffer, -# le `am start`, et le dump final dans -# `make qemu-logcat-boot`) -AVD_NAME ?= postit_test_avd -ADB_SERIAL ?= emulator-5554 -ANDROID_HOME ?= /opt/android-sdk -POSTIT_RID ?= android-x64 -EMU_HEADLESS ?= 0 -LOGCAT_LINES ?= 600 -LOGCAT_FOLLOW ?= 0 -LOGCAT_BOOT_WAIT ?= 30 - -ANDROID_PACKAGE_NAME = fr.pschneider.postit -POSTIT_ANDROID_CSPROJ := PostIt.Android/PostIt.Android.csproj -POSTIT_APK_DIR := PostIt.Android/bin/$(CONFIG)/net10.0-android/$(POSTIT_RID) -POSTIT_APK := $(POSTIT_APK_DIR)/$(ANDROID_PACKAGE_NAME)-Signed.apk - -clean: clean-PostIt clean-PostIt.Android clean-PostIt.Desktop - -clean-%: - rm -rf $*/obj $*/bin - -qemu-run: - @echo " Starting AVD $(AVD_NAME) on $(ADB_SERIAL)..." - @mkdir -p /tmp/yavsc-emu - @EMU_ARGS=""; \ - if [ "$(EMU_HEADLESS)" = "1" ]; then EMU_ARGS="-no-window -no-audio"; fi; \ - $(ANDROID_HOME)/emulator/emulator -avd $(AVD_NAME) $$EMU_ARGS \ - >/tmp/yavsc-emu/$(AVD_NAME).log 2>&1 & \ - echo " ✅ Started emulator PID: $$!" - -qemu-stop: - adb -s $(ADB_SERIAL) emu kill - echo " ✅ Stopped emulator" - -qemu-wait-boot: - @echo " Waiting for $(ADB_SERIAL) to finish booting..." - adb -s $(ADB_SERIAL) wait-for-device - @for i in $$(seq 1 180); do \ - BOOTED=$$(adb -s $(ADB_SERIAL) shell getprop sys.boot_completed 2>/dev/null | tr -d '\r\n'); \ - if [ "$$BOOTED" = "1" ]; then \ - echo " ✓ booted in $${i}s"; \ - exit 0; \ - fi; \ - sleep 1; \ - done; \ - echo " 👿 ERROR: device did not boot within 180s." >&2; \ - echo " Logs: /tmp/yavsc-emu/$(AVD_NAME).log" >&2; \ - exit 1 - -android-build: - # EmbedAssembliesIntoApk=true: without this, the Debug APK ships - # without the managed assemblies in it (they are pushed at runtime - # via `adb push`, "Fast Deployment"). On the qemu emulator, the - # runtime cannot find them in `files/.__override__//` and - # aborts at startup with "No assemblies found in '.__override__'" - # (monodroid-glue.cc:757, SIGABRT). Forcing this property on - # packages the .dlls into the APK as `assemblies//` so the - # runtime reads them directly. - # - # The Xamarin.Android SDK property is `EmbedAssembliesIntoApk`, - # not `AndroidEnableFastDeployment` (which exists in older - # templates but is a no-op in the .NET 10 SDK). - dotnet build $(POSTIT_ANDROID_CSPROJ) \ - -c $(CONFIG) \ - -p:RuntimeIdentifier=$(POSTIT_RID) \ - -p:EmbedAssembliesIntoApk=true \ - --nologo - @if [ ! -f "$(POSTIT_APK)" ]; then \ - echo " APK not found at $(POSTIT_APK)." >&2; \ - echo " Files in $(POSTIT_APK_DIR):" >&2; \ - ls -la "$(POSTIT_APK_DIR)" 2>/dev/null || echo " (directory does not exist)" >&2; \ - exit 1; \ - fi - - -android-install: android-build - @echo " Installing $(POSTIT_APK) on $(ADB_SERIAL)..." - adb -s $(ADB_SERIAL) install -r "$(POSTIT_APK)" -r - @echo " ✅ PostIt.Android installed on $(ADB_SERIAL)" - -qemu-uninstall: - adb -s $(ADB_SERIAL) uninstall $(ANDROID_PACKAGE_NAME) - -# Dump recent logcat output for the running PostIt.Android process. -# By default, prints the last $(LOGCAT_LINES) lines (one-shot, with -# `-d`). Set LOGCAT_FOLLOW=1 to follow the stream live instead. -# Filtering is by PID (pidof $(ANDROID_PACKAGE_NAME)), not by tag, -# because Mono/Xamarin can emit logs under several tags -# (mono, PostIt.Android, Avalonia.Android) and tag-based filtering -# would miss the ones not matching. PID-based filtering is exact. -# If the app is not running, pidof returns empty and logcat exits -# silently with no output; that is the expected behaviour for -# "no logs yet". -logcat: - @PID=$$(adb -s $(ADB_SERIAL) shell pidof $(ANDROID_PACKAGE_NAME) 2>/dev/null | tr -d '\r\n'); \ - if [ -z "$$PID" ]; then \ - echo " $(ANDROID_PACKAGE_NAME) is not running on $(ADB_SERIAL)."; \ - echo " Start the app first (am start -n $(ANDROID_PACKAGE_NAME)/PostIt.Android.PostItMainActivity)"; \ - exit 1; \ - fi; \ - echo " Following PID $$PID (LOGCAT_FOLLOW=$(LOGCAT_FOLLOW), LOGCAT_LINES=$(LOGCAT_LINES))"; \ - if [ "$(LOGCAT_FOLLOW)" = "1" ]; then \ - adb -s $(ADB_SERIAL) logcat -v time --pid=$$PID $(ANDROID_PACKAGE_NAME); \ - else \ - adb -s $(ADB_SERIAL) logcat -d -v time -t $(LOGCAT_LINES) --pid=$$PID $(ANDROID_PACKAGE_NAME); \ - fi - -# Clear logcat, launch PostIt.Android, then dump everything that was -# emitted during the startup window. Targets the "démarrage KO" case -# where the process starts but Avalonia never renders a frame — the -# logcat trace from process start to first frame is what diagnoses it. -# -# Override LOGCAT_BOOT_WAIT to extend the post-launch wait -# (default 15s; raise to 30+ if the device is slow to boot Avalonia). -LOGCAT_BOOT_WAIT ?= 15 - - -android-start: - @echo " Clearing logcat buffer..." - adb -s $(ADB_SERIAL) logcat -c - @echo " Launching $(ANDROID_PACKAGE_NAME)..." - adb -s $(ADB_SERIAL) shell am start \ - -n $(ANDROID_PACKAGE_NAME)/PostIt.Android.PostItMainActivity - @echo " ✅ $(ANDROID_PACKAGE_NAME) started on $(ADB_SERIAL)" - -qemu-logcat-boot: android-start - @echo " Waiting $(LOGCAT_BOOT_WAIT)s for the app to start rendering..." - @sleep $(LOGCAT_BOOT_WAIT) - - @echo " Dumping logcat (PostIt PID + system buffer):" - @PID=$$(adb -s $(ADB_SERIAL) shell pidof $(ANDROID_PACKAGE_NAME) 2>/dev/null | tr -d '\r\n'); \ - if [ -n "$$PID" ]; then \ - echo " ✅ (PID $$PID at dump time)"; \ - sleep 10; \ - adb -s $(ADB_SERIAL) logcat -d -v time -t $(LOGCAT_LINES) --pid=$$PID; \ - else \ - echo " 👿 (PostIt process not running at dump time — dumping last $(LOGCAT_LINES) lines unfiltered)"; \ - adb -s $(ADB_SERIAL) logcat -d -v time -t $(LOGCAT_LINES); \ - exit 1; \ - fi - -qemu: qemu-run qemu-wait-boot android-install - -.PHONY: clean qemu qemu-run qemu-stop qemu-wait-boot android-build android-install logcat qemu-logcat-boot diff --git a/src/PostIt/PostIt.Android/Application.cs b/src/PostIt/PostIt.Android/Application.cs index 040b01ca3..fb6b08d39 100644 --- a/src/PostIt/PostIt.Android/Application.cs +++ b/src/PostIt/PostIt.Android/Application.cs @@ -1,17 +1,7 @@ using Android.App; -using Android; using Android.Runtime; using Avalonia; using Avalonia.Android; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; -using Avalonia.Controls; -using Avalonia.Styling; -using Yavsc.Api.Client; - -[assembly: UsesPermission(Manifest.Permission.AccessFineLocation)] -[assembly: UsesPermission(Manifest.Permission.AccessCoarseLocation)] namespace PostIt.Android { diff --git a/src/PostIt/PostIt.Android/MainActivity.cs b/src/PostIt/PostIt.Android/MainActivity.cs index 549100804..86ce394a9 100644 --- a/src/PostIt/PostIt.Android/MainActivity.cs +++ b/src/PostIt/PostIt.Android/MainActivity.cs @@ -1,11 +1,8 @@ - using Android.App; -using Android.Content; using Android.Content.PM; -using AndroidX.Core.Provider; -using AndroidX.Emoji2.Text; +using Android.Content; +using Avalonia; using Avalonia.Android; -using PostIt.Droid.Services; namespace PostIt.Android; @@ -15,28 +12,26 @@ namespace PostIt.Android; Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, + LaunchMode = LaunchMode.SingleTask, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)] public class MainActivity : AvaloniaMainActivity { /// - /// The current MainActivity instance. + /// Strongly-typed handle to the current MainActivity instance, set in + /// and consumed by platform services such as + /// which need to launch + /// Chrome Custom Tabs. /// public static MainActivity? Current { get; private set; } protected override void OnCreate(global::Android.OS.Bundle? savedInstanceState) { - FontRequest fontRequest = new FontRequest( - "com.google.android.gms.fonts", - "com.google.android.gms", - "Noto Color Emoji Compat", - Yavsc.Resource.Array.com_google_android_gms_fonts_certs); //com_google_android_gms_fonts_certs - EmojiCompat.Config config = new FontRequestEmojiCompatConfig(this, fontRequest); - EmojiCompat.Init(config); - PlatformBootstrap.InitPlatform(); base.OnCreate(savedInstanceState); + PlatformBootstrap.EnsureInitialized(); Current = this; } - /// + + /// /// Receives the deep-link Intent fired by the system browser after the /// user completes the OIDC login on https://yavsc.pschneider.fr. The /// Intent URI has the shape android://postit-signin?code=...&state=.... @@ -48,24 +43,7 @@ public class MainActivity : AvaloniaMainActivity protected override void OnNewIntent(Intent? intent) { base.OnNewIntent(intent); - - var url = intent?.DataString; - if (!string.IsNullOrEmpty(url) && url.StartsWith("postit://callback")) - { - OidcCallbackManager.SetResult(url); - } - - } - - public override void OnRequestPermissionsResult(int requestCode, string[]? permissions, Permission[]? grantResults) - { - if (PostIt.Android.Services.AndroidCurrentLocationProvider - .HandlePermissionResult(requestCode, grantResults)) - { - return; - } - - base.OnRequestPermissionsResult(requestCode, permissions, grantResults); + if (intent is not null) AndroidOidcCallbackSink.Handle(intent); } internal static class AndroidOidcCallbackSink @@ -85,4 +63,4 @@ public class MainActivity : AvaloniaMainActivity tcs?.TrySetResult(intent?.Data?.ToString() ?? string.Empty); } } -} +} \ No newline at end of file diff --git a/src/PostIt/PostIt.Android/PlatformBootstrap.cs b/src/PostIt/PostIt.Android/PlatformBootstrap.cs index 5b90267f0..d59b154fb 100644 --- a/src/PostIt/PostIt.Android/PlatformBootstrap.cs +++ b/src/PostIt/PostIt.Android/PlatformBootstrap.cs @@ -12,14 +12,18 @@ namespace PostIt.Android; /// internal static class PlatformBootstrap { - internal static void InitPlatform() + private static int _initialized; + + internal static void EnsureInitialized() { + if (System.Threading.Interlocked.Exchange(ref _initialized, 1) != 0) + return; + + Platform.DefaultRedirectUri = ViewModels.Settings.AndroidRedirectUri; Platform.CreateBrowser = () => { var activity = MainActivity.Current; return activity is null ? null : new AndroidSystemBrowser(activity); }; - - Platform.TryGetCurrentLocationAsync = AndroidCurrentLocationProvider.TryGetCurrentLocationAsync; } } diff --git a/src/PostIt/PostIt.Android/PostIt.Android.csproj b/src/PostIt/PostIt.Android/PostIt.Android.csproj index 3d8be385c..b08143b4b 100644 --- a/src/PostIt/PostIt.Android/PostIt.Android.csproj +++ b/src/PostIt/PostIt.Android/PostIt.Android.csproj @@ -2,16 +2,19 @@ Exe net10.0-android - 23 + + android-arm64;android-x64 + 23.0.0 enable - fr.pschneider.postit + com.CompanyName.PostIt 1 1.0 apk false + android-arm;android-arm64;android-x86;android-x64 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070 + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 @@ -27,4 +30,7 @@ + + + \ No newline at end of file diff --git a/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml b/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml index 8793aae8a..2472d06d3 100644 --- a/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml +++ b/src/PostIt/PostIt.Android/Properties/AndroidManifest.xml @@ -1,6 +1,32 @@ - + - + + + + + + + + + + + - + \ No newline at end of file diff --git a/src/PostIt/PostIt.Android/Resources/values/font_certs.xml b/src/PostIt/PostIt.Android/Resources/values/font_certs.xml deleted file mode 100644 index f4adce1bd..000000000 --- a/src/PostIt/PostIt.Android/Resources/values/font_certs.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - @array/com_google_android_gms_fonts_certs_dev - @array/com_google_android_gms_fonts_certs_prod - - - MIIEqDCCA5CgAwIBAgIJAN5gc16AJfAsMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRAwDgYDVQQDEwdBbmRyb2lkMSEwHwYJKoZIhvcNAQkBFhJhbmRyb2lkQGFuZHJvaWQuY29tMCAXDTA4MDQxNTIyNDA0M1YYDzQyMDgxMzA0MjI0MDQzWjCBlDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDURvdW50YWluIFZpZXcxEDAOBgNVBAoTB0dvb2dsZTEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEhMB8GCSqGSIb3DQEJARYSYW5kcm9pZEBhbmRyb2lkLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALBi1vF0K1vOEHG7AxneTjOHUka46MIidBqvFcO164A49iU2DkYPhUaM4H8JCdzh6N1GzM6h9o6E2V6z8+gEtdI6nqqs0EGA0G0H701bFjLp9+K/1DkMIFeD4P8J7X1/M8t4+X09X/7bQyV3w0v7q+Qh38sY8W/7K29B3f2O2sLw+uX9U8a8Tf4Xv8A== - - - MIIEQzCCAyugAwIBAgIJAMLgh0ZgXpYOMA0GCSqGSIb3DQEBBQUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKKvSkUIXm+t9M8rXj2V - - diff --git a/src/PostIt/PostIt.Android/Services/AndroidCurrentLocationProvider.cs b/src/PostIt/PostIt.Android/Services/AndroidCurrentLocationProvider.cs deleted file mode 100644 index f710cacc7..000000000 --- a/src/PostIt/PostIt.Android/Services/AndroidCurrentLocationProvider.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Android; -using Android.App; -using Android.Content.PM; -using Android.Locations; -using AndroidX.Core.App; -using AndroidX.Core.Content; -using PostIt.Services; - -namespace PostIt.Android.Services; - -internal static class AndroidCurrentLocationProvider -{ - public static async Task TryGetCurrentLocationAsync(CancellationToken cancellationToken) - { - var activity = MainActivity.Current; - if (activity is null) - { - return CurrentLocationResult.Unavailable("L'activité Android n'est pas encore prête."); - } - - var permissionGranted = await LocationPermissionBroker.EnsureGrantedAsync(activity, cancellationToken).ConfigureAwait(false); - if (!permissionGranted) - { - return CurrentLocationResult.PermissionDenied(); - } - - var locationManager = activity.GetSystemService(global::Android.Content.Context.LocationService) as LocationManager; - if (locationManager is null) - { - return CurrentLocationResult.Unavailable("Le service de localisation Android est indisponible."); - } - - var location = locationManager.GetProviders(enabledOnly: true)? - .Select(provider => locationManager.GetLastKnownLocation(provider)) - .Where(candidate => candidate is not null) - .OrderByDescending(candidate => candidate!.Time) - .ThenBy(candidate => candidate!.Accuracy) - .FirstOrDefault(); - - if (location is null) - { - return CurrentLocationResult.Unavailable("Aucune position n'est disponible. Activez la localisation du système puis réessayez."); - } - - return CurrentLocationResult.Success(location.Latitude, location.Longitude); - } - - public static bool HandlePermissionResult(int requestCode, Permission[]? grantResults) - => LocationPermissionBroker.HandleResult(requestCode, grantResults); - - private static class LocationPermissionBroker - { - private const int RequestCode = 4042; - private static readonly string[] RequestedPermissions = - { - Manifest.Permission.AccessFineLocation, - Manifest.Permission.AccessCoarseLocation, - }; - - private static readonly object SyncRoot = new(); - private static TaskCompletionSource? _pendingRequest; - - public static Task EnsureGrantedAsync(Activity activity, CancellationToken cancellationToken) - { - if (HasLocationPermission(activity)) - { - return Task.FromResult(true); - } - - lock (SyncRoot) - { - if (_pendingRequest is null) - { - _pendingRequest = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - ActivityCompat.RequestPermissions(activity, RequestedPermissions, RequestCode); - } - - if (!cancellationToken.CanBeCanceled) - { - return _pendingRequest.Task; - } - - return WaitAsync(_pendingRequest.Task, cancellationToken); - } - } - - public static bool HandleResult(int requestCode, Permission[]? grantResults) - { - if (requestCode != RequestCode) - { - return false; - } - - var granted = grantResults is { Length: > 0 } && grantResults.All(result => result == Permission.Granted); - TaskCompletionSource? pendingRequest; - lock (SyncRoot) - { - pendingRequest = _pendingRequest; - _pendingRequest = null; - } - - pendingRequest?.TrySetResult(granted); - return true; - } - - private static bool HasLocationPermission(Activity activity) - { - return ContextCompat.CheckSelfPermission(activity, Manifest.Permission.AccessFineLocation) == Permission.Granted - || ContextCompat.CheckSelfPermission(activity, Manifest.Permission.AccessCoarseLocation) == Permission.Granted; - } - - private static async Task WaitAsync(Task task, CancellationToken cancellationToken) - { - using var registration = cancellationToken.Register(() => - { - lock (SyncRoot) - { - _pendingRequest?.TrySetCanceled(cancellationToken); - _pendingRequest = null; - } - }); - - return await task.ConfigureAwait(false); - } - } -} diff --git a/src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs b/src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs index bb10b364f..cb9c324b1 100644 --- a/src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs +++ b/src/PostIt/PostIt.Android/Services/AndroidSystemBrowser.cs @@ -1,9 +1,9 @@ using System; using System.Threading.Tasks; using Android.App; +using Android.Content; using AndroidX.Browser.CustomTabs; using IdentityModel.OidcClient.Browser; -using PostIt.Droid.Services; namespace PostIt.Android.Services; @@ -36,19 +36,14 @@ public sealed class AndroidSystemBrowser : IBrowser }; } - // 1. Enregistrez la tâche avant de lancer le Custom Tab - var callbackTask = OidcCallbackManager.RegisterCallback(cancellationToken); - - // 2. LANCEZ VOTRE CUSTOM TAB ICI (via AndroidX.Browser.CustomTabs) - // ... code pour ouvrir l'URL d'authentification ... - - var uri = global::Android.Net.Uri.Parse(options.StartUrl)!; + var callbackTask = MainActivity.AndroidOidcCallbackSink.AwaitNextCallbackAsync(); + var tabsIntent = new CustomTabsIntent.Builder() - .SetShowTitle(true)! + .SetShowTitle(true) .Build(); - tabsIntent!.LaunchUrl(_activity, uri); + tabsIntent.LaunchUrl(_activity, uri); string responseUri; try @@ -85,4 +80,4 @@ public sealed class AndroidSystemBrowser : IBrowser Response = responseUri }; } -} +} \ No newline at end of file diff --git a/src/PostIt/PostIt.Android/Services/OidcCallbackManager.cs b/src/PostIt/PostIt.Android/Services/OidcCallbackManager.cs deleted file mode 100644 index 30f8fa188..000000000 --- a/src/PostIt/PostIt.Android/Services/OidcCallbackManager.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace PostIt.Droid.Services; - -public static class OidcCallbackManager -{ - private static TaskCompletionSource? _tcs; - - public static Task RegisterCallback(CancellationToken cancellationToken) - { - _tcs = new TaskCompletionSource(); - cancellationToken.Register(() => _tcs.TrySetCanceled()); - return _tcs.Task; - } - - public static void SetResult(string url) - { - _tcs?.TrySetResult(url); - } -} diff --git a/src/PostIt/PostIt.Android/WebAuthenticationCallbackActivity.cs b/src/PostIt/PostIt.Android/WebAuthenticationCallbackActivity.cs deleted file mode 100644 index 9ed2eb186..000000000 --- a/src/PostIt/PostIt.Android/WebAuthenticationCallbackActivity.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Android.App; -using Android.Content; -using Android.Content.PM; -using Android.OS; -using PostIt.Droid.Services; - -namespace PostIt.Android; - -[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)] -[IntentFilter(new[] { Intent.ActionView }, - Categories = new[] { Intent.CategoryDefault, Intent.CategoryBrowsable }, - DataScheme = "postit", // Remplacez par votre schéma personnalisé (ex: yavsc ou postit) - DataHost = "callback")] // Correspond à postit://callback -public class WebAuthenticationCallbackActivity : Activity -{ - protected override void OnCreate(Bundle? savedInstanceState) - { - base.OnCreate(savedInstanceState); - - // Capturer l'URL de redirection OIDC - var url = Intent?.DataString; - - if (!string.IsNullOrEmpty(url)) - { - // Transmettre l'URL au gestionnaire partagé pour compléter la Task - OidcCallbackManager.SetResult(url); - } - - // Fermer cette activité transparente et ramener l'application au premier plan - var intent = new Intent(this, typeof(MainActivity)); - intent.AddFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop); - StartActivity(intent); - Finish(); - } -} diff --git a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj index 4534a2940..8643fcc6c 100644 --- a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj +++ b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj @@ -6,7 +6,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070 + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 @@ -15,4 +15,7 @@ + + + \ No newline at end of file diff --git a/src/PostIt/PostIt.Browser/Program.cs b/src/PostIt/PostIt.Browser/Program.cs index f91cc4eec..8700609d0 100644 --- a/src/PostIt/PostIt.Browser/Program.cs +++ b/src/PostIt/PostIt.Browser/Program.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Runtime.Versioning; +using System.Threading.Tasks; using Avalonia; using Avalonia.Browser; using PostIt; @@ -14,4 +15,4 @@ internal sealed partial class Program public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure(); -} +} \ No newline at end of file diff --git a/src/PostIt/PostIt.Desktop/PlatformBootstrap.cs b/src/PostIt/PostIt.Desktop/PlatformBootstrap.cs new file mode 100644 index 000000000..1563ec53e --- /dev/null +++ b/src/PostIt/PostIt.Desktop/PlatformBootstrap.cs @@ -0,0 +1,30 @@ +using IdentityModel.OidcClient.Browser; +using PostIt.Services; + +namespace PostIt.Desktop; + +/// +/// One-shot platform bootstrap. Called from Program.Main so that +/// the shared OIDC login path sees a working IBrowser — the +/// custom-scheme browser that hands the OIDC callback off to the +/// running instance through the named pipe. Desktop builds do NOT use +/// a loopback HTTP listener: the postit:// scheme is registered +/// with the OS at install time and the browser is whatever the user +/// has configured to open it. +/// +internal static class PlatformBootstrap +{ + private static int _initialized; + + internal static void EnsureInitialized() + { + if (System.Threading.Interlocked.Exchange(ref _initialized, 1) != 0) + return; + + // Use the custom-scheme redirect on Desktop. Loopback is only + // a fallback for platforms that cannot register postit:// + // (see Settings.DefaultLoopbackRedirectUri for that path). + Platform.DefaultRedirectUri = AuthenticationSettings.DefaultDesktopRedirectUri; + Platform.CustomScheme = "postit"; + } +} diff --git a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj index 948e726c5..5043da6e5 100644 --- a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj +++ b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj @@ -7,7 +7,7 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070 + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 @@ -24,4 +24,7 @@ + + + \ No newline at end of file diff --git a/src/PostIt/PostIt.Desktop/Program.cs b/src/PostIt/PostIt.Desktop/Program.cs index 0de3bd69e..23c4ef62d 100644 --- a/src/PostIt/PostIt.Desktop/Program.cs +++ b/src/PostIt/PostIt.Desktop/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using Avalonia; using PostIt.Services; @@ -12,6 +13,8 @@ sealed class Program [STAThread] public static void Main(string[] args) { + PlatformBootstrap.EnsureInitialized(); + // Short-circuit 2nd-instance launches (OS handing us the // postit://callback URL) BEFORE Avalonia spins up a window. // If we let Avalonia initialise, the new MainWindow flashes @@ -66,6 +69,9 @@ sealed class Program public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() +#if DEBUG + .WithDeveloperTools() +#endif .WithInterFont() .LogToTrace(); -} +} \ No newline at end of file diff --git a/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs b/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs deleted file mode 100644 index 6cac39603..000000000 --- a/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System.Net.Http; -using PostIt.ViewModels; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.Tests; - -public class ActivitiesPageViewModelTests -{ - [Fact] - public void ActivityApiClient_uses_avatar_authority_when_provided() - { - var api = new StubActivityApi(); - var client = new ActivityApiClient( - api, - "https://api.pschneider.fr/api/v1/", - "https://yavsc.pschneider.fr/"); - - var url = client.BuildAvatarXsUrl("paul"); - - Assert.Equal("https://yavsc.pschneider.fr/avatars/paul.xs.png", url); - } - - [Fact] - public async Task ActivityApiClient_uses_business_absolute_paths() - { - var api = new StubActivityApi(); - var client = new ActivityApiClient(api, "https://business.example/api/v1/"); - var billingClient = new BillingApiClient(api, "https://business.example/api/v1/"); - - await client.GetCatalogAsync("brush", TestContext.Current.CancellationToken); - await client.GetUsersAsync("brush-pro", TestContext.Current.CancellationToken); - await billingClient.CreateAsync("Rdv", new { Foo = "Bar" }, TestContext.Current.CancellationToken); - await billingClient.GetQuerySummariesAsync("Rdv", TestContext.Current.CancellationToken); - - Assert.Equal("https://business.example/api/v1/activity/catalog?parentCode=brush", api.Paths[0]); - Assert.Equal("https://business.example/api/v1/activity/brush-pro/users", api.Paths[1]); - Assert.Equal("https://business.example/api/v1/billing/Rdv", api.Paths[2]); - Assert.Equal("https://business.example/api/v1/billing/Rdv", api.Paths[3]); - } - - [Fact] - public async Task ActivityApiClient_uses_updated_business_base_without_restart() - { - var api = new StubActivityApi(); - var baseUrl = "https://business-a.example/api/v1/"; - var client = new ActivityApiClient(api, () => baseUrl); - - await client.GetCatalogAsync(ct: TestContext.Current.CancellationToken); - - baseUrl = "https://business-b.example/api/v1/"; - await client.GetUsersAsync("brush", TestContext.Current.CancellationToken); - - Assert.Equal("https://business-a.example/api/v1/activity/catalog", api.Paths[0]); - Assert.Equal("https://business-b.example/api/v1/activity/brush/users", api.Paths[1]); - } - - [Fact] - public async Task RefreshAsync_loads_first_activity_then_specialization_performers() - { - var api = new StubActivityApi(); - var client = new ActivityApiClient(api, "https://business.example/api/v1/"); - var billingClient = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new ActivitiesPageViewModel(client, billingClient); - - await vm.RefreshAsync(); - - Assert.Equal("brush", vm.SelectedActivity?.Code); - Assert.Single(vm.Specializations); - Assert.Equal("brush", vm.CurrentActivity?.Code); - Assert.Single(vm.Performers); - Assert.Equal("Alice", vm.Performers[0].UserName); - Assert.Equal("https://business.example/avatars/Alice.xs.png", vm.Performers[0].AvatarXsUrl); - Assert.True(vm.Performers[0].HasPerformerProfile); - Assert.True(vm.Performers[0].IsPerformerActive); - Assert.Equal("Actif", vm.Performers[0].PerformerStatusBadgeLabel); - Assert.Equal("Pas d'autre activité", vm.Performers[0].ExtraActivityLabel); - - await vm.ShowSpecializationAsync(vm.Specializations[0]); - - Assert.Equal("brush-pro", vm.CurrentActivity?.Code); - Assert.Single(vm.Performers); - Assert.Equal("Bob", vm.Performers[0].UserName); - Assert.Equal("https://business.example/avatars/Bob.xs.png", vm.Performers[0].AvatarXsUrl); - Assert.True(vm.Performers[0].HasPerformerProfile); - Assert.False(vm.Performers[0].IsPerformerActive); - Assert.Equal("Inactif", vm.Performers[0].PerformerStatusBadgeLabel); - Assert.Equal("Autres spécialisations: 2", vm.Performers[0].ExtraActivityLabel); - Assert.Contains("brush pro", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); - - await vm.ShowSpecializationAsync(null); - - Assert.Equal("brush", vm.CurrentActivity?.Code); - Assert.Single(vm.Performers); - Assert.Equal("Alice", vm.Performers[0].UserName); - } - - private sealed class StubActivityApi : IYavscApiClient - { - public HttpClient Http { get; } = new(); - public List Paths { get; } = new(); - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - Paths.Add(path); - - if (typeof(T) == typeof(List)) - { - var activities = new List - { - new() - { - Code = "brush", - Name = "Brush", - Description = "Coiffure à domicile", - PerformerCount = 1, - Forms = new List - { - new() { Id = 1, ActionName = "Rdv", Title = "Rendez-vous" } - }, - Children = new List - { - new() - { - Code = "brush-pro", - Name = "Brush Pro", - Description = "Spécialisation premium", - ParentCode = "brush", - PerformerCount = 1, - Forms = new List - { - new() { Id = 2, ActionName = "Rdv", Title = "Rendez-vous premium" } - } - } - } - } - }; - return Task.FromResult((T)(object)activities); - } - - if (typeof(T) == typeof(List)) - { - var performers = path.EndsWith("brush-pro/users", StringComparison.Ordinal) - ? new List - { - new() { PerformerId = "pro-2", HasPerformerProfile = true, Active = false, UserName = "Bob", ActivityCode = "brush-pro", ActivityName = "Brush Pro", ExtraActivityCount = 2 } - } - : new List - { - new() { PerformerId = "pro-1", HasPerformerProfile = true, Active = true, UserName = "Alice", ActivityCode = "brush", ActivityName = "Brush", ExtraActivityCount = 0 } - }; - - return Task.FromResult((T)(object)performers); - } - - return Task.FromResult(default(T)!); - } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - Paths.Add(path); - return Task.CompletedTask; - } - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => CallAsync(method, path, (object?)null, ct); - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => CallAsync(method, path, (object?)null, ct); - - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - } -} diff --git a/src/PostIt/PostIt.Tests/AddCircleMemberDialogTests.cs b/src/PostIt/PostIt.Tests/AddCircleMemberDialogTests.cs deleted file mode 100644 index 8bec1dc60..000000000 --- a/src/PostIt/PostIt.Tests/AddCircleMemberDialogTests.cs +++ /dev/null @@ -1,153 +0,0 @@ - -using Avalonia; -using Avalonia.Headless.XUnit; -using Microsoft.Extensions.DependencyInjection; -using PostIt.Helpers; -using PostIt.Services; -using PostIt.ViewModels; -using PostIt.Views; -using Yavsc.Api.Client; - -namespace PostIt.Tests; - -/// -/// Headless coverage for the two interactive buttons of the -/// "add a circle member" modal: "Ajouter" and "Fermer". -/// -/// The dialog is pushed on top of -/// via the canonical App.PushPageAsync pipeline (the -/// same path CirclesPageViewModel.OpenAddMemberAsync -/// uses). The test asserts on NavRoot.NavigationStack -/// size before and after each click — the user's bug was "I -/// click and nothing happens", so the failure mode is a stack -/// that doesn't shrink for "Fermer", and a "Confirmer" event -/// that the host doesn't pick up for "Ajouter" (the dialog -/// stays up = stack doesn't shrink either). -/// -/// Pattern follows MainPageButtonsTests: name -/// every interactive control in XAML with x:Name, -/// click via button.Command?.Execute(...) + flush -/// any async command before asserting. -/// -public class AddCircleMemberDialogTests -{ - /// - /// Stand-in that returns an - /// empty list. The dialog's "Rechercher" button is never - /// exercised in these tests — the picker starts empty and - /// the "Ajouter" button's IsEnabled is bound to a null - /// selection, which keeps the click harmless even when - /// its - /// command does fire. - /// - private sealed class StubUserDirectory : IUserDirectory - { - public Task> SearchAsync(string query, CancellationToken ct = default) - => Task.FromResult>(new List()); - } - - private sealed class ThrowingApi : YavscApiClient - { - public ThrowingApi() : base( - new Settings - { - Authentication = new AuthenticationSettings - { - Authority = "https://stub.invalid", - ClientId = "stub", - Scopes = new[] { "openid" }, - }, - }, - new TokenStore(System.IO.Path.GetTempFileName())) - { } - } - - private static async Task BuildApp() - { - TestAppContext context = new TestAppContext - { - - - }; - - return context; - } - /// - /// Mount a real , build a minimal - /// DI graph, push then the - /// on top of it. - /// Returns the stack size so the test can pin the delta. - /// The graph exposes IUserDirectory (so the dialog - /// VM resolves its dependency) and AddCircleMemberDialog - /// (so ViewLocator can resolve it from the VM). - /// - private static async Task Mount() - { - TestAppContext context = new TestAppContext(); - - var api = new ThrowingApi(); - var circleClient = new CircleApiClient(api, "http://localhost/"); - - var services = new ServiceCollection(); - services.AddSingleton(new Settings()); - services.AddSingleton(new StubUserDirectory()); - services.AddSingleton(circleClient); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - var sp = services.BuildServiceProvider(); - - context.Window = new MainView(); - context.App = (PostIt.App)Application.Current!; - context.App.AttachMainWindow(context.Window); - - context.page = sp.GetRequiredService(); - context.Window.NavRoot.PushAsync(context.page).GetAwaiter().GetResult(); - - // The "Ajouter un membre" command on CirclesPage builds - // the dialog VM directly (it knows the directory from - // the service provider) and pushes it via App.PushPage. - await context.App.PushPageAsync(sp.GetRequiredService()); - - context.dialog = context.Window.NavRoot.NavigationStack[^1] as AddCircleMemberDialog - ?? throw new System.InvalidOperationException("Dialog page not at top of stack."); - - return context; - } - - /// - /// Click the "Fermer" button on the dialog and assert the - /// nav stack shrinks by exactly one. - /// - [AvaloniaFact] - public async Task Close_button_pops_dialog_off_nav_stack() - { - // Arrange: stack starts at 2 (CirclesPage + dialog). - var context = await Mount(); - var window = context.Window!; - - var stackBefore = window.NavRoot.NavigationStack.Count; - Assert.Equal(2, stackBefore); - - // Act - var dialog = window.NavRoot.NavigationStack[^1] as AddCircleMemberDialog ?? throw new System.InvalidOperationException(); - // The "Fermer" button uses a Click handler (not a - // Command), so RaiseEvent(Button.ClickEvent) is the - // right way to fire it from headless code. Executing - // Command would no-op because no Command is bound. - - // FIXME Assert.NotNull(dialog.CloseButton): - // in order to click it by its def : - - // dialog.CloseButton.RaiseEvent(new Avalonia.Interactivity.RoutedEventArgs(Button.ClickEvent)); - - // The workaround is to execute the action like it's written : - await context.App!.GoBackAsync(); - - // Assert: stack -1, the top is the CirclesPage again. - Assert.True(window.NavRoot.NavigationStack.Count == stackBefore - 1, - $"Click on 'Fermer' must shrink the nav stack by one. Before: {stackBefore}, after: {window.NavRoot.NavigationStack.Count}."); - Assert.IsType(window.NavRoot.NavigationStack[^1]); - } -} diff --git a/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs b/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs deleted file mode 100644 index 25630c836..000000000 --- a/src/PostIt/PostIt.Tests/AndroidAppLaunchTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Diagnostics; -using Xamarin.UITest; - -namespace PostIt.Tests; - -/// -/// Smoke test: launches the installed PostIt.Android app on the running -/// emulator and waits for the first Avalonia frame to render. Reveals the -/// "démarrage KO" bug — the test fails if Avalonia never draws a frame -/// within the timeout. -/// -/// Skip conditions: the package is not installed on the connected device, -/// or no device is connected via adb. -/// -[Trait("Category", "Platform-Android")] -public class AndroidAppLaunchTests -{ - private const string PackageName = "fr.pschneider.postit"; - - private readonly ITestOutputHelper _output; - - public AndroidAppLaunchTests(ITestOutputHelper output) - { - _output = output; - } - - // TODO https://twosixtech.com/blog/integrating-docker-and-adb/ - [Fact] - public void PostIt_starts_and_draws_a_first_frame_on_the_emulator() - { - if (!IsPackageInstalledOnAnyDevice()) - { - _output.WriteLine($"[skip] {PackageName} not installed on any device"); - return; - } - - _output.WriteLine($"[step] configuring app via InstalledApp({PackageName})"); - var app = ConfigureApp.Android - .InstalledApp(PackageName) - .StartApp(Xamarin.UITest.Configuration.AppDataMode.DoNotClear); - _output.WriteLine("[step] app.StartApp returned, waiting for first frame"); - - app.WaitForElement( - e => e.Class("android.view.View"), - timeout: TimeSpan.FromSeconds(30)); - _output.WriteLine("[step] first frame observed"); - } - - private static bool IsPackageInstalledOnAnyDevice() - { - try - { - var startInfo = new ProcessStartInfo("adb", "shell pm list packages") - { - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - using var proc = Process.Start(startInfo); - if (proc is null) return false; - var stdout = proc.StandardOutput.ReadToEnd(); - proc.WaitForExit(5000); - return stdout - .Split('\n', StringSplitOptions.RemoveEmptyEntries) - .Any(line => line.Trim().Equals($"package:{PackageName}", StringComparison.Ordinal)); - } - catch - { - return false; - } - } -} diff --git a/src/PostIt/PostIt.Tests/BillingCommandPageViewModelTests.cs b/src/PostIt/PostIt.Tests/BillingCommandPageViewModelTests.cs deleted file mode 100644 index 34804b4cd..000000000 --- a/src/PostIt/PostIt.Tests/BillingCommandPageViewModelTests.cs +++ /dev/null @@ -1,363 +0,0 @@ -using System.Text.Json; -using PostIt.Helpers; -using PostIt.Services; -using PostIt.ViewModels; -using PostIt.ViewModels.Commands; -using Yavsc; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; -using Yavsc.Models.Haircut; - -namespace PostIt.Tests; - -public class BillingCommandPageViewModelTests -{ - [Fact] - public async Task SubmitAsync_posts_rdv_payload_to_selected_billing_route() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - - vm!.EventDate = DateTime.Parse("2026-09-02 14:30"); - vm!.Reason = "Point de cadrage"; - vm!.Address = "1 rue du Test"; - vm!.Latitude = 48.8566; - vm!.Longitude = 2.3522; - vm!.Consent = true; - - await vm.SubmitCommand.ExecuteAsync(null); - - Assert.Equal("https://business.example/api/v1/billing/Rdv", api.LastPath); - Assert.NotNull(api.LastBody); - - using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody)); - Assert.Equal("dev", json.RootElement.GetProperty("ActivityCode").GetString()); - Assert.Equal("perf-1", json.RootElement.GetProperty("PerformerId").GetString()); - Assert.Equal("Point de cadrage", json.RootElement.GetProperty("Reason").GetString()); - Assert.Equal((int)QueryStatus.Inserted, json.RootElement.GetProperty("Status").GetInt32()); - } - - [Fact] - public async Task SubmitAsync_refuses_unsupported_billing_code() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - - var vm = - new CommandFormSummary { Id = 13, ActionName = "Book", Title = "Réservation" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "book", Name = "Book" }, - new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" }, - client); - - Assert.Null(vm); - } - - [Fact] - public async Task SubmitAsync_allows_missing_coordinates_and_omits_them_from_payload() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - vm!.EventDate = DateTime.Parse("2026-09-02 14:30"); - vm!.Reason = "Point de cadrage"; - vm!.Address = "1 rue du Test"; - vm!.Latitude = null; - vm!.Longitude = null; - vm!.Consent = true; - - await vm.SubmitCommand.ExecuteAsync(null); - - using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody)); - var location = json.RootElement.GetProperty("Location"); - Assert.Equal("1 rue du Test", location.GetProperty("Address").GetString()); - Assert.False(location.TryGetProperty("Latitude", out _)); - Assert.False(location.TryGetProperty("Longitude", out _)); - } - - [Fact] - public async Task UseCurrentLocationAsync_prefills_coordinates_from_platform_provider() - { - var original = Platform.TryGetCurrentLocationAsync; - try - { - Platform.TryGetCurrentLocationAsync = _ => Task.FromResult(CurrentLocationResult.Success(48.8566, 2.3522)); - - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - - await vm!.UseCurrentLocationCommand.ExecuteAsync(null); - - Assert.Equal(48.8566, vm!.Latitude); - Assert.Equal(2.3522, vm!.Longitude); - } - finally - { - Platform.TryGetCurrentLocationAsync = original; - } - } - - [Fact] - public void ApplyLocationFromMap_sets_coordinates_and_updates_status() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - - vm!.Address = string.Empty; - vm.ApplyLocationFromMap(48.85661234, 2.35224567); - - Assert.Equal(48.856612, vm.Latitude); - Assert.Equal(2.352246, vm.Longitude); - Assert.Contains("Position sélectionnée", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void EventDateSelection_round_trips_with_EventDate_for_DatePicker_binding() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - - var selected = new DateTimeOffset(2026, 9, 7, 14, 30, 0, TimeSpan.FromHours(2)); - vm!.EventDateSelection = selected; - - Assert.Equal(selected.LocalDateTime, vm.EventDate); - Assert.Equal(vm.EventDate, vm.EventDateSelection!.Value.LocalDateTime); - } - - [Fact] - public void ApplyResolvedAddress_populates_empty_address_directly() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - - vm!.Address = string.Empty; - vm.ApplyResolvedAddress("10 rue de Rivoli, 75001 Paris"); - - Assert.Equal("10 rue de Rivoli, 75001 Paris", vm.Address); - Assert.False(vm.HasSuggestedAddress); - } - - [Fact] - public void ApplyResolvedAddress_preserves_manual_address_and_exposes_suggestion() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - client) as RdvViewModel; - - vm!.Address = "Saisie manuelle"; - vm.ApplyResolvedAddress("10 rue de Rivoli, 75001 Paris"); - - Assert.Equal("Saisie manuelle", vm.Address); - Assert.True(vm.HasSuggestedAddress); - Assert.Equal("10 rue de Rivoli, 75001 Paris", vm.SuggestedAddress); - - vm.ApplySuggestedAddressCommand.Execute(null); - - Assert.Equal("10 rue de Rivoli, 75001 Paris", vm.Address); - Assert.False(vm.HasSuggestedAddress); - } - - [Fact] - public async Task InitializeAsync_loads_prestations_for_brush_and_submit_posts_selected_prestation() - { - var api = new RecordingApi - { - HairPrestations = new List - { - new() { Id = 10, Title = "Femme · Cheveux mi-longs", Details = "Coupe · Brushing" }, - new() { Id = 11, Title = "Homme · Cheveux courts", Details = "Coupe · Coiffage" }, - } - }; - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 13, ActionName = "Brush", Title = "Coupe" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "brush", Name = "Brush" }, - new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" }, - client) as BrushViewModel; - vm!.EventDate = DateTime.Parse("2026-09-02 14:30"); - vm!.Address = "1 rue du Test"; - vm!.Latitude = 48.8566; - vm!.Longitude = 2.3522; - vm!.Consent = true; - vm!.AdditionalInfo = "Prévoir shampoing"; - - await vm.InitializeAsync(); - vm.SelectedPrestation = vm.AvailablePrestations[1]; - await vm.SubmitCommand.ExecuteAsync(null); - - Assert.Equal("https://business.example/api/v1/billing/Brush", api.LastPath); - using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody)); - Assert.Equal(11, json.RootElement.GetProperty("PrestationId").GetInt32()); - Assert.Equal("Prévoir shampoing", json.RootElement.GetProperty("AdditionalInfo").GetString()); - } - - [Fact] - public async Task InitializeAsync_loads_prestations_for_mbrush_and_submit_posts_selected_prestations() - { - var api = new RecordingApi - { - HairPrestations = new List - { - new() { Id = 21, Title = "Femme · Cheveux longs", Details = "Coupe · Couleur" }, - new() { Id = 22, Title = "Enfant · Cheveux courts", Details = "Coupe · Sans technique" }, - } - }; - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 14, ActionName = "MBrush", Title = "Coupe groupée" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "mbrush", Name = "MBrush" }, - new ActivityUserDisplayItem { PerformerId = "perf-3", UserName = "Cara" }, - client) as MBrushViewModel; - vm!.EventDate = DateTime.Parse("2026-09-03 10:00"); - vm!.Address = "2 rue du Test"; - vm!.Latitude = 48.8567; - vm!.Longitude = 2.3523; - vm!.Consent = true; - - await vm.InitializeAsync(); - vm!.MultiPrestations[0].IsSelected = true; - vm!.MultiPrestations[1].IsSelected = true; - await vm!.SubmitCommand.ExecuteAsync(null); - - Assert.Equal("https://business.example/api/v1/billing/MBrush", api.LastPath); - using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody)); - var prestations = json.RootElement.GetProperty("Prestations"); - Assert.Equal(2, prestations.GetArrayLength()); - Assert.Equal(21, prestations[0].GetProperty("PrestationId").GetInt32()); - Assert.Equal(22, prestations[1].GetProperty("PrestationId").GetInt32()); - } - - [Fact] - public async Task InitializeAsync_with_existing_brush_query_prefills_and_submit_updates_query() - { - var api = new RecordingApi - { - HairPrestations = new List - { - new() { Id = 30, Title = "Femme · Cheveux longs", Details = "Coupe · Brushing" }, - new() { Id = 31, Title = "Homme · Cheveux courts", Details = "Coupe" }, - } - }; - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = - new CommandFormSummary { Id = 13, ActionName = "Brush", Title = "Coupe" } - .CreateCommandPageViewModel( - new ActivityInfo { Code = "brush", Name = "Brush" }, - new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" }, - client) as BrushViewModel; - await vm!.InitializeAsync(new BillingQueryDetailsDto - { - Id = 77, - BillingCode = "Brush", - ActivityCode = "brush", - PerformerId = "perf-2", - ClientId = "cli-1", - EventDate = new DateTime(2026, 9, 2, 14, 30, 0, DateTimeKind.Utc), - Consent = true, - Status = QueryStatus.Accepted, - PrestationId = 30, - AdditionalInfo = "Ancienne note", - Location = new BillingLocationDto - { - Address = "1 rue du Test", - Latitude = 48.8566, - Longitude = 2.3522, - } - }); - - vm!.SelectedPrestation = vm!.AvailablePrestations[1]; - vm!.AdditionalInfo = "Note mise à jour"; - await vm!.SubmitCommand.ExecuteAsync(null); - - Assert.Equal(HttpMethod.Put, api.LastMethod); - Assert.Equal("https://business.example/api/v1/billing/Brush/77", api.LastPath); - Assert.True(vm.IsEditingExisting); - Assert.Equal("Mettre à jour la commande", vm.SubmitLabel); - - using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody)); - Assert.Equal(77, json.RootElement.GetProperty("Id").GetInt32()); - Assert.Equal(31, json.RootElement.GetProperty("PrestationId").GetInt32()); - Assert.Equal("Note mise à jour", json.RootElement.GetProperty("AdditionalInfo").GetString()); - Assert.Equal((int)QueryStatus.Accepted, json.RootElement.GetProperty("Status").GetInt32()); - } - - private sealed class RecordingApi : IYavscApiClient - { - public HttpClient Http { get; } = new(); - public HttpMethod? LastMethod { get; private set; } - public string? LastPath { get; private set; } - public object? LastBody { get; private set; } - public List? HairPrestations { get; init; } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - LastMethod = method; - LastPath = path; - LastBody = body; - if (typeof(T) == typeof(List)) - { - return Task.FromResult((T)(object)(HairPrestations ?? new List())); - } - return Task.FromResult(default(T)!); - } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - LastMethod = method; - LastPath = path; - LastBody = body; - return Task.CompletedTask; - } - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => CallAsync(method, path, (object?)null, ct); - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => CallAsync(method, path, (object?)null, ct); - - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - } -} diff --git a/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs b/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs deleted file mode 100644 index e53139351..000000000 --- a/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System.Net.Http; -using PostIt.ViewModels; -using Yavsc; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.Tests; - -public class BillingQueriesPageViewModelTests -{ - [Fact] - public async Task RefreshAsync_filters_queries_by_selected_activity_and_performer() - { - var api = new StubBillingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new BillingQueriesPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - new CommandFormSummary { Id = 1, ActionName = "Rdv", Title = "Rendez-vous" }, - client); - - await vm.InitializeAsync(); - - Assert.Equal("https://business.example/api/v1/billing/Rdv", api.Paths.Single()); - Assert.Equal(3, vm.Queries.Count); - Assert.Contains(vm.Queries, q => q.Description == "Rendez-vous #1"); - Assert.Contains("3 commande", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public async Task RefreshAsync_in_readonly_ongoing_mode_keeps_only_ongoing_statuses_and_disables_open() - { - var api = new StubBillingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new BillingQueriesPageViewModel( - new ActivityInfo { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - new CommandFormSummary { Id = 1, ActionName = "Rdv", Title = "Rendez-vous" }, - client, - isReadOnly: true, - ongoingOnly: true); - - await vm.InitializeAsync(); - - Assert.Equal(2, vm.Queries.Count); - Assert.All(vm.Queries, q => Assert.DoesNotContain("Rejected", q.StatusLabel, StringComparison.OrdinalIgnoreCase)); - Assert.Contains("lecture seule", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); - - Assert.True(vm.Queries.Count > 0); - } - - private sealed class StubBillingApi : IYavscApiClient - { - public HttpClient Http { get; } = new(); - public List Paths { get; } = new(); - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - Paths.Add(path); - - if (typeof(T) == typeof(List)) - { - var data = new List - { - new() - { - Id = 11, - ActivityCode = "dev", - PerformerId = "perf-1", - ClientId = "cli-1", - Status = QueryStatus.Inserted, - Description = "Rendez-vous #1", - Reason = "Point de cadrage", - EventDate = new DateTime(2026, 9, 1, 10, 0, 0, DateTimeKind.Utc), - }, - new() - { - Id = 12, - ActivityCode = "other", - PerformerId = "perf-1", - ClientId = "cli-1", - Status = QueryStatus.Accepted, - Description = "Autre activité", - EventDate = new DateTime(2026, 9, 2, 10, 0, 0, DateTimeKind.Utc), - }, - new() - { - Id = 13, - ActivityCode = "dev", - PerformerId = "perf-2", - ClientId = "cli-1", - Status = QueryStatus.Accepted, - Description = "Autre performer", - EventDate = new DateTime(2026, 9, 3, 10, 0, 0, DateTimeKind.Utc), - }, - new() - { - Id = 14, - ActivityCode = "dev", - PerformerId = "perf-1", - ClientId = "cli-1", - Status = QueryStatus.InProgress, - Description = "En cours", - EventDate = new DateTime(2026, 9, 4, 10, 0, 0, DateTimeKind.Utc), - }, - new() - { - Id = 15, - ActivityCode = "dev", - PerformerId = "perf-1", - ClientId = "cli-1", - Status = QueryStatus.Rejected, - Description = "Rejetée", - EventDate = new DateTime(2026, 9, 5, 10, 0, 0, DateTimeKind.Utc), - } - }; - - return Task.FromResult((T)(object)data); - } - - return Task.FromResult(default(T)!); - } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - Paths.Add(path); - return Task.CompletedTask; - } - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => CallAsync(method, path, (object?)null, ct); - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => CallAsync(method, path, (object?)null, ct); - - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - } -} diff --git a/src/PostIt/PostIt.Tests/BlogPostAuthorDtoTests.cs b/src/PostIt/PostIt.Tests/BlogPostAuthorDtoTests.cs deleted file mode 100644 index daabdf59b..000000000 --- a/src/PostIt/PostIt.Tests/BlogPostAuthorDtoTests.cs +++ /dev/null @@ -1,216 +0,0 @@ -using System.Text.Json; -using Yavsc.Blogspot; - -namespace PostIt.Tests; - -/// -/// Round-trip tests for the wire shape of a blog post as -/// serialised by Yavsc.Blogs and consumed by PostIt. -/// -/// -/// Background: in 1.0.7, BlogPostDto.Author was typed as -/// the abstract interface IApplicationUser. System.Text.Json -/// cannot materialise an interface without a polymorphic -/// converter, so the "load posts" call from PostIt crashed when -/// the server returned a post with a populated Author -/// object. The fix replaced IApplicationUser with a thin -/// concrete DTO, BlogPostAuthorDto, embedded directly in -/// BlogPostDto.Author. -/// -/// -/// -/// These tests pin the wire shape: a JSON document with an -/// Author object must deserialise without throwing and -/// must round-trip the three fields PostIt exposes in the UI -/// (Id, UserName, Avatar). They are intentionally placed in -/// PostIt.Tests — the client-side assembly — so the -/// regression is caught at the deserialisation boundary, where -/// it actually manifested in production. -/// -/// -public class BlogPostAuthorDtoTests -{ - private static readonly JsonSerializerOptions CaseInsensitiveJson - = new() { PropertyNameCaseInsensitive = true }; - - [Fact] - public void BlogPostDto_deserialises_with_populated_author() - { - // A representative JSON shape the server would emit for - // GET /api/BlogApi. The Author object is fully populated - // — that's the shape that used to break deserialisation - // when Author was typed as the abstract IApplicationUser - // interface. - var json = """ - { - "id": 42, - "title": "Premier billet", - "article": "Contenu", - "photo": null, - "dateCreated": "2026-08-01T12:00:00Z", - "dateModified": "2026-08-02T12:00:00Z", - "userCreated": "alice", - "userModified": "alice", - "authorId": "u-alice", - "isPublished": true, - "author": { - "id": "u-alice", - "userName": "alice", - "avatar": "/avatars/alice.png" - } - } - """; - - var post = JsonSerializer.Deserialize(json, CaseInsensitiveJson); - - Assert.NotNull(post); - Assert.Equal(42, post!.Id); - Assert.Equal("Premier billet", post.Title); - Assert.Equal("u-alice", post.AuthorId); - Assert.True(post.IsPublished); - - // The actual regression coverage: Author must - // materialise as a concrete DTO, not be left null because - // of a JsonException on IApplicationUser. - Assert.NotNull(post.Author); - Assert.Equal("u-alice", post.Author!.Id); - Assert.Equal("alice", post.Author.UserName); - Assert.Equal("/avatars/alice.png", post.Author.Avatar); - } - - [Fact] - public void BlogPostDto_deserialises_when_author_is_null() - { - // The server is allowed to omit Author (the field is - // nullable on the wire — it maps to a navigation - // property that may not have been Included). The client - // must accept that shape without throwing. - var json = """ - { - "id": 7, - "title": "Sans auteur", - "article": null, - "photo": null, - "dateCreated": "2026-08-01T12:00:00Z", - "dateModified": "2026-08-01T12:00:00Z", - "userCreated": "system", - "userModified": "system", - "authorId": "system", - "isPublished": false, - "author": null - } - """; - - var post = JsonSerializer.Deserialize(json, CaseInsensitiveJson); - - Assert.NotNull(post); - Assert.Null(post!.Author); - Assert.Equal("system", post.AuthorId); - } - - [Fact] - public void BlogPostDto_deserialises_when_author_field_is_missing() - { - // Forward-compatibility: an older server that doesn't - // emit the Author field at all. Should not throw. - var json = """ - { - "id": 9, - "title": "Ancien format", - "article": "Pas d'auteur dans la charge utile", - "photo": null, - "dateCreated": "2026-07-01T12:00:00Z", - "dateModified": "2026-07-01T12:00:00Z", - "userCreated": "bob", - "userModified": "bob", - "authorId": "u-bob", - "isPublished": true - } - """; - - var post = JsonSerializer.Deserialize(json, CaseInsensitiveJson); - - Assert.NotNull(post); - Assert.Null(post!.Author); - } - - [Fact] - public void BlogPostAuthorDto_serialises_back_to_expected_json_shape() - { - // Pin the wire shape on the way out too. The server - // builds BlogPostAuthorDto from an ApplicationUser and - // PostIt receives it as JSON; if the field names - // change (e.g. case) the round-trip on the client side - // is what would silently break. - // - // The server emits camelCase (ASP.NET Core's Web - // defaults — PropertyNamingPolicy = CamelCase). We - // mirror that here so the test reflects what the wire - // actually looks like. PropertyNameCaseInsensitive on - // the client deserialiser means we don't have to - // hardcode the casing for the inbound assertions. - var author = new BlogPostAuthorDto - { - Id = "u-alice", - UserName = "alice", - Avatar = "/avatars/alice.png" - }; - - var json = JsonSerializer.Serialize(author, - new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - - using var doc = JsonDocument.Parse(json); - var root = doc.RootElement; - - Assert.True(root.TryGetProperty("id", out _)); - Assert.True(root.TryGetProperty("userName", out _)); - Assert.True(root.TryGetProperty("avatar", out _)); - } - - [Fact] - public void BlogPostDto_deserialises_acl_from_detail_payload() - { - // Detail payload shape emitted by BlogApiController.GetBlog: - // ACL entries are included under "acl"/"ACL". - var json = """ - { - "id": 99, - "title": "ACL test", - "authorId": "u-alice", - "acl": [ - { "circleId": 12, "blogPostId": 99 }, - { "circleId": 34, "blogPostId": 99 } - ] - } - """; - - var post = JsonSerializer.Deserialize(json, CaseInsensitiveJson); - - Assert.NotNull(post); - var acl = post!.GetACL(); - Assert.Equal(2, acl.Length); - Assert.Contains(acl, a => a.CircleId == 12); - Assert.Contains(acl, a => a.CircleId == 34); - } - - [Fact] - public void BlogPostDto_does_not_emit_acl_when_serialized_for_write() - { - var post = new BlogPostDto - { - Id = 77, - Title = "Write payload" - }; - post.AuthorizeCircle(11); - - // The client should not send ACL through POST/PUT blog payloads. - // ACL mutations have their own dedicated /blogacl endpoint. - var json = JsonSerializer.Serialize(post, - new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - - using var doc = JsonDocument.Parse(json); - var root = doc.RootElement; - Assert.False(root.TryGetProperty("acl", out _)); - Assert.False(root.TryGetProperty("wireAcl", out _)); - } -} diff --git a/src/PostIt/PostIt.Tests/EstimateEditionPageViewModelTests.cs b/src/PostIt/PostIt.Tests/EstimateEditionPageViewModelTests.cs deleted file mode 100644 index db70e25fa..000000000 --- a/src/PostIt/PostIt.Tests/EstimateEditionPageViewModelTests.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System.Net.Http; -using PostIt.ViewModels; -using Yavsc; -using Yavsc.Api.Client; - -namespace PostIt.Tests; - -public class EstimateEditionPageViewModelTests -{ - private static BillingQuerySummaryDto SampleQuery() => new() - { - Id = 42, - BillingCode = "Brush", - ActivityCode = "hair", - PerformerId = "perf-1", - ClientId = "cli-1", - Status = QueryStatus.InProgress, - Description = "Coupe simple", - EventDate = new DateTime(2026, 9, 12, 10, 0, 0, DateTimeKind.Utc), - }; - - private static EstimateEditionPageViewModel CreateViewModel(StubEstimateApi api, BillingQuerySummaryDto? query = null) - { - var client = new EstimateApiClient(api, "https://business.example/api/v1/"); - return new EstimateEditionPageViewModel(query ?? SampleQuery(), client); - } - - [Fact] - public void Constructor_prefills_description_and_adds_a_first_line() - { - var api = new StubEstimateApi(); - var vm = CreateViewModel(api); - - Assert.Equal("Coupe simple", vm.EstimateDescription); - Assert.Single(vm.Lines); - Assert.Same(vm.Lines[0], vm.SelectedLine); - Assert.Contains("#42", vm.ContextLabel); - Assert.Contains("cli-1", vm.ContextLabel); - } - - [Fact] - public void AddLine_appends_and_selects_the_new_line() - { - var api = new StubEstimateApi(); - var vm = CreateViewModel(api); - - vm.AddLineCommand.Execute(null); - - Assert.Equal(2, vm.Lines.Count); - Assert.Same(vm.Lines[1], vm.SelectedLine); - } - - [Fact] - public void RemoveLine_removes_the_selected_line() - { - var api = new StubEstimateApi(); - var vm = CreateViewModel(api); - var first = vm.Lines[0]; - - vm.RemoveLineCommand.Execute(null); - - Assert.Empty(vm.Lines); - Assert.Null(vm.SelectedLine); - Assert.False(vm.RemoveLineCommand.CanExecute(null)); - Assert.DoesNotContain(first, vm.Lines); - } - - [Fact] - public void Total_sums_line_totals_and_tracks_edits() - { - var api = new StubEstimateApi(); - var vm = CreateViewModel(api); - - vm.Lines[0].Count = 2; - vm.Lines[0].UnitaryCost = 15.5m; - - Assert.Equal(31m, vm.Total); - Assert.Equal($"{31m:0.00} EUR", vm.TotalLabel); - - vm.AddLineCommand.Execute(null); - vm.Lines[1].Count = 1; - vm.Lines[1].UnitaryCost = 9m; - - Assert.Equal(40m, vm.Total); - } - - [Fact] - public async Task Send_without_title_warns_and_does_not_post() - { - var api = new StubEstimateApi(); - var vm = CreateViewModel(api); - vm.Lines[0].Name = "Coupe"; - vm.Lines[0].Description = "Coupe simple"; - vm.Lines[0].UnitaryCost = 25m; - - await vm.SendCommand.ExecuteAsync(null); - - Assert.Null(api.LastBody); - Assert.Equal(StatusSeverity.Warning, vm.ActionStatus.Severity); - Assert.Contains("titre", vm.ActionStatus.Message); - } - - [Fact] - public async Task Send_without_any_line_warns_and_does_not_post() - { - var api = new StubEstimateApi(); - var vm = CreateViewModel(api); - vm.EstimateTitle = "Devis coupe"; - vm.Lines.Clear(); - - await vm.SendCommand.ExecuteAsync(null); - - Assert.Null(api.LastBody); - Assert.Equal(StatusSeverity.Warning, vm.ActionStatus.Severity); - Assert.Contains("ligne", vm.ActionStatus.Message); - } - - [Fact] - public async Task Send_with_a_blank_line_name_warns_and_does_not_post() - { - var api = new StubEstimateApi(); - var vm = CreateViewModel(api); - vm.EstimateTitle = "Devis coupe"; - vm.Lines[0].Description = "Oubli du nom"; - - await vm.SendCommand.ExecuteAsync(null); - - Assert.Null(api.LastBody); - Assert.Equal(StatusSeverity.Warning, vm.ActionStatus.Severity); - Assert.Contains("nom", vm.ActionStatus.Message); - } - - [Fact] - public async Task Send_posts_the_estimate_payload_to_the_estimate_route() - { - var api = new StubEstimateApi(); - var vm = CreateViewModel(api); - vm.EstimateTitle = " Devis coupe "; - vm.Lines[0].Name = "Coupe"; - vm.Lines[0].Description = "Coupe simple"; - vm.Lines[0].Count = 2.4m; - vm.Lines[0].UnitaryCost = 25m; - - await vm.SendCommand.ExecuteAsync(null); - - Assert.Equal("https://business.example/api/v1/estimate", api.LastPath); - Assert.Equal(HttpMethod.Post, api.LastMethod); - - var payload = Assert.IsType(api.LastBody); - Assert.Equal(42, payload.CommandId); - Assert.Equal("cli-1", payload.ClientId); - Assert.Equal("Brush", payload.CommandType); - Assert.Equal("Devis coupe", payload.Title); - Assert.Equal("Coupe simple", payload.Description); - Assert.Empty(payload.AttachedFiles); - Assert.Empty(payload.AttachedGraphics); - - var line = Assert.Single(payload.Bill); - Assert.Equal("Coupe", line.Name); - Assert.Equal(2, line.Count); - Assert.Equal(25m, line.UnitaryCost); - Assert.Equal("EUR", line.Currency); - } - - [Fact] - public async Task Send_marks_the_page_as_sent_and_disables_resend() - { - var api = new StubEstimateApi(); - var vm = CreateViewModel(api); - vm.EstimateTitle = "Devis coupe"; - vm.Lines[0].Name = "Coupe"; - vm.Lines[0].Description = "Coupe simple"; - vm.Lines[0].UnitaryCost = 25m; - - await vm.SendCommand.ExecuteAsync(null); - - Assert.True(vm.HasSent); - Assert.False(vm.SendCommand.CanExecute(null)); - Assert.Equal("Devis envoyé", vm.SendLabel); - Assert.Equal(StatusSeverity.Info, vm.ActionStatus.Severity); - Assert.Contains("#7", vm.ActionStatus.Message); - } - - [Fact] - public async Task Send_surfaces_server_errors_as_error_status() - { - var api = new StubEstimateApi { Failure = new HttpRequestException("boom", null, System.Net.HttpStatusCode.InternalServerError) }; - var vm = CreateViewModel(api); - vm.EstimateTitle = "Devis coupe"; - vm.Lines[0].Name = "Coupe"; - vm.Lines[0].Description = "Coupe simple"; - - await vm.SendCommand.ExecuteAsync(null); - - Assert.False(vm.HasSent); - Assert.Equal(StatusSeverity.Error, vm.ActionStatus.Severity); - Assert.True(vm.SendCommand.CanExecute(null)); - } - - [Fact] - public async Task Send_accepts_negative_amounts_for_discount_lines() - { - var api = new StubEstimateApi(); - var vm = CreateViewModel(api); - vm.EstimateTitle = "Devis avec remise"; - vm.Lines[0].Name = "Coupe"; - vm.Lines[0].Description = "Coupe simple"; - vm.Lines[0].UnitaryCost = 25m; - - vm.AddLineCommand.Execute(null); - vm.Lines[1].Name = "Remise fidélité"; - vm.Lines[1].Description = "Remise client régulier"; - vm.Lines[1].UnitaryCost = -5m; - - Assert.Equal(20m, vm.Total); - - await vm.SendCommand.ExecuteAsync(null); - - var payload = Assert.IsType(api.LastBody); - Assert.Equal(2, payload.Bill.Count); - Assert.Equal(-5m, payload.Bill[1].UnitaryCost); - Assert.True(vm.HasSent); - } - - private sealed class StubEstimateApi : IYavscApiClient - { - public HttpClient Http { get; } = new(); - public string? LastPath { get; private set; } - public HttpMethod? LastMethod { get; private set; } - public object? LastBody { get; private set; } - public Exception? Failure { get; init; } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - LastMethod = method; - LastPath = path; - LastBody = body; - - if (Failure is not null) - { - throw Failure; - } - - if (typeof(T) == typeof(EstimateCreatedDto)) - { - var payload = (EstimateDto)body!; - var created = new EstimateCreatedDto { Id = 7, Bill = payload.Bill }; - return Task.FromResult((T)(object)created); - } - - return Task.FromResult(default(T)!); - } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - LastMethod = method; - LastPath = path; - LastBody = body; - return Task.CompletedTask; - } - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => CallAsync(method, path, (object?)null, ct); - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => CallAsync(method, path, (object?)null, ct); - - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - } -} diff --git a/src/PostIt/PostIt.Tests/HomePageProviderFlowTests.cs b/src/PostIt/PostIt.Tests/HomePageProviderFlowTests.cs deleted file mode 100644 index 16f01c5e9..000000000 --- a/src/PostIt/PostIt.Tests/HomePageProviderFlowTests.cs +++ /dev/null @@ -1,15 +0,0 @@ -using PostIt.ViewModels; - -namespace PostIt.Tests; - -public class HomePageProviderFlowTests -{ - [Fact] - public void HomePage_exposes_provider_requests_command() - { - var vm = new HomePageViewModel(); - - Assert.NotNull(vm.OpenProviderRequests); - Assert.True(vm.OpenProviderRequests.CanExecute(null)); - } -} diff --git a/src/PostIt/PostIt.Tests/MainPageButtonsTests.cs b/src/PostIt/PostIt.Tests/MainPageButtonsTests.cs deleted file mode 100644 index f72c8d4d5..000000000 --- a/src/PostIt/PostIt.Tests/MainPageButtonsTests.cs +++ /dev/null @@ -1,236 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Headless.XUnit; -using CommunityToolkit.Mvvm.Input; -using Microsoft.Extensions.DependencyInjection; -using Yavsc.Api.Client; -using Yavsc.Blogspot; -using PostIt.Services; -using PostIt.ViewModels; -using PostIt.Views; -using PostIt.Views.Blogs; -using PostIt.Helpers; - -namespace PostIt.Tests; - -/// -/// Regression coverage for the three toolbar buttons on -/// that the user reported as inoperative: -/// "ACL", "Mes cercles", and "[DEV] Signature". -/// -/// Pattern (per the Avalonia headless testing docs — -/// TestableApp.Headless.XUnit/CalculatorTests): name every -/// interactive control in the XAML with x:Name="...", then -/// in the test focus the named control and raise the click via -/// window.KeyPressQwerty(PhysicalKey.Enter, ...). This is -/// the supported path — searching the visual tree via -/// GetVisualDescendants().OfType<Button>() for a -/// button by Content text is brittle and was tried first; it does -/// not work reliably when the page is hosted inside an -/// , which wraps the -/// pushed page in an internal container that the visual-tree walk -/// does not always expose under headless. -/// -/// The assertion is on the post-click top of -/// : -/// the user's bug is "I click and the dialog / page never opens", -/// so the test fails when the click doesn't push anything onto the -/// stack. We pin γ + sniff léger — the new top must be a non-null -/// , but we do not yet assert the concrete type -/// (that would require a fully stubbed App.ServiceProvider, -/// which is the next iteration of this suite). -/// -/// Each test exercises the bit that would silently break if -/// the wiring was reverted: -/// -/// "ACL" — click with a selected post pushes a page onto -/// the stack. -/// "Mes cercles" — click pushes a page onto the stack. -/// "[DEV] Signature" — click pushes a page onto the -/// stack. -/// -/// -public class MainPageButtonsTests -{ - /// - /// Fake that throws on any - /// wire call. These tests never invoke a command that hits - /// the API — only the click → nav side of the pipeline is - /// asserted. - /// - private sealed class ThrowingApi : YavscApiClient - { - public ThrowingApi() : base( - new Settings - { - Authentication = new AuthenticationSettings - { - Authority = "https://stub.invalid", - ClientId = "stub", - Scopes = new[] { "openid" }, - }, - }, - new TokenStore(System.IO.Path.GetTempFileName())) - { } - } - - private static BlogsViewModel MakeViewModel(BlogPostDto? selectedPost = null) - { - var api = new ThrowingApi(); - var blog = new BlogApiClient(api, "http://localhost/"); - var circle = new CircleApiClient(api, "http://localhost/"); - var acl = new BlogAclApiClient(api, "http://localhost/"); - // Minimal DI graph: only what BlogsViewModel resolves - // when the user clicks a navigation button. Today that's - // SignaturePageViewModel / CirclesPageViewModel / ACL - // dependencies. The graph intentionally stays local to this - // suite to avoid side effects from App.BuildServices() (real - // token-store wiring). - var services = new ServiceCollection(); - services.AddSingleton(new Settings()); - services.AddSingleton(circle); - services.AddSingleton(acl); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - var vm = new BlogsViewModel(blog, services: services.BuildServiceProvider()); - if (selectedPost is not null) vm.SelectedPost = selectedPost; - return vm; - } - - /// - /// Mount a real (as - /// SessionStatusBannerTests does), push a - /// with the given VM onto - /// NavRoot. PushAsync is awaited (via - /// GetAwaiter().GetResult()) so the page is on the - /// nav stack before the test tries to interact with its - /// named buttons. The window is shown so the visual tree is - /// realised and KeyPressQwerty has a real - /// to dispatch against. - /// - private static (MainView window, BlogsPage page) MountMainPage(BlogsViewModel vm) - { - var window = new MainView(); - var page = new BlogsPage { DataContext = vm }; - var app = (PostIt.App)Application.Current!; - - app.AttachMainWindow(window); - - window.NavRoot.PushAsync(page).GetAwaiter().GetResult(); - var mainWindow = new Window { Content = window }; - mainWindow.Show(); - return (window, page); - } - - /// - /// Click a button by focusing it and pressing Enter — the - /// supported headless pattern (cf. CalculatorTests in the - /// Avalonia.Samples repo). Returns the nav-stack count - /// before the click so the caller can assert on the delta. - /// KeyPressQwerty is dispatched on the - /// itself — it is the that owns the - /// headless implementation, and routing the key through any - /// descendant TopLevel (e.g. one obtained via - /// TopLevel.GetTopLevel(button)) fails with a - /// NullReferenceException from the headless impl - /// because the descendant does not carry the - /// PlatformHandle the harness expects. - /// - private static int ClickAndCapture(MainView window, Button button) - { - var stackBefore = window.NavRoot.NavigationStack.Count; - button.Command?.Execute(button.CommandParameter); - if (button.Command is IAsyncRelayCommand asyncCommand) - { - asyncCommand.ExecutionTask?.GetAwaiter().GetResult(); - } - return stackBefore; - } - - [AvaloniaFact] - public async Task Acl_button_click_pushes_a_page_onto_nav_stack() - { - // Arrange: a VM whose SelectedPost is non-null so - // CanManageAcl evaluates to true and the button is - // armed. - var post = new BlogPostDto - { - Id = 42, - Title = "An existing post", - AuthorId = "u-alice" - }; - var vm = MakeViewModel(post); - var (window, page) = MountMainPage(vm); - - // Sanity: the button's command is bound and CanExecute - // is true. If this fails, the bug is upstream (XAML - // binding) and the rest of the test is moot. - var aclButton = page.ManageAclButton; - Assert.NotNull(aclButton.Command); - Assert.True(aclButton.Command.CanExecute(null)); - - // Act - var stackBefore = ClickAndCapture(window, aclButton); - - // Assert γ + sniff léger: stack grew, new top is a Page. - Assert.True(window.NavRoot.NavigationStack.Count > stackBefore, - $"Click on ACL must push a new page onto the nav stack. Stack size before: {stackBefore}, after: {window.NavRoot.NavigationStack.Count}."); - var pushed = window.NavRoot.NavigationStack.Last(); - Assert.NotNull(pushed); - Assert.IsAssignableFrom(pushed); - } - - [AvaloniaFact] - public async Task Circles_button_click_pushes_a_page_onto_nav_stack() - { - // Arrange: OpenCircles has no CanExecute guard today — - // any click should fire it and push the page. - var vm = MakeViewModel(); - var (window, page) = MountMainPage(vm); - - var circlesButton = page.OpenCirclesButton; - Assert.NotNull(circlesButton.Command); - - // Act - var stackBefore = ClickAndCapture(window, circlesButton); - - // Assert - Assert.True(window.NavRoot.NavigationStack.Count > stackBefore, - "Click on 'Mes cercles' must push a new page onto the nav stack."); - var pushed = window.NavRoot.NavigationStack.Last(); - Assert.NotNull(pushed); - Assert.IsAssignableFrom(pushed); - } - - [AvaloniaFact] - public void Signature_dev_button_click_pushes_a_page_onto_nav_stack() - { - // Arrange: the "[DEV] Signature" button is bound to the - // BlogsViewModel.OpenSignatureDevCommand [RelayCommand]. - // The click must push SignaturePage on top of NavRoot. - // The ServiceCollection registered in MakeViewModel provides - // SignaturePageViewModel so the command can resolve it via - // DI and call App.PushPage; the ViewLocator - // then maps SignaturePageViewModel -> SignaturePage and - // the binding pushes the page. - var vm = MakeViewModel(); - var (window, page) = MountMainPage(vm); - - var signatureButton = page.OpenSignatureDevButton; - Assert.NotNull(signatureButton.Command); - Assert.True(signatureButton.Command.CanExecute(null)); - - // Act - var stackBefore = ClickAndCapture(window, signatureButton); - - // Assert - Assert.True(window.NavRoot.NavigationStack.Count > stackBefore, - "Click on '[DEV] Signature' must push a new page onto the nav stack."); - var pushed = window.NavRoot.NavigationStack.Last(); - Assert.NotNull(pushed); - Assert.IsAssignableFrom(pushed); - } -} diff --git a/src/PostIt/PostIt.Tests/NominatimReverseGeocodingServiceTests.cs b/src/PostIt/PostIt.Tests/NominatimReverseGeocodingServiceTests.cs deleted file mode 100644 index 4eceb7a2f..000000000 --- a/src/PostIt/PostIt.Tests/NominatimReverseGeocodingServiceTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Net; -using System.Text; -using PostIt.Services; - -namespace PostIt.Tests; - -public class NominatimReverseGeocodingServiceTests -{ - [Fact] - public async Task TryResolveAddressAsync_formats_compact_street_address_from_nominatim_payload() - { - var handler = new StubHandler(""" - { - "display_name": "6, Place de l'Hôtel-de-Ville - Esplanade de la Libération, Paris, 75004, France", - "address": { - "house_number": "6", - "road": "Place de l'Hôtel-de-Ville - Esplanade de la Libération", - "postcode": "75004", - "city": "Paris" - } - } - """); - - var service = new NominatimReverseGeocodingService(new HttpClient(handler) - { - BaseAddress = new Uri("https://nominatim.openstreetmap.org/") - }); - - var result = await service.TryResolveAddressAsync(48.8566, 2.3522); - - Assert.Equal("6, Place de l'Hôtel-de-Ville - Esplanade de la Libération, 75004, Paris", result); - Assert.NotNull(handler.LastRequest); - Assert.Contains("reverse?format=jsonv2", handler.LastRequest!.RequestUri!.ToString(), StringComparison.Ordinal); - } - - [Fact] - public async Task TryResolveAddressAsync_returns_null_on_unsuccessful_response() - { - var handler = new StubHandler("{}", HttpStatusCode.TooManyRequests); - var service = new NominatimReverseGeocodingService(new HttpClient(handler) - { - BaseAddress = new Uri("https://nominatim.openstreetmap.org/") - }); - - var result = await service.TryResolveAddressAsync(48.8566, 2.3522); - - Assert.Null(result); - } - - private sealed class StubHandler : HttpMessageHandler - { - private readonly string _payload; - private readonly HttpStatusCode _statusCode; - - public HttpRequestMessage? LastRequest { get; private set; } - - public StubHandler(string payload, HttpStatusCode statusCode = HttpStatusCode.OK) - { - _payload = payload; - _statusCode = statusCode; - } - - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - LastRequest = request; - return Task.FromResult(new HttpResponseMessage(_statusCode) - { - Content = new StringContent(_payload, Encoding.UTF8, "application/json") - }); - } - } -} \ No newline at end of file diff --git a/src/PostIt/PostIt.Tests/PostAclDialogTests.cs b/src/PostIt/PostIt.Tests/PostAclDialogTests.cs deleted file mode 100644 index ed8282173..000000000 --- a/src/PostIt/PostIt.Tests/PostAclDialogTests.cs +++ /dev/null @@ -1,281 +0,0 @@ -using System.Net; -using System.Text; -using System.Text.Json; -using Avalonia; -using Avalonia.Headless.XUnit; -using Microsoft.Extensions.DependencyInjection; -using PostIt.Helpers; -using PostIt.Services; -using PostIt.ViewModels; -using PostIt.Views; -using Yavsc.Api.Client; -using Yavsc.Api.Client.Dtos; -using Yavsc.Blogspot; - -namespace PostIt.Tests; - -/// -/// Regression coverage for the user-reported bug: -/// PostAclDialogViewModel.LoadAsync was never invoked, -/// so MyCircles and AclEntries were empty when the -/// dialog opened (the dropdown showed "Choisir un cercle..." and -/// the list was blank, with no error to hint at why). -/// -/// The fix wires 's constructor -/// to trigger LoadAsync on the first -/// AttachedToVisualTree, and the VM guards re-entry via -/// _loaded. Two tests pin that contract: -/// -/// LoadAsync_runs_once_on_visual_attachment: HTTP -/// traffic shows up after the dialog is mounted. -/// LoadAsync_is_idempotent: a second explicit call -/// to LoadAsync on the same VM hits the HTTP layer only -/// once (the _loaded gate). -/// -/// -/// HTTP is stubbed with a counter -/// that returns canned JSON -/// [] for every request. The handler counts calls so the -/// tests can assert "exactly one round-trip on mount" and -/// "exactly one round-trip after two calls to LoadAsync". This -/// is the same shape used by BearerScopeTests: real -/// subclass, real -/// with an injected handler, real -/// / -/// talking to it. -/// -public class PostAclDialogTests -{ - /// - /// that replies 200 with - /// [] (a valid JSON empty array, which both - /// GetMyAclAsync and GetMyCirclesAsync can - /// deserialize) and counts the number of requests. - /// - private sealed class CountingHttpHandler : HttpMessageHandler - { - public int RequestCount { get; private set; } - - protected override Task SendAsync( - HttpRequestMessage request, CancellationToken cancellationToken) - { - RequestCount++; - var response = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent("[]", Encoding.UTF8, "application/json"), - }; - return Task.FromResult(response); - } - } - - /// - /// Subclass of that routes HTTP - /// traffic through a caller-supplied - /// . Same recipe as - /// BearerScopeTests.TestableYavscApiClient — we - /// override CallAsync{T} to talk to our own - /// and skip the OIDC refresh path, - /// because the load-on-attach bug has nothing to do with - /// token refresh. - /// - private sealed class TestableYavscApiClient : YavscApiClient - { - private readonly HttpClient _http; - - public TestableYavscApiClient( - Settings settings, - TokenStore store, - HttpMessageHandler handler) - : base(settings, store, oidc: null!) - { - _http = new HttpClient(handler, disposeHandler: false); - } - - public override Task CallAsync( - HttpMethod method, string path, object? body = null, - CancellationToken ct = default) - { - var absolute = new Uri(new Uri(Settings.ApiUrl), path); - using var req = new HttpRequestMessage(method, absolute); - using var resp = _http.SendAsync(req, ct).GetAwaiter().GetResult(); - resp.EnsureSuccessStatusCode(); - using var stream = resp.Content.ReadAsStream(); - var dto = JsonSerializer.Deserialize(stream, - new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); - return Task.FromResult(dto!); - } - } - - /// - /// Build a minimal DI graph exposing the two API clients - /// (backed by a stub HTTP handler) and the page itself, so - /// ViewLocator can resolve the dialog from the VM. - /// Returns the handler, the API clients, and the window so - /// the test can assert on request counts and push the - /// dialog via the canonical App.PushPageAsync path. - /// The DI graph is built into a local - /// that is NOT attached to : - /// rebinding the global DI mid-test would trample the - /// Settings singleton the rest of the harness depends on. - /// - private static (MainView window, BlogAclApiClient aclClient, CircleApiClient circleClient, CountingHttpHandler handler) Mount() - { - var handler = new CountingHttpHandler(); - var settings = new Settings(); - var api = new TestableYavscApiClient(settings, new TokenStore(System.IO.Path.GetTempFileName()), handler); - var aclClient = new BlogAclApiClient(api, settings.ApiUrl); - var circleClient = new CircleApiClient(api, settings.ApiUrl); - - var services = new ServiceCollection(); - services.AddSingleton(settings); - services.AddSingleton(api); - services.AddSingleton(aclClient); - services.AddSingleton(circleClient); - services.AddTransient(); - var sp = services.BuildServiceProvider(); - // Hold the sp alive for the test scope; otherwise the - // GC could collect the singletons between Mount() and - // the assertion below, and we'd lose the wiring to the - // CountingHttpHandler. - GC.KeepAlive(sp); - - var window = new MainView(); - var app = (App)Application.Current!; - app.AttachMainWindow(window); - - return (window, aclClient, circleClient, handler); - } - - /// - /// The bug: opening the dialog never called LoadAsync, so - /// MyCircles/AclEntries were empty. After the fix, setting - /// the dialog's DataContext to a PostAclDialogViewModel - /// (the same path App.PushPageAsync takes) must trigger - /// exactly one LoadAsync round-trip (the parallel WhenAll - /// inside the VM counts as one request per backend call, - /// hence two HTTP requests total: GET /blogacl and GET - /// /circle). - /// - [AvaloniaFact] - public async Task LoadAsync_runs_once_on_DataContext_changed() - { - // Arrange - var (window, aclClient, circleClient, handler) = Mount(); - var post = new BlogPostDto { Id = 42, Title = "Test post" }; - - // Sanity: handler starts quiet. - Assert.Equal(0, handler.RequestCount); - - // Act: push the dialog via the canonical VM-first pipeline. - // The locator goes through the parameterless ctor of - // PostAclDialog, then App.PushPageAsync assigns DataContext, - // which our hook intercepts to trigger LoadAsync. - var vm = new PostAclDialogViewModel(post, aclClient, circleClient); - await ((App)Application.Current!).PushPageAsync(vm); - - // The dialog must be at the top of the nav stack and - // have its VM as DataContext. - var dialog = window.NavRoot.NavigationStack[^1] as PostAclDialog - ?? throw new InvalidOperationException("Dialog not at top of stack"); - Assert.Same(vm, dialog.DataContext); - - // Drain pending async work. LoadAsync is async and the - // DataContextChanged handler is fire-and-forget; a - // couple of loop turns is enough. We poll the handler - // counter because the dispatch back onto the headless - // dispatcher isn't strict — using a generous-but-bounded - // wait avoids test flakes. - var deadline = DateTime.UtcNow.AddSeconds(2); - while (handler.RequestCount < 2 && DateTime.UtcNow < deadline) - { - await Task.Delay(20); - } - - // Assert: one GET went out (for /circle) from LoadAsync. - Assert.Equal(1, handler.RequestCount); - - // And the VM's idempotency gate has flipped. - Assert.True(vm.Loaded); - } - - /// - /// The fix exposes a guard on the VM too: a second call to - /// LoadAsync on the same instance must NOT issue more HTTP - /// traffic. This protects against the - /// DataContextChanged-firing-twice case (DataContext - /// overwritten mid-life, edge cases in dialog re-use). - /// - [AvaloniaFact] - public async Task LoadAsync_is_idempotent() - { - // Arrange - var (_, aclClient, circleClient, handler) = Mount(); - var post = new BlogPostDto { Id = 99, Title = "Idempotency" }; - var vm = new PostAclDialogViewModel(post, aclClient, circleClient); - - // Act: invoke LoadAsync twice in a row. - await vm.LoadAsync(); - await vm.LoadAsync(); - - // Assert: the second call short-circuited on _loaded. - Assert.Equal(1, handler.RequestCount); - Assert.True(vm.Loaded); - } - - [Fact] - public async Task LoadAsync_keeps_acl_from_blogpostdto_and_only_loads_circles() - { - var post = new BlogPostDto { Id = 42, Title = "ACL hydration" }; - post.AuthorizeCircle(12); - post.AuthorizeCircle(34); - - var api = new StubAclApiClient(); - var aclClient = new BlogAclApiClient(api, "http://localhost/"); - var circleClient = new CircleApiClient(api, "http://localhost/"); - var vm = new PostAclDialogViewModel(post, aclClient, circleClient); - - await vm.LoadAsync(); - - Assert.Equal(1, api.CallCount); - Assert.Equal(2, vm.AclEntries.Count); - Assert.Contains(vm.AclEntries, a => a.CircleId == 12); - Assert.Contains(vm.AclEntries, a => a.CircleId == 34); - } - - private sealed class StubAclApiClient : IYavscApiClient - { - public HttpClient Http { get; } = new(); - public int CallCount { get; private set; } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - CallCount++; - - if (typeof(T) == typeof(List)) - { - var circles = new List - { - new() { Id = 12, Name = "A", OwnerId = "owner", Public = false }, - new() { Id = 34, Name = "B", OwnerId = "owner", Public = false }, - }; - return Task.FromResult((T)(object)circles); - } - - return Task.FromResult(default(T)!); - } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - CallCount++; - return Task.CompletedTask; - } - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => CallAsync(method, path, (object?)null, ct); - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => CallAsync(method, path, (object?)null, ct); - - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - } -} diff --git a/src/PostIt/PostIt.Tests/ProviderOngoingRequestsPageViewModelTests.cs b/src/PostIt/PostIt.Tests/ProviderOngoingRequestsPageViewModelTests.cs deleted file mode 100644 index cfb300316..000000000 --- a/src/PostIt/PostIt.Tests/ProviderOngoingRequestsPageViewModelTests.cs +++ /dev/null @@ -1,238 +0,0 @@ -using System.Net.Http; -using PostIt.ViewModels; -using Yavsc; -using Yavsc.Api.Client; - -namespace PostIt.Tests; - -public class ProviderOngoingRequestsPageViewModelTests -{ - [Fact] - public async Task RefreshAsync_calls_provider_endpoint_and_filters_out_unknown_billing_codes() - { - var api = new StubProviderApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new ProviderOngoingRequestsPageViewModel(client); - - await vm.InitializeAsync(); - - Assert.Contains("https://business.example/api/v1/bill/provider/ongoing", api.Paths); - Assert.Equal(3, vm.Queries.Count); - Assert.Equal(12, vm.Queries[0].Id); - Assert.Equal(11, vm.Queries[1].Id); - Assert.Equal(10, vm.Queries[2].Id); - } - - [Fact] - public async Task FilterText_filters_by_activity_code_and_status() - { - var api = new StubProviderApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new ProviderOngoingRequestsPageViewModel(client); - - await vm.InitializeAsync(); - - vm.FilterText = "mbrush"; - Assert.Single(vm.Queries); - Assert.Equal("MBrush", vm.Queries[0].BillingCode); - - vm.FilterText = "accepted"; - Assert.Single(vm.Queries); - Assert.Equal(QueryStatus.Accepted, vm.Queries[0].Status); - } - - [Fact] - public async Task OpenSelectedEditorCommand_can_execute_only_when_selection_exists() - { - var api = new StubProviderApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new ProviderOngoingRequestsPageViewModel(client); - - await vm.InitializeAsync(); - - Assert.False(vm.OpenSelectedEditorCommand.CanExecute(null)); - - vm.SelectedQuery = vm.Queries[0]; - - Assert.True(vm.OpenSelectedEditorCommand.CanExecute(null)); - } - - [Fact] - public async Task SelectedSortOption_date_keeps_most_recent_first() - { - var api = new StubProviderApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new ProviderOngoingRequestsPageViewModel(client); - - await vm.InitializeAsync(); - vm.SelectedSortOption = ProviderOngoingRequestsPageViewModel.SortByDate; - - Assert.Equal(new long[] { 12, 11, 10 }, vm.Queries.Select(q => q.Id).ToArray()); - } - - [Fact] - public async Task SelectedSortOption_date_ascending_keeps_oldest_first() - { - var api = new StubProviderApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new ProviderOngoingRequestsPageViewModel(client); - - await vm.InitializeAsync(); - vm.SelectedSortOption = ProviderOngoingRequestsPageViewModel.SortByDateAsc; - - Assert.Equal(new long[] { 10, 11, 12 }, vm.Queries.Select(q => q.Id).ToArray()); - } - - [Fact] - public async Task SelectedSortOption_status_prioritizes_inprogress_then_accepted_then_inserted() - { - var api = new StubProviderApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new ProviderOngoingRequestsPageViewModel(client); - - await vm.InitializeAsync(); - vm.SelectedSortOption = ProviderOngoingRequestsPageViewModel.SortByStatus; - - Assert.Equal(new long[] { 11, 12, 10 }, vm.Queries.Select(q => q.Id).ToArray()); - } - - [Fact] - public void Constructor_reads_saved_sort_option_from_settings() - { - var api = new StubProviderApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var settings = new Settings - { - ProviderOngoingRequestsSortOption = ProviderOngoingRequestsPageViewModel.SortByStatus, - }; - - var vm = new ProviderOngoingRequestsPageViewModel(client, settings); - - Assert.Equal(ProviderOngoingRequestsPageViewModel.SortByStatus, vm.SelectedSortOption); - } - - [Fact] - public void Constructor_falls_back_to_default_when_saved_sort_is_invalid() - { - var api = new StubProviderApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var settings = new Settings - { - ProviderOngoingRequestsSortOption = "invalide", - }; - - var vm = new ProviderOngoingRequestsPageViewModel(client, settings); - - Assert.Equal(ProviderOngoingRequestsPageViewModel.SortByDate, vm.SelectedSortOption); - } - - [Fact] - public async Task CreateEstimateForSelectedCommand_requires_a_selection() - { - var api = new StubProviderApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var estimateClient = new EstimateApiClient(api, "https://business.example/api/v1/"); - var vm = new ProviderOngoingRequestsPageViewModel(client, estimateClient: estimateClient); - - await vm.InitializeAsync(); - - Assert.False(vm.CreateEstimateForSelectedCommand.CanExecute(null)); - - vm.SelectedQuery = vm.Queries[0]; - - Assert.True(vm.CreateEstimateForSelectedCommand.CanExecute(null)); - } - - [Fact] - public async Task CreateEstimateForSelectedCommand_is_disabled_without_estimate_client() - { - var api = new StubProviderApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new ProviderOngoingRequestsPageViewModel(client); - - await vm.InitializeAsync(); - vm.SelectedQuery = vm.Queries[0]; - - Assert.False(vm.CreateEstimateForSelectedCommand.CanExecute(null)); - } - - private sealed class StubProviderApi : IYavscApiClient - { - public HttpClient Http { get; } = new(); - public List Paths { get; } = new(); - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - Paths.Add(path); - - if (typeof(T) == typeof(List)) - { - var data = new List - { - new() - { - Id = 10, - BillingCode = "Rdv", - ActivityCode = "dev", - PerformerId = "perf-1", - ClientId = "cli-1", - Status = QueryStatus.Inserted, - Description = "Rendez-vous", - EventDate = new DateTime(2026, 9, 10, 10, 0, 0, DateTimeKind.Utc), - }, - new() - { - Id = 11, - BillingCode = "MBrush", - ActivityCode = "hair", - PerformerId = "perf-1", - ClientId = "cli-2", - Status = QueryStatus.InProgress, - Description = "Coupe multiple", - EventDate = new DateTime(2026, 9, 11, 10, 0, 0, DateTimeKind.Utc), - }, - new() - { - Id = 12, - BillingCode = "Brush", - ActivityCode = "hair", - PerformerId = "perf-1", - ClientId = "cli-3", - Status = QueryStatus.Accepted, - Description = "Coupe simple", - EventDate = new DateTime(2026, 9, 12, 10, 0, 0, DateTimeKind.Utc), - }, - new() - { - Id = 13, - BillingCode = "", - ActivityCode = "unknown", - PerformerId = "perf-1", - ClientId = "cli-4", - Status = QueryStatus.Accepted, - Description = "Doit être filtrée", - EventDate = new DateTime(2026, 9, 13, 10, 0, 0, DateTimeKind.Utc), - } - }; - - return Task.FromResult((T)(object)data); - } - - return Task.FromResult(default(T)!); - } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - Paths.Add(path); - return Task.CompletedTask; - } - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => CallAsync(method, path, (object?)null, ct); - - public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) - => CallAsync(method, path, (object?)null, ct); - - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - } -} diff --git a/src/PostIt/PostIt.Tests/RdvPageHeadlessTests.cs b/src/PostIt/PostIt.Tests/RdvPageHeadlessTests.cs deleted file mode 100644 index 1bcb8a0d8..000000000 --- a/src/PostIt/PostIt.Tests/RdvPageHeadlessTests.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Headless.XUnit; -using PostIt.ViewModels; -using PostIt.ViewModels.Commands; -using PostIt.Views.Commands; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.Tests; - -public class RdvPageHeadlessTests -{ - [AvaloniaFact] - public void Suggested_address_panel_is_hidden_by_default() - { - var page = CreatePage(out _); - - var panel = page.FindControl("SuggestedAddressPanel"); - var progress = page.FindControl("SuggestedAddressProgress"); - - Assert.NotNull(panel); - Assert.NotNull(progress); - Assert.False(panel!.IsVisible); - Assert.False(progress!.IsVisible); - } - - [AvaloniaFact] - public async Task Suggested_address_panel_and_spinner_follow_viewmodel_state() - { - var page = CreatePage(out var vm); - var panel = page.FindControl("SuggestedAddressPanel")!; - var progress = page.FindControl("SuggestedAddressProgress")!; - var applyButton = page.FindControl public SignaturePadData Snapshot() => new(_strokes.ToArray()); - /// - /// Copy of the current in-progress stroke, without the length - /// prefix used for sealed strokes. The view can render this as a - /// live preview while the user is still drawing. - /// - internal IReadOnlyList PendingStroke - => _capturing && _pendingPoints > 0 - ? _strokes.GetRange(_strokes.Count - 2 * _pendingPoints, 2 * _pendingPoints) - : Array.Empty(); - // --- Test-only surface (visible to PostIt.Tests) ------------------- /// @@ -212,17 +183,6 @@ public class SignaturePadControl : TemplatedControl _pendingPoints++; } - /// - /// Test hook: mark the control as actively capturing so tests - /// can exercise the live-preview path without synthetic pointer - /// events. - /// - internal void BeginCaptureForTest() - { - _capturing = true; - _pendingPoints = 0; - } - /// /// Test hook: seal the currently-pending stroke with a length /// prefix. Mirrors what does at diff --git a/src/PostIt/PostIt/Controls/StatusBar.axaml b/src/PostIt/PostIt/Controls/StatusBar.axaml deleted file mode 100644 index eaa6f5a09..000000000 --- a/src/PostIt/PostIt/Controls/StatusBar.axaml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - diff --git a/src/PostIt/PostIt/Controls/StatusBar.axaml.cs b/src/PostIt/PostIt/Controls/StatusBar.axaml.cs deleted file mode 100644 index 975f9b2f9..000000000 --- a/src/PostIt/PostIt/Controls/StatusBar.axaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Avalonia.Controls; - -namespace PostIt.Controls; - -public partial class StatusBar : UserControl -{ - public StatusBar() - { - InitializeComponent(); - } -} diff --git a/src/PostIt/PostIt/Helpers/FormHelpers.cs b/src/PostIt/PostIt/Helpers/FormHelpers.cs deleted file mode 100644 index 18cc8fd0e..000000000 --- a/src/PostIt/PostIt/Helpers/FormHelpers.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using PostIt.ViewModels; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.Helpers; - -public static class FormHelpers -{ - public static BillingCommandPageViewModel? - CreateCommandPageViewModel( - this CommandFormSummary form, - ActivityInfo activity, - ActivityUserDisplayItem performer, - BillingApiClient billingClient) - { - - string namespacePrefix = typeof(PostIt.ViewModels.Commands.RdvViewModel).Namespace + "."; - - string formVMName = form.ActionName + "ViewModel"; - - string formOnActivityVMName = activity.Code + formVMName + "ViewModel"; - - var vmType = Type.GetType(namespacePrefix +formOnActivityVMName); - if (vmType == null) - { - vmType = Type.GetType(namespacePrefix + formVMName); - } - if (vmType == null) - { - Console.Error.WriteLine( - $"! Cannot find type '{formOnActivityVMName}' or '{formVMName}'"); - return null; - } - if (!typeof(BillingCommandPageViewModel).IsAssignableFrom(vmType)) - { - Console.Error.WriteLine($"! The type '{formOnActivityVMName}' or '{formVMName}' is not a BillingCommandPageViewModel"); - return null; - } - - var vm = Activator.CreateInstance(vmType, activity, performer, form, billingClient); - - if (vm == null) - { - throw new InvalidOperationException($"Cannot create instance of '{formOnActivityVMName}' or '{formVMName}'"); - } - - return vm as BillingCommandPageViewModel ?? throw new InvalidOperationException($"The type '{formOnActivityVMName}' or '{formVMName}' is not a BillingCommandPageViewModel"); - } -} diff --git a/src/PostIt/PostIt/Helpers/ImageHelper.cs b/src/PostIt/PostIt/Helpers/ImageHelper.cs deleted file mode 100644 index 30e7e34e5..000000000 --- a/src/PostIt/PostIt/Helpers/ImageHelper.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.IO; -using System.Net.Http; -using System.Threading.Tasks; -using Avalonia.Media.Imaging; -using Avalonia.Platform; - -namespace PostIt.Helpers; - -public static class ImageHelper -{ - private static readonly HttpClient HttpClient = new(); - - public static Bitmap LoadFromResource(Uri resourceUri) - { - return new Bitmap(AssetLoader.Open(resourceUri)); - } - - public static async Task LoadFromWeb(Uri url) - { - try - { - var response = await HttpClient.GetAsync(url).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - var data = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); - return new Bitmap(new MemoryStream(data)); - } - catch (HttpRequestException ex) - { - Console.WriteLine($"An error occurred while downloading image '{url}': {ex.Message}"); - return null; - } - } -} \ No newline at end of file diff --git a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs deleted file mode 100644 index 6bd54ca20..000000000 --- a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using PostIt.Services; -using PostIt.ViewModels; -using PostIt.Views; -using PostIt.Views.Blogs; -using PostIt.Views.Commands; -using Yavsc.Api.Client; - -namespace PostIt.Helpers; - -public static class ServiceCollectionHelpers -{ - public static IServiceProvider BuildServices(this ServiceCollection services) - { - var settings = new Settings(); - settings.Load(); - - var tokenStore = new TokenStore(System.IO.Path.Combine( - System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData), - "PostIt", "tokens.json")); - - var api = new YavscApiClient(settings, tokenStore); - var client = new BlogApiClient(api, settings.BlogsApiUrl); - var circleClient = new CircleApiClient(api, settings.BlogsApiUrl); - var blogAclClient = new BlogAclApiClient(api, settings.BlogsApiUrl); - var userSearchClient = new UserSearchClient(api, settings.BlogsApiUrl); - var activityClient = new ActivityApiClient( - api, - () => settings.ApiUrl, - () => settings.Authentication?.Authority); - var billingClient = new BillingApiClient(api, () => settings.ApiUrl); - var estimateClient = new EstimateApiClient(api, () => settings.ApiUrl); - var userDirectory = new UserDirectory(userSearchClient); - var reverseGeocoding = new NominatimReverseGeocodingService(); - - // Vues - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - // SettingsPage is a singleton: there must be one and only one - // instance of the settings UI for the lifetime of the app. - // This guarantees that (a) the bindings always reflect the - // current in-memory Settings state, (b) the page already has - // its DataContext wired up at composition-root time (see - // below), and (c) PushPageAsync's anti-empilement guard sees - // the same instance across pushes, so a second Settings tap - // is a no-op rather than re-pushing the page. Transient would - // let the user accumulate stale SettingsPage instances on - // the navigation stack, each bound to a fresh - // SettingsViewModel and missing any in-flight edits. - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); - - // ViewModels - services.AddSingleton(settings); - services.AddSingleton(api); - services.AddSingleton(client); - services.AddSingleton(circleClient); - services.AddSingleton(blogAclClient); - services.AddSingleton(userSearchClient); - services.AddSingleton(activityClient); - services.AddSingleton(billingClient); - services.AddSingleton(estimateClient); - services.AddSingleton(reverseGeocoding); - services.AddSingleton(userDirectory); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddTransient(); - - // Dialogs (modal-light pages): the ViewLocator resolves - // them when a caller pushes a PostAclDialogViewModel or - // AddCircleMemberDialogViewModel via App.PushPageAsync. - // App.PushPageAsync overwrites the page's DataContext with - // the caller-built VM, so the parameterless ctor is enough - // here — the parametrised ctors stay for direct test wiring. - services.AddTransient(); - services.AddTransient(); - // Persistent session banner: one instance for the lifetime of - // the app so the same VM survives page navigation. - var sessionStatus = new SessionStatusViewModel { Api = api }; - sessionStatus.Refresh(); - services.AddSingleton(sessionStatus); - services.AddSingleton(); - services.AddSingleton(); - return services.BuildServiceProvider(); - } -} diff --git a/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs b/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs deleted file mode 100644 index f30e021d9..000000000 --- a/src/PostIt/PostIt/Helpers/ViewModelBaseHelpers.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Avalonia.Controls; -using PostIt.ViewModels; - -namespace PostIt.Helpers; - -public static class ViewModelBaseHelpers -{ - public static async Task PushPageAsync(this App app, ViewModelBase vm) - { - var window = app.View; - if (window is null) - { - throw new InvalidOperationException("MainWindow is not initialized yet."); - } - - var template = app.DataTemplates.FirstOrDefault(t => t.Match(vm)); - if (template is null) - { - throw new InvalidOperationException($"No IDataTemplate found for {vm.GetType().Name}."); - } - - var view = template.Build(vm); - if (view is null) - { - throw new InvalidOperationException( - $"Template for {vm.GetType().Name} returned ."); - } - - var page = view as Page; - if (page is null) - { - // NavigationPage expects Page instances. Wrap any fallback control - // (e.g. ViewLocator error TextBlock) into a ContentPage so it can render. - page = new ContentPage { Content = view }; - } - - page.DataContext = vm; - - // Avoid stacking the same singleton page twice (e.g. SettingsPage). - var stack = window.NavRoot.NavigationStack; - if (stack.Count > 0 && ReferenceEquals(stack[stack.Count - 1], page)) - { - return page; - } - - await window.NavRoot.PushAsync(page); - - return page; - } -} diff --git a/src/PostIt/PostIt/PostIt.csproj b/src/PostIt/PostIt/PostIt.csproj index 09632bdf8..163a6a774 100644 --- a/src/PostIt/PostIt/PostIt.csproj +++ b/src/PostIt/PostIt/PostIt.csproj @@ -3,14 +3,30 @@ net10.0 enable latest + true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070 + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 + + + + + + + None + All + + + + + + + PreserveNewest @@ -22,21 +38,6 @@ - - - - - None - All - - - - - - - - - - + \ No newline at end of file diff --git a/src/PostIt/PostIt/Services/ContactService.Desktop.cs b/src/PostIt/PostIt/Services/ContactService.Desktop.cs new file mode 100644 index 000000000..fa7d37f64 --- /dev/null +++ b/src/PostIt/PostIt/Services/ContactService.Desktop.cs @@ -0,0 +1,36 @@ +#if !ANDROID && !IOS +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace PostIt.Services; + +/// +/// Desktop stub for . +/// +/// The desktop has no equivalent of the mobile address +/// book (no Contacts.Default, no CardDAV out of the +/// box). Rather than synthesise a list from a different +/// source, this provider returns an empty list and lets the +/// UI render an honest "no local contacts on this platform" +/// message. +/// +/// If desktop users want to invite people who aren't +/// Yavsc members, that flow goes through a separate path +/// (manual email entry + invitation endpoint) — not through +/// . Finding existing Yavsc +/// members is 's job, not this +/// one's. +/// +/// Future CardDAV / Google Contacts / Exchange +/// providers can plug in here as additional +/// implementations selected +/// from DI by configuration. +/// +public sealed class ContactService : IContactService +{ + public Task> GetDeviceContactsAsync(CancellationToken ct = default) + => Task.FromResult>(Array.Empty()); +} +#endif diff --git a/src/PostIt/PostIt/Services/ContactService.Mobile.cs b/src/PostIt/PostIt/Services/ContactService.Mobile.cs new file mode 100644 index 000000000..8dbd134d8 --- /dev/null +++ b/src/PostIt/PostIt/Services/ContactService.Mobile.cs @@ -0,0 +1,82 @@ +#if ANDROID || IOS +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Maui.ApplicationModel.Communication; +using Microsoft.Maui.ApplicationModel; +using Microsoft.Maui.Devices; + +namespace PostIt.Services; + +/// +/// Mobile implementation backed by MAUI Essentials +/// Contacts.Default. +/// +/// Compiled only for ANDROID and IOS. On desktop targets, +/// see ContactService.Desktop.cs (the stub that wins at +/// compile time). +/// +/// Note: at runtime, this class throws +/// NotImplementedInReferenceAssemblyException unless +/// the host application project also references the +/// platform-specific Microsoft.Maui.Essentials implementation +/// (typically PostIt.Android). On iOS the same is +/// required via PostIt.iOS. On desktop the stub is used +/// and this file is excluded. +/// +public sealed class ContactService : IContactService +{ + public async Task> GetDeviceContactsAsync(CancellationToken ct = default) + { + if (DeviceInfo.Current.Platform == DevicePlatform.Unknown) + return Array.Empty(); + + try + { + var status = await Permissions.RequestAsync(); + if (status != PermissionStatus.Granted) + return Array.Empty(); + + var contacts = await Contacts.Default.GetAllAsync(); + if (contacts is null) return Array.Empty(); + + // Carry the per-contact email list as-is. A real + // device contact can carry several addresses (home / + // work / other); the UI use case ("invite / add to a + // circle") can then decide which address to use, or + // let the user pick. The platform-neutral ContactDto + // shape is intentionally richer than the Yavsc + // directory's single-Email shape — the two flows + // answer different questions. + var result = new List(contacts.Count); + foreach (var c in contacts) + { + var emails = ExtractEmails(c.Emails); + result.Add(new ContactDto( + c.Id, + c.DisplayName ?? string.Empty, + emails)); + } + return result; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"ContactService: {ex.Message}"); + return Array.Empty(); + } + } + + private static IReadOnlyList ExtractEmails(IEnumerable? emails) + { + if (emails is null) return Array.Empty(); + var list = new List(); + foreach (var e in emails) + { + if (!string.IsNullOrEmpty(e.EmailAddress)) + list.Add(e.EmailAddress); + } + return list; + } +} +#endif diff --git a/src/PostIt/PostIt/Services/CurrentLocationResult.cs b/src/PostIt/PostIt/Services/CurrentLocationResult.cs deleted file mode 100644 index ea19da436..000000000 --- a/src/PostIt/PostIt/Services/CurrentLocationResult.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace PostIt.Services; - -public sealed class CurrentLocationResult -{ - private CurrentLocationResult(bool isSuccess, bool isPermissionDenied, double? latitude, double? longitude, string message) - { - IsSuccess = isSuccess; - IsPermissionDenied = isPermissionDenied; - Latitude = latitude; - Longitude = longitude; - Message = message; - } - - public bool IsSuccess { get; } - public bool IsPermissionDenied { get; } - public double? Latitude { get; } - public double? Longitude { get; } - public string Message { get; } - - public static CurrentLocationResult Success(double latitude, double longitude, string? message = null) - => new(true, false, latitude, longitude, message ?? "Position récupérée."); - - public static CurrentLocationResult PermissionDenied(string? message = null) - => new(false, true, null, null, message ?? "La géolocalisation n'est pas autorisée."); - - public static CurrentLocationResult Unavailable(string? message = null) - => new(false, false, null, null, message ?? "La géolocalisation n'est pas disponible sur cette plateforme."); -} diff --git a/src/PostIt/PostIt/Services/IReverseGeocodingService.cs b/src/PostIt/PostIt/Services/IReverseGeocodingService.cs deleted file mode 100644 index 0e62fe9dc..000000000 --- a/src/PostIt/PostIt/Services/IReverseGeocodingService.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; - -namespace PostIt.Services; - -public interface IReverseGeocodingService -{ - Task TryResolveAddressAsync(double latitude, double longitude, CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/PostIt/PostIt/Services/NominatimReverseGeocodingService.cs b/src/PostIt/PostIt/Services/NominatimReverseGeocodingService.cs deleted file mode 100644 index f9ddb2f6a..000000000 --- a/src/PostIt/PostIt/Services/NominatimReverseGeocodingService.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; - -namespace PostIt.Services; - -public sealed class NominatimReverseGeocodingService : IReverseGeocodingService -{ - private static readonly Uri BaseUri = new("https://nominatim.openstreetmap.org/"); - private readonly HttpClient _httpClient; - - public NominatimReverseGeocodingService(HttpClient? httpClient = null) - { - _httpClient = httpClient ?? CreateDefaultClient(); - } - - public async Task TryResolveAddressAsync(double latitude, double longitude, CancellationToken cancellationToken = default) - { - var requestUri = BuildReverseUri(latitude, longitude); - - try - { - using var response = await _httpClient.GetAsync(requestUri, cancellationToken).ConfigureAwait(false); - if (!response.IsSuccessStatusCode) - return null; - - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - using var json = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken).ConfigureAwait(false); - return FormatAddress(json.RootElement); - } - catch (OperationCanceledException) - { - throw; - } - catch - { - return null; - } - } - - private static HttpClient CreateDefaultClient() - { - var client = new HttpClient - { - BaseAddress = BaseUri, - Timeout = TimeSpan.FromSeconds(10), - }; - client.DefaultRequestHeaders.UserAgent.Clear(); - client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("PostIt", "1.1")); - client.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("fr-FR")); - client.DefaultRequestHeaders.AcceptLanguage.Add(new StringWithQualityHeaderValue("fr", 0.9)); - return client; - } - - private static Uri BuildReverseUri(double latitude, double longitude) - { - var lat = latitude.ToString("0.######", CultureInfo.InvariantCulture); - var lon = longitude.ToString("0.######", CultureInfo.InvariantCulture); - var path = $"reverse?format=jsonv2&addressdetails=1&accept-language=fr&zoom=18&lat={lat}&lon={lon}"; - return new Uri(path, UriKind.Relative); - } - - private static string? FormatAddress(JsonElement root) - { - if (root.TryGetProperty("address", out var address)) - { - var street = JoinNonEmpty( - TryGetString(address, "house_number"), - TryGetString(address, "road")); - - var locality = JoinNonEmpty( - TryGetString(address, "postcode"), - TryGetString(address, "city") - ?? TryGetString(address, "town") - ?? TryGetString(address, "village") - ?? TryGetString(address, "municipality")); - - var formatted = JoinNonEmpty(street, locality); - if (!string.IsNullOrWhiteSpace(formatted)) - return formatted; - } - - if (root.TryGetProperty("display_name", out var displayName)) - { - var value = displayName.GetString(); - if (!string.IsNullOrWhiteSpace(value)) - return value; - } - - return null; - } - - private static string? TryGetString(JsonElement element, string propertyName) - { - return element.TryGetProperty(propertyName, out var property) - ? property.GetString() - : null; - } - - private static string? JoinNonEmpty(params string?[] values) - { - List? parts = null; - foreach (var value in values) - { - if (string.IsNullOrWhiteSpace(value)) - continue; - - parts ??= new List(); - parts.Add(value.Trim()); - } - - return parts is null || parts.Count == 0 ? null : string.Join(", ", parts); - } -} \ No newline at end of file diff --git a/src/PostIt/PostIt/Services/Platform.cs b/src/PostIt/PostIt/Services/Platform.cs index 2e5ac76a3..c867c63cf 100644 --- a/src/PostIt/PostIt/Services/Platform.cs +++ b/src/PostIt/PostIt/Services/Platform.cs @@ -1,7 +1,4 @@ -using System; using IdentityModel.OidcClient.Browser; -using System.Threading; -using System.Threading.Tasks; namespace PostIt.Services; @@ -24,14 +21,14 @@ public static class Platform /// override this property at startup (e.g. PostIt.Android sets /// it to android://postit-signin). /// - public const string RedirectUri = "postit://callback"; + public static string DefaultRedirectUri { get; set; } = "postit://callback"; /// /// Scheme prefix the matches /// against BrowserOptions.EndUrl. Overridable for apps /// that want to register their own scheme. /// - public const string CustomScheme = "postit"; + public static string CustomScheme { get; set; } = "postit"; /// /// Constructs a fresh for the running platform. @@ -40,12 +37,4 @@ public static class Platform /// public static System.Func? CreateBrowser { get; set; } = () => new CustomSchemeBrowser(CustomScheme); - - /// - /// Optional platform hook used by the shared billing form to request a - /// current device position. Platforms that do not expose a native - /// location provider can leave the default delegate in place. - /// - public static Func> TryGetCurrentLocationAsync { get; set; } = - _ => Task.FromResult(CurrentLocationResult.Unavailable()); -} +} \ No newline at end of file diff --git a/src/PostIt/PostIt/Services/UiDispatcher.cs b/src/PostIt/PostIt/Services/UiDispatcher.cs new file mode 100644 index 000000000..e935ac1a2 --- /dev/null +++ b/src/PostIt/PostIt/Services/UiDispatcher.cs @@ -0,0 +1,72 @@ +using System; +using System.Threading.Tasks; +using Avalonia.Threading; + +namespace PostIt.Services; + +/// +/// Tiny marshalling helper around so +/// the rest of the codebase does not have to import Avalonia.Threading +/// directly. We want exactly one place that decides "is the current +/// thread the Avalonia UI thread, and if not, post there" so that +/// -derived types (Settings, the various +/// ViewModels) can fire PropertyChanged safely from background +/// work — which is exactly the cross-thread case that previously blew +/// up inside DataValidationErrors.SetErrors on Avalonia 11. +/// +/// The helper is intentionally tiny: a sync post when we are off the +/// UI thread, a no-op when we are already on it, and an async fire- +/// and-forget variant for places where awaiting would deadlock the +/// caller (e.g. Settings.Load continuation paths). +/// +public static class UiDispatcher +{ + /// + /// True when the calling thread is the Avalonia UI thread. Property + /// setters that touch bindings should check this before mutating + /// state; the safe path is . + /// + public static bool IsOnUiThread => Dispatcher.UIThread.CheckAccess(); + + /// + /// Run on the UI thread. If the caller is + /// already on the UI thread, run synchronously to preserve stack + /// traces and ordering; otherwise post to the dispatcher and wait. + /// Never throws on shutdown — a missing dispatcher is treated as + /// "best-effort skipped", matching Avalonia's own behaviour when + /// the application lifetime has been torn down. + /// + public static void InvokeIfNeeded(Action action) + { + if (action is null) return; + if (IsOnUiThread) { action(); return; } + try { Dispatcher.UIThread.Post(action, DispatcherPriority.Normal); } + catch (InvalidOperationException) { /* dispatcher gone, nothing to do */ } + } + + /// + /// Fire-and-forget variant: schedules on + /// the UI thread but does not block the caller. Use this from + /// background workers (OIDC discovery, HTTP callbacks, file I/O) + /// where awaiting the dispatcher would deadlock the calling sync + /// context. + /// + public static void Post(Action action) + { + if (action is null) return; + try { Dispatcher.UIThread.Post(action, DispatcherPriority.Normal); } + catch (InvalidOperationException) { /* dispatcher gone */ } + } + + /// + /// Awaitable variant. Useful inside async ViewModel methods + /// that must touch bindings only after the dispatcher has processed + /// a queued update (e.g. "load file then refresh observable state"). + /// + public static Task InvokeAsync(Action action) + { + if (action is null) return Task.CompletedTask; + if (IsOnUiThread) { action(); return Task.CompletedTask; } + return Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Normal).GetTask(); + } +} diff --git a/src/PostIt/PostIt/Services/YavscApiClient.cs b/src/PostIt/PostIt/Services/YavscApiClient.cs index 7e3187d8b..b611fe02b 100644 --- a/src/PostIt/PostIt/Services/YavscApiClient.cs +++ b/src/PostIt/PostIt/Services/YavscApiClient.cs @@ -1,9 +1,9 @@ using System; -using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; +using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -191,28 +191,7 @@ public class YavscApiClient : IYavscApiClient, IAsyncDisposable object? body = null, CancellationToken ct = default) { - using var response = await SendAsync(method, path, body is null ? null : () => JsonContent.Create(body), ct).ConfigureAwait(false); - await EnsureSuccessOrThrowAsync(response, ct).ConfigureAwait(false); - - var stream = await response.Content.ReadAsStreamAsync(ct).ConfigureAwait(false); - var dto = await JsonSerializer.DeserializeAsync(stream, - new JsonSerializerOptions { PropertyNameCaseInsensitive = true }, ct).ConfigureAwait(false); - return dto!; - } - - /// - /// Call a multipart endpoint, transparently refreshing the token if needed. - /// - public virtual async Task CallAsync( - HttpMethod method, - string path, - Func contentFactory, - CancellationToken ct = default) - { - if (contentFactory is null) - throw new ArgumentNullException(nameof(contentFactory)); - - using var response = await SendAsync(method, path, contentFactory, ct).ConfigureAwait(false); + using var response = await SendAsync(method, path, body, ct).ConfigureAwait(false); await EnsureSuccessOrThrowAsync(response, ct).ConfigureAwait(false); var stream = await response.Content.ReadAsStreamAsync(ct).ConfigureAwait(false); @@ -238,23 +217,7 @@ public class YavscApiClient : IYavscApiClient, IAsyncDisposable object? body = null, CancellationToken ct = default) { - using var response = await SendAsync(method, path, body is null ? null : () => JsonContent.Create(body), ct).ConfigureAwait(false); - await EnsureSuccessOrThrowAsync(response, ct).ConfigureAwait(false); - } - - /// - /// Call a multipart endpoint that returns no useful body (DELETE, etc.). - /// - public async Task CallAsync( - HttpMethod method, - string path, - Func contentFactory, - CancellationToken ct = default) - { - if (contentFactory is null) - throw new ArgumentNullException(nameof(contentFactory)); - - using var response = await SendAsync(method, path, contentFactory, ct).ConfigureAwait(false); + using var response = await SendAsync(method, path, body, ct).ConfigureAwait(false); await EnsureSuccessOrThrowAsync(response, ct).ConfigureAwait(false); } @@ -269,7 +232,7 @@ public class YavscApiClient : IYavscApiClient, IAsyncDisposable => CallAsync(method, path, body: null, ct); private async Task SendAsync( - HttpMethod method, string path, Func? contentFactory, CancellationToken ct) + HttpMethod method, string path, object? body, CancellationToken ct) { if (_tokens is null) throw new InvalidOperationException("Not logged in. Call LoginInteractiveAsync first."); @@ -277,8 +240,8 @@ public class YavscApiClient : IYavscApiClient, IAsyncDisposable await EnsureFreshTokenAsync(ct).ConfigureAwait(false); using var req = new HttpRequestMessage(method, path); - if (contentFactory is not null) - req.Content = contentFactory(); + if (body is not null) + req.Content = JsonContent.Create(body); var response = await Http.SendAsync(req, ct).ConfigureAwait(false); if (response.StatusCode == HttpStatusCode.Unauthorized) @@ -289,8 +252,8 @@ public class YavscApiClient : IYavscApiClient, IAsyncDisposable await ForceRefreshAsync(ct).ConfigureAwait(false); using var retry = new HttpRequestMessage(method, path); - if (contentFactory is not null) - retry.Content = contentFactory(); + if (body is not null) + retry.Content = JsonContent.Create(body); response = await Http.SendAsync(retry, ct).ConfigureAwait(false); } @@ -389,77 +352,6 @@ public class YavscApiClient : IYavscApiClient, IAsyncDisposable _store.Save(_tokens); } - /// - /// Upload a user avatar to the Yavsc API. The server expects a - /// single multipart file named file and validates the image - /// content type before persisting it. - /// - public async Task SetAvatarAsync( - Stream imageStream, - string fileName, - string? contentType = null, - CancellationToken ct = default) - { - if (imageStream is null) - throw new ArgumentNullException(nameof(imageStream)); - if (string.IsNullOrWhiteSpace(fileName)) - throw new ArgumentException("A file name is required.", nameof(fileName)); - - var endpoint = new Uri(new Uri(Settings.ApiUrl.TrimEnd('/') + "/", UriKind.Absolute), "account/set-avatar"); - - await EnsureFreshTokenAsync(ct).ConfigureAwait(false); - - var attemptUpload = async () => - { - if (imageStream.CanSeek) - imageStream.Position = 0; - - using var content = new MultipartFormDataContent(); - using var fileContent = new StreamContent(imageStream); - fileContent.Headers.ContentType = new MediaTypeHeaderValue( - string.IsNullOrWhiteSpace(contentType) ? "application/octet-stream" : contentType); - content.Add(fileContent, "file", fileName); - - using var request = new HttpRequestMessage(HttpMethod.Post, endpoint) - { - Content = content, - }; - - return await Http.SendAsync(request, ct).ConfigureAwait(false); - }; - - var response = await attemptUpload().ConfigureAwait(false); - if (response.StatusCode == HttpStatusCode.Unauthorized) - { - response.Dispose(); - await ForceRefreshAsync(ct).ConfigureAwait(false); - response = await attemptUpload().ConfigureAwait(false); - } - - await EnsureSuccessOrThrowAsync(response, ct).ConfigureAwait(false); - - var payload = await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(payload)) - return "Avatar mis à jour."; - - try - { - using var json = JsonDocument.Parse(payload); - if (json.RootElement.TryGetProperty("message", out var msgEl)) - { - var message = msgEl.GetString(); - if (!string.IsNullOrWhiteSpace(message)) - return message; - } - } - catch (JsonException) - { - // Keep a user-friendly fallback when the API payload is not JSON. - } - - return "Avatar mis à jour."; - } - public async Task LogoutAsync() { _store.Clear(); diff --git a/src/PostIt/PostIt/ViewModels/Settings/AuthenticationSettings.cs b/src/PostIt/PostIt/Settings/AuthenticationSettings.cs similarity index 90% rename from src/PostIt/PostIt/ViewModels/Settings/AuthenticationSettings.cs rename to src/PostIt/PostIt/Settings/AuthenticationSettings.cs index 3c23187e4..ad71063b4 100644 --- a/src/PostIt/PostIt/ViewModels/Settings/AuthenticationSettings.cs +++ b/src/PostIt/PostIt/Settings/AuthenticationSettings.cs @@ -10,7 +10,7 @@ public partial class AuthenticationSettings : ObservableObject /// hand-off in /// (RFC 8252 §7.1). Production Desktop builds use this. /// - public const string DesktopRedirectUri = "postit://callback"; + public const string DefaultDesktopRedirectUri = "postit://callback"; /// /// Redirect URI used by the Android app. The corresponding IntentFilter @@ -18,12 +18,9 @@ public partial class AuthenticationSettings : ObservableObject /// public const string AndroidRedirectUri = "android://postit-signin"; - public const string DefaultAuthority = "https://yavsc.pschneider.fr"; - - public const string DefaultClientId = "postit"; - - public static readonly string[] DefaultScopes = { "blogs", "api" }; + public static string DefaultAuthority { get; internal set; } = "https://yavsc.pschneider.fr"; + public static string DefaultClientId { get; internal set; } = "postit"; [ObservableProperty] public partial string Authority { get; set; } @@ -34,19 +31,15 @@ public partial class AuthenticationSettings : ObservableObject [ObservableProperty] public partial string[] Scopes { get; set; } + /// - /// OAuth redirect URI. Defaults to + /// OAuth redirect URI. Defaults to /// (custom URI scheme) which is the right answer for desktop /// production builds. Mobile platforms must set this to /// before calling LoginAsync. /// [ObservableProperty] - public partial string RedirectUri { get; set; } -#if ANDROID - = AndroidRedirectUri; -#else - = DesktopRedirectUri; -#endif + public partial string RedirectUri { get; set; } = DefaultDesktopRedirectUri; /// /// Space-separated view of . Exists for the diff --git a/src/PostIt/PostIt/ViewLocator.cs b/src/PostIt/PostIt/ViewLocator.cs index 6f8c5d902..e725d0d95 100644 --- a/src/PostIt/PostIt/ViewLocator.cs +++ b/src/PostIt/PostIt/ViewLocator.cs @@ -1,61 +1,35 @@ using System; -using System.Diagnostics.CodeAnalysis; using Avalonia.Controls; using Avalonia.Controls.Templates; using Microsoft.Extensions.DependencyInjection; using PostIt.ViewModels; -using PostIt.ViewModels.Commands; using PostIt.Views; -using PostIt.Views.Blogs; -using PostIt.Views.Commands; namespace PostIt; /// /// Given a view model, returns the corresponding view if possible. /// -[RequiresUnreferencedCode( - "Default implementation of ViewLocator involves reflection which may be trimmed away.", - Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")] + public class ViewLocator : IDataTemplate { + private readonly IServiceProvider _services; + + public ViewLocator(IServiceProvider services) + { + _services = services; + } public Control Build(object? data) { - try - { - return BuildCore(data); - } - catch (Exception ex) - { - return new TextBlock { Text = $"ViewLocator threw: {ex}" }; - } - } - - - private Control BuildCore(object? data) - { - var app = App.Current as App; - var services = app!.ServiceProvider!; return data switch { - BlogsViewModel => services.GetRequiredService(), - Settings => services.GetRequiredService(), - HomePageViewModel => services.GetRequiredService(), - ActivitiesPageViewModel => services.GetRequiredService(), - CommandFormsPageViewModel => services.GetRequiredService(), - BrushViewModel => services.GetRequiredService(), - RdvViewModel => services.GetRequiredService(), - SignaturePageViewModel => services.GetRequiredService(), - AddCircleMemberDialogViewModel => services.GetRequiredService(), - CirclesPageViewModel => services.GetRequiredService(), - PostAclDialogViewModel => services.GetRequiredService(), - BillingQueriesPageViewModel => services.GetRequiredService(), - BillingQueryDetailsPageViewModel => services.GetRequiredService(), - ProviderOngoingRequestsPageViewModel => services.GetRequiredService(), - EstimateEditionPageViewModel => services.GetRequiredService(), + MainPageViewModel => _services.GetRequiredService(), + Settings => _services.GetRequiredService(), + HomePageViewModel => _services.GetRequiredService(), + SignaturePageViewModel => _services.GetRequiredService(), null => new TextBlock { Text = "No view for " }, - _ => new TextBlock { Text = $"No view for {data.GetType().Name}" } + _ => new TextBlock { Text = $"No view for {data.GetType().Name}" } }; } diff --git a/src/PostIt/PostIt/ViewModels/ACL/PostAclDialogViewModel.cs b/src/PostIt/PostIt/ViewModels/ACL/PostAclDialogViewModel.cs deleted file mode 100644 index be1b220fa..000000000 --- a/src/PostIt/PostIt/ViewModels/ACL/PostAclDialogViewModel.cs +++ /dev/null @@ -1,228 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using Yavsc.Blogspot; -using Yavsc.Api.Client; -using Yavsc.Api.Client.Dtos; -using Yavsc.Abstract.BlogSpot; -using Yavsc.Abstract.Identity.Security; -using System.Net.Http; - -namespace PostIt.ViewModels; - -public sealed class PostAclEntry -{ - public long CircleId { get; init; } - public string CircleName { get; init; } = string.Empty; -} - -/// -/// View model for the "Gérer l'ACL" modal of a single blog post. -/// -/// Loads the caller's circles once on construct (the dropdown -/// only shows circles the user owns), then keeps an in-memory list -/// of the ACL entries for the post. / -/// are the only mutating verbs; both -/// refresh the list afterwards so the UI stays in sync with the -/// server. -/// -/// The server is the source of truth: it scopes every -/// endpoint to the caller's uid and rejects ACL grants on posts -/// the caller doesn't own. This VM does not re-validate that — -/// any 403 / 404 will surface as an exception caught by the -/// command and routed to . -/// -public partial class PostAclDialogViewModel : ViewModelBase, IActionStatusViewModel -{ - private readonly BlogAclApiClient _aclClient; - private readonly CircleApiClient _circleClient; - - /// The post whose ACL is being edited. Set by the - /// caller (MainPage) when opening the dialog. - public BlogPostDto Post { get; } - - [ObservableProperty] - public partial ObservableCollection - MyCircles { get; set; } = new(); - - [ObservableProperty] - public partial ObservableCollection - AclEntries { get; set; } = new(); - - [ObservableProperty] - public partial CircleDto? SelectedCircleToAdd { get; set; } - - [ObservableProperty] - public partial bool IsBusy { get; set; } - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Pret."; - - [ObservableProperty] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret."); - - /// - /// Idempotency gate for : the dialog - /// attaches the load trigger in DataContextChanged, - /// which can fire more than once if the page is detached - /// and re-attached (dialog re-use, navigation edge cases) - /// with a different VM. Without this guard, the second load - /// would race against the first and could overwrite - /// mid-edit. Pattern copied from - /// Settings.Load. - /// - private bool _loaded; - - /// True once has run at least - /// once. Exposed for tests; do not bind from XAML. - public bool Loaded => _loaded; - - public PostAclDialogViewModel( - BlogPostDto post, - BlogAclApiClient aclClient, - CircleApiClient circleClient) - { - Post = post ?? throw new ArgumentNullException(nameof(post)); - _aclClient = aclClient ?? throw new ArgumentNullException(nameof(aclClient)); - _circleClient = circleClient ?? throw new ArgumentNullException(nameof(circleClient)); - - AclEntries = new ObservableCollection(post.GetACL().Select(a => ToAclEntry(a.CircleId))); - SelectedCircleToAdd = null; - } - - public override bool CanNavigateNext { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } - public override bool CanNavigatePrevious { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } - - [RelayCommand] - public async Task LoadAsync() - { - if (_loaded) return; - - IsBusy = true; - try - { - // Load circles for the picker. ACL entries come from the - // BlogPostDto detail payload (source of truth for initial state). - var circlesTask = _circleClient.GetMyCirclesAsync(); - await Task.WhenAll(circlesTask); - - var circles = circlesTask.Result ?? new List(); - MyCircles = new ObservableCollection(circles); - - // Resolve labels now that circles are available. - AclEntries = new ObservableCollection(AclEntries.Select(a => ToAclEntry(a.CircleId))); - - - this.SetInfoStatus($"{AclEntries.Count} autorisation(s)"); - _loaded = true; - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - [RelayCommand] - public async Task AddAsync() - { - if (SelectedCircleToAdd is null) - { - this.SetWarningStatus("Sélectionnez un cercle à ajouter"); - return; - } - - IsBusy = true; - try - { - if (AclEntries.Any(a => a.CircleId == SelectedCircleToAdd.Id)) - { - this.SetWarningStatus($"Cercle « {SelectedCircleToAdd.Name} » déjà autorisé"); - return; - } - - var created = await _aclClient.GrantAsync(new PostAccessControlRulePayload - { - CircleId = SelectedCircleToAdd.Id, - BlogPostId = Post.Id - }); - if (created is not null) - { - AclEntries.Add(ToAclEntry(created.CircleId)); - this.SetInfoStatus($"Cercle « {SelectedCircleToAdd.Name} » autorisé"); - } - else - { - this.SetWarningStatus("Autorisation refusée par le serveur"); - } - } - catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Conflict) - { - // Conflict means the link already exists in backend. Resync - // from the dedicated ACL API so the UI reflects server truth. - await ReloadAclEntriesFromServerAsync(); - this.SetWarningStatus($"Cercle « {SelectedCircleToAdd.Name} » déjà autorisé"); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - [RelayCommand] - public async Task RevokeAsync(PostAclEntry? acl) - { - if (acl is null) return; - IsBusy = true; - try - { - await _aclClient.RevokeAsync(acl.CircleId); - var existing = AclEntries.FirstOrDefault(e => e.CircleId == acl.CircleId); - if (existing is not null) - AclEntries.Remove(existing); - this.SetInfoStatus("Autorisation révoquée"); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - private async Task ReloadAclEntriesFromServerAsync() - { - var allAcl = await _aclClient.GetMyAclAsync(); - var currentPostAcl = (allAcl ?? new List()) - .Where(a => a.BlogPostId == Post.Id) - .Select(a => ToAclEntry(a.CircleId)) - .GroupBy(a => a.CircleId) - .Select(g => g.First()) - .ToList(); - AclEntries = new ObservableCollection(currentPostAcl); - } - - private PostAclEntry ToAclEntry(long circleId) - { - var circleName = MyCircles.FirstOrDefault(c => c.Id == circleId)?.Name; - return new PostAclEntry - { - CircleId = circleId, - CircleName = string.IsNullOrWhiteSpace(circleName) ? $"Cercle #{circleId}" : circleName - }; - } -} diff --git a/src/PostIt/PostIt/ViewModels/Activity/ActivitiesPageViewModel.cs b/src/PostIt/PostIt/ViewModels/Activity/ActivitiesPageViewModel.cs deleted file mode 100644 index bd13faf9b..000000000 --- a/src/PostIt/PostIt/ViewModels/Activity/ActivitiesPageViewModel.cs +++ /dev/null @@ -1,278 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Avalonia; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Helpers; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.ViewModels; - -public partial class ActivitiesPageViewModel : ViewModelBase, IActionStatusViewModel -{ - private readonly ActivityApiClient _client; - private readonly BillingApiClient _billingClient; - private bool _syncingSelection; - - [ObservableProperty] - public partial ObservableCollection Activities { get; set; } = new(); - - [ObservableProperty] - public partial ActivityInfo? SelectedActivity { get; set; } - - [ObservableProperty] - public partial ObservableCollection Specializations { get; set; } = new(); - - [ObservableProperty] - public partial ActivityInfo? SelectedSpecialization { get; set; } - - [ObservableProperty] - public partial ObservableCollection Performers { get; set; } = new(); - - [ObservableProperty, NotifyCanExecuteChangedFor(nameof(OpenCommandFormsCommand))] - public partial ActivityUserDisplayItem? SelectedPerformer { get; set; } - - [ObservableProperty] - public partial bool IsBusy { get; set; } - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Choisissez une activité."; - - [ObservableProperty] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Choisissez une activité."); - - public ActivityInfo? CurrentActivity => SelectedSpecialization ?? SelectedActivity; - public string SelectedActivityLabel => SelectedActivity?.Name ?? "(aucune activité)"; - public string CurrentActivityLabel => CurrentActivity?.Name ?? "(aucune)"; - public int CurrentFormCount => CurrentActivity?.Forms?.Count ?? 0; - - public override bool CanNavigateNext - { - get => false; - protected set { _ = value; } - } - - public override bool CanNavigatePrevious - { - get => true; - protected set { _ = value; } - } - - public ActivitiesPageViewModel(ActivityApiClient client, BillingApiClient billingClient) - { - _client = client ?? throw new ArgumentNullException(nameof(client)); - _billingClient = billingClient ?? throw new ArgumentNullException(nameof(billingClient)); - } - - partial void OnSelectedActivityChanged(ActivityInfo? value) - { - if (_syncingSelection) return; - _ = ShowActivitySafeAsync(value); - } - - partial void OnSelectedSpecializationChanged(ActivityInfo? value) - { - if (_syncingSelection) return; - _ = ShowSpecializationSafeAsync(value); - } - - private async Task ShowActivitySafeAsync(ActivityInfo? value) - { - try - { - await ShowActivityAsync(value); - } - catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - this.SetWarningStatus("Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous."); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur: {ex.Message}"); - } - } - - private async Task ShowSpecializationSafeAsync(ActivityInfo? value) - { - try - { - await ShowSpecializationAsync(value); - } - catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - this.SetWarningStatus("Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous."); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur: {ex.Message}"); - } - } - - [RelayCommand] - public async Task RefreshAsync() - { - IsBusy = true; - try - { - var list = await _client.GetCatalogAsync(); - Activities = new ObservableCollection(list ?? new()); - - var first = Activities.FirstOrDefault(); - await ShowActivityAsync(first); - if (first is null) - { - this.SetInfoStatus("Aucune activité disponible."); - } - } - catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - Activities = new ObservableCollection(); - Specializations = new ObservableCollection(); - Performers = new ObservableCollection(); - this.SetWarningStatus("Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous."); - } - catch (Exception ex) - { - Activities = new ObservableCollection(); - Specializations = new ObservableCollection(); - Performers = new ObservableCollection(); - this.SetErrorStatus($"Erreur: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - public async Task ShowActivityAsync(ActivityInfo? activity) - { - _syncingSelection = true; - try - { - SelectedActivity = activity; - SelectedSpecialization = null; - } - finally - { - _syncingSelection = false; - } - - OnPropertyChanged(nameof(CurrentActivity)); - OnPropertyChanged(nameof(SelectedActivityLabel)); - OnPropertyChanged(nameof(CurrentActivityLabel)); - OnPropertyChanged(nameof(CurrentFormCount)); - Specializations = new ObservableCollection(activity?.Children ?? new()); - - if (activity is null) - { - Performers = new ObservableCollection(); - SelectedPerformer = null; - return; - } - - await LoadPerformersAsync(activity); - } - - public async Task ShowSpecializationAsync(ActivityInfo? specialization) - { - _syncingSelection = true; - try - { - SelectedSpecialization = specialization; - } - finally - { - _syncingSelection = false; - } - - OnPropertyChanged(nameof(CurrentActivity)); - OnPropertyChanged(nameof(CurrentActivityLabel)); - OnPropertyChanged(nameof(CurrentFormCount)); - - if (specialization is null) - { - if (SelectedActivity is not null) - { - await LoadPerformersAsync(SelectedActivity); - } - return; - } - - await LoadPerformersAsync(specialization); - } - - private async Task LoadPerformersAsync(ActivityInfo activity) - { - IsBusy = true; - try - { - var list = await _client.GetUsersAsync(activity.Code); - var items = (list ?? new()) - .Select(dto => ActivityUserDisplayItem.FromDto(dto, _client.BuildAvatarXsUrl(dto.UserName))) - .ToList(); - - await Task.WhenAll(items.Select(async item => - { - if (string.IsNullOrWhiteSpace(item.AvatarXsUrl)) - { - return; - } - - if (!Uri.TryCreate(item.AvatarXsUrl, UriKind.Absolute, out var avatarUri)) - { - return; - } - - item.AvatarImage = await ImageHelper.LoadFromWeb(avatarUri); - })); - - Performers = new ObservableCollection(items); - SelectedPerformer = null; - this.SetInfoStatus($"{activity.Name} · {Performers.Count} utilisateur(s)"); - } - catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - Performers = new ObservableCollection(); - SelectedPerformer = null; - this.SetWarningStatus("Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous."); - } - catch (Exception ex) - { - Performers = new ObservableCollection(); - SelectedPerformer = null; - this.SetErrorStatus($"Erreur: {ex.Message}"); - } - finally - { - IsBusy = false; - OpenCommandFormsCommand.NotifyCanExecuteChanged(); - } - } - - private bool CanOpenCommandForms() - => SelectedPerformer is not null && CurrentActivity?.Forms?.Count > 0; - - [RelayCommand(CanExecute = nameof(CanOpenCommandForms))] - private async Task OpenCommandFormsAsync() - { - if (SelectedPerformer is null || CurrentActivity is null) - { - this.SetWarningStatus("Sélectionnez un utilisateur et une activité avec formulaire."); - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - var vm = new CommandFormsPageViewModel(CurrentActivity, SelectedPerformer, _billingClient); - await app.PushPageAsync(vm); - } -} diff --git a/src/PostIt/PostIt/ViewModels/Activity/ActivityUserDisplayItem.cs b/src/PostIt/PostIt/ViewModels/Activity/ActivityUserDisplayItem.cs deleted file mode 100644 index d89a66e96..000000000 --- a/src/PostIt/PostIt/ViewModels/Activity/ActivityUserDisplayItem.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Avalonia.Media.Imaging; -using CommunityToolkit.Mvvm.ComponentModel; -using Yavsc.Abstract.Workflow; - -namespace PostIt.ViewModels; - -public sealed partial class ActivityUserDisplayItem : ObservableObject -{ - public string PerformerId { get; init; } = string.Empty; - public string AvatarXsUrl { get; init; } = string.Empty; - public bool HasPerformerProfile { get; init; } - public string PerformerBadgeLabel { get; init; } = "Profil pro"; - public bool IsPerformerActive { get; init; } - public string PerformerStatusBadgeLabel { get; init; } = "Inactif"; - public string PerformerStatusBadgeBackground { get; init; } = "#FDECEA"; - public string PerformerStatusBadgeBorder { get; init; } = "#C62828"; - public string PerformerStatusBadgeForeground { get; init; } = "#8E0000"; - public string UserName { get; init; } = string.Empty; - public string AvatarFallbackLabel { get; init; } = "?"; - public string WebSite { get; init; } = string.Empty; - public int ExtraActivityCount { get; init; } - public string ExtraActivityLabel { get; init; } = "Pas d'autre activité"; - - [ObservableProperty] - public partial Bitmap? AvatarImage { get; set; } - - public static ActivityUserDisplayItem FromDto(PerformerActivity dto, string avatarXsUrl) - { - return new ActivityUserDisplayItem - { - PerformerId = dto.PerformerId, - AvatarXsUrl = avatarXsUrl, - HasPerformerProfile = dto.HasPerformerProfile, - UserName = dto.UserName, - AvatarFallbackLabel = string.IsNullOrWhiteSpace(dto.UserName) - ? "?" - : dto.UserName.Trim()[0].ToString().ToUpperInvariant(), - WebSite = dto.WebSite, - IsPerformerActive = dto.Active, - PerformerStatusBadgeLabel = dto.Active ? "Actif" : "Inactif", - PerformerStatusBadgeBackground = dto.Active ? "#E6F7EC" : "#FDECEA", - PerformerStatusBadgeBorder = dto.Active ? "#2E7D32" : "#C62828", - PerformerStatusBadgeForeground = dto.Active ? "#1B5E20" : "#8E0000", - ExtraActivityCount = dto.ExtraActivityCount, - ExtraActivityLabel = dto.ExtraActivityCount == 0 - ? "Pas d'autre activité" - : $"Autres spécialisations: {dto.ExtraActivityCount}" - }; - } -} diff --git a/src/PostIt/PostIt/ViewModels/Activity/BillingQueriesPageViewModel.cs b/src/PostIt/PostIt/ViewModels/Activity/BillingQueriesPageViewModel.cs deleted file mode 100644 index aab923d80..000000000 --- a/src/PostIt/PostIt/ViewModels/Activity/BillingQueriesPageViewModel.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Avalonia; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Helpers; -using Yavsc; -using Yavsc.Api.Client; -using Yavsc.Abstract.Workflow; - -namespace PostIt.ViewModels; - -public partial class BillingQueriesPageViewModel : ViewModelBase, IActionStatusViewModel -{ - private readonly BillingApiClient _billingClient; - - public ActivityInfo Activity { get; } - public ActivityUserDisplayItem Performer { get; } - public CommandFormSummary Form { get; } - public bool IsReadOnly { get; } - public bool OngoingOnly { get; } - - [ObservableProperty] - public partial ObservableCollection Queries { get; set; } = new(); - - [ObservableProperty, NotifyCanExecuteChangedFor(nameof(OpenSelectedQueryCommand))] - public partial BillingQueryDisplayItem? SelectedQuery { get; set; } - - [ObservableProperty] - public partial bool IsBusy { get; set; } - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Chargement des commandes..."; - - [ObservableProperty] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Chargement des commandes..."); - - public string Title => IsReadOnly - ? $"Demandes en cours ({Form.Title})" - : $"Commandes {Form.Title}"; - public string ContextLabel => $"{Performer.UserName} · {Activity.Name}"; - public bool CanOpenDetails => !IsReadOnly; - - public override bool CanNavigateNext - { - get => false; - protected set { _ = value; } - } - - public override bool CanNavigatePrevious - { - get => true; - protected set { _ = value; } - } - - public BillingQueriesPageViewModel( - ActivityInfo activity, - ActivityUserDisplayItem performer, - CommandFormSummary form, - BillingApiClient billingClient, - bool isReadOnly = false, - bool ongoingOnly = false) - { - Activity = activity ?? throw new ArgumentNullException(nameof(activity)); - Performer = performer ?? throw new ArgumentNullException(nameof(performer)); - Form = form ?? throw new ArgumentNullException(nameof(form)); - _billingClient = billingClient ?? throw new ArgumentNullException(nameof(billingClient)); - IsReadOnly = isReadOnly; - OngoingOnly = ongoingOnly; - } - - public Task InitializeAsync() => RefreshAsync(); - - private bool CanOpenSelectedQuery() => CanOpenDetails && SelectedQuery is not null; - - [RelayCommand] - public async Task RefreshAsync() - { - IsBusy = true; - try - { - var list = await _billingClient.GetQuerySummariesAsync(Form.ActionName).ConfigureAwait(true); - var filtered = (list ?? new()) - .Where(q => q.ActivityCode == Activity.Code && q.PerformerId == Performer.PerformerId) - .Where(q => !OngoingOnly || IsOngoingStatus(q.Status)) - .OrderByDescending(q => q.EventDate ?? DateTime.MinValue) - .ThenByDescending(q => q.Id) - .Select(BillingQueryDisplayItem.FromDto) - .ToList(); - - Queries = new ObservableCollection(filtered); - this.SetInfoStatus(BuildLoadedStatusMessage(filtered.Count)); - } - catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - Queries = new ObservableCollection(); - this.SetWarningStatus("Accès refusé au billing (scope 'api'). Déconnectez puis reconnectez-vous."); - } - catch (Exception ex) - { - Queries = new ObservableCollection(); - this.SetErrorStatus($"Erreur: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - [RelayCommand(CanExecute = nameof(CanOpenSelectedQuery))] - public async Task OpenSelectedQueryAsync() - { - if (SelectedQuery is null) - { - this.SetWarningStatus("Sélectionnez une commande."); - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - IsBusy = true; - try - { - var details = await _billingClient.GetQueryAsync(Form.ActionName, SelectedQuery.Id).ConfigureAwait(true); - var vm = new BillingQueryDetailsPageViewModel( - Activity, - Performer, - Form, - _billingClient, - details, - IsReadOnly); - await app.PushPageAsync(vm).ConfigureAwait(true); - } - catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - this.SetWarningStatus("Accès refusé au billing (scope 'api'). Déconnectez puis reconnectez-vous."); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur lors de l'ouverture: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - private string BuildLoadedStatusMessage(int count) - { - if (count == 0) - { - return OngoingOnly - ? "Aucune demande en cours pour ce formulaire." - : "Aucune commande trouvée pour ce formulaire."; - } - - if (OngoingOnly) - { - return $"{count} demande(s) en cours chargée(s) (lecture seule)."; - } - - return $"{count} commande(s) chargée(s)."; - } - - private static bool IsOngoingStatus(QueryStatus status) - => status is QueryStatus.Inserted or QueryStatus.Accepted or QueryStatus.InProgress; -} diff --git a/src/PostIt/PostIt/ViewModels/Activity/BillingQueryDetailsPageViewModel.cs b/src/PostIt/PostIt/ViewModels/Activity/BillingQueryDetailsPageViewModel.cs deleted file mode 100644 index cd13daaf1..000000000 --- a/src/PostIt/PostIt/ViewModels/Activity/BillingQueryDetailsPageViewModel.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using Avalonia; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Helpers; -using Yavsc; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.ViewModels; - -public partial class BillingQueryDetailsPageViewModel : ViewModelBase, IActionStatusViewModel -{ - private readonly BillingApiClient _billingClient; - private readonly BillingQueryDetailsDto _details; - - public ActivityInfo Activity { get; } - public ActivityUserDisplayItem Performer { get; } - public CommandFormSummary Form { get; } - public bool IsReadOnly { get; } - - public long Id => _details.Id; - public string Title => $"Detail commande #{_details.Id}"; - public string ContextLabel => $"{Performer.UserName} · {Activity.Name} · {Form.Title}"; - public string StatusLabel => _details.Status.ToString(); - public string StatusGlyph => GetStatusGlyph(_details.Status); - public string StatusBadgeBackground => GetStatusBadgeBackground(_details.Status); - public string StatusBadgeBorder => GetStatusBadgeBorder(_details.Status); - public string StatusBadgeForeground => GetStatusBadgeForeground(_details.Status); - public string TitleForeground => StatusBadgeForeground; - public string BillingCode => _details.BillingCode; - public string Description => EmptyAsPlaceholder(_details.Description, "(sans description)"); - public string Reason => EmptyAsPlaceholder(_details.Reason, "(aucun motif)"); - public string AdditionalInfo => EmptyAsPlaceholder(_details.AdditionalInfo, "(aucune info complementaire)"); - public string ClientId => EmptyAsPlaceholder(_details.ClientId, "(non renseigne)"); - public string EventDateLabel => _details.EventDate?.ToLocalTime().ToString("f") ?? "Date non precisee"; - public string ConsentLabel => _details.Consent ? "Oui" : "Non"; - public string ProvisionalLabel => _details.Provisional.HasValue ? _details.Provisional.Value.ToString("0.00") : "(non renseigne)"; - public string LocationLabel => BuildLocationLabel(_details.Location); - public string PrestationsLabel => BuildPrestationsLabel(_details); - public bool CanEdit => !IsReadOnly; - - [ObservableProperty] - public partial bool IsBusy { get; set; } - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Pret."; - - [ObservableProperty] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret."); - - public override bool CanNavigateNext - { - get => false; - protected set { _ = value; } - } - - public override bool CanNavigatePrevious - { - get => true; - protected set { _ = value; } - } - - public BillingQueryDetailsPageViewModel( - ActivityInfo activity, - ActivityUserDisplayItem performer, - CommandFormSummary form, - BillingApiClient billingClient, - BillingQueryDetailsDto details, - bool isReadOnly) - { - Activity = activity ?? throw new ArgumentNullException(nameof(activity)); - Performer = performer ?? throw new ArgumentNullException(nameof(performer)); - Form = form ?? throw new ArgumentNullException(nameof(form)); - _billingClient = billingClient ?? throw new ArgumentNullException(nameof(billingClient)); - _details = details ?? throw new ArgumentNullException(nameof(details)); - IsReadOnly = isReadOnly; - - this.SetInfoStatus("Details de commande charges."); - } - - [RelayCommand] - private async Task OpenEditorAsync() - { - if (IsReadOnly) - { - this.SetWarningStatus("Mode lecture seule: edition desactivee."); - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - IsBusy = true; - try - { - var vm = Form.CreateCommandPageViewModel(Activity, Performer, _billingClient); - if (vm is null) - { - this.SetWarningStatus("Ce formulaire n'est pas encore pris en charge en edition."); - return; - } - - await vm.InitializeAsync(_details).ConfigureAwait(true); - await app.PushPageAsync(vm).ConfigureAwait(true); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur lors de l'ouverture en edition: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - [RelayCommand] - private async Task BackAsync() - { - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - await app.GoBackAsync().ConfigureAwait(true); - } - - private static string EmptyAsPlaceholder(string? value, string placeholder) - => string.IsNullOrWhiteSpace(value) ? placeholder : value; - - private static string BuildLocationLabel(BillingLocationDto? location) - { - if (location is null) - { - return "(non renseignee)"; - } - - var text = EmptyAsPlaceholder(location.Address, "adresse vide"); - if (location.Latitude.HasValue && location.Longitude.HasValue) - { - text += $" ({location.Latitude.Value:0.####}, {location.Longitude.Value:0.####})"; - } - - return text; - } - - private static string BuildPrestationsLabel(BillingQueryDetailsDto details) - { - if (details.PrestationIds.Count > 0) - { - return string.Join(", ", details.PrestationIds.Select(static id => id.ToString())); - } - - return details.PrestationId.HasValue - ? details.PrestationId.Value.ToString() - : "(aucune)"; - } - - private static string GetStatusBadgeBackground(QueryStatus status) - => status switch - { - QueryStatus.Accepted => "#E6F7EC", - QueryStatus.InProgress => "#FFF4D6", - QueryStatus.Rejected => "#FDECEA", - QueryStatus.Failed => "#ECEFF1", - QueryStatus.Success => "#E8F8EF", - _ => "#EAF3FF", - }; - - private static string GetStatusBadgeBorder(QueryStatus status) - => status switch - { - QueryStatus.Accepted => "#2E7D32", - QueryStatus.InProgress => "#B26A00", - QueryStatus.Rejected => "#C62828", - QueryStatus.Failed => "#607D8B", - QueryStatus.Success => "#1E8E3E", - _ => "#2A5EA8", - }; - - private static string GetStatusBadgeForeground(QueryStatus status) - => status switch - { - QueryStatus.Accepted => "#1B5E20", - QueryStatus.InProgress => "#7A4A00", - QueryStatus.Rejected => "#8E0000", - QueryStatus.Failed => "#37474F", - QueryStatus.Success => "#145A2A", - _ => "#1A4178", - }; - - private static string GetStatusGlyph(QueryStatus status) - => status switch - { - QueryStatus.Accepted => "OK", - QueryStatus.InProgress => "~", - QueryStatus.Rejected => "!", - QueryStatus.Failed => "X", - QueryStatus.Success => "V", - _ => "i", - }; -} \ No newline at end of file diff --git a/src/PostIt/PostIt/ViewModels/Activity/BillingQueryDisplayItem.cs b/src/PostIt/PostIt/ViewModels/Activity/BillingQueryDisplayItem.cs deleted file mode 100644 index bc4373377..000000000 --- a/src/PostIt/PostIt/ViewModels/Activity/BillingQueryDisplayItem.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Yavsc.Api.Client; - -namespace PostIt.ViewModels; - -public sealed class BillingQueryDisplayItem -{ - public long Id { get; init; } - public string Description { get; init; } = string.Empty; - public string Summary { get; init; } = string.Empty; - public string StatusLabel { get; init; } = string.Empty; - public string EventDateLabel { get; init; } = string.Empty; - public string BillingCode { get; init; } = string.Empty; - - public static BillingQueryDisplayItem FromDto(BillingQuerySummaryDto dto) - { - var summary = !string.IsNullOrWhiteSpace(dto.Reason) - ? dto.Reason - : !string.IsNullOrWhiteSpace(dto.AdditionalInfo) - ? dto.AdditionalInfo - : dto.Description; - - return new BillingQueryDisplayItem - { - Id = dto.Id, - Description = string.IsNullOrWhiteSpace(dto.Description) - ? $"Commande #{dto.Id}" - : dto.Description, - Summary = summary, - StatusLabel = dto.Status.ToString(), - EventDateLabel = dto.EventDate?.ToLocalTime().ToString("g") ?? "Date non précisée", - BillingCode = dto.BillingCode, - }; - } -} \ No newline at end of file diff --git a/src/PostIt/PostIt/ViewModels/Activity/CommandFormsPageViewModel.cs b/src/PostIt/PostIt/ViewModels/Activity/CommandFormsPageViewModel.cs deleted file mode 100644 index 533fbc380..000000000 --- a/src/PostIt/PostIt/ViewModels/Activity/CommandFormsPageViewModel.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Threading.Tasks; -using Avalonia; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Helpers; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.ViewModels; - -public partial class CommandFormsPageViewModel : ViewModelBase, IActionStatusViewModel -{ - private readonly BillingApiClient _billingClient; - - public ActivityInfo Activity { get; } - public ActivityUserDisplayItem Performer { get; } - - [ObservableProperty] - public partial ObservableCollection Forms { get; set; } - - [ObservableProperty, NotifyCanExecuteChangedFor(nameof(OpenSelectedFormCommand)), NotifyCanExecuteChangedFor(nameof(OpenQueriesCommand)), NotifyCanExecuteChangedFor(nameof(OpenOngoingQueriesCommand))] - public partial CommandFormSummary? SelectedForm { get; set; } - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Pret."; - - [ObservableProperty] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret."); - - public string Title => $"Formulaires pour {Performer.UserName}"; - public string ContextLabel => $"{Activity.Name} · {Forms.Count} formulaire(s)"; - - public override bool CanNavigateNext - { - get => false; - protected set { _ = value; } - } - - public override bool CanNavigatePrevious - { - get => true; - protected set { _ = value; } - } - - public CommandFormsPageViewModel( - ActivityInfo activity, - ActivityUserDisplayItem performer, - BillingApiClient billingClient) - { - Activity = activity ?? throw new ArgumentNullException(nameof(activity)); - Performer = performer ?? throw new ArgumentNullException(nameof(performer)); - _billingClient = billingClient ?? throw new ArgumentNullException(nameof(billingClient)); - - Forms = new ObservableCollection((activity.Forms ?? new()) - .OrderBy(f => f.Title) - .ThenBy(f => f.ActionName)); - SelectedForm = Forms.FirstOrDefault(); - this.SetStatus( - Forms.Count == 0 - ? "Aucun formulaire n'est disponible pour cette activité." - : "Choisissez le formulaire à utiliser.", - Forms.Count == 0 ? StatusSeverity.Warning : StatusSeverity.Info); - } - - private bool CanOpenSelectedForm() => SelectedForm is not null; - - private bool CanOpenQueries() => SelectedForm is not null; - - private bool CanOpenOngoingQueries() => SelectedForm is not null; - - [RelayCommand(CanExecute = nameof(CanOpenSelectedForm))] - private async Task OpenSelectedFormAsync() - { - if (SelectedForm is null) - { - this.SetWarningStatus("Sélectionnez un formulaire."); - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - var vm = SelectedForm.CreateCommandPageViewModel( - Activity, Performer, _billingClient); - await vm!.InitializeAsync(); - await app.PushPageAsync(vm); - } - - [RelayCommand(CanExecute = nameof(CanOpenQueries))] - private async Task OpenQueriesAsync() - { - if (SelectedForm is null) - { - this.SetWarningStatus("Sélectionnez un formulaire."); - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - var vm = new BillingQueriesPageViewModel(Activity, Performer, SelectedForm, _billingClient); - await vm.InitializeAsync(); - await app.PushPageAsync(vm); - } - - [RelayCommand(CanExecute = nameof(CanOpenOngoingQueries))] - private async Task OpenOngoingQueriesAsync() - { - if (SelectedForm is null) - { - this.SetWarningStatus("Sélectionnez un formulaire."); - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - var vm = new BillingQueriesPageViewModel( - Activity, - Performer, - SelectedForm, - _billingClient, - isReadOnly: true, - ongoingOnly: true); - await vm.InitializeAsync(); - await app.PushPageAsync(vm); - } -} diff --git a/src/PostIt/PostIt/ViewModels/Activity/EstimateEditionPageViewModel.cs b/src/PostIt/PostIt/ViewModels/Activity/EstimateEditionPageViewModel.cs deleted file mode 100644 index 8f007dc08..000000000 --- a/src/PostIt/PostIt/ViewModels/Activity/EstimateEditionPageViewModel.cs +++ /dev/null @@ -1,293 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Avalonia; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Helpers; -using Yavsc.Api.Client; - -namespace PostIt.ViewModels; - -/// -/// Edition d'un devis (Estimate) créé en réponse à une demande -/// client () consultée depuis la -/// page « Mes demandes en cours ». L'envoi poste le devis sur -/// api/v1/estimate; côté serveur, la commande liée -/// () est alors marquée comme -/// validée par le prestataire. -/// -public partial class EstimateEditionPageViewModel : ViewModelBase, IActionStatusViewModel -{ - private readonly EstimateApiClient _estimateClient; - private readonly BillingQuerySummaryDto _query; - - public long QueryId => _query.Id; - public string ClientId => _query.ClientId; - public string BillingCode => _query.BillingCode; - - public string Title => $"Devis — demande #{_query.Id}"; - - public string ContextLabel - => $"Demande #{_query.Id} · {BillingCode} · client {ClientId}"; - - public string QueryDescription => string.IsNullOrWhiteSpace(_query.Description) - ? "(sans description)" - : _query.Description; - - [ObservableProperty] - public partial string EstimateTitle { get; set; } = string.Empty; - - [ObservableProperty] - public partial string EstimateDescription { get; set; } = string.Empty; - - [ObservableProperty] - public partial ObservableCollection Lines { get; set; } = new(); - - [ObservableProperty, NotifyCanExecuteChangedFor(nameof(RemoveLineCommand))] - public partial EstimateLineItemViewModel? SelectedLine { get; set; } - - [ObservableProperty, NotifyCanExecuteChangedFor(nameof(SendCommand))] - public partial bool IsBusy { get; set; } - - /// - /// True une fois le devis accepté par le serveur: l'envoi est - /// désactivé pour éviter les doublons, il ne reste que « Retour ». - /// - [ObservableProperty, NotifyCanExecuteChangedFor(nameof(SendCommand))] - public partial bool HasSent { get; set; } - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Prêt."; - - [ObservableProperty] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Prêt."); - - public decimal Total => Lines.Sum(line => line.LineTotal); - - public string TotalLabel => $"{Total:0.00} {Lines.FirstOrDefault()?.Currency ?? "EUR"}"; - - public string SendLabel => HasSent ? "Devis envoyé" : "Envoyer le devis"; - - public override bool CanNavigateNext - { - get => false; - protected set { _ = value; } - } - - public override bool CanNavigatePrevious - { - get => true; - protected set { _ = value; } - } - - public EstimateEditionPageViewModel(BillingQuerySummaryDto query, EstimateApiClient estimateClient) - { - _query = query ?? throw new ArgumentNullException(nameof(query)); - _estimateClient = estimateClient ?? throw new ArgumentNullException(nameof(estimateClient)); - - EstimateDescription = query.Description ?? string.Empty; - Lines.CollectionChanged += OnLinesCollectionChanged; - - AddLine(); - this.SetInfoStatus("Complétez le devis puis envoyez-le. La demande associée sera validée."); - } - - [RelayCommand] - private void AddLine() - { - var line = new EstimateLineItemViewModel(); - Lines.Add(line); - SelectedLine = line; - } - - private bool CanRemoveLine() => SelectedLine is not null && !IsBusy && !HasSent; - - [RelayCommand(CanExecute = nameof(CanRemoveLine))] - private void RemoveLine() - { - if (SelectedLine is null) - { - return; - } - - var index = Lines.IndexOf(SelectedLine); - Lines.Remove(SelectedLine); - SelectedLine = Lines.Count == 0 - ? null - : Lines[Math.Min(index, Lines.Count - 1)]; - } - - private bool CanSend() => !IsBusy && !HasSent; - - [RelayCommand(CanExecute = nameof(CanSend))] - private async Task SendAsync() - { - if (!TryValidate(out var validationMessage)) - { - this.SetWarningStatus(validationMessage); - return; - } - - IsBusy = true; - try - { - var payload = BuildPayload(); - var created = await _estimateClient.CreateAsync(payload).ConfigureAwait(true); - - HasSent = true; - OnPropertyChanged(nameof(SendLabel)); - this.SetInfoStatus( - $"Devis #{created.Id} envoyé ({created.Bill.Count} ligne(s)). La demande #{QueryId} est validée."); - } - catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - this.SetWarningStatus("Accès refusé à l'API devis (scope 'api'). Déconnectez puis reconnectez-vous."); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur lors de l'envoi du devis: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - [RelayCommand] - private async Task BackAsync() - { - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - await app.GoBackAsync().ConfigureAwait(true); - } - - internal EstimateDto BuildPayload() - { - return new EstimateDto - { - CommandId = QueryId, - ClientId = ClientId, - CommandType = BillingCode, - Title = EstimateTitle.Trim(), - Description = EstimateDescription.Trim(), - Bill = Lines.Select(line => new EstimateLineDto - { - Id = line.Id, - Name = line.Name.Trim(), - Description = line.Description.Trim(), - Count = Math.Max(1, (int)Math.Round(line.Count)), - UnitaryCost = line.UnitaryCost, - Currency = string.IsNullOrWhiteSpace(line.Currency) ? "EUR" : line.Currency.Trim(), - }).ToList(), - }; - } - - private bool TryValidate(out string message) - { - if (string.IsNullOrWhiteSpace(EstimateTitle)) - { - message = "Le titre du devis est requis."; - return false; - } - - if (string.IsNullOrWhiteSpace(ClientId)) - { - message = "La demande sélectionnée n'identifie pas de client."; - return false; - } - - if (string.IsNullOrWhiteSpace(BillingCode)) - { - message = "La demande sélectionnée n'a pas de code de facturation."; - return false; - } - - if (Lines.Count == 0) - { - message = "Ajoutez au moins une ligne au devis."; - return false; - } - - foreach (var line in Lines) - { - if (string.IsNullOrWhiteSpace(line.Name)) - { - message = "Chaque ligne doit avoir un nom."; - return false; - } - - if (line.Name.Trim().Length > 256) - { - message = $"Le nom de la ligne « {line.Name.Trim()[..20]}… » dépasse 256 caractères."; - return false; - } - - if (string.IsNullOrWhiteSpace(line.Description)) - { - message = $"La ligne « {line.Name.Trim()} » doit avoir une description."; - return false; - } - - if (line.Description.Trim().Length > 512) - { - message = $"La description de la ligne « {line.Name.Trim()} » dépasse 512 caractères."; - return false; - } - - if (line.Count < 1) - { - message = $"La quantité de la ligne « {line.Name.Trim()} » doit être d'au moins 1."; - return false; - } - } - - message = string.Empty; - return true; - } - - private void OnLinesCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - if (e.OldItems is not null) - { - foreach (var item in e.OldItems.OfType()) - { - item.PropertyChanged -= OnLinePropertyChanged; - } - } - - if (e.NewItems is not null) - { - foreach (var item in e.NewItems.OfType()) - { - item.PropertyChanged += OnLinePropertyChanged; - } - } - - RaiseTotalsChanged(); - } - - private void OnLinePropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (e.PropertyName is nameof(EstimateLineItemViewModel.LineTotal) - or nameof(EstimateLineItemViewModel.Currency)) - { - RaiseTotalsChanged(); - } - } - - private void RaiseTotalsChanged() - { - OnPropertyChanged(nameof(Total)); - OnPropertyChanged(nameof(TotalLabel)); - } -} diff --git a/src/PostIt/PostIt/ViewModels/Activity/EstimateLineItemViewModel.cs b/src/PostIt/PostIt/ViewModels/Activity/EstimateLineItemViewModel.cs deleted file mode 100644 index 864906cdd..000000000 --- a/src/PostIt/PostIt/ViewModels/Activity/EstimateLineItemViewModel.cs +++ /dev/null @@ -1,35 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; - -namespace PostIt.ViewModels; - -/// -/// Editable estimate line. is exposed as a -/// so it binds directly to -/// NumericUpDown.Value (decimal?); it is rounded back -/// to an integer when the DTO is built. -/// -public partial class EstimateLineItemViewModel : ObservableObject -{ - public long Id { get; set; } - - [ObservableProperty] - public partial string Name { get; set; } = string.Empty; - - [ObservableProperty] - public partial string Description { get; set; } = string.Empty; - - [ObservableProperty, NotifyPropertyChangedFor(nameof(LineTotal))] - [NotifyPropertyChangedFor(nameof(LineTotalLabel))] - public partial decimal Count { get; set; } = 1m; - - [ObservableProperty, NotifyPropertyChangedFor(nameof(LineTotal))] - [NotifyPropertyChangedFor(nameof(LineTotalLabel))] - public partial decimal UnitaryCost { get; set; } - - [ObservableProperty] - public partial string Currency { get; set; } = "EUR"; - - public decimal LineTotal => Count * UnitaryCost; - - public string LineTotalLabel => $"{LineTotal:0.00}"; -} diff --git a/src/PostIt/PostIt/ViewModels/Activity/ProviderOngoingRequestsPageViewModel.cs b/src/PostIt/PostIt/ViewModels/Activity/ProviderOngoingRequestsPageViewModel.cs deleted file mode 100644 index 71e979e66..000000000 --- a/src/PostIt/PostIt/ViewModels/Activity/ProviderOngoingRequestsPageViewModel.cs +++ /dev/null @@ -1,390 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Avalonia; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Helpers; -using Yavsc; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.ViewModels; - -public partial class ProviderOngoingRequestsPageViewModel : ViewModelBase, IActionStatusViewModel -{ - public const string SortByDate = "Date (plus récent d'abord)"; - public const string SortByDateAsc = "Date (plus ancien d'abord)"; - public const string SortByStatus = "Statut (en cours d'abord)"; - - private readonly BillingApiClient _billingClient; - private readonly EstimateApiClient? _estimateClient; - private readonly Settings? _settings; - private List _allQueries = new(); - - [ObservableProperty] - public partial ObservableCollection Queries { get; set; } = new(); - - [ObservableProperty] - public partial string FilterText { get; set; } = string.Empty; - - public IReadOnlyList SortOptions { get; } = new[] - { - SortByDate, - SortByDateAsc, - SortByStatus, - }; - - [ObservableProperty] - public partial string SelectedSortOption { get; set; } = SortByDate; - - [ObservableProperty, NotifyCanExecuteChangedFor(nameof(OpenSelectedQueryCommand))] - [NotifyCanExecuteChangedFor(nameof(OpenSelectedEditorCommand))] - [NotifyCanExecuteChangedFor(nameof(CreateEstimateForSelectedCommand))] - public partial BillingQuerySummaryDto? SelectedQuery { get; set; } - - [ObservableProperty] - public partial bool IsBusy { get; set; } - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Chargement des demandes fournisseur..."; - - [ObservableProperty] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Chargement des demandes fournisseur..."); - - public string Title => "Mes demandes en cours"; - - public override bool CanNavigateNext - { - get => false; - protected set { _ = value; } - } - - public override bool CanNavigatePrevious - { - get => true; - protected set { _ = value; } - } - - public ProviderOngoingRequestsPageViewModel( - BillingApiClient billingClient, - Settings? settings = null, - EstimateApiClient? estimateClient = null) - { - _billingClient = billingClient ?? throw new ArgumentNullException(nameof(billingClient)); - _estimateClient = estimateClient; - _settings = settings; - - if (_settings is not null) - { - var preferredSort = NormalizeSortOption(_settings.ProviderOngoingRequestsSortOption); - if (!string.Equals(preferredSort, SelectedSortOption, StringComparison.Ordinal)) - { - SelectedSortOption = preferredSort; - } - } - } - - public Task InitializeAsync() => RefreshAsync(); - - [RelayCommand] - public async Task RefreshAsync() - { - IsBusy = true; - try - { - var items = await _billingClient.GetProviderOngoingQueriesAsync().ConfigureAwait(true) ?? new(); - _allQueries = items - .Where(x => !string.IsNullOrWhiteSpace(x.BillingCode)) - .OrderByDescending(x => x.EventDate ?? DateTime.MinValue) - .ThenByDescending(x => x.Id) - .ToList(); - - ApplyFilter(); - this.SetInfoStatus(_allQueries.Count == 0 - ? "Aucune demande en cours pour votre profil fournisseur." - : $"{_allQueries.Count} demande(s) en cours chargée(s)."); - } - catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - _allQueries = new List(); - Queries = new ObservableCollection(); - this.SetWarningStatus("Accès refusé au billing (scope 'api'). Déconnectez puis reconnectez-vous."); - } - catch (Exception ex) - { - _allQueries = new List(); - Queries = new ObservableCollection(); - this.SetErrorStatus($"Erreur: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - private bool CanOpenSelectedQuery() => SelectedQuery is not null; - - private bool CanOpenSelectedEditor() => SelectedQuery is not null; - - [RelayCommand(CanExecute = nameof(CanOpenSelectedQuery))] - public async Task OpenSelectedQueryAsync() - { - if (SelectedQuery is null) - { - this.SetWarningStatus("Sélectionnez une demande."); - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - IsBusy = true; - try - { - var details = await _billingClient - .GetQueryAsync(SelectedQuery.BillingCode, SelectedQuery.Id) - .ConfigureAwait(true); - var (activity, performer, form) = BuildNavigationContext(SelectedQuery); - - var vm = new BillingQueryDetailsPageViewModel( - activity, - performer, - form, - _billingClient, - details, - isReadOnly: false); - - await app.PushPageAsync(vm).ConfigureAwait(true); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur lors de l'ouverture: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - [RelayCommand(CanExecute = nameof(CanOpenSelectedEditor))] - public async Task OpenSelectedEditorAsync() - { - if (SelectedQuery is null) - { - this.SetWarningStatus("Sélectionnez une demande."); - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - IsBusy = true; - try - { - var details = await _billingClient - .GetQueryAsync(SelectedQuery.BillingCode, SelectedQuery.Id) - .ConfigureAwait(true); - - var (activity, performer, form) = BuildNavigationContext(SelectedQuery); - var vm = form.CreateCommandPageViewModel(activity, performer, _billingClient); - if (vm is null) - { - this.SetWarningStatus($"Le formulaire '{form.ActionName}' n'est pas pris en charge en édition."); - return; - } - - await vm.InitializeAsync(details).ConfigureAwait(true); - await app.PushPageAsync(vm).ConfigureAwait(true); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur lors de l'ouverture en édition: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - private bool CanCreateEstimateForSelected() => SelectedQuery is not null && _estimateClient is not null; - - [RelayCommand(CanExecute = nameof(CanCreateEstimateForSelected))] - public async Task CreateEstimateForSelectedAsync() - { - if (SelectedQuery is null) - { - this.SetWarningStatus("Sélectionnez une demande."); - return; - } - - if (_estimateClient is null) - { - this.SetWarningStatus("Le client devis n'est pas disponible."); - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - var vm = new EstimateEditionPageViewModel(SelectedQuery, _estimateClient); - await app.PushPageAsync(vm).ConfigureAwait(true); - } - - partial void OnFilterTextChanged(string value) - { - ApplyFilter(); - } - - partial void OnSelectedSortOptionChanged(string value) - { - var normalized = NormalizeSortOption(value); - if (!string.Equals(normalized, value, StringComparison.Ordinal)) - { - SelectedSortOption = normalized; - return; - } - - PersistSortPreference(value); - ApplyFilter(); - } - - private void ApplyFilter() - { - var query = FilterText?.Trim(); - var filtered = string.IsNullOrWhiteSpace(query) - ? _allQueries - : _allQueries.Where(x => - ContainsInsensitive(x.Description, query) - || ContainsInsensitive(x.ActivityCode, query) - || ContainsInsensitive(x.BillingCode, query) - || ContainsInsensitive(x.ClientId, query) - || ContainsInsensitive(x.Status.ToString(), query)) - .ToList(); - - var sorted = ApplySort(filtered); - Queries = new ObservableCollection(sorted); - } - - private List ApplySort(IEnumerable source) - { - if (string.Equals(SelectedSortOption, SortByStatus, StringComparison.Ordinal)) - { - return source - .OrderBy(x => GetStatusRank(x.Status)) - .ThenByDescending(x => x.EventDate ?? DateTime.MinValue) - .ThenByDescending(x => x.Id) - .ToList(); - } - - if (string.Equals(SelectedSortOption, SortByDateAsc, StringComparison.Ordinal)) - { - return source - .OrderBy(x => x.EventDate ?? DateTime.MinValue) - .ThenBy(x => x.Id) - .ToList(); - } - - return source - .OrderByDescending(x => x.EventDate ?? DateTime.MinValue) - .ThenByDescending(x => x.Id) - .ToList(); - } - - private void PersistSortPreference(string selectedSort) - { - if (_settings is null) - { - return; - } - - if (string.Equals(_settings.ProviderOngoingRequestsSortOption, selectedSort, StringComparison.Ordinal)) - { - return; - } - - _settings.ProviderOngoingRequestsSortOption = selectedSort; - - try - { - _settings.Save(); - } - catch - { - this.SetWarningStatus("Le tri a été appliqué, mais sa sauvegarde a échoué."); - } - } - - private static string NormalizeSortOption(string? sortOption) - { - if (string.Equals(sortOption, SortByDate, StringComparison.Ordinal) - || string.Equals(sortOption, SortByDateAsc, StringComparison.Ordinal) - || string.Equals(sortOption, SortByStatus, StringComparison.Ordinal)) - { - return sortOption!; - } - - return SortByDate; - } - - private static int GetStatusRank(QueryStatus status) - => status switch - { - QueryStatus.InProgress => 0, - QueryStatus.Accepted => 1, - QueryStatus.Inserted => 2, - QueryStatus.Success => 3, - QueryStatus.Rejected => 4, - QueryStatus.Failed => 5, - _ => 99, - }; - - private static bool ContainsInsensitive(string? source, string query) - => !string.IsNullOrWhiteSpace(source) - && source.Contains(query, StringComparison.OrdinalIgnoreCase); - - private static (ActivityInfo activity, ActivityUserDisplayItem performer, CommandFormSummary form) - BuildNavigationContext(BillingQuerySummaryDto query) - { - var activity = new ActivityInfo - { - Code = query.ActivityCode, - Name = string.IsNullOrWhiteSpace(query.ActivityCode) - ? "Activité" - : query.ActivityCode, - }; - - var performer = new ActivityUserDisplayItem - { - PerformerId = query.PerformerId, - UserName = "Mon profil fournisseur", - AvatarFallbackLabel = "M", - IsPerformerActive = true, - PerformerStatusBadgeLabel = "Actif", - PerformerStatusBadgeBackground = "#E6F7EC", - PerformerStatusBadgeBorder = "#2E7D32", - PerformerStatusBadgeForeground = "#1B5E20", - }; - - var form = new CommandFormSummary - { - ActionName = query.BillingCode, - Title = query.BillingCode, - }; - - return (activity, performer, form); - } -} diff --git a/src/PostIt/PostIt/ViewModels/Activity/SelectableHairPrestationItem.cs b/src/PostIt/PostIt/ViewModels/Activity/SelectableHairPrestationItem.cs deleted file mode 100644 index ee07bb97a..000000000 --- a/src/PostIt/PostIt/ViewModels/Activity/SelectableHairPrestationItem.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using Yavsc.Models.Haircut; - -namespace PostIt.ViewModels; - -public partial class SelectableHairPrestationItem : ObservableObject -{ - public long Id { get; init; } - public string Title { get; init; } = string.Empty; - public string Details { get; init; } = string.Empty; - - [ObservableProperty] - public partial bool IsSelected { get; set; } - - public static SelectableHairPrestationItem FromDto(HairPrestationDto dto) - => new() - { - Id = dto.Id, - Title = dto.Title, - Details = dto.Details, - }; -} \ No newline at end of file diff --git a/src/PostIt/PostIt/ViewModels/ACL/AddCircleMemberDialogViewModel.cs b/src/PostIt/PostIt/ViewModels/AddCircleMemberDialogViewModel.cs similarity index 81% rename from src/PostIt/PostIt/ViewModels/ACL/AddCircleMemberDialogViewModel.cs rename to src/PostIt/PostIt/ViewModels/AddCircleMemberDialogViewModel.cs index 635073d1a..a721d7385 100644 --- a/src/PostIt/PostIt/ViewModels/ACL/AddCircleMemberDialogViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/AddCircleMemberDialogViewModel.cs @@ -29,7 +29,7 @@ namespace PostIt.ViewModels; /// CirclesPage then calls /// . /// -public partial class AddCircleMemberDialogViewModel : ViewModelBase, IActionStatusViewModel +public partial class AddCircleMemberDialogViewModel : ViewModelBase { private readonly IUserDirectory _directory; @@ -46,10 +46,7 @@ public partial class AddCircleMemberDialogViewModel : ViewModelBase, IActionStat public partial bool IsBusy { get; set; } [ObservableProperty] - public partial string StatusMessage { get; set; } = "Pret."; - - [ObservableProperty] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret."); + public partial string StatusMessage { get; set; } = string.Empty; /// /// Raised when the user confirms a selection. The hosting @@ -82,7 +79,7 @@ public partial class AddCircleMemberDialogViewModel : ViewModelBase, IActionStat if (string.IsNullOrWhiteSpace(SearchQuery)) { Results.Clear(); - this.SetWarningStatus("Tapez un nom ou un email"); + StatusMessage = "Tapez un nom ou un email"; return; } @@ -91,11 +88,11 @@ public partial class AddCircleMemberDialogViewModel : ViewModelBase, IActionStat { var hits = await _directory.SearchAsync(SearchQuery, CancellationToken.None).ConfigureAwait(true); Results = new ObservableCollection(hits ?? Array.Empty()); - this.SetInfoStatus($"{Results.Count} résultat(s)"); + StatusMessage = $"{Results.Count} résultat(s)"; } catch (Exception ex) { - this.SetErrorStatus($"Erreur: {ex.Message}"); + StatusMessage = $"Erreur: {ex.Message}"; } finally { @@ -109,24 +106,13 @@ public partial class AddCircleMemberDialogViewModel : ViewModelBase, IActionStat /// UI from firing an event with a null payload. /// [RelayCommand] - public async Task AddAsync() + public void Add() { if (Selected is null) { - this.SetWarningStatus("Sélectionnez un utilisateur"); + StatusMessage = "Sélectionnez un utilisateur"; return; } Confirmed?.Invoke(this, Selected); - var app = App.Current as App - ?? throw new InvalidOperationException("Application PostIt indisponible."); - await app.GoBackAsync(); - } - - [RelayCommand] - public async Task CloseAsync() - { - var app = App.Current as App - ?? throw new InvalidOperationException("Application PostIt indisponible."); - await app.GoBackAsync(); } } diff --git a/src/PostIt/PostIt/ViewModels/ACL/CirclesPageViewModel.cs b/src/PostIt/PostIt/ViewModels/CirclesPageViewModel.cs similarity index 74% rename from src/PostIt/PostIt/ViewModels/ACL/CirclesPageViewModel.cs rename to src/PostIt/PostIt/ViewModels/CirclesPageViewModel.cs index cd068b4f4..c017c4261 100644 --- a/src/PostIt/PostIt/ViewModels/ACL/CirclesPageViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/CirclesPageViewModel.cs @@ -1,11 +1,9 @@ using System; using System.Collections.ObjectModel; +using System.Linq; using System.Threading.Tasks; -using Avalonia; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Microsoft.Extensions.DependencyInjection; -using PostIt.Helpers; using PostIt.Services; using Yavsc.Api.Client; using Yavsc.Api.Client.Dtos; @@ -34,7 +32,7 @@ namespace PostIt.ViewModels; /// . The "remove" /// command is per-row and runs inline. /// -public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewModel +public partial class CirclesPageViewModel : ViewModelBase { private readonly CircleApiClient _client; @@ -63,10 +61,14 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode public partial bool IsBusy { get; set; } [ObservableProperty] - public partial string StatusMessage { get; set; } = "Pret."; + public partial string StatusMessage { get; set; } = string.Empty; - [ObservableProperty] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret."); + /// + /// Raised when the user wants to add a member to the + /// currently selected circle. The view listens to this + /// event and opens AddCircleMemberDialog. + /// + public event EventHandler? AddMemberRequested; public CirclesPageViewModel(CircleApiClient client) { @@ -102,11 +104,11 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode { var list = await _client.GetMyCirclesAsync(); Circles = new ObservableCollection(list ?? new()); - this.SetInfoStatus($"{Circles.Count} cercle(s)"); + StatusMessage = $"{Circles.Count} cercle(s)"; } catch (Exception ex) { - this.SetErrorStatus($"Erreur: {ex.Message}"); + StatusMessage = $"Erreur: {ex.Message}"; } finally { @@ -114,29 +116,6 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode } } - [RelayCommand] - internal async Task OpenAddMemberAsync() - { - var app = Application.Current as App - ?? throw new InvalidOperationException("Application PostIt indisponible."); - var services = app.ServiceProvider - ?? throw new InvalidOperationException("ServiceProvider PostIt indisponible."); - var directory = services.GetRequiredService(); - AddCircleMemberDialogViewModel model = - new AddCircleMemberDialogViewModel(directory); - // Wire the dialog's Confirmed event to OnAddMemberConfirmedAsync. - // Without this, the dialog's "Ajouter" button fires the event - // into the void: no subscriber, the picked user is silently - // dropped, and nothing is added to the circle. The dialog - // stays open until the user uses the back gesture — which is - // how the user noticed the button was a no-op. - // Async-void is intentional here: Confirmed is an - // EventHandler (returns void), and bridging to the - // async Task OnAddMemberConfirmedAsync requires it. - model.Confirmed += async (_, picked) => - await OnAddMemberConfirmedAsync(_, picked); - await app.PushPageAsync(model); - } /// /// Load the members of one of the caller's circles. The /// server scopes the endpoint with a 404 when the circle @@ -151,11 +130,11 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode { var list = await _client.GetMembersAsync(circleId); Members = new ObservableCollection(list ?? new()); - this.SetInfoStatus($"{Members.Count} membre(s)"); + StatusMessage = $"{Members.Count} membre(s)"; } catch (Exception ex) { - this.SetErrorStatus($"Erreur: {ex.Message}"); + StatusMessage = $"Erreur: {ex.Message}"; Members = new ObservableCollection(); } finally @@ -170,7 +149,7 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode SelectedCircle = null; DraftName = string.Empty; DraftPublic = false; - this.SetInfoStatus("Nouveau cercle"); + StatusMessage = "Nouveau cercle"; } [RelayCommand] @@ -180,7 +159,7 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode SelectedCircle = circle; DraftName = circle.Name; DraftPublic = circle.Public; - this.SetInfoStatus($"Édition de « {circle.Name} »"); + StatusMessage = $"Édition de « {circle.Name} »"; } [RelayCommand] @@ -188,7 +167,7 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode { if (string.IsNullOrWhiteSpace(DraftName)) { - this.SetWarningStatus("Le nom est obligatoire"); + StatusMessage = "Le nom est obligatoire"; return; } @@ -202,22 +181,22 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode Name = DraftName.Trim(), Public = DraftPublic, }); - this.SetStatus( - created is null ? "Création échouée" : $"Cercle « {created.Name} » créé", - created is null ? StatusSeverity.Warning : StatusSeverity.Info); + StatusMessage = created is null + ? "Création échouée" + : $"Cercle « {created.Name} » créé"; } else { SelectedCircle.Name = DraftName.Trim(); SelectedCircle.Public = DraftPublic; await _client.UpdateCircleAsync(SelectedCircle.Id, SelectedCircle); - this.SetInfoStatus($"Cercle « {SelectedCircle.Name} » mis à jour"); + StatusMessage = $"Cercle « {SelectedCircle.Name} » mis à jour"; } await RefreshAsync(); } catch (Exception ex) { - this.SetErrorStatus($"Erreur: {ex.Message}"); + StatusMessage = $"Erreur: {ex.Message}"; } finally { @@ -233,7 +212,7 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode try { await _client.DeleteCircleAsync(circle.Id); - this.SetInfoStatus($"Cercle « {circle.Name} » supprimé"); + StatusMessage = $"Cercle « {circle.Name} » supprimé"; // If the deleted circle was the selected one, // clear the selection so the Members view goes // empty too (the partial setter on @@ -244,7 +223,7 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode } catch (Exception ex) { - this.SetErrorStatus($"Erreur: {ex.Message}"); + StatusMessage = $"Erreur: {ex.Message}"; } finally { @@ -252,6 +231,22 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode } } + /// + /// Fire the event so + /// the view opens AddCircleMemberDialog. The view + /// forwards the dialog's Confirmed event back to + /// . + /// + [RelayCommand] + public void OpenAddMember() + { + if (SelectedCircle is null) + { + StatusMessage = "Sélectionnez d'abord un cercle"; + return; + } + AddMemberRequested?.Invoke(this, EventArgs.Empty); + } /// /// Called by the view when the dialog confirms a @@ -265,7 +260,7 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode try { await _client.AddMemberAsync(SelectedCircle.Id, picked.Id); - this.SetInfoStatus($"« {picked.DisplayName} » ajouté au cercle"); + StatusMessage = $"« {picked.DisplayName} » ajouté au cercle"; await LoadMembersAsync(SelectedCircle.Id); } catch (Exception ex) @@ -280,7 +275,7 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode var msg = ex.Message.Contains("409") || ex.Message.Contains("Conflict") ? "Déjà membre du cercle" : $"Erreur: {ex.Message}"; - this.SetStatus(msg, msg == "Déjà membre du cercle" ? StatusSeverity.Warning : StatusSeverity.Error); + StatusMessage = msg; } finally { @@ -301,11 +296,11 @@ public partial class CirclesPageViewModel : ViewModelBase, IActionStatusViewMode { await _client.RemoveMemberAsync(SelectedCircle.Id, member.Id); Members.Remove(member); - this.SetInfoStatus($"« {member.UserName} » retiré du cercle"); + StatusMessage = $"« {member.UserName} » retiré du cercle"; } catch (Exception ex) { - this.SetErrorStatus($"Erreur: {ex.Message}"); + StatusMessage = $"Erreur: {ex.Message}"; } finally { diff --git a/src/PostIt/PostIt/ViewModels/Commands/BillingCommandPageViewModel.cs b/src/PostIt/PostIt/ViewModels/Commands/BillingCommandPageViewModel.cs deleted file mode 100644 index 5ad2b08f4..000000000 --- a/src/PostIt/PostIt/ViewModels/Commands/BillingCommandPageViewModel.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using Yavsc; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; -using Yavsc.Models.Billing; - -namespace PostIt.ViewModels; - -public abstract partial class BillingCommandPageViewModel : RemoteViewModelBase, IActionStatusViewModel -{ - protected readonly BillingApiClient _billingClient; - - public ActivityInfo Activity { get; } - public ActivityUserDisplayItem Performer { get; } - public CommandFormSummary Form { get; } - - [ObservableProperty] - public partial bool IsBusy { get; set; } - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Pret."; - - [ObservableProperty] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret."); - - [ObservableProperty] - public partial string Reason { get; set; } = string.Empty; - - - - [ObservableProperty] - public partial bool Consent { get; set; } = true; - - - [ObservableProperty] - public partial string AdditionalInfo { get; set; } = string.Empty; - - [ObservableProperty] - public partial long? ExistingQueryId { get; set; } - - [ObservableProperty] - public partial QueryStatus CommandStatus { get; set; } = QueryStatus.Inserted; - - public bool CanUseCurrentLocation => IsSupported && !IsBusy; - - public string Title => Form.Title; - public string PerformerLabel => Performer.UserName; - public string ActivityLabel => Activity.Name; - public virtual bool IsSupported => true; - public string BillingRoute => $"/billing/{Form.ActionName}"; - public bool IsEditingExisting => ExistingQueryId.HasValue; - public string SubmitLabel => IsEditingExisting ? "Mettre à jour la commande" : "Poster la commande"; - public virtual string SupportMessage => $"Le formulaire {Form.ActionName} n'est pas encore pris en charge dans PostIt."; - - public override bool CanNavigateNext - { - get => false; - protected set { _ = value; } - } - - public override bool CanNavigatePrevious - { - get => true; - protected set { _ = value; } - } - - public BillingCommandPageViewModel( - ActivityInfo activity, - ActivityUserDisplayItem performer, - CommandFormSummary form, - BillingApiClient billingClient) - { - Activity = activity ?? throw new ArgumentNullException(nameof(activity)); - Performer = performer ?? throw new ArgumentNullException(nameof(performer)); - Form = form ?? throw new ArgumentNullException(nameof(form)); - _billingClient = billingClient ?? throw new ArgumentNullException(nameof(billingClient)); - - this.SetInfoStatus(SupportMessage); - } - - partial void OnExistingQueryIdChanged(long? value) - { - OnPropertyChanged(nameof(IsEditingExisting)); - OnPropertyChanged(nameof(SubmitLabel)); - } - - partial void OnIsBusyChanged(bool value) - { - OnPropertyChanged(nameof(CanUseCurrentLocation)); - } - - public async Task InitializeAsync(BillingQueryDetailsDto? existingQuery = null) - { - await LoadAsync(); - if (existingQuery is not null) - { - ApplyExistingQuery(existingQuery); - return; - } - } - - protected abstract void ApplyExistingQuery(BillingQueryDetailsDto existingQuery); - - - - [RelayCommand] - protected abstract Task SubmitAsync(); -} diff --git a/src/PostIt/PostIt/ViewModels/Commands/BrushViewModel.cs b/src/PostIt/PostIt/ViewModels/Commands/BrushViewModel.cs deleted file mode 100644 index 72d9ce8ea..000000000 --- a/src/PostIt/PostIt/ViewModels/Commands/BrushViewModel.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; -using Yavsc.Models.Billing; -using Yavsc.Models.Haircut; -namespace PostIt.ViewModels.Commands; - -public partial class BrushViewModel : RdvViewModel -{ - public override string SupportMessage => "Choisissez une prestation coiffure puis postez la commande."; - - [ObservableProperty] - public partial ObservableCollection AvailablePrestations { get; set; } = new(); - - [ObservableProperty] - public partial HairPrestationDto? SelectedPrestation { get; set; } - - public BrushViewModel(ActivityInfo activity, ActivityUserDisplayItem performer, CommandFormSummary form, BillingApiClient billingClient) - : base(activity, performer, form, billingClient) - { - } - - public override async Task LoadAsync() - { - var prestations = await _billingClient.GetHairPrestationsAsync(Form.ActionName); - - AvailablePrestations = new ObservableCollection - (prestations ?? new List()); - - if (SelectedPrestation is null) - { - SelectedPrestation = AvailablePrestations.FirstOrDefault(); - } - - } - - protected override void ApplyExistingQuery(BillingQueryDetailsDto existingQuery) - { - base.ApplyExistingQuery(existingQuery); - - if (existingQuery.PrestationId is not null) - { - SelectedPrestation = AvailablePrestations.FirstOrDefault(x => x.Id == existingQuery.PrestationId.Value); - } - - IsBusy = true; - try - { - if (SelectedPrestation is null) - { - SelectedPrestation = AvailablePrestations.FirstOrDefault(); - } - - this.SetStatus( - AvailablePrestations.Count == 0 - ? "Aucune prestation coiffure disponible." - : SupportMessage, - AvailablePrestations.Count == 0 ? StatusSeverity.Warning : StatusSeverity.Info); - } - catch (HttpRequestException ex) - when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - this.SetWarningStatus("Accès refusé au catalogue de prestations (scope 'api'). Déconnectez puis reconnectez-vous."); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur lors du chargement des prestations: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - protected override async Task SubmitAsync() - { - if (!Consent) - { - this.SetWarningStatus("Le consentement est requis pour poster la commande."); - return; - } - - if (string.IsNullOrWhiteSpace(Address)) - { - this.SetWarningStatus("L'adresse du rendez-vous est requise."); - return; - } - - if (SelectedPrestation is null) - { - this.SetWarningStatus("Sélectionnez une prestation coiffure."); - return; - } - - IsBusy = true; - try - { - var address = Address.Trim(); - var locationPayload = BuildLocationPayload(address, Latitude, Longitude); - - var payload = new BillingQueryDetailsDto - { - Id = ExistingQueryId ?? 0, - BillingCode = Form.ActionName, - ActivityCode = Activity.Code, - PerformerId = Performer.PerformerId, - Consent = Consent, - EventDate = EventDate, - Status = CommandStatus, - Reason = Reason.Trim(), - AdditionalInfo = string.IsNullOrWhiteSpace(AdditionalInfo) ? string.Empty : AdditionalInfo.Trim(), - Location = new BillingLocationDto - { - Address = address, - Latitude = Latitude, - Longitude = Longitude, - } - }; - - payload.PrestationId = SelectedPrestation.Id; - - if (IsEditingExisting) - { - await _billingClient.UpdateAsync(Form.ActionName, ExistingQueryId!.Value, payload).ConfigureAwait(true); - } - else - { - await _billingClient.CreateAsync(Form.ActionName, new - { - ActivityCode = Activity.Code, - PerformerId = Performer.PerformerId, - Consent, - EventDate = (DateTime?)EventDate, - Location = locationPayload, - PrestationId = SelectedPrestation.Id, - AdditionalInfo = string.IsNullOrWhiteSpace(AdditionalInfo) ? null : AdditionalInfo.Trim(), - Status = payload.Status, - }).ConfigureAwait(true); - } - - this.SetInfoStatus(IsEditingExisting - ? $"Commande #{ExistingQueryId} mise à jour sur {BillingRoute} pour {Performer.UserName}." - : $"Commande transmise sur {BillingRoute} pour {Performer.UserName}."); - } - catch (HttpRequestException ex) - when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - this.SetWarningStatus("Accès refusé au billing (scope 'api'). Déconnectez puis reconnectez-vous."); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur lors de l'envoi de la commande: {ex.Message}"); - } - finally - { - IsBusy = false; - } - - } -} diff --git a/src/PostIt/PostIt/ViewModels/Commands/MBrushViewModel.cs b/src/PostIt/PostIt/ViewModels/Commands/MBrushViewModel.cs deleted file mode 100644 index 05481c2c3..000000000 --- a/src/PostIt/PostIt/ViewModels/Commands/MBrushViewModel.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; -using Yavsc.Models.Billing; - -namespace PostIt.ViewModels.Commands; - -public partial class MBrushViewModel : BrushViewModel -{ - public override string SupportMessage => "Choisissez une ou plusieurs prestations coiffure puis postez la commande."; - - [ObservableProperty] - public partial ObservableCollection MultiPrestations { get; set; } = new(); - - public MBrushViewModel(ActivityInfo activity, ActivityUserDisplayItem performer, CommandFormSummary form, BillingApiClient billingClient) - : base(activity, performer, form, billingClient) - { - } - - public override async Task LoadAsync() - { - await base.LoadAsync().ConfigureAwait(true); - MultiPrestations = new ObservableCollection( - AvailablePrestations.Select(SelectableHairPrestationItem.FromDto)); - } - - protected override void ApplyExistingQuery(BillingQueryDetailsDto existingQuery) - { - base.ApplyExistingQuery(existingQuery); - - var selectedIds = existingQuery.PrestationIds is null - ? new HashSet() - : new HashSet(existingQuery.PrestationIds); - - foreach (var item in MultiPrestations) - { - item.IsSelected = selectedIds.Contains(item.Id); - } - } - - protected override async Task SubmitAsync() - { - if (!Consent) - { - this.SetWarningStatus("Le consentement est requis pour poster la commande."); - return; - } - - if (string.IsNullOrWhiteSpace(Address)) - { - this.SetWarningStatus("L'adresse du rendez-vous est requise."); - return; - } - - var selectedPrestations = MultiPrestations.Where(x => x.IsSelected).ToList(); - if (selectedPrestations.Count == 0) - { - this.SetWarningStatus("Sélectionnez au moins une prestation coiffure."); - return; - } - - IsBusy = true; - try - { - var address = Address.Trim(); - var locationPayload = BuildLocationPayload(address, Latitude, Longitude); - - var payload = new BillingQueryDetailsDto - { - Id = ExistingQueryId ?? 0, - BillingCode = Form.ActionName, - ActivityCode = Activity.Code, - PerformerId = Performer.PerformerId, - Consent = Consent, - EventDate = EventDate, - Status = CommandStatus, - Reason = Reason.Trim(), - Location = new BillingLocationDto - { - Address = address, - Latitude = Latitude, - Longitude = Longitude, - }, - PrestationIds = selectedPrestations.Select(x => x.Id).ToList(), - }; - - if (IsEditingExisting) - { - await _billingClient.UpdateAsync(Form.ActionName, ExistingQueryId!.Value, payload).ConfigureAwait(true); - } - else - { - await _billingClient.CreateAsync(Form.ActionName, new - { - ActivityCode = Activity.Code, - PerformerId = Performer.PerformerId, - Consent, - EventDate = EventDate, - Location = locationPayload, - Prestations = selectedPrestations.Select(x => new { PrestationId = x.Id }).ToList(), - Status = payload.Status, - }).ConfigureAwait(true); - } - - this.SetInfoStatus(IsEditingExisting - ? $"Commande #{ExistingQueryId} mise à jour sur {BillingRoute} pour {Performer.UserName}." - : $"Commande transmise sur {BillingRoute} pour {Performer.UserName}."); - } - catch (HttpRequestException ex) - when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - this.SetWarningStatus("Accès refusé au billing (scope 'api'). Déconnectez puis reconnectez-vous."); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur lors de l'envoi de la commande: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } -} diff --git a/src/PostIt/PostIt/ViewModels/Commands/RdvViewModel.cs b/src/PostIt/PostIt/ViewModels/Commands/RdvViewModel.cs deleted file mode 100644 index 4f9d457a5..000000000 --- a/src/PostIt/PostIt/ViewModels/Commands/RdvViewModel.cs +++ /dev/null @@ -1,353 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Services; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.ViewModels.Commands; - -public partial class RdvViewModel : BillingCommandPageViewModel -{ - private long? _existingLocationId; - private bool _hydratingExistingQuery; - - public override string SupportMessage => "Complétez les informations du rendez-vous puis postez la commande."; - - [ObservableProperty] - public partial string Address { get; set; } = string.Empty; - - [ObservableProperty] - public partial string SuggestedAddress { get; set; } = string.Empty; - - [ObservableProperty] - public partial bool IsResolvingAddress { get; set; } - - [ObservableProperty] - public partial double? Latitude { get; set; } - - [ObservableProperty] - public partial double? Longitude { get; set; } - - - [ObservableProperty] - public partial DateTime EventDate { get; set; } - - public DateTimeOffset? EventDateSelection - { - get => new(EventDate); - set - { - if (!value.HasValue) - return; - - EventDate = value.Value.LocalDateTime; - } - } - - public RdvViewModel(ActivityInfo activity, ActivityUserDisplayItem performer, CommandFormSummary form, BillingApiClient billingClient) - : base(activity, performer, form, billingClient) - { - EventDate = DateTime.Now.AddDays(1); - } - - public bool HasSuggestedAddress => !string.IsNullOrWhiteSpace(SuggestedAddress); - public bool HasSuggestedAddressPanel => HasSuggestedAddress || IsResolvingAddress; - - protected override void ApplyExistingQuery(BillingQueryDetailsDto existingQuery) - { - _hydratingExistingQuery = true; - ExistingQueryId = existingQuery.Id; - CommandStatus = existingQuery.Status; - Consent = existingQuery.Consent; - Reason = existingQuery.Reason ?? string.Empty; - AdditionalInfo = existingQuery.AdditionalInfo ?? string.Empty; - - if (existingQuery.EventDate is not null) - { - EventDate = existingQuery.EventDate.Value - .ToLocalTime(); - } - - if (existingQuery.Location is not null) - { - _existingLocationId = existingQuery.Location.Id; - Address = existingQuery.Location.Address ?? string.Empty; - SuggestedAddress = string.Empty; - Latitude = existingQuery.Location.Latitude; - Longitude = existingQuery.Location.Longitude; - } - else - { - _existingLocationId = null; - } - - _hydratingExistingQuery = false; - - this.SetInfoStatus($"Commande #{existingQuery.Id} chargée."); - } - - [RelayCommand(CanExecute = nameof(CanUseCurrentLocation))] - private async Task UseCurrentLocationAsync() - { - if (!CanUseCurrentLocation) - { - return; - } - - IsBusy = true; - try - { - var result = await Platform.TryGetCurrentLocationAsync(default).ConfigureAwait(true); - if (!result.IsSuccess || !result.Latitude.HasValue || !result.Longitude.HasValue) - { - this.SetWarningStatus(result.Message); - return; - } - - Latitude = result.Latitude.Value; - Longitude = result.Longitude.Value; - this.SetInfoStatus(string.IsNullOrWhiteSpace(Address) - ? "Position récupérée. Complétez l'adresse puis envoyez la commande." - : result.Message); - } - catch (OperationCanceledException) - { - this.SetWarningStatus("La récupération de la position a été annulée."); - } - catch (Exception ex) - { - this.SetErrorStatus($"Impossible de récupérer la position: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - protected static BillingLocationDto BuildLocationPayload(string address, double? latitude, double? longitude, long? locationId = null) - { - if (latitude.HasValue && longitude.HasValue) - { - return new BillingLocationDto - { - Id = locationId, - Address = address, - Latitude = latitude.Value, - Longitude = longitude.Value, - }; - } - - return new BillingLocationDto - { - Id = locationId, - Address = address, - }; - } - - public void ApplyLocationFromMap(double latitude, double longitude) - { - Latitude = Math.Round(latitude, 6); - Longitude = Math.Round(longitude, 6); - - if (string.IsNullOrWhiteSpace(Address)) - { - this.SetInfoStatus("Position sélectionnée sur la carte. Complétez l'adresse puis envoyez la commande."); - return; - } - - this.SetInfoStatus("Position sélectionnée sur la carte."); - } - - public void NotifyReverseGeocodingStarted() - { - IsResolvingAddress = true; - this.SetInfoStatus(string.IsNullOrWhiteSpace(Address) - ? "Recherche de l'adresse depuis la carte..." - : "Recherche d'une adresse suggérée..." - ); - } - - public void NotifyReverseGeocodingUnavailable() - { - IsResolvingAddress = false; - if (HasSuggestedAddress || !string.IsNullOrWhiteSpace(Address)) - return; - - this.SetInfoStatus("Position sélectionnée sur la carte. Complétez l'adresse puis envoyez la commande."); - } - - public void ApplyResolvedAddress(string address) - { - if (string.IsNullOrWhiteSpace(address)) - return; - - var trimmedAddress = address.Trim(); - if (string.IsNullOrWhiteSpace(Address)) - { - Address = trimmedAddress; - SuggestedAddress = string.Empty; - IsResolvingAddress = false; - this.SetInfoStatus("Adresse mise à jour depuis la carte."); - return; - } - - if (string.Equals(Address.Trim(), trimmedAddress, StringComparison.Ordinal)) - { - SuggestedAddress = string.Empty; - IsResolvingAddress = false; - return; - } - - SuggestedAddress = trimmedAddress; - IsResolvingAddress = false; - this.SetInfoStatus("Adresse suggérée depuis la carte. Appliquez-la si besoin."); - } - - [RelayCommand(CanExecute = nameof(HasSuggestedAddress))] - private void ApplySuggestedAddress() - { - if (string.IsNullOrWhiteSpace(SuggestedAddress)) - return; - - Address = SuggestedAddress.Trim(); - SuggestedAddress = string.Empty; - IsResolvingAddress = false; - this.SetInfoStatus("Adresse suggérée appliquée."); - } - - partial void OnSuggestedAddressChanged(string value) - { - OnPropertyChanged(nameof(HasSuggestedAddress)); - OnPropertyChanged(nameof(HasSuggestedAddressPanel)); - ApplySuggestedAddressCommand.NotifyCanExecuteChanged(); - } - - partial void OnIsResolvingAddressChanged(bool value) - { - OnPropertyChanged(nameof(HasSuggestedAddressPanel)); - } - - partial void OnEventDateChanged(DateTime value) - { - OnPropertyChanged(nameof(EventDateSelection)); - } - - partial void OnAddressChanged(string value) - { - if (_hydratingExistingQuery) - return; - - _existingLocationId = null; - } - - partial void OnLatitudeChanged(double? value) - { - if (_hydratingExistingQuery) - return; - - _existingLocationId = null; - } - - partial void OnLongitudeChanged(double? value) - { - if (_hydratingExistingQuery) - return; - - _existingLocationId = null; - } - - - protected override async Task SubmitAsync() - { - if (!IsSupported) - { - this.SetWarningStatus(SupportMessage); - return; - } - - if (!Consent) - { - this.SetWarningStatus("Le consentement est requis pour poster la commande."); - return; - } - - if (string.IsNullOrWhiteSpace(Address)) - { - this.SetWarningStatus("L'adresse du rendez-vous est requise."); - return; - } - - - if (string.IsNullOrWhiteSpace(Reason)) - { - this.SetWarningStatus("Le motif du rendez-vous est requis."); - return; - } - - - - IsBusy = true; - try - { - var address = Address.Trim(); - var locationPayload = BuildLocationPayload(address, Latitude, Longitude, IsEditingExisting ? _existingLocationId : null); - - var payload = new BillingQueryDetailsDto - { - Id = ExistingQueryId ?? 0, - BillingCode = Form.ActionName, - ActivityCode = Activity.Code, - PerformerId = Performer.PerformerId, - Consent = Consent, - EventDate = EventDate, - Status = CommandStatus, - Reason = Reason.Trim(), - AdditionalInfo = string.IsNullOrWhiteSpace(AdditionalInfo) ? string.Empty : AdditionalInfo.Trim(), - Location = locationPayload - }; - - if (IsEditingExisting) - { - await _billingClient.UpdateAsync(Form.ActionName, ExistingQueryId!.Value, payload).ConfigureAwait(true); - } - else - { - await _billingClient.CreateAsync(Form.ActionName, new - { - ActivityCode = Activity.Code, - PerformerId = Performer.PerformerId, - Consent, - EventDate = EventDate, - Location = locationPayload, - Reason = payload.Reason, - Status = payload.Status, - }).ConfigureAwait(true); - } - - this.SetInfoStatus(IsEditingExisting - ? $"Commande #{ExistingQueryId} mise à jour sur {BillingRoute} pour {Performer.UserName}." - : $"Commande transmise sur {BillingRoute} pour {Performer.UserName}."); - } - catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - this.SetWarningStatus("Accès refusé au billing (scope 'api'). Déconnectez puis reconnectez-vous."); - } - catch (Exception ex) - { - this.SetErrorStatus($"Erreur lors de l'envoi: {ex.Message}"); - } - finally - { - IsBusy = false; - } - } - - public override Task LoadAsync() - { - return Task.CompletedTask; - } -} diff --git a/src/PostIt/PostIt/ViewModels/HomePageViewModel.cs b/src/PostIt/PostIt/ViewModels/HomePageViewModel.cs new file mode 100644 index 000000000..876f862cc --- /dev/null +++ b/src/PostIt/PostIt/ViewModels/HomePageViewModel.cs @@ -0,0 +1,44 @@ +using CommunityToolkit.Mvvm.Input; +using Microsoft.Extensions.DependencyInjection; +using PostIt; +using PostIt.Services; +namespace PostIt.ViewModels; + +public class HomePageViewModel : ViewModelBase +{ + public YavscApiClient Api { get; } + public Settings Settings { get; } + public SessionStatusViewModel SessionStatus { get; } + + private string _welcomeText = "Welcome to PostIt!"; + public string WelcomeText + { + get => _welcomeText; + set => SetProperty(ref _welcomeText, value); + } + + public override bool CanNavigateNext { get => true; protected set => throw new System.NotImplementedException(); } + public override bool CanNavigatePrevious { get => false; protected set => throw new System.NotImplementedException(); } + + public HomePageViewModel(YavscApiClient api, Settings settings, SessionStatusViewModel sessionStatus) + { + Api = api; + Settings = settings; + SessionStatus = sessionStatus; + + } + public RelayCommand OpenBlogs { get; set; } = new RelayCommand(() => App.PushMainPageAsync()); + /// + /// Avalonia designer constructor. Builds a self-contained VM + /// with a freshly-constructed Settings so the XAML preview can + /// render without a running App. Production paths always reach + /// the parameterised constructor (DI or direct injection), and + /// the postit://callback crash is fixed at the Settings layer + /// (thread-safe dispatcher marshalling on PropertyChanged) — a + /// designer-only duplicate instance is therefore harmless. + /// + public HomePageViewModel() : this(null!, new Settings(), new SessionStatusViewModel()) + { + + } +} diff --git a/src/PostIt/PostIt/ViewModels/Layout/ActionStatusViewModelExtensions.cs b/src/PostIt/PostIt/ViewModels/Layout/ActionStatusViewModelExtensions.cs deleted file mode 100644 index 614d477c8..000000000 --- a/src/PostIt/PostIt/ViewModels/Layout/ActionStatusViewModelExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace PostIt.ViewModels; - -public interface IActionStatusViewModel -{ - string StatusMessage { get; set; } - StatusNotice ActionStatus { get; set; } -} - -public static class ActionStatusViewModelExtensions -{ - public static void SetInfoStatus(this IActionStatusViewModel viewModel, string message) - => viewModel.SetStatus(message, StatusSeverity.Info); - - public static void SetWarningStatus(this IActionStatusViewModel viewModel, string message) - => viewModel.SetStatus(message, StatusSeverity.Warning); - - public static void SetErrorStatus(this IActionStatusViewModel viewModel, string message) - => viewModel.SetStatus(message, StatusSeverity.Error); - - public static void SetStatus(this IActionStatusViewModel viewModel, string message, StatusSeverity severity) - { - var normalizedMessage = string.IsNullOrWhiteSpace(message) ? "Pret." : message.Trim(); - - viewModel.StatusMessage = normalizedMessage; - viewModel.ActionStatus = severity switch - { - StatusSeverity.Error => StatusNotice.Error(normalizedMessage), - StatusSeverity.Warning => StatusNotice.Warning(normalizedMessage), - _ => StatusNotice.Info(normalizedMessage), - }; - } -} \ No newline at end of file diff --git a/src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs b/src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs deleted file mode 100644 index fd2ff1091..000000000 --- a/src/PostIt/PostIt/ViewModels/Layout/HomePageViewModel.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Threading.Tasks; -using Avalonia; -using CommunityToolkit.Mvvm.Input; -using Microsoft.Extensions.DependencyInjection; -using PostIt.Helpers; -using PostIt.Services; -using Yavsc.Api.Client; -namespace PostIt.ViewModels; - -public class HomePageViewModel : ViewModelBase -{ - public YavscApiClient Api { get; } - public Settings Settings { get; } - public SessionStatusViewModel SessionStatus { get; } - - private string _welcomeText = "Welcome to PostIt!"; - public string WelcomeText - { - get => _welcomeText; - set => SetProperty(ref _welcomeText, value); - } - - public override bool CanNavigateNext { get => true; protected set => throw new System.NotImplementedException(); } - public override bool CanNavigatePrevious { get => false; protected set => throw new System.NotImplementedException(); } - - public HomePageViewModel(YavscApiClient api, Settings settings, SessionStatusViewModel sessionStatus) - { - Api = api; - Settings = settings; - SessionStatus = sessionStatus; - - OpenActivities = new AsyncRelayCommand(OpenActivitiesAsync); - OpenProviderRequests = new AsyncRelayCommand(OpenProviderRequestsAsync); - OpenBlogs = new AsyncRelayCommand(App.PushBlogsPageAsync); - } - public IAsyncRelayCommand OpenBlogs { get; } - public IAsyncRelayCommand OpenActivities { get; } - public IAsyncRelayCommand OpenProviderRequests { get; } - - private async Task OpenActivitiesAsync() - { - var app = (App?)Application.Current; - var vm = app?.ServiceProvider?.GetRequiredService(); - if (app is null || vm is null) - { - throw new InvalidOperationException("Activities page is not available."); - } - - await vm.RefreshAsync(); - await app.PushPageAsync(vm); - } - - private async Task OpenProviderRequestsAsync() - { - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - var billingClient = app.ServiceProvider?.GetRequiredService(); - if (billingClient is null) - { - throw new InvalidOperationException("Client billing indisponible."); - } - - var estimateClient = app.ServiceProvider?.GetRequiredService(); - - var vm = new ProviderOngoingRequestsPageViewModel(billingClient, Settings, estimateClient); - await vm.InitializeAsync(); - await app.PushPageAsync(vm); - } - - /// - /// Avalonia designer constructor. Builds a self-contained VM - /// with a freshly-constructed Settings so the XAML preview can - /// render without a running App. Production paths always reach - /// the parameterised constructor (DI or direct injection), and - /// the postit://callback crash is fixed at the Settings layer - /// (thread-safe dispatcher marshalling on PropertyChanged) — a - /// designer-only duplicate instance is therefore harmless. - /// - public HomePageViewModel() : this(null!, new Settings(), new SessionStatusViewModel()) - { - - } -} diff --git a/src/PostIt/PostIt/ViewModels/Layout/StatusNotice.cs b/src/PostIt/PostIt/ViewModels/Layout/StatusNotice.cs deleted file mode 100644 index c87af390c..000000000 --- a/src/PostIt/PostIt/ViewModels/Layout/StatusNotice.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace PostIt.ViewModels; - -public enum StatusSeverity -{ - Info, - Warning, - Error -} - -public sealed class StatusNotice -{ - public string Message { get; } - public StatusSeverity Severity { get; } - public string Glyph { get; } - public string Background { get; } - public string BorderBrush { get; } - public string Foreground { get; } - - private StatusNotice(string message, StatusSeverity severity) - { - Message = string.IsNullOrWhiteSpace(message) ? "Pret." : message; - Severity = severity; - - (Glyph, Background, BorderBrush, Foreground) = severity switch - { - StatusSeverity.Error => ("!", "#7F1D1D", "#C62828", "#e1f0f6"), - StatusSeverity.Warning => ("~", "#7C4A03", "#E6A700", "#eaeaea"), - _ => ("i", "#E8F0FE", "#5B8DEF", "#1E3A8A"), - }; - } - - public static StatusNotice Info(string message) => new(message, StatusSeverity.Info); - public static StatusNotice Warning(string message) => new(message, StatusSeverity.Warning); - public static StatusNotice Error(string message) => new(message, StatusSeverity.Error); -} diff --git a/src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs b/src/PostIt/PostIt/ViewModels/MainPageViewModel.cs similarity index 54% rename from src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs rename to src/PostIt/PostIt/ViewModels/MainPageViewModel.cs index 78be2e789..d1d163068 100644 --- a/src/PostIt/PostIt/ViewModels/Blogs/BlogsViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/MainPageViewModel.cs @@ -1,20 +1,16 @@ using System; using System.Collections.ObjectModel; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Avalonia; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Microsoft.Extensions.DependencyInjection; using Yavsc.Blogspot; using Yavsc.Api.Client; -using Yavsc.Abstract.Files; -using PostIt.Helpers; +using PostIt.Services; namespace PostIt.ViewModels; -public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel +public partial class MainPageViewModel : ViewModelBase { /// Window/tab title. Cosmetic — bound by /// MainPage.axaml if at all. Not the post title. @@ -50,15 +46,15 @@ public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel /// mutable field. Toggling is its own action. [ObservableProperty] public partial bool DraftIsPublished { get; set; } - public bool IsLoaded { get; private set; } + + [ObservableProperty] + public partial ViewModelBase? CurrentViewModel { get; set; } + public Settings SettingsModel { get; } [ObservableProperty] public partial string StatusMessage { get; set; } - [ObservableProperty] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret."); - [ObservableProperty] public partial string SearchText { get; set; } @@ -68,9 +64,6 @@ public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel [ObservableProperty] public partial ObservableCollection FilteredPosts { get; set; } - [ObservableProperty] - public partial ObservableCollection DraftAttachments { get; set; } - [ObservableProperty] public partial BlogPostDto? SelectedPost { get; set; } @@ -80,24 +73,112 @@ public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel [ObservableProperty] public partial Settings Settings { get; private set; } + /// + /// API surface that hits the Yavsc.Blogs deployment at + /// . Owned and constructed by + /// App.axaml.cs so the same client (and its token store) + /// is shared with the login flow. + /// + public BlogApiClient? BlogClient { get; } + + public override bool CanNavigateNext { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + public override bool CanNavigatePrevious { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + + + public MainPageViewModel() + { + Init(null); + SettingsModel = new Settings(); + BlogClient = null; + } + + private void Init(Settings? settings) + { + SearchText = string.Empty; + Posts = new ObservableCollection(); + FilteredPosts = new ObservableCollection(); + SelectedPost = null; + IsBusy = false; + StatusMessage = "Ready"; + // Production path: DI injects the canonical Settings singleton + // and we use it as-is. Test path: tests call this constructor + // without a Settings argument; we fall back to a fresh + // instance so the fixture can build a self-contained VM. + // The previous "?? new Settings()" silently worked in prod + // too, which is what allowed a second Settings instance to + // race the singleton and crash the postit://callback binding + // sink; that crash is fixed in Settings.OnPropertyChanged + // (thread-safe dispatcher marshalling) so the duplicate + // instance is now merely wasteful, not dangerous. + Settings = settings ?? new Settings(); + WindowTitle = "PostIt"; + DraftTitle = string.Empty; + DraftArticle = string.Empty; + DraftIsPublished = false; + CurrentViewModel = this; + } + + /// + /// Test-friendly constructor: caller supplies a pre-built + /// . Production code uses the + /// (Settings, BlogApiClient) overload below. + /// + public MainPageViewModel(BlogApiClient blogClient, Settings? settings = null) + { + SettingsModel = new Settings(); + BlogClient = blogClient ?? throw new ArgumentNullException(nameof(blogClient));; + + Init(settings); + } + + partial void OnSearchTextChanged(string value) => ApplyFilter(); + + partial void OnSelectedPostChanged(BlogPostDto? value) + { + // Mirror the selection into the editor buffer so the + // XAML-bound TextBox/TextEditor show the right content + // when the user clicks a post in the list. When the + // selection is cleared (e.g. after a successful create + // rebinds to the server-issued record, or Delete + // nulls it out), the buffer is reset so the editor + // doesn't show stale content. + DraftTitle = value?.Title ?? string.Empty; + DraftArticle = value?.Article ?? string.Empty; + // Mirror publication state too. Defaults to false on + // null selection so a fresh draft starts unpublished. + DraftIsPublished = value?.IsPublished ?? false; + UpdateCommandStates(); + } + + partial void OnIsBusyChanged(bool value) => UpdateCommandStates(); + + // Save's CanExecute depends on the buffer: the button must + // enable as soon as the user has typed a non-whitespace + // title, regardless of whether a post is selected. + partial void OnDraftTitleChanged(string value) => SaveCommand.NotifyCanExecuteChanged(); + partial void OnDraftArticleChanged(string value) => SaveCommand.NotifyCanExecuteChanged(); + [RelayCommand] - internal async Task RefreshAsync() + internal async Task LoadPosts() { await ExecuteAsync(async () => { - var posts = await BlogClient!.GetPostsAsync(); + var posts = await BlogClient.GetPostsAsync(); Posts.Clear(); foreach (var post in posts.OrderByDescending(p => p.DateModified)) { Posts.Add(post); } ApplyFilter(); - this.SetInfoStatus($"{Posts.Count} billet(s) chargé(s)."); + StatusMessage = $"Loaded {Posts.Count} posts."; }); } [RelayCommand] - internal async Task SaveAsync() + internal void Search() => ApplyFilter(); + + [RelayCommand] + internal async Task Save() { // The button is already disabled when the title is empty // (see CanSave), but the test path (and any programmatic @@ -106,14 +187,12 @@ public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel // than to send a request the server will reject. if (string.IsNullOrWhiteSpace(DraftTitle)) { - this.SetWarningStatus("Le titre est obligatoire."); + StatusMessage = "Title is required."; return; } await ExecuteAsync(async () => { - var attachments = DraftAttachments.ToArray(); - // Build a fresh BlogPostDto from the editor buffer on // every Save — we no longer mutate SelectedPost in // place. The previous behaviour copied the buffer @@ -132,30 +211,12 @@ public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel Article = DraftArticle ?? string.Empty, DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow, - IsPublished = DraftIsPublished }; - var created = await BlogClient!.CreatePostAsync(draft, attachments); + var created = await BlogClient.CreatePostAsync(draft); if (created is not null) { SelectedPost = created; - - if (TryAppendAttachmentLinks(created, attachments)) - { - var linkUpdate = new BlogPostDto - { - Id = created.Id, - AuthorId = created.AuthorId, - Photo = created.Photo, - Title = DraftTitle, - Article = DraftArticle ?? string.Empty, - DateCreated = created.DateCreated, - DateModified = DateTime.UtcNow, - }; - await BlogClient.UpdatePostAsync(created.Id, linkUpdate); - } - - this.SetInfoStatus($"Billet {created.Id} créé."); - DraftAttachments.Clear(); + StatusMessage = $"Created post {created.Id}."; } } else @@ -170,26 +231,8 @@ public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel DateCreated = SelectedPost.DateCreated, DateModified = DateTime.UtcNow, }; - - await BlogClient!.UpdatePostAsync(SelectedPost.Id, update, attachments); - - if (TryAppendAttachmentLinks(SelectedPost, attachments)) - { - var linkUpdate = new BlogPostDto - { - Id = SelectedPost.Id, - AuthorId = SelectedPost.AuthorId, - Photo = SelectedPost.Photo, - Title = DraftTitle, - Article = DraftArticle ?? string.Empty, - DateCreated = SelectedPost.DateCreated, - DateModified = DateTime.UtcNow, - }; - await BlogClient.UpdatePostAsync(SelectedPost.Id, linkUpdate); - } - - this.SetInfoStatus($"Billet {SelectedPost.Id} enregistré."); - DraftAttachments.Clear(); + await BlogClient.UpdatePostAsync(SelectedPost.Id, update); + StatusMessage = $"Saved post {SelectedPost.Id}."; } await RefreshPostsAsync(); @@ -197,18 +240,18 @@ public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel } [RelayCommand] - internal async Task DeleteAsync() + internal async Task Delete() { if (SelectedPost is null || SelectedPost.Id == 0) { - this.SetWarningStatus("Sélectionnez un billet existant avant suppression."); + StatusMessage = "Select an existing post before deleting."; return; } await ExecuteAsync(async () => { - await BlogClient!.DeletePostAsync(SelectedPost.Id); - this.SetInfoStatus($"Billet {SelectedPost.Id} supprimé."); + await BlogClient.DeletePostAsync(SelectedPost.Id); + StatusMessage = $"Deleted post {SelectedPost.Id}."; SelectedPost = null; await RefreshPostsAsync(); }); @@ -229,257 +272,40 @@ public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel /// overload; the dedicated endpoint keeps the wire /// contract clean. /// - public async Task SetPublishStateAsync(bool publish) + [RelayCommand] + internal async Task TogglePublish() { if (SelectedPost is null || SelectedPost.Id == 0) { - this.SetWarningStatus("Sélectionnez un billet existant pour changer sa publication."); + StatusMessage = "Sélectionnez un billet existant pour changer sa publication."; return; } await ExecuteAsync(async () => { - // The checkbox updates DraftIsPublished before the command is - // executed. Using the current bound value avoids the - // double-toggle bug in which the UI has already flipped the - // state and the command flips it again. - await BlogClient!.SetPublishAsync(SelectedPost.Id, publish); - DraftIsPublished = publish; + var desired = !DraftIsPublished; + await BlogClient.SetPublishAsync(SelectedPost.Id, desired); + DraftIsPublished = desired; // Mirror into the selected post so a subsequent // RefreshPostsAsync() doesn't blow away the // locally flipped state until the round-trip // re-hydrates it. - SelectedPost.IsPublished = publish; - this.SetInfoStatus(publish + SelectedPost.IsPublished = desired; + StatusMessage = desired ? $"Billet {SelectedPost.Id} publié." - : $"Billet {SelectedPost.Id} remis en brouillon."); + : $"Billet {SelectedPost.Id} remis en brouillon."; }); } [RelayCommand] - internal async Task TogglePublishAsync() + internal void OpenSettings() { - await SetPublishStateAsync(DraftIsPublished); + CurrentViewModel = SettingsModel; } - /// - /// DEV ONLY: open the signature capture page. The production - /// entry point is a SignalR push from Yavsc.Org ("devis - /// received, sign here"); this command is the dev-time - /// shortcut to reach the page without that infrastructure. - /// Aligned on the same VM-first navigation pattern as - /// : the VM resolves the target VM - /// through , the ViewLocator picks - /// the matching Control at bind time. No - /// Click handler, no App.ServiceProvider - /// access from the view layer. - /// - [RelayCommand] - internal async Task OpenSignatureDevAsync() - { - await ((App)App.Current!).PushPageAsync(SignatureModel).ConfigureAwait(true); - } - - - [RelayCommand(CanExecute = nameof(CanManageAcl))] - public async Task ManageAclAsync() - { - if (SelectedPost is null) - { - this.SetWarningStatus("Sélectionnez un billet existant avant de gérer l'ACL."); - return; - } - - var postForAcl = SelectedPost; - try - { - var detailed = await BlogClient!.GetPostAsync(SelectedPost.Id).ConfigureAwait(true); - if (detailed is not null) - { - postForAcl = detailed; - SelectedPost = detailed; - } - } - catch - { - // Keep the dialog usable even if the detail refresh fails. - } - - await ((App)App.Current!).PushPageAsync(GetACLViewModel(postForAcl)).ConfigureAwait(true); - } - - [RelayCommand] - public async Task OpenCirclesAsync() - { - var circlesVm = ResolveServices().GetRequiredService(); - await ((App)App.Current!).PushPageAsync(circlesVm).ConfigureAwait(true); - } - - private ViewModelBase GetACLViewModel(BlogPostDto selectedPost) - { - var sp = ResolveServices(); - var aclClient = sp.GetRequiredService(); - var circleClient = sp.GetRequiredService(); - return new PostAclDialogViewModel(selectedPost, aclClient, circleClient); - } - - /// - /// API surface that hits the Yavsc.Blogs deployment at - /// . Owned and constructed by - /// App.axaml.cs so the same client (and its token store) - /// is shared with the login flow. - /// - public BlogApiClient? BlogClient { get; } - - /// - /// DI container the VM uses to resolve navigation targets - /// (other ViewModels) when the user clicks a toolbar button - /// that opens a sub-screen. Owned by App.ServiceProvider - /// in production; injected directly in tests. The VM resolves - /// ViewModels via this provider, never Views — the - /// actual to push is decided by - /// at bind time, per CONTRIBUTING.md - /// §"Navigation (PostIt)". - /// - public IServiceProvider? Services { get; } - - private SignaturePageViewModel? _signatureModel; - - /// - /// Resolved on first access. Lazy so the test path (which - /// never pushes SignaturePage) does not require a - /// fully-built DI graph just to construct the VM. Mirrors the - /// pattern of for the Settings case. - /// - public SignaturePageViewModel SignatureModel => - _signatureModel ??= ResolveSignatureModel(); - - public override bool CanNavigateNext { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } - public override bool CanNavigatePrevious { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } - - private SignaturePageViewModel ResolveSignatureModel() - { - var sp = ResolveServices(); - return sp.GetRequiredService(); - } - - private IServiceProvider ResolveServices() - { - return Services ?? (Application.Current as App)?.ServiceProvider ?? - throw new InvalidOperationException( - "No IServiceProvider available for navigation. Inject one in tests " + - "or ensure App.ServiceProvider is initialized in production."); - } - - - public BlogsViewModel() - { - SettingsModel = new Settings(); - Init(SettingsModel); - BlogClient = null; - } - - private void Init(Settings? settings) - { - Posts = new ObservableCollection(); - FilteredPosts = new ObservableCollection(); - DraftAttachments = new ObservableCollection(); - SelectedPost = null; - IsBusy = false; - this.SetInfoStatus("Prêt."); - Settings = settings ?? new Settings(); - SearchText = Settings.SearchText; - WindowTitle = "PostIt"; - DraftTitle = string.Empty; - DraftArticle = string.Empty; - DraftIsPublished = false; - IsLoaded = false; - // Production path: DI injects the canonical Settings singleton - // and we use it as-is. Test path: tests call this constructor - // without a Settings argument; we fall back to a fresh - // instance so the fixture can build a self-contained VM. - // The previous "?? new Settings()" silently worked in prod - // too, which is what allowed a second Settings instance to - // race the singleton and crash the postit://callback binding - // sink; that crash is fixed in Settings.OnPropertyChanged - // (thread-safe dispatcher marshalling) so the duplicate - // instance is now merely wasteful, not dangerous. - - Settings.PropertyChanged += (s, e) => - { - if (e.PropertyName == nameof(Settings.SearchText)) - { - SearchText = Settings.SearchText; - ApplyFilter(); - } - }; - - } - - /// Save is enabled as soon as the user has typed - /// a non-whitespace title in the editor, regardless of - /// whether a post is selected. The "no selection" case is - /// the create-new-post path; the "with selection" case is - /// the update path. Both read from the editor buffer. - /// Previously this also required SelectedPost is not null - /// — which contradicted the create-new-post intent and - /// forced the buggy "draft with empty title" branch. - private bool CanSave() => !IsBusy && !string.IsNullOrWhiteSpace(DraftTitle); - private bool CanDelete() => SelectedPost is not null && SelectedPost.Id != 0 && !IsBusy; - private bool CanManageAcl() => SelectedPost is not null && SelectedPost.Id != 0 && !IsBusy; - - /// - /// Test-friendly constructor: caller supplies a pre-built - /// . Production code uses the - /// (Settings, BlogApiClient) overload below. - /// - public BlogsViewModel(BlogApiClient blogClient, Settings? settings = null, IServiceProvider? services = null) - { - SettingsModel = new Settings(); - BlogClient = blogClient ?? throw new ArgumentNullException(nameof(blogClient)); ; - Services = services; - Init(settings); - } - - partial void OnSearchTextChanged(string value) - { - if (Settings is not null && Settings.SearchText != value) - { - Settings.SearchText = value; - } - ApplyFilter(); - } - - partial void OnSelectedPostChanged(BlogPostDto? value) - { - // Mirror the selection into the editor buffer so the - // XAML-bound TextBox/TextEditor show the right content - // when the user clicks a post in the list. When the - // selection is cleared (e.g. after a successful create - // rebinds to the server-issued record, or Delete - // nulls it out), the buffer is reset so the editor - // doesn't show stale content. - DraftTitle = value?.Title ?? string.Empty; - DraftArticle = value?.Article ?? string.Empty; - // Mirror publication state too. Defaults to false on - // null selection so a fresh draft starts unpublished. - DraftIsPublished = value?.IsPublished ?? false; - DraftAttachments.Clear(); - UpdateCommandStates(); - } - - partial void OnIsBusyChanged(bool value) => UpdateCommandStates(); - - // Save's CanExecute depends on the buffer: the button must - // enable as soon as the user has typed a non-whitespace - // title, regardless of whether a post is selected. - partial void OnDraftTitleChanged(string value) => SaveCommand.NotifyCanExecuteChanged(); - partial void OnDraftArticleChanged(string value) => SaveCommand.NotifyCanExecuteChanged(); - - private async Task RefreshPostsAsync() { - var posts = await BlogClient!.GetPostsAsync(); + var posts = await BlogClient.GetPostsAsync(); Posts.Clear(); foreach (var post in posts.OrderByDescending(p => p.DateModified)) { @@ -517,12 +343,12 @@ public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel try { IsBusy = true; - this.SetInfoStatus("Traitement en cours..."); + StatusMessage = "Working..."; await action(); } catch (Exception ex) { - this.SetErrorStatus($"Erreur: {ex.Message}"); + StatusMessage = $"Error: {ex.Message}"; } finally { @@ -532,69 +358,47 @@ public partial class BlogsViewModel : ViewModelBase, IActionStatusViewModel private void UpdateCommandStates() { - RefreshCommand.NotifyCanExecuteChanged(); + LoadPostsCommand.NotifyCanExecuteChanged(); SaveCommand.NotifyCanExecuteChanged(); DeleteCommand.NotifyCanExecuteChanged(); } + /// Save is enabled as soon as the user has typed + /// a non-whitespace title in the editor, regardless of + /// whether a post is selected. The "no selection" case is + /// the create-new-post path; the "with selection" case is + /// the update path. Both read from the editor buffer. + /// Previously this also required SelectedPost is not null + /// — which contradicted the create-new-post intent and + /// forced the buggy "draft with empty title" branch. + private bool CanSave() => !IsBusy && !string.IsNullOrWhiteSpace(DraftTitle); + private bool CanDelete() => SelectedPost is not null && SelectedPost.Id != 0 && !IsBusy; + private bool CanManageAcl() => SelectedPost is not null && SelectedPost.Id != 0 && !IsBusy; - internal async Task InitializeAsync() + /// + /// Raised when the user asks to open the "manage ACL" dialog for + /// the currently selected post. The MainPage code-behind + /// listens to this event and pushes a PostAclDialog on the + /// navigation stack. The VM itself can't navigate directly + /// because the navigation surface (NavigationPage) lives + /// in the View layer. + /// + public event EventHandler? ManageAclRequested; + + [RelayCommand(CanExecute = nameof(CanManageAcl))] + public void ManageAcl() { - if (!IsLoaded) - { - await RefreshAsync(); - IsLoaded = true; - } + if (SelectedPost is null) return; + ManageAclRequested?.Invoke(this, SelectedPost); } - private bool TryAppendAttachmentLinks(BlogPostDto post, IReadOnlyCollection attachments) - { - if (attachments.Count == 0) - return false; + /// + /// Raised when the user asks to open the circles page (full + /// CRUD on their own circles). Same routing as + /// . + /// + public event EventHandler? OpenCirclesRequested; - var ownerSegment = post.Author?.UserName; - if (string.IsNullOrWhiteSpace(ownerSegment)) - ownerSegment = post.AuthorId; - - if (string.IsNullOrWhiteSpace(ownerSegment)) - return false; - - var article = DraftArticle ?? string.Empty; - var links = new List(); - - foreach (var attachment in attachments) - { - var relativePath = $"{EscapePathSegment(ownerSegment)}/blogs/{post.Id}/{EscapePathSegment(attachment.FileName)}"; - var fileUrl = ResolveUserFileUrl(relativePath); - var markdownLine = $"- [{attachment.FileName}]({fileUrl})"; - - if (!article.Contains(markdownLine, StringComparison.Ordinal)) - links.Add(markdownLine); - } - - if (links.Count == 0) - return false; - - var prefix = article.Length == 0 - ? "" - : (article.EndsWith("\n", StringComparison.Ordinal) ? "\n" : "\n\n"); - - DraftArticle = article + prefix + string.Join("\n", links); - return true; - } - - private string ResolveUserFileUrl(string relativePath) - { - var authority = Settings?.Authentication?.Authority; - if (!string.IsNullOrWhiteSpace(authority) - && Uri.TryCreate(authority, UriKind.Absolute, out var baseUri)) - { - return FileServerUrlHelpers.GetUserFilesUri(baseUri, relativePath).ToString(); - } - - return $"{Yavsc.Constants.UserFilesPath}/{relativePath}"; - } - - private static string EscapePathSegment(string segment) - => Uri.EscapeDataString(segment); + [RelayCommand] + public void OpenCircles() => OpenCirclesRequested?.Invoke(this, EventArgs.Empty); } diff --git a/src/PostIt/PostIt/ViewModels/PostAclDialogViewModel.cs b/src/PostIt/PostIt/ViewModels/PostAclDialogViewModel.cs new file mode 100644 index 000000000..68b96b7cf --- /dev/null +++ b/src/PostIt/PostIt/ViewModels/PostAclDialogViewModel.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Yavsc.Blogspot; +using Yavsc.Api.Client; +using Yavsc.Api.Client.Dtos; + +namespace PostIt.ViewModels; + +/// +/// View model for the "Gérer l'ACL" modal of a single blog post. +/// +/// Loads the caller's circles once on construct (the dropdown +/// only shows circles the user owns), then keeps an in-memory list +/// of the ACL entries for the post. / +/// are the only mutating verbs; both +/// refresh the list afterwards so the UI stays in sync with the +/// server. +/// +/// The server is the source of truth: it scopes every +/// endpoint to the caller's uid and rejects ACL grants on posts +/// the caller doesn't own. This VM does not re-validate that — +/// any 403 / 404 will surface as an exception caught by the +/// command and routed to . +/// +public partial class PostAclDialogViewModel : ViewModelBase +{ + private readonly BlogAclApiClient _aclClient; + private readonly CircleApiClient _circleClient; + + /// The post whose ACL is being edited. Set by the + /// caller (MainPage) when opening the dialog. + public BlogPostDto Post { get; } + + [ObservableProperty] + public partial ObservableCollection MyCircles { get; set; } = new(); + + [ObservableProperty] + public partial ObservableCollection AclEntries { get; set; } = new(); + + [ObservableProperty] + public partial CircleDto? SelectedCircleToAdd { get; set; } + + [ObservableProperty] + public partial bool IsBusy { get; set; } + + [ObservableProperty] + public partial string StatusMessage { get; set; } = string.Empty; + + public PostAclDialogViewModel( + BlogPostDto post, + BlogAclApiClient aclClient, + CircleApiClient circleClient) + { + Post = post ?? throw new ArgumentNullException(nameof(post)); + _aclClient = aclClient ?? throw new ArgumentNullException(nameof(aclClient)); + _circleClient = circleClient ?? throw new ArgumentNullException(nameof(circleClient)); + } + + public override bool CanNavigateNext { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + public override bool CanNavigatePrevious { get => throw new NotImplementedException(); protected set => throw new NotImplementedException(); } + + [RelayCommand] + public async Task LoadAsync() + { + IsBusy = true; + try + { + // Load circles and ACL entries in parallel — both are + // independent reads on the same host. The caller's uid + // is implicit in both endpoints. + var circlesTask = _circleClient.GetMyCirclesAsync(); + var aclTask = _aclClient.GetMyAclAsync(); + await Task.WhenAll(circlesTask, aclTask); + + var circles = circlesTask.Result ?? new List(); + MyCircles = new ObservableCollection(circles); + + var allAcl = aclTask.Result ?? new List(); + AclEntries = new ObservableCollection( + allAcl.Where(a => a.BlogPostId == Post.Id)); + + StatusMessage = $"{AclEntries.Count} autorisation(s)"; + } + catch (Exception ex) + { + StatusMessage = $"Erreur: {ex.Message}"; + } + finally + { + IsBusy = false; + } + } + + [RelayCommand] + public async Task AddAsync() + { + if (SelectedCircleToAdd is null) + { + StatusMessage = "Sélectionnez un cercle à ajouter"; + return; + } + + IsBusy = true; + try + { + var created = await _aclClient.GrantAsync(new CircleAuthorizationDto + { + CircleId = SelectedCircleToAdd.Id, + BlogPostId = Post.Id, + Comment = false, + }); + if (created is not null) + { + AclEntries.Add(created); + StatusMessage = $"Cercle « {SelectedCircleToAdd.Name} » autorisé"; + } + else + { + StatusMessage = "Autorisation refusée par le serveur"; + } + } + catch (Exception ex) + { + StatusMessage = $"Erreur: {ex.Message}"; + } + finally + { + IsBusy = false; + } + } + + [RelayCommand] + public async Task RevokeAsync(CircleAuthorizationDto? acl) + { + if (acl is null) return; + IsBusy = true; + try + { + await _aclClient.RevokeAsync(acl.CircleId); + AclEntries.Remove(acl); + StatusMessage = "Autorisation révoquée"; + } + catch (Exception ex) + { + StatusMessage = $"Erreur: {ex.Message}"; + } + finally + { + IsBusy = false; + } + } +} diff --git a/src/PostIt/PostIt/ViewModels/RemoteViewModelBase.cs b/src/PostIt/PostIt/ViewModels/RemoteViewModelBase.cs deleted file mode 100644 index 0229cca49..000000000 --- a/src/PostIt/PostIt/ViewModels/RemoteViewModelBase.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Threading.Tasks; - -namespace PostIt.ViewModels; - -public abstract class RemoteViewModelBase : ViewModelBase -{ - public abstract Task LoadAsync(); - - -} diff --git a/src/PostIt/PostIt/ViewModels/Layout/SessionStatusViewModel.cs b/src/PostIt/PostIt/ViewModels/SessionStatusViewModel.cs similarity index 87% rename from src/PostIt/PostIt/ViewModels/Layout/SessionStatusViewModel.cs rename to src/PostIt/PostIt/ViewModels/SessionStatusViewModel.cs index f2496b857..55f2cab45 100644 --- a/src/PostIt/PostIt/ViewModels/Layout/SessionStatusViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/SessionStatusViewModel.cs @@ -2,8 +2,6 @@ using System; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using Microsoft.Extensions.DependencyInjection; -using PostIt.Helpers; using PostIt.Services; namespace PostIt.ViewModels; @@ -34,6 +32,15 @@ public partial class SessionStatusViewModel : ViewModelBase /// HomePage so the user lands on the blog editor. public event System.Action? LoginSucceeded; + /// Raised when the user clicks the "Paramètres" button on + /// the session banner. App.axaml.cs listens and pushes + /// SettingsPage (resolved from DI, bound to the canonical + /// Settings singleton) on top of the current navigation + /// stack. Same event pattern as and + /// so the VM stays decoupled from + /// NavigationPage / window lifetime. + public event System.Action? OpenSettingsRequested; + [ObservableProperty] public partial bool IsLoggedIn { get; private set; } @@ -137,10 +144,9 @@ public partial class SessionStatusViewModel : ViewModelBase } [RelayCommand] - internal async Task OpenSettings() + public async System.Threading.Tasks.Task OpenSettingsCommand() { - var app = (App)App.Current!; - await app.PushPageAsync(app.ServiceProvider!.GetRequiredService()).ConfigureAwait(true); + OpenSettingsRequested?.Invoke(); + await System.Threading.Tasks.Task.CompletedTask; } - } diff --git a/src/PostIt/PostIt/ViewModels/Settings/Settings.cs b/src/PostIt/PostIt/ViewModels/Settings.cs similarity index 70% rename from src/PostIt/PostIt/ViewModels/Settings/Settings.cs rename to src/PostIt/PostIt/ViewModels/Settings.cs index 35d5a59fe..890ca15cb 100644 --- a/src/PostIt/PostIt/ViewModels/Settings/Settings.cs +++ b/src/PostIt/PostIt/ViewModels/Settings.cs @@ -2,12 +2,13 @@ using System.Runtime.CompilerServices; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using IdentityModel.OidcClient; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Text.Json; -using System.Text.Json.Serialization; +using System.Threading; [assembly: InternalsVisibleTo("PostIt.Tests")] @@ -15,8 +16,71 @@ namespace PostIt.ViewModels; public partial class Settings : ViewModelBase { - [JsonIgnore] - public string? SettingsFileFullName { get; private set; } + const string SettingsFileName = "postit-settings.json"; + + /// + /// Redirect URI used by the Android app. The corresponding IntentFilter + /// in PostIt.Android/Properties/AndroidManifest.xml must match. + /// + public const string AndroidRedirectUri = "android://postit-signin"; + + + + /// + /// Process-wide canonical instance, wired up + /// at application boot by + /// through . The hybrid pattern: + /// + /// The static Current reference gives + /// ViewModels a non-DI way to reach the same instance (and lets + /// the framework bindings push notifications through one stable + /// ). + /// Tests that want to exercise a clean + /// instance still call new Settings(); Current + /// stays null in those contexts because + /// is never invoked. + /// Reads () are + /// thread-safe and never allocate; mutations always go through + /// the DI-resolved singleton so two threads cannot each register + /// a different "current" Settings. + /// + /// + private static Settings? s_current; + + /// + /// Wire the canonical Settings instance to a DI container. Called + /// exactly once from App.axaml.cs after the singleton has + /// been registered. Subsequent calls are no-ops: the DI container + /// owns the instance lifetime and we don't want a stray + /// BindToServiceProvider in a test fixture to silently + /// rebind the production instance. + /// + public static void BindToServiceProvider(IServiceProvider services) + { + if (services is null) throw new ArgumentNullException(nameof(services)); + Interlocked.CompareExchange(ref s_current, + services.GetService() ?? throw new InvalidOperationException( + "Settings is not registered in the DI container."), + null); + } + + /// + /// Returns the canonical Settings instance previously bound through + /// , or null when called + /// outside a running Avalonia application (tests, CLI tools). + /// + public static Settings? GetCurrent() => Volatile.Read(ref s_current); + + /// + /// Resolve the canonical Settings instance or throw. Use this in + /// production code paths that must not silently fall back to a + /// freshly-constructed (which used to be + /// the root cause of the postit://callback crash: two Settings + /// instances racing on PropertyChanged from different threads). + /// + public static Settings RequireCurrent() => + GetCurrent() ?? throw new InvalidOperationException( + "Settings.Current is not bound. Call App.OnFrameworkInitializationCompleted first."); [ObservableProperty] public partial AuthenticationSettings Authentication { get; set; } = new(); @@ -28,88 +92,14 @@ public partial class Settings : ViewModelBase public partial string BlogsApiUrl { get; set; } = "https://blogs.pschneider.fr/api/v1/"; [ObservableProperty] - public partial string ApiUrl { get; set; } = "https://api.pschneider.fr/api/v1/"; - - [ObservableProperty] - public partial string SearchText { get; set; } = string.Empty; - - [ObservableProperty] - public partial string ProviderOngoingRequestsSortOption { get; set; } = string.Empty; - - [ObservableProperty] - [JsonIgnore] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret."); - - - public bool Loaded { get; private set; } = false; - - - /// - /// True when the in-memory state has drifted from the last - /// or snapshot. The - /// Settings page binds the Sauver button's IsEnabled to - /// this flag, so it only enables when the user has actually - /// touched something since the last load / save. Cleared by - /// (and by ), set by - /// every successful setter on the four top-level mutable - /// properties and on the sub-properties of - /// . - /// - [ObservableProperty] - public partial bool IsDirty { get; private set; } = false; - - - /// - /// Guards every mutation of the observable state. [ObservableProperty] - /// generates setters that call SetProperty(...) which fires - /// PropertyChanged. Avalonia bindings consume that event on - /// the UI thread, and a stray background-thread update is exactly - /// what crashed DataValidationErrors.SetErrors on - /// postit://callback re-launches. The lock makes mutations - /// atomic; - /// then marshals the notification onto the UI thread so bindings - /// observe the change on the right thread. - /// - private readonly object _mutationGate = new(); - - /// - /// Scopes the PostIt client always requires from the OIDC provider, - /// regardless of what the user has in their settings file. - /// - /// PostIt calls into the Blog API (and any other Yavsc API - /// gated by an [Authorize("…Scope")] policy) and is silent - /// about the contract: a missing scope here surfaces as a 401 - /// on the very first API call after login, with no obvious link - /// to the settings. The "feature" scopes the user must opt into - /// (e.g. blogs) are still their choice — we only force the - /// structural ones that OIDC itself needs. - /// - private static readonly string[] BuiltInScopes = new[] - { - "openid", // OIDC: required for the id_token - "profile", // OIDC: standard profile claims - "offline_access", // OIDC: required to receive a refresh_token - "blogs", - "api" - }; - private readonly string DEFAULT_SETTINGS_FILENAME = "postit-settings.json"; - - public void SetActionStatus(string message, StatusSeverity severity = StatusSeverity.Info) - { - ActionStatus = severity switch - { - StatusSeverity.Error => StatusNotice.Error(message), - StatusSeverity.Warning => StatusNotice.Warning(message), - _ => StatusNotice.Info(message), - }; - } + public partial string BusinessApiUrl { get; set; } = "https://business.pschneider.fr/api/v1/"; /// /// Catch top-level mutations: the four ObservableProperty /// setters above all funnel through here, and we flip /// in lock-step. Sub-property mutations /// (e.g. Authentication.Authority) are caught by the - /// subscription wired up in + /// subscription wired up in /// below. disables the flag during bulk /// hydration so the disk load itself does not count as a user /// edit. @@ -118,9 +108,7 @@ public partial class Settings : ViewModelBase partial void OnDarkModeChanged(bool value) => MarkDirty(); partial void OnBlogsApiUrlChanged(string value) => MarkDirty(); - partial void OnApiUrlChanged(string value) => MarkDirty(); - partial void OnSearchTextChanged(string value) => MarkDirty(); - partial void OnProviderOngoingRequestsSortOptionChanged(string value) => MarkDirty(); + partial void OnBusinessApiUrlChanged(string value) => MarkDirty(); /// /// Authentication can be reassigned wholesale by @@ -141,6 +129,35 @@ public partial class Settings : ViewModelBase MarkDirty(); } + public bool Loaded { get; private set; } = false; + + /// + /// True when the in-memory state has drifted from the last + /// or snapshot. The + /// Settings page binds the Sauver button's IsEnabled to + /// this flag, so it only enables when the user has actually + /// touched something since the last load / save. Cleared by + /// (and by ), set by + /// every successful setter on the four top-level mutable + /// properties and on the sub-properties of + /// . + /// + [ObservableProperty] + public partial bool IsDirty { get; private set; } = false; + + /// + /// Guards every mutation of the observable state. [ObservableProperty] + /// generates setters that call SetProperty(...) which fires + /// PropertyChanged. Avalonia bindings consume that event on + /// the UI thread, and a stray background-thread update is exactly + /// what crashed DataValidationErrors.SetErrors on + /// postit://callback re-launches. The lock makes mutations + /// atomic; + /// then marshals the notification onto the UI thread so bindings + /// observe the change on the right thread. + /// + private readonly object _mutationGate = new(); + /// /// Build OidcClient options configured for Authorization Code + PKCE /// (no client secret). The browser implementation should be supplied @@ -155,8 +172,6 @@ public partial class Settings : ViewModelBase // build options from a torn read. lock (_mutationGate) { - EnsureAuthenticationDefaultsLocked(); - var options = new OidcClientOptions { Authority = Authentication.Authority, @@ -185,25 +200,24 @@ public partial class Settings : ViewModelBase } } - private void EnsureAuthenticationDefaultsLocked() + /// + /// Scopes the PostIt client always requires from the OIDC provider, + /// regardless of what the user has in their settings file. + /// + /// PostIt calls into the Blog API (and any other Yavsc API + /// gated by an [Authorize("…Scope")] policy) and is silent + /// about the contract: a missing scope here surfaces as a 401 + /// on the very first API call after login, with no obvious link + /// to the settings. The "feature" scopes the user must opt into + /// (e.g. blogs) are still their choice — we only force the + /// structural ones that OIDC itself needs. + /// + private static readonly string[] BuiltInScopes = new[] { - Authentication ??= new AuthenticationSettings(); - - if (string.IsNullOrWhiteSpace(Authentication.Authority)) - Authentication.Authority = AuthenticationSettings.DefaultAuthority; - - if (string.IsNullOrWhiteSpace(Authentication.ClientId)) - Authentication.ClientId = AuthenticationSettings.DefaultClientId; - - if (string.IsNullOrWhiteSpace(Authentication.RedirectUri)) - Authentication.RedirectUri = AuthenticationSettings.DesktopRedirectUri; - - if (Authentication.Scopes is null || Authentication.Scopes.Length == 0) - Authentication.Scopes = AuthenticationSettings.DefaultScopes; - - Authentication.RefreshScopeListText(); - } - + "openid", // OIDC: required for the id_token + "profile", // OIDC: standard profile claims + "offline_access" // OIDC: required to receive a refresh_token + }; /// /// Merge user-configured scopes with the built-in ones. User scopes @@ -256,42 +270,16 @@ public partial class Settings : ViewModelBase return; } } - if (Environment.GetEnvironmentVariable("POSTIT_SETTINGS_JSON") is string envJson - && !string.IsNullOrWhiteSpace(envJson)) - { - Console.WriteLine("🔎 Loading settings from POSTIT_SETTINGS_JSON environment variable."); - FileInfo configByEnvFileInfo = new FileInfo(envJson); - if (!configByEnvFileInfo.Exists) - { - throw new Exception($"🩎 Settings file not found at {configByEnvFileInfo.FullName}"); - } - string json = File.ReadAllText(configByEnvFileInfo.FullName); - ApplyJson(json, "POSTIT_SETTINGS_JSON"); - SettingsFileFullName = configByEnvFileInfo.FullName; - Loaded = true; - return; - } + string configDir = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "PostIt" - ); + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "PostIt" +); + Directory.CreateDirectory(configDir); - if (SettingsFileFullName is not null) - { - // Already set by a previous Load() or by the environment - // variable path above. Use it as-is. - } - else if (Environment.GetEnvironmentVariable("POSTIT_SETTINGS_JSON") is string envPath - && !string.IsNullOrWhiteSpace(envPath)) - { - SettingsFileFullName = envPath; - } - else - { - SettingsFileFullName = Path.Combine(configDir, "postit-settings.json"); - } + string configPath = Path.Combine(configDir, SettingsFileName); - FileInfo configFileInfo = new FileInfo(SettingsFileFullName); + FileInfo configFileInfo = new FileInfo(configPath); if (!configFileInfo.Exists) { @@ -320,7 +308,6 @@ public partial class Settings : ViewModelBase using var reader = new StreamReader(stream); var json = reader.ReadToEnd(); ApplyJson(json, $"user file {configFileInfo.FullName}"); - SettingsFileFullName = configFileInfo.FullName; Loaded = true; } catch (Exception ex) @@ -363,26 +350,18 @@ public partial class Settings : ViewModelBase var settings = JsonSerializer.Deserialize(json); if (settings is null) { - UseDefaultSettings(); + Console.Error.WriteLine($"🩎 Settings payload is invalid (source: {source})."); + return; } // Apply under the gate so concurrent Load() callers cannot // see half the new values / half the old ones. The actual // PropertyChanged fan-out is handled by [ObservableProperty]'s // setters which we route through SetProperty → OnPropertyChanged // → our overridden dispatcher-safe marshaller below. - else lock (_mutationGate) + lock (_mutationGate) { - var legacyApiUrl = TryReadApiUrl(json); this.Authentication = settings.Authentication; this.DarkMode = settings.DarkMode; - this.BlogsApiUrl = !string.IsNullOrWhiteSpace(settings.BlogsApiUrl) - ? settings.BlogsApiUrl - : legacyApiUrl ?? this.BlogsApiUrl; - this.ApiUrl = !string.IsNullOrWhiteSpace(settings.ApiUrl) - ? settings.ApiUrl - : this.ApiUrl; - this.SearchText = settings.SearchText ?? string.Empty; - this.ProviderOngoingRequestsSortOption = settings.ProviderOngoingRequestsSortOption ?? string.Empty; if (!(settings.Authentication is null)) { this.Authentication = new AuthenticationSettings(); @@ -391,16 +370,9 @@ public partial class Settings : ViewModelBase this.Authentication.ClientId = string.IsNullOrWhiteSpace(settings.Authentication.ClientId) ? AuthenticationSettings.DefaultClientId : settings.Authentication.ClientId; this.Authentication.RedirectUri = string.IsNullOrWhiteSpace(settings.Authentication.RedirectUri) ? - AuthenticationSettings.DesktopRedirectUri : settings.Authentication.RedirectUri; - if (settings.Authentication.Scopes is null || settings.Authentication.Scopes.Length == 0) - { - this.Authentication.Scopes = AuthenticationSettings.DefaultScopes; - } - else - this.Authentication.Scopes = settings.Authentication.Scopes; + AuthenticationSettings.DefaultDesktopRedirectUri : settings.Authentication.RedirectUri; + this.Authentication.Scopes = settings.Authentication.Scopes; } - - EnsureAuthenticationDefaultsLocked(); } // A disk load (or an embedded-resource fallback) is the // baseline, not a user edit. Clear the dirty flag last @@ -428,42 +400,6 @@ public partial class Settings : ViewModelBase } } - private static string? TryReadApiUrl(string json) - { - try - { - using var doc = JsonDocument.Parse(json); - if (doc.RootElement.TryGetProperty("ApiUrl", out var apiUrl) - && apiUrl.ValueKind == JsonValueKind.String) - { - return apiUrl.GetString(); - } - } - catch - { - // Ignore legacy payload parse errors: normal deserialization - // already reports actionable diagnostics to the caller. - } - - return null; - } - - private void UseDefaultSettings() - { - this.Authentication = new AuthenticationSettings - { - Authority = AuthenticationSettings.DefaultAuthority, - ClientId = AuthenticationSettings.DefaultClientId, - RedirectUri = AuthenticationSettings.DesktopRedirectUri, - Scopes = AuthenticationSettings.DefaultScopes - }; - this.DarkMode = false; - this.BlogsApiUrl = "https://blogs.pschneider.fr/api/v1/"; - this.ApiUrl = "https://api.pschneider.fr/api/v1/"; - this.SearchText = string.Empty; - this.ProviderOngoingRequestsSortOption = string.Empty; - } - /// /// Persist the current in-memory state to /// ~/.config/PostIt/postit-settings.json (Linux) / @@ -482,19 +418,11 @@ public partial class Settings : ViewModelBase [RelayCommand(CanExecute = nameof(CanSave))] public void Save() { - SetActionStatus("Enregistrement des parametres...", StatusSeverity.Info); - - if (SettingsFileFullName is null) - { - var configDir = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "PostIt"); - Directory.CreateDirectory(configDir); - SettingsFileFullName = Path.Combine(configDir, DEFAULT_SETTINGS_FILENAME); - } - - var configPath = SettingsFileFullName!; - Directory.CreateDirectory(Path.GetDirectoryName(configPath)!); + var configDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + "PostIt"); + Directory.CreateDirectory(configDir); + var configPath = Path.Combine(configDir, SettingsFileName); lock (_mutationGate) { @@ -509,13 +437,10 @@ public partial class Settings : ViewModelBase File.SetUnixFileMode(configPath, UnixFileMode.UserRead | UnixFileMode.UserWrite); IsDirty = false; - SetActionStatus("Parametres sauvegardes.", StatusSeverity.Info); - Console.WriteLine($"💾 Settings saved to {configPath}"); } catch (Exception ex) { - SetActionStatus($"Echec sauvegarde parametres: {ex.Message}", StatusSeverity.Error); Console.Error.WriteLine($"🩎 Error saving settings to {configPath}: {ex.Message}"); throw; } diff --git a/src/PostIt/PostIt/ViewModels/Signature/SignaturePageViewModel.cs b/src/PostIt/PostIt/ViewModels/SignaturePageViewModel.cs similarity index 90% rename from src/PostIt/PostIt/ViewModels/Signature/SignaturePageViewModel.cs rename to src/PostIt/PostIt/ViewModels/SignaturePageViewModel.cs index 23ba70cf9..b4b37974b 100644 --- a/src/PostIt/PostIt/ViewModels/Signature/SignaturePageViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/SignaturePageViewModel.cs @@ -30,7 +30,7 @@ namespace PostIt.ViewModels; /// until the Yavsc.Org endpoint exists; the contract there will /// be POST /api/signature/{devisId} with this same payload. /// -public partial class SignaturePageViewModel : ViewModelBase, IActionStatusViewModel +public partial class SignaturePageViewModel : ViewModelBase { /// /// Default capture surface, in DIPs. 3:1 ratio matches a @@ -42,9 +42,6 @@ public partial class SignaturePageViewModel : ViewModelBase, IActionStatusViewMo [ObservableProperty] public partial string StatusMessage { get; set; } = "Prêt."; - [ObservableProperty] - public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Prêt."); - [ObservableProperty] public partial int StrokeCount { get; set; } @@ -110,7 +107,7 @@ public partial class SignaturePageViewModel : ViewModelBase, IActionStatusViewMo private void OnStrokeCompleted(object? sender, SignaturePadData data) { - this.SetInfoStatus($"Trait terminé. {data.StrokeCount} trait(s)."); + StatusMessage = $"Trait terminé. {data.StrokeCount} trait(s)."; RefreshCounts(); } @@ -128,7 +125,7 @@ public partial class SignaturePageViewModel : ViewModelBase, IActionStatusViewMo public void Clear() { _control?.Clear(); - this.SetInfoStatus("Effacé."); + StatusMessage = "Effacé."; RefreshCounts(); } @@ -137,14 +134,14 @@ public partial class SignaturePageViewModel : ViewModelBase, IActionStatusViewMo { if (_control is null) { - this.SetWarningStatus("Contrôle non attaché."); + StatusMessage = "Contrôle non attaché."; return; } var data = _control.Snapshot(); if (data.IsEmpty) { - this.SetWarningStatus("Rien à capturer."); + StatusMessage = "Rien à capturer."; return; } @@ -152,11 +149,11 @@ public partial class SignaturePageViewModel : ViewModelBase, IActionStatusViewMo { var path = WriteCapture(data); LastCapturedPath = path; - this.SetInfoStatus($"Capture enregistrée: {path}"); + StatusMessage = $"Capture enregistrée: {path}"; } catch (Exception ex) { - this.SetErrorStatus($"Erreur: {ex.Message}"); + StatusMessage = $"Erreur: {ex.Message}"; } await Task.CompletedTask; } diff --git a/src/PostIt/PostIt/ViewModels/ViewModelBase.cs b/src/PostIt/PostIt/ViewModels/ViewModelBase.cs index 307837645..93019360f 100644 --- a/src/PostIt/PostIt/ViewModels/ViewModelBase.cs +++ b/src/PostIt/PostIt/ViewModels/ViewModelBase.cs @@ -1,10 +1,12 @@ +using Avalonia.Styling; using CommunityToolkit.Mvvm.ComponentModel; namespace PostIt.ViewModels; -public abstract class ViewModelBase : ObservableObject +public abstract partial class ViewModelBase : ObservableObject { - /// + + /// /// Gets if the user can navigate to the next page /// public abstract bool CanNavigateNext { get; protected set; } diff --git a/src/PostIt/PostIt/Views/ACL/CirclesPage.axaml.cs b/src/PostIt/PostIt/Views/ACL/CirclesPage.axaml.cs deleted file mode 100644 index 3fe7a16c5..000000000 --- a/src/PostIt/PostIt/Views/ACL/CirclesPage.axaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Markup.Xaml; - -namespace PostIt.Views; - -public partial class CirclesPage : ContentPage -{ - - public CirclesPage() - { - InitializeComponent(); - } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } -} diff --git a/src/PostIt/PostIt/Views/ACL/PostAclDialog.axaml.cs b/src/PostIt/PostIt/Views/ACL/PostAclDialog.axaml.cs deleted file mode 100644 index c36ddeef5..000000000 --- a/src/PostIt/PostIt/Views/ACL/PostAclDialog.axaml.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; -using PostIt.ViewModels; -using Yavsc.Blogspot; -using Yavsc.Api.Client; - -namespace PostIt.Views; - -/// -/// Modal "manage ACL" page for a single blog post. -/// -/// The ViewModel is constructed by the caller (the post -/// list page) and handed to , -/// which routes through and lands -/// here via the parameterless DI constructor. The VM is then -/// assigned to by -/// App.PushPageAsync — we listen for that one-shot -/// assignment and trigger LoadAsync right after, so the -/// dropdown's MyCircles and the list's AclEntries -/// are populated when the dialog appears. The VM is idempotent -/// under repeated loads. -/// -public partial class PostAclDialog : ContentPage -{ - public PostAclDialog() - { - InitializeComponent(); - - // App.PushPageAsync wires the VM via DataContext after - // building the page. We subscribe once to fire LoadAsync - // the moment the VM is attached. Using DataContextChanged - // (rather than AttachedToVisualTree) is what makes this - // work in the headless test harness too: the load is - // tied to the VM being available, not to the visual tree - // being realised (which is a separate concern). - EventHandler? handler = null; - handler = (_, _) => - { - if (DataContext is PostAclDialogViewModel vm) - { - this.DataContextChanged -= handler; - _ = vm.LoadAsync(); - } - }; - this.DataContextChanged += handler; - } - - public PostAclDialog(BlogPostDto post, BlogAclApiClient aclClient, CircleApiClient circleClient) - { - // This overload is not used by the production path — - // MainPageViewModel pushes the VM via App.PushPageAsync - // and App routes through ViewLocator, which resolves this - // page via the parameterless ctor. It is kept so test - // scaffolding that wants to bypass the nav pipeline can - // still wire a VM directly without losing the load - // trigger: the constructor sets DataContext before the - // DataContextChanged subscription fires, so the load - // is guaranteed to run in either case. - InitializeComponent(); - DataContext = new PostAclDialogViewModel(post, aclClient, circleClient); - } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } - - private void OnCloseClicked(object? sender, Avalonia.Interactivity.RoutedEventArgs e) - { - // Pop this page off the navigation stack. Avalonia's - // NavigationPage doesn't have a typed "Close" — the - // hosting control (a NavigationPage in MainWindow.axaml) - // is the one that owns the back stack, but the - // ContentPage itself doesn't know about it. A simpler - // contract: fire an event the host listens to, or rely - // on the system back gesture. We do the latter — the - // dialog is intentionally modal-light. - if (this.VisualRoot is NavigationPage nav) - { - // The actual API varies between Avalonia 11.x - // versions; the safest call is the equivalent of - // "go back", which lives on the host. For now, hide - // the page and let the host decide. - } - } -} diff --git a/src/PostIt/PostIt/Views/Activity/ActivitiesPage.axaml b/src/PostIt/PostIt/Views/Activity/ActivitiesPage.axaml deleted file mode 100644 index a34d8edf2..000000000 --- a/src/PostIt/PostIt/Views/Activity/ActivitiesPage.axaml +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - - - - -public partial class AddCircleMemberDialog : Avalonia.Controls.ContentPage +public partial class AddCircleMemberDialog : ContentPage { public AddCircleMemberDialog() { InitializeComponent(); + } + public AddCircleMemberDialog(IUserDirectory directory) + { + InitializeComponent(); + DataContext = new AddCircleMemberDialogViewModel(directory); } private void InitializeComponent() @@ -39,4 +44,11 @@ public partial class AddCircleMemberDialog : Avalonia.Controls.ContentPage /// public AddCircleMemberDialogViewModel? ViewModel => DataContext as AddCircleMemberDialogViewModel; + + private void OnCloseClicked(object? sender, RoutedEventArgs e) + { + // Same light-modal pattern as PostAclDialog: rely on + // the system back gesture or the navigation host's + // "pop" — the ContentPage doesn't own the back stack. + } } diff --git a/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml b/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml deleted file mode 100644 index d4124a651..000000000 --- a/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml.cs b/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml.cs deleted file mode 100644 index 09ccc79f4..000000000 --- a/src/PostIt/PostIt/Views/Blogs/BlogsPage.axaml.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using Avalonia.Controls; -using Avalonia.Platform.Storage; -using Avalonia.Controls.Primitives; - -namespace PostIt.Views.Blogs; - -public partial class BlogsPage : ContentPage -{ - public BlogsPage() - { - InitializeComponent(); - } - - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - if (DataContext is ViewModels.BlogsViewModel vm) - { - if (!vm.IsLoaded) - { - vm.RefreshAsync().Wait(); - } - } - } - - private async void AddAttachment_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) - { - var topLevel = TopLevel.GetTopLevel(this); - if (topLevel is null || DataContext is not ViewModels.BlogsViewModel vm) - return; - - var files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions - { - Title = "Choisir des fichiers à joindre au billet", - AllowMultiple = true, - }); - - if (files.Count == 0) - return; - - var uploads = new System.Collections.Generic.List(files.Count); - foreach (var file in files) - { - await using var stream = await file.OpenReadAsync(); - using var memory = new MemoryStream(); - await stream.CopyToAsync(memory); - uploads.Add(new Yavsc.Api.Client.BlogUploadFile(file.Name, memory.ToArray(), GetMimeType(file.Name))); - } - - vm.DraftAttachments.Clear(); - foreach (var upload in uploads) - vm.DraftAttachments.Add(upload); - } - - private static string GetMimeType(string fileName) - { - var ext = Path.GetExtension(fileName)?.ToLowerInvariant(); - return ext switch - { - ".png" => "image/png", - ".jpg" or ".jpeg" => "image/jpeg", - ".webp" => "image/webp", - ".gif" => "image/gif", - ".pdf" => "application/pdf", - _ => "application/octet-stream" - }; - } -} diff --git a/src/PostIt/PostIt/Views/ACL/CirclesPage.axaml b/src/PostIt/PostIt/Views/CirclesPage.axaml similarity index 95% rename from src/PostIt/PostIt/Views/ACL/CirclesPage.axaml rename to src/PostIt/PostIt/Views/CirclesPage.axaml index 8488201e2..3e156940c 100644 --- a/src/PostIt/PostIt/Views/ACL/CirclesPage.axaml +++ b/src/PostIt/PostIt/Views/CirclesPage.axaml @@ -1,7 +1,6 @@ public bool IsPublished { get; set; } - public virtual bool AuthorizeCircle(long circleId) + public bool AuthorizeCircle(long circleId) { - ACL.Add(new CircleAuthorization { CircleId = circleId }); - return true; + throw new NotImplementedException(); } - public ICollection ACL = new List(); - - /// - /// Wire-only ACL bridge for System.Text.Json: accepts the - /// acl/ACL payload from GET detail responses, - /// but is never emitted on POST/PUT from the client. - /// - [JsonPropertyName("acl")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public List? WireAcl + public ICircleAuthorization[] GetACL() { - get => null; - set => ACL = value ?? new List(); + throw new NotImplementedException(); } - public string[] Tags { get; set; } - - ICollection ICircleAuthorized.ACL => this.ACL; - - public string[] GetTags() => Tags; - - public CircleAuthorization[] GetACL() => ACL.ToArray(); + public string[] GetTags() + { + throw new NotImplementedException(); + } } diff --git a/src/Yavsc.Abstract/Blogspot/BlogPostAuthorDto.cs b/src/Yavsc.Abstract/Blogspot/BlogPostAuthorDto.cs deleted file mode 100644 index ab1b3fbe8..000000000 --- a/src/Yavsc.Abstract/Blogspot/BlogPostAuthorDto.cs +++ /dev/null @@ -1,35 +0,0 @@ -#nullable enable annotations - -namespace Yavsc.Blogspot; - -/// -/// Minimum-viable author payload embedded in . -/// -/// -/// Before this record existed, BlogPostDto.Author was typed -/// as the abstract interface IApplicationUser. The -/// interface is fine for server-side contract (we have a concrete -/// entity that implements it) but System.Text.Json cannot -/// materialise an interface without a polymorphic converter -/// configured on both ends. PostIt would crash on load-posts -/// because the JSON contained an Author object that the -/// client could not deserialise. -/// -/// -/// -/// This record is the wire shape: Id for "go to author -/// profile", UserName for "by @username", Avatar -/// for the round badge next to the title. The server-side -/// BlogPost entity (Yavsc.Server.Models.Blog) keeps -/// its full ApplicationUser navigation property for -/// permission checks and authorisation; the DTO is built on -/// demand by the controller / service layer when the post is -/// served to the wire. -/// -/// -public sealed record BlogPostAuthorDto -{ - public string Id { get; init; } = string.Empty; - public string? UserName { get; init; } - public string? Avatar { get; init; } -} diff --git a/src/Yavsc.Abstract/Blogspot/IBlogPost.cs b/src/Yavsc.Abstract/Blogspot/IBlogPost.cs index 38a50b78f..5287685df 100644 --- a/src/Yavsc.Abstract/Blogspot/IBlogPost.cs +++ b/src/Yavsc.Abstract/Blogspot/IBlogPost.cs @@ -1,19 +1,14 @@ -#nullable enable annotations - +using Yavsc.Abstract.Identity; using Yavsc.Abstract.Identity.Security; +using Yavsc.Interfaces; namespace Yavsc.Blogspot { public interface IBlogPost : IBlogPostPayLoad, ICircleAuthorized, ITrackedEntity, ITitle { - // Typed as a concrete wire DTO (not the IApplicationUser - // interface) so System.Text.Json can materialise it on the - // client without a polymorphic converter. The server-side - // BlogPost entity implements this getter by mapping its - // ApplicationUser navigation to a BlogPostAuthorDto. - BlogPostAuthorDto? Author { get; } + IApplicationUser Author { get; } } } diff --git a/src/Yavsc.Abstract/Blogspot/PostAccessControlRulePayload.cs b/src/Yavsc.Abstract/Blogspot/PostAccessControlRulePayload.cs deleted file mode 100644 index a42e54285..000000000 --- a/src/Yavsc.Abstract/Blogspot/PostAccessControlRulePayload.cs +++ /dev/null @@ -1,9 +0,0 @@ - -using Yavsc.Abstract.Identity.Security; - -namespace Yavsc.Abstract.BlogSpot; - -public class PostAccessControlRulePayload : CircleAuthorization -{ - public long BlogPostId { get; set; } -} diff --git a/src/Yavsc.Abstract/Chat/ChatHubConstants.cs b/src/Yavsc.Abstract/Chat/ChatHubConstants.cs index c673e6fae..b54c00c74 100644 --- a/src/Yavsc.Abstract/Chat/ChatHubConstants.cs +++ b/src/Yavsc.Abstract/Chat/ChatHubConstants.cs @@ -1,5 +1,3 @@ -#nullable enable annotations - namespace Yavsc.Abstract.Chat { public static class ChatHubConstants diff --git a/src/Yavsc.Abstract/Chat/IChatRoom.cs b/src/Yavsc.Abstract/Chat/IChatRoom.cs index 133ca2837..665c71266 100644 --- a/src/Yavsc.Abstract/Chat/IChatRoom.cs +++ b/src/Yavsc.Abstract/Chat/IChatRoom.cs @@ -1,5 +1,4 @@ -#nullable enable annotations - +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace Yavsc.Abstract.Chat @@ -16,4 +15,4 @@ namespace Yavsc.Abstract.Chat List Moderation { get; } } -} +} \ No newline at end of file diff --git a/src/Yavsc.Abstract/Constants.cs b/src/Yavsc.Abstract/Constants.cs index a2649491c..af78ab0b9 100644 --- a/src/Yavsc.Abstract/Constants.cs +++ b/src/Yavsc.Abstract/Constants.cs @@ -3,17 +3,8 @@ using Yavsc.Models.Auth; namespace Yavsc { - public static class Constants + public static class YavscConstants { - - public const string APIPrefix = "api/v1"; - - public const string BlogSpotPath = "blogspot"; - public const string BlogAclPath = "blogacl"; - public const string BlogTagPath = "blogtag"; - public const string CirclePath = "circle"; - public const string CommentsPath = "blogcomments"; - public static readonly Scope[] SiteScopes = { new Scope { Id = "profile", Description = "Your profile informations" }, new Scope { Id = "book" , Description ="Your booking interface"}, diff --git a/src/Yavsc.Abstract/FileSystem/AbstractFileSystemHelpers.cs b/src/Yavsc.Abstract/FileSystem/AbstractFileSystemHelpers.cs index 14bc86435..5e571e249 100644 --- a/src/Yavsc.Abstract/FileSystem/AbstractFileSystemHelpers.cs +++ b/src/Yavsc.Abstract/FileSystem/AbstractFileSystemHelpers.cs @@ -1,5 +1,6 @@ -#nullable enable annotations - +using System; +using System.IO; +using System.Linq; using System.Text; using Yavsc.ViewModels.UserFiles; @@ -41,10 +42,10 @@ namespace Yavsc.Server.Helpers { if (name.Any(c => !ValidFileNameChars.Contains(c))) return false; - + if (!name.Any(c => !AlfaNum.Contains(c))) return false; - + return true; } diff --git a/src/Yavsc.Abstract/FileSystem/FsOperationInfo.cs b/src/Yavsc.Abstract/FileSystem/FsOperationInfo.cs index 0e3c0e0c7..76a481492 100644 --- a/src/Yavsc.Abstract/FileSystem/FsOperationInfo.cs +++ b/src/Yavsc.Abstract/FileSystem/FsOperationInfo.cs @@ -1,5 +1,3 @@ -#nullable enable annotations - namespace Yavsc.Abstract.Helpers { public enum ErrorCode { diff --git a/src/Yavsc.Abstract/FileSystem/RemoteFileInfo.cs b/src/Yavsc.Abstract/FileSystem/RemoteFileInfo.cs index 2a21823c1..76cb17eb9 100644 --- a/src/Yavsc.Abstract/FileSystem/RemoteFileInfo.cs +++ b/src/Yavsc.Abstract/FileSystem/RemoteFileInfo.cs @@ -1,6 +1,8 @@ -namespace Yavsc.ViewModels +using System; + +namespace Yavsc.ViewModels { - public class RemoteFileInfo + public class RemoteFileInfo { public string Name { get; set; } @@ -9,7 +11,7 @@ public DateTime CreationTime { get; set; } public DateTime LastModified { get; set; } - + } -} +} \ No newline at end of file diff --git a/src/Yavsc.Abstract/FileSystem/UserDirectoryInfo.cs b/src/Yavsc.Abstract/FileSystem/UserDirectoryInfo.cs index 2ec5dcfaf..cced9397d 100644 --- a/src/Yavsc.Abstract/FileSystem/UserDirectoryInfo.cs +++ b/src/Yavsc.Abstract/FileSystem/UserDirectoryInfo.cs @@ -1,5 +1,7 @@ -#nullable enable annotations - +using System; +using System.IO; +using System.Linq; +using Yavsc.Abstract.FileSystem; using Yavsc.Server.Helpers; namespace Yavsc.ViewModels.UserFiles @@ -11,7 +13,7 @@ namespace Yavsc.ViewModels.UserFiles public RemoteFileInfo [] Files { get; set; } - public DirectoryShortInfo [] SubDirectories { + public DirectoryShortInfo [] SubDirectories {  get; set; } private readonly DirectoryInfo dInfo; @@ -21,7 +23,7 @@ namespace Yavsc.ViewModels.UserFiles { } - + public UserDirectoryInfo(string userReposPath, string userId, string path) { if (string.IsNullOrWhiteSpace(userId)) diff --git a/src/Yavsc.Abstract/Files/FileServerUrlHelpers.cs b/src/Yavsc.Abstract/Files/FileServerUrlHelpers.cs deleted file mode 100644 index 64bcb405b..000000000 --- a/src/Yavsc.Abstract/Files/FileServerUrlHelpers.cs +++ /dev/null @@ -1,67 +0,0 @@ -namespace Yavsc.Abstract.Files; - -/// -/// Helpers pour dériver les URL publiques des fichiers statiques à partir -/// d'une URL d'autorité OIDC ou d'un autre point d'entrée racine. -/// -public static class FileServerUrlHelpers -{ - /// - /// Dérive la racine publique des fichiers utilisateur en alignant - /// le chemin sur . - /// - /// - /// URL absolue de base, typiquement l'autorité OIDC de Yavsc.Org. - /// - /// Une URL absolue pointant vers la racine des fichiers utilisateur. - public static Uri GetUserFilesBaseUri(Uri authorityBaseUrl) - { - ArgumentNullException.ThrowIfNull(authorityBaseUrl); - - if (!authorityBaseUrl.IsAbsoluteUri) - { - throw new ArgumentException( - "The authority base URL must be absolute.", - nameof(authorityBaseUrl)); - } - - var baseString = authorityBaseUrl.GetLeftPart(UriPartial.Authority); - return new Uri(new Uri(baseString, UriKind.Absolute), EnsureTrailingSlash(Yavsc.Constants.UserFilesPath)); - } - - /// - /// Dérive la racine publique des fichiers utilisateur en alignant - /// le chemin sur . - /// - /// - /// URL absolue de base, typiquement l'autorité OIDC de Yavsc.Org. - /// - /// Une URL absolue pointant vers la racine des fichiers utilisateur. - public static Uri GetUserFilesBaseUri(string authorityBaseUrl) - => GetUserFilesBaseUri(new Uri(authorityBaseUrl, UriKind.Absolute)); - - /// - /// Construit l'URL d'un fichier utilisateur à partir de la base d'autorité - /// et d'un chemin relatif sous la racine des fichiers. - /// - public static Uri GetUserFilesUri(Uri authorityBaseUrl, string relativePath) - { - ArgumentException.ThrowIfNullOrWhiteSpace(relativePath); - - var baseUri = GetUserFilesBaseUri(authorityBaseUrl); - return new Uri(baseUri, NormalizeRelativePath(relativePath)); - } - - /// - /// Construit l'URL d'un fichier utilisateur à partir de la base d'autorité - /// et d'un chemin relatif sous la racine des fichiers. - /// - public static Uri GetUserFilesUri(string authorityBaseUrl, string relativePath) - => GetUserFilesUri(new Uri(authorityBaseUrl, UriKind.Absolute), relativePath); - - private static string EnsureTrailingSlash(string path) - => path.EndsWith("/", StringComparison.Ordinal) ? path : path + "/"; - - private static string NormalizeRelativePath(string relativePath) - => relativePath.TrimStart('/'); -} diff --git a/src/Yavsc.Abstract/Google/Calendar/CalendarEventList.cs b/src/Yavsc.Abstract/Google/Calendar/CalendarEventList.cs index 0b8b0682a..095c28be2 100644 --- a/src/Yavsc.Abstract/Google/Calendar/CalendarEventList.cs +++ b/src/Yavsc.Abstract/Google/Calendar/CalendarEventList.cs @@ -18,6 +18,8 @@ // // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see . +using System; + namespace Yavsc.Models.Google { diff --git a/src/Yavsc.Abstract/Google/Calendar/CalendarList.cs b/src/Yavsc.Abstract/Google/Calendar/CalendarList.cs index 84ab1bf0f..c7caccda8 100644 --- a/src/Yavsc.Abstract/Google/Calendar/CalendarList.cs +++ b/src/Yavsc.Abstract/Google/Calendar/CalendarList.cs @@ -19,6 +19,8 @@ // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see . +using System; + namespace Yavsc.Models.Google.Calendar { /// diff --git a/src/Yavsc.Abstract/Google/Calendar/CalendarListEntry.cs b/src/Yavsc.Abstract/Google/Calendar/CalendarListEntry.cs index 99d9a99b0..00a5ffcba 100644 --- a/src/Yavsc.Abstract/Google/Calendar/CalendarListEntry.cs +++ b/src/Yavsc.Abstract/Google/Calendar/CalendarListEntry.cs @@ -19,12 +19,14 @@ // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see . +using System; + namespace Yavsc.Models.Google.Calendar { /// /// Calendar list entry. /// - /// + /// [Obsolete("use GoogleUse.Apis")] public class CalendarListEntry { /// diff --git a/src/Yavsc.Abstract/Google/Calendar/Reminder.cs b/src/Yavsc.Abstract/Google/Calendar/Reminder.cs index d10e6da7d..8a48753c3 100644 --- a/src/Yavsc.Abstract/Google/Calendar/Reminder.cs +++ b/src/Yavsc.Abstract/Google/Calendar/Reminder.cs @@ -1,3 +1,5 @@ +using System; + namespace Yavsc.Models.Google.Calendar { [Obsolete("use GoogleUse.Apis")] diff --git a/src/Yavsc.Abstract/Google/GDate.cs b/src/Yavsc.Abstract/Google/GDate.cs index 10e4de92e..e1fe7f5c1 100644 --- a/src/Yavsc.Abstract/Google/GDate.cs +++ b/src/Yavsc.Abstract/Google/GDate.cs @@ -1,5 +1,3 @@ -#nullable enable annotations - // // GDate.cs // @@ -20,6 +18,8 @@ // // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see . +using System; + namespace Yavsc.Models.Google { /// diff --git a/src/Yavsc.Abstract/Google/Messaging/MessageWithPayLoad.cs b/src/Yavsc.Abstract/Google/Messaging/MessageWithPayLoad.cs index 344273add..ed6833042 100644 --- a/src/Yavsc.Abstract/Google/Messaging/MessageWithPayLoad.cs +++ b/src/Yavsc.Abstract/Google/Messaging/MessageWithPayLoad.cs @@ -20,6 +20,7 @@ // along with this program. If not, see . using Yavsc.Abstract.Models.Messaging; +using Yavsc.Models.Messaging; namespace Yavsc.Models.Google.Messaging { diff --git a/src/Yavsc.Abstract/Google/Messaging/MessageWithPayloadResponse.cs b/src/Yavsc.Abstract/Google/Messaging/MessageWithPayloadResponse.cs index 980a4abcf..59538ca62 100644 --- a/src/Yavsc.Abstract/Google/Messaging/MessageWithPayloadResponse.cs +++ b/src/Yavsc.Abstract/Google/Messaging/MessageWithPayloadResponse.cs @@ -1,5 +1,3 @@ -#nullable enable annotations - // // MessageWithPayloadResponse.cs // diff --git a/src/Yavsc.Abstract/HairCut/HairPrestationDto.cs b/src/Yavsc.Abstract/HairCut/HairPrestationDto.cs deleted file mode 100644 index 0c4b80e2c..000000000 --- a/src/Yavsc.Abstract/HairCut/HairPrestationDto.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Yavsc.Models.Haircut; - -/// -/// Lightweight hair-prestation description exposed to API clients. -/// -public sealed class HairPrestationDto -{ - public long Id { get; set; } - public string Title { get; set; } = string.Empty; - public string Details { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/src/Yavsc.Abstract/IT/CodeFromChars.cs b/src/Yavsc.Abstract/IT/CodeFromChars.cs index e0e31ce28..b8e2f0d6d 100644 --- a/src/Yavsc.Abstract/IT/CodeFromChars.cs +++ b/src/Yavsc.Abstract/IT/CodeFromChars.cs @@ -1,4 +1,6 @@ +using System; using System.Collections; +using System.Collections.Generic; namespace Yavsc.Abstract.IT { @@ -11,10 +13,10 @@ namespace Yavsc.Abstract.IT } public CharArray (IList word): base(word) { - + } public CharArray (IEnumerable word): base(word) { - + } public IList Aggregate(char other) @@ -44,6 +46,7 @@ namespace Yavsc.Abstract.IT public bool Validate() { // this is a n*n task + throw new NotImplementedException(); } @@ -86,10 +89,10 @@ namespace Yavsc.Abstract.IT State = -3; return; } - + State = states[letter]; } } } -} +} \ No newline at end of file diff --git a/src/Yavsc.Abstract/IT/Fixing/Bug.cs b/src/Yavsc.Abstract/IT/Fixing/Bug.cs index 60bd155f6..f0b403bb7 100644 --- a/src/Yavsc.Abstract/IT/Fixing/Bug.cs +++ b/src/Yavsc.Abstract/IT/Fixing/Bug.cs @@ -1,5 +1,3 @@ -#nullable enable annotations - using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Yavsc.Attributes.Validation; diff --git a/src/Yavsc.Abstract/IT/ICode.cs b/src/Yavsc.Abstract/IT/ICode.cs index 2661c70c1..c190065a0 100644 --- a/src/Yavsc.Abstract/IT/ICode.cs +++ b/src/Yavsc.Abstract/IT/ICode.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace Yavsc.Abstract.IT { // un code est, parmis les ensembles de suites de signes, @@ -12,7 +14,7 @@ namespace Yavsc.Abstract.IT bool Validate(); /// - /// Defines a new letter in this code, + /// Defines a new letter in this code, /// as an enumerable of TLetter /// /// diff --git a/src/Yavsc.Abstract/IT/IProject.cs b/src/Yavsc.Abstract/IT/IProject.cs index 946ab6e9e..e15fb0677 100644 --- a/src/Yavsc.Abstract/IT/IProject.cs +++ b/src/Yavsc.Abstract/IT/IProject.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace Yavsc.Abstract.IT { public interface IProject diff --git a/src/Yavsc.Abstract/Identity/IApplicationUser.cs b/src/Yavsc.Abstract/Identity/IApplicationUser.cs index 0b8e1047c..5d1d5b1ca 100644 --- a/src/Yavsc.Abstract/Identity/IApplicationUser.cs +++ b/src/Yavsc.Abstract/Identity/IApplicationUser.cs @@ -1,6 +1,4 @@ -#nullable enable annotations - -namespace Yavsc.Abstract.Identity +namespace Yavsc.Abstract.Identity { public interface IApplicationUser { diff --git a/src/Yavsc.Abstract/Identity/Security/FileAccessControlRulePayload.cs b/src/Yavsc.Abstract/Identity/Security/FileAccessControlRulePayload.cs deleted file mode 100644 index af3af22bc..000000000 --- a/src/Yavsc.Abstract/Identity/Security/FileAccessControlRulePayload.cs +++ /dev/null @@ -1,12 +0,0 @@ - -namespace Yavsc.Models.Access -{ - using Yavsc.Abstract.Identity.Security; - - public class FileAccessControlRulePayload : CircleAuthorization - { - public virtual string Path { get; set; } - - - } -} diff --git a/src/Yavsc.Abstract/Identity/Security/ICircleAuthorization.cs b/src/Yavsc.Abstract/Identity/Security/ICircleAuthorization.cs new file mode 100644 index 000000000..9c16bd3b1 --- /dev/null +++ b/src/Yavsc.Abstract/Identity/Security/ICircleAuthorization.cs @@ -0,0 +1,8 @@ +namespace Yavsc.Abstract.Identity.Security +{ + + public interface ICircleAuthorization + { + long CircleId { get; set; } + } +} diff --git a/src/Yavsc.Abstract/Identity/Security/ICircleAuthorized.cs b/src/Yavsc.Abstract/Identity/Security/ICircleAuthorized.cs index 6b593f3c2..25c21961d 100644 --- a/src/Yavsc.Abstract/Identity/Security/ICircleAuthorized.cs +++ b/src/Yavsc.Abstract/Identity/Security/ICircleAuthorized.cs @@ -9,7 +9,7 @@ namespace Yavsc.Abstract.Identity.Security bool AuthorizeCircle(long circleId); - ICollection ACL { get; } //ICircleAuthorization [] GetACL(); + ICircleAuthorization [] GetACL(); } } diff --git a/src/Yavsc.Abstract/Identity/TokenInfo.cs b/src/Yavsc.Abstract/Identity/TokenInfo.cs index 2c7a4fd45..1847f3a2e 100644 --- a/src/Yavsc.Abstract/Identity/TokenInfo.cs +++ b/src/Yavsc.Abstract/Identity/TokenInfo.cs @@ -1,3 +1,5 @@ +using System; + namespace Yavsc.Abstract.Identity { public class TokenInfo diff --git a/src/Yavsc.Abstract/Identity/UserDisplayHelpers.cs b/src/Yavsc.Abstract/Identity/UserDisplayHelpers.cs index 4277d6e3b..04bc8e338 100644 --- a/src/Yavsc.Abstract/Identity/UserDisplayHelpers.cs +++ b/src/Yavsc.Abstract/Identity/UserDisplayHelpers.cs @@ -1,5 +1,3 @@ -#nullable enable annotations - namespace Yavsc.Abstract.Identity { /// @@ -21,7 +19,7 @@ namespace Yavsc.Abstract.Identity /// /// /// Le path retourné est aligné sur - /// (minuscule). + /// (minuscule). /// Les anciens display templates utilisaient "/Avatars/" /// avec un S majuscule, en désaccord avec le path statique /// servi par le middleware de fichiers — les images ne @@ -31,8 +29,8 @@ namespace Yavsc.Abstract.Identity public static string AvatarSrc(IApplicationUser? user) { if (user==null || string.IsNullOrWhiteSpace(user?.UserName)) - return Constants.DefaultAvatar; - return $"{Constants.AvatarsPath}/{user!.UserName}.s.png"; + return YavscConstants.DefaultAvatar; + return $"{YavscConstants.AvatarsPath}/{user!.UserName}.s.png"; } } } diff --git a/src/Yavsc.Abstract/Interfaces/IBaseTrackedEntity.cs b/src/Yavsc.Abstract/Interfaces/IBaseTrackedEntity.cs index f79a596ea..f544f6541 100644 --- a/src/Yavsc.Abstract/Interfaces/IBaseTrackedEntity.cs +++ b/src/Yavsc.Abstract/Interfaces/IBaseTrackedEntity.cs @@ -1,3 +1,5 @@ +using System; + namespace Yavsc { public interface ITrackedEntity diff --git a/src/Yavsc.Abstract/Interfaces/IBatch.cs b/src/Yavsc.Abstract/Interfaces/IBatch.cs index a31470ec9..e3dac4506 100644 --- a/src/Yavsc.Abstract/Interfaces/IBatch.cs +++ b/src/Yavsc.Abstract/Interfaces/IBatch.cs @@ -1,3 +1,5 @@ +using System; + namespace Yavsc.Abstract.Interfaces { public interface IBatch diff --git a/src/Yavsc.Abstract/Interfaces/IBillingService.cs b/src/Yavsc.Abstract/Interfaces/IBillingService.cs index e11c9c99b..3ba7fc59f 100644 --- a/src/Yavsc.Abstract/Interfaces/IBillingService.cs +++ b/src/Yavsc.Abstract/Interfaces/IBillingService.cs @@ -1,7 +1,8 @@ namespace Yavsc.Services { - using System.Threading.Tasks; - using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using System.Collections.Generic; using Yavsc.Abstract.Workflow; public interface IBillingService diff --git a/src/Yavsc.Abstract/Interfaces/Workflow/IBookQueryData.cs b/src/Yavsc.Abstract/Interfaces/Workflow/IBookQueryData.cs index eb3dee591..6934f3a90 100644 --- a/src/Yavsc.Abstract/Interfaces/Workflow/IBookQueryData.cs +++ b/src/Yavsc.Abstract/Interfaces/Workflow/IBookQueryData.cs @@ -1,5 +1,4 @@ -#nullable enable annotations - +using System; using Yavsc.Abstract.Identity; namespace Yavsc.Interfaces @@ -12,4 +11,4 @@ namespace Yavsc.Interfaces ILocation Location { get; set; } decimal? Previsionnal { get; set; } } -} +} \ No newline at end of file diff --git a/src/Yavsc.Abstract/Messaging/Comment.cs b/src/Yavsc.Abstract/Messaging/Comment.cs index d8eef456c..3b8702ae1 100644 --- a/src/Yavsc.Abstract/Messaging/Comment.cs +++ b/src/Yavsc.Abstract/Messaging/Comment.cs @@ -1,5 +1,3 @@ -#nullable enable annotations - using Yavsc.Interfaces; diff --git a/src/Yavsc.Abstract/Messaging/IAnnounce.cs b/src/Yavsc.Abstract/Messaging/IAnnounce.cs index 04d16c1b1..cd1957afd 100644 --- a/src/Yavsc.Abstract/Messaging/IAnnounce.cs +++ b/src/Yavsc.Abstract/Messaging/IAnnounce.cs @@ -1,4 +1,6 @@ -using Yavsc.Interfaces; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Yavsc.Interfaces; namespace Yavsc.Models.Messaging { @@ -6,5 +8,5 @@ namespace Yavsc.Models.Messaging Reason For { get; set; } string Message { get; set; } } - -} + +} \ No newline at end of file diff --git a/src/Yavsc.Abstract/Messaging/Notification.cs b/src/Yavsc.Abstract/Messaging/Notification.cs index f7b35b48b..8b79e2c88 100644 --- a/src/Yavsc.Abstract/Messaging/Notification.cs +++ b/src/Yavsc.Abstract/Messaging/Notification.cs @@ -1,5 +1,4 @@ -#nullable enable annotations - +using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; @@ -30,7 +29,7 @@ namespace Yavsc.Abstract.Models.Messaging /// [StringLength(512)] [Display(Name = "Icône")] - public string? icon { get; set; } + public string? icon { get; set; } /// /// The sound. /// diff --git a/src/Yavsc.Abstract/Messaging/RdvQueryEvent.cs b/src/Yavsc.Abstract/Messaging/RdvQueryEvent.cs index 9e2ecb754..b2168bd7c 100644 --- a/src/Yavsc.Abstract/Messaging/RdvQueryEvent.cs +++ b/src/Yavsc.Abstract/Messaging/RdvQueryEvent.cs @@ -1,5 +1,3 @@ -#nullable enable annotations - // // BookQueryEvent.cs // diff --git a/src/Yavsc.Abstract/Messaging/RdvQueryProviderInfo.cs b/src/Yavsc.Abstract/Messaging/RdvQueryProviderInfo.cs index 94eeb5ab9..5e9118f38 100644 --- a/src/Yavsc.Abstract/Messaging/RdvQueryProviderInfo.cs +++ b/src/Yavsc.Abstract/Messaging/RdvQueryProviderInfo.cs @@ -1,5 +1,4 @@ -#nullable enable annotations - +using System; using Yavsc.Abstract.Identity; using Yavsc.Models.Relationship; diff --git a/src/Yavsc.Abstract/Relationship/Location.cs b/src/Yavsc.Abstract/Relationship/Location.cs index de4beb204..6c7491f60 100644 --- a/src/Yavsc.Abstract/Relationship/Location.cs +++ b/src/Yavsc.Abstract/Relationship/Location.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Yavsc.Attributes.Validation; namespace Yavsc.Models.Relationship { diff --git a/src/Yavsc.Abstract/Relationship/Position.cs b/src/Yavsc.Abstract/Relationship/Position.cs index 717ac9227..22b03c76c 100644 --- a/src/Yavsc.Abstract/Relationship/Position.cs +++ b/src/Yavsc.Abstract/Relationship/Position.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Yavsc.Attributes.Validation; namespace Yavsc.Models.Relationship { diff --git a/src/Yavsc.Abstract/Templates/Template.cs b/src/Yavsc.Abstract/Templates/Template.cs index c71fcfbf1..ee1a8c8b6 100644 --- a/src/Yavsc.Abstract/Templates/Template.cs +++ b/src/Yavsc.Abstract/Templates/Template.cs @@ -1,4 +1,5 @@ using System.Text; +using System.Threading.Tasks; namespace Yavsc.Abstract.Templates { diff --git a/src/Yavsc.Abstract/Workflow/ActivityInfo.cs b/src/Yavsc.Abstract/Workflow/ActivityInfo.cs deleted file mode 100644 index c0f543f40..000000000 --- a/src/Yavsc.Abstract/Workflow/ActivityInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Yavsc.Abstract.Workflow; - -/// -/// Activity node returned by the browsing API. -/// -public sealed class ActivityInfo -{ - public string Code { get; set; } = string.Empty; - public string Name { get; set; } = string.Empty; - public string ParentCode { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public string Photo { get; set; } = string.Empty; - public int Rate { get; set; } - public int PerformerCount { get; set; } - public List Forms { get; set; } = new(); - public List Children { get; set; } = new(); -} diff --git a/src/Yavsc.Abstract/Workflow/CommandFormSummary.cs b/src/Yavsc.Abstract/Workflow/CommandFormSummary.cs deleted file mode 100644 index 865690e08..000000000 --- a/src/Yavsc.Abstract/Workflow/CommandFormSummary.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Yavsc.Abstract.Workflow; - -/// -/// Lightweight command-form description exposed to API clients. -/// -public sealed class CommandFormSummary -{ - public long Id { get; set; } - public string ActionName { get; set; } = string.Empty; - public string Title { get; set; } = string.Empty; -} diff --git a/src/Yavsc.Abstract/Workflow/Country.cs b/src/Yavsc.Abstract/Workflow/Country.cs deleted file mode 100644 index d782d0aac..000000000 --- a/src/Yavsc.Abstract/Workflow/Country.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Yavsc.Models.Workflow -{ - /// - /// Supported country of exercise for performer profile validations. - /// - public class Country - { - [Key] - [MaxLength(2)] - [MinLength(2)] - public string Code { get; set; } = string.Empty; - - [Required] - [MaxLength(64)] - public string DisplayName { get; set; } = string.Empty; - } -} \ No newline at end of file diff --git a/src/Yavsc.Abstract/Workflow/IActivity.cs b/src/Yavsc.Abstract/Workflow/IActivity.cs index 2f06420bf..45781b287 100644 --- a/src/Yavsc.Abstract/Workflow/IActivity.cs +++ b/src/Yavsc.Abstract/Workflow/IActivity.cs @@ -1,3 +1,5 @@ +using System; + namespace Yavsc { public interface IActivity diff --git a/src/Yavsc.Abstract/Workflow/IMobileDeviceDeclaration.cs b/src/Yavsc.Abstract/Workflow/IMobileDeviceDeclaration.cs index 7a1dbc261..4f3613e8f 100644 --- a/src/Yavsc.Abstract/Workflow/IMobileDeviceDeclaration.cs +++ b/src/Yavsc.Abstract/Workflow/IMobileDeviceDeclaration.cs @@ -1,6 +1,4 @@ -#nullable enable annotations - -// Copyright (C) 2016 Paul Schneider +// Copyright (C) 2016 Paul Schneider // // This file is part of yavsc. // @@ -18,6 +16,8 @@ // along with yavsc. If not, see . // +using System; + namespace Yavsc { public interface IMobileDeviceDeclaration diff --git a/src/Yavsc.Abstract/Workflow/INominativeQuery.cs b/src/Yavsc.Abstract/Workflow/INominativeQuery.cs index 84dcee694..d498e28d4 100644 --- a/src/Yavsc.Abstract/Workflow/INominativeQuery.cs +++ b/src/Yavsc.Abstract/Workflow/INominativeQuery.cs @@ -1,3 +1,5 @@ +using System; + namespace Yavsc.Abstract.Workflow { public interface IDecidableQuery: ITrackedEntity, IQuery diff --git a/src/Yavsc.Abstract/Workflow/IPerformerProfile.cs b/src/Yavsc.Abstract/Workflow/IPerformerProfile.cs index 606285a6c..e53583a59 100644 --- a/src/Yavsc.Abstract/Workflow/IPerformerProfile.cs +++ b/src/Yavsc.Abstract/Workflow/IPerformerProfile.cs @@ -1,11 +1,8 @@ -#nullable enable annotations - namespace Yavsc.Workflow { public interface IPerformerProfile { string PerformerId { get; set; } - string ExerciseCountryCode { get; set; } string SIREN { get; set; } bool AcceptNotifications { get; set; } long OrganizationAddressId { get; set; } diff --git a/src/Yavsc.Abstract/Workflow/PerformerActivity.cs b/src/Yavsc.Abstract/Workflow/PerformerActivity.cs deleted file mode 100644 index a5ee3b254..000000000 --- a/src/Yavsc.Abstract/Workflow/PerformerActivity.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Yavsc.Abstract.Workflow; - -/// -/// Lightweight performer description returned for one activity. -/// -public sealed class PerformerActivity -{ - public string PerformerId { get; set; } = string.Empty; - public bool HasPerformerProfile { get; set; } - public string UserName { get; set; } = string.Empty; - public bool Active { get; set; } - public bool AcceptNotifications { get; set; } - public bool AcceptPublicContact { get; set; } - public string WebSite { get; set; } = string.Empty; - public string ActivityCode { get; set; } = string.Empty; - public string ActivityName { get; set; } = string.Empty; - public string SettingsClassName { get; set; } = string.Empty; - public int ExtraActivityCount { get; set; } -} diff --git a/src/Yavsc.Abstract/Workflow/PerformerCodeInputValidation.cs b/src/Yavsc.Abstract/Workflow/PerformerCodeInputValidation.cs deleted file mode 100644 index 4ed60e465..000000000 --- a/src/Yavsc.Abstract/Workflow/PerformerCodeInputValidation.cs +++ /dev/null @@ -1,88 +0,0 @@ -#nullable enable annotations - -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace Yavsc.Models.Workflow -{ - /// - /// Validation rule for performer business code input by country. - /// - public class PerformerCodeInputValidation - { - [Key] - public long Id { get; set; } - - [Required] - [MaxLength(2)] - [MinLength(2)] - public string CountryCode { get; set; } = string.Empty; - - [Required] - [MaxLength(256)] - public string RegularExpression { get; set; } = string.Empty; - - [Required] - [MaxLength(128)] - public string ErrorMessage { get; set; } = string.Empty; - - [ForeignKey(nameof(CountryCode))] - public Country Country { get; set; } - } - - public static class PerformerCodeInputValidationCatalog - { - public static readonly IReadOnlyList Countries = new List - { - new() { Code = "fr", DisplayName = "France" }, - new() { Code = "en", DisplayName = "England" }, - new() { Code = "pt", DisplayName = "Portugal" }, - }; - - public static readonly IReadOnlyList Rules = new List - { - new() - { - Id = 1, - CountryCode = "fr", - RegularExpression = "^[0-9]{9,14}$", - ErrorMessage = "Le code FR doit contenir entre 9 et 14 chiffres." - }, - new() - { - Id = 2, - CountryCode = "en", - RegularExpression = "^[A-Za-z0-9]{8,14}$", - ErrorMessage = "Le code EN doit contenir entre 8 et 14 caracteres alphanumeriques." - }, - new() - { - Id = 3, - CountryCode = "pt", - RegularExpression = "^[0-9]{9}$", - ErrorMessage = "Le code PT doit contenir exactement 9 chiffres." - }, - }; - - public static string NormalizeCountryCode(string? countryCode) - { - return (countryCode ?? string.Empty).Trim().ToLowerInvariant(); - } - - public static PerformerCodeInputValidation? GetRule(string? countryCode) - { - var normalized = NormalizeCountryCode(countryCode); - foreach (var rule in Rules) - { - if (string.Equals(rule.CountryCode, normalized, StringComparison.Ordinal)) - { - return rule; - } - } - - return null; - } - } -} \ No newline at end of file diff --git a/src/Yavsc.Abstract/Workflow/Process/Conjonction.cs b/src/Yavsc.Abstract/Workflow/Process/Conjonction.cs index a45de4e6a..bd2125db1 100644 --- a/src/Yavsc.Abstract/Workflow/Process/Conjonction.cs +++ b/src/Yavsc.Abstract/Workflow/Process/Conjonction.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace Yavsc.Models.Process { public class Conjonction : List, IRequisition diff --git a/src/Yavsc.Abstract/Workflow/Process/Disjonction.cs b/src/Yavsc.Abstract/Workflow/Process/Disjonction.cs index 5c4e08315..416257b73 100644 --- a/src/Yavsc.Abstract/Workflow/Process/Disjonction.cs +++ b/src/Yavsc.Abstract/Workflow/Process/Disjonction.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace Yavsc.Models.Process { public class Disjonction : List, IRequisition diff --git a/src/Yavsc.Abstract/Workflow/Tasks/IExecutionData.cs b/src/Yavsc.Abstract/Workflow/Tasks/IExecutionData.cs index 674ed6acf..fbc72a529 100644 --- a/src/Yavsc.Abstract/Workflow/Tasks/IExecutionData.cs +++ b/src/Yavsc.Abstract/Workflow/Tasks/IExecutionData.cs @@ -1,3 +1,7 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + + namespace Yavsc.Abstract.Workflow { public interface IExecutionData diff --git a/src/Yavsc.Abstract/Workflow/Tasks/ITaskMetaData.cs b/src/Yavsc.Abstract/Workflow/Tasks/ITaskMetaData.cs index 9e5365a37..e23716416 100644 --- a/src/Yavsc.Abstract/Workflow/Tasks/ITaskMetaData.cs +++ b/src/Yavsc.Abstract/Workflow/Tasks/ITaskMetaData.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Yavsc.Models; namespace Yavsc.Abstract.Workflow diff --git a/src/Yavsc.Abstract/Workflow/Tasks/TaskManager.cs b/src/Yavsc.Abstract/Workflow/Tasks/TaskManager.cs index 59f23b8b0..55c262f74 100644 --- a/src/Yavsc.Abstract/Workflow/Tasks/TaskManager.cs +++ b/src/Yavsc.Abstract/Workflow/Tasks/TaskManager.cs @@ -1,3 +1,6 @@ +using System.Collections.Generic; +using System.Linq; + namespace Yavsc.Abstract.Workflow { public class TaskManager : ITaskRunnerProvider @@ -14,4 +17,4 @@ namespace Yavsc.Abstract.Workflow return runners.Where(r => r.GetType().Name.IndexOf(runnerName.Trim())>=0).ToArray(); } } -} +} \ No newline at end of file diff --git a/src/Yavsc.Abstract/Yavsc.Abstract.csproj b/src/Yavsc.Abstract/Yavsc.Abstract.csproj index 78ffe531e..7a4f83ae6 100644 --- a/src/Yavsc.Abstract/Yavsc.Abstract.csproj +++ b/src/Yavsc.Abstract/Yavsc.Abstract.csproj @@ -5,13 +5,16 @@ A shared model for a little client/server app, dealing about establishing some contract, between some human client and provider. Yavsc.Abstract - https://forgejo.pschneider.fr/notazof/yavsc + https://github.com/pazof/yavsc true true latest 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070 + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 + + + \ No newline at end of file diff --git a/src/Yavsc.Api.Client/ActivityApiClient.cs b/src/Yavsc.Api.Client/ActivityApiClient.cs deleted file mode 100644 index 8199de9d4..000000000 --- a/src/Yavsc.Api.Client/ActivityApiClient.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Yavsc.Abstract.Workflow; - -namespace Yavsc.Api.Client; - -/// -/// HTTP client for browsing business activities and their performers. -/// Uses absolute URLs so it can coexist with other Yavsc clients that -/// target a different API host on the same shared transport. The same -/// activity payload also carries the eligible billing forms for a -/// selected performer/activity pair. -/// -public sealed class ActivityApiClient -{ - private const string PathPrefix = "activity"; - - private readonly IYavscApiClient _api; - private readonly Func _businessBaseAddress; - private readonly Func _avatarBaseAddress; - - public ActivityApiClient(IYavscApiClient api, string businessBaseAddress, string? avatarBaseAddress = null) - : this( - api, - () => businessBaseAddress, - () => avatarBaseAddress) - { - } - - public ActivityApiClient( - IYavscApiClient api, - Func businessBaseAddress, - Func? avatarBaseAddress = null) - { - _api = api ?? throw new ArgumentNullException(nameof(api)); - _businessBaseAddress = businessBaseAddress ?? throw new ArgumentNullException(nameof(businessBaseAddress)); - _avatarBaseAddress = avatarBaseAddress ?? (() => null); - - // Validate initial values early to fail fast on invalid setup. - _ = ResolveBusinessBaseAddress(); - _ = ResolveAvatarBaseAddress(); - } - - public Task> GetCatalogAsync( - string? parentCode = null, - CancellationToken ct = default) - { - var path = string.IsNullOrWhiteSpace(parentCode) - ? $"{PathPrefix}/catalog" - : $"{PathPrefix}/catalog?parentCode={Uri.EscapeDataString(parentCode)}"; - - return _api.CallAsync>(HttpMethod.Get, Absolute(path), ct: ct); - } - - public Task> GetUsersAsync( - string activityCode, - CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(activityCode)) - throw new ArgumentException("Activity code is required.", nameof(activityCode)); - - return _api.CallAsync>( - HttpMethod.Get, - Absolute($"{PathPrefix}/{Uri.EscapeDataString(activityCode)}/users"), - ct: ct); - } - - public Task> GetPerformersAsync( - string activityCode, - CancellationToken ct = default) - => GetUsersAsync(activityCode, ct); - - public string BuildAvatarXsUrl(string? userName) - { - var siteRoot = new Uri(ResolveAvatarBaseAddress(), "/"); - - if (string.IsNullOrWhiteSpace(userName)) - { - return new Uri(siteRoot, "images/Users/icon_user.xs.png").ToString(); - } - - return new Uri(siteRoot, $"avatars/{Uri.EscapeDataString(userName)}.xs.png").ToString(); - } - - private string Absolute(string relativePath) => new Uri(ResolveBusinessBaseAddress(), relativePath).ToString(); - - private Uri ResolveBusinessBaseAddress() - { - var raw = _businessBaseAddress(); - if (string.IsNullOrWhiteSpace(raw)) - throw new InvalidOperationException("Business base address is required."); - - return new Uri(raw, UriKind.Absolute); - } - - private Uri ResolveAvatarBaseAddress() - { - var raw = _avatarBaseAddress(); - return string.IsNullOrWhiteSpace(raw) - ? ResolveBusinessBaseAddress() - : new Uri(raw, UriKind.Absolute); - } -} diff --git a/src/Yavsc.Api.Client/BillingApiClient.cs b/src/Yavsc.Api.Client/BillingApiClient.cs deleted file mode 100644 index a9e9deda6..000000000 --- a/src/Yavsc.Api.Client/BillingApiClient.cs +++ /dev/null @@ -1,395 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Yavsc.Models.Billing; -using Yavsc.Models.Haircut; -using Yavsc; - -namespace Yavsc.Api.Client; - -/// -/// HTTP client for posting commands to the business billing routes. -/// Uses absolute URLs so it can coexist with blog-targeting clients on -/// the same shared transport. -/// -public sealed class BillingApiClient -{ - private const string PathPrefix = "billing"; - - private readonly IYavscApiClient _api; - private readonly Func _businessBaseAddress; - - public BillingApiClient(IYavscApiClient api, string businessBaseAddress) - : this(api, () => businessBaseAddress) - { - } - - public BillingApiClient(IYavscApiClient api, Func businessBaseAddress) - { - _api = api ?? throw new ArgumentNullException(nameof(api)); - _businessBaseAddress = businessBaseAddress ?? throw new ArgumentNullException(nameof(businessBaseAddress)); - - // Validate initial value early to fail fast on invalid setup. - _ = ResolveBusinessBaseAddress(); - } - - public Task CreateAsync(string billingCode, object payload, CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(billingCode)) - throw new ArgumentException("Billing code is required.", nameof(billingCode)); - - return _api.CallAsync( - HttpMethod.Post, - Absolute($"{PathPrefix}/{Uri.EscapeDataString(billingCode)}"), - body: payload, - ct: ct); - } - - public Task> GetHairPrestationsAsync(string billingCode, CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(billingCode)) - throw new ArgumentException("Billing code is required.", nameof(billingCode)); - - return _api.CallAsync>( - HttpMethod.Get, - Absolute($"{PathPrefix}/{Uri.EscapeDataString(billingCode)}/prestations"), - ct: ct); - } - - public async Task> GetQuerySummariesAsync(string billingCode, CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(billingCode)) - throw new ArgumentException("Billing code is required.", nameof(billingCode)); - - var items = await _api.CallAsync>( - HttpMethod.Get, - Absolute($"{PathPrefix}/{Uri.EscapeDataString(billingCode)}"), - ct: ct) ?? new List(); - - foreach (var item in items) - { - item.BillingCode = billingCode; - } - - return items; - } - - public Task> GetProviderOngoingQueriesAsync(CancellationToken ct = default) - { - return _api.CallAsync>( - HttpMethod.Get, - Absolute("bill/provider/ongoing"), - ct: ct); - } - - public async Task GetQueryAsync(string billingCode, long queryId, CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(billingCode)) - throw new ArgumentException("Billing code is required.", nameof(billingCode)); - if (queryId <= 0) - throw new ArgumentOutOfRangeException(nameof(queryId)); - - var code = billingCode.Trim(); - var path = Absolute($"{PathPrefix}/{Uri.EscapeDataString(code)}/{queryId}"); - - if (string.Equals(code, BillingCodes.Rdv, StringComparison.Ordinal)) - { - var dto = await _api.CallAsync(HttpMethod.Get, path, ct: ct).ConfigureAwait(false); - return MapRdv(dto, code); - } - - if (string.Equals(code, BillingCodes.Brush, StringComparison.Ordinal)) - { - var dto = await _api.CallAsync(HttpMethod.Get, path, ct: ct).ConfigureAwait(false); - return MapBrush(dto, code); - } - - if (string.Equals(code, BillingCodes.MBrush, StringComparison.Ordinal)) - { - var dto = await _api.CallAsync(HttpMethod.Get, path, ct: ct).ConfigureAwait(false); - return MapMBrush(dto, code); - } - - throw new NotSupportedException($"Billing code '{code}' is not supported."); - } - - public Task UpdateAsync(string billingCode, long queryId, BillingQueryDetailsDto payload, CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(billingCode)) - throw new ArgumentException("Billing code is required.", nameof(billingCode)); - if (queryId <= 0) - throw new ArgumentOutOfRangeException(nameof(queryId)); - if (payload is null) - throw new ArgumentNullException(nameof(payload)); - - var code = billingCode.Trim(); - - return _api.CallAsync( - HttpMethod.Put, - Absolute($"{PathPrefix}/{Uri.EscapeDataString(code)}/{queryId}"), - body: BuildUpdatePayload(code, queryId, payload), - ct: ct); - } - - private string Absolute(string relativePath) => new Uri(ResolveBusinessBaseAddress(), relativePath).ToString(); - - private Uri ResolveBusinessBaseAddress() - { - var raw = _businessBaseAddress(); - if (string.IsNullOrWhiteSpace(raw)) - throw new InvalidOperationException("Business base address is required."); - - return new Uri(raw, UriKind.Absolute); - } - - private static BillingQueryDetailsDto MapRdv(RdvQueryResponse dto, string billingCode) - { - return new BillingQueryDetailsDto - { - Id = dto.Id, - BillingCode = billingCode, - ActivityCode = dto.ActivityCode ?? string.Empty, - PerformerId = dto.PerformerId ?? string.Empty, - ClientId = dto.ClientId ?? string.Empty, - Description = dto.Description ?? string.Empty, - Consent = dto.Consent, - EventDate = dto.EventDate, - Status = dto.Status, - Reason = dto.Reason ?? string.Empty, - Provisional = dto.Provisional, - Location = dto.Location is null - ? null - : new BillingLocationDto - { - Id = dto.Location.Id > 0 ? dto.Location.Id : null, - Address = dto.Location.Address ?? string.Empty, - Latitude = dto.Location.Latitude, - Longitude = dto.Location.Longitude, - } - }; - } - - private static BillingQueryDetailsDto MapBrush(HairCutQueryResponse dto, string billingCode) - { - return new BillingQueryDetailsDto - { - Id = dto.Id, - BillingCode = billingCode, - ActivityCode = dto.ActivityCode ?? string.Empty, - PerformerId = dto.PerformerId ?? string.Empty, - ClientId = dto.ClientId ?? string.Empty, - Description = dto.Description ?? string.Empty, - Consent = dto.Consent, - EventDate = dto.EventDate, - Status = dto.Status, - AdditionalInfo = dto.AdditionalInfo ?? string.Empty, - Provisional = dto.Provisional, - PrestationId = dto.PrestationId, - Location = dto.Location is null - ? null - : new BillingLocationDto - { - Id = dto.Location.Id > 0 ? dto.Location.Id : null, - Address = dto.Location.Address ?? string.Empty, - Latitude = dto.Location.Latitude, - Longitude = dto.Location.Longitude, - } - }; - } - - private static BillingQueryDetailsDto MapMBrush(HairMultiCutQueryResponse dto, string billingCode) - { - return new BillingQueryDetailsDto - { - Id = dto.Id, - BillingCode = billingCode, - ActivityCode = dto.ActivityCode ?? string.Empty, - PerformerId = dto.PerformerId ?? string.Empty, - ClientId = dto.ClientId ?? string.Empty, - Description = dto.Description ?? string.Empty, - Consent = dto.Consent, - EventDate = dto.EventDate, - Status = dto.Status, - Provisional = dto.Provisional, - PrestationIds = (dto.Prestations ?? new List()) - .Select(p => p.PrestationId) - .Where(id => id > 0) - .ToList(), - Location = dto.Location is null - ? null - : new BillingLocationDto - { - Id = dto.Location.Id > 0 ? dto.Location.Id : null, - Address = dto.Location.Address ?? string.Empty, - Latitude = dto.Location.Latitude, - Longitude = dto.Location.Longitude, - } - }; - } - - private static object BuildUpdatePayload(string billingCode, long queryId, BillingQueryDetailsDto payload) - { - if (string.Equals(billingCode, BillingCodes.Rdv, StringComparison.Ordinal)) - { - if (payload.EventDate is null) - throw new ArgumentException("EventDate is required for Rdv.", nameof(payload)); - - return new - { - Id = queryId, - ActivityCode = payload.ActivityCode, - PerformerId = payload.PerformerId, - ClientId = payload.ClientId, - Consent = payload.Consent, - EventDate = payload.EventDate.Value, - Location = ToLocationPayload(payload.Location), - Reason = payload.Reason, - Status = payload.Status, - Provisional = payload.Provisional, - Description = payload.Description, - }; - } - - if (string.Equals(billingCode, BillingCodes.Brush, StringComparison.Ordinal)) - { - if (payload.PrestationId is null || payload.PrestationId <= 0) - throw new ArgumentException("PrestationId is required for Brush.", nameof(payload)); - - return new - { - Id = queryId, - ActivityCode = payload.ActivityCode, - PerformerId = payload.PerformerId, - ClientId = payload.ClientId, - Consent = payload.Consent, - EventDate = payload.EventDate, - Location = ToLocationPayload(payload.Location), - PrestationId = payload.PrestationId.Value, - AdditionalInfo = payload.AdditionalInfo, - Status = payload.Status, - Provisional = payload.Provisional, - Description = payload.Description, - }; - } - - if (string.Equals(billingCode, BillingCodes.MBrush, StringComparison.Ordinal)) - { - if (payload.EventDate is null) - throw new ArgumentException("EventDate is required for MBrush.", nameof(payload)); - if (payload.PrestationIds is null || payload.PrestationIds.Count == 0) - throw new ArgumentException("At least one prestation is required for MBrush.", nameof(payload)); - - return new - { - Id = queryId, - ActivityCode = payload.ActivityCode, - PerformerId = payload.PerformerId, - ClientId = payload.ClientId, - Consent = payload.Consent, - EventDate = payload.EventDate.Value, - Location = ToLocationPayload(payload.Location), - Prestations = payload.PrestationIds - .Where(id => id > 0) - .Select(id => new { PrestationId = id }) - .ToList(), - Status = payload.Status, - Provisional = payload.Provisional, - Description = payload.Description, - }; - } - - throw new NotSupportedException($"Billing code '{billingCode}' is not supported."); - } - - private static object? ToLocationPayload(BillingLocationDto? location) - { - if (location is null) - { - return null; - } - - var payload = new Dictionary - { - ["Address"] = location.Address, - }; - - if (location.Id.HasValue && location.Id.Value > 0) - { - payload["Id"] = location.Id.Value; - } - - if (location.Latitude.HasValue) - { - payload["Latitude"] = location.Latitude.Value; - } - - if (location.Longitude.HasValue) - { - payload["Longitude"] = location.Longitude.Value; - } - - return payload; - } - - private sealed class BillingLocationResponse - { - public long Id { get; set; } - public string? Address { get; set; } - public double Latitude { get; set; } - public double Longitude { get; set; } - } - - private sealed class RdvQueryResponse - { - public long Id { get; set; } - public string? ActivityCode { get; set; } - public string? PerformerId { get; set; } - public string? ClientId { get; set; } - public string? Description { get; set; } - public bool Consent { get; set; } - public DateTime EventDate { get; set; } - public QueryStatus Status { get; set; } - public string? Reason { get; set; } - public decimal? Provisional { get; set; } - public BillingLocationResponse? Location { get; set; } - } - - private sealed class HairCutQueryResponse - { - public long Id { get; set; } - public string? ActivityCode { get; set; } - public string? PerformerId { get; set; } - public string? ClientId { get; set; } - public string? Description { get; set; } - public bool Consent { get; set; } - public DateTime? EventDate { get; set; } - public QueryStatus Status { get; set; } - public decimal? Provisional { get; set; } - public long PrestationId { get; set; } - public string? AdditionalInfo { get; set; } - public BillingLocationResponse? Location { get; set; } - } - - private sealed class HairMultiCutQueryResponse - { - public long Id { get; set; } - public string? ActivityCode { get; set; } - public string? PerformerId { get; set; } - public string? ClientId { get; set; } - public string? Description { get; set; } - public bool Consent { get; set; } - public DateTime EventDate { get; set; } - public QueryStatus Status { get; set; } - public decimal? Provisional { get; set; } - public BillingLocationResponse? Location { get; set; } - public List? Prestations { get; set; } - } - - private sealed class HairPrestationCollectionItemResponse - { - public long PrestationId { get; set; } - } -} diff --git a/src/Yavsc.Api.Client/BlogAclApiClient.cs b/src/Yavsc.Api.Client/BlogAclApiClient.cs index 9a93d3103..4cb19f9a7 100644 --- a/src/Yavsc.Api.Client/BlogAclApiClient.cs +++ b/src/Yavsc.Api.Client/BlogAclApiClient.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using Yavsc.Abstract.BlogSpot; -using Yavsc.Abstract.Identity.Security; using Yavsc.Api.Client.Dtos; namespace Yavsc.Api.Client; @@ -12,7 +10,7 @@ namespace Yavsc.Api.Client; /// /// HTTP client for /api/blogacl on the Yavsc Blogs server. /// -/// Each grants a single +/// Each grants a single /// Circle access to a single BlogPostDto. The server /// scopes every endpoint to the caller's uid: only the author of /// the underlying blog post can list, create, modify, or delete @@ -34,16 +32,16 @@ public sealed class BlogAclApiClient api.Http.BaseAddress = new Uri(blogsBaseAddress); } - public Task> GetMyAclAsync(CancellationToken ct = default) - => _api.CallAsync>(HttpMethod.Get, Path, ct: ct); + public Task> GetMyAclAsync(CancellationToken ct = default) + => _api.CallAsync>(HttpMethod.Get, Path, ct: ct); - public Task GetAclAsync(long circleId, CancellationToken ct = default) - => _api.CallAsync(HttpMethod.Get, $"{Path}/{circleId}", ct: ct); + public Task GetAclAsync(long circleId, CancellationToken ct = default) + => _api.CallAsync(HttpMethod.Get, $"{Path}/{circleId}", ct: ct); - public Task GrantAsync(PostAccessControlRulePayload acl, CancellationToken ct = default) - => _api.CallAsync(HttpMethod.Post, Path, body: acl, ct: ct); + public Task GrantAsync(CircleAuthorizationDto acl, CancellationToken ct = default) + => _api.CallAsync(HttpMethod.Post, Path, body: acl, ct: ct); - public Task UpdateAclAsync(long circleId, PostAccessControlRulePayload acl, CancellationToken ct = default) + public Task UpdateAclAsync(long circleId, CircleAuthorizationDto acl, CancellationToken ct = default) => _api.CallAsync(HttpMethod.Put, $"{Path}/{circleId}", body: acl, ct: ct); public Task RevokeAsync(long circleId, CancellationToken ct = default) diff --git a/src/Yavsc.Api.Client/BlogApiClient.cs b/src/Yavsc.Api.Client/BlogApiClient.cs index 3de5b37ec..cbc823581 100644 --- a/src/Yavsc.Api.Client/BlogApiClient.cs +++ b/src/Yavsc.Api.Client/BlogApiClient.cs @@ -1,5 +1,8 @@ -using System.Net.Http.Headers; -using System.Text.Json; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; using Yavsc.Blogspot; namespace Yavsc.Api.Client; @@ -30,7 +33,7 @@ namespace Yavsc.Api.Client; /// public sealed class BlogApiClient { - private const string DefaultPathPrefix = "blogspot"; + private const string DefaultPathPrefix = "blog"; private readonly IYavscApiClient _api; private readonly Uri _baseAddress; @@ -59,18 +62,11 @@ public sealed class BlogApiClient public Task GetPostAsync(long id, CancellationToken ct = default) => _api.CallAsync(HttpMethod.Get, $"{_pathPrefix}/{id}", ct: ct); - public Task CreatePostAsync( - BlogPostDto post, - IReadOnlyCollection? files = null, - CancellationToken ct = default) - => SendPostAsync(HttpMethod.Post, _pathPrefix, post, files, ct); + public Task CreatePostAsync(BlogPostDto post, CancellationToken ct = default) + => _api.CallAsync(HttpMethod.Post, _pathPrefix, body: post, ct: ct); - public Task UpdatePostAsync( - long id, - BlogPostDto post, - IReadOnlyCollection? files = null, - CancellationToken ct = default) - => SendPostAsync(HttpMethod.Put, $"{_pathPrefix}/{id}", post, files, ct); + public Task UpdatePostAsync(long id, BlogPostDto post, CancellationToken ct = default) + => _api.CallAsync(HttpMethod.Put, $"{_pathPrefix}/{id}", body: post, ct: ct); public Task DeletePostAsync(long id, CancellationToken ct = default) => _api.CallAsync(HttpMethod.Delete, $"{_pathPrefix}/{id}", ct: ct); @@ -85,39 +81,4 @@ public sealed class BlogApiClient public Task SetPublishAsync(long id, bool publish, CancellationToken ct = default) => _api.CallAsync(HttpMethod.Put, $"{_pathPrefix}/{id}/publish", body: new { publish }, ct: ct); - - private async Task SendPostAsync( - HttpMethod method, - string path, - BlogPostDto post, - IReadOnlyCollection? files, - CancellationToken ct) - { - if (files is null || files.Count == 0) - return await _api.CallAsync(method, path, body: post, ct: ct); - - return await _api.CallAsync(method, path, () => CreateMultipartContent(post, files), ct: ct); - } - - private static HttpContent CreateMultipartContent(BlogPostDto post, IReadOnlyCollection files) - { - var content = new MultipartFormDataContent(); - var blogJson = JsonSerializer.Serialize(post, new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, - }); - - content.Add(new StringContent(blogJson), "blog"); - - foreach (var file in files) - { - var fileContent = new ByteArrayContent(file.Content); - fileContent.Headers.ContentType = new MediaTypeHeaderValue( - string.IsNullOrWhiteSpace(file.ContentType) ? "application/octet-stream" : file.ContentType); - content.Add(fileContent, "file", file.FileName); - } - - return content; - } } diff --git a/src/Yavsc.Api.Client/BlogUploadFile.cs b/src/Yavsc.Api.Client/BlogUploadFile.cs deleted file mode 100644 index 4b3a7b26d..000000000 --- a/src/Yavsc.Api.Client/BlogUploadFile.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Yavsc.Api.Client; - -/// -/// Buffered file payload for multipart blog uploads. -/// -public sealed record BlogUploadFile(string FileName, byte[] Content, string? ContentType = null); diff --git a/src/Yavsc.Api.Client/Dtos/BillingQueryDetailsDto.cs b/src/Yavsc.Api.Client/Dtos/BillingQueryDetailsDto.cs deleted file mode 100644 index eb33ba816..000000000 --- a/src/Yavsc.Api.Client/Dtos/BillingQueryDetailsDto.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text.Json.Serialization; -using Yavsc; - -namespace Yavsc.Api.Client; - -/// -/// Normalized billing-query shape used by PostIt when opening an existing -/// command from history. -/// -public sealed class BillingQueryDetailsDto -{ - public long Id { get; set; } - public string BillingCode { get; set; } = string.Empty; - public string ActivityCode { get; set; } = string.Empty; - public string PerformerId { get; set; } = string.Empty; - public string ClientId { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public bool Consent { get; set; } = true; - public DateTime? EventDate { get; set; } - public QueryStatus Status { get; set; } = QueryStatus.Inserted; - public string Reason { get; set; } = string.Empty; - public string AdditionalInfo { get; set; } = string.Empty; - public decimal? Provisional { get; set; } - public BillingLocationDto? Location { get; set; } - public long? PrestationId { get; set; } - public List PrestationIds { get; set; } = new(); -} - -public sealed class BillingLocationDto -{ - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public long? Id { get; set; } - - public string Address { get; set; } = string.Empty; - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public double? Latitude { get; set; } - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public double? Longitude { get; set; } -} diff --git a/src/Yavsc.Api.Client/Dtos/BillingQuerySummaryDto.cs b/src/Yavsc.Api.Client/Dtos/BillingQuerySummaryDto.cs deleted file mode 100644 index 0102b6917..000000000 --- a/src/Yavsc.Api.Client/Dtos/BillingQuerySummaryDto.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using Yavsc; - -namespace Yavsc.Api.Client; - -/// -/// Lightweight billing-query projection consumed by PostIt list views. -/// Extra JSON fields from concrete query types are ignored. -/// -public sealed class BillingQuerySummaryDto -{ - public long Id { get; set; } - public string BillingCode { get; set; } = string.Empty; - public string ActivityCode { get; set; } = string.Empty; - public string PerformerId { get; set; } = string.Empty; - public string ClientId { get; set; } = string.Empty; - public QueryStatus Status { get; set; } - public string Description { get; set; } = string.Empty; - public DateTime? EventDate { get; set; } - public string Reason { get; set; } = string.Empty; - public string AdditionalInfo { get; set; } = string.Empty; - public decimal? Provisional { get; set; } -} \ No newline at end of file diff --git a/src/Yavsc.Abstract/Identity/Security/CircleAuthorization.cs b/src/Yavsc.Api.Client/Dtos/CircleAuthorizationDto.cs similarity index 78% rename from src/Yavsc.Abstract/Identity/Security/CircleAuthorization.cs rename to src/Yavsc.Api.Client/Dtos/CircleAuthorizationDto.cs index 96392c7bf..f5d1e50ea 100644 --- a/src/Yavsc.Abstract/Identity/Security/CircleAuthorization.cs +++ b/src/Yavsc.Api.Client/Dtos/CircleAuthorizationDto.cs @@ -1,4 +1,4 @@ -namespace Yavsc.Abstract.Identity.Security; +namespace Yavsc.Api.Client.Dtos; /// /// Wire format for GET /api/blogacl and friends. @@ -11,7 +11,9 @@ namespace Yavsc.Abstract.Identity.Security; /// UI already has the post, and the circles are looked up by id /// against the list returned by GET /api/circle. /// -public class CircleAuthorization +public sealed class CircleAuthorizationDto { public long CircleId { get; set; } + public long BlogPostId { get; set; } + public bool Comment { get; set; } } diff --git a/src/Yavsc.Api.Client/Dtos/EstimateDtos.cs b/src/Yavsc.Api.Client/Dtos/EstimateDtos.cs deleted file mode 100644 index 43c09d94a..000000000 --- a/src/Yavsc.Api.Client/Dtos/EstimateDtos.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Yavsc.Api.Client; - -/// -/// A single billable line of an estimate, mirroring the JSON shape of -/// the server-side Yavsc.Models.Billing.CommandLine entity. -/// -public sealed class EstimateLineDto -{ - public long Id { get; set; } - public string Name { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public int Count { get; set; } = 1; - public decimal UnitaryCost { get; set; } - public long EstimateId { get; set; } - public string Currency { get; set; } = "EUR"; -} - -/// -/// Estimate payload exchanged with the api/v1/estimate routes -/// (EstimateApiController). and -/// are always initialised: the server-side -/// entity reads them from non-nullable string properties and a null -/// list would break its serialisation. -/// -public sealed class EstimateDto -{ - public long Id { get; set; } - public long? CommandId { get; set; } - public string Title { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public List Bill { get; set; } = new(); - public List AttachedGraphics { get; set; } = new(); - public List AttachedFiles { get; set; } = new(); - public string? OwnerId { get; set; } - public string ClientId { get; set; } = string.Empty; - public string CommandType { get; set; } = string.Empty; - public DateTime ProviderValidationDate { get; set; } - public DateTime ClientValidationDate { get; set; } -} - -/// -/// Response of a successful estimate creation -/// (Ok(new { estimate.Id, estimate.Bill })). -/// -public sealed class EstimateCreatedDto -{ - public long Id { get; set; } - public List Bill { get; set; } = new(); -} diff --git a/src/Yavsc.Api.Client/EstimateApiClient.cs b/src/Yavsc.Api.Client/EstimateApiClient.cs deleted file mode 100644 index 15b260786..000000000 --- a/src/Yavsc.Api.Client/EstimateApiClient.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace Yavsc.Api.Client; - -/// -/// HTTP client for the estimate routes (api/v1/estimate, -/// served by EstimateApiController). Follows the same -/// DTO↔path mapper shape as : all -/// transport concerns (base URL, JSON, Bearer auth, silent refresh -/// on 401) are delegated to . -/// -public sealed class EstimateApiClient -{ - private const string PathPrefix = "estimate"; - - private readonly IYavscApiClient _api; - private readonly Func _businessBaseAddress; - - public EstimateApiClient(IYavscApiClient api, string businessBaseAddress) - : this(api, () => businessBaseAddress) - { - } - - public EstimateApiClient(IYavscApiClient api, Func businessBaseAddress) - { - _api = api ?? throw new ArgumentNullException(nameof(api)); - _businessBaseAddress = businessBaseAddress ?? throw new ArgumentNullException(nameof(businessBaseAddress)); - - // Validate initial value early to fail fast on invalid setup. - _ = ResolveBusinessBaseAddress(); - } - - /// - /// Lists the estimates of the given owner; when - /// is null, the server falls back to the current user. - /// - public Task> GetEstimatesAsync(string? ownerId = null, CancellationToken ct = default) - { - var path = string.IsNullOrWhiteSpace(ownerId) - ? PathPrefix - : $"{PathPrefix}?ownerId={Uri.EscapeDataString(ownerId)}"; - - return _api.CallAsync>(HttpMethod.Get, Absolute(path), ct: ct); - } - - public Task GetEstimateAsync(long id, CancellationToken ct = default) - { - if (id <= 0) - throw new ArgumentOutOfRangeException(nameof(id)); - - return _api.CallAsync(HttpMethod.Get, Absolute($"{PathPrefix}/{id}"), ct: ct); - } - - /// - /// Creates an estimate. When is set, - /// the server also stamps the linked command as validated. - /// - public async Task CreateAsync(EstimateDto estimate, CancellationToken ct = default) - { - if (estimate is null) - throw new ArgumentNullException(nameof(estimate)); - - var created = await _api.CallAsync( - HttpMethod.Post, - Absolute(PathPrefix), - body: estimate, - ct: ct).ConfigureAwait(false); - - return created ?? new EstimateCreatedDto(); - } - - public Task UpdateAsync(long id, EstimateDto estimate, CancellationToken ct = default) - { - if (id <= 0) - throw new ArgumentOutOfRangeException(nameof(id)); - if (estimate is null) - throw new ArgumentNullException(nameof(estimate)); - - estimate.Id = id; - - return _api.CallAsync( - HttpMethod.Put, - Absolute($"{PathPrefix}/{id}"), - body: estimate, - ct: ct); - } - - public Task DeleteAsync(long id, CancellationToken ct = default) - { - if (id <= 0) - throw new ArgumentOutOfRangeException(nameof(id)); - - return _api.CallAsync(HttpMethod.Delete, Absolute($"{PathPrefix}/{id}"), ct: ct); - } - - private string Absolute(string relativePath) => new Uri(ResolveBusinessBaseAddress(), relativePath).ToString(); - - private Uri ResolveBusinessBaseAddress() - { - var raw = _businessBaseAddress(); - if (string.IsNullOrWhiteSpace(raw)) - throw new InvalidOperationException("Business base address is required."); - - return new Uri(raw, UriKind.Absolute); - } -} diff --git a/src/Yavsc.Api.Client/IYavscApiClient.cs b/src/Yavsc.Api.Client/IYavscApiClient.cs index 1d470b682..209ec07d7 100644 --- a/src/Yavsc.Api.Client/IYavscApiClient.cs +++ b/src/Yavsc.Api.Client/IYavscApiClient.cs @@ -53,24 +53,10 @@ public interface IYavscApiClient : IAsyncDisposable object? body = null, CancellationToken ct = default); - /// Call a multipart endpoint with a typed return value. - Task CallAsync( - HttpMethod method, - string path, - Func contentFactory, - CancellationToken ct = default); - /// Call a JSON endpoint that returns no useful body (DELETE, 204, etc.). Task CallAsync( HttpMethod method, string path, object? body = null, CancellationToken ct = default); - - /// Call a multipart endpoint that returns no useful body. - Task CallAsync( - HttpMethod method, - string path, - Func contentFactory, - CancellationToken ct = default); } diff --git a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj index 6ceaab113..ade7ca17c 100644 --- a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj +++ b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj @@ -17,9 +17,12 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070 + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 + + + diff --git a/src/Yavsc.Api.Test/ActivityApiControllerTests.cs b/src/Yavsc.Api.Test/ActivityApiControllerTests.cs deleted file mode 100644 index 0525e349f..000000000 --- a/src/Yavsc.Api.Test/ActivityApiControllerTests.cs +++ /dev/null @@ -1,161 +0,0 @@ -using System.Net; -using System.Net.Http.Headers; -using System.Net.Http.Json; -using Microsoft.Extensions.DependencyInjection; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Test.Fixtures; -using Yavsc.Tests.Shared; - -namespace Yavsc.Api.Test; - -[Collection("Yavsc Api")] -public sealed class ActivityApiControllerTests : IClassFixture -{ - private readonly ApiWebServerFixture _fixture; - - public ActivityApiControllerTests(ApiWebServerFixture fixture) - { - _fixture = fixture; - } - - private HttpClient NewClient(string subject = "alice", string scope = "api") - { - var handler = new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (_, _, _, _) => true - }; - var http = new HttpClient(handler) - { - BaseAddress = new Uri(_fixture.BaseAddress) - }; - http.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, scope)); - return http; - } - - [Fact] - public async Task GetUsers_returns_declared_user_for_exact_activity_code() - { - _fixture.ResetAndSeedActivityGraph(); - using var http = NewClient(); - - var response = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var payload = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(payload); - Assert.Single(payload!); - Assert.Equal("alice", payload[0].PerformerId); - Assert.Equal("alice", payload[0].UserName); - Assert.True(payload[0].HasPerformerProfile); - Assert.True(payload[0].Active); - Assert.Equal("dev", payload[0].ActivityCode); - } - - [Fact] - public async Task GetUsers_returns_user_even_when_performer_inactive() - { - _fixture.ResetAndSeedActivityGraph(); - using (var scope = _fixture.Services.CreateScope()) - { - var db = scope.ServiceProvider.GetRequiredService(); - var performer = db.Performers.Single(p => p.PerformerId == "alice"); - performer.Active = false; - db.SaveChanges(); - } - - using var http = NewClient(); - var response = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var payload = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(payload); - Assert.Single(payload!); - Assert.Equal("alice", payload[0].PerformerId); - Assert.False(payload[0].Active); - } - - [Fact] - public async Task Catalog_and_Performers_are_consistent_for_dev_activity() - { - _fixture.ResetAndSeedActivityGraph(); - using var http = NewClient(); - - var catalogResponse = await http.GetAsync("/api/v1/activity/catalog", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, catalogResponse.StatusCode); - - var catalog = await catalogResponse.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(catalog); - - var dev = catalog!.Single(a => a.Code == "dev"); - Assert.True(dev.PerformerCount > 0); - - var performersResponse = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, performersResponse.StatusCode); - - var performers = await performersResponse.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(performers); - Assert.Equal(dev.PerformerCount, performers!.Count); - Assert.Contains(performers, p => p.PerformerId == "alice"); - } - - [Fact] - public async Task Catalog_does_not_list_activity_without_declaration() - { - _fixture.ResetAndSeedActivityGraph(); - using var http = NewClient(); - - var catalogResponse = await http.GetAsync("/api/v1/activity/catalog", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, catalogResponse.StatusCode); - - var catalog = await catalogResponse.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(catalog); - Assert.DoesNotContain(catalog!, a => a.Code == "ghost"); - Assert.Contains(catalog!, a => a.Code == "dev"); - } - - [Fact] - public async Task Catalog_lists_declared_activity_even_when_performer_inactive() - { - _fixture.ResetAndSeedActivityGraph(); - using var http = NewClient(); - - var catalogResponse = await http.GetAsync("/api/v1/activity/catalog", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, catalogResponse.StatusCode); - - var catalog = await catalogResponse.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(catalog); - Assert.Contains(catalog!, a => a.Code == "declared-only"); - - var response = await http.GetAsync("/api/v1/activity/declared-only/users", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var payload = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(payload); - Assert.Single(payload!); - Assert.Equal("bob", payload[0].PerformerId); - Assert.Equal("bob", payload[0].UserName); - Assert.True(payload[0].HasPerformerProfile); - Assert.False(payload[0].Active); - } - - [Fact] - public async Task Performers_alias_returns_same_payload_as_users_endpoint() - { - _fixture.ResetAndSeedActivityGraph(); - using var http = NewClient(); - - var usersResponse = await http.GetAsync("/api/v1/activity/dev/users", TestContext.Current.CancellationToken); - var performersResponse = await http.GetAsync("/api/v1/activity/dev/performers", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, usersResponse.StatusCode); - Assert.Equal(HttpStatusCode.OK, performersResponse.StatusCode); - - var usersPayload = await usersResponse.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - var performersPayload = await performersResponse.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - - Assert.NotNull(usersPayload); - Assert.NotNull(performersPayload); - Assert.Equal(usersPayload!.Count, performersPayload!.Count); - Assert.Equal(usersPayload[0].PerformerId, performersPayload[0].PerformerId); - Assert.Equal(usersPayload[0].UserName, performersPayload[0].UserName); - } -} diff --git a/src/Yavsc.Api.Test/ApiCollection.cs b/src/Yavsc.Api.Test/ApiCollection.cs deleted file mode 100644 index 2c670522b..000000000 --- a/src/Yavsc.Api.Test/ApiCollection.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Yavsc.Api.Test; - -[CollectionDefinition("Yavsc Api")] -public sealed class ApiCollection -{ -} diff --git a/src/Yavsc.Api.Test/BillingControllerTests.cs b/src/Yavsc.Api.Test/BillingControllerTests.cs deleted file mode 100644 index 38b9a9919..000000000 --- a/src/Yavsc.Api.Test/BillingControllerTests.cs +++ /dev/null @@ -1,236 +0,0 @@ -using System.Net; -using System.Net.Http.Headers; -using System.Net.Http.Json; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Yavsc.Api.Test.Fixtures; -using Yavsc.Helpers; -using Yavsc.Models; -using Yavsc.Models.Billing; -using Yavsc.Models.Haircut; -using Yavsc.Models.Workflow; -using Yavsc.Tests.Shared; - -namespace Yavsc.Api.Test; - -[Collection("Yavsc Api")] -public sealed class BillingControllerTests : IClassFixture -{ - private readonly ApiWebServerFixture _fixture; - - public BillingControllerTests(ApiWebServerFixture fixture) - { - _fixture = fixture; - } - - private HttpClient NewClient(string subject = "alice", string scope = "api") - { - var handler = new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (_, _, _, _) => true - }; - - var http = new HttpClient(handler) - { - BaseAddress = new Uri(_fixture.BaseAddress) - }; - - http.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, scope)); - - return http; - } - - [Fact] - public async Task GetProviderOngoingCommands_returns_current_provider_requests() - { - WorkflowHelpers.ConfigureBillingService(); - _fixture.ResetAndSeedActivityGraph(); - - using (var scope = _fixture.Services.CreateScope()) - { - var db = scope.ServiceProvider.GetRequiredService(); - var location = db.Locations.Single(); - - db.RdvQueries.Add(new RdvQuery - { - ActivityCode = "dev", - ClientId = "bob", - PerformerId = "alice", - Consent = true, - UserCreated = "alice", - UserModified = "alice", - DateCreated = DateTime.UtcNow.AddMinutes(-10), - DateModified = DateTime.UtcNow.AddMinutes(-8), - EventDate = DateTime.UtcNow.AddDays(1), - Location = location, - Reason = "Rendez-vous fournisseur", - Status = QueryStatus.InProgress, - Description = "Commande fournisseur en cours", - }); - - db.RdvQueries.Add(new RdvQuery - { - ActivityCode = "dev", - ClientId = "alice", - PerformerId = "bob", - Consent = true, - UserCreated = "bob", - UserModified = "bob", - DateCreated = DateTime.UtcNow.AddMinutes(-20), - DateModified = DateTime.UtcNow.AddMinutes(-20), - EventDate = DateTime.UtcNow.AddDays(2), - Location = location, - Reason = "Commande d'un autre prestataire", - Status = QueryStatus.Accepted, - Description = "Autre prestataire", - }); - - db.SaveChanges(); - } - - using var http = NewClient(); - - var response = await http.GetAsync("/api/v1/bill/provider/ongoing", TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, $"Unexpected status {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - var payload = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(payload); - Assert.NotEmpty(payload!); - Assert.All(payload!, item => Assert.Equal("alice", item.PerformerId)); - Assert.Contains(payload!, item => item.BillingCode == BillingCodes.Rdv); - } - - [Fact] - public async Task GetProviderOngoingCommands_ignores_rows_with_invalid_discriminator() - { - WorkflowHelpers.ConfigureBillingService(); - _fixture.ResetAndSeedActivityGraph(); - - using (var scope = _fixture.Services.CreateScope()) - { - var db = scope.ServiceProvider.GetRequiredService(); - - db.Database.ExecuteSqlInterpolated($@" -INSERT INTO ""NominativeServiceCommands"" -(""ActivityCode"", ""ClientId"", ""Consent"", ""DateCreated"", ""DateModified"", ""Description"", ""Discriminator"", ""PerformerId"", ""Status"", ""UserCreated"", ""UserModified"") -VALUES -({"dev"}, {"bob"}, {true}, {DateTime.UtcNow.AddMinutes(-5)}, {DateTime.UtcNow.AddMinutes(-4)}, {"Legacy malformed row"}, {""}, {"alice"}, {(int)QueryStatus.Accepted}, {"alice"}, {"alice"}); -"); - - var location = db.Locations.Single(); - db.RdvQueries.Add(new RdvQuery - { - ActivityCode = "dev", - ClientId = "bob", - PerformerId = "alice", - Consent = true, - UserCreated = "alice", - UserModified = "alice", - DateCreated = DateTime.UtcNow.AddMinutes(-3), - DateModified = DateTime.UtcNow.AddMinutes(-2), - EventDate = DateTime.UtcNow.AddDays(1), - Location = location, - Reason = "Commande valide", - Status = QueryStatus.InProgress, - Description = "Commande fournisseur valide", - }); - - db.SaveChanges(); - } - - using var http = NewClient(); - - var response = await http.GetAsync("/api/v1/bill/provider/ongoing", TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, $"Unexpected status {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - var payload = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(payload); - Assert.NotEmpty(payload!); - Assert.Contains(payload!, item => item.BillingCode == BillingCodes.Rdv && item.PerformerId == "alice"); - Assert.DoesNotContain(payload!, item => string.IsNullOrWhiteSpace(item.BillingCode)); - } - - [Fact] - public async Task GetProviderOngoingCommands_returns_haircut_and_grouped_haircut_requests() - { - WorkflowHelpers.ConfigureBillingService(); - _fixture.ResetAndSeedHaircutGraph(); - - using (var scope = _fixture.Services.CreateScope()) - { - var db = scope.ServiceProvider.GetRequiredService(); - db.UserActivities.Add(new UserActivity - { - UserId = "alice", - DoesCode = "brush", - Weight = 50, - }); - db.UserActivities.Add(new UserActivity - { - UserId = "alice", - DoesCode = "mbrush", - Weight = 50, - }); - db.CommandForm.Add(new CommandForm - { - ActivityCode = "brush", - ActionName = BillingCodes.Brush, - Title = "Brush", - }); - db.CommandForm.Add(new CommandForm - { - ActivityCode = "mbrush", - ActionName = BillingCodes.MBrush, - Title = "MBrush", - }); - db.SaveChanges(); - } - - using var http = NewClient(); - - var response = await http.GetAsync("/api/v1/bill/provider/ongoing", TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, $"Unexpected status {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - var payload = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(payload); - Assert.Contains(payload!, item => item.BillingCode == BillingCodes.Brush && item.PerformerId == "alice"); - Assert.Contains(payload!, item => item.BillingCode == BillingCodes.MBrush && item.PerformerId == "alice"); - } - - [Fact] - public async Task GetProviderOngoingCommands_excludes_requests_outside_performer_declared_activities() - { - WorkflowHelpers.ConfigureBillingService(); - _fixture.ResetAndSeedHaircutGraph(); - - using var http = NewClient(); - - var response = await http.GetAsync("/api/v1/bill/provider/ongoing", TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, $"Unexpected status {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - var payload = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(payload); - Assert.DoesNotContain(payload!, item => item.BillingCode == BillingCodes.Brush); - Assert.DoesNotContain(payload!, item => item.BillingCode == BillingCodes.MBrush); - } - - private sealed class ProviderOngoingCommandDto - { - public long Id { get; set; } - public string BillingCode { get; set; } = string.Empty; - public string ActivityCode { get; set; } = string.Empty; - public string PerformerId { get; set; } = string.Empty; - public string ClientId { get; set; } = string.Empty; - public QueryStatus Status { get; set; } - public string Description { get; set; } = string.Empty; - } -} diff --git a/src/Yavsc.Api.Test/Directory.Packages.props b/src/Yavsc.Api.Test/Directory.Packages.props deleted file mode 100644 index c2edf9eae..000000000 --- a/src/Yavsc.Api.Test/Directory.Packages.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/src/Yavsc.Api.Test/EstimateApiControllerTests.cs b/src/Yavsc.Api.Test/EstimateApiControllerTests.cs deleted file mode 100644 index f48f81ce5..000000000 --- a/src/Yavsc.Api.Test/EstimateApiControllerTests.cs +++ /dev/null @@ -1,209 +0,0 @@ -using System.Net; -using System.Net.Http.Headers; -using System.Net.Http.Json; -using System.Text.Json; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Yavsc.Api.Test.Fixtures; -using Yavsc.Models; -using Yavsc.Models.Billing; -using Yavsc.Models.Workflow; -using Yavsc.Tests.Shared; - -namespace Yavsc.Api.Test; - -[Collection("Yavsc Api")] -public sealed class EstimateApiControllerTests : IClassFixture -{ - private readonly ApiWebServerFixture _fixture; - - public EstimateApiControllerTests(ApiWebServerFixture fixture) - { - _fixture = fixture; - } - - private HttpClient NewClient(string subject = "alice", string scope = "api") - { - var handler = new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (_, _, _, _) => true - }; - - var http = new HttpClient(handler) - { - BaseAddress = new Uri(_fixture.BaseAddress) - }; - - http.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, scope)); - - return http; - } - - /// - /// Seed a provider (alice) and a client (bob) with a pending - /// from bob to alice, and return the - /// command id. - /// - private long SeedPendingCommand() - { - _fixture.ResetAndSeedActivityGraph(); - - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - var location = db.Locations.Single(); - - var query = new RdvQuery - { - ActivityCode = "dev", - ClientId = "bob", - PerformerId = "alice", - Consent = true, - UserCreated = "bob", - UserModified = "bob", - DateCreated = DateTime.UtcNow.AddMinutes(-10), - DateModified = DateTime.UtcNow.AddMinutes(-10), - EventDate = DateTime.UtcNow.AddDays(1), - Location = location, - Reason = "Demande de devis", - Status = QueryStatus.InProgress, - Description = "Demande en attente de devis", - }; - db.RdvQueries.Add(query); - db.SaveChanges(); - - return query.Id; - } - - private static object NewEstimatePayload(long? commandId, string clientId, string? ownerId = null) - => new - { - CommandId = commandId, - ClientId = clientId, - OwnerId = ownerId, - CommandType = BillingCodes.Rdv, - Title = "Devis prestation", - Description = "Devis détaillé", - AttachedFiles = Array.Empty(), - AttachedGraphics = Array.Empty(), - Bill = new[] - { - new { Name = "Prestation", Description = "Prestation de base", Count = 1, UnitaryCost = 120m, Currency = "EUR" }, - new { Name = "Remise", Description = "Remise fidélité", Count = 1, UnitaryCost = -20m, Currency = "EUR" }, - }, - }; - - [Fact] - public async Task PostEstimate_creates_the_estimate_and_validates_the_linked_command() - { - var commandId = SeedPendingCommand(); - using var http = NewClient("alice"); - - var response = await http.PostAsJsonAsync( - "/api/v1/estimate", - NewEstimatePayload(commandId, clientId: "bob"), - TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, $"Unexpected status {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - using var doc = JsonDocument.Parse(body); - var estimateId = doc.RootElement.GetProperty("id").GetInt64(); - Assert.True(estimateId > 0); - Assert.Equal(2, doc.RootElement.GetProperty("bill").GetArrayLength()); - - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - - var estimate = db.Estimates.Include(e => e.Bill).Single(e => e.Id == estimateId); - Assert.Equal("alice", estimate.OwnerId); - Assert.Equal("bob", estimate.ClientId); - Assert.Equal(commandId, estimate.CommandId); - Assert.Equal(BillingCodes.Rdv, estimate.CommandType); - Assert.Equal(2, estimate.Bill.Count); - Assert.Contains(estimate.Bill, line => line.UnitaryCost == -20m); - - // PostEstimate stamps the linked command as validated. - var query = db.RdvQueries.Single(q => q.Id == commandId); - Assert.NotNull(query.ValidationDate); - } - - [Fact] - public async Task PostEstimate_without_command_creates_a_standalone_estimate() - { - _fixture.ResetAndSeedActivityGraph(); - using var http = NewClient("alice"); - - var response = await http.PostAsJsonAsync( - "/api/v1/estimate", - NewEstimatePayload(commandId: null, clientId: "bob"), - TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, $"Unexpected status {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - using var doc = JsonDocument.Parse(body); - var estimateId = doc.RootElement.GetProperty("id").GetInt64(); - - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - var estimate = db.Estimates.Single(e => e.Id == estimateId); - Assert.Null(estimate.CommandId); - Assert.Equal("alice", estimate.OwnerId); - } - - [Fact] - public async Task PostEstimate_for_another_owner_is_rejected() - { - _fixture.ResetAndSeedActivityGraph(); - using var http = NewClient("alice"); - - var response = await http.PostAsJsonAsync( - "/api/v1/estimate", - NewEstimatePayload(commandId: null, clientId: "bob", ownerId: "bob"), - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - Assert.Empty(db.Estimates); - } - - [Fact] - public async Task PostEstimate_with_an_unknown_command_id_is_rejected() - { - _fixture.ResetAndSeedActivityGraph(); - using var http = NewClient("alice"); - - var response = await http.PostAsJsonAsync( - "/api/v1/estimate", - NewEstimatePayload(commandId: 999999, clientId: "bob"), - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - Assert.Empty(db.Estimates); - } - - [Fact] - public async Task PostEstimate_without_token_is_unauthorized() - { - _fixture.ResetAndSeedActivityGraph(); - - var handler = new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (_, _, _, _) => true - }; - using var http = new HttpClient(handler) { BaseAddress = new Uri(_fixture.BaseAddress) }; - - var response = await http.PostAsJsonAsync( - "/api/v1/estimate", - NewEstimatePayload(commandId: null, clientId: "bob"), - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - } -} diff --git a/src/Yavsc.Api.Test/Fixtures/ApiWebServerFixture.cs b/src/Yavsc.Api.Test/Fixtures/ApiWebServerFixture.cs deleted file mode 100644 index 51e939ceb..000000000 --- a/src/Yavsc.Api.Test/Fixtures/ApiWebServerFixture.cs +++ /dev/null @@ -1,494 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; -using Npgsql; -using Yavsc.Controllers; -using Yavsc.Interfaces.Workflow; -using Yavsc.Models; -using Yavsc.Models.Billing; -using Yavsc.Models.Google.Messaging; -using Yavsc.Models.Haircut; -using Yavsc.Models.Messaging; -using Yavsc.Models.Relationship; -using Yavsc.Models.Workflow; -using Yavsc.Services; -using Yavsc.Tests.Shared; - -namespace Yavsc.Api.Test.Fixtures; - -public sealed class ApiWebServerFixture : WebHostFixture -{ - private const string DbProviderEnvVar = "YAVSC_API_TEST_DB_PROVIDER"; - private const string NpgsqlAdminConnectionEnvVar = "YAVSC_API_TEST_NPGSQL_ADMIN_CONNECTION"; - private const string DedicatedNpgsqlDatabaseName = "yavscTestDb"; - private const string DefaultDevelopmentConnectionString = "Server=localhost;Port=5432;Database=yavscdev;Username=yavscdev;Password=8*5idas;Include Error Detail=true"; - - protected override int HttpsPort => 5104; - - private static SqliteConnection? _sharedSqliteConnection; - private static readonly object _sqliteLock = new(); - private static readonly object _npgsqlLock = new(); - private static string? _sharedNpgsqlConnectionString; - - protected override WebApplication BuildApp(WebApplicationBuilder builder) - { - if (UseNpgsqlProvider()) - { - var npgsqlConnectionString = EnsureNpgsqlDatabaseCreated(); - builder.Services.AddDbContext(opt => - opt.UseNpgsql(npgsqlConnectionString, - x => x.MigrationsAssembly("Yavsc.Org"))); - } - else - { - SqliteConnection sharedConnection; - lock (_sqliteLock) - { - if (_sharedSqliteConnection is null) - { - _sharedSqliteConnection = new SqliteConnection( - "Data Source=YavscApiTests;Mode=Memory;Cache=Shared"); - _sharedSqliteConnection.Open(); - } - sharedConnection = _sharedSqliteConnection; - } - - builder.Services.AddDbContext(opt => - opt.UseSqlite(sharedConnection)); - } - - builder.Services.AddControllers() - .AddApplicationPart(typeof(ActivityApiController).Assembly); - - builder.Services.AddLocalization(); - builder.Services.Configure(_ => { }); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddAuthorization(); - - builder.Services.AddAuthentication("Bearer") - .AddJwtBearer("Bearer", options => - { - options.IncludeErrorDetails = true; - options.MapInboundClaims = false; - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuer = true, - ValidIssuer = TestTokenIssuer.Issuer, - ValidateAudience = false, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - IssuerSigningKey = TestTokenIssuer.SigningKey, - NameClaimType = "sub", - RoleClaimType = Yavsc.Constants.RoleClaimType, - }; - }); - - return builder.Build(); - } - - protected override async Task ConfigurePipelineAsync(WebApplication app) - { - app.UseDeveloperExceptionPage(); - app.UseRouting(); - app.UseAuthentication(); - app.UseAuthorization(); - app.MapControllers(); - - using (var scope = app.Services.CreateScope()) - { - var db = scope.ServiceProvider.GetRequiredService(); - if (UseNpgsqlProvider()) - { - // Apply the EF Core migrations (Yavsc.Org assembly) so the - // test database schema matches the production provider. - // EnsureCreated must not be used here: it would create the - // schema without the migrations history and break Migrate(). - db.Database.Migrate(); - } - else - { - db.Database.EnsureCreated(); - } - } - - await Task.CompletedTask; - return app; - } - - public string BaseAddress => Addresses.First(a => a.StartsWith("https://", StringComparison.Ordinal)); - - public static bool UseNpgsqlProvider() - => string.Equals( - Environment.GetEnvironmentVariable(DbProviderEnvVar), - "npgsql", - StringComparison.OrdinalIgnoreCase); - - private static string EnsureNpgsqlDatabaseCreated() - { - lock (_npgsqlLock) - { - if (!string.IsNullOrWhiteSpace(_sharedNpgsqlConnectionString)) - { - return _sharedNpgsqlConnectionString; - } - - var adminConnectionString = BuildAdminConnectionString(); - var databaseName = DedicatedNpgsqlDatabaseName; - - using (var adminConnection = new NpgsqlConnection(adminConnectionString)) - { - adminConnection.Open(); - using var existsCommand = adminConnection.CreateCommand(); - existsCommand.CommandText = "SELECT 1 FROM pg_database WHERE datname = @databaseName"; - existsCommand.Parameters.AddWithValue("databaseName", databaseName); - - if (existsCommand.ExecuteScalar() is null) - { - using var createCommand = adminConnection.CreateCommand(); - createCommand.CommandText = $"CREATE DATABASE \"{databaseName}\""; - createCommand.ExecuteNonQuery(); - } - } - - var testConnectionBuilder = new NpgsqlConnectionStringBuilder(adminConnectionString) - { - Database = databaseName, - Pooling = false, - IncludeErrorDetail = true - }; - - _sharedNpgsqlConnectionString = testConnectionBuilder.ToString(); - return _sharedNpgsqlConnectionString; - } - } - - private static string BuildAdminConnectionString() - { - var configured = Environment.GetEnvironmentVariable(NpgsqlAdminConnectionEnvVar); - var source = string.IsNullOrWhiteSpace(configured) - ? DefaultDevelopmentConnectionString - : configured; - - var builder = new NpgsqlConnectionStringBuilder(source) - { - Pooling = false, - IncludeErrorDetail = true - }; - - if (string.IsNullOrWhiteSpace(configured)) - { - builder.Database = "postgres"; - } - else if (string.IsNullOrWhiteSpace(builder.Database)) - { - builder.Database = "postgres"; - } - - return builder.ToString(); - } - - private sealed class NoopMessageSender : IYavscMessageSender - { - public Task NotifyBookQueryAsync(IEnumerable connectionIds, RdvQueryEvent ev) - => Task.FromResult(new MessageWithPayloadResponse()); - - public Task NotifyEstimateAsync(IEnumerable connectionIds, EstimationEvent ev) - => Task.FromResult(new MessageWithPayloadResponse()); - - public Task NotifyHairCutQueryAsync(IEnumerable connectionIds, HairCutQueryEvent ev) - => Task.FromResult(new MessageWithPayloadResponse()); - - public Task NotifyAsync(IEnumerable connectionIds, IEvent yaev) - => Task.FromResult(new MessageWithPayloadResponse()); - } - - public void ResetAndSeedActivityGraph() - { - using var scope = Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - - ResetDatabase(db); - db.Database.EnsureCreated(); - - var user = new ApplicationUser - { - Id = "alice", - UserName = "alice", - Email = "alice@example.test", - EmailConfirmed = true, - FullName = "Alice", - }; - db.Users.Add(user); - - var location = new Location - { - Address = "1 rue du Test", - Latitude = 48.8566, - Longitude = 2.3522, - }; - db.Add(location); - db.SaveChanges(); - - var activity = new Activity - { - Code = "dev", - Name = "Dev", - Hidden = false, - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - }; - db.Activities.Add(activity); - - db.Activities.Add(new Activity - { - Code = "ghost", - Name = "Ghost", - Hidden = false, - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - }); - - var performer = new PerformerProfile - { - PerformerId = "alice", - SIREN = "123456789", - OrganizationAddressId = location.Id, - AcceptNotifications = true, - AcceptPublicContact = true, - Active = true, - Rate = 5, - WebSite = "https://alice.dev", - }; - db.Performers.Add(performer); - - db.UserActivities.Add(new UserActivity - { - UserId = "alice", - DoesCode = "dev", - Weight = 100, - }); - - db.Users.Add(new ApplicationUser - { - Id = "bob", - UserName = "bob", - Email = "bob@example.test", - EmailConfirmed = true, - FullName = "Bob", - }); - - db.Activities.Add(new Activity - { - Code = "declared-only", - Name = "Declared Only", - Hidden = false, - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - }); - - db.Performers.Add(new PerformerProfile - { - PerformerId = "bob", - SIREN = "987654321", - OrganizationAddressId = location.Id, - AcceptNotifications = false, - AcceptPublicContact = false, - Active = false, - Rate = 0, - WebSite = "", - }); - - db.UserActivities.Add(new UserActivity - { - UserId = "bob", - DoesCode = "declared-only", - Weight = 10, - }); - - db.SaveChanges(); - } - - private static void ResetDatabase(ApplicationDbContext db) - { - if (UseNpgsqlProvider()) - { - // Purge only the tables of the test graph, children before - // parents, so no DELETE violates a foreign key. Estimate and - // CommandLine reference NominativeServiceCommand (CommandId) and - // must be deleted before the Rdv/HairCut/HairMultiCut queries. - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - db.Set().RemoveRange(db.Set()); - - db.SaveChanges(); - return; - } - - db.Database.EnsureDeleted(); - } - - public void ResetAndSeedRdvQueryGraph() - { - ResetAndSeedActivityGraph(); - - using var scope = Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - - var location = db.Locations.Single(l => l.Address == "1 rue du Test"); - - db.RdvQueries.Add(new RdvQuery - { - ActivityCode = "dev", - ClientId = "alice", - PerformerId = "alice", - Consent = true, - UserCreated = "alice", - UserModified = "alice", - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - EventDate = DateTime.UtcNow.AddDays(1), - Location = location, - Reason = "Initial rendez-vous", - Status = Yavsc.QueryStatus.Inserted, - }); - - db.SaveChanges(); - } - - public void ResetAndSeedHaircutGraph() - { - ResetAndSeedActivityGraph(); - - using var scope = Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - - var location = db.Locations.Single(l => l.Address == "1 rue du Test"); - - if (!db.Activities.Any(a => a.Code == "brush")) - { - db.Activities.Add(new Activity - { - Code = "brush", - Name = "Brush", - Hidden = false, - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - }); - } - - if (!db.Activities.Any(a => a.Code == "mbrush")) - { - db.Activities.Add(new Activity - { - Code = "mbrush", - Name = "MBrush", - Hidden = false, - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - }); - } - - if (!db.BrusherProfile.Any(p => p.UserId == "alice")) - { - db.BrusherProfile.Add(new BrusherProfile - { - UserId = "alice", - ActionDistance = 25, - WomenLongCutPrice = 50m, - WomenHalfCutPrice = 40m, - WomenShortCutPrice = 30m, - ManCutPrice = 20m, - KidCutPrice = 15m, - ShampooPrice = 5m, - }); - } - - var prestation1 = new HairPrestation - { - Gender = HairCutGenders.Women, - Length = HairLength.HalfLong, - Cut = true, - Shampoo = true, - Dressing = HairDressings.Brushing, - Tech = HairTechnos.NoTech, - Cares = false, - Taints = new List(), - }; - var prestation2 = new HairPrestation - { - Gender = HairCutGenders.Man, - Length = HairLength.Short, - Cut = true, - Shampoo = false, - Dressing = HairDressings.Brushing, - Tech = HairTechnos.NoTech, - Cares = false, - Taints = new List(), - }; - - db.HairPrestation.AddRange(prestation1, prestation2); - db.SaveChanges(); - - db.HairCutQueries.Add(new HairCutQuery - { - ActivityCode = "brush", - ClientId = "alice", - PerformerId = "alice", - Consent = true, - UserCreated = "alice", - UserModified = "alice", - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - EventDate = DateTime.UtcNow.AddDays(3), - Location = location, - PrestationId = prestation1.Id, - Prestation = prestation1, - AdditionalInfo = "Coupe test", - Status = Yavsc.QueryStatus.Inserted, - Description = "Haircut seed", - }); - - db.HairMultiCutQueries.Add(new HairMultiCutQuery - { - ActivityCode = "mbrush", - ClientId = "alice", - PerformerId = "alice", - Consent = true, - UserCreated = "alice", - UserModified = "alice", - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - EventDate = DateTime.UtcNow.AddDays(4), - Location = location, - Prestations = new List - { - new() { PrestationId = prestation1.Id, Prestation = prestation1 }, - new() { PrestationId = prestation2.Id, Prestation = prestation2 }, - }, - Status = Yavsc.QueryStatus.Inserted, - }); - - db.SaveChanges(); - } - - public override void Dispose() - { - // Keep the shared in-memory SQLite connection alive for the - // whole test process. Closing it from one fixture instance can - // drop the schema while other tests are still running. - base.Dispose(); - } -} diff --git a/src/Yavsc.Api.Test/FrontOfficeApiControllerTests.cs b/src/Yavsc.Api.Test/FrontOfficeApiControllerTests.cs deleted file mode 100644 index 97ed2e61c..000000000 --- a/src/Yavsc.Api.Test/FrontOfficeApiControllerTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Net; -using System.Net.Http.Headers; -using Microsoft.Extensions.DependencyInjection; -using Yavsc.Api.Test.Fixtures; -using Yavsc.Helpers; -using Yavsc.Models; -using Yavsc.Tests.Shared; - -namespace Yavsc.Api.Test; - -[Collection("Yavsc Api")] -public sealed class FrontOfficeApiControllerTests : IClassFixture -{ - private readonly ApiWebServerFixture _fixture; - - public FrontOfficeApiControllerTests(ApiWebServerFixture fixture) - { - _fixture = fixture; - } - - private HttpClient NewClient(string subject = "alice", string scope = "api") - { - var handler = new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (_, _, _, _) => true - }; - - var http = new HttpClient(handler) - { - BaseAddress = new Uri(_fixture.BaseAddress) - }; - http.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, scope)); - return http; - } - - [Fact] - public async Task Front_accept_query_updates_status_without_server_error() - { - WorkflowHelpers.ConfigureBillingService(); - _fixture.ResetAndSeedRdvQueryGraph(); - - long queryId; - using (var scope = _fixture.Services.CreateScope()) - { - var db = scope.ServiceProvider.GetRequiredService(); - queryId = db.RdvQueries.Select(q => q.Id).Single(); - } - - using var http = NewClient(); - var response = await http.PostAsync($"/api/v1/front/query/accept?billingCode=Rdv&queryId={queryId}", content: null, TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, $"Unexpected status {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - using var assertScope = _fixture.Services.CreateScope(); - var assertDb = assertScope.ServiceProvider.GetRequiredService(); - var updated = assertDb.RdvQueries.Single(q => q.Id == queryId); - - Assert.Equal(QueryStatus.Accepted, updated.Status); - } -} diff --git a/src/Yavsc.Api.Test/HairCutQueryApiControllerTests.cs b/src/Yavsc.Api.Test/HairCutQueryApiControllerTests.cs deleted file mode 100644 index b4b7e3e42..000000000 --- a/src/Yavsc.Api.Test/HairCutQueryApiControllerTests.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Net; -using System.Net.Http.Headers; -using System.Net.Http.Json; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.EntityFrameworkCore; -using Yavsc.Api.Test.Fixtures; -using Yavsc.Models; -using Yavsc.Models.Haircut; -using Yavsc.Models.Relationship; -using Yavsc.Tests.Shared; - -namespace Yavsc.Api.Test; - -[Collection("Yavsc Api")] -public sealed class HairCutQueryApiControllerTests : IClassFixture -{ - private readonly ApiWebServerFixture _fixture; - - public HairCutQueryApiControllerTests(ApiWebServerFixture fixture) - { - _fixture = fixture; - } - - private HttpClient NewClient(string subject = "alice", string scope = "api") - { - var handler = new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (_, _, _, _) => true - }; - - var http = new HttpClient(handler) - { - BaseAddress = new Uri(_fixture.BaseAddress) - }; - http.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, scope)); - return http; - } - - [Fact] - public async Task Billing_brush_route_supports_crud() - { - _fixture.ResetAndSeedHaircutGraph(); - using var http = NewClient(); - - var prestationId = await GetPrestationIdAsync(); - - var createPayload = new HairCutQuery - { - ActivityCode = "brush", - PerformerId = "alice", - Consent = true, - EventDate = DateTime.UtcNow.AddDays(5), - Location = new Location - { - Address = "2 rue de la Coupe", - Latitude = 48.8570, - Longitude = 2.3525, - }, - PrestationId = prestationId, - AdditionalInfo = "Brushing test", - Status = QueryStatus.Inserted, - Description = "Haircut create", - }; - - var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Brush", createPayload, TestContext.Current.CancellationToken); - if (createResponse.StatusCode != HttpStatusCode.Created) - { - var body = await createResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - Assert.Fail($"Unexpected status {createResponse.StatusCode}: {body}"); - } - - var created = await createResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); - Assert.NotNull(created); - Assert.NotEqual(0, created!.Id); - Assert.Equal("alice", created.ClientId); - - var getResponse = await http.GetAsync($"/api/v1/billing/Brush/{created.Id}", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode); - - var fetched = await getResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); - Assert.NotNull(fetched); - Assert.Equal(created.Id, fetched!.Id); - Assert.Equal("Brushing test", fetched.AdditionalInfo); - - fetched.AdditionalInfo = "Brushing modifié"; - var putResponse = await http.PutAsJsonAsync($"/api/v1/billing/Brush/{fetched.Id}", fetched, TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.NoContent, putResponse.StatusCode); - - var deleteResponse = await http.DeleteAsync($"/api/v1/billing/Brush/{fetched.Id}", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode); - - var missingResponse = await http.GetAsync($"/api/v1/billing/Brush/{fetched.Id}", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.NotFound, missingResponse.StatusCode); - } - - [Fact] - public async Task Billing_brush_route_exposes_prestation_catalog() - { - _fixture.ResetAndSeedHaircutGraph(); - using var http = NewClient(); - - var response = await http.GetAsync("/api/v1/billing/Brush/prestations", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var catalog = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(catalog); - Assert.NotEmpty(catalog!); - Assert.All(catalog!, item => Assert.False(string.IsNullOrWhiteSpace(item.Title))); - Assert.Contains(catalog!, item => item.Title == "Femme · Cheveux mi-longs" - && item.Details == "Coupe · Brushing · Aucune technique spécifique · Shampoing · Sans soins"); - } - - private async Task GetPrestationIdAsync() - { - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - return await db.HairPrestation.Select(p => p.Id).FirstAsync(TestContext.Current.CancellationToken); - } -} \ No newline at end of file diff --git a/src/Yavsc.Api.Test/HairMultiCutQueryApiControllerTests.cs b/src/Yavsc.Api.Test/HairMultiCutQueryApiControllerTests.cs deleted file mode 100644 index 8d940b8b1..000000000 --- a/src/Yavsc.Api.Test/HairMultiCutQueryApiControllerTests.cs +++ /dev/null @@ -1,123 +0,0 @@ -using System.Net; -using System.Net.Http.Headers; -using System.Net.Http.Json; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Yavsc.Api.Test.Fixtures; -using Yavsc.Models; -using Yavsc.Models.Haircut; -using Yavsc.Models.Relationship; -using Yavsc.Tests.Shared; - -namespace Yavsc.Api.Test; - -[Collection("Yavsc Api")] -public sealed class HairMultiCutQueryApiControllerTests : IClassFixture -{ - private readonly ApiWebServerFixture _fixture; - - public HairMultiCutQueryApiControllerTests(ApiWebServerFixture fixture) - { - _fixture = fixture; - } - - private HttpClient NewClient(string subject = "alice", string scope = "api") - { - var handler = new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (_, _, _, _) => true - }; - - var http = new HttpClient(handler) - { - BaseAddress = new Uri(_fixture.BaseAddress) - }; - http.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, scope)); - return http; - } - - [Fact] - public async Task Billing_mbrush_route_supports_crud() - { - _fixture.ResetAndSeedHaircutGraph(); - using var http = NewClient(); - - var prestationIds = await GetPrestationIdsAsync(); - - var createPayload = new HairMultiCutQuery - { - ActivityCode = "mbrush", - PerformerId = "alice", - Consent = true, - EventDate = DateTime.UtcNow.AddDays(6), - Location = new Location - { - Address = "3 rue du Groupe", - Latitude = 48.8580, - Longitude = 2.3530, - }, - Prestations = prestationIds.Select(id => new HairPrestationCollectionItem { PrestationId = id }).ToList(), - Status = QueryStatus.Inserted, - }; - - var createResponse = await http.PostAsJsonAsync("/api/v1/billing/MBrush", createPayload, TestContext.Current.CancellationToken); - if (createResponse.StatusCode != HttpStatusCode.Created) - { - var body = await createResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - Assert.Fail($"Unexpected status {createResponse.StatusCode}: {body}"); - } - - var created = await createResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); - Assert.NotNull(created); - Assert.NotEqual(0, created!.Id); - Assert.Equal("alice", created.ClientId); - Assert.Equal(2, created.Prestations.Count); - - var getResponse = await http.GetAsync($"/api/v1/billing/MBrush/{created.Id}", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode); - - var fetched = await getResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); - Assert.NotNull(fetched); - Assert.Equal(created.Id, fetched!.Id); - Assert.Equal(2, fetched.Prestations.Count); - - fetched.Status = QueryStatus.Accepted; - var putResponse = await http.PutAsJsonAsync($"/api/v1/billing/MBrush/{fetched.Id}", fetched, TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.NoContent, putResponse.StatusCode); - - var deleteResponse = await http.DeleteAsync($"/api/v1/billing/MBrush/{fetched.Id}", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode); - - var missingResponse = await http.GetAsync($"/api/v1/billing/MBrush/{fetched.Id}", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.NotFound, missingResponse.StatusCode); - } - - [Fact] - public async Task Billing_mbrush_route_exposes_prestation_catalog() - { - _fixture.ResetAndSeedHaircutGraph(); - using var http = NewClient(); - - var response = await http.GetAsync("/api/v1/billing/MBrush/prestations", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var catalog = await response.Content.ReadFromJsonAsync>(TestContext.Current.CancellationToken); - Assert.NotNull(catalog); - Assert.NotEmpty(catalog!); - Assert.All(catalog!, item => Assert.False(string.IsNullOrWhiteSpace(item.Details))); - Assert.Contains(catalog!, item => item.Title == "Femme · Cheveux mi-longs" - && item.Details == "Coupe · Brushing · Aucune technique spécifique · Shampoing · Sans soins"); - } - - private async Task> GetPrestationIdsAsync() - { - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - return await db.HairPrestation - .OrderBy(p => p.Id) - .Select(p => p.Id) - .Take(2) - .ToListAsync(TestContext.Current.CancellationToken); - } -} \ No newline at end of file diff --git a/src/Yavsc.Api.Test/HomeControllerTests.cs b/src/Yavsc.Api.Test/HomeControllerTests.cs deleted file mode 100644 index a5706dc5f..000000000 --- a/src/Yavsc.Api.Test/HomeControllerTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.FileProviders; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Yavsc.Api.Test.Fixtures; -using Yavsc.Controllers; -using Yavsc.Models.Workflow; - -namespace Yavsc.Api.Test; - -[Collection("Yavsc Api")] -public sealed class HomeControllerTests : IClassFixture -{ - private readonly ApiWebServerFixture _fixture; - - public HomeControllerTests(ApiWebServerFixture fixture) - { - _fixture = fixture; - } - - [Fact] - public async Task Index_does_not_list_activity_without_declaration() - { - _fixture.ResetAndSeedActivityGraph(); - - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - - var controller = new HomeController( - NullLogger.Instance, - localizer: null!, - context: db, - settingsOptions: Options.Create(new SiteSettings()), - env: new TestEnvironment()); - - controller.ControllerContext = new ControllerContext - { - HttpContext = new DefaultHttpContext - { - User = new ClaimsPrincipal(new ClaimsIdentity(new[] - { - new Claim("sub", "alice"), - new Claim(ClaimTypes.NameIdentifier, "alice") - }, "Bearer")) - } - }; - - var result = await controller.Index(id: null); - var view = Assert.IsType(result); - var model = Assert.IsAssignableFrom>(view.Model); - - Assert.Contains(model, a => a.Code == "dev"); - Assert.DoesNotContain(model, a => a.Code == "ghost"); - } - - private sealed class TestEnvironment : IWebHostEnvironment - { - public string ApplicationName { get; set; } = "Yavsc.Api.Test"; - public IFileProvider WebRootFileProvider { get; set; } = null!; - public string WebRootPath { get; set; } = string.Empty; - public string EnvironmentName { get; set; } = "Development"; - public string ContentRootPath { get; set; } = string.Empty; - public IFileProvider ContentRootFileProvider { get; set; } = null!; - } -} diff --git a/src/Yavsc.Api.Test/RdvQueryApiControllerTests.cs b/src/Yavsc.Api.Test/RdvQueryApiControllerTests.cs deleted file mode 100644 index c3e4aad2b..000000000 --- a/src/Yavsc.Api.Test/RdvQueryApiControllerTests.cs +++ /dev/null @@ -1,229 +0,0 @@ -using System.Net; -using System.Net.Http.Headers; -using System.Net.Http.Json; -using Yavsc; -using Yavsc.Api.Test.Fixtures; -using Yavsc.Models.Workflow; -using Yavsc.Tests.Shared; - -namespace Yavsc.Api.Test; - -[Collection("Yavsc Api")] -public sealed class RdvQueryApiControllerTests : IClassFixture -{ - private readonly ApiWebServerFixture _fixture; - - public RdvQueryApiControllerTests(ApiWebServerFixture fixture) - { - _fixture = fixture; - } - - private HttpClient NewClient(string subject = "alice", string scope = "api") - { - var handler = new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (_, _, _, _) => true - }; - - var http = new HttpClient(handler) - { - BaseAddress = new Uri(_fixture.BaseAddress) - }; - http.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, scope)); - return http; - } - - [Fact] - public async Task Billing_rdv_route_supports_crud() - { - _fixture.ResetAndSeedRdvQueryGraph(); - using var http = NewClient(); - - var createPayload = new RdvQuery - { - ActivityCode = "dev", - PerformerId = "alice", - Consent = true, - EventDate = DateTime.UtcNow.AddDays(2), - Location = new Yavsc.Models.Relationship.Location - { - Address = "1 rue du Test", - Latitude = 48.8566, - Longitude = 2.3522, - }, - Reason = "Second rendez-vous", - Status = QueryStatus.Inserted, - }; - - var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Rdv", createPayload, TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.Created, createResponse.StatusCode); - - var created = await createResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); - Assert.NotNull(created); - Assert.NotEqual(0, created!.Id); - Assert.Equal("alice", created.ClientId); - - var getResponse = await http.GetAsync($"/api/v1/billing/Rdv/{created.Id}", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode); - - var fetched = await getResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); - Assert.NotNull(fetched); - Assert.Equal(created.Id, fetched!.Id); - Assert.Equal("Second rendez-vous", fetched.Reason); - - fetched.Reason = "Rendez-vous modifié"; - var putResponse = await http.PutAsJsonAsync($"/api/v1/billing/Rdv/{fetched.Id}", fetched, TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.NoContent, putResponse.StatusCode); - - var deleteResponse = await http.DeleteAsync($"/api/v1/billing/Rdv/{fetched.Id}", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode); - - var missingResponse = await http.GetAsync($"/api/v1/billing/Rdv/{fetched.Id}", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.NotFound, missingResponse.StatusCode); - } - - [Fact] - public async Task PostQuery_ignores_client_field_and_uses_authenticated_user() - { - _fixture.ResetAndSeedRdvQueryGraph(); - using var http = NewClient(subject: "alice"); - - var createPayload = new - { - ActivityCode = "dev", - PerformerId = "alice", - Consent = true, - EventDate = DateTime.UtcNow.AddDays(2), - Location = new - { - Address = "2 rue du Test", - Latitude = 48.8567, - Longitude = 2.3523, - }, - Reason = "Rendez-vous sans champ client", - Status = QueryStatus.Inserted, - }; - - var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Rdv", createPayload, TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.Created, createResponse.StatusCode); - - var created = await createResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); - Assert.NotNull(created); - Assert.Equal("alice", created!.ClientId); - } - - [Fact] - public async Task PostQuery_accepts_local_datetime_and_persists_as_utc() - { - _fixture.ResetAndSeedRdvQueryGraph(); - using var http = NewClient(subject: "alice"); - - var localEventDate = DateTime.Now.AddDays(2); - var createPayload = new - { - ActivityCode = "dev", - PerformerId = "alice", - Consent = true, - EventDate = localEventDate, - Location = new - { - Address = "3 rue du Test", - Latitude = 48.8568, - Longitude = 2.3524, - }, - Reason = "Rendez-vous date locale", - Status = QueryStatus.Inserted, - }; - - var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Rdv", createPayload, TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.Created, createResponse.StatusCode); - - var created = await createResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); - Assert.NotNull(created); - Assert.Equal(DateTimeKind.Utc, created!.EventDate.Kind); - } - - [Fact] - public async Task PostQuery_with_unknown_location_id_creates_location_and_succeeds() - { - _fixture.ResetAndSeedRdvQueryGraph(); - using var http = NewClient(subject: "alice"); - - var createPayload = new - { - ActivityCode = "dev", - PerformerId = "alice", - Consent = true, - EventDate = DateTime.UtcNow.AddDays(3), - Location = new - { - Id = 999999L, - Address = "4 rue du Test", - Latitude = 48.8569, - Longitude = 2.3525, - }, - Reason = "Rendez-vous id location inconnu", - Status = QueryStatus.Inserted, - }; - - var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Rdv", createPayload, TestContext.Current.CancellationToken); - var body = await createResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(createResponse.StatusCode == HttpStatusCode.Created, $"Unexpected status {(int)createResponse.StatusCode} ({createResponse.StatusCode}): {body}"); - - var created = await createResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); - Assert.NotNull(created); - Assert.NotNull(created!.Location); - Assert.True(created.Location.Id > 0); - Assert.NotEqual(999999L, created.Location.Id); - Assert.Equal("alice", created.ClientId); - } - - [Fact] - public async Task PostQuery_without_location_returns_bad_request() - { - _fixture.ResetAndSeedRdvQueryGraph(); - using var http = NewClient(subject: "alice"); - - var createPayload = new - { - ActivityCode = "dev", - PerformerId = "alice", - Consent = true, - EventDate = DateTime.UtcNow.AddDays(1), - Reason = "Rendez-vous sans location", - Status = QueryStatus.Inserted, - }; - - var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Rdv", createPayload, TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.BadRequest, createResponse.StatusCode); - } - - [Fact] - public async Task PostQuery_with_unknown_location_id_and_missing_address_returns_bad_request() - { - _fixture.ResetAndSeedRdvQueryGraph(); - using var http = NewClient(subject: "alice"); - - var createPayload = new - { - ActivityCode = "dev", - PerformerId = "alice", - Consent = true, - EventDate = DateTime.UtcNow.AddDays(1), - Location = new - { - Id = 777777L, - Address = "", - Latitude = 0.0, - Longitude = 0.0, - }, - Reason = "Rendez-vous location invalide", - Status = QueryStatus.Inserted, - }; - - var createResponse = await http.PostAsJsonAsync("/api/v1/billing/Rdv", createPayload, TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.BadRequest, createResponse.StatusCode); - } -} diff --git a/src/Yavsc.Api.Test/Yavsc.Api.Test.csproj b/src/Yavsc.Api.Test/Yavsc.Api.Test.csproj deleted file mode 100644 index 760f5ce84..000000000 --- a/src/Yavsc.Api.Test/Yavsc.Api.Test.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - net10.0 - enable - enable - false - Yavsc.Api.Test - true - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs b/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs index aef393868..d2da2ea71 100644 --- a/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs +++ b/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs @@ -1,7 +1,11 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Yavsc.Abstract.Workflow; using Yavsc.Server.Helpers; using Yavsc.Models; using Yavsc.Models.Workflow; @@ -9,9 +13,8 @@ using Yavsc.Models.Workflow; namespace Yavsc.Controllers { - [Authorize] [Produces("application/json")] - [Route(Constants.APIPrefix + "/activity")] + [Route("api/activity")] public class ActivityApiController : Controller { private ApplicationDbContext _context; @@ -28,127 +31,6 @@ namespace Yavsc.Controllers return _context.Activities.Include(a=>a.Forms).Where( a => !a.Hidden ); } - [HttpGet("catalog")] - public async Task>> GetCatalog( - CancellationToken cancellationToken, - [FromQuery] string parentCode = null) - { - var activities = await _context.Activities - .AsNoTracking() - .Include(a => a.Forms) - .Include(a => a.Children) - .ThenInclude(c => c.Forms) - .Where(a => !a.Hidden && a.ParentCode == parentCode) - .OrderByDescending(a => a.Rate) - .ToListAsync(cancellationToken); - - var codes = activities - .Select(a => a.Code) - .Concat(activities.SelectMany(a => (a.Children ?? new List()) - .Where(c => !c.Hidden) - .Select(c => c.Code))) - .Where(c => !string.IsNullOrWhiteSpace(c)) - .Distinct() - .ToArray(); - - // Some providers are brittle when translating Contains over an - // empty in-memory array. If there is no candidate activity code, - // the catalog is empty by definition. - if (codes.Length == 0) - { - return Ok(new List()); - } - - var performerCounts = await ( - from ua in _context.UserActivities.AsNoTracking() - where !string.IsNullOrWhiteSpace(ua.DoesCode) && codes.Contains(ua.DoesCode) - group ua by ua.DoesCode into g - select new - { - Code = g.Key, - Count = g.Select(x => x.UserId).Distinct().Count() - }) - .ToDictionaryAsync(x => x.Code, x => x.Count, cancellationToken); - - var filteredActivities = activities - .Where(a => - (TryGetPerformerCount(performerCounts, a.Code, out var ownCount) && ownCount > 0) - || (a.Children ?? new List()) - .Where(c => !c.Hidden) - .Any(c => TryGetPerformerCount(performerCounts, c.Code, out var childCount) && childCount > 0)) - .ToList(); - - return Ok(filteredActivities.Select(a => ToBrowseItem(a, performerCounts)).ToList()); - } - - [HttpGet("{id}/users")] - public async Task>> GetUsers( - [FromRoute] string id, - CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(id)) - { - return BadRequest("Activity code is required."); - } - - var activity = await _context.Activities - .AsNoTracking() - .SingleOrDefaultAsync(a => a.Code == id, cancellationToken); - if (activity is null) - { - return NotFound(); - } - - var users = await QueryDeclaredUsersAsync(id, activity.Name, cancellationToken); - - return Ok(users); - } - - [HttpGet("{id}/performers")] - public Task>> GetPerformers( - [FromRoute] string id, - CancellationToken cancellationToken) - { - // Backward-compatible alias kept for existing clients. - return GetUsers(id, cancellationToken); - } - - private Task> QueryDeclaredUsersAsync( - string activityCode, - string activityName, - CancellationToken cancellationToken) - { - return ( - from ua in _context.UserActivities.AsNoTracking() - join u in _context.ApplicationUser.AsNoTracking() on ua.UserId equals u.Id into users - from user in users.DefaultIfEmpty() - join p in _context.Performers.AsNoTracking() on ua.UserId equals p.PerformerId into performerProfiles - from performer in performerProfiles.DefaultIfEmpty() - where ua.DoesCode == activityCode - orderby user != null ? user.UserName : ua.UserId - select new PerformerActivity - { - PerformerId = ua.UserId, - HasPerformerProfile = performer != null, - UserName = user != null ? (user.UserName ?? string.Empty) : string.Empty, - Active = performer != null && performer.Active, - AcceptNotifications = performer != null && performer.AcceptNotifications, - AcceptPublicContact = performer != null && performer.AcceptPublicContact, - WebSite = performer != null ? (performer.WebSite ?? string.Empty) : string.Empty, - ActivityCode = activityCode, - ActivityName = activityName, - SettingsClassName = _context.Activities - .Where(a => a.Code == activityCode) - .Select(a => a.SettingsClassName) - .FirstOrDefault() ?? string.Empty, - ExtraActivityCount = _context.UserActivities - .Where(x => x.UserId == ua.UserId && x.DoesCode != activityCode) - .Count() - }) - .Distinct() - .ToListAsync(cancellationToken); - } - // GET: api/ActivityApi/5 [HttpGet("{id}", Name = "GetActivity")] public async Task GetActivity([FromRoute] string id) @@ -267,66 +149,5 @@ namespace Yavsc.Controllers { return _context.Activities.Count(e => e.Code == id) > 0; } - - private static ActivityInfo ToBrowseItem( - Activity activity, - IReadOnlyDictionary performerCounts) - { - return new ActivityInfo - { - Code = activity.Code, - Name = activity.Name, - ParentCode = activity.ParentCode, - Description = activity.Description ?? string.Empty, - Photo = activity.Photo, - Rate = activity.Rate, - PerformerCount = TryGetPerformerCount(performerCounts, activity.Code, out var count) ? count : 0, - Forms = (activity.Forms ?? Enumerable.Empty()) - .Select(f => new CommandFormSummary - { - Id = f.Id, - ActionName = f.ActionName, - Title = f.Title, - }) - .ToList(), - Children = (activity.Children ?? Enumerable.Empty()) - .Where(c => !c.Hidden) - .Where(c => TryGetPerformerCount(performerCounts, c.Code, out var childCount) && childCount > 0) - .OrderByDescending(c => c.Rate) - .Select(c => new ActivityInfo - { - Code = c.Code, - Name = c.Name, - ParentCode = c.ParentCode, - Description = c.Description ?? string.Empty, - Photo = c.Photo, - Rate = c.Rate, - PerformerCount = TryGetPerformerCount(performerCounts, c.Code, out var childCount) ? childCount : 0, - Forms = (c.Forms ?? Enumerable.Empty()) - .Select(f => new CommandFormSummary - { - Id = f.Id, - ActionName = f.ActionName, - Title = f.Title, - }) - .ToList(), - }) - .ToList(), - }; - } - - private static bool TryGetPerformerCount( - IReadOnlyDictionary performerCounts, - string code, - out int count) - { - if (string.IsNullOrWhiteSpace(code)) - { - count = 0; - return false; - } - - return performerCounts.TryGetValue(code, out count); - } } } diff --git a/src/Yavsc.Api/Controllers/Business/BillingController.cs b/src/Yavsc.Api/Controllers/Business/BillingController.cs index d10e166e3..870354067 100644 --- a/src/Yavsc.Api/Controllers/Business/BillingController.cs +++ b/src/Yavsc.Api/Controllers/Business/BillingController.cs @@ -3,12 +3,9 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization; using Newtonsoft.Json; using System.Security.Claims; -using Yavsc.Billing; using Yavsc.Helpers; using Yavsc.ViewModels; using Yavsc.Models.Billing; -using Yavsc.Models.Haircut; -using Yavsc.Models.Workflow; using Yavsc.Server.Models.FileSystem; namespace Yavsc.ApiControllers @@ -22,8 +19,7 @@ namespace Yavsc.ApiControllers using Yavsc.ViewModels.Auth; using Yavsc.Server.Helpers; - [Authorize] - [Route(Constants.APIPrefix + "/bill"), Authorize] + [Route("api/bill"), Authorize] public class BillingController : Controller { readonly ApplicationDbContext dbContext; @@ -103,114 +99,6 @@ namespace Yavsc.ApiControllers return ViewComponent("Bill",new object[] { billingCode, bill, OutputFormat.Pdf, true } ); } - /// - /// Lists ongoing service commands for the authenticated performer. - /// This endpoint is tailored for the PostIt provider homepage flow - /// ("Mes demandes en cours"). - /// - [HttpGet("provider/ongoing")] - [Produces("application/json")] - public IActionResult GetProviderOngoingCommands() - { - var uid = User.GetUserId(); - if (string.IsNullOrWhiteSpace(uid)) - { - return Unauthorized(); - } - - if (billingService.BillingMap.Count == 0) - { - WorkflowHelpers.ConfigureBillingService(); - } - - var allowedActivityCodes = dbContext.UserActivities - .AsNoTracking() - .Where(a => a.UserId == uid) - .Select(a => a.DoesCode) - .Distinct() - .ToList(); - - if (allowedActivityCodes.Count == 0) - { - return Ok(Array.Empty()); - } - - var allowedBillingCodes = dbContext.CommandForm - .AsNoTracking() - .Where(form => allowedActivityCodes.Contains(form.ActivityCode)) - .Select(form => form.ActionName) - .Where(actionName => !string.IsNullOrWhiteSpace(actionName)) - .Distinct() - .ToHashSet(StringComparer.OrdinalIgnoreCase); - - var fallbackToActivityFilteringOnly = allowedBillingCodes.Count == 0; - - // Query only the command types allowed by the performer's declared - // activities; this avoids touching unrelated legacy slices. - var rdvCommands = fallbackToActivityFilteringOnly || allowedBillingCodes.Contains(BillingCodes.Rdv) - ? dbContext.Set() - .AsNoTracking() - .Where(q => q.PerformerId == uid) - .Where(q => allowedActivityCodes.Contains(q.ActivityCode)) - .Where(q => q.Status == QueryStatus.Inserted - || q.Status == QueryStatus.Accepted - || q.Status == QueryStatus.InProgress) - .Cast() - .ToList() - : new List(); - - var hairCommands = fallbackToActivityFilteringOnly || allowedBillingCodes.Contains(BillingCodes.Brush) - ? dbContext.Set() - .AsNoTracking() - .Where(q => q.PerformerId == uid) - .Where(q => allowedActivityCodes.Contains(q.ActivityCode)) - .Where(q => q.Status == QueryStatus.Inserted - || q.Status == QueryStatus.Accepted - || q.Status == QueryStatus.InProgress) - .Cast() - .ToList() - : new List(); - - var hairMultiCommands = fallbackToActivityFilteringOnly || allowedBillingCodes.Contains(BillingCodes.MBrush) - ? dbContext.Set() - .AsNoTracking() - .Where(q => q.PerformerId == uid) - .Where(q => allowedActivityCodes.Contains(q.ActivityCode)) - .Where(q => q.Status == QueryStatus.Inserted - || q.Status == QueryStatus.Accepted - || q.Status == QueryStatus.InProgress) - .Cast() - .ToList() - : new List(); - - var commands = rdvCommands - .Concat(hairCommands) - .Concat(hairMultiCommands) - .OrderByDescending(q => q.DateModified) - .ThenByDescending(q => q.Id) - .ToList(); - - var payload = commands - .Select(q => new - { - Id = q.Id, - BillingCode = ResolveBillingCode(q), - ActivityCode = q.ActivityCode, - PerformerId = q.PerformerId, - ClientId = q.ClientId, - Status = q.Status, - Description = q.Description, - EventDate = ResolveEventDate(q), - Reason = q is Models.Workflow.RdvQuery rdv ? rdv.Reason : string.Empty, - AdditionalInfo = q is Models.Haircut.HairCutQuery hc ? hc.AdditionalInfo : string.Empty, - Provisional = q.Provisional, - }) - .Where(x => !string.IsNullOrWhiteSpace(x.BillingCode)) - .ToList(); - - return Ok(payload); - } - [HttpPost("prosign/{billingCode}/{id}")] public async Task ProSign(string billingCode, long id) @@ -244,23 +132,6 @@ namespace Yavsc.ApiControllers return Ok (new { ProviderValidationDate = estimate.ProviderValidationDate, GCMSent = gcmSent }); } - private string ResolveBillingCode(NominativeServiceCommand command) - { - var typeName = command.GetType().Name; - return billingService.BillingMap.TryGetValue(typeName, out var code) - ? code - : string.Empty; - } - - private static DateTime? ResolveEventDate(NominativeServiceCommand command) - => command switch - { - Models.Workflow.RdvQuery rdv => rdv.EventDate, - Models.Haircut.HairCutQuery brush => brush.EventDate, - Models.Haircut.HairMultiCutQuery mbrush => mbrush.EventDate, - _ => null, - }; - [HttpGet("prosign/{billingCode}/{id}")] public async Task GetProSign(string billingCode, long id) { @@ -282,28 +153,9 @@ namespace Yavsc.ApiControllers public async Task CliSign(string billingCode, long id) { var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); - var estimate = dbContext.Estimates - .Include(e => e.Owner) - .Include(e => e.Owner.Performer) - .Include(e => e.Client) - .FirstOrDefault(e => e.Id == id); - if (estimate is null) - { - return NotFound(); - } - - if (estimate.CommandId is null) - { - return new ChallengeResult(); - } - - var command = dbContext.Set() - .FirstOrDefault(c => c.Id == estimate.CommandId.Value); - if (command is null || command.ClientId != uid) - { - return new ChallengeResult(); - } - + var estimate = dbContext.Estimates.Include( e=>e.Query + ).Include(e=>e.Owner).Include(e=>e.Owner.Performer).Include(e=>e.Client) + .FirstOrDefault( e=> e.Id == id && e.Query.ClientId == uid ); if (!(await authorizationService.AuthorizeAsync(User, estimate, new ReadPermission())).Succeeded) { return new ChallengeResult(); diff --git a/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs b/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs new file mode 100644 index 000000000..494075c61 --- /dev/null +++ b/src/Yavsc.Api/Controllers/Business/BookQueryApiController.cs @@ -0,0 +1,197 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Yavsc.Controllers +{ + using System; + using Yavsc.Models; + using Yavsc.Models.Workflow; + using Yavsc.Models.Billing; + using Yavsc.Abstract.Identity; + using Microsoft.EntityFrameworkCore; + using Yavsc.Helpers; + using Yavsc.Server.Helpers; + + [Produces("application/json")] + [Route("api/bookquery"), Authorize("Performer")] + public class BookQueryApiController : Controller + { + private ApplicationDbContext _context; + private ILogger _logger; + + public BookQueryApiController(ApplicationDbContext context, ILoggerFactory loggerFactory) + { + _context = context; + _logger = loggerFactory.CreateLogger(); + } + + // GET: api/BookQueryApi + /// + /// Book queries, by creation order + /// + /// returned Ids must be lower than this value + /// book queries + [HttpGet] + public IEnumerable GetCommands(long maxId=long.MaxValue) + { + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); + var now = DateTime.UtcNow; + + var result = _context.RdvQueries.Include(c => c.Location). + Include(c => c.Client).Where(c => c.PerformerId == uid && c.Id < maxId && c.EventDate > now + && c.ValidationDate == null). + Select(c => new RdvQueryProviderInfo + { + Client = new ClientProviderInfo { + UserName = c.Client.UserName, + UserId = c.ClientId, + Avatar = c.Client.Avatar }, + Location = c.Location, + EventDate = c.EventDate, + Id = c.Id, + Previsional = c.Provisional, + Reason = c.Reason, + ActivityCode = c.ActivityCode, + BillingCode = BillingCodes.Rdv + }). + OrderBy(c=>c.Id). + Take(25); + return result; + } + + // GET: api/BookQueryApi/5 + [HttpGet("{id}", Name = "GetBookQuery")] + public IActionResult GetBookQuery([FromRoute] long id) + { + + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); + + RdvQuery bookQuery = _context.RdvQueries.Where(c => c.ClientId == uid || c.PerformerId == uid).Single(m => m.Id == id); + + if (bookQuery == null) + { + return NotFound(); + } + + return Ok(bookQuery); + } + + // PUT: api/BookQueryApi/5 + [HttpPut("{id}")] + public IActionResult PutBookQuery(long id, [FromBody] RdvQuery bookQuery) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + if (id != bookQuery.Id) + { + return BadRequest(); + } + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (bookQuery.ClientId != uid) + return NotFound(); + + _context.Entry(bookQuery).State = EntityState.Modified; + + try + { + _context.SaveChanges(User.GetUserId()); + } + catch (DbUpdateConcurrencyException) + { + if (!BookQueryExists(id)) + { + return NotFound(); + } + else + { + throw; + } + } + + return new StatusCodeResult(StatusCodes.Status204NoContent); + } + + // POST: api/BookQueryApi + [HttpPost] + public IActionResult PostBookQuery([FromBody] RdvQuery bookQuery) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (bookQuery.ClientId != uid) + { + ModelState.AddModelError("ClientId", "You must be the client at creating a book query"); + return new BadRequestObjectResult(ModelState); + } + _context.RdvQueries.Add(bookQuery); + try + { + _context.SaveChanges(User.GetUserId()); + } + catch (DbUpdateException) + { + if (BookQueryExists(bookQuery.Id)) + { + return new StatusCodeResult(StatusCodes.Status409Conflict); + } + else + { + throw; + } + } + + return CreatedAtRoute("GetBookQuery", new { id = bookQuery.Id }, bookQuery); + } + + // DELETE: api/BookQueryApi/5 + [HttpDelete("{id}")] + public IActionResult DeleteBookQuery(long id) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); + RdvQuery bookQuery = _context.RdvQueries.Single(m => m.Id == id); + + if (bookQuery == null) + { + return NotFound(); + } + if (bookQuery.ClientId != uid) return NotFound(); + + _context.RdvQueries.Remove(bookQuery); + _context.SaveChanges(User.GetUserId()); + + return Ok(bookQuery); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _context.Dispose(); + } + base.Dispose(disposing); + } + + private bool BookQueryExists(long id) + { + return _context.RdvQueries.Count(e => e.Id == id) > 0; + } + } +} diff --git a/src/Yavsc.Api/Controllers/Business/DictionnaireMetierController.cs b/src/Yavsc.Api/Controllers/Business/DictionnaireMetierController.cs deleted file mode 100644 index 1cb0b983e..000000000 --- a/src/Yavsc.Api/Controllers/Business/DictionnaireMetierController.cs +++ /dev/null @@ -1,181 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Yavsc.Models; -using Yavsc.Models.Workflow; -using Yavsc.Server.Helpers; -using Yavsc.Server.Services; - -namespace Yavsc.Controllers -{ - [Authorize] - [Produces("application/json")] - [Route(Constants.APIPrefix + "/dictionnaire-metier")] - public class DictionnaireMetierController : Controller - { - private readonly ApplicationDbContext _context; - private readonly DictionnaireMetierModerationService _moderationService; - - public DictionnaireMetierController(ApplicationDbContext context) - { - _context = context; - _moderationService = new DictionnaireMetierModerationService(context); - } - - [HttpGet("{activityCode}")] - public async Task>> GetTerms( - [FromRoute] string activityCode, - [FromQuery] string langue = "fr", - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(activityCode)) - { - return BadRequest("Activity code is required."); - } - - var activityCodes = await ResolveActivityCodesAsync(activityCode, cancellationToken); - if (activityCodes.Count == 0) - { - return NotFound(); - } - - var dictionaryIds = await _context.DictionnaireMetier - .AsNoTracking() - .Where(d => activityCodes.Contains(d.DomaineActiviteCode) && d.Langue == langue) - .Select(d => d.Id) - .ToListAsync(cancellationToken); - - if (dictionaryIds.Count == 0) - { - return Ok(new List()); - } - - var terms = await _context.TermeMetier - .AsNoTracking() - .Where(t => dictionaryIds.Contains(t.DictionnaireMetierId) - && t.StatutValidation == StatutValidationTerme.Valide) - .OrderBy(t => t.Mot) - .ToListAsync(cancellationToken); - - return Ok(terms); - } - - [HttpGet("dictionnaires/{activityCode}")] - public async Task>> GetDictionaries( - [FromRoute] string activityCode, - [FromQuery] string langue = "fr", - CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(activityCode)) - { - return BadRequest("Activity code is required."); - } - - var activityCodes = await ResolveActivityCodesAsync(activityCode, cancellationToken); - if (activityCodes.Count == 0) - { - return NotFound(); - } - - var dictionaries = await _context.DictionnaireMetier - .AsNoTracking() - .Where(d => activityCodes.Contains(d.DomaineActiviteCode) && d.Langue == langue) - .OrderBy(d => d.Nom) - .ToListAsync(cancellationToken); - - return Ok(dictionaries); - } - - [HttpPost("proposer")] - public async Task> ProposeTerm( - [FromBody] TermeMetier term, - CancellationToken cancellationToken) - { - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - if (string.IsNullOrWhiteSpace(term.Mot) || string.IsNullOrWhiteSpace(term.Definition)) - { - return BadRequest("Le terme et sa définition sont requis."); - } - - var dictionary = await _context.DictionnaireMetier - .SingleOrDefaultAsync(d => d.Id == term.DictionnaireMetierId, cancellationToken); - - if (dictionary is null) - { - return NotFound("Dictionary not found."); - } - - var proposerId = User.GetUserId(); - var result = await _moderationService.ProposerTermAsync( - term.DictionnaireMetierId, - term.Mot, - term.Definition, - term.Langue, - proposerId); - - return CreatedAtAction(nameof(GetTerms), new { activityCode = dictionary.DomaineActiviteCode }, result); - } - - [HttpPut("{id}/valider")] - [Authorize("AdministratorOnly")] - public async Task ValidateTerm( - [FromRoute] long id, - CancellationToken cancellationToken) - { - try - { - var term = await _moderationService.ValiderTermAsync(id, User.GetUserId()); - return Ok(term); - } - catch (KeyNotFoundException) - { - return NotFound(); - } - } - - [HttpPut("{id}/rejeter")] - [Authorize("AdministratorOnly")] - public async Task RejectTerm( - [FromRoute] long id, - CancellationToken cancellationToken) - { - try - { - var term = await _moderationService.RejeterTermAsync(id, User.GetUserId()); - return Ok(term); - } - catch (KeyNotFoundException) - { - return NotFound(); - } - } - - private async Task> ResolveActivityCodesAsync(string activityCode, CancellationToken cancellationToken) - { - var result = new HashSet(); - var currentCode = activityCode; - - while (!string.IsNullOrWhiteSpace(currentCode)) - { - result.Add(currentCode); - - var current = await _context.Activities - .AsNoTracking() - .SingleOrDefaultAsync(a => a.Code == currentCode, cancellationToken); - - if (current is null || string.IsNullOrWhiteSpace(current.ParentCode)) - { - break; - } - - currentCode = current.ParentCode; - } - - return result.ToList(); - } - } -} diff --git a/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs b/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs index 3e3e870c1..41bdd353b 100644 --- a/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs +++ b/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs @@ -1,17 +1,21 @@ +using System; +using System.Linq; using System.Security.Claims; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Models.Billing; using Yavsc.Server.Helpers; namespace Yavsc.Controllers { - [Authorize] [Produces("application/json")] - [Route(Constants.APIPrefix + "/estimate"), Authorize] + [Route("api/estimate"), Authorize] public class EstimateApiController : Controller { private readonly ApplicationDbContext _context; @@ -23,12 +27,12 @@ namespace Yavsc.Controllers } bool UserIsAdminOrThis(string uid) { - if (User.IsInRole(Constants.AdminGroupName)) return true; + if (User.IsInRole(YavscConstants.AdminGroupName)) return true; return uid == User.GetUserId(); } bool UserIsAdminOrInThese(string oid, string uid) { - if (User.IsInRole(Constants.AdminGroupName)) return true; + if (User.IsInRole(YavscConstants.AdminGroupName)) return true; var cuid = User.GetUserId(); return cuid == uid || cuid == oid; } @@ -77,8 +81,8 @@ namespace Yavsc.Controllers { return BadRequest(); } - var uid = User.GetUserId(); - if (!User.IsInRole(Constants.AdminGroupName)) + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (!User.IsInRole(YavscConstants.AdminGroupName)) { if (uid != estimate.OwnerId) { @@ -111,10 +115,10 @@ namespace Yavsc.Controllers [HttpPost, Produces("application/json")] public IActionResult PostEstimate([FromBody] Estimate estimate) { - var uid = User.GetUserId(); + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); if (estimate.OwnerId == null) estimate.OwnerId = uid; - if (!User.IsInRole(Constants.AdminGroupName)) + if (!User.IsInRole(YavscConstants.AdminGroupName)) { if (uid != estimate.OwnerId) { @@ -125,8 +129,7 @@ namespace Yavsc.Controllers if (estimate.CommandId != null) { - var query = _context.NominativeServiceCommands - .FirstOrDefault(q => q.Id == estimate.CommandId); + var query = _context.RdvQueries.FirstOrDefault(q => q.Id == estimate.CommandId); if (query == null) { return BadRequest(ModelState); @@ -183,8 +186,8 @@ namespace Yavsc.Controllers { return NotFound(); } - var uid = User.GetUserId(); - if (!User.IsInRole(Constants.AdminGroupName)) + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); + if (!User.IsInRole(YavscConstants.AdminGroupName)) { if (uid != estimate.OwnerId) { diff --git a/src/Yavsc.Api/Controllers/Business/EstimateTemplatesApiController.cs b/src/Yavsc.Api/Controllers/Business/EstimateTemplatesApiController.cs index 748f60cc6..4442e0b34 100644 --- a/src/Yavsc.Api/Controllers/Business/EstimateTemplatesApiController.cs +++ b/src/Yavsc.Api/Controllers/Business/EstimateTemplatesApiController.cs @@ -1,16 +1,15 @@ using System.Security.Claims; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Models.Billing; using Yavsc.Server.Helpers; namespace Yavsc.Controllers { - [Authorize] [Produces("application/json")] - [Route(Constants.APIPrefix + "/EstimateTemplatesApi")] + [Route("api/EstimateTemplatesApi")] public class EstimateTemplatesApiController : Controller { private ApplicationDbContext _context; @@ -63,7 +62,7 @@ namespace Yavsc.Controllers } var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); if (estimateTemplate.OwnerId!=uid) - if (!User.IsInRole(Constants.AdminGroupName)) + if (!User.IsInRole(YavscConstants.AdminGroupName)) return new StatusCodeResult(StatusCodes.Status403Forbidden); _context.Entry(estimateTemplate).State = EntityState.Modified; @@ -133,7 +132,7 @@ namespace Yavsc.Controllers } var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); if (estimateTemplate.OwnerId!=uid) - if (!User.IsInRole(Constants.AdminGroupName)) + if (!User.IsInRole(YavscConstants.AdminGroupName)) return new StatusCodeResult(StatusCodes.Status403Forbidden); _context.EstimateTemplates.Remove(estimateTemplate); diff --git a/src/Yavsc.Api/Controllers/Business/FrontOfficeApiController.cs b/src/Yavsc.Api/Controllers/Business/FrontOfficeApiController.cs index 3adac5b03..b91cba51e 100644 --- a/src/Yavsc.Api/Controllers/Business/FrontOfficeApiController.cs +++ b/src/Yavsc.Api/Controllers/Business/FrontOfficeApiController.cs @@ -1,25 +1,24 @@ -using Microsoft.AspNetCore.Authorization; +using System; +using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Services; -using Yavsc.Server.Helpers; using Yavsc.ViewModels.FrontOffice; namespace Yavsc.ApiControllers { - [Authorize] - [Route(Constants.APIPrefix + "/front")] + [Route("api/front")] public class FrontOfficeApiController : Controller { ApplicationDbContext dbContext; private IBillingService billing; - public FrontOfficeApiController(ApplicationDbContext context, IBillingService billing = null) + public FrontOfficeApiController(ApplicationDbContext context, IBillingService billing) { dbContext = context; - this.billing = billing ?? new BillingService(context); + this.billing = billing; } [HttpGet("profiles/{actCode}")] @@ -37,7 +36,7 @@ namespace Yavsc.ApiControllers if (billing == null) return BadRequest(); billing.Status = QueryStatus.Rejected; - dbContext.SaveChanges(User.GetUserId()); + dbContext.SaveChanges(); return Ok(); } @@ -49,7 +48,7 @@ namespace Yavsc.ApiControllers var billing = BillingService.GetBillable(dbContext, billingCode, queryId); if (billing == null) return BadRequest(); billing.Status = QueryStatus.Accepted; - dbContext.SaveChanges(User.GetUserId()); + dbContext.SaveChanges(); return Ok(); } } diff --git a/src/Yavsc.Api/Controllers/Business/HairCutQueryApiController.cs b/src/Yavsc.Api/Controllers/Business/HairCutQueryApiController.cs deleted file mode 100644 index 7f445fdd9..000000000 --- a/src/Yavsc.Api/Controllers/Business/HairCutQueryApiController.cs +++ /dev/null @@ -1,232 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Yavsc.Models; -using Yavsc.Models.Billing; -using Yavsc.Models.Haircut; -using Yavsc.Models.Relationship; -using Yavsc.Server.Helpers; - -namespace Yavsc.Controllers; - -[Authorize] -[Produces("application/json")] -[Route(Constants.APIPrefix + "/billing/" + BillingCodes.Brush)] -public class HairCutQueryApiController : Controller -{ - private readonly ApplicationDbContext _context; - - public HairCutQueryApiController(ApplicationDbContext context) - { - _context = context; - } - - [HttpGet] - public async Task GetQueries(CancellationToken cancellationToken) - { - var uid = User.GetUserId(); - - var queries = await _context.HairCutQueries - .AsNoTracking() - .Include(q => q.Prestation) - .Include(q => q.Location) - .Include(q => q.Client) - .Include(q => q.PerformerProfile) - .Where(q => q.ClientId == uid || q.PerformerId == uid) - .OrderByDescending(q => q.Id) - .ToListAsync(cancellationToken); - - return Ok(queries); - } - - [HttpGet("prestations")] - public async Task GetPrestations(CancellationToken cancellationToken) - { - var prestations = await _context.HairPrestation - .AsNoTracking() - .OrderBy(p => p.Gender) - .ThenBy(p => p.Length) - .ThenBy(p => p.Tech) - .Select(p => ToDto(p)) - .ToListAsync(cancellationToken); - - return Ok(prestations); - } - - [HttpGet("{id}", Name = "GetBillingHairCutQuery")] - public async Task GetQuery([FromRoute] long id, CancellationToken cancellationToken) - { - var uid = User.GetUserId(); - - var query = await _context.HairCutQueries - .Include(q => q.Prestation) - .Include(q => q.Location) - .Include(q => q.Client) - .Include(q => q.PerformerProfile) - .SingleOrDefaultAsync(q => q.Id == id, cancellationToken); - - if (query is null) - { - return NotFound(); - } - - if (query.ClientId != uid && query.PerformerId != uid && !User.IsInRole(Constants.AdminGroupName)) - { - return Forbid(); - } - - return Ok(query); - } - - [HttpPost] - public async Task PostQuery([FromBody] HairCutQuery query, CancellationToken cancellationToken) - { - var uid = User.GetUserId(); - query.ClientId = uid; - - ModelState.Remove("Client"); - ModelState.Remove("ClientId"); - ModelState.Remove("UserCreated"); - ModelState.Remove("UserModified"); - ModelState.Remove("SelectedProfile"); - ModelState.Remove("Prestation"); - ModelState.Remove("PerformerProfile"); - ModelState.Remove("Context"); - ModelState.Remove("Regularization"); - - query.Prestation = await _context.HairPrestation - .SingleOrDefaultAsync(p => p.Id == query.PrestationId, cancellationToken); - if (query.Prestation is null) - { - ModelState.AddModelError("PrestationId", "Unknown hair prestation."); - return BadRequest(ModelState); - } - - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - query.Location = await ResolveLocationAsync(query.Location, cancellationToken); - - _context.HairCutQueries.Add(query); - - try - { - await _context.SaveChangesAsync(User.GetUserId(), cancellationToken); - } - catch (DbUpdateException) - { - if (QueryExists(query.Id)) - { - return Conflict(); - } - - throw; - } - - return CreatedAtRoute("GetBillingHairCutQuery", new { id = query.Id }, query); - } - - [HttpPut("{id}")] - public async Task PutQuery([FromRoute] long id, [FromBody] HairCutQuery query, CancellationToken cancellationToken) - { - var existing = await _context.HairCutQueries - .Include(q => q.Location) - .SingleOrDefaultAsync(q => q.Id == id, cancellationToken); - - if (existing is null) - { - return NotFound(); - } - - var uid = User.GetUserId(); - if (existing.ClientId != uid && !User.IsInRole(Constants.AdminGroupName)) - { - return Forbid(); - } - - var prestation = await _context.HairPrestation - .SingleOrDefaultAsync(p => p.Id == query.PrestationId, cancellationToken); - if (prestation is null) - { - ModelState.AddModelError("PrestationId", "Unknown hair prestation."); - return BadRequest(ModelState); - } - - existing.ActivityCode = query.ActivityCode; - existing.PerformerId = query.PerformerId; - existing.Consent = query.Consent; - existing.EventDate = query.EventDate; - existing.AdditionalInfo = query.AdditionalInfo; - existing.Status = query.Status; - existing.Provisional = query.Provisional; - existing.PrestationId = prestation.Id; - existing.Prestation = prestation; - existing.Location = await ResolveLocationAsync(query.Location, cancellationToken); - - await _context.SaveChangesAsync(User.GetUserId(), cancellationToken); - return NoContent(); - } - - [HttpDelete("{id}")] - public async Task DeleteQuery([FromRoute] long id, CancellationToken cancellationToken) - { - var uid = User.GetUserId(); - - var query = await _context.HairCutQueries - .SingleOrDefaultAsync(q => q.Id == id, cancellationToken); - - if (query is null) - { - return NotFound(); - } - - if (query.ClientId != uid && !User.IsInRole(Constants.AdminGroupName)) - { - return Forbid(); - } - - _context.HairCutQueries.Remove(query); - await _context.SaveChangesAsync(User.GetUserId(), cancellationToken); - - return Ok(query); - } - - private async Task ResolveLocationAsync(Location candidate, CancellationToken cancellationToken) - { - if (candidate is null) - { - return null; - } - - var existingLocation = await _context.Locations.FirstOrDefaultAsync( - x => x.Address == candidate.Address - && x.Longitude == candidate.Longitude - && x.Latitude == candidate.Latitude, - cancellationToken); - - if (existingLocation is not null) - { - return existingLocation; - } - - _context.Attach(candidate); - return candidate; - } - - private bool QueryExists(long id) - { - return _context.HairCutQueries.Any(e => e.Id == id); - } - - private static HairPrestationDto ToDto(HairPrestation prestation) - { - return new HairPrestationDto - { - Id = prestation.Id, - Title = prestation.GetDisplayTitle(), - Details = prestation.GetDisplayDetails(), - }; - } -} \ No newline at end of file diff --git a/src/Yavsc.Api/Controllers/Business/HairMultiCutQueryApiController.cs b/src/Yavsc.Api/Controllers/Business/HairMultiCutQueryApiController.cs deleted file mode 100644 index 3b33c4160..000000000 --- a/src/Yavsc.Api/Controllers/Business/HairMultiCutQueryApiController.cs +++ /dev/null @@ -1,284 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Yavsc.Models; -using Yavsc.Models.Billing; -using Yavsc.Models.Haircut; -using Yavsc.Models.Relationship; -using Yavsc.Server.Helpers; - -namespace Yavsc.Controllers; - -[Authorize] -[Produces("application/json")] -[Route(Constants.APIPrefix + "/billing/" + BillingCodes.MBrush)] -public class HairMultiCutQueryApiController : Controller -{ - private readonly ApplicationDbContext _context; - - public HairMultiCutQueryApiController(ApplicationDbContext context) - { - _context = context; - } - - [HttpGet] - public async Task GetQueries(CancellationToken cancellationToken) - { - var uid = User.GetUserId(); - - var queries = await _context.HairMultiCutQueries - .AsNoTracking() - .Include(q => q.Prestations) - .ThenInclude(p => p.Prestation) - .Include(q => q.Location) - .Include(q => q.Client) - .Include(q => q.PerformerProfile) - .Where(q => q.ClientId == uid || q.PerformerId == uid) - .OrderByDescending(q => q.Id) - .ToListAsync(cancellationToken); - - return Ok(queries.Select(SanitizeForResponse).ToList()); - } - - [HttpGet("prestations")] - public async Task GetPrestations(CancellationToken cancellationToken) - { - var prestations = await _context.HairPrestation - .AsNoTracking() - .OrderBy(p => p.Gender) - .ThenBy(p => p.Length) - .ThenBy(p => p.Tech) - .Select(p => new HairPrestationDto - { - Id = p.Id, - Title = p.GetDisplayTitle(), - Details = p.GetDisplayDetails() - }) - .ToListAsync(cancellationToken); - - return Ok(prestations); - } - - [HttpGet("{id}", Name = "GetBillingHairMultiCutQuery")] - public async Task GetQuery([FromRoute] long id, CancellationToken cancellationToken) - { - var uid = User.GetUserId(); - - var query = await _context.HairMultiCutQueries - .Include(q => q.Prestations) - .ThenInclude(p => p.Prestation) - .Include(q => q.Location) - .Include(q => q.Client) - .Include(q => q.PerformerProfile) - .SingleOrDefaultAsync(q => q.Id == id, cancellationToken); - - if (query is null) - { - return NotFound(); - } - - if (query.ClientId != uid && query.PerformerId != uid && !User.IsInRole(Constants.AdminGroupName)) - { - return Forbid(); - } - - return Ok(SanitizeForResponse(query)); - } - - [HttpPost] - public async Task PostQuery([FromBody] HairMultiCutQuery query, CancellationToken cancellationToken) - { - var uid = User.GetUserId(); - query.ClientId = uid; - - ModelState.Remove("Client"); - ModelState.Remove("ClientId"); - ModelState.Remove("UserCreated"); - ModelState.Remove("UserModified"); - ModelState.Remove("SelectedProfile"); - ModelState.Remove("PerformerProfile"); - ModelState.Remove("Context"); - ModelState.Remove("Regularization"); - - if (query.Prestations is null || query.Prestations.Count == 0) - { - ModelState.AddModelError("Prestations", "At least one hair prestation is required."); - return BadRequest(ModelState); - } - - var prestationItems = await ResolvePrestationsAsync(query.Prestations, cancellationToken); - if (prestationItems is null) - { - ModelState.AddModelError("Prestations", "One or more hair prestations are unknown."); - return BadRequest(ModelState); - } - - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - query.Prestations = prestationItems; - query.Location = await ResolveLocationAsync(query.Location, cancellationToken); - _context.HairMultiCutQueries.Add(query); - - try - { - await _context.SaveChangesAsync(User.GetUserId(), cancellationToken); - } - catch (DbUpdateException) - { - if (QueryExists(query.Id)) - { - return Conflict(); - } - - throw; - } - - return CreatedAtRoute("GetBillingHairMultiCutQuery", new { id = query.Id }, SanitizeForResponse(query)); - } - - [HttpPut("{id}")] - public async Task PutQuery([FromRoute] long id, [FromBody] HairMultiCutQuery query, CancellationToken cancellationToken) - { - var existing = await _context.HairMultiCutQueries - .Include(q => q.Prestations) - .SingleOrDefaultAsync(q => q.Id == id, cancellationToken); - - if (existing is null) - { - return NotFound(); - } - - var uid = User.GetUserId(); - if (existing.ClientId != uid && !User.IsInRole(Constants.AdminGroupName)) - { - return Forbid(); - } - - if (query.Prestations is null || query.Prestations.Count == 0) - { - ModelState.AddModelError("Prestations", "At least one hair prestation is required."); - return BadRequest(ModelState); - } - - var prestationItems = await ResolvePrestationsAsync(query.Prestations, cancellationToken); - if (prestationItems is null) - { - ModelState.AddModelError("Prestations", "One or more hair prestations are unknown."); - return BadRequest(ModelState); - } - - _context.RemoveRange(existing.Prestations); - existing.ActivityCode = query.ActivityCode; - existing.PerformerId = query.PerformerId; - existing.Consent = query.Consent; - existing.EventDate = query.EventDate; - existing.Status = query.Status; - existing.Provisional = query.Provisional; - existing.Prestations = prestationItems; - existing.Location = await ResolveLocationAsync(query.Location, cancellationToken); - - await _context.SaveChangesAsync(User.GetUserId(), cancellationToken); - return NoContent(); - } - - [HttpDelete("{id}")] - public async Task DeleteQuery([FromRoute] long id, CancellationToken cancellationToken) - { - var uid = User.GetUserId(); - - var query = await _context.HairMultiCutQueries - .Include(q => q.Prestations) - .SingleOrDefaultAsync(q => q.Id == id, cancellationToken); - - if (query is null) - { - return NotFound(); - } - - if (query.ClientId != uid && !User.IsInRole(Constants.AdminGroupName)) - { - return Forbid(); - } - - _context.RemoveRange(query.Prestations); - _context.HairMultiCutQueries.Remove(query); - await _context.SaveChangesAsync(User.GetUserId(), cancellationToken); - - return Ok(SanitizeForResponse(query)); - } - - private async Task> ResolvePrestationsAsync( - IEnumerable requestedItems, - CancellationToken cancellationToken) - { - var ids = requestedItems - .Select(x => x.PrestationId) - .Where(x => x > 0) - .ToArray(); - - if (ids.Length == 0) - { - return null; - } - - var prestations = await _context.HairPrestation - .Where(p => ids.Contains(p.Id)) - .ToDictionaryAsync(p => p.Id, cancellationToken); - - if (prestations.Count != ids.Distinct().Count()) - { - return null; - } - - return requestedItems - .Select(item => new HairPrestationCollectionItem - { - PrestationId = item.PrestationId, - Prestation = prestations[item.PrestationId], - }) - .ToList(); - } - - private async Task ResolveLocationAsync(Location candidate, CancellationToken cancellationToken) - { - if (candidate is null) - { - return null; - } - - var existingLocation = await _context.Locations.FirstOrDefaultAsync( - x => x.Address == candidate.Address - && x.Longitude == candidate.Longitude - && x.Latitude == candidate.Latitude, - cancellationToken); - - if (existingLocation is not null) - { - return existingLocation; - } - - _context.Attach(candidate); - return candidate; - } - - private bool QueryExists(long id) - { - return _context.HairMultiCutQueries.Any(e => e.Id == id); - } - - private static HairMultiCutQuery SanitizeForResponse(HairMultiCutQuery query) - { - if (query.Prestations is not null) - { - foreach (var item in query.Prestations) - { - item.Query = null; - } - } - - return query; - } -} \ No newline at end of file diff --git a/src/Yavsc.Api/Controllers/Business/PaymentApiController.cs b/src/Yavsc.Api/Controllers/Business/PaymentApiController.cs index 930eea918..3076dbe14 100644 --- a/src/Yavsc.Api/Controllers/Business/PaymentApiController.cs +++ b/src/Yavsc.Api/Controllers/Business/PaymentApiController.cs @@ -1,4 +1,3 @@ -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newtonsoft.Json; @@ -7,8 +6,7 @@ using Yavsc.Models; namespace Yavsc.ApiControllers { - [Authorize] - [Route(Constants.APIPrefix + "/payment")] + [Route("api/payment")] public class PaymentApiController : Controller { private readonly ApplicationDbContext dbContext; diff --git a/src/Yavsc.Api/Controllers/Business/PerformersApiController.cs b/src/Yavsc.Api/Controllers/Business/PerformersApiController.cs index 36586adb9..b552eff31 100644 --- a/src/Yavsc.Api/Controllers/Business/PerformersApiController.cs +++ b/src/Yavsc.Api/Controllers/Business/PerformersApiController.cs @@ -10,9 +10,8 @@ namespace Yavsc.Controllers using Yavsc.Helpers; using Yavsc.Services; - [Authorize] [Produces("application/json")] - [Route(Constants.APIPrefix + "/performers")] + [Route("api/performers")] public class PerformersApiController : Controller { ApplicationDbContext dbContext; diff --git a/src/Yavsc.Api/Controllers/Business/ProductApiController.cs b/src/Yavsc.Api/Controllers/Business/ProductApiController.cs index ae3820dc9..abd621c31 100644 --- a/src/Yavsc.Api/Controllers/Business/ProductApiController.cs +++ b/src/Yavsc.Api/Controllers/Business/ProductApiController.cs @@ -1,15 +1,15 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Models.Market; using Yavsc.Server.Helpers; namespace Yavsc.Controllers { - [Authorize] [Produces("application/json")] - [Route(Constants.APIPrefix + "/ProductApi")] + [Route("api/ProductApi")] public class ProductApiController : Controller { private readonly ApplicationDbContext _context; @@ -46,7 +46,7 @@ namespace Yavsc.Controllers } // PUT: api/ProductApi/5 - [HttpPut("{id}"),Authorize(Constants.FrontOfficeGroupName)] + [HttpPut("{id}"),Authorize(YavscConstants.FrontOfficeGroupName)] public IActionResult PutProduct(long id, [FromBody] Product product) { if (!ModelState.IsValid) @@ -81,7 +81,7 @@ namespace Yavsc.Controllers } // POST: api/ProductApi - [HttpPost,Authorize(Constants.FrontOfficeGroupName)] + [HttpPost,Authorize(YavscConstants.FrontOfficeGroupName)] public IActionResult PostProduct([FromBody] Product product) { if (!ModelState.IsValid) @@ -110,7 +110,7 @@ namespace Yavsc.Controllers } // DELETE: api/ProductApi/5 - [HttpDelete("{id}"),Authorize(Constants.FrontOfficeGroupName)] + [HttpDelete("{id}"),Authorize(YavscConstants.FrontOfficeGroupName)] public IActionResult DeleteProduct(long id) { if (!ModelState.IsValid) diff --git a/src/Yavsc.Api/Controllers/Business/RdvQueryApiController.cs b/src/Yavsc.Api/Controllers/Business/RdvQueryApiController.cs deleted file mode 100644 index 06256a987..000000000 --- a/src/Yavsc.Api/Controllers/Business/RdvQueryApiController.cs +++ /dev/null @@ -1,302 +0,0 @@ -#nullable enable annotations - -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Npgsql; -using Yavsc.Models; -using Yavsc.Models.Billing; -using Yavsc.Models.Relationship; -using Yavsc.Models.Workflow; -using Yavsc.Server.Helpers; - -namespace Yavsc.Controllers; - -[Authorize] -[Produces("application/json")] -[Route(Constants.APIPrefix + "/billing/" + BillingCodes.Rdv)] -public class RdvQueryApiController : Controller -{ - private readonly ApplicationDbContext _context; - - public RdvQueryApiController(ApplicationDbContext context) - { - _context = context; - } - - [HttpGet] - public async Task GetQueries(CancellationToken cancellationToken) - { - var uid = User.GetUserId(); - - var queries = await _context.RdvQueries - .AsNoTracking() - .Include(q => q.Location) - .Include(q => q.Client) - .Include(q => q.PerformerProfile) - .Where(q => q.ClientId == uid || q.PerformerId == uid) - .OrderByDescending(q => q.Id) - .ToListAsync(cancellationToken); - - return Ok(queries); - } - - [HttpGet("{id}", Name = "GetRdvQuery")] - public async Task GetQuery([FromRoute] long id, CancellationToken cancellationToken) - { - var uid = User.GetUserId(); - - var query = await _context.RdvQueries - .Include(q => q.Location) - .Include(q => q.Client) - .Include(q => q.PerformerProfile) - .SingleOrDefaultAsync(q => q.Id == id, cancellationToken); - - if (query is null) - { - return NotFound(); - } - - if (query.ClientId != uid && query.PerformerId != uid && !User.IsInRole(Constants.AdminGroupName)) - { - return Forbid(); - } - - return Ok(query); - } - - [HttpPost] - public async Task PostQuery([FromBody] RdvQuery query, CancellationToken cancellationToken) - { - var uid = User.GetUserId(); - // Security: the caller always posts for themselves. - query.ClientId = uid; - query.EventDate = EnsureUtc(query.EventDate); - - ModelState.Remove("Client"); - ModelState.Remove("ClientId"); - ModelState.Remove("UserCreated"); - ModelState.Remove("UserModified"); - ModelState.Remove("SelectedProfile"); - ModelState.Remove("PerformerProfile"); - ModelState.Remove("Context"); - ModelState.Remove("Regularization"); - - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - if (query.Location is null) - { - return BadRequest(new { Error = "location is required" }); - } - - var resolvedLocation = await ResolveLocationAsync(query.Location, cancellationToken); - if (resolvedLocation is null) - { - return BadRequest(new { Error = "location payload is invalid" }); - } - - await PersistLocationIfNeededAsync(resolvedLocation, uid, cancellationToken); - - query.Location = resolvedLocation; - - var addedEntry = _context.RdvQueries.Add(query); - EnsureLocationForeignKey(addedEntry, resolvedLocation.Id); - - try - { - await _context.SaveChangesAsync(User.GetUserId(), cancellationToken); - } - catch (DbUpdateException ex) when (IsLocationForeignKeyViolation(ex)) - { - return BadRequest(new { Error = "location reference is invalid" }); - } - catch (DbUpdateException) - { - if (QueryExists(query.Id)) - { - return Conflict(); - } - - throw; - } - - return CreatedAtRoute("GetRdvQuery", new { id = query.Id }, query); - } - - [HttpPut("{id}")] - public async Task PutQuery([FromRoute] long id, [FromBody] RdvQuery query, CancellationToken cancellationToken) - { - var uid = User.GetUserId(); - var existing = await _context.RdvQueries - .Include(q => q.Location) - .SingleOrDefaultAsync(q => q.Id == id, cancellationToken); - - if (existing is null) - { - return NotFound(); - } - - if (existing.ClientId != uid && !User.IsInRole(Constants.AdminGroupName)) - { - return Forbid(); - } - - existing.ActivityCode = query.ActivityCode; - existing.PerformerId = query.PerformerId; - existing.Consent = query.Consent; - existing.EventDate = EnsureUtc(query.EventDate); - existing.LocationType = query.LocationType; - existing.Reason = query.Reason; - existing.Status = query.Status; - existing.Provisional = query.Provisional; - - if (query.Location is not null) - { - var resolvedLocation = await ResolveLocationAsync(query.Location, cancellationToken); - if (resolvedLocation is null) - { - return BadRequest(new { Error = "location payload is invalid" }); - } - - await PersistLocationIfNeededAsync(resolvedLocation, uid, cancellationToken); - - existing.Location = resolvedLocation; - EnsureLocationForeignKey(_context.Entry(existing), resolvedLocation.Id); - } - - try - { - await _context.SaveChangesAsync(User.GetUserId(), cancellationToken); - } - catch (DbUpdateConcurrencyException) - { - if (!QueryExists(id)) - { - return NotFound(); - } - - throw; - } - catch (DbUpdateException ex) when (IsLocationForeignKeyViolation(ex)) - { - return BadRequest(new { Error = "location reference is invalid" }); - } - - return NoContent(); - } - - [HttpDelete("{id}")] - public async Task DeleteQuery([FromRoute] long id, CancellationToken cancellationToken) - { - var uid = User.GetUserId(); - - var query = await _context.RdvQueries - .SingleOrDefaultAsync(q => q.Id == id, cancellationToken); - - if (query is null) - { - return NotFound(); - } - - if (query.ClientId != uid && !User.IsInRole(Constants.AdminGroupName)) - { - return Forbid(); - } - - _context.RdvQueries.Remove(query); - await _context.SaveChangesAsync(User.GetUserId(), cancellationToken); - - return Ok(query); - } - - private bool QueryExists(long id) - { - return _context.RdvQueries.Any(e => e.Id == id); - } - - private async Task ResolveLocationAsync(Location postedLocation, CancellationToken cancellationToken) - { - if (postedLocation.Id > 0) - { - var byId = await _context.Locations - .FirstOrDefaultAsync(x => x.Id == postedLocation.Id, cancellationToken); - if (byId is not null) - { - return byId; - } - } - - if (string.IsNullOrWhiteSpace(postedLocation.Address)) - { - return null; - } - - var existingByCoordinates = await _context.Locations.FirstOrDefaultAsync( - x => x.Address == postedLocation.Address - && x.Longitude == postedLocation.Longitude - && x.Latitude == postedLocation.Latitude, - cancellationToken); - - if (existingByCoordinates is not null) - { - return existingByCoordinates; - } - - // Treat unknown location ids as client-side placeholders and insert a new row. - postedLocation.Id = 0; - _context.Locations.Add(postedLocation); - return postedLocation; - } - - private async Task PersistLocationIfNeededAsync(Location location, string userId, CancellationToken cancellationToken) - { - if (_context.Entry(location).State != EntityState.Added) - { - return; - } - - await _context.SaveChangesAsync(userId, cancellationToken); - } - - private static bool IsLocationForeignKeyViolation(DbUpdateException ex) - { - if (ex.InnerException is not PostgresException pg) - { - return false; - } - - return pg.SqlState == PostgresErrorCodes.ForeignKeyViolation - && string.Equals(pg.ConstraintName, "FK_NominativeServiceCommand_Locations_LocationId", StringComparison.Ordinal); - } - - private static void EnsureLocationForeignKey(EntityEntry entry, long locationId) - { - SetFkIfPresent(entry, "LocationId", locationId); - SetFkIfPresent(entry, "RdvQuery_LocationId", locationId); - } - - private static void SetFkIfPresent(EntityEntry entry, string propertyName, long value) - { - var property = entry.Metadata.FindProperty(propertyName); - if (property is null) - { - return; - } - - entry.Property(propertyName).CurrentValue = value; - } - - private static DateTime EnsureUtc(DateTime value) - { - return value.Kind switch - { - DateTimeKind.Utc => value, - DateTimeKind.Local => value.ToUniversalTime(), - _ => DateTime.SpecifyKind(value, DateTimeKind.Utc) - }; - } -} diff --git a/src/Yavsc.Api/Controllers/HairCut/BursherProfilesApiController.cs b/src/Yavsc.Api/Controllers/HairCut/BursherProfilesApiController.cs index 00e6e4ea2..22fdf1e9f 100644 --- a/src/Yavsc.Api/Controllers/HairCut/BursherProfilesApiController.cs +++ b/src/Yavsc.Api/Controllers/HairCut/BursherProfilesApiController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Models.Haircut; using Yavsc.Server.Helpers; @@ -7,7 +8,7 @@ using Yavsc.Server.Helpers; namespace Yavsc.Controllers { [Produces("application/json")] - [Route(Constants.APIPrefix + "/bursherprofiles")] + [Route("api/bursherprofiles")] public class BursherProfilesApiController : Controller { private readonly ApplicationDbContext _context; @@ -56,7 +57,7 @@ namespace Yavsc.Controllers { return BadRequest(); } - + if (id != User.GetUserId()) { return BadRequest(); diff --git a/src/Yavsc.Api/Controllers/HairCut/HairCutController.cs b/src/Yavsc.Api/Controllers/HairCut/HairCutController.cs index bf27d1d96..822c3182d 100644 --- a/src/Yavsc.Api/Controllers/HairCut/HairCutController.cs +++ b/src/Yavsc.Api/Controllers/HairCut/HairCutController.cs @@ -1,4 +1,6 @@ +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Localization; namespace Yavsc.ApiControllers @@ -9,6 +11,7 @@ namespace Yavsc.ApiControllers using System.Security.Claims; using Microsoft.Extensions.Logging; using Models; + using Services; using Models.Haircut; using System.Threading.Tasks; using Helpers; @@ -21,7 +24,7 @@ namespace Yavsc.ApiControllers using Microsoft.AspNetCore.Authorization; using Yavsc.Server.Helpers; - [Route(Constants.APIPrefix + "/haircut")][Authorize] + [Route("api/haircut")][Authorize] public class HairCutController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Api/Controllers/HyperLinkApiController.cs b/src/Yavsc.Api/Controllers/HyperLinkApiController.cs index 3ba742195..b2d28baa7 100644 --- a/src/Yavsc.Api/Controllers/HyperLinkApiController.cs +++ b/src/Yavsc.Api/Controllers/HyperLinkApiController.cs @@ -6,7 +6,7 @@ using Yavsc.Models.Relationship; namespace Yavsc.Controllers { [Produces("application/json")] - [Route(Constants.APIPrefix + "/hyperlink")] + [Route("api/hyperlink")] public class HyperLinkApiController : Controller { private ApplicationDbContext _context; diff --git a/src/Yavsc.Api/Controllers/IT/GitRefsApiController.cs b/src/Yavsc.Api/Controllers/IT/GitRefsApiController.cs index 67f38d22e..55ae08b7c 100644 --- a/src/Yavsc.Api/Controllers/IT/GitRefsApiController.cs +++ b/src/Yavsc.Api/Controllers/IT/GitRefsApiController.cs @@ -7,7 +7,7 @@ using Yavsc.Server.Models.IT.SourceCode; namespace Yavsc.Controllers { [Produces("application/json")] - [Route(Constants.APIPrefix + "/GitRefsApi")] + [Route("api/GitRefsApi")] [Authorize("AdministratorOnly")] public class GitRefsApiController : Controller { diff --git a/src/Yavsc.Api/Controllers/MailTemplatingApiController.cs b/src/Yavsc.Api/Controllers/MailTemplatingApiController.cs index c289c3da2..958ade66b 100644 --- a/src/Yavsc.Api/Controllers/MailTemplatingApiController.cs +++ b/src/Yavsc.Api/Controllers/MailTemplatingApiController.cs @@ -2,9 +2,9 @@ using Microsoft.AspNetCore.Mvc; namespace Yavsc.ApiControllers { - [Route(Constants.APIPrefix + "/mailtemplate")] + [Route("api/mailtemplate")] public class MailTemplatingApiController: Controller { - + } } diff --git a/src/Yavsc.Api/Controllers/MailingTemplateApiController.cs b/src/Yavsc.Api/Controllers/MailingTemplateApiController.cs index 4373d8477..dc535476f 100644 --- a/src/Yavsc.Api/Controllers/MailingTemplateApiController.cs +++ b/src/Yavsc.Api/Controllers/MailingTemplateApiController.cs @@ -7,7 +7,7 @@ using Microsoft.EntityFrameworkCore; namespace Yavsc.Controllers { [Produces("application/json")] - [Route(Constants.APIPrefix + "/mailing")] + [Route("api/mailing")] [Authorize("AdministratorOnly")] public class MailingTemplateApiController : Controller { diff --git a/src/Yavsc.Api/Controllers/Musical/DjProfileApiController.cs b/src/Yavsc.Api/Controllers/Musical/DjProfileApiController.cs index 35e97194f..050241a7b 100644 --- a/src/Yavsc.Api/Controllers/Musical/DjProfileApiController.cs +++ b/src/Yavsc.Api/Controllers/Musical/DjProfileApiController.cs @@ -1,5 +1,6 @@ namespace Yavsc.ApiControllers { + using Models; using Yavsc.Models.Musical.Profiles; public class DjProfileApiController : ProfileApiController diff --git a/src/Yavsc.Api/Controllers/Musical/MusicalPreferencesApiController.cs b/src/Yavsc.Api/Controllers/Musical/MusicalPreferencesApiController.cs index 89d1d265d..944b335bc 100644 --- a/src/Yavsc.Api/Controllers/Musical/MusicalPreferencesApiController.cs +++ b/src/Yavsc.Api/Controllers/Musical/MusicalPreferencesApiController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Models.Musical; using Yavsc.Server.Helpers; @@ -7,7 +8,7 @@ using Yavsc.Server.Helpers; namespace Yavsc.Controllers { [Produces("application/json")] - [Route(Constants.APIPrefix + "/museprefs")] + [Route("api/museprefs")] public class MusicalPreferencesApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Api/Controllers/Musical/MusicalTendenciesApiController.cs b/src/Yavsc.Api/Controllers/Musical/MusicalTendenciesApiController.cs index 67c5c2f8a..eacccb0a4 100644 --- a/src/Yavsc.Api/Controllers/Musical/MusicalTendenciesApiController.cs +++ b/src/Yavsc.Api/Controllers/Musical/MusicalTendenciesApiController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Models.Musical; using Yavsc.Server.Helpers; @@ -7,7 +8,7 @@ using Yavsc.Server.Helpers; namespace Yavsc.Controllers { [Produces("application/json")] - [Route(Constants.APIPrefix + "/MusicalTendenciesApi")] + [Route("api/MusicalTendenciesApi")] public class MusicalTendenciesApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Api/Controllers/PostRateApiController.cs b/src/Yavsc.Api/Controllers/PostRateApiController.cs index 83e2bee18..dc132da49 100644 --- a/src/Yavsc.Api/Controllers/PostRateApiController.cs +++ b/src/Yavsc.Api/Controllers/PostRateApiController.cs @@ -1,6 +1,8 @@ +using System.Linq; using System.Security.Claims; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Server.Helpers; @@ -35,7 +37,7 @@ namespace Yavsc.Controllers var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); if (blogpost.AuthorId!=uid) - if (!User.IsInRole(Constants.AdminGroupName)) + if (!User.IsInRole(YavscConstants.AdminGroupName)) return BadRequest(); _context.SaveChanges(User.GetUserId()); diff --git a/src/Yavsc.Api/Controllers/ProfileApiController.cs b/src/Yavsc.Api/Controllers/ProfileApiController.cs index 7ae9f153c..60ad1f601 100644 --- a/src/Yavsc.Api/Controllers/ProfileApiController.cs +++ b/src/Yavsc.Api/Controllers/ProfileApiController.cs @@ -2,11 +2,13 @@ using Microsoft.AspNetCore.Mvc; namespace Yavsc.ApiControllers { + using Models; + /// /// Base class for managing performers profiles /// - [Produces("application/json"),Route(Constants.APIPrefix + "/profile")] - public abstract class ProfileApiController : Controller + [Produces("application/json"),Route("api/profile")] + public abstract class ProfileApiController : Controller { public ProfileApiController() { } diff --git a/src/Yavsc.Api/Controllers/Relationship/BlackListApiController.cs b/src/Yavsc.Api/Controllers/Relationship/BlackListApiController.cs index 3a0ca6300..ebc1c03b9 100644 --- a/src/Yavsc.Api/Controllers/Relationship/BlackListApiController.cs +++ b/src/Yavsc.Api/Controllers/Relationship/BlackListApiController.cs @@ -2,6 +2,7 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Models.Access; using Yavsc.Server.Helpers; @@ -9,7 +10,7 @@ using Yavsc.Server.Helpers; namespace Yavsc.Controllers { [Produces("application/json")] - [Route(Constants.APIPrefix + "/blacklist"), Authorize] + [Route("api/blacklist"), Authorize] public class BlackListApiController : Controller { private readonly ApplicationDbContext _context; @@ -49,8 +50,8 @@ namespace Yavsc.Controllers { var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); if (uid != blackListed.OwnerId) - if (!User.IsInRole(Constants.AdminGroupName)) - if (!User.IsInRole(Constants.FrontOfficeGroupName)) + if (!User.IsInRole(YavscConstants.AdminGroupName)) + if (!User.IsInRole(YavscConstants.FrontOfficeGroupName)) return false; return true; } @@ -139,7 +140,7 @@ namespace Yavsc.Controllers if (!CheckPermission(blackListed)) return BadRequest(); - + _context.BlackListed.Remove(blackListed); _context.SaveChanges(User.GetUserId()); diff --git a/src/Yavsc.Api/Controllers/Relationship/ChatApiController.cs b/src/Yavsc.Api/Controllers/Relationship/ChatApiController.cs index b991c0fb6..cdaeecde4 100644 --- a/src/Yavsc.Api/Controllers/Relationship/ChatApiController.cs +++ b/src/Yavsc.Api/Controllers/Relationship/ChatApiController.cs @@ -9,14 +9,14 @@ using Microsoft.EntityFrameworkCore; namespace Yavsc.Controllers { - [Route(Constants.APIPrefix + "/chat")] + [Route("api/chat")] public class ChatApiController : Controller { readonly ApplicationDbContext dbContext; readonly UserManager userManager; private readonly IConnexionManager _cxManager; public ChatApiController(ApplicationDbContext dbContext, - UserManager userManager, + UserManager userManager, IConnexionManager cxManager) { this.dbContext = dbContext; diff --git a/src/Yavsc.Api/Controllers/Relationship/ChatRoomAccessApiController.cs b/src/Yavsc.Api/Controllers/Relationship/ChatRoomAccessApiController.cs index fba8bd432..5fe3a0bf6 100644 --- a/src/Yavsc.Api/Controllers/Relationship/ChatRoomAccessApiController.cs +++ b/src/Yavsc.Api/Controllers/Relationship/ChatRoomAccessApiController.cs @@ -9,7 +9,7 @@ using Yavsc.Server.Helpers; namespace Yavsc.Controllers { [Produces("application/json")] - [Route(Constants.APIPrefix + "/ChatRoomAccessApi")] + [Route("api/ChatRoomAccessApi")] public class ChatRoomAccessApiController : Controller { private readonly ApplicationDbContext _context; @@ -37,7 +37,7 @@ namespace Yavsc.Controllers ChatRoomAccess chatRoomAccess = await _context.ChatRoomAccess.SingleAsync(m => m.ChannelName == id); - + if (chatRoomAccess == null) { @@ -46,13 +46,13 @@ namespace Yavsc.Controllers var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); if (uid != chatRoomAccess.UserId && uid != chatRoomAccess.Room.OwnerId - && ! User.IsInMsRole(Constants.AdminGroupName)) - + && ! User.IsInMsRole(YavscConstants.AdminGroupName)) + { ModelState.AddModelError("UserId","get refused"); return BadRequest(ModelState); } - + return Ok(chatRoomAccess); } @@ -72,7 +72,7 @@ namespace Yavsc.Controllers } var room = _context.ChatRoom.First(channel => channel.Name == chatRoomAccess.ChannelName ); - if (uid != room.OwnerId && ! User.IsInMsRole(Constants.AdminGroupName)) + if (uid != room.OwnerId && ! User.IsInMsRole(YavscConstants.AdminGroupName)) { ModelState.AddModelError("ChannelName", "access put refused"); return BadRequest(ModelState); @@ -110,7 +110,7 @@ namespace Yavsc.Controllers var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); var room = _context.ChatRoom.First(channel => channel.Name == chatRoomAccess.ChannelName ); - if (room == null || (uid != room.OwnerId && ! User.IsInMsRole(Constants.AdminGroupName))) + if (room == null || (uid != room.OwnerId && ! User.IsInMsRole(YavscConstants.AdminGroupName))) { ModelState.AddModelError("ChannelName", "access post refused"); return BadRequest(ModelState); @@ -154,7 +154,7 @@ namespace Yavsc.Controllers var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); var room = _context.ChatRoom.First(channel => channel.Name == chatRoomAccess.ChannelName ); - if (room == null || (uid != room.OwnerId && chatRoomAccess.UserId != uid && ! User.IsInMsRole(Constants.AdminGroupName))) + if (room == null || (uid != room.OwnerId && chatRoomAccess.UserId != uid && ! User.IsInMsRole(YavscConstants.AdminGroupName))) { ModelState.AddModelError("UserId", "access drop refused"); return BadRequest(ModelState); diff --git a/src/Yavsc.Api/Controllers/Relationship/ChatRoomApiController.cs b/src/Yavsc.Api/Controllers/Relationship/ChatRoomApiController.cs index d0f712b34..990646fc8 100644 --- a/src/Yavsc.Api/Controllers/Relationship/ChatRoomApiController.cs +++ b/src/Yavsc.Api/Controllers/Relationship/ChatRoomApiController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Models.Chat; using Yavsc.Server.Helpers; @@ -7,7 +8,7 @@ using Yavsc.Server.Helpers; namespace Yavsc.Controllers { [Produces("application/json")] - [Route(Constants.APIPrefix + "/ChatRoomApi")] + [Route("api/ChatRoomApi")] public class ChatRoomApiController : Controller { private readonly ApplicationDbContext _context; @@ -127,7 +128,7 @@ namespace Yavsc.Controllers } ChatRoom chatRoom = await _context.ChatRoom.SingleAsync(m => m.Name == id); - + if (chatRoom == null) { @@ -136,7 +137,7 @@ namespace Yavsc.Controllers if (User.GetUserId() != chatRoom.OwnerId ) { - if (!User.IsInMsRole(Constants.AdminGroupName)) + if (!User.IsInMsRole(YavscConstants.AdminGroupName)) return BadRequest(new {error = "OwnerId"}); } diff --git a/src/Yavsc.Api/Controllers/Relationship/ContactsApiController.cs b/src/Yavsc.Api/Controllers/Relationship/ContactsApiController.cs index d7ed46072..ffd6eb0b6 100644 --- a/src/Yavsc.Api/Controllers/Relationship/ContactsApiController.cs +++ b/src/Yavsc.Api/Controllers/Relationship/ContactsApiController.cs @@ -1,13 +1,14 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Yavsc.Abstract.Identity; +using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Server.Helpers; namespace Yavsc.Controllers { [Produces("application/json")] - [Route(Constants.APIPrefix + "/ContactsApi")] + [Route("api/ContactsApi")] public class ContactsApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Api/Controllers/ServiceApiController.cs b/src/Yavsc.Api/Controllers/ServiceApiController.cs index 98817eebc..e9330543b 100644 --- a/src/Yavsc.Api/Controllers/ServiceApiController.cs +++ b/src/Yavsc.Api/Controllers/ServiceApiController.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Models.Market; using Yavsc.Server.Helpers; @@ -8,7 +9,7 @@ using Yavsc.Server.Helpers; namespace Yavsc.Controllers { [Produces("application/json")] - [Route(Constants.APIPrefix + "/ServiceApi")] + [Route("api/ServiceApi")] public class ServiceApiController : Controller { private readonly ApplicationDbContext _context; @@ -45,7 +46,7 @@ namespace Yavsc.Controllers } // PUT: api/ServiceApi/5 - [HttpPut("{id}"),Authorize(Constants.FrontOfficeGroupName)] + [HttpPut("{id}"),Authorize(YavscConstants.FrontOfficeGroupName)] public IActionResult PutService(long id, [FromBody] Service service) { if (!ModelState.IsValid) @@ -80,7 +81,7 @@ namespace Yavsc.Controllers } // POST: api/ServiceApi - [HttpPost,Authorize(Constants.FrontOfficeGroupName)] + [HttpPost,Authorize(YavscConstants.FrontOfficeGroupName)] public IActionResult PostService([FromBody] Service service) { if (!ModelState.IsValid) @@ -109,7 +110,7 @@ namespace Yavsc.Controllers } // DELETE: api/ServiceApi/5 - [HttpDelete("{id}"),Authorize(Constants.FrontOfficeGroupName)] + [HttpDelete("{id}"),Authorize(YavscConstants.FrontOfficeGroupName)] public IActionResult DeleteService(long id) { if (!ModelState.IsValid) diff --git a/src/Yavsc.Api/Controllers/accounting/AccountController.cs b/src/Yavsc.Api/Controllers/accounting/AccountController.cs index 155274cdf..aff710137 100644 --- a/src/Yavsc.Api/Controllers/accounting/AccountController.cs +++ b/src/Yavsc.Api/Controllers/accounting/AccountController.cs @@ -2,8 +2,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Microsoft.AspNetCore.Http; -using ImageMagick; using Yavsc.Models; using Yavsc.Api.Helpers; @@ -12,21 +10,10 @@ using System.Diagnostics; namespace Yavsc.WebApi.Controllers { - [Route( Constants.APIPrefix + "/account")] + [Route("~/api/account")] [Authorize("ApiScope")] public class ApiAccountController : Controller { - private const long MaxAvatarSizeBytes = 2 * 1024 * 1024; - private static readonly string[] AcceptedAvatarMimeTypes = - [ - "image/png", - "image/jpeg", - "image/webp", - "image/gif", - "image/bmp", - "image/tiff", - ]; - readonly ApplicationDbContext _dbContext; private readonly ILogger _logger; @@ -74,102 +61,23 @@ namespace Yavsc.WebApi.Controllers return Ok(new { host = Request.ForwardedFor() }); } - + /// /// Updates the avatar /// /// - [HttpPost("set-avatar")] + [HttpPost("~/api/set-avatar")] public async Task SetAvatar() { var user = await GetUserData(User.GetUserId()); - if (!Request.HasFormContentType) - { - return BadRequest(new - { - status = "invalid_request", - message = "A multipart/form-data request is required.", - acceptedMimeTypes = AcceptedAvatarMimeTypes, - maxFileSizeBytes = MaxAvatarSizeBytes, - outputFormat = "image/png", - }); - } + if (Request.Form.Files.Count!=1) + return new BadRequestResult(); + if (!Request.Form.Files[0].ContentType.StartsWith("image/png")) + return new BadRequestResult(); - if (Request.Form.Files.Count != 1) - { - return BadRequest(new - { - status = "invalid_file_count", - message = "Exactly one file is required.", - acceptedMimeTypes = AcceptedAvatarMimeTypes, - maxFileSizeBytes = MaxAvatarSizeBytes, - outputFormat = "image/png", - }); - } - - var avatarFile = Request.Form.Files[0]; - if (avatarFile.Length <= 0) - { - return BadRequest(new - { - status = "empty_file", - message = "The uploaded file is empty.", - acceptedMimeTypes = AcceptedAvatarMimeTypes, - maxFileSizeBytes = MaxAvatarSizeBytes, - outputFormat = "image/png", - }); - } - - if (avatarFile.Length > MaxAvatarSizeBytes) - { - return BadRequest(new - { - status = "file_too_large", - message = "Avatar is too large.", - acceptedMimeTypes = AcceptedAvatarMimeTypes, - maxFileSizeBytes = MaxAvatarSizeBytes, - outputFormat = "image/png", - }); - } - - if (!AcceptedAvatarMimeTypes.Any(m => string.Equals(m, avatarFile.ContentType, StringComparison.OrdinalIgnoreCase))) - { - return StatusCode(StatusCodes.Status415UnsupportedMediaType, new - { - status = "unsupported_media_type", - message = "Unsupported image format.", - acceptedMimeTypes = AcceptedAvatarMimeTypes, - maxFileSizeBytes = MaxAvatarSizeBytes, - outputFormat = "image/png", - }); - } - - try - { - var info = user.ReceiveAvatar(avatarFile); - await _dbContext.SaveChangesAsync(); - return Ok(new - { - status = "uploaded", - message = "Avatar uploaded successfully.", - acceptedMimeTypes = AcceptedAvatarMimeTypes, - maxFileSizeBytes = MaxAvatarSizeBytes, - outputFormat = "image/png", - avatar = info, - }); - } - catch (MagickException ex) - { - _logger.LogWarning(ex, "Avatar upload failed: invalid image data for user {UserId}", user.Id); - return BadRequest(new - { - status = "invalid_image_data", - message = "Image content could not be decoded.", - acceptedMimeTypes = AcceptedAvatarMimeTypes, - maxFileSizeBytes = MaxAvatarSizeBytes, - outputFormat = "image/png", - }); - } + var info = user.ReceiveAvatar(Request.Form.Files[0]); + await _dbContext.SaveChangesAsync(); + return Ok(info); } [HttpGet("identity")] diff --git a/src/Yavsc.Api/Controllers/accounting/ApplicationUserApiController.cs b/src/Yavsc.Api/Controllers/accounting/ApplicationUserApiController.cs index 837e7f1eb..11c70d60b 100644 --- a/src/Yavsc.Api/Controllers/accounting/ApplicationUserApiController.cs +++ b/src/Yavsc.Api/Controllers/accounting/ApplicationUserApiController.cs @@ -1,14 +1,19 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Yavsc.Abstract.Identity; +using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Server.Helpers; namespace Yavsc.Controllers { [Produces("application/json"),Authorize("AdministratorOnly")] - [Route(Constants.APIPrefix + "/users")] + [Route("api/users")] public class ApplicationUserApiController : Controller { private readonly ApplicationDbContext _context; @@ -23,7 +28,7 @@ namespace Yavsc.Controllers public IEnumerable GetApplicationUser(int skip=0, int take = 25) { return _context.Users.Skip(skip).Take(take) - .Select(u=> new UserInfo{ + .Select(u=> new UserInfo{ UserId = u.Id, UserName = u.UserName, Avatar = u.Avatar}); @@ -34,7 +39,7 @@ namespace Yavsc.Controllers { return _context.Users.Where(u => u.UserName.Contains(pattern)) .Skip(skip).Take(take) - .Select(u=> new UserInfo { + .Select(u=> new UserInfo { UserId = u.Id, UserName = u.UserName, Avatar = u.Avatar }); diff --git a/src/Yavsc.Api/Controllers/accounting/ProfileApiController.cs b/src/Yavsc.Api/Controllers/accounting/ProfileApiController.cs index 3b72e1b6b..ce90b0762 100644 --- a/src/Yavsc.Api/Controllers/accounting/ProfileApiController.cs +++ b/src/Yavsc.Api/Controllers/accounting/ProfileApiController.cs @@ -1,7 +1,11 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; +using System.Threading.Tasks; +using System.Linq; using Yavsc.Models; using Yavsc.Abstract.Identity; +using Yavsc.Helpers; using Yavsc.Server.Helpers; namespace Yavsc.ApiControllers.accounting diff --git a/src/Yavsc.Api/Helpers/RequestHelpers.cs b/src/Yavsc.Api/Helpers/RequestHelpers.cs index d92e765eb..0ab687e03 100644 --- a/src/Yavsc.Api/Helpers/RequestHelpers.cs +++ b/src/Yavsc.Api/Helpers/RequestHelpers.cs @@ -1,3 +1,13 @@ +using System.Collections.Generic; + +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Yavsc.ViewModels; +using Yavsc.Models; +using System.Linq; + namespace Yavsc.Api.Helpers { public static class RequestHelpers diff --git a/src/Yavsc.Api/Program.cs b/src/Yavsc.Api/Program.cs index 5f7ef25c0..f7f247cc5 100644 --- a/src/Yavsc.Api/Program.cs +++ b/src/Yavsc.Api/Program.cs @@ -1,14 +1,26 @@ +/* + Copyright (c) 2024 HigginsSoft, Alexander Higgins - https://github.com/alexhiggins732/ + + Copyright (c) 2018, Brock Allen & Dominick Baier. All rights reserved. + + Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + Source code and license this software can be found + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. +*/ using Anthropic.SDK; using IdentityModel; +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection.Extensions; +using Yavsc; using Yavsc.Abstract.Interfaces; using Yavsc.Helpers; using Yavsc.Interface; using Yavsc.Interfaces; using Yavsc.Models; -using Yavsc; using Yavsc.Server.Helpers; using Yavsc.Services; @@ -21,7 +33,6 @@ internal class Program var builder = WebApplication.CreateBuilder(args); builder.AddConfiguration("api"); - Config.SiteSetup = builder.Configuration.GetSection("Site").Get() ?? new SiteSettings(); var services = builder.Services; @@ -62,13 +73,9 @@ internal class Program services.AddAuthentication("Bearer") .AddYavscJwtBearer(builder.Configuration); - services.AddSignalR(); - services.AddSingleton(); - - // DbContextBuilder services.AddDbContext(options => - options.UseNpgsql(builder.Configuration.GetConnectionString( - Yavsc.Constants.YavscConnectionStringName))); + + options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); services.AddLocalization(options => { @@ -82,8 +89,7 @@ internal class Program .TryAddSingleton(); services .AddTransient() - .AddTransient() - .AddTransient(); + .AddTransient(); services.AddTransient(); builder.Services.AddSession(options => { @@ -115,7 +121,9 @@ internal class Program ; app.MapIdentityApi().RequireAuthorization("ApiScope"); app.MapDefaultControllerRoute(); - + app.MapGet("/identity", (HttpContext context) => + new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value })) + ); app.UseSession(); await app.RunAsync(); diff --git a/src/Yavsc.Api/Properties/launchSettings.json b/src/Yavsc.Api/Properties/launchSettings.json new file mode 100644 index 000000000..3ede67745 --- /dev/null +++ b/src/Yavsc.Api/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "https://localhost:6001", + "sslPort": 6001 + } + }, + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:6001", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Yavsc.Api/Yavsc.Api.csproj b/src/Yavsc.Api/Yavsc.Api.csproj index c8c30904d..5672ab7a9 100644 --- a/src/Yavsc.Api/Yavsc.Api.csproj +++ b/src/Yavsc.Api/Yavsc.Api.csproj @@ -7,11 +7,14 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070 + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 + + + \ No newline at end of file diff --git a/src/Yavsc.Api/appsettings-api.json b/src/Yavsc.Api/appsettings-api.json index 6783f565e..ac626c0d6 100644 --- a/src/Yavsc.Api/appsettings-api.json +++ b/src/Yavsc.Api/appsettings-api.json @@ -1,10 +1,8 @@ { "Site": { "Authority": "https://localhost:5001", - "Audience": ["api"], "CorsAllowedOrigins": [ - "https://localhost:5003", - "https://yavsc.pschneider.fr" + "https://localhost:5003" ] }, "Logging": { diff --git a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs deleted file mode 100644 index 174838173..000000000 --- a/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs +++ /dev/null @@ -1,390 +0,0 @@ -using System.Net; -using System.Net.Http.Json; -using System.Text.Json; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Yavsc.Abstract.BlogSpot; -using Yavsc.Models; -using Yavsc.Models.Access; -using Yavsc.Models.Blog; -using Yavsc.Tests.Shared; -using static Yavsc.Constants; - -namespace Yavsc.Blogs.Tests; - -/// -/// Behavioural tests for BlogAclApiController.PostCircleAuthorizationToBlogPost: -/// POST /api/v1/blogacl with a JSON body of -/// CircleAuthorizationToBlogPost (CircleId + BlogPostId). -/// -/// Same fixture as : -/// provides a SQLite -/// :memory: ApplicationDbContext (so FKs are -/// enforced the way a real relational engine would) and JWT -/// bearer auth via TestTokenIssuer. No mocks — the real -/// DbContext receives the real INSERT attempt. -/// -/// The bug being pinned by these tests: the POST endpoint -/// calls _context.CircleAuthorizationToBlogPost.Add(...) -/// then SaveChangesAsync. The entity has a composite -/// key (CircleId + BlogPostId) and two FKs; EF Core refuses -/// the INSERT with -/// System.InvalidOperationException: The value of -/// 'CircleAuthorizationToBlogPost.BlogPostId' is unknown when -/// attempting to save changes when the principal entities -/// (the existing BlogPost and Circle) are not -/// attached to the DbContext in the same change-tracker graph. -/// -[Collection("Yavsc Blogs")] -public sealed class BlogAclApiTests : IClassFixture -{ - private readonly BlogsWebServerFixture _fixture; - - public BlogAclApiTests(BlogsWebServerFixture fixture) - { - _fixture = fixture; - } - - - private string BlogUrl() - => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/{BlogSpotPath}"; - private string BlogAclUrl() - => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/{BlogAclPath}"; - - /// Delete any ACL rows tied to the specified - /// (CircleId, BlogPostId) pair. The shared SQLite store - /// persists across tests, so tests that POST a successful ACL - /// row would otherwise conflict with whichever other test runs - /// next against the same pair — xUnit does not guarantee - /// execution order. Calling this at the start of each - /// insert-bearing test guarantees a clean slate regardless of - /// the previous test's outcome. - private void CleanupAcl(long circleId, long blogPostId) - { - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - db.CircleAuthorizationToBlogPost - .Where(a => a.CircleId == circleId - && a.BlogPostId == blogPostId) - .ExecuteDelete(); - } - - private HttpClient NewClient(string subject) - { - var handler = new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (_, _, _, _) => true - }; - var http = new HttpClient(handler) - { - BaseAddress = new Uri(_fixture.Addresses.First(a => a.StartsWith("https://"))) - }; - // The Blogs fixture disables JwtSecurityTokenHandler's - // inbound claim-type remap, so the JWT's "sub" stays "sub" - // rather than being rewritten to ClaimTypes.NameIdentifier. - // The controller, however, reads the user id via - // User.FindFirstValue(ClaimTypes.NameIdentifier), so we add - // an explicit nameid claim to keep the legacy lookup happy. - http.DefaultRequestHeaders.Authorization = - new System.Net.Http.Headers.AuthenticationHeaderValue( - "Bearer", - TestTokenIssuer.Issue( - subject, - extraClaims: new[] - { - new System.Security.Claims.Claim( - System.Security.Claims.ClaimTypes.NameIdentifier, - subject), - })); - return http; - } - - /// - /// Reproduces the prod 500 logged on 2026-08-21 on mercure: - /// InvalidOperationException: The value of - /// 'CircleAuthorizationToBlogPost.BlogPostId' is unknown - /// when POSTs the - /// shape { "circleId": <id> } — the exact body the - /// PostIt client builds from - /// (which only carries CircleId). The server deserialises - /// it into , leaves - /// BlogPostId at its default(long) = 0, attaches - /// no Target navigation, and EF Core refuses to INSERT - /// during PrepareToSave(). The fix lives in PostIt - /// (enrich the payload with blogPostId + comment) - /// and on the wire DTO ( must - /// carry those fields); the server validates. Until that ships, - /// this test stays red. - /// - [Fact] - public async Task PostCircleAuthorization_returns_201_when_payload_mirrors_PostIt_shape_against_existing_circle_named_test() - { - // The prod circle already exists with Name="test", Public=true, - // owned by the caller. We seed the same shape pre-POST so the - // test reproduces the prod scenario end-to-end. - _fixture.SeedUser(_fixture.DefaultUserLogin); - var seededCircleId = _fixture.SeedCircle(_fixture.DefaultUserLogin, "test"); - var seededBlogPostId = _fixture.SeedBlogPost(_fixture.DefaultUserLogin, "acl-target"); - CleanupAcl(seededCircleId, seededBlogPostId); - using var http = NewClient(_fixture.DefaultUserLogin); - - var payload = new PostAccessControlRulePayload - { - CircleId = seededCircleId, - BlogPostId = seededBlogPostId - }; - - var response = await http.PostAsJsonAsync( - BlogAclUrl(), payload, - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Created, response.StatusCode); - } - - /// - /// Payload templates for . - /// Each row carries the shape we want to POST; -1L and - /// -2L are negative sentinels that the test substitutes - /// with the ids of freshly seeded Circle / BlogPost - /// rows before sending, so every shape lands against a real - /// principal entity and the seeded fixtures are not dead. - /// - public static IEnumerable BlogAclPayloadsForNever500() - { - - // circleId only (the historical bug shape, 2026-08-21 mercure): - // must be rejected, never 500. - return new object[][] - { - [ - new PostAccessControlRulePayload - { - BlogPostId = -2, - CircleId = -1 - } - ], - [new PostAccessControlRulePayload - { - BlogPostId = 1, - CircleId = -1 - } - ], - [new PostAccessControlRulePayload - { - BlogPostId = 1, - CircleId = 1 - } - ] - } ; - } - - /// - /// Hard rule (Paul, 2026-08-21): a 500 is never acceptable - /// - [Theory] - [MemberData(nameof(BlogAclPayloadsForNever500))] - public async Task PostCircleAuthorization_never_returns_500(PostAccessControlRulePayload payload) - { - using var http = NewClient(_fixture.DefaultUserLogin); - - var response = await http.PostAsJsonAsync( - BlogAclUrl(), payload, - TestContext.Current.CancellationToken); - - Assert.NotEqual(HttpStatusCode.InternalServerError, response.StatusCode); - } - - [Fact] - async Task PostCircleAuthorization_dosent_return_500 () - { - _fixture.SeedUser(_fixture.DefaultUserLogin); - var seededCircleId = _fixture.SeedCircle(_fixture.DefaultUserLogin, "test-" + Guid.NewGuid().ToString("N")); - var seededBlogPostId = _fixture.SeedBlogPost(_fixture.DefaultUserLogin, "acl-never-500"); - CleanupAcl(seededCircleId, seededBlogPostId); - await PostCircleAuthorization_never_returns_500( - - new PostAccessControlRulePayload - { - BlogPostId = -1, - CircleId = seededCircleId - } - ); - - } - - [Fact] - async Task PostCircleAuthorization_dosent_return_500_on_success () - { - _fixture.SeedUser(_fixture.DefaultUserLogin); - var seededCircleId = _fixture.SeedCircle(_fixture.DefaultUserLogin, "test-" + Guid.NewGuid().ToString("N")); - var seededBlogPostId = _fixture.SeedBlogPost(_fixture.DefaultUserLogin, "acl-never-500-success"); - CleanupAcl(seededCircleId, seededBlogPostId); - await PostCircleAuthorization_never_returns_500( - - new PostAccessControlRulePayload - { - BlogPostId = seededBlogPostId, - CircleId = seededCircleId - } - ); - - } - - [Fact] - public async Task PostBlog_with_ACL_creates_a_post_and_Get_returns_it_in_the_list() - { - _fixture.SeedUser(_fixture.DefaultUserLogin); - _fixture.SeedUser("tester"); - var seededCircleId = _fixture.SeedCircle(_fixture.DefaultUserLogin, "test-" + Guid.NewGuid().ToString("N"), - false, - new String[] - { - _fixture.DefaultUserLogin, - "tester" - }); - var seededBlogPostId = _fixture.SeedBlogPost(_fixture.DefaultUserLogin, "acl-seeded-target"); - CleanupAcl(seededCircleId, seededBlogPostId); - using var http = NewClient(_fixture.DefaultUserLogin ); - - // Create a minimal BlogPost. The server assigns Id, so we - // send 0 + an explicit AuthorId; the production - // BlogSpotService.Create() tolerates that. - var draft = new BlogPost - { - Id = 0, - Title = "Premier billet", - AuthorId = "tester", - Article = "Contenu de test.", - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - ACL = new List( - new CircleAuthorizationToBlogPost[] - { - new CircleAuthorizationToBlogPost - { - CircleId = seededCircleId, - BlogPostId = seededBlogPostId - } - } - ) - }; - - var postResponse = await http.PostAsJsonAsync( - BlogUrl(), - draft, - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); - - // The POST returns the server-issued post (with a real Id). - var created = await postResponse.Content.ReadFromJsonAsync( - TestContext.Current.CancellationToken - ); - Assert.NotNull(created); - Assert.NotEqual(0, created!.Id); - Assert.Equal(draft.Title, created.Title); - - // The list should now contain exactly one entry. - var listResponse = await http.GetAsync( - BlogUrl(), - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); - - using var doc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken - )); - Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind); - Assert.True(doc.RootElement.GetArrayLength() >= 1); - Assert.Contains(doc.RootElement.EnumerateArray(), p => p.GetProperty("id").GetInt64() == created.Id); - - // detail should return the same post, with ACL and tags. - var detailResponse = await http.GetAsync( - $"{BlogUrl()}/{created.Id}", - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, detailResponse.StatusCode); - using var detailDoc = JsonDocument.Parse(await detailResponse.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken - )); - Assert.Equal(JsonValueKind.Object, detailDoc.RootElement.ValueKind); - Assert.Equal(created.Id, detailDoc.RootElement.GetProperty("id").GetInt64()); - Assert.True(detailDoc.RootElement.TryGetProperty("acl", out var acl)); - Assert.False(detailDoc.RootElement.TryGetProperty("ACL", out _)); - Assert.Equal(JsonValueKind.Array, acl.ValueKind); - Assert.Equal(1, acl.GetArrayLength()); - - var aclEntry = acl[0]; - Assert.Equal(JsonValueKind.Object, aclEntry.ValueKind); - Assert.True(aclEntry.TryGetProperty("circleId", out var returnedCircleId)); - Assert.Equal(seededCircleId, returnedCircleId.GetInt64()); - } - - [Fact] - public async Task Non_owner_can_read_restricted_post_but_receives_empty_acl_in_list_and_detail() - { - _fixture.SeedUser(_fixture.DefaultUserLogin); - _fixture.SeedUser("tester"); - var seededCircleId = _fixture.SeedCircle(_fixture.DefaultUserLogin, "test-" + Guid.NewGuid().ToString("N"), false, - new[] { _fixture.DefaultUserLogin, "tester" }); - - using var ownerHttp = NewClient(_fixture.DefaultUserLogin); - using var readerHttp = NewClient("tester"); - - var draft = new BlogPost - { - Id = 0, - Title = "ACL scrub test", - Article = "Visible to circle member", - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow - }; - - var postResponse = await ownerHttp.PostAsJsonAsync( - BlogUrl(), - draft, - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); - - var created = await postResponse.Content.ReadFromJsonAsync( - TestContext.Current.CancellationToken); - Assert.NotNull(created); - Assert.NotEqual(0, created!.Id); - - var grantResponse = await ownerHttp.PostAsJsonAsync( - BlogAclUrl(), - new PostAccessControlRulePayload - { - CircleId = seededCircleId, - BlogPostId = created.Id - }, - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.Created, grantResponse.StatusCode); - - var listResponse = await readerHttp.GetAsync( - BlogUrl(), - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); - - using var listDoc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken)); - Assert.Equal(JsonValueKind.Array, listDoc.RootElement.ValueKind); - foreach (var listed in listDoc.RootElement.EnumerateArray()) - { - var authorId = listed.GetProperty("authorId").GetString(); - if (string.Equals(authorId, "tester", StringComparison.Ordinal)) - continue; - - Assert.True(listed.TryGetProperty("acl", out var listedAcl)); - Assert.Equal(0, listedAcl.GetArrayLength()); - } - - var detailResponse = await readerHttp.GetAsync( - $"{BlogUrl()}/{created.Id}", - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, detailResponse.StatusCode); - - using var detailDoc = JsonDocument.Parse(await detailResponse.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken)); - Assert.True(detailDoc.RootElement.TryGetProperty("acl", out var detailAcl)); - Assert.Equal(0, detailAcl.GetArrayLength()); - } - -} diff --git a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs index 00e2ea61b..f02a1b998 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs @@ -8,13 +8,11 @@ using Microsoft.IdentityModel.Tokens; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Tests.Shared; -using Yavsc.Blogs.Tests.Fixtures; namespace Yavsc.Blogs.Tests; [Collection("JwtClaimMapping")] -public sealed class BlogApiMappedClaimsTests : -IClassFixture +public sealed class BlogApiMappedClaimsTests : IClassFixture { private readonly MappedClaimsBlogsWebServerFixture _fixture; @@ -81,13 +79,10 @@ IClassFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync( - _fixture.BlogSpotUrl(), - draft, - TestContext.Current.CancellationToken); + var response = await http.PostAsJsonAsync("/api/v1/blog", draft); Assert.Equal(HttpStatusCode.Created, response.StatusCode); - var created = await response.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); + var created = await response.Content.ReadFromJsonAsync(); Assert.NotNull(created); Assert.Equal("mapped-user", created!.AuthorId); } @@ -98,7 +93,7 @@ IClassFixture ResetDatabase(); using var http = NewClient(subject: "mapped-owner"); - var createdResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), new BlogPost + var createdResponse = await http.PostAsJsonAsync("/api/v1/blog", new BlogPost { Id = 0, Title = "Billet à modifier", @@ -106,13 +101,13 @@ IClassFixture Article = "Contenu initial.", DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow - }, TestContext.Current.CancellationToken); + }); Assert.Equal(HttpStatusCode.Created, createdResponse.StatusCode); - var created = await createdResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); + var created = await createdResponse.Content.ReadFromJsonAsync(); Assert.NotNull(created); - var updateResponse = await http.PutAsJsonAsync(_fixture.BlogSpotUrl() + $"/{created!.Id}", new BlogPost + var updateResponse = await http.PutAsJsonAsync($"/api/v1/blog/{created!.Id}", new BlogPost { Id = created.Id, Title = "Billet modifié", @@ -120,7 +115,7 @@ IClassFixture Article = "Contenu mis à jour.", DateCreated = created.DateCreated, DateModified = DateTime.UtcNow - }, TestContext.Current.CancellationToken); + }); Assert.Equal(HttpStatusCode.NoContent, updateResponse.StatusCode); } @@ -131,7 +126,7 @@ IClassFixture ResetDatabase(); using var ownerHttp = NewClient(subject: "mapped-owner"); - var createdResponse = await ownerHttp.PostAsJsonAsync(_fixture.BlogSpotUrl(), new BlogPost + var createdResponse = await ownerHttp.PostAsJsonAsync("/api/v1/blog", new BlogPost { Id = 0, Title = "Billet protégé", @@ -139,14 +134,14 @@ IClassFixture Article = "Contenu initial.", DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow - }, TestContext.Current.CancellationToken); + }); Assert.Equal(HttpStatusCode.Created, createdResponse.StatusCode); - var created = await createdResponse.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); + var created = await createdResponse.Content.ReadFromJsonAsync(); Assert.NotNull(created); using var otherHttp = NewClient(subject: "mapped-other"); - var updateResponse = await otherHttp.PutAsJsonAsync(_fixture.BlogSpotUrl() + $"/{created!.Id}", new BlogPost + var updateResponse = await otherHttp.PutAsJsonAsync($"/api/v1/blog/{created!.Id}", new BlogPost { Id = created.Id, Title = "Tentative de modification", @@ -154,7 +149,7 @@ IClassFixture Article = "Contenu non autorisé.", DateCreated = created.DateCreated, DateModified = DateTime.UtcNow - }, TestContext.Current.CancellationToken); + }); Assert.Equal(HttpStatusCode.Unauthorized, updateResponse.StatusCode); } diff --git a/src/Yavsc.Blogs.Tests/BlogApiSmokeTests.cs b/src/Yavsc.Blogs.Tests/BlogApiSmokeTests.cs index 2fd42e2fe..b84e75b66 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiSmokeTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiSmokeTests.cs @@ -1,4 +1,7 @@ +using System.Net; +using System.Net.Http; using Microsoft.Extensions.DependencyInjection; +using Yavsc.Tests.Shared; namespace Yavsc.Blogs.Tests; @@ -9,7 +12,6 @@ namespace Yavsc.Blogs.Tests; /// surface. The first behavioural test (GET /api/v1/blog returns /// 200) lands in a follow-up commit. /// -[Collection("Yavsc Blogs")] public sealed class BlogApiSmokeTests : IClassFixture { private readonly BlogsWebServerFixture _fixture; diff --git a/src/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs index 9d4f6ad81..cc7aaec80 100644 --- a/src/Yavsc.Blogs.Tests/BlogApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogApiTests.cs @@ -1,5 +1,5 @@ using System.Net; -using System.Net.Http.Headers; +using System.Net.Http; using System.Net.Http.Json; using System.Security.Claims; using System.Text.Json; @@ -8,7 +8,6 @@ using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Server.Helpers; using Yavsc.Tests.Shared; -using Yavsc.Blogs.Tests.Fixtures; namespace Yavsc.Blogs.Tests; @@ -23,7 +22,7 @@ namespace Yavsc.Blogs.Tests; /// header (or sending a token signed with the wrong key) gets a /// 401 back from the framework. /// -[Collection("Yavsc Blogs")] +[Collection("JwtClaimMapping")] public sealed class BlogApiTests : IClassFixture { private readonly BlogsWebServerFixture _fixture; @@ -33,22 +32,27 @@ public sealed class BlogApiTests : IClassFixture _fixture = fixture; } - - /// Reset the database and seed the - /// tester row. Required - /// for any test that POST/PUT/DELETE a BlogPost: - /// BlogPost.AuthorId is a FK to - /// AspNetUsers.Id, and SQLite (unlike the EF Core - /// InMemory provider) enforces it. Without the seed, the - /// POST handler hits - /// SQLite Error 19: 'FOREIGN KEY constraint failed' - /// at SaveChanges and the controller returns 500. - private void ResetAndSeedDefaultUser() + /// Reset the in-memory database to a known empty state. + /// UseInMemoryDatabase shares its store across the + /// lifetime of the instance, + /// so without a per-test reset the test order would leak + /// state between tests. + private void ResetDatabase() { - _fixture.ResetDatabase(); - _fixture.SeedUser("tester"); + using var scope = _fixture.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.EnsureDeleted(); + db.Database.EnsureCreated(); } + /// The fixture's WebApplication is bound to + /// https://localhost:<random> via + /// . We pick the first + /// https URL and append the controller route + /// (/api/v1/blog, matching the production + /// [Route(APIPrefix + "/blog")]). + private string BlogsUrl => + _fixture.Addresses.First(a => a.StartsWith("https://")) + "/api/v1/blog"; /// Build an authenticated client: a real /// Authorization: Bearer <jwt> header where the JWT @@ -90,26 +94,17 @@ public sealed class BlogApiTests : IClassFixture }; } - private int CountAttachmentsForPost(long postId) - { - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - return db.BlogAttachedFiles.Count(a => a.PostId == postId); - } - [Fact] public async Task GetBlogs_returns_200_with_empty_list_when_no_posts() { - _fixture.ResetDatabase(); + ResetDatabase(); using var http = NewClient(); - var response = await http.GetAsync( - _fixture.BlogSpotUrl(), - TestContext.Current.CancellationToken); + var response = await http.GetAsync("/api/v1/blog"); Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); + var body = await response.Content.ReadAsStringAsync(); // Empty table → empty JSON array. We compare as a JsonDocument // so a future change in formatting (whitespace, indentation) // doesn't break the assertion. @@ -121,7 +116,7 @@ public sealed class BlogApiTests : IClassFixture [Fact] public async Task PostBlog_creates_a_post_and_Get_returns_it_in_the_list() { - ResetAndSeedDefaultUser(); + ResetDatabase(); using var http = NewClient(); // Create a minimal BlogPost. The server assigns Id, so we @@ -137,26 +132,20 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, - TestContext.Current.CancellationToken); + var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); // The POST returns the server-issued post (with a real Id). - var created = await postResponse.Content.ReadFromJsonAsync( - TestContext.Current.CancellationToken - ); + var created = await postResponse.Content.ReadFromJsonAsync(); Assert.NotNull(created); Assert.NotEqual(0, created!.Id); Assert.Equal(draft.Title, created.Title); // The list should now contain exactly one entry. - var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), - TestContext.Current.CancellationToken); + var listResponse = await http.GetAsync("/api/v1/blog"); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); - using var doc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken - )); + using var doc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync()); Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind); Assert.Equal(1, doc.RootElement.GetArrayLength()); Assert.Equal(created.Id, doc.RootElement[0].GetProperty("id").GetInt64()); @@ -165,7 +154,7 @@ public sealed class BlogApiTests : IClassFixture [Fact] public async Task PostBlog_sets_AuthorId_on_created_post_and_list_entry() { - ResetAndSeedDefaultUser(); + ResetDatabase(); using var http = NewClient(subject: "tester"); var draft = new BlogPost @@ -178,23 +167,17 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, - TestContext.Current.CancellationToken); + var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); - var created = await postResponse.Content.ReadFromJsonAsync( - TestContext.Current.CancellationToken - ); + var created = await postResponse.Content.ReadFromJsonAsync(); Assert.NotNull(created); Assert.Equal("tester", created!.AuthorId); - var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), - TestContext.Current.CancellationToken); + var listResponse = await http.GetAsync("/api/v1/blog"); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); - using var doc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken - )); + using var doc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync()); Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind); Assert.Equal(1, doc.RootElement.GetArrayLength()); Assert.Equal("tester", doc.RootElement[0].GetProperty("authorId").GetString()); @@ -203,7 +186,7 @@ public sealed class BlogApiTests : IClassFixture [Fact] public async Task PostBlogComment_returns_201_for_existing_post() { - ResetAndSeedDefaultUser(); + ResetDatabase(); using var http = NewClient(subject: "tester"); var draft = new BlogPost @@ -216,27 +199,21 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, - TestContext.Current.CancellationToken); + var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); - var createdPost = await postResponse.Content.ReadFromJsonAsync( - TestContext.Current.CancellationToken - ); + var createdPost = await postResponse.Content.ReadFromJsonAsync(); Assert.NotNull(createdPost); - Thread.Sleep(100); + var commentResponse = await http.PostAsJsonAsync("/api/v1/blogcomments", new { Article = "Premier commentaire", ReceiverId = createdPost!.Id - }, TestContext.Current.CancellationToken); + }); Assert.Equal(HttpStatusCode.Created, commentResponse.StatusCode); - using var doc = JsonDocument.Parse( - await commentResponse.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken - )); + using var doc = JsonDocument.Parse(await commentResponse.Content.ReadAsStringAsync()); Assert.True(doc.RootElement.TryGetProperty("id", out var id)); Assert.True(id.GetInt64() > 0); Assert.True(doc.RootElement.TryGetProperty("dateCreated", out _)); @@ -256,7 +233,7 @@ public sealed class BlogApiTests : IClassFixture [Fact] public async Task GetBlog_returns_401_when_no_token_is_provided() { - _fixture.ResetDatabase(); + ResetDatabase(); using var http = NewAnonymousClient(); // No Authorization header → the JwtBearer middleware @@ -265,15 +242,14 @@ public sealed class BlogApiTests : IClassFixture // the framework returns 401. This is the proof that the // production policy is wired in the test host and not // short-circuited by a test-only auth bypass. - var response = await http.GetAsync(_fixture.BlogSpotUrl(), - TestContext.Current.CancellationToken); + var response = await http.GetAsync("/api/v1/blog"); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } [Fact] public async Task PutBlog_with_valid_token_and_owner_returns_204_and_Get_reflects_update() { - ResetAndSeedDefaultUser(); + ResetDatabase(); // The JWT's sub must match the post's AuthorId: // PermissionHandler.IsOwner checks blog.AuthorId == user.GetUserId(), // and UserHelpers.GetUserId reads "sub" off the principal. @@ -293,13 +269,10 @@ public sealed class BlogApiTests : IClassFixture DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, - TestContext.Current.CancellationToken); + var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); - var created = (await postResponse.Content.ReadFromJsonAsync( - TestContext.Current.CancellationToken - ))!; + var created = (await postResponse.Content.ReadFromJsonAsync())!; // PUT with the server-issued Id; the controller rejects // mismatched id/blog.Id with 400, so we keep them aligned. @@ -312,148 +285,22 @@ public sealed class BlogApiTests : IClassFixture DateCreated = created.DateCreated, DateModified = DateTime.UtcNow }; - var putResponse = await http.PutAsJsonAsync(_fixture.BlogSpotUrl()+$"/{created.Id}", - update, - TestContext.Current.CancellationToken); + var putResponse = await http.PutAsJsonAsync($"/api/v1/blog/{created.Id}", update); Assert.Equal(HttpStatusCode.NoContent, putResponse.StatusCode); // The list should now reflect the new title. - var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), - TestContext.Current.CancellationToken); + var listResponse = await http.GetAsync("/api/v1/blog"); Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode); - using var doc = JsonDocument.Parse( - await listResponse.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken - )); + using var doc = JsonDocument.Parse(await listResponse.Content.ReadAsStringAsync()); Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind); Assert.Equal(1, doc.RootElement.GetArrayLength()); Assert.Equal("Après", doc.RootElement[0].GetProperty("title").GetString()); } - [Fact] - public async Task PutBlog_multipart_with_blog_and_file_returns_204_and_persists_attachment() - { - ResetAndSeedDefaultUser(); - using var http = NewClient(subject: "tester"); - - var previousRoot = AbstractFileSystemHelpers.UserFilesDirName; - var tempRoot = Path.Combine(Path.GetTempPath(), "yavsc-blogs-tests-files-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(tempRoot); - AbstractFileSystemHelpers.UserFilesDirName = tempRoot; - - try - { - var draft = new BlogPost - { - Id = 0, - Title = "Initial", - AuthorId = "tester", - Article = "Contenu initial.", - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow - }; - - var postResponse = await http.PostAsJsonAsync( - _fixture.BlogSpotUrl(), - draft, - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); - - var created = (await postResponse.Content.ReadFromJsonAsync( - TestContext.Current.CancellationToken))!; - - var update = new BlogPost - { - Id = created.Id, - Title = "Mis a jour via multipart", - AuthorId = created.AuthorId, - Article = "Contenu mis a jour.", - DateCreated = created.DateCreated, - DateModified = DateTime.UtcNow - }; - - var form = new MultipartFormDataContent(); - form.Add(new StringContent(JsonSerializer.Serialize(update)), "blog"); - - var fileBytes = System.Text.Encoding.UTF8.GetBytes("payload test"); - var fileContent = new ByteArrayContent(fileBytes); - fileContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); - form.Add(fileContent, "file", "note.txt"); - - using var request = new HttpRequestMessage( - HttpMethod.Put, - _fixture.BlogSpotUrl() + $"/{created.Id}") - { - Content = form - }; - - var putResponse = await http.SendAsync(request, TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.NoContent, putResponse.StatusCode); - - var detailsResponse = await http.GetAsync( - _fixture.BlogSpotUrl() + $"/{created.Id}", - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, detailsResponse.StatusCode); - - using var detailsDoc = JsonDocument.Parse( - await detailsResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); - Assert.Equal("Mis a jour via multipart", detailsDoc.RootElement.GetProperty("title").GetString()); - - Assert.True(CountAttachmentsForPost(created.Id) >= 1); - } - finally - { - AbstractFileSystemHelpers.UserFilesDirName = previousRoot; - try { Directory.Delete(tempRoot, recursive: true); } catch { } - } - } - - [Fact] - public async Task PutBlog_multipart_without_blog_field_returns_400() - { - ResetAndSeedDefaultUser(); - using var http = NewClient(subject: "tester"); - - var draft = new BlogPost - { - Id = 0, - Title = "Initial", - AuthorId = "tester", - Article = "Contenu initial.", - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow - }; - - var postResponse = await http.PostAsJsonAsync( - _fixture.BlogSpotUrl(), - draft, - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); - - var created = (await postResponse.Content.ReadFromJsonAsync( - TestContext.Current.CancellationToken))!; - - var form = new MultipartFormDataContent(); - var fileBytes = System.Text.Encoding.UTF8.GetBytes("payload test"); - var fileContent = new ByteArrayContent(fileBytes); - fileContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); - form.Add(fileContent, "file", "note.txt"); - - using var request = new HttpRequestMessage( - HttpMethod.Put, - _fixture.BlogSpotUrl() + $"/{created.Id}") - { - Content = form - }; - - var putResponse = await http.SendAsync(request, TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.BadRequest, putResponse.StatusCode); - } - [Fact] public async Task DeleteBlog_removes_a_post_and_Get_returns_an_empty_list() { - ResetAndSeedDefaultUser(); + ResetDatabase(); using var http = NewClient(); // Seed a post we can delete. @@ -466,23 +313,15 @@ public sealed class BlogApiTests : IClassFixture DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - var postResponse = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, - TestContext.Current.CancellationToken); - var created = (await postResponse.Content.ReadFromJsonAsync( - TestContext.Current.CancellationToken - ))!; + var postResponse = await http.PostAsJsonAsync("/api/v1/blog", draft); + var created = (await postResponse.Content.ReadFromJsonAsync())!; - var deleteResponse = await http.DeleteAsync(_fixture.BlogSpotUrl()+$"/{created.Id}", - TestContext.Current.CancellationToken - ); + var deleteResponse = await http.DeleteAsync($"/api/v1/blog/{created.Id}"); Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode); // The list should now be empty. - var listResponse = await http.GetAsync(_fixture.BlogSpotUrl(), - TestContext.Current.CancellationToken); - String response = await listResponse.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken - ); + var listResponse = await http.GetAsync("/api/v1/blog"); + String response = await listResponse.Content.ReadAsStringAsync(); using var doc = JsonDocument.Parse(response); Assert.Equal(0, doc.RootElement.GetArrayLength()); } @@ -503,7 +342,7 @@ public sealed class BlogApiTests : IClassFixture // ModelState validation starts rejecting the PostIt payload // (missing field, wrong casing, etc.), this test fails // before the regression reaches a user. - ResetAndSeedDefaultUser(); + ResetDatabase(); using var http = NewClient(subject: "tester"); // Mirrors what MainPageViewModel.Save builds: a BlogPost with @@ -521,8 +360,7 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, - TestContext.Current.CancellationToken); + var response = await http.PostAsJsonAsync("/api/v1/blog", draft); // Dump the body on failure so the test name + the response // payload are enough to start a fix; the framework's @@ -530,9 +368,7 @@ public sealed class BlogApiTests : IClassFixture // Created, got BadRequest"). if (response.StatusCode != HttpStatusCode.Created) { - var body = await response.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken - ); + var body = await response.Content.ReadAsStringAsync(); Assert.Fail(string.Format("Expected 201 Created, got {0} {1}. Body: {2}", (int)response.StatusCode, response.StatusCode, body)); } } @@ -553,7 +389,7 @@ public sealed class BlogApiTests : IClassFixture // behaviour so a future change that, say, makes Title // nullable in the model or drops [Required], triggers a // conscious update of the test (and probably of the VM). - _fixture.ResetDatabase(); + ResetDatabase(); using var http = NewClient(subject: "tester"); var draft = new BlogPost @@ -566,14 +402,11 @@ public sealed class BlogApiTests : IClassFixture DateModified = DateTime.UtcNow }; - var response = await http.PostAsJsonAsync(_fixture.BlogSpotUrl(), draft, - TestContext.Current.CancellationToken); + var response = await http.PostAsJsonAsync("/api/v1/blog", draft); if (response.StatusCode != HttpStatusCode.BadRequest) { - var body = await response.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken - ); + var body = await response.Content.ReadAsStringAsync(); Assert.Fail(string.Format("Expected 400 BadRequest (empty Title is invalid), got {0} {1}. Body: {2}", (int)response.StatusCode, response.StatusCode, body)); } } diff --git a/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs new file mode 100644 index 000000000..1e610082a --- /dev/null +++ b/src/Yavsc.Blogs.Tests/BlogsWebServerFixture.cs @@ -0,0 +1,183 @@ +using System.Text; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using Yavsc.Blogs.Controllers; +using Yavsc.Models; +using Yavsc.Services; +using Yavsc.Tests.Shared; + +namespace Yavsc.Blogs.Tests; + +/// +/// Test host for the Yavsc.Blogs API surface. Specialisation of +/// that wires up only the bits the +/// blog API actually depends on: +/// +/// +/// An in-memory +/// (the real one — no mock) so BlogSpotService.Index can run +/// against an empty table and return an empty list. +/// A trivial +/// stub: the GET index path doesn't read the file system, so any +/// implementation is fine. +/// The real BlogSpotService, which calls +/// IAuthorizationService.AuthorizeAsync(user, blog, new EditPermission()) +/// on PUT. The fixture registers the real +/// so the resource-based ownership +/// check runs end-to-end; tests that want a 204 PUT must sign a +/// JWT whose sub matches the post's AuthorId. +/// A real AddJwtBearer with HS256, +/// sharing its with the +/// token issuer. The production OIDC discovery path is bypassed: +/// the test host validates tokens locally, against the static +/// signing key, so no IdP is required to exercise auth. +/// The production BlogScope policy +/// (RequireAuthenticatedUser + RequireClaim("scope", "blogs")) +/// registered verbatim. Tests that omit the bearer header exercise +/// the unauthenticated path and get 401. +/// +/// +/// No IdentityServer, no SMTP, no static assets — the Org fixture +/// owns all of that and we don't need any of it for blog integration +/// tests. +/// +public sealed class BlogsWebServerFixture : WebHostFixture +{ + protected override int HttpsPort => 5103; + + private InMemoryDatabaseRoot? _inMemoryRoot; + + protected override WebApplication BuildApp(WebApplicationBuilder builder) + { + // Use the real ApplicationDbContext with an in-memory store. + // BlogSpotService reads _context.BlogSpot directly, so any + // attempt to mock it would be wasted work; the real service + // against an empty table returns an empty list, which is + // exactly what the first test wants to assert. + // + // Share a single InMemoryDatabaseRoot across the test + // lifetime so POST + GET on the same fixture see the same + // store. Without the root, EF Core's In-Memory provider + // creates independent stores per DbContext in some + // configurations, and the second request would see an + // empty list even after the first wrote a row. + _inMemoryRoot = new InMemoryDatabaseRoot(); + builder.Services.AddDbContext(opt => + opt.UseInMemoryDatabase("Yavsc.Blogs.Tests", _inMemoryRoot)); + + // Trivial file-system auth: the GET index path never calls + // into it, but the DI container needs an instance. + builder.Services.AddSingleton( + new NoopFileSystemAuthManager()); + + // Real BlogSpotService — same instance the production host + // builds (ApplicationDbContext, IAuthorizationService, + // IFileSystemAuthManager). With PermissionHandler registered + // below, Modify() now answers "is the caller the author of + // the post?" for real, which is exactly what we want to + // assert in the PUT tests. + builder.Services.AddScoped(); + + // The real PermissionHandler: BlogSpotService calls + // IAuthorizationService.AuthorizeAsync(user, blog, new + // EditPermission()) on Modify, and PermissionHandler + // resolves it via IsOwner(user, blog) — i.e. blog.AuthorId + // == user.GetUserId(). To PUT a post, the test JWT must + // carry sub == post.AuthorId. + builder.Services.AddScoped(); + + // The BlogApiController is reached through MVC. AddControllers() + // by default scans the test assembly only; we explicitly add the + // Yavsc.Blogs application part so the controller is discovered + // and routed. + builder.Services.AddControllers() + .AddApplicationPart(typeof(BlogApiController).Assembly); + + // Production BlogScope policy, verbatim. Two requirements: + // 1. RequireAuthenticatedUser: a request with no bearer + // token (or an invalid one) will be rejected. + // 2. RequireClaim("scope", "blogs"): the JWT must carry a + // "scope" claim whose value is "blogs". + // TestTokenIssuer.Issue() defaults to scope=blogs; the + // GetBlog_returns_401_when_no_token test omits the token + // entirely and asserts the policy fails closed. + builder.Services.AddAuthorization(opt => + { + opt.AddPolicy("BlogScope", policy => + { + policy.RequireAuthenticatedUser() + .RequireClaim("scope", "blogs"); + }); + }); + + // Real JWT Bearer authentication, sharing the signing key + // with TestTokenIssuer. No Authority → no OIDC discovery, + // no IdP roundtrip; the middleware validates the signature + // and the standard claims against the static configuration + // below. Production uses AddYavscJwtBearer with an IdP, but + // for the unit-test host that path is unwanted coupling. + builder.Services.AddAuthentication("Bearer") + .AddJwtBearer("Bearer", options => + { + options.IncludeErrorDetails = true; + // MapInboundClaims = false here mirrors the + // JwtSecurityTokenHandler.DefaultInboundClaimTypeMap + // .Clear() in TestTokenIssuer: the validation + // pipeline must not rewrite "sub" to + // ClaimTypes.NameIdentifier, otherwise the + // PermissionHandler ownership check sees a null + // user id and rejects every PUT. + options.MapInboundClaims = false; + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = TestTokenIssuer.Issuer, + ValidateAudience = false, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + IssuerSigningKey = TestTokenIssuer.SigningKey, + // "sub" stays "sub" (MapInboundClaims only + // remaps long Microsoft claim URIs, not sub). + // UserHelpers.GetUserId reads sub directly. + NameClaimType = "sub", + RoleClaimType = YavscConstants.RoleClaimType, + }; + }); + + return builder.Build(); + } + + protected override async Task ConfigurePipelineAsync(WebApplication app) + { + // UseDeveloperExceptionPage gives full stack traces on + // 500s during tests — much easier to debug than the + // default empty InternalServerError body. Production + // (Yavsc.Org) wires its own exception handler; this + // fixture is test-only. + app.UseDeveloperExceptionPage(); + app.UseRouting(); + app.UseAuthentication(); + app.UseAuthorization(); + app.MapControllers(); + await Task.CompletedTask; + return app; + } + + /// Trivial stub. The + /// blog API endpoints exercised by the first tests don't read the + /// file system, so the implementation can be a no-op. + private sealed class NoopFileSystemAuthManager : IFileSystemAuthManager + { + public FileAccessRight GetFilePathAccess(System.Security.Claims.ClaimsPrincipal user, string fileRelativePath) + => FileAccessRight.None; + + public void SetAccess(long circleId, string normalizedFullPath, FileAccessRight access) + { + } + } +} diff --git a/src/Yavsc.Blogs.Tests/CircleMembersApiTests.cs b/src/Yavsc.Blogs.Tests/CircleMembersApiTests.cs index a7c3001b4..4e9bfb00f 100644 --- a/src/Yavsc.Blogs.Tests/CircleMembersApiTests.cs +++ b/src/Yavsc.Blogs.Tests/CircleMembersApiTests.cs @@ -1,11 +1,11 @@ using System.Net; +using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Yavsc.Models; using Yavsc.Models.Relationship; using Yavsc.Tests.Shared; -using static Yavsc.Constants; namespace Yavsc.Blogs.Tests; @@ -88,7 +88,7 @@ public sealed class CircleMembersApiTests : IClassFixture } private string MembersUrl(long circleId) - => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/{APIPrefix}/circle/{circleId}/members"; + => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/api/circle/{circleId}/members"; private HttpClient NewClient(string subject) { @@ -113,10 +113,10 @@ public sealed class CircleMembersApiTests : IClassFixture var circleId = SeedCircle("alice", "Famille"); using var http = NewClient("alice"); - var response = await http.GetAsync(MembersUrl(circleId), TestContext.Current.CancellationToken); + var response = await http.GetAsync(MembersUrl(circleId)); Assert.Equal(HttpStatusCode.OK, response.StatusCode); - using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); + using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind); Assert.Equal(0, doc.RootElement.GetArrayLength()); } @@ -130,14 +130,14 @@ public sealed class CircleMembersApiTests : IClassFixture var postResponse = await http.PostAsJsonAsync( MembersUrl(circleId), - new { userId = "bob" }, TestContext.Current.CancellationToken); + new { userId = "bob" }); Assert.Equal(HttpStatusCode.Created, postResponse.StatusCode); - var getResponse = await http.GetAsync(MembersUrl(circleId), TestContext.Current.CancellationToken); + var getResponse = await http.GetAsync(MembersUrl(circleId)); Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode); - using var doc = JsonDocument.Parse(await getResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); + using var doc = JsonDocument.Parse(await getResponse.Content.ReadAsStringAsync()); Assert.Equal(JsonValueKind.Array, doc.RootElement.ValueKind); Assert.Equal(1, doc.RootElement.GetArrayLength()); var member = doc.RootElement[0]; @@ -155,12 +155,12 @@ public sealed class CircleMembersApiTests : IClassFixture var first = await http.PostAsJsonAsync( MembersUrl(circleId), - new { userId = "bob" }, TestContext.Current.CancellationToken); + new { userId = "bob" }); Assert.Equal(HttpStatusCode.Created, first.StatusCode); var second = await http.PostAsJsonAsync( MembersUrl(circleId), - new { userId = "bob" }, TestContext.Current.CancellationToken); + new { userId = "bob" }); Assert.Equal(HttpStatusCode.Conflict, second.StatusCode); } @@ -171,14 +171,14 @@ public sealed class CircleMembersApiTests : IClassFixture var circleId = SeedCircle("alice", "Famille"); using var http = NewClient("alice"); - await http.PostAsJsonAsync(MembersUrl(circleId), new { userId = "bob" }, TestContext.Current.CancellationToken); + await http.PostAsJsonAsync(MembersUrl(circleId), new { userId = "bob" }); var deleteResponse = await http.DeleteAsync( - $"{MembersUrl(circleId)}/bob", TestContext.Current.CancellationToken); + $"{MembersUrl(circleId)}/bob"); Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode); - var getResponse = await http.GetAsync(MembersUrl(circleId), TestContext.Current.CancellationToken); - using var doc = JsonDocument.Parse(await getResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); + var getResponse = await http.GetAsync(MembersUrl(circleId)); + using var doc = JsonDocument.Parse(await getResponse.Content.ReadAsStringAsync()); Assert.Equal(0, doc.RootElement.GetArrayLength()); } @@ -190,7 +190,7 @@ public sealed class CircleMembersApiTests : IClassFixture var circleId = SeedCircle("alice", "Famille"); using var http = NewClient("bob"); - var response = await http.GetAsync(MembersUrl(circleId), TestContext.Current.CancellationToken); + var response = await http.GetAsync(MembersUrl(circleId)); // 404, not 403 — the controller deliberately avoids leaking // the existence of someone else's circle. diff --git a/src/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs deleted file mode 100644 index e0606f5d7..000000000 --- a/src/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs +++ /dev/null @@ -1,394 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Builder; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Tokens; -using Yavsc.Blogs.Controllers; -using Yavsc.Models; -using Yavsc.Models.Blog; -using Yavsc.Models.Relationship; -using Yavsc.Services; -using Yavsc.Tests.Shared; -using static Yavsc.Constants; -namespace Yavsc.Blogs.Tests; - -/// -/// Shared integration-test host for the Yavsc.Blogs API surface. -/// Specialisation of that wires up -/// only the bits the blog API actually depends on: -/// -/// -/// A SQLite :memory: database -/// () backed -/// by a single shared held open -/// for the lifetime of the host. SQLite enforces real foreign -/// keys and real transactional semantics, so the tests see the -/// same INSERT-time FK validation a production Postgres host -/// would — unlike the EF Core InMemory provider, which silently -/// ignores FKs and masks bugs that surface only against a real -/// relational engine. -/// A trivial -/// stub: the GET index path doesn't read the file system, so any -/// implementation is fine. -/// The real BlogSpotService, which calls -/// IAuthorizationService.AuthorizeAsync(user, blog, new EditPermission()) -/// on PUT. The fixture registers the real -/// so the resource-based ownership -/// check runs end-to-end; tests that want a 204 PUT must sign a -/// JWT whose sub matches the post's AuthorId. -/// A real AddJwtBearer with HS256, -/// sharing its with the -/// token issuer. The production OIDC discovery path is bypassed: -/// the test host validates tokens locally, against the static -/// signing key, so no IdP is required to exercise auth. -/// The production BlogScope policy -/// (RequireAuthenticatedUser + RequireClaim("scope", "blogs")) -/// registered verbatim. Tests that omit the bearer header exercise -/// the unauthenticated path and get 401. -/// -/// -/// No IdentityServer, no SMTP, no static assets — the Org fixture -/// owns all of that and we don't need any of it for blog integration -/// tests. Marked so the -/// host is shared across every [Collection("Yavsc Blogs")] -/// test class: one host, one SQLite DB, one Kestrel port. -/// -[CollectionDefinition("Yavsc Blogs")] -public sealed class BlogsWebServerFixture : WebHostFixture -{ - protected override int HttpsPort => 5103; - - public long CircleId { get; private set; } - public long PostId { get; private set; } - public string DefaultUserLogin { get => "alice"; } - - // A single SqliteConnection held open at the static level, - // mirroring how Yavsc.Org.Tests.WebServerFixture hoists its - // shared configuration into static slots. Closing the - // connection destroys the in-memory database — so we close - // it only when the last fixture instance is disposed (see - // Dispose below), exactly when WebHostFixture tears down the - // host. - private static SqliteConnection? _sharedSqliteConnection; - private static readonly object _sqliteLock = new(); - - protected override WebApplication BuildApp(WebApplicationBuilder builder) - { - // Open the shared in-memory connection lazily on the first - // fixture construction. Subsequent constructions (xUnit - // creates one fixture instance per IClassFixture) reuse - // the same connection so all DbContexts across all tests - // see the same database. - SqliteConnection sharedConnection; - lock (_sqliteLock) - { - if (_sharedSqliteConnection is null) - { - // Mode=Memory + Cache=Shared gives us a named - // in-memory database that every connection string - // referencing "File:YavscBlogsTests?mode=memory&cache=shared" - // will resolve to the same backing store, as long - // as at least one SqliteConnection stays open - // against it. - _sharedSqliteConnection = new SqliteConnection( - "Data Source=YavscBlogsTests;Mode=Memory;Cache=Shared"); - _sharedSqliteConnection.Open(); - } - sharedConnection = _sharedSqliteConnection; - } - - builder.Services.AddDbContext(opt => - // UseSqlite(DbConnection) keeps the connection we just - // opened alive for the DbContext's lifetime, instead of - // letting EF open and close its own. Without this, - // each DbContext would get a fresh connection pointing - // at an empty :memory: store and nothing would persist - // across requests. - opt.UseSqlite(sharedConnection)); - - // Trivial file-system auth: the GET index path never calls - // into it, but the DI container needs an instance. - builder.Services.AddSingleton( - new NoopFileSystemAuthManager()); - - // Real BlogSpotService — same instance the production host - // builds (ApplicationDbContext, IAuthorizationService, - // IFileSystemAuthManager). With PermissionHandler registered - // below, Modify() now answers "is the caller the author of - // the post?" for real, which is exactly what we want to - // assert in the PUT tests. - builder.Services.AddScoped(); - - // The real PermissionHandler: BlogSpotService calls - // IAuthorizationService.AuthorizeAsync(user, blog, new - // EditPermission()) on Modify, and PermissionHandler - // resolves it via IsOwner(user, blog) — i.e. blog.AuthorId - // == user.GetUserId(). To PUT a post, the test JWT must - // carry sub == post.AuthorId. - builder.Services.AddScoped(); - - // The BlogApiController is reached through MVC. AddControllers() - // by default scans the test assembly only; we explicitly add the - // Yavsc.Blogs application part so the controller is discovered - // and routed. - builder.Services.AddControllers() - .AddApplicationPart(typeof(BlogApiController).Assembly); - - // Production BlogScope policy, verbatim. Two requirements: - // 1. RequireAuthenticatedUser: a request with no bearer - // token (or an invalid one) will be rejected. - // 2. RequireClaim("scope", "blogs"): the JWT must carry a - // "scope" claim whose value is "blogs". - // TestTokenIssuer.Issue() defaults to scope=blogs; the - // GetBlog_returns_401_when_no_token test omits the token - // entirely and asserts the policy fails closed. - builder.Services.AddAuthorization(opt => - { - opt.AddPolicy("BlogScope", policy => - { - policy.RequireAuthenticatedUser() - .RequireClaim("scope", "blogs"); - }); - }); - - // Real JWT Bearer authentication, sharing the signing key - // with TestTokenIssuer. No Authority → no OIDC discovery, - // no IdP roundtrip; the middleware validates the signature - // and the standard claims against the static configuration - // below. Production uses AddYavscJwtBearer with an IdP, but - // for the unit-test host that path is unwanted coupling. - builder.Services.AddAuthentication("Bearer") - .AddJwtBearer("Bearer", options => - { - options.IncludeErrorDetails = true; - // MapInboundClaims = false here mirrors the - // JwtSecurityTokenHandler.DefaultInboundClaimTypeMap - // .Clear() in TestTokenIssuer: the validation - // pipeline must not rewrite "sub" to - // ClaimTypes.NameIdentifier, otherwise the - // PermissionHandler ownership check sees a null - // user id and rejects every PUT. - options.MapInboundClaims = false; - options.TokenValidationParameters - = new TokenValidationParameters - { - ValidateIssuer = true, - ValidIssuer = TestTokenIssuer.Issuer, - ValidateAudience = false, - ValidateLifetime = true, - ValidateIssuerSigningKey = true, - IssuerSigningKey = TestTokenIssuer.SigningKey, - // "sub" stays "sub" (MapInboundClaims only - // remaps long Microsoft claim URIs, not sub). - // UserHelpers.GetUserId reads sub directly. - NameClaimType = "sub", - RoleClaimType = Yavsc.Constants.RoleClaimType, - }; - }); - - return builder.Build(); - } - - protected override async Task ConfigurePipelineAsync(WebApplication app) - { - // UseDeveloperExceptionPage gives full stack traces on - // 500s during tests — much easier to debug than the - // default empty InternalServerError body. Production - // (Yavsc.Org) wires its own exception handler; this - // fixture is test-only. - app.UseDeveloperExceptionPage(); - app.UseRouting(); - app.UseAuthentication(); - app.UseAuthorization(); - app.MapControllers(); - - // EnsureCreated + seed alice, run once at host startup. - // EnsureCreated is idempotent (creates only the tables that - // don't exist yet) and runs against the shared - // SqliteConnection (Cache=Shared), so every DbContext that - // resolves through this fixture's host sees the same schema. - // We do NOT call EnsureDeleted: the SqliteConnection is held - // open at the static level and closing it destroys the - // :memory: store for every other DbContext — the org - // fixture can afford EnsureDeleted because its store is - // built fresh per fixture, but the blogs fixture's static - // connection outlives a single fixture instance. - using (var seedScope = app.Services.CreateScope()) - { - var db = seedScope.ServiceProvider - .GetRequiredService(); - db.Database.EnsureCreated(); - if (!db.Users.Any(u => u.Id == "alice")) - { - db.Users.Add(new ApplicationUser - { - Id = "alice", - UserName = "alice", - Email = "alice@example.com", - EmailConfirmed = true, - FullName = "Alice Dupont", - Avatar = "/avatars/alice.png", - }); - db.SaveChanges(); - - // Inline the seed of the circle + post. We don't - // call SeedCircle/SeedBlogPost (the instance helpers) - // because those resolve through this.Services, which - // is null until WebHostFixture.InitializeAsync has - // finished wiring the shared slot — i.e. after this - // method returns. Use app.Services directly. - var circle = new Circle - { - OwnerId = "alice", - Name = "test", - Public = true, - }; - db.Circle.Add(circle); - db.SaveChanges(); - CircleId = circle.Id; - - var post = new BlogPost - { - AuthorId = "alice", - Title = "Billet ACL test", - Article = "Test article body.", - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - }; - db.BlogSpot.Add(post); - db.SaveChanges(); - PostId = post.Id; - } - } - - await Task.CompletedTask; - return app; - } -/// Reset the in-memory database to a known empty state. - /// UseInMemoryDatabase shares its store across the - /// lifetime of the instance, - /// so without a per-test reset the test order would leak - /// state between tests. - public void ResetDatabase() - { - using var scope = Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - db.Database.EnsureDeleted(); - db.Database.EnsureCreated(); - } - public void CleanupAcl() - { - using var scope = Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - db.CircleAuthorizationToBlogPost - .Where(a => a.CircleId == CircleId - && a.BlogPostId == PostId) - .ExecuteDelete(); - } - - public override void Dispose() - { - // Keep the shared in-memory SQLite connection alive for the - // whole test process. Closing it from one fixture instance can - // destroy the database while other collections are still using - // it, which surfaces as intermittent "no such table" failures. - base.Dispose(); - } - - /// Seed an in the shared - /// SQLite store, so tests that POST/PUT/DELETE a - /// BlogPost (whose AuthorId is a FK to - /// AspNetUsers.Id) don't trip the FK constraint that - /// SQLite enforces but the EF Core InMemory provider silently - /// ignored. Idempotent on : a - /// second call for the same id is a no-op (the user already - /// exists). - /// Both the PK id and the login name. - /// The JWT subject in tests is this same string, so seeding - /// this id is enough to make the FK from a - /// BlogPost.AuthorId resolve. - /// Optional hook to fill in fields - /// like FullName / Avatar / EmailConfirmed - /// that downstream tests assert on. - public ApplicationUser SeedUser(string userName, - Action? configure = null) - { - using var scope = Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - var existing = db.Users.SingleOrDefault(u => u.Id == userName); - if (existing != null) return existing; - - // Email is an alternate key on ApplicationUser; seeding - // it explicitly avoids the InMemory provider's null-claim - // tracking quirk (cf. PublishEndpointTests.ResetDatabase) - // and keeps the column shape realistic for prod. - var user = new ApplicationUser - { - Id = userName, - UserName = userName, - Email = $"{userName}@example.test", - }; - configure?.Invoke(user); - db.Users.Add(user); - db.SaveChanges(); - return user; - } - - /// Trivial stub. The - /// blog API endpoints exercised by the first tests don't read the - /// file system, so the implementation can be a no-op. - private sealed class NoopFileSystemAuthManager : IFileSystemAuthManager - { - public FileAccessRight GetFilePathAccess(System.Security.Claims.ClaimsPrincipal user, string fileRelativePath) - => FileAccessRight.None; - - public void SetAccess(long circleId, string normalizedFullPath, FileAccessRight access) - { - } - } - - /// Create a circle owned by - /// directly in the SQLite store and return its server-assigned - /// id. - public long SeedCircle(string ownerId, string name, bool isPublic = false, - ICollection? members = null - ) - { - using var scope = Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - var circle = new Circle { OwnerId = ownerId, Name = name, Public = isPublic }; - db.Circle.Add(circle); - db.SaveChanges(); - if (members != null && members.Count > 0) - { - foreach (String memberId in members) - { - var member = new CircleMember { CircleId = circle.Id, MemberId = memberId }; - db.CircleMembers.Add(member); - } - db.SaveChanges(); - } - return circle.Id; - } - - /// Create a blog post owned by - /// directly in the SQLite store and return its server-assigned - /// id. - public long SeedBlogPost(string authorId, string title) - { - using var scope = Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - var post = new BlogPost - { - AuthorId = authorId, - Title = title, - Article = "Test article body.", - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - }; - db.BlogSpot.Add(post); - db.SaveChanges(); - return post.Id; - } -} diff --git a/src/Yavsc.Blogs.Tests/JwtClaimMappingCollection.cs b/src/Yavsc.Blogs.Tests/JwtClaimMappingCollection.cs index 927453acc..e141c0f3f 100644 --- a/src/Yavsc.Blogs.Tests/JwtClaimMappingCollection.cs +++ b/src/Yavsc.Blogs.Tests/JwtClaimMappingCollection.cs @@ -1,3 +1,5 @@ +using Xunit; + namespace Yavsc.Blogs.Tests; [CollectionDefinition("JwtClaimMapping", DisableParallelization = true)] diff --git a/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs similarity index 94% rename from src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs rename to src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs index 7311ce042..c9d957742 100644 --- a/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs +++ b/src/Yavsc.Blogs.Tests/MappedClaimsBlogsWebServerFixture.cs @@ -1,5 +1,6 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -21,11 +22,11 @@ namespace Yavsc.Blogs.Tests; /// This is the closest in-process reproduction of the production /// authentication surface for the blog API. /// -public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFixture +public sealed class MappedClaimsBlogsWebServerFixture : IDisposable { private readonly InMemoryDatabaseRoot _inMemoryRoot = new(); private readonly Dictionary _savedInboundMap; - private WebApplication? _app = null; + private readonly WebApplication _app; public MappedClaimsBlogsWebServerFixture() { @@ -64,8 +65,8 @@ public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFix ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = TestTokenIssuer.SigningKey, - RoleClaimType = Yavsc.Constants.RoleClaimType, - NameClaimType = Yavsc.Constants.NameClaimType, + RoleClaimType = YavscConstants.RoleClaimType, + NameClaimType = YavscConstants.NameClaimType, }; }); @@ -86,7 +87,6 @@ public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFix public void Dispose() { - if (_app is null) return; _app.StopAsync().GetAwaiter().GetResult(); _app.DisposeAsync().AsTask().GetAwaiter().GetResult(); @@ -97,7 +97,6 @@ public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFix } } - private sealed class NoopFileSystemAuthManager : IFileSystemAuthManager { public FileAccessRight GetFilePathAccess(System.Security.Claims.ClaimsPrincipal user, string fileRelativePath) diff --git a/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs b/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs index 113707caa..af1a26c64 100644 --- a/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs +++ b/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs @@ -1,11 +1,11 @@ using System.Net; +using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Tests.Shared; -using Yavsc.Blogs.Tests.Fixtures; namespace Yavsc.Blogs.Tests; @@ -25,7 +25,7 @@ namespace Yavsc.Blogs.Tests; /// in-memory ApplicationDbContext, JWT bearer auth /// via . /// -[Collection("Yavsc Blogs")] +[Collection("JwtClaimMapping")] public sealed class PublishEndpointTests : IClassFixture { private readonly BlogsWebServerFixture _fixture; @@ -72,6 +72,12 @@ public sealed class PublishEndpointTests : IClassFixture return post.Id; } + private string PublishUrl(long id) + => $"{_fixture.Addresses.First(a => a.StartsWith("https://"))}/api/v1/blog/{id}/publish"; + + private string BlogsUrl + => _fixture.Addresses.First(a => a.StartsWith("https://")) + "/api/v1/blog"; + private HttpClient NewClient(string subject) { var handler = new HttpClientHandler @@ -95,13 +101,12 @@ public sealed class PublishEndpointTests : IClassFixture var postId = SeedPost("alice"); using var http = NewClient("alice"); - var put = await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(PublishUrl(postId), new { publish = true }); Assert.Equal(HttpStatusCode.NoContent, put.StatusCode); - var get = await http.GetAsync(_fixture.BlogSpotUrl() + $"/{postId}", TestContext.Current.CancellationToken); + var get = await http.GetAsync($"{BlogsUrl}/{postId}"); Assert.Equal(HttpStatusCode.OK, get.StatusCode); - using var doc = JsonDocument.Parse(await get.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); - Assert.Equal($"post-by-alice", doc.RootElement.GetProperty("title").GetString()); + using var doc = JsonDocument.Parse(await get.Content.ReadAsStringAsync()); Assert.True(doc.RootElement.GetProperty("isPublished").GetBoolean()); } @@ -112,13 +117,12 @@ public sealed class PublishEndpointTests : IClassFixture var postId = SeedPost("alice"); using var http = NewClient("alice"); - await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); - var put = await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = false }, TestContext.Current.CancellationToken); + await http.PutAsJsonAsync(PublishUrl(postId), new { publish = true }); + var put = await http.PutAsJsonAsync(PublishUrl(postId), new { publish = false }); Assert.Equal(HttpStatusCode.NoContent, put.StatusCode); - var get = await http.GetAsync(_fixture.BlogSpotUrl() + $"/{postId}", TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, get.StatusCode); - using var doc = JsonDocument.Parse(await get.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); + var get = await http.GetAsync($"{BlogsUrl}/{postId}"); + using var doc = JsonDocument.Parse(await get.Content.ReadAsStringAsync()); Assert.False(doc.RootElement.GetProperty("isPublished").GetBoolean()); } @@ -127,7 +131,7 @@ public sealed class PublishEndpointTests : IClassFixture { ResetDatabase(); using var http = NewClient("alice"); - var put = await http.PutAsJsonAsync(_fixture.PublishUrl(99999L), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(PublishUrl(99999L), new { publish = true }); Assert.Equal(HttpStatusCode.NotFound, put.StatusCode); } @@ -138,7 +142,7 @@ public sealed class PublishEndpointTests : IClassFixture var postId = SeedPost("alice"); using var http = NewClient("bob"); - var put = await http.PutAsJsonAsync(_fixture.PublishUrl(postId), new { publish = true }, TestContext.Current.CancellationToken); + var put = await http.PutAsJsonAsync(PublishUrl(postId), new { publish = true }); // 401 Challenge (the controller returns Challenge() // for AuthorizationFailureException). The exact code // is framework-dependent; what matters is "not 204". diff --git a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj index 887648060..256bdc4de 100644 --- a/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj +++ b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj @@ -9,7 +9,7 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070 + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 @@ -17,7 +17,6 @@ - @@ -32,4 +31,7 @@ + + + \ No newline at end of file diff --git a/src/Yavsc.Blogs/Constants.cs b/src/Yavsc.Blogs/Constants.cs index 9d400032d..4dbdfb8bf 100644 --- a/src/Yavsc.Blogs/Constants.cs +++ b/src/Yavsc.Blogs/Constants.cs @@ -1,8 +1,10 @@ namespace Yavsc.Blogs; -public static class BlogConstants +public static class Constants { public const string AdminRole = "Admin"; public const string ModeratorRole = "Moderator"; public const string UserRole = "User"; + + public const string APIPrefix = "api/v1"; } diff --git a/src/Yavsc.Blogs/Controllers/BlogAclApiController.cs b/src/Yavsc.Blogs/Controllers/BlogAclApiController.cs index a33d75d80..aa81f9d5a 100644 --- a/src/Yavsc.Blogs/Controllers/BlogAclApiController.cs +++ b/src/Yavsc.Blogs/Controllers/BlogAclApiController.cs @@ -1,17 +1,15 @@ - +using System.Linq; using System.Security.Claims; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Yavsc.Abstract.BlogSpot; using Yavsc.Models; using Yavsc.Models.Access; using Yavsc.Server.Helpers; -using static Yavsc.Constants; namespace Yavsc.Blogs.Controllers { [Produces("application/json")] - [Route(APIPrefix+"/blogacl")] + [Route("api/blogacl")] public class BlogAclApiController : Controller { private readonly ApplicationDbContext _context; @@ -26,7 +24,7 @@ namespace Yavsc.Blogs.Controllers /// Blog posts (and therefore their ACLs) are private to their /// author — the API never exposes another user's ACL. /// - // GET: api/v1/blogacl + // GET: api/blogacl [HttpGet] public IEnumerable GetBlogACL() { @@ -70,7 +68,7 @@ namespace Yavsc.Blogs.Controllers return BadRequest(); } - if (!await CheckOwnerAsync(circleAuthorizationToBlogPost.CircleId)) + if (!CheckOwner(circleAuthorizationToBlogPost.CircleId)) { return new ChallengeResult(); } @@ -94,42 +92,27 @@ namespace Yavsc.Blogs.Controllers return new StatusCodeResult(StatusCodes.Status204NoContent); } - private async Task CheckOwnerAsync (long circleId) + private bool CheckOwner (long circleId) { + var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); - if (uid==null) return false; - var circle = await _context.Circle.FirstOrDefaultAsync(c=>c.Id==circleId); - if (circle == null) return false; - return circle.OwnerId == uid; + var circle = _context.Circle.First(c=>c.Id==circleId); + _context.Entry(circle).State = EntityState.Detached; + return (circle.OwnerId == uid); } // POST: api/BlogAclApi [HttpPost] - public async Task PostCircleAuthorizationToBlogPost( - [FromBody] PostAccessControlRulePayload circleAuthorizationToBlogPost) + public async Task PostCircleAuthorizationToBlogPost([FromBody] CircleAuthorizationToBlogPost circleAuthorizationToBlogPost) { if (!ModelState.IsValid) { return BadRequest(ModelState); } - // No 500: a missing or zero BlogPostId is a client - // error, not an EF Core FK violation waiting to happen. - // The 2026-08-21 prod 500 was this exact path (PostIt - // sent only circleId, server saw BlogPostId = 0 and - // SaveChangesAsync threw InvalidOperationException). - if (circleAuthorizationToBlogPost.BlogPostId <= 0) - { - return BadRequest("BlogPostId is required and must be > 0."); - } - if (!await CheckOwnerAsync(circleAuthorizationToBlogPost.CircleId)) + if (!CheckOwner(circleAuthorizationToBlogPost.CircleId)) { return new ChallengeResult(); } - CircleAuthorizationToBlogPost entity = new CircleAuthorizationToBlogPost - { - BlogPostId = circleAuthorizationToBlogPost.BlogPostId, - CircleId = circleAuthorizationToBlogPost.CircleId - }; - _context.CircleAuthorizationToBlogPost.Add(entity); + _context.CircleAuthorizationToBlogPost.Add(circleAuthorizationToBlogPost); try { await _context.SaveChangesAsync(User.GetUserId()); diff --git a/src/Yavsc.Blogs/Controllers/BlogApiController.cs b/src/Yavsc.Blogs/Controllers/BlogApiController.cs index 97c070a3c..2f97de53a 100644 --- a/src/Yavsc.Blogs/Controllers/BlogApiController.cs +++ b/src/Yavsc.Blogs/Controllers/BlogApiController.cs @@ -1,17 +1,16 @@ using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using System.Text.Json; using Yavsc.Blogspot; +using Yavsc.Models.Blog; using Yavsc.Server.Exceptions; using Yavsc.Server.Helpers; -using static Yavsc.Constants; +using static Yavsc.Blogs.Constants; namespace Yavsc.Blogs.Controllers { [Authorize("BlogScope")] [Produces("application/json")] - [Route(APIPrefix + "/" + BlogSpotPath)] + [Route(APIPrefix + "/blog")] public class BlogApiController : Controller { private readonly BlogSpotService blogSpotService; @@ -21,14 +20,14 @@ namespace Yavsc.Blogs.Controllers this.blogSpotService = blogSpotService; } - // GET: api/v1/blogspot + // GET: api/BlogApi [HttpGet] public async Task> GetBlogspot(int start = 0, int take = 25) { return await blogSpotService.Index(User, null, start, take); } - // GET: api/v1/blogspot/5 + // GET: api/BlogApi/5 [HttpGet("{id}", Name = "GetBlog")] public async Task GetBlog([FromRoute] long id) { @@ -45,7 +44,7 @@ namespace Yavsc.Blogs.Controllers return NotFound(); } - return Ok(blog.GetPayload()); + return Ok(blog); } catch (AuthorizationFailureException) { @@ -53,24 +52,10 @@ namespace Yavsc.Blogs.Controllers } } - // PUT: api/v1/blogspot/5 + // PUT: api/BlogApi/5 [HttpPut("{id}")] - public async Task PutBlog(long id) + public async Task PutBlog(long id, [FromBody] BlogPost blog) { - var blog = await ReadPutBlogRequestAsync(); - if (blog is null) - { - return BadRequest(ModelState); - } - - // These properties are server-managed or optional graph members and - // should not block JSON payloads coming from API clients. - ModelState.Remove(nameof(Models.Blog.BlogPost.Author)); - ModelState.Remove(nameof(Models.Blog.BlogPost.Tags)); - ModelState.Remove(nameof(Models.Blog.BlogPost.Comments)); - ModelState.Remove(nameof(Models.Blog.BlogPost.UserCreated)); - ModelState.Remove(nameof(Models.Blog.BlogPost.UserModified)); - if (!ModelState.IsValid) { return BadRequest(ModelState); @@ -81,10 +66,6 @@ namespace Yavsc.Blogs.Controllers return BadRequest(); } - var files = Request.HasFormContentType - ? Request.Form.Files - : (IFormFileCollection)new FormFileCollection(); - var existing = await blogSpotService.GetBlogPostAsync(id); if (existing == null) { @@ -93,7 +74,7 @@ namespace Yavsc.Blogs.Controllers try { - await blogSpotService.Modify(User, blog, files); + await blogSpotService.Modify(User, blog); } catch (AuthorizationFailureException) { @@ -103,18 +84,10 @@ namespace Yavsc.Blogs.Controllers return new StatusCodeResult(StatusCodes.Status204NoContent); } - // POST: api/v1/blogspot + // POST: api/v1/blog [HttpPost] public IActionResult PostBlog([FromBody] Models.Blog.BlogPost blog) { - // These properties are server-managed or optional graph members and - // should not block JSON payloads coming from API clients. - ModelState.Remove(nameof(Models.Blog.BlogPost.Author)); - ModelState.Remove(nameof(Models.Blog.BlogPost.Tags)); - ModelState.Remove(nameof(Models.Blog.BlogPost.Comments)); - ModelState.Remove(nameof(Models.Blog.BlogPost.UserCreated)); - ModelState.Remove(nameof(Models.Blog.BlogPost.UserModified)); - if (!ModelState.IsValid) { return BadRequest(ModelState); @@ -144,8 +117,7 @@ namespace Yavsc.Blogs.Controllers : (IFormFileCollection)new FormFileCollection(); var uid = User.GetUserId(); var post = blogSpotService.Create(uid, blog, files); - return CreatedAtRoute("GetBlog", new { id = post.Id }, - post.GetPayload()); + return CreatedAtRoute("GetBlog", new { id = post.Id }, post); } // DELETE: api/BlogApi/5 @@ -164,7 +136,7 @@ namespace Yavsc.Blogs.Controllers } await blogSpotService.Delete(User, id); - return Ok(blog.GetPayload()); + return Ok(blog); } /// @@ -209,34 +181,6 @@ namespace Yavsc.Blogs.Controllers { base.Dispose(disposing); } - - private async Task ReadPutBlogRequestAsync() - { - if (!Request.HasFormContentType) - { - return await Request.ReadFromJsonAsync(); - } - - var raw = Request.Form["blog"].ToString(); - if (string.IsNullOrWhiteSpace(raw)) - { - ModelState.AddModelError("blog", "A blog payload is required in the multipart form field 'blog'."); - return null; - } - - try - { - return JsonSerializer.Deserialize(raw, new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - }); - } - catch (JsonException ex) - { - ModelState.AddModelError("blog", $"Invalid blog JSON payload: {ex.Message}"); - return null; - } - } } /// diff --git a/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs b/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs index 533e55943..a5d905ebf 100644 --- a/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs +++ b/src/Yavsc.Blogs/Controllers/BlogTagsApiController.cs @@ -1,12 +1,16 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Yavsc.Models; using Yavsc.Models.Blog; -using static Yavsc.Constants; +using static Yavsc.Blogs.Constants; namespace Yavsc.Blogs.Controllers { [Produces("application/json")] - [Route(APIPrefix + "/" + BlogTagPath )] + [Route(APIPrefix + "/blogtags")] public class BlogTagsApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Blogs/Controllers/CircleApiController.cs b/src/Yavsc.Blogs/Controllers/CircleApiController.cs index fafd00ac0..c35da7c58 100644 --- a/src/Yavsc.Blogs/Controllers/CircleApiController.cs +++ b/src/Yavsc.Blogs/Controllers/CircleApiController.cs @@ -1,14 +1,14 @@ +using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Yavsc.Models; using Yavsc.Models.Relationship; using Yavsc.Server.Helpers; -using static Yavsc.Constants; namespace Yavsc.Blogs.Controllers { [Produces("application/json")] - [Route(APIPrefix +"/" + CirclePath)] + [Route("api/circle")] public class CircleApiController : Controller { private readonly ApplicationDbContext _context; @@ -56,25 +56,12 @@ namespace Yavsc.Blogs.Controllers /// /// Replaces a circle. The caller must own it; the server - /// reasserts ownership regardless of any OwnerId - /// the client tries to put in the body. - /// - /// The body shape is a — a - /// flat, navigation-free projection — not the EF entity. - /// The EF entity carries [JsonIgnore]-decorated - /// navigation properties (Owner, Members) - /// that bind to server-only types (ApplicationUser, - /// CircleMember); keeping the wire shape as a - /// DTO avoids any future regression where the entity - /// grows a navigable property that System.Text.Json - /// refuses to materialise. The client-side mirror lives - /// in Yavsc.Api.Client.Dtos.CircleDto. + /// reasserts ownership regardless of any OwnerId the client + /// tries to put in the body. /// // PUT: api/circle/5 [HttpPut("{id}")] - public async Task PutCircle( - [FromRoute] long id, - [FromBody] CircleDto circle) + public async Task PutCircle([FromRoute] long id, [FromBody] Circle circle) { if (!ModelState.IsValid) { @@ -94,14 +81,9 @@ namespace Yavsc.Blogs.Controllers return new ChallengeResult(); } - // Map the wire shape onto the entity. OwnerId is - // forced to the caller regardless of what the body - // says; Name and Public come from the body. - existing.Name = circle.Name; - existing.Public = circle.Public; - existing.OwnerId = uid; - - _context.Entry(existing).State = EntityState.Modified; + // Force OwnerId to the caller; the body value is ignored. + circle.OwnerId = uid; + _context.Entry(circle).State = EntityState.Modified; try { @@ -128,7 +110,7 @@ namespace Yavsc.Blogs.Controllers /// // POST: api/circle [HttpPost] - public async Task PostCircle([FromBody] CircleDto circle) + public async Task PostCircle([FromBody] Circle circle) { if (!ModelState.IsValid) { @@ -137,14 +119,8 @@ namespace Yavsc.Blogs.Controllers var uid = User.GetUserId(); circle.OwnerId = uid; - Circle newCircle = new Circle - { - OwnerId = User.GetUserId(), - Name = circle.Name, - Public = circle.Public - }; - _context.Circle.Add(newCircle); + _context.Circle.Add(circle); try { await _context.SaveChangesAsync(User.GetUserId()); @@ -345,26 +321,6 @@ namespace Yavsc.Blogs.Controllers } } - /// - /// Wire shape for PUT /api/circle/{id}. Flat by - /// design — navigation properties (Owner, - /// Members) live on the EF entity only and never - /// cross the wire. - /// - /// Field names match the JSON the server emits - /// (camelCase via ASP.NET Core's Web defaults), so no - /// [JsonPropertyName] attributes are required. - /// Mirrors the client-side Yavsc.Api.Client.Dtos.CircleDto - /// — keep them in sync. - /// - public sealed class CircleDto - { - public long Id { get; set; } - public string Name { get; set; } = string.Empty; - public string OwnerId { get; set; } = string.Empty; - public bool Public { get; set; } - } - /// /// Wire shape for GET /api/circle/{id}/members. /// Mirrors but stops diff --git a/src/Yavsc.Blogs/Controllers/CommentsApiController.cs b/src/Yavsc.Blogs/Controllers/CommentsApiController.cs index d0747c295..b9f334dcf 100644 --- a/src/Yavsc.Blogs/Controllers/CommentsApiController.cs +++ b/src/Yavsc.Blogs/Controllers/CommentsApiController.cs @@ -5,13 +5,13 @@ using Microsoft.EntityFrameworkCore; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Server.Helpers; -using static Yavsc.Constants; +using static Yavsc.Blogs.Constants; namespace Yavsc.Blogs.Controllers { [Authorize] [Produces("application/json")] - [Route(APIPrefix + "/" + CommentsPath)] + [Route(APIPrefix + "/blogcomments")] public class CommentsApiController : Controller { private readonly ApplicationDbContext _context; diff --git a/src/Yavsc.Blogs/Controllers/FileSystemApiController.cs b/src/Yavsc.Blogs/Controllers/FileSystemApiController.cs index 5e8345034..5b067c1bc 100644 --- a/src/Yavsc.Blogs/Controllers/FileSystemApiController.cs +++ b/src/Yavsc.Blogs/Controllers/FileSystemApiController.cs @@ -2,7 +2,7 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using static Yavsc.Constants; +using static Yavsc.Blogs.Constants; namespace Yavsc.Blogs.Controllers { @@ -21,7 +21,7 @@ namespace Yavsc.Blogs.Controllers private readonly ILogger _logger; public FileSystemApiController(ApplicationDbContext context, - IAuthorizationService authorizationService, + IAuthorizationService authorizationService, ILoggerFactory loggerFactory) { @@ -38,7 +38,7 @@ namespace Yavsc.Blogs.Controllers [HttpGet("{*subdir}")] public IActionResult GetDir([ValidRemoteUserFilePath] string subdir="") - { + { if (!ModelState.IsValid) return new BadRequestObjectResult(ModelState); // _logger.LogInformation($"listing files from {User.Identity.Name}{subdir}"); var files = AbstractFileSystemHelpers.GetUserFiles(User.GetUserId(), subdir); @@ -57,20 +57,20 @@ namespace Yavsc.Blogs.Controllers } catch (InvalidPathException ex) { pathex = ex; } - if (pathex!=null) + if (pathex!=null) { _logger.LogError($"invalid sub path: '{subdir}'."); return BadRequest(pathex); } _logger.LogInformation($"Receiving files, saved in '{destDir}' (specified as '{subdir}')."); - + var uid = User.GetUserId(); var user = dbContext.Users.Single( u => u.Id == uid ); int i=0; _logger.LogInformation($"Receiving {Request.Form.Files.Count} files."); - + foreach (var f in Request.Form.Files) { var item = user.ReceiveUserFile(destDir, f); @@ -178,7 +178,7 @@ namespace Yavsc.Blogs.Controllers return Ok(new { deleted=id }); } - + } } diff --git a/src/Yavsc.Blogs/Controllers/FileSystemStreamController.cs b/src/Yavsc.Blogs/Controllers/FileSystemStreamController.cs index bc6485dd4..23cf0cc60 100644 --- a/src/Yavsc.Blogs/Controllers/FileSystemStreamController.cs +++ b/src/Yavsc.Blogs/Controllers/FileSystemStreamController.cs @@ -8,7 +8,7 @@ using Yavsc.Models.Messaging; using Yavsc.Services; using Microsoft.AspNetCore.SignalR; using Yavsc.Server.Helpers; -using static Yavsc.Constants; +using static Yavsc.Blogs.Constants; using Yavsc.Server.Hubs; namespace Yavsc.Blogs.Controllers diff --git a/src/Yavsc.Blogs/Controllers/PostTagsApiController.cs b/src/Yavsc.Blogs/Controllers/PostTagsApiController.cs index da03c19c6..e908edec6 100644 --- a/src/Yavsc.Blogs/Controllers/PostTagsApiController.cs +++ b/src/Yavsc.Blogs/Controllers/PostTagsApiController.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Mvc; -using static Yavsc.Constants; +using static Yavsc.Blogs.Constants; namespace Yavsc.Blogs.Controllers { diff --git a/src/Yavsc.Blogs/Controllers/TagsApiController.cs b/src/Yavsc.Blogs/Controllers/TagsApiController.cs index d4c2b5389..daf0220b2 100644 --- a/src/Yavsc.Blogs/Controllers/TagsApiController.cs +++ b/src/Yavsc.Blogs/Controllers/TagsApiController.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Yavsc.Models; -using static Yavsc.Constants; +using static Yavsc.Blogs.Constants; namespace Yavsc.Controllers { diff --git a/src/Yavsc.Blogs/Controllers/UserSearchApiController.cs b/src/Yavsc.Blogs/Controllers/UserSearchApiController.cs index 951a5880e..e99441e0e 100644 --- a/src/Yavsc.Blogs/Controllers/UserSearchApiController.cs +++ b/src/Yavsc.Blogs/Controllers/UserSearchApiController.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Yavsc.Models; -using static Yavsc.Constants; namespace Yavsc.Blogs.Controllers { @@ -27,7 +26,7 @@ namespace Yavsc.Blogs.Controllers /// exposing it. /// [Produces("application/json")] - [Route(APIPrefix + "/user-search")] + [Route("api/user-search")] [Authorize] public class UserSearchApiController : Controller { @@ -67,9 +66,8 @@ namespace Yavsc.Blogs.Controllers // book callers already know the email they're // searching for and we don't want to surface a // long tail of partial matches. - var normalized = e.Trim(); - query = query.Where(u => u.Email != null && - string.Compare(u.Email, normalized, true) ==0); + var normalised = e.Trim(); + query = query.Where(u => u.Email != null && u.Email.ToLower() == normalised.ToLower()); } if (!string.IsNullOrWhiteSpace(q)) @@ -110,4 +108,4 @@ namespace Yavsc.Blogs.Controllers public string? Avatar { get; set; } public string? Email { get; set; } } -} +} \ No newline at end of file diff --git a/src/Yavsc.Blogs/Program.cs b/src/Yavsc.Blogs/Program.cs index 00ac7f6fe..742c9eeee 100644 --- a/src/Yavsc.Blogs/Program.cs +++ b/src/Yavsc.Blogs/Program.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection.Extensions; +using Yavsc; using Yavsc.Interface; using Yavsc.Interfaces; using Yavsc.Models; @@ -50,7 +51,7 @@ internal class Program // DbContextBuilder services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString( - Yavsc.Constants.YavscConnectionStringName))); + YavscConstants.YavscConnectionStringName))); // other services services diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj index 25521e8a5..5c175cb54 100644 --- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj +++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj @@ -4,15 +4,18 @@ enable 1c73094f-959f-4211-b1a1-6a69b236c283 Yavsc.Blogs - https://forgejo.pschneider.fr/notazof/yavsc + https://github.com/pazof/yavsc true 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070 + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 + + + \ No newline at end of file diff --git a/src/Yavsc.Org.Tests/ComputeKidTests.cs b/src/Yavsc.Org.Tests/ComputeKidTests.cs index 61aa5260f..af9d94a9f 100644 --- a/src/Yavsc.Org.Tests/ComputeKidTests.cs +++ b/src/Yavsc.Org.Tests/ComputeKidTests.cs @@ -1,3 +1,6 @@ +using System; +using System.IO; +using System.Linq; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using Xunit; diff --git a/src/Yavsc.Org.Tests/Controllers/ClientControllerCollectionTests.cs b/src/Yavsc.Org.Tests/Controllers/ClientControllerCollectionTests.cs index 264eff64d..8883c75d4 100644 --- a/src/Yavsc.Org.Tests/Controllers/ClientControllerCollectionTests.cs +++ b/src/Yavsc.Org.Tests/Controllers/ClientControllerCollectionTests.cs @@ -1,11 +1,13 @@ using System.Net; +using System.Net.Http; +using System.Threading.Tasks; using IdentityServer8.EntityFramework.Entities; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Xunit; using Yavsc.Models; using Yavsc.Tests.Shared; -using Xunit; namespace Yavsc.Org.Tests.Controllers; diff --git a/src/Yavsc.Org.Tests/Controllers/CommandFormsControllerTests.cs b/src/Yavsc.Org.Tests/Controllers/CommandFormsControllerTests.cs index 621663285..953ac43c4 100644 --- a/src/Yavsc.Org.Tests/Controllers/CommandFormsControllerTests.cs +++ b/src/Yavsc.Org.Tests/Controllers/CommandFormsControllerTests.cs @@ -1,7 +1,9 @@ using System.Net; +using System.Net.Http; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Testing; -using Yavsc.Tests.Shared; using Xunit; +using Yavsc.Tests.Shared; namespace Yavsc.Org.Tests.Controllers; diff --git a/src/Yavsc.Org.Tests/Controllers/CommentsApiIntegrationTests.cs b/src/Yavsc.Org.Tests/Controllers/CommentsApiIntegrationTests.cs index 39d649311..4b145cd70 100644 --- a/src/Yavsc.Org.Tests/Controllers/CommentsApiIntegrationTests.cs +++ b/src/Yavsc.Org.Tests/Controllers/CommentsApiIntegrationTests.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.DependencyInjection; using Yavsc.Models; using Yavsc.Models.Blog; using Yavsc.Tests.Shared; -using Xunit; namespace Yavsc.Org.Tests.Controllers; diff --git a/src/Yavsc.Org.Tests/Controllers/CommentsControllerTests.cs b/src/Yavsc.Org.Tests/Controllers/CommentsControllerTests.cs index 0954cdd27..28213effe 100644 --- a/src/Yavsc.Org.Tests/Controllers/CommentsControllerTests.cs +++ b/src/Yavsc.Org.Tests/Controllers/CommentsControllerTests.cs @@ -5,7 +5,6 @@ using Microsoft.EntityFrameworkCore; using Yavsc.Controllers; using Yavsc.Models; using Yavsc.Models.Blog; -using Xunit; namespace Yavsc.Org.Tests.Controllers; diff --git a/src/Yavsc.Org.Tests/Controllers/TestWebApplicationFactoryIsolationTests.cs b/src/Yavsc.Org.Tests/Controllers/TestWebApplicationFactoryIsolationTests.cs deleted file mode 100644 index f1507bf49..000000000 --- a/src/Yavsc.Org.Tests/Controllers/TestWebApplicationFactoryIsolationTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -using IdentityServer8.EntityFramework.DbContexts; -using IdentityServer8.EntityFramework.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Yavsc.Org.Tests.Controllers; - -/// -/// Regression sentinel: two -/// instances must not see each other's clients. -/// -/// EF Core's UseInMemoryDatabase(name) returns the same -/// backing store to every DbContext that asks for it under -/// the same name, in the same process. Before the per-fixture GUID -/// fix, both and -/// used the bare "InMemory" -/// connection string, so every fixture shared one store and tests -/// were silently order-dependent. -/// -/// We assert against directly -/// rather than via IClientStore: the validating wrapper around -/// IClientStore raises events through IEventService, -/// which is not registered in the test host and crashes with a -/// NullReferenceException before it can return a result. Going -/// straight to the DbContext is the same code path the production -/// code uses, so it is the right surface to assert against. -/// -public class TestWebApplicationFactoryIsolationTests -{ - [Fact] - public async Task Second_factory_does_not_see_clients_seeded_into_first() - { - var marker = $"marker-A-{Guid.NewGuid():N}"; - - // First factory: seed a distinctive client. - using (var first = new TestWebApplicationFactory()) - { - await using var scope = first.Services.CreateAsyncScope(); - var configDb = scope.ServiceProvider.GetRequiredService(); - var firstCs = scope.ServiceProvider.GetRequiredService() - .GetConnectionString("YavscConnection"); - configDb.Clients.Add(new Client { ClientId = marker, ClientName = "marker-A" }); - await configDb.SaveChangesAsync(TestContext.Current.CancellationToken); - - // Sanity: the first factory can see its own seed. - var seenByFirst = await configDb.Clients - .AsNoTracking() - .AnyAsync(c => c.ClientId == marker, TestContext.Current.CancellationToken); - Assert.True(seenByFirst); - } - - // Second factory: must start from a clean slate. If the - // in-memory store leaked from the first factory, this - // assertion fails. - using var second = new TestWebApplicationFactory(); - await using var secondScope = second.Services.CreateAsyncScope(); - var secondCs = secondScope.ServiceProvider.GetRequiredService() - .GetConnectionString("YavscConnection"); - Assert.StartsWith("InMemory-", secondCs); - var secondDb = secondScope.ServiceProvider.GetRequiredService(); - var seenBySecond = await secondDb.Clients - .AsNoTracking() - .AnyAsync(c => c.ClientId == marker, TestContext.Current.CancellationToken); - Assert.False(seenBySecond); - } -} diff --git a/src/Yavsc.Org.Tests/DictionnaireMetierTests.cs b/src/Yavsc.Org.Tests/DictionnaireMetierTests.cs deleted file mode 100644 index 2bb99b072..000000000 --- a/src/Yavsc.Org.Tests/DictionnaireMetierTests.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Yavsc.Models; -using Yavsc.Models.Workflow; -using Yavsc.Server.Services; -using Xunit; - -namespace Yavsc.Org.Tests; - -public class DictionnaireMetierTests -{ - [Fact] - public void DictionnaireMetier_and_TermeMetier_can_be_constructed() - { - var dictionary = new DictionnaireMetier - { - Id = 1, - Nom = "Droit", - Langue = "fr", - DomaineActiviteCode = "Droit" - }; - - var term = new TermeMetier - { - Id = 2, - DictionnaireMetierId = dictionary.Id, - DictionnaireMetier = dictionary, - Mot = "contrat", - Definition = "Accord de volontés", - Langue = "fr", - StatutValidation = StatutValidationTerme.Propose, - ProposeParId = "user-1" - }; - - Assert.Equal("Droit", dictionary.DomaineActiviteCode); - Assert.Equal("contrat", term.Mot); - Assert.Equal(StatutValidationTerme.Propose, term.StatutValidation); - } - - [Fact] - public async Task DictionnaireMetier_moderation_flow_allows_propose_validate_and_reject() - { - var options = new DbContextOptionsBuilder() - .UseInMemoryDatabase(Guid.NewGuid().ToString()) - .Options; - - await using var context = new ApplicationDbContext(options); - context.Activities.Add(new Activity - { - Code = "Droit", - Name = "Droit", - ParentCode = null, - Description = "Domaine de référence", - Hidden = false, - Forms = new List() - }); - - context.DictionnaireMetier.Add(new DictionnaireMetier - { - Nom = "Droit civil", - Langue = "fr", - DomaineActiviteCode = "Droit" - }); - await context.SaveChangesAsync(); - - var service = new DictionnaireMetierModerationService(context); - - var proposed = await service.ProposerTermAsync(1, "contrat", "Accord de volontés", "fr", "user-proposer"); - Assert.Equal(StatutValidationTerme.Propose, proposed.StatutValidation); - - var validated = await service.ValiderTermAsync(proposed.Id, "user-moderator"); - Assert.Equal(StatutValidationTerme.Valide, validated.StatutValidation); - Assert.Equal("user-moderator", validated.ValideParId); - - var rejected = await service.RejeterTermAsync(1, "user-moderator"); - Assert.Equal(StatutValidationTerme.Rejete, rejected.StatutValidation); - } -} diff --git a/src/Yavsc.Org.Tests/Directory.Packages.props b/src/Yavsc.Org.Tests/Directory.Packages.props index af2ef948c..b267eafe2 100644 --- a/src/Yavsc.Org.Tests/Directory.Packages.props +++ b/src/Yavsc.Org.Tests/Directory.Packages.props @@ -4,8 +4,10 @@ - - - + + + + + diff --git a/src/Yavsc.Org.Tests/DumpHtml.cs b/src/Yavsc.Org.Tests/DumpHtml.cs index 2a6b4ce66..8caf3c3ee 100644 --- a/src/Yavsc.Org.Tests/DumpHtml.cs +++ b/src/Yavsc.Org.Tests/DumpHtml.cs @@ -1,7 +1,5 @@ -using Xunit; - namespace Yavsc.Org.Tests { [Collection("Yavsc Server")] diff --git a/src/Yavsc.Org.Tests/EstimateSignatureFileHelperTests.cs b/src/Yavsc.Org.Tests/EstimateSignatureFileHelperTests.cs index 91a4e1532..9de881afc 100644 --- a/src/Yavsc.Org.Tests/EstimateSignatureFileHelperTests.cs +++ b/src/Yavsc.Org.Tests/EstimateSignatureFileHelperTests.cs @@ -1,6 +1,11 @@ +using System; +using System.IO; using System.Security.Claims; using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; using Xunit; +using Yavsc.Models; using Yavsc.Models.Billing; using Yavsc.Server.Helpers; using Yavsc.Server.Models.FileSystem; @@ -92,13 +97,9 @@ public class EstimateSignatureFileHelperTests : IDisposable public async Task ReceiveEstimateSignatureAsync_rejects_null_payload() { var user = MakeUser("bob"); - // Capture TestContext.Current.CancellationToken outside the - // lambda so xUnit1051 sees a real CancellationToken argument - // (the lambda body runs on a different stack frame). - var ct = TestContext.Current.CancellationToken; await Assert.ThrowsAsync(() => EstimateSignatureFileHelper.ReceiveEstimateSignatureAsync( - user, 1L, SignatureType.Pro, payload: null!, token: ct)); + user, 1L, SignatureType.Pro, payload: null!)); } [Fact] @@ -106,10 +107,9 @@ public class EstimateSignatureFileHelperTests : IDisposable { var user = MakeUser("bob"); var payload = new SignaturePadPayload { Strokes = new[] { 1, 100, 100 } }; - var ct = TestContext.Current.CancellationToken; await Assert.ThrowsAsync(() => EstimateSignatureFileHelper.ReceiveEstimateSignatureAsync( - user, 0L, SignatureType.Pro, payload, token: ct)); + user, 0L, SignatureType.Pro, payload)); } // --- helpers ---------------------------------------------------- diff --git a/src/Yavsc.Org.Tests/Mandatory/BatchTests.cs b/src/Yavsc.Org.Tests/Mandatory/BatchTests.cs index 33065e013..35bb5aa59 100644 --- a/src/Yavsc.Org.Tests/Mandatory/BatchTests.cs +++ b/src/Yavsc.Org.Tests/Mandatory/BatchTests.cs @@ -3,17 +3,17 @@ using Microsoft.Extensions.DependencyInjection; using Yavsc.Models; using Yavsc.Server.Models.IT.SourceCode; using Microsoft.EntityFrameworkCore; -using Xunit; + using Yavsc.Server.Models.IT; namespace Yavsc.Org.Tests { [Collection("Yavsc Server")] [Trait("regression", "oui")] - public abstract class BaseTestContext : IClassFixture, IDisposable + public class BaseTestContext: IClassFixture, IDisposable { - protected readonly WebServerFixture _serverFixture; - protected readonly ITestOutputHelper _output; + public readonly WebServerFixture _serverFixture; + private readonly ITestOutputHelper _output; public BaseTestContext(ITestOutputHelper output, WebServerFixture fixture) { @@ -21,45 +21,6 @@ namespace Yavsc.Org.Tests this._output = output; } - public HttpClient CreateHttpClient() - { - return new HttpClient(new BypassSslValidationHandler()) - { - BaseAddress = new Uri(this._serverFixture.HttpsAuthority ?? throw new InvalidOperationException("Missing HttpsAuthority")) - }; - } - - /// - /// Issue a GET against on the - /// in-memory test server. Returns the raw HttpResponseMessage - /// without following redirects — the test asserts on the first - /// hop, not the eventual page. - /// - protected static async Task GetRaw( - HttpClient client, string relativePath) - { - Assert.NotNull(client); - var request = new HttpRequestMessage(HttpMethod.Get, relativePath); - return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); - } - /// - /// Smoke assertion: a GET on - /// returns 2xx (page served) or 3xx (redirect to login) or - /// 401/403 (anonymous rejected by [Authorize]). Anything else - /// — 404 (route missing), 5xx (server crash), connection - /// refused (host not started) — fails the test. - /// - protected static async Task AssertResponds( - HttpClient client, string relativePath) - { - var response = await GetRaw(client, relativePath); - var status = (int)response.StatusCode; - Assert.True( - status >= 200 && status < 400 || status == 401 || status == 403, - $"GET {relativePath} returned {status} {response.StatusCode}, " + - "expected 2xx/3xx (page or redirect) or 401/403 (auth required)."); - } - // FIXME write a scenario from an empty database [Fact] public void GitClone() { @@ -75,7 +36,7 @@ namespace Yavsc.Org.Tests var firstProject = dbContext.Project.Include(p => p.Repository).FirstOrDefault( p => p.Name == "Yavsc" ); - Assert.NotNull(firstProject); + Assert.NotNull (firstProject); var di = new DirectoryInfo(_serverFixture.SiteSettings.GitRepository); if (!di.Exists) di.Create(); @@ -83,7 +44,7 @@ namespace Yavsc.Org.Tests clone.Launch(firstProject); gitRepo = di.FullName; } - string gitRepo = null; + string gitRepo=null; private IConfigurationRoot configurationRoot; @@ -96,9 +57,9 @@ namespace Yavsc.Org.Tests public void Dispose() { - if (gitRepo != null) + if (gitRepo!=null) { - Directory.Delete(Path.Combine(gitRepo, "yavsc"), true); + Directory.Delete(Path.Combine(gitRepo,"yavsc"), true); } } } diff --git a/src/Yavsc.Org.Tests/Mandatory/Remoting.cs b/src/Yavsc.Org.Tests/Mandatory/Remoting.cs index 7def1a43f..be5b716f4 100644 --- a/src/Yavsc.Org.Tests/Mandatory/Remoting.cs +++ b/src/Yavsc.Org.Tests/Mandatory/Remoting.cs @@ -1,7 +1,6 @@ using System.Security.Cryptography.X509Certificates; using System.Net.Security; using IdentityModel.Client; -using Xunit; namespace Yavsc.Org.Tests { @@ -70,31 +69,6 @@ namespace Yavsc.Org.Tests } - [Fact] - public async Task GetSignin_returns_a_page() - { - using var client = new HttpClient(new BypassSslValidationHandler()) - { - BaseAddress = new Uri(this._serverFixture.HttpsAuthority ?? throw new InvalidOperationException("Missing HttpsAuthority")) - }; - await AssertResponds(client, "/signin"); - } - - - - [Fact] - public async Task GetOpenIdConfiguration_returns_ok() - { - using var client = CreateHttpClient(); - var response = await GetRaw(client, "/.well-known/openid-configuration"); - var payload = await response.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken - ); - - Assert.True( - response.IsSuccessStatusCode, - $"GET /.well-known/openid-configuration returned {(int)response.StatusCode} {response.StatusCode}. Body: {payload}"); - } public static IEnumerable GetLoginIntentData() { return new object[][] { new object[] { "testuser", "test" } }; @@ -150,5 +124,4 @@ namespace Yavsc.Org.Tests return true; } } - } diff --git a/src/Yavsc.Org.Tests/Mandatory/Services.cs b/src/Yavsc.Org.Tests/Mandatory/Services.cs index 8dc0aad36..fa9e60877 100644 --- a/src/Yavsc.Org.Tests/Mandatory/Services.cs +++ b/src/Yavsc.Org.Tests/Mandatory/Services.cs @@ -1,5 +1,4 @@ namespace Yavsc.Org.Tests.Mandatory; -using Xunit; [Collection("Yavsc Server")] [Trait("regression", "oui")] diff --git a/src/Yavsc.Org.Tests/NonRegression/AbstractTests.cs b/src/Yavsc.Org.Tests/NonRegression/AbstractTests.cs index 398542375..b85ae3cbd 100644 --- a/src/Yavsc.Org.Tests/NonRegression/AbstractTests.cs +++ b/src/Yavsc.Org.Tests/NonRegression/AbstractTests.cs @@ -1,6 +1,5 @@ using Yavsc.Server.Helpers; -using Xunit; namespace Yavsc { diff --git a/src/Yavsc.Org.Tests/NonRegression/ApplicationUserDisplayTemplateTests.cs b/src/Yavsc.Org.Tests/NonRegression/ApplicationUserDisplayTemplateTests.cs index 553d73e83..9fed6a157 100644 --- a/src/Yavsc.Org.Tests/NonRegression/ApplicationUserDisplayTemplateTests.cs +++ b/src/Yavsc.Org.Tests/NonRegression/ApplicationUserDisplayTemplateTests.cs @@ -1,6 +1,8 @@ -namespace Yavsc.Org.Tests.NonRegression; +using System.IO; using Xunit; +namespace Yavsc.Org.Tests.NonRegression; + /// /// Régression du 500 sur GET /BlogSpot/Details/{id} (auteur /// sans UserName) : le display template diff --git a/src/Yavsc.Org.Tests/NonRegression/AvatarFallbackTests.cs b/src/Yavsc.Org.Tests/NonRegression/AvatarFallbackTests.cs deleted file mode 100644 index 7513e43e6..000000000 --- a/src/Yavsc.Org.Tests/NonRegression/AvatarFallbackTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace Yavsc.Org.Tests.NonRegression; -using Xunit; - -/// -/// Non-regression: avatar requests under /avatars must never return 404 -/// for missing files. The pipeline falls back to static defaults under -/// /images/Users/icon_user*.png. -/// -public class AvatarFallbackTests : IClassFixture -{ - private readonly TestWebApplicationFactory _factory; - - public AvatarFallbackTests(TestWebApplicationFactory factory) - { - _factory = factory; - } - - [Theory] - [InlineData("/avatars/user-does-not-exist.png")] - [InlineData("/avatars/user-does-not-exist.s.png")] - [InlineData("/avatars/user-does-not-exist.xs.png")] - public async Task Missing_avatar_file_returns_default_image_instead_of_404(string path) - { - using var client = _factory.CreateClient(); - var ct = TestContext.Current.CancellationToken; - - var response = await client.GetAsync(path, ct); - - Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode); - Assert.Equal("image/png", response.Content.Headers.ContentType?.MediaType); - - var payload = await response.Content.ReadAsByteArrayAsync(ct); - Assert.True(payload.Length > 0, $"Expected a non-empty fallback image for {path}."); - } -} diff --git a/src/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs b/src/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs index 4e8ae0d6f..bf40438ad 100644 --- a/src/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs +++ b/src/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs @@ -1,10 +1,12 @@ using Microsoft.EntityFrameworkCore; +using Xunit; +using Yavsc; using Yavsc.Abstract.Workflow; using Yavsc.Helpers; using Yavsc.Models; +using Yavsc.Models.Billing; using Yavsc.Models.Haircut; using Yavsc.Services; -using Xunit; namespace Yavsc { diff --git a/src/Yavsc.Org.Tests/NonRegression/Database.cs b/src/Yavsc.Org.Tests/NonRegression/Database.cs index d079ec51c..01cd0a5dc 100644 --- a/src/Yavsc.Org.Tests/NonRegression/Database.cs +++ b/src/Yavsc.Org.Tests/NonRegression/Database.cs @@ -1,32 +1,34 @@ + + namespace Yavsc.Org.Tests.Mandatory { - using Xunit; - + [Collection("Database")] [Trait("regression", "II")] [Trait("dev", "wip")] - public class Database : IClassFixture + public class Database: IClassFixture, IDisposable { - readonly ITestOutputHelper output; readonly WebServerFixture _serverFixture; - - public Database(ITestOutputHelper output, WebServerFixture _serverFixture) + readonly ITestOutputHelper output; + public Database(WebServerFixture serverFixture, ITestOutputHelper output) { this.output = output; - this._serverFixture = _serverFixture; + _serverFixture = serverFixture; + } /// - /// Assuming we're using an account that may create databases, + /// Assuming we're using an account that may create databases, /// Install all our migrations in a fresh new database. /// - [Fact] - public void TestDatabaseMigration() + public void Dispose() { - // Test logic goes here - _serverFixture.ResetAndMigrateDatabase(); + if (_serverFixture!=null) + { + _serverFixture.Dispose(); + } + } } - } diff --git a/src/Yavsc.Org.Tests/NonRegression/EMailling.cs b/src/Yavsc.Org.Tests/NonRegression/EMailling.cs index eed2e9e18..453c26507 100644 --- a/src/Yavsc.Org.Tests/NonRegression/EMailling.cs +++ b/src/Yavsc.Org.Tests/NonRegression/EMailling.cs @@ -1,25 +1,12 @@ -using System.ComponentModel.DataAnnotations; -using System.Globalization; -using MailKit.Net.Smtp; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using MimeKit; using Yavsc.Interface; using Yavsc.Interfaces; -using Yavsc.Models.Relationship; using Yavsc.Org.Tests.Fakes; -using Yavsc.Services; -using Yavsc.Settings; -using Yavsc.ViewModels.Account; -using Xunit; namespace Yavsc.Org.Tests { - [Collection("EMaillingTeststCollection")] [Trait("regression", "II")] public class EMaillingTests : IClassFixture @@ -32,11 +19,11 @@ namespace Yavsc.Org.Tests { this.output = output; _serverFixture = serverFixture; - _logger = serverFixture.Logger!; + _logger = serverFixture.Logger; } [Fact] - public async Task SendEMailSynchrone() + public void SendEMailSynchrone() { using IServiceScope scope = _serverFixture.Services.CreateScope(); @@ -45,12 +32,12 @@ namespace Yavsc.Org.Tests scope.ServiceProvider.GetRequiredService()); output.WriteLine("SendEMailSynchrone ..."); - await mailSender.SendEmailAsync + mailSender.SendEmailAsync ( - _serverFixture.SiteSettings!.Owner.Name, - _serverFixture.SiteSettings!.Owner.EMail, + _serverFixture.SiteSettings.Owner.Name, + _serverFixture.SiteSettings.Owner.EMail, $"monthly email", - "test boby monthly email"); + "test boby monthly email").Wait(); // Assert the SMTP roundtrip was short-circuited by the // recording fake installed in WebServerFixture: exactly @@ -68,97 +55,5 @@ namespace Yavsc.Org.Tests client.Calls.Select(c => c.Kind).ToArray()); Assert.Equal(_serverFixture.SiteSettings.Owner.EMail, client.LastSentMessage?.To.Mailboxes.First().Address); } - - [Fact] - public void RegisterModel_rejects_invalid_email_format() - { - var model = new RegisterModel - { - UserName = "alice", - Email = "this is not an email", - Password = "Password123!", - ConfirmPassword = "Password123!" - }; - - var results = new List(); - var valid = Validator.TryValidateObject( - model, - new ValidationContext(model), - results, - validateAllProperties: true); - - Assert.False(valid); - Assert.Contains(results, r => r.MemberNames.Contains(nameof(RegisterModel.Email))); - } - - [Fact] - public async Task SendEmailAsync_ignores_smtp_recipient_rejection() - { - var sender = new MailSender( - Options.Create(new SiteSettings - { - Title = "Test", - Authority = "example.com", - Owner = new StaticContact { Name = "Test Owner", EMail = "owner@example.com" } - }), - Options.Create(new SmtpSettings - { - Host = "smtp.test.local", - Port = 465, - UserName = "test-user", - Password = "secret" - }), - NullLoggerFactory.Instance, - new TestStringLocalizer(), - new RejectingSmtpClientFactory()); - - var result = await sender.SendEmailAsync( - "Alice", - "contact@pschneider.fr", - "Welcome", - "hello"); - - Assert.Equal(string.Empty, result); - } - - private sealed class RejectingSmtpClientFactory : ISmtpClientFactory - { - public Yavsc.Interfaces.ISmtpClient CreateClient() => new RejectingSmtpClient(); - } - - private sealed class RejectingSmtpClient : Yavsc.Interfaces.ISmtpClient - { - public int Timeout { get; set; } - public void Connect(string host, int port, MailKit.Security.SecureSocketOptions options) { } - public void Authenticate(string userName, string password) { } - public Task SendAsync(MimeMessage message, CancellationToken cancellationToken = default) - { - throw new SmtpCommandException( - SmtpErrorCode.RecipientNotAccepted, - SmtpStatusCode.MailboxUnavailable, - "Recipient address rejected: User unknown in local recipient table"); - } - public void Disconnect(bool quit) { } - public void Dispose() { } - } - - private sealed class TestStringLocalizer : IStringLocalizer - { - public LocalizedString this[string name] => new(name, name); - public LocalizedString this[string name, params object[] arguments] => new(name, string.Format(CultureInfo.InvariantCulture, name, arguments)); - - public IEnumerable GetAllStrings(bool includeParentCultures) - => Enumerable.Empty(); - - public LocalizedString GetString(string name) - => new(name, name); - - public LocalizedString GetString(string name, params object[] arguments) - => new(name, string.Format(CultureInfo.InvariantCulture, name, arguments)); - - public IStringLocalizer WithCulture(CultureInfo culture) - => this; - } - } } diff --git a/src/Yavsc.Org.Tests/NonRegression/FileServerUrlHelpersTests.cs b/src/Yavsc.Org.Tests/NonRegression/FileServerUrlHelpersTests.cs deleted file mode 100644 index 88224b577..000000000 --- a/src/Yavsc.Org.Tests/NonRegression/FileServerUrlHelpersTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Yavsc.Abstract.Files; - -namespace Yavsc.Org.Tests.NonRegression; -using Xunit; - -public class FileServerUrlHelpersTests -{ - [Fact] - public void GetUserFilesBaseUri_appends_the_user_files_path_to_the_authority_root() - { - var baseUri = FileServerUrlHelpers.GetUserFilesBaseUri("https://oidc.example.org"); - - Assert.Equal("https://oidc.example.org/files/", baseUri.ToString()); - } - - [Fact] - public void GetUserFilesBaseUri_preserves_the_authority_and_discards_any_existing_path() - { - var baseUri = FileServerUrlHelpers.GetUserFilesBaseUri("https://oidc.example.org/signin"); - - Assert.Equal("https://oidc.example.org/files/", baseUri.ToString()); - } - - [Fact] - public void GetUserFilesUri_builds_an_absolute_file_url_from_a_relative_path() - { - var fileUri = FileServerUrlHelpers.GetUserFilesUri( - "https://oidc.example.org", - "/alice/inbox/report.pdf"); - - Assert.Equal("https://oidc.example.org/files/alice/inbox/report.pdf", fileUri.ToString()); - } - - [Fact] - public void GetUserFilesUri_rejects_blank_relative_path() - { - Assert.Throws( - () => FileServerUrlHelpers.GetUserFilesUri( - "https://oidc.example.org", - " ")); - } -} diff --git a/src/Yavsc.Org.Tests/NonRegression/PerformerCodeInputValidationTests.cs b/src/Yavsc.Org.Tests/NonRegression/PerformerCodeInputValidationTests.cs deleted file mode 100644 index 9d418404a..000000000 --- a/src/Yavsc.Org.Tests/NonRegression/PerformerCodeInputValidationTests.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using Yavsc.Models.Relationship; -using Yavsc.Models.Workflow; -using Xunit; - -namespace Yavsc.Tests.NonRegression; - -public class PerformerCodeInputValidationTests -{ - [Fact] - public void Validate_rejects_unknown_country_code() - { - var profile = CreateBaseProfile(); - profile.ExerciseCountryCode = "de"; - profile.SIREN = "123456789"; - - var results = Validate(profile); - - Assert.Contains(results, r => r.MemberNames.Contains(nameof(PerformerProfile.ExerciseCountryCode))); - } - - [Fact] - public void Validate_rejects_code_not_matching_country_rule() - { - var profile = CreateBaseProfile(); - profile.ExerciseCountryCode = "pt"; - profile.SIREN = "ABC123"; - - var results = Validate(profile); - - Assert.Contains(results, r => r.MemberNames.Contains(nameof(PerformerProfile.SIREN))); - } - - [Fact] - public void Validate_accepts_country_specific_valid_codes() - { - var fr = CreateBaseProfile(); - fr.ExerciseCountryCode = "fr"; - fr.SIREN = "123456789"; - - var en = CreateBaseProfile(); - en.ExerciseCountryCode = "en"; - en.SIREN = "AB12CD34"; - - var pt = CreateBaseProfile(); - pt.ExerciseCountryCode = "pt"; - pt.SIREN = "501964843"; - - Assert.Empty(Validate(fr)); - Assert.Empty(Validate(en)); - Assert.Empty(Validate(pt)); - } - - private static PerformerProfile CreateBaseProfile() - { - return new PerformerProfile - { - PerformerId = "perf-1", - SIREN = "123456789", - ExerciseCountryCode = "fr", - OrganizationAddress = new Location - { - Address = "1 rue du Test", - Latitude = 48.8566, - Longitude = 2.3522, - }, - }; - } - - private static List Validate(PerformerProfile profile) - { - var ctx = new ValidationContext(profile); - var results = new List(); - Validator.TryValidateObject(profile, ctx, results, validateAllProperties: true); - return results; - } -} diff --git a/src/Yavsc.Org.Tests/NonRegression/SetActivityCountryValidationViewTests.cs b/src/Yavsc.Org.Tests/NonRegression/SetActivityCountryValidationViewTests.cs deleted file mode 100644 index 73e9d8f0a..000000000 --- a/src/Yavsc.Org.Tests/NonRegression/SetActivityCountryValidationViewTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Xunit; -namespace Yavsc.Org.Tests.NonRegression; - -/// -/// Guard rails for the SetActivity performer settings page: -/// - countries are provided to the ComboBox via ViewBag.Countries -/// - client-side SIREN validation is wired to country-specific regex rules -/// - controller exposes the validation catalog to the view -/// -public class SetActivityCountryValidationViewTests -{ - [Fact] - public void SetActivity_cshtml_binds_country_combo_to_ViewBag_Countries() - { - var content = File.ReadAllText(ResolveSetActivityViewPath()); - - Assert.Contains("asp-for=\"ExerciseCountryCode\"", content); - Assert.Contains("asp-items=\"ViewBag.Countries\"", content); - } - - [Fact] - public void SetActivity_cshtml_contains_country_aware_siren_javascript_validation() - { - var content = File.ReadAllText(ResolveSetActivityViewPath()); - - Assert.Contains("$.validator.addMethod(\"sirenByCountry\"", content); - Assert.Contains("new RegExp(selectedRule.regex)", content); - Assert.Contains("const countryInput = $(\"#ExerciseCountryCode\")", content); - Assert.Contains("const sirenInput = $(\"#SIREN\")", content); - } - - [Fact] - public void ManageController_exposes_countries_and_country_validation_rules_to_view() - { - var content = File.ReadAllText(ResolveManageControllerPath()); - - Assert.Contains("ViewBag.Countries = countries;", content); - Assert.Contains("ViewBag.CountryCodeValidationRules = PerformerCodeInputValidationCatalog.Rules;", content); - Assert.Contains("ModelState.Remove(nameof(PerformerProfile.ExerciseCountryCode));", content); - } - - private static string ResolveSetActivityViewPath() - { - return ResolveFromWorkspaceRoot( - "src", "Yavsc.Org", "Views", "Manage", "SetActivity.cshtml"); - } - - private static string ResolveManageControllerPath() - { - return ResolveFromWorkspaceRoot( - "src", "Yavsc.Org", "Controllers", "Accounting", "ManageController.cs"); - } - - private static string ResolveFromWorkspaceRoot(params string[] relative) - { - var dir = AppContext.BaseDirectory; - for (var i = 0; i < 10 && dir is not null; i++) - { - var candidate = Path.Combine(new[] { dir }.Concat(relative).ToArray()); - if (File.Exists(candidate)) - { - return candidate; - } - - dir = Path.GetDirectoryName(dir); - } - - throw new FileNotFoundException( - "Could not locate test target from " + AppContext.BaseDirectory); - } -} diff --git a/src/Yavsc.Org.Tests/NonRegression/UserDisplayHelpersTests.cs b/src/Yavsc.Org.Tests/NonRegression/UserDisplayHelpersTests.cs index c20270cc8..a0249fa45 100644 --- a/src/Yavsc.Org.Tests/NonRegression/UserDisplayHelpersTests.cs +++ b/src/Yavsc.Org.Tests/NonRegression/UserDisplayHelpersTests.cs @@ -1,5 +1,6 @@ -using Yavsc.Abstract.Identity; using Xunit; +using Yavsc.Abstract; +using Yavsc.Abstract.Identity; namespace Yavsc.Org.Tests.NonRegression; @@ -13,7 +14,7 @@ namespace Yavsc.Org.Tests.NonRegression; /// ne voit rien — juste un 500 muet. /// /// Le fix passe par qui -/// retourne pour toute +/// retourne pour toute /// donnée partielle. Ces tests couvrent les trois formes de /// "donnée absente" : user null, UserName vide, UserName whitespace. /// @@ -22,21 +23,21 @@ public class UserDisplayHelpersTests [Fact] public void AvatarSrc_null_user_returns_default_avatar() { - Assert.Equal(Yavsc.Constants.DefaultAvatar, UserDisplayHelpers.AvatarSrc(null)); + Assert.Equal(YavscConstants.DefaultAvatar, UserDisplayHelpers.AvatarSrc(null)); } [Fact] public void AvatarSrc_user_with_empty_UserName_returns_default_avatar() { var user = new FakeUser { UserName = "" }; - Assert.Equal(Yavsc.Constants.DefaultAvatar, UserDisplayHelpers.AvatarSrc(user)); + Assert.Equal(YavscConstants.DefaultAvatar, UserDisplayHelpers.AvatarSrc(user)); } [Fact] public void AvatarSrc_user_with_whitespace_UserName_returns_default_avatar() { var user = new FakeUser { UserName = " " }; - Assert.Equal(Yavsc.Constants.DefaultAvatar, UserDisplayHelpers.AvatarSrc(user)); + Assert.Equal(YavscConstants.DefaultAvatar, UserDisplayHelpers.AvatarSrc(user)); } [Fact] @@ -46,7 +47,7 @@ public class UserDisplayHelpersTests // Le path doit matcher YavscConstants.AvatarsPath (minuscule), // pas un /Avatars/ avec S majuscule qui ne résout pas // dans le middleware de fichiers statiques. - var expected = $"{Yavsc.Constants.AvatarsPath}/alice.s.png"; + var expected = $"{YavscConstants.AvatarsPath}/alice.s.png"; Assert.Equal(expected, UserDisplayHelpers.AvatarSrc(user)); } diff --git a/src/Yavsc.Org.Tests/Services/FileSystemAuthManagerTests.cs b/src/Yavsc.Org.Tests/Services/FileSystemAuthManagerTests.cs deleted file mode 100644 index ff532390f..000000000 --- a/src/Yavsc.Org.Tests/Services/FileSystemAuthManagerTests.cs +++ /dev/null @@ -1,129 +0,0 @@ -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; -using Yavsc.Models; -using Yavsc.Models.Relationship; -using Yavsc.Services; -using Xunit; - -namespace Yavsc.Org.Tests.Services; - -public class FileSystemAuthManagerTests -{ - [Fact] - public void SetAccess_creates_acl_row_with_owner_path_and_flags() - { - using var scope = CreateScope(); - scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Read | FileAccessRight.Write); - - var row = scope.Db.CircleAuthorizationToFile.Single(); - - Assert.Equal(scope.Circle.Id, row.CircleId); - Assert.Equal("alice/documents/report.txt", row.Path); - Assert.Equal("alice", row.OwnerId); - Assert.Equal(FileAccessRight.Read | FileAccessRight.Write, row.Access); - } - - [Fact] - public void SetAccess_updates_existing_acl_row_without_duplicates() - { - using var scope = CreateScope(); - - scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Read); - scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Write); - - var rows = scope.Db.CircleAuthorizationToFile.ToList(); - - Assert.Single(rows); - Assert.Equal(FileAccessRight.Write, rows[0].Access); - } - - [Fact] - public void SetAccess_none_removes_existing_acl_row() - { - using var scope = CreateScope(); - - scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Read); - scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.None); - - Assert.Empty(scope.Db.CircleAuthorizationToFile); - } - - [Fact] - public void SetAccess_ignores_unknown_owner_prefix() - { - using var scope = CreateScope(); - - scope.Service.SetAccess(scope.Circle.Id, "unknown/documents/report.txt", FileAccessRight.Read); - - Assert.Empty(scope.Db.CircleAuthorizationToFile); - } - - [Fact] - public void Deleting_circle_cascades_file_acl_rows() - { - using var scope = CreateScope(); - - scope.Service.SetAccess(scope.Circle.Id, "alice/documents/report.txt", FileAccessRight.Read); - scope.Db.Circle.Remove(scope.Circle); - scope.Db.SaveChanges(); - - Assert.Empty(scope.Db.CircleAuthorizationToFile); - } - - private static TestScope CreateScope() - { - var connection = new SqliteConnection("Data Source=:memory:"); - connection.Open(); - - var options = new DbContextOptionsBuilder() - .UseSqlite(connection) - .Options; - - var db = new ApplicationDbContext(options); - db.Database.EnsureCreated(); - - db.Users.Add(new ApplicationUser - { - Id = "alice", - UserName = "alice", - Email = "alice@example.test" - }); - db.SaveChanges(); - - var circle = new Circle - { - OwnerId = "alice", - Name = "shared", - Public = false - }; - - db.Circle.Add(circle); - db.SaveChanges(); - - var service = new FileSystemAuthManager(db, Options.Create(new SiteSettings())); - return new TestScope(connection, db, service, circle); - } - - private sealed class TestScope : IDisposable - { - public TestScope(SqliteConnection connection, ApplicationDbContext db, FileSystemAuthManager service, Circle circle) - { - Connection = connection; - Db = db; - Service = service; - Circle = circle; - } - - public SqliteConnection Connection { get; } - public ApplicationDbContext Db { get; } - public FileSystemAuthManager Service { get; } - public Circle Circle { get; } - - public void Dispose() - { - Db.Dispose(); - Connection.Dispose(); - } - } -} diff --git a/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs b/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs index 61d774c57..50f92d63c 100644 --- a/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs +++ b/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs @@ -1,5 +1,4 @@ -using IdentityServer8.Stores; -using Microsoft.Extensions.DependencyInjection; +using System.Threading.Tasks; using Xunit; namespace Yavsc.Org.Tests.Smoke; @@ -19,7 +18,7 @@ namespace Yavsc.Org.Tests.Smoke; /// entire pipeline (routing + Razor + IdentityServer + EF + DI) /// is wired correctly end-to-end. /// -public class AccountSmokeTests : IClassFixture +public class AccountSmokeTests : SmokeTestBase, IClassFixture { private readonly TestWebApplicationFactory _factory; @@ -28,16 +27,10 @@ public class AccountSmokeTests : IClassFixture _factory = factory; } - - [Fact] - public async Task ResourceStore_get_all_resources_does_not_throw() + public async Task GetSignin_returns_a_page() { - using var scope = _factory.Services.CreateScope(); - var resourceStore = scope.ServiceProvider.GetRequiredService(); - - var exception = await Record.ExceptionAsync(resourceStore.GetAllResourcesAsync); - - Assert.Null(exception); + using var client = _factory.CreateClient(); + await AssertResponds(client, "/signin"); } } diff --git a/src/Yavsc.Org.Tests/Smoke/BlogSmokeTests.cs b/src/Yavsc.Org.Tests/Smoke/BlogSmokeTests.cs index e1aedcf0e..8aa953472 100644 --- a/src/Yavsc.Org.Tests/Smoke/BlogSmokeTests.cs +++ b/src/Yavsc.Org.Tests/Smoke/BlogSmokeTests.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Xunit; namespace Yavsc.Org.Tests.Smoke; @@ -16,13 +17,11 @@ namespace Yavsc.Org.Tests.Smoke; /// doc/architecture/decoupage-organisation.md. The smoke /// here asserts the front-end side of the BC. /// -public class BlogSmokeTests : BaseTestContext, IClassFixture +public class BlogSmokeTests : SmokeTestBase, IClassFixture { private readonly TestWebApplicationFactory _factory; - public BlogSmokeTests(TestWebApplicationFactory factory, ITestOutputHelper output, - WebServerFixture webServerFixture) - : base(output, webServerFixture) + public BlogSmokeTests(TestWebApplicationFactory factory) { _factory = factory; } diff --git a/src/Yavsc.Org.Tests/Smoke/SmokeTestBase.cs b/src/Yavsc.Org.Tests/Smoke/SmokeTestBase.cs new file mode 100644 index 000000000..1029effc2 --- /dev/null +++ b/src/Yavsc.Org.Tests/Smoke/SmokeTestBase.cs @@ -0,0 +1,57 @@ +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Xunit; + +namespace Yavsc.Org.Tests.Smoke; + +/// +/// Base for the smoke tests covering the production hosts +/// (Yavsc.Org / Yavsc.Api / Yavsc.Blogs). One smoke test per +/// bounded context (BC): each test hits one GET endpoint and +/// asserts a 2xx or 3xx status, with no follow-up redirect. +/// Together they satisfy the 'Tests d'intégration smoke par BC' +/// item of Jalon 0 in ROADMAP.md. +/// +/// Status code policy: +/// - 200 OK : endpoint serves a page. +/// - 302 / 301 : endpoint requires auth and redirects to login +/// (acceptable smoke signal: routing + middleware are wired). +/// - 401 / 403 : endpoint exists but rejects anonymous (acceptable +/// for API smoke tests where the smoke is "the host boots"). +/// Anything else (404, 500, connection refused) is a failure. +/// +public abstract class SmokeTestBase +{ + /// + /// Issue a GET against on the + /// in-memory test server. Returns the raw HttpResponseMessage + /// without following redirects — the test asserts on the first + /// hop, not the eventual page. + /// + protected static async Task GetRaw( + HttpClient client, string relativePath) + { + Assert.NotNull(client); + var request = new HttpRequestMessage(HttpMethod.Get, relativePath); + return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + } + + /// + /// Smoke assertion: a GET on + /// returns 2xx (page served) or 3xx (redirect to login) or + /// 401/403 (anonymous rejected by [Authorize]). Anything else + /// — 404 (route missing), 5xx (server crash), connection + /// refused (host not started) — fails the test. + /// + protected static async Task AssertResponds( + HttpClient client, string relativePath) + { + var response = await GetRaw(client, relativePath); + var status = (int)response.StatusCode; + Assert.True( + status >= 200 && status < 400 || status == 401 || status == 403, + $"GET {relativePath} returned {status} {response.StatusCode}, " + + "expected 2xx/3xx (page or redirect) or 401/403 (auth required)."); + } +} diff --git a/src/Yavsc.Org.Tests/StaticAssetsPathsTests.cs b/src/Yavsc.Org.Tests/StaticAssetsPathsTests.cs index d8cbd9b35..019622683 100644 --- a/src/Yavsc.Org.Tests/StaticAssetsPathsTests.cs +++ b/src/Yavsc.Org.Tests/StaticAssetsPathsTests.cs @@ -1,6 +1,8 @@ -namespace Yavsc.Org.Tests; +using System.IO; using Xunit; +using Xunit.v3; +namespace Yavsc.Org.Tests; /// /// Diagnostic-only test that confirms the static-assets manifests diff --git a/src/Yavsc.Org.Tests/TestUserMiddleware.cs b/src/Yavsc.Org.Tests/TestUserMiddleware.cs index 7117b2ed4..b88a75e39 100644 --- a/src/Yavsc.Org.Tests/TestUserMiddleware.cs +++ b/src/Yavsc.Org.Tests/TestUserMiddleware.cs @@ -1,4 +1,6 @@ +using System.Linq; using System.Security.Claims; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Yavsc.Tests.Shared; diff --git a/src/Yavsc.Org.Tests/TestWebApplicationFactory.cs b/src/Yavsc.Org.Tests/TestWebApplicationFactory.cs index b85cba11c..dfd6edeab 100644 --- a/src/Yavsc.Org.Tests/TestWebApplicationFactory.cs +++ b/src/Yavsc.Org.Tests/TestWebApplicationFactory.cs @@ -21,54 +21,9 @@ namespace Yavsc.Org.Tests; /// so that User.GetUserId() /// in user code sees a logged-in identity derived from the same /// header. -/// -/// Each instance gets its own in-memory database, identified by a -/// GUID generated in the constructor. The connection string -/// (ConnectionStrings:YavscConnection) is set as an -/// environment variable (ConnectionStrings__YavscConnection) -/// in the constructor and unset in , so the -/// production AddIdentityDBAndStores registers DbContext -/// instances against this fixture's own store. Without this, the -/// "InMemory" connection string from -/// appsettings-org.Testing.json would route every -/// instance — and any -/// running in the same process — to -/// the same backing store, leaking state between fixtures. -/// -/// Env vars are used (rather than ConfigureAppConfiguration or -/// UseSetting) because WebApplicationFactory applies -/// those too late: Program.Main has already captured the -/// connection string in AddIdentityDBAndStores by the time -/// the test host's overrides take effect. Env vars are the last -/// provider added in AddConfiguration (see -/// Yavsc.Server/Helpers/ConfigHelpers.cs), so they win. /// public class TestWebApplicationFactory : WebApplicationFactory { - private readonly string _fixtureId = Guid.NewGuid().ToString("N"); - - // ASP.NET Core's environment-variable configuration provider uses - // the key ConnectionStrings__YavscConnection (double underscore - // for the section separator). Set it before the host starts so - // the per-fixture connection string wins over - // appsettings-org.Testing.json. We do NOT touch the appsettings - // file; env vars take precedence in the configuration pipeline - // (see AddConfiguration in Yavsc.Server/Helpers/ConfigHelpers.cs, - // which adds AddEnvironmentVariables last). - private static readonly object _envLock = new(); - private bool _envSet; - - public TestWebApplicationFactory() - { - lock (_envLock) - { - Environment.SetEnvironmentVariable( - "ConnectionStrings__YavscConnection", - InMemoryDatabaseName.For(_fixtureId)); - _envSet = true; - } - } - protected override void ConfigureWebHost(IWebHostBuilder builder) { // UseEnvironment("Testing") puts the host in a dedicated @@ -95,18 +50,4 @@ public class TestWebApplicationFactory : WebApplicationFactory services.AddTransient(); }); } - - protected override void Dispose(bool disposing) - { - if (disposing && _envSet) - { - lock (_envLock) - { - Environment.SetEnvironmentVariable( - "ConnectionStrings__YavscConnection", null); - _envSet = false; - } - } - base.Dispose(disposing); - } } diff --git a/src/Yavsc.Org.Tests/WebServerFixture.cs b/src/Yavsc.Org.Tests/WebServerFixture.cs index 5ca1daa06..a623bc9e4 100644 --- a/src/Yavsc.Org.Tests/WebServerFixture.cs +++ b/src/Yavsc.Org.Tests/WebServerFixture.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Net; using System.Net.Sockets; +using Yavsc; using Yavsc.Extensions; using Yavsc.Interfaces; using Yavsc.Models; @@ -17,7 +18,6 @@ using Yavsc.Server.Helpers; using Yavsc.Tests.Shared; using Client = IdentityServer8.EntityFramework.Entities.Client; using Yavsc.Org.Tests.Fakes; -using Xunit; namespace Yavsc.Org.Tests; @@ -43,17 +43,6 @@ public sealed class WebServerFixture : WebHostFixture { private static readonly int _httpsPort = GetAvailableLoopbackPort(); - // One in-memory database name for the whole process: WebHostFixture - // is a per-process singleton (see _app, _isInitialized, _sharedServices - // in the base class), so every WebServerFixture instance shares the - // same backing store. That is intentional — the "Yavsc Server" test - // collection groups tests that should see the same seeded state, and - // re-initialising the store per fixture would just regress the - // order-dependence we are trying to eliminate. The GUID still matters - // because TestWebApplicationFactory and WebServerFixture must not - // collide in the in-memory store; see InMemoryDatabaseName. - private static readonly string _fixtureId = Guid.NewGuid().ToString("N"); - protected override int HttpsPort => _httpsPort; private static IConfiguration? _sharedConfiguration; @@ -64,7 +53,6 @@ public sealed class WebServerFixture : WebHostFixture private static string? _sharedTestingUserName; private static string? _sharedTestingUserPassword; private static string? _sharedTestingUserEmail; - private static string? _sharedHttpsAuthority; private static RecordingSmtpClientFactory? _sharedSmtpClientFactory; public IConfiguration? Configuration { get; private set; } @@ -80,19 +68,9 @@ public sealed class WebServerFixture : WebHostFixture public RecordingSmtpClientFactory? SmtpClientFactory { get; private set; } public ILogger? Logger { get; internal set; } - public string? HttpsAuthority { get; private set; } - - protected override WebApplicationOptions CreateBuilderOptions() - { - return new WebApplicationOptions - { - ApplicationName = typeof(Yavsc.Program).Assembly.GetName().Name - }; - } - protected override WebApplication BuildApp(WebApplicationBuilder builder) { - HttpsAuthority = $"https://localhost:{HttpsPort}"; + var authority = $"https://localhost:{_httpsPort}"; // WebApplication.CreateBuilder defaults WebRootPath to // {ContentRoot}/wwwroot. The test assembly runs from @@ -102,8 +80,8 @@ public sealed class WebServerFixture : WebHostFixture // can resolve it. The AddConfiguration extension takes care of // that plus the in-memory overrides below. builder.AddConfiguration(null).AddInMemoryCollection(new Dictionary - { - [$"ConnectionStrings:{Yavsc.Constants.YavscConnectionStringName}"] = InMemoryDatabaseName.For(_fixtureId), + { + [$"ConnectionStrings:{YavscConstants.YavscConnectionStringName}"] = "InMemory", // SMTP test config: UserName non-null so MailSender // exercises the Authenticate branch — the // RecordingSmtpClient captures it. @@ -111,7 +89,7 @@ public sealed class WebServerFixture : WebHostFixture ["Smtp:Port"] = "465", ["Smtp:UserName"] = "test-user", ["Smtp:Password"] = "test-pass", - ["Site:Authority"] = HttpsAuthority + ["Site:Authority"] = authority }); Configuration = builder.Configuration; @@ -194,7 +172,6 @@ public sealed class WebServerFixture : WebHostFixture _sharedTestingUserName = TestingUserName; _sharedTestingUserPassword = TestingUserPassword; _sharedTestingUserEmail = TestingUserEmail; - _sharedHttpsAuthority = HttpsAuthority; _sharedLogger = app.Services.GetRequiredService().CreateLogger(); Logger = _sharedLogger; SmtpClientFactory = smtpFactory; @@ -202,49 +179,6 @@ public sealed class WebServerFixture : WebHostFixture return app; } - public void ResetAndMigrateDatabase() - { - using var scope = Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - db.Database.EnsureDeleted(); - db.Database.EnsureCreated(); - if (db.Database.IsRelational()) - { - db.Database.Migrate(); - ReseedAuthTestData(scope); - return; - } - ReseedAuthTestData(scope); - } - - private void ReseedAuthTestData(IServiceScope scope) - { - TestingUserName ??= "Tester"; - TestingUserPassword ??= "Test123!"; - TestingUserEmail ??= "test@no-reply.com"; - TestClientId ??= "testClientId"; - TestClientSecret ??= Guid.CreateVersion7().ToString(); - - TestingUser = null; - EnsureUser(TestingUserName, TestingUserPassword, TestingUserEmail, scope); - - var db = scope.ServiceProvider.GetRequiredService(); - TestingUser = db.Users.FirstOrDefault(u => u.UserName == TestingUserName); - - var configDb = scope.ServiceProvider.GetRequiredService(); - var hasClient = configDb.Set().Any(c => c.ClientId == TestClientId); - if (!hasClient) - { - AddAuthorizedClient(scope, TestClientId, TestClientSecret); - } - - _sharedTestClientId = TestClientId; - _sharedTestClientSecret = TestClientSecret; - _sharedTestingUserName = TestingUserName; - _sharedTestingUserPassword = TestingUserPassword; - _sharedTestingUserEmail = TestingUserEmail; - } - protected override async Task ConfigurePipelineAsync(WebApplication app) { // The MSBuild target CopyYavscOrgStaticAssets in @@ -267,7 +201,6 @@ public sealed class WebServerFixture : WebHostFixture TestingUserName = _sharedTestingUserName; TestingUserPassword = _sharedTestingUserPassword; TestingUserEmail = _sharedTestingUserEmail; - HttpsAuthority = _sharedHttpsAuthority; SmtpClientFactory = _sharedSmtpClientFactory; Configuration = _sharedConfiguration; SiteSettings = _sharedSiteSettings; diff --git a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj index 58bafb8a0..88842cb45 100644 --- a/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj +++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj @@ -11,7 +11,7 @@ $(MSBuildProjectDirectory)\test.runsettings 1.1.0.0 1.1.0.0 - 1.1.0-beta.1+183.Branch.release-1.0.8-rc8.Sha.6cff3db32ecf72c0d2d430b7002fa7816a34e070 + 1.1.0-beta.1+1.Branch.release-1.0.8-rc1.Sha.1167169aa89e1bf25290e9a152d27b357a500ab3 1.1.0-beta.1 @@ -49,13 +49,10 @@ - - + - - PreserveNewest - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Yavsc.Org/Extensions/HostingExtensions.cs b/src/Yavsc.Org/Extensions/HostingExtensions.cs index e986620f1..0cae23a72 100644 --- a/src/Yavsc.Org/Extensions/HostingExtensions.cs +++ b/src/Yavsc.Org/Extensions/HostingExtensions.cs @@ -18,7 +18,6 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Razor; -using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -170,20 +169,10 @@ public static class HostingExtensions public static IdentityBuilder AddIdentityDBAndStores(this WebApplicationBuilder builder) { IServiceCollection services = builder.Services; + var connectionString = builder.Configuration.GetConnectionString(YavscConstants.YavscConnectionStringName); - services.AddDbContext((sp, options) => + services.AddDbContext(options => { - // Read the connection string at DbContext construction time - // rather than at AddDbContext registration time, so test - // fixtures (e.g. WebApplicationFactory) can - // override the value via the host's IConfiguration before - // any DbContext is built. Reading it eagerly at the top of - // this method would freeze whatever was in configuration - // when Program.Main ran — too early for the test host's - // ConfigureAppConfiguration / UseSetting hooks to apply. - var connectionString = sp.GetRequiredService() - .GetConnectionString(Constants.YavscConnectionStringName); - if (UsesInMemoryProvider(connectionString)) { options.UseInMemoryDatabase(connectionString); @@ -208,7 +197,7 @@ public static class HostingExtensions options.SignIn.RequireConfirmedAccount = builder.Environment.IsEnvironment( builder.Environment.EnvironmentName); options.ClaimsIdentity.UserNameClaimType = JwtClaimTypes.PreferredUserName; - options.ClaimsIdentity.RoleClaimType = Constants.RoleClaimType; + options.ClaimsIdentity.RoleClaimType = YavscConstants.RoleClaimType; } ) .AddEntityFrameworkStores(); @@ -250,18 +239,18 @@ public static class HostingExtensions { policy .RequireAuthenticatedUser() - .RequireClaim(Constants.RoleClaimType, - new string[] { Constants.PerformerGroupName, Constants.AdminGroupName }) + .RequireClaim(YavscConstants.RoleClaimType, + new string[] { YavscConstants.PerformerGroupName, YavscConstants.AdminGroupName }) ; }); options.AddPolicy("AdministratorOnly", policy => { _ = policy .RequireAuthenticatedUser() - .RequireClaim(Constants.RoleClaimType, Constants.AdminGroupName); + .RequireClaim(YavscConstants.RoleClaimType, YavscConstants.AdminGroupName); }); - options.AddPolicy("FrontOffice", policy => policy.RequireRole(Constants.FrontOfficeGroupName)); + options.AddPolicy("FrontOffice", policy => policy.RequireRole(YavscConstants.FrontOfficeGroupName)); // options.AddPolicy("EmployeeId", policy => policy.RequireClaim("EmployeeId", "123", "456")); // options.AddPolicy("BuildingEntry", policy => policy.Requirements.Add(new OfficeEntryRequirement())); @@ -325,23 +314,12 @@ public static class HostingExtensions { options.ClaimsIdentity.UserIdClaimType = JwtClaimTypes.Subject; options.ClaimsIdentity.UserNameClaimType = JwtClaimTypes.Name; - options.ClaimsIdentity.RoleClaimType = Constants.RoleClaimType; + options.ClaimsIdentity.RoleClaimType = YavscConstants.RoleClaimType; }); var migrationsAssembly = typeof(Program).GetTypeInfo().Assembly.GetName().Name; + var connectionString = builder.Configuration.GetConnectionString(YavscConstants.YavscConnectionStringName); - // The IdentityServer8.EntityFramework ConfigurationStoreOptions - // and OperationalStoreOptions expose ConfigureDbContext as an - // Action with no service-provider - // access, so the connection string has to be captured here at - // registration time. For the production runtime this is fine: - // the connection string does not change after startup. For - // tests, this is the one knob we cannot push into the per-fixture - // config pipeline; the TestWebApplicationFactory bridge instead - // sets ConnectionStrings__YavscConnection as an environment - // variable, which AddEnvironmentVariables picks up as the last - // configuration provider in AddConfiguration. See - // Yavsc.Server/Helpers/ConfigHelpers.cs. - var connectionString = builder.Configuration.GetConnectionString(Constants.YavscConnectionStringName); + string sqliteConnectionString = $"Data Source={Path.Combine(Path.GetTempPath(), "yavsc_test.db")}"; var identityServerBuilder = builder.Services.AddIdentityServer(options => { @@ -622,13 +600,7 @@ public static class HostingExtensions private static bool UsesInMemoryProvider(string connectionString) { - // Test fixtures may suffix the connection string with a - // per-fixture GUID (see InMemoryDatabaseName in - // Yavsc.Tests.Shared) to keep their in-memory stores - // isolated. The base name "InMemory" is still what - // identifies an in-memory provider — anything starting - // with it is one. - return connectionString.StartsWith(InMemoryProviderName, StringComparison.OrdinalIgnoreCase); + return string.Equals(connectionString, InMemoryProviderName, StringComparison.OrdinalIgnoreCase); } private static Action EnsureDefaultApplicationScopes() @@ -804,7 +776,6 @@ public static class HostingExtensions // silent refresh path to work; without it IdentityServer // refuses to issue a refresh_token. "blogs", - "api", IdentityServer8.IdentityServerConstants.StandardScopes.OpenId, IdentityServer8.IdentityServerConstants.StandardScopes.Profile, IdentityServer8.IdentityServerConstants.StandardScopes.OfflineAccess, @@ -1249,7 +1220,7 @@ ADD COLUMN IF NOT EXISTS ""Moderated"" boolean NOT NULL DEFAULT FALSE;"); Config.UserFilesOptions = new FileServerOptions() { FileProvider = new PhysicalFileProvider(AbstractFileSystemHelpers.UserFilesDirName), - RequestPath = PathString.FromUriComponent(Constants.UserFilesPath), + RequestPath = PathString.FromUriComponent(YavscConstants.UserFilesPath), EnableDirectoryBrowsing = enableDirectoryBrowsing, }; Config.UserFilesOptions.EnableDefaultFiles = true; @@ -1262,7 +1233,7 @@ ADD COLUMN IF NOT EXISTS ""Moderated"" boolean NOT NULL DEFAULT FALSE;"); Config.AvatarsOptions = new FileServerOptions() { FileProvider = new PhysicalFileProvider(Config.AvatarsDirName), - RequestPath = PathString.FromUriComponent(Constants.AvatarsPath), + RequestPath = PathString.FromUriComponent(YavscConstants.AvatarsPath), EnableDirectoryBrowsing = enableDirectoryBrowsing }; @@ -1273,7 +1244,7 @@ ADD COLUMN IF NOT EXISTS ""Moderated"" boolean NOT NULL DEFAULT FALSE;"); Config.GitOptions = new FileServerOptions() { FileProvider = new PhysicalFileProvider(Config.GitDirName), - RequestPath = PathString.FromUriComponent(Constants.GitPath), + RequestPath = PathString.FromUriComponent(YavscConstants.GitPath), EnableDirectoryBrowsing = enableDirectoryBrowsing, }; Config.GitOptions.DefaultFilesOptions.DefaultFileNames.Add("index.md"); @@ -1283,52 +1254,10 @@ ADD COLUMN IF NOT EXISTS ""Moderated"" boolean NOT NULL DEFAULT FALSE;"); app.UseFileServer(Config.AvatarsOptions); - app.Use(async (context, next) => - { - await next(); - - if (context.Response.StatusCode != StatusCodes.Status404NotFound - || context.Response.HasStarted - || !HttpMethods.IsGet(context.Request.Method) - || !context.Request.Path.StartsWithSegments(Constants.AvatarsPath, out var avatarFile)) - { - return; - } - - var webHostEnvironment = app.ApplicationServices.GetRequiredService(); - var fallbackAsset = ResolveAvatarFallbackAsset(avatarFile); - var fallbackFile = webHostEnvironment.WebRootFileProvider.GetFileInfo(fallbackAsset.TrimStart('/')); - if (!fallbackFile.Exists) - { - return; - } - - context.Response.StatusCode = StatusCodes.Status200OK; - context.Response.ContentType = "image/png"; - await context.Response.SendFileAsync(fallbackFile); - }); - app.UseFileServer(Config.GitOptions); app.UseStaticFiles(); return app; } - private static string ResolveAvatarFallbackAsset(PathString avatarFile) - { - var fileName = avatarFile.Value ?? string.Empty; - - if (fileName.EndsWith(".xs.png", StringComparison.OrdinalIgnoreCase)) - { - return "/images/Users/icon_user.xs.png"; - } - - if (fileName.EndsWith(".s.png", StringComparison.OrdinalIgnoreCase)) - { - return "/images/Users/icon_user.s.png"; - } - - return Constants.DefaultAvatar; - } - } diff --git a/src/Yavsc.Org/Helpers/Ansi2HtmlEncoder.cs b/src/Yavsc.Org/Helpers/Ansi2HtmlEncoder.cs index fb832a5d6..61f8c51b5 100644 --- a/src/Yavsc.Org/Helpers/Ansi2HtmlEncoder.cs +++ b/src/Yavsc.Org/Helpers/Ansi2HtmlEncoder.cs @@ -3,7 +3,9 @@ // paul schneider 19/06/2018 15:58 20182018 6 19 // */ +using System.IO; using System.Diagnostics; +using System.Threading.Tasks; namespace Yavsc.Helpers { diff --git a/src/Yavsc.Org/Helpers/ControllerHelpers.cs b/src/Yavsc.Org/Helpers/ControllerHelpers.cs index 9f92c9e77..07937e4a0 100644 --- a/src/Yavsc.Org/Helpers/ControllerHelpers.cs +++ b/src/Yavsc.Org/Helpers/ControllerHelpers.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Yavsc.Abstract.Models.Messaging; diff --git a/src/Yavsc.Org/Helpers/ListItemHelpers.cs b/src/Yavsc.Org/Helpers/ListItemHelpers.cs index ea81e24ec..a91016223 100644 --- a/src/Yavsc.Org/Helpers/ListItemHelpers.cs +++ b/src/Yavsc.Org/Helpers/ListItemHelpers.cs @@ -1,3 +1,6 @@ + +using System.Collections.Generic; +using System.Linq; using Microsoft.AspNetCore.Mvc.Rendering; using Yavsc.Models; using Yavsc.Models.Workflow; diff --git a/src/Yavsc.Org/Helpers/OAuthHelpers.cs b/src/Yavsc.Org/Helpers/OAuthHelpers.cs index 668d720ef..efd3942b6 100644 --- a/src/Yavsc.Org/Helpers/OAuthHelpers.cs +++ b/src/Yavsc.Org/Helpers/OAuthHelpers.cs @@ -1,3 +1,4 @@ +using System; using System.Security.Cryptography; namespace Yavsc.Helpers { diff --git a/src/Yavsc.Org/Helpers/PageHelpers.cs b/src/Yavsc.Org/Helpers/PageHelpers.cs index 4258565ad..c094495e0 100644 --- a/src/Yavsc.Org/Helpers/PageHelpers.cs +++ b/src/Yavsc.Org/Helpers/PageHelpers.cs @@ -1,5 +1,8 @@ +using System; +using System.Collections.Generic; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Extensions.Localization; namespace Yavsc.Server.Helpers diff --git a/src/Yavsc.Org/Helpers/TeXHelpers.cs b/src/Yavsc.Org/Helpers/TeXHelpers.cs index daacb7417..9e7ed330a 100644 --- a/src/Yavsc.Org/Helpers/TeXHelpers.cs +++ b/src/Yavsc.Org/Helpers/TeXHelpers.cs @@ -1,4 +1,7 @@ +using System; using System.Diagnostics; +using System.IO; +using System.Linq; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; diff --git a/src/Yavsc.Org/Migrations/20260309015232_init.cs b/src/Yavsc.Org/Migrations/20260309015232_init.cs index a61679882..894d57cb5 100644 --- a/src/Yavsc.Org/Migrations/20260309015232_init.cs +++ b/src/Yavsc.Org/Migrations/20260309015232_init.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using System; +using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable @@ -11,10 +12,6 @@ namespace Yavsc.Migrations /// protected override void Up(MigrationBuilder migrationBuilder) { - var declarationDateDefaultSql = ActiveProvider == "Microsoft.EntityFrameworkCore.Sqlite" - ? "CURRENT_TIMESTAMP" - : "LOCALTIMESTAMP"; - migrationBuilder.CreateTable( name: "Activities", columns: table => new @@ -1488,7 +1485,7 @@ namespace Yavsc.Migrations Platform = table.Column(type: "text", nullable: true), Version = table.Column(type: "text", nullable: true), DeviceOwnerId = table.Column(type: "text", nullable: true), - DeclarationDate = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: declarationDateDefaultSql), + DeclarationDate = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "LOCALTIMESTAMP"), LatestActivityUpdate = table.Column(type: "timestamp with time zone", nullable: true) }, constraints: table => diff --git a/src/Yavsc.Org/Migrations/20260604103455_pending.cs b/src/Yavsc.Org/Migrations/20260604103455_pending.cs index 1f34c2941..5f9eb8b91 100644 --- a/src/Yavsc.Org/Migrations/20260604103455_pending.cs +++ b/src/Yavsc.Org/Migrations/20260604103455_pending.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using System; +using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable diff --git a/src/Yavsc.Org/Migrations/20260706013420_activityModerated.cs b/src/Yavsc.Org/Migrations/20260706013420_activityModerated.cs index d99d3296a..79e4000d3 100644 --- a/src/Yavsc.Org/Migrations/20260706013420_activityModerated.cs +++ b/src/Yavsc.Org/Migrations/20260706013420_activityModerated.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using System; +using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable diff --git a/src/Yavsc.Org/Migrations/20260820232152_DropCommentFromCircleAuthorizationToBlogPost.Designer.cs b/src/Yavsc.Org/Migrations/20260820232152_DropCommentFromCircleAuthorizationToBlogPost.Designer.cs deleted file mode 100644 index a9af372c5..000000000 --- a/src/Yavsc.Org/Migrations/20260820232152_DropCommentFromCircleAuthorizationToBlogPost.Designer.cs +++ /dev/null @@ -1,4645 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Yavsc.Models; - -#nullable disable - -namespace Yavsc.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260820232152_DropCommentFromCircleAuthorizationToBlogPost")] - partial class DropCommentFromCircleAuthorizationToBlogPost - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("AllowedAccessTokenSigningAlgorithms") - .HasColumnType("text"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("ApiResources"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Scope") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceSecrets"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("ApiScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("ScopeId1") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.HasIndex("ScopeId1"); - - b.ToTable("ApiScopeClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("ScopeId1") - .HasColumnType("integer"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.HasIndex("ScopeId1"); - - b.ToTable("ApiScopeProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("AbsoluteRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenType") - .HasColumnType("integer"); - - b.Property("AllowAccessTokensViaBrowser") - .HasColumnType("boolean"); - - b.Property("AllowOfflineAccess") - .HasColumnType("boolean"); - - b.Property("AllowPlainTextPkce") - .HasColumnType("boolean"); - - b.Property("AllowRememberConsent") - .HasColumnType("boolean"); - - b.Property("AllowedIdentityTokenSigningAlgorithms") - .HasColumnType("text"); - - b.Property("AlwaysIncludeUserClaimsInIdToken") - .HasColumnType("boolean"); - - b.Property("AlwaysSendClientClaims") - .HasColumnType("boolean"); - - b.Property("AuthorizationCodeLifetime") - .HasColumnType("integer"); - - b.Property("BackChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("BackChannelLogoutUri") - .HasColumnType("text"); - - b.Property("ClientClaimsPrefix") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("ClientName") - .HasColumnType("text"); - - b.Property("ClientUri") - .HasColumnType("text"); - - b.Property("ConsentLifetime") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DeviceCodeLifetime") - .HasColumnType("integer"); - - b.Property("EnableLocalLogin") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutUri") - .HasColumnType("text"); - - b.Property("IdentityTokenLifetime") - .HasColumnType("integer"); - - b.Property("IncludeJwtId") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("LogoUri") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("PairWiseSubjectSalt") - .HasColumnType("text"); - - b.Property("ProtocolType") - .HasColumnType("text"); - - b.Property("RefreshTokenExpiration") - .HasColumnType("integer"); - - b.Property("RefreshTokenUsage") - .HasColumnType("integer"); - - b.Property("RequireClientSecret") - .HasColumnType("boolean"); - - b.Property("RequireConsent") - .HasColumnType("boolean"); - - b.Property("RequirePkce") - .HasColumnType("boolean"); - - b.Property("RequireRequestObject") - .HasColumnType("boolean"); - - b.Property("SlidingRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("UpdateAccessTokenClaimsOnRefresh") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.Property("UserCodeType") - .HasColumnType("text"); - - b.Property("UserSsoLifetime") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("Clients"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Origin") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientCorsOrigins"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("GrantType") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientGrantTypes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Provider") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientIdPRestrictions"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("PostLogoutRedirectUri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientPostLogoutRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("RedirectUri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Scope") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientSecrets"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.DeviceFlowCodes", b => - { - b.Property("UserCode") - .HasColumnType("text"); - - b.Property("DeviceCode") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasColumnType("text"); - - b.Property("SubjectId") - .HasColumnType("text"); - - b.HasKey("UserCode", "DeviceCode"); - - b.ToTable("DeviceFlowCodes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("IdentityResources"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.PersistedGrant", b => - { - b.Property("Key") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("ConsumedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasColumnType("text"); - - b.Property("SubjectId") - .HasColumnType("text"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Key"); - - b.ToTable("PersistedGrants"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("Yavsc.Abstract.Identity.ClientProviderInfo", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Avatar") - .HasColumnType("text"); - - b.Property("BillingAddressId") - .HasColumnType("bigint"); - - b.Property("EMail") - .HasColumnType("text"); - - b.Property("Phone") - .HasColumnType("text"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("ClientProviderInfo"); - }); - - modelBuilder.Entity("Yavsc.Abstract.Models.Messaging.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Target") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("body") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("click_action") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("color") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("icon") - .ValueGeneratedOnAdd() - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasDefaultValue("exclam"); - - b.Property("sound") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("tag") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("title") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.HasKey("Id"); - - b.ToTable("Notification"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.Ban", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.Property("TargetId") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("TargetId"); - - b.ToTable("Ban"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.BlackListed", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.HasIndex("UserId"); - - b.ToTable("BlackListed"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToBlogPost", b => - { - b.Property("CircleId") - .HasColumnType("bigint"); - - b.Property("BlogPostId") - .HasColumnType("bigint"); - - b.HasKey("CircleId", "BlogPostId"); - - b.HasIndex("BlogPostId"); - - b.ToTable("CircleAuthorizationToBlogPost"); - }); - - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("ContactCredits") - .HasColumnType("bigint"); - - b.Property("Credits") - .HasColumnType("numeric"); - - b.HasKey("UserId"); - - b.ToTable("BankStatus"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("AllowMonthlyEmail") - .HasColumnType("boolean"); - - b.Property("Avatar") - .ValueGeneratedOnAdd() - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasDefaultValue("/images/Users/icon_user.png"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("DedicatedGoogleCalendar") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("DiskQuota") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasDefaultValue(524288000L); - - b.Property("DiskUsage") - .HasColumnType("bigint"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("FullName") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("MaxFileSize") - .HasColumnType("bigint"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("PostalAddressId") - .HasColumnType("bigint"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasAlternateKey("Email"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("PostalAddressId"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Yavsc.Models.BalanceImpact", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BalanceId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExecDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Impact") - .HasColumnType("numeric"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("BalanceId"); - - b.ToTable("BalanceImpact"); - }); - - modelBuilder.Entity("Yavsc.Models.Bank.BankIdentity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountNumber") - .HasColumnType("text"); - - b.Property("BIC") - .HasColumnType("text"); - - b.Property("BankCode") - .HasColumnType("text"); - - b.Property("BankedKey") - .HasColumnType("integer"); - - b.Property("IBAN") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("WicketCode") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("BankIdentity"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.CommandLine", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Count") - .HasColumnType("integer"); - - b.Property("Currency") - .HasColumnType("text"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.Property("EstimateTemplateId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("UnitaryCost") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.HasIndex("EstimateId"); - - b.HasIndex("EstimateTemplateId"); - - b.ToTable("CommandLine"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AttachedFilesString") - .HasColumnType("text"); - - b.Property("AttachedGraphicsString") - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CommandId") - .HasColumnType("bigint"); - - b.Property("CommandType") - .IsRequired() - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("ProviderValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("CommandId"); - - b.HasIndex("OwnerId"); - - b.ToTable("Estimates"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EstimateTemplates"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.ExceptionSIREN", b => - { - b.Property("SIREN") - .HasColumnType("text"); - - b.HasKey("SIREN"); - - b.ToTable("ExceptionsSIREN"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CapturedAtUtc") - .HasColumnType("timestamp with time zone"); - - b.Property("CoordinateMax") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(10000); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.Property("FilePath") - .IsRequired() - .HasColumnType("text"); - - b.Property("SignerId") - .IsRequired() - .HasColumnType("text"); - - b.PrimitiveCollection("Strokes") - .IsRequired() - .HasColumnType("integer[]"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SignerId"); - - b.HasIndex("EstimateId", "Type") - .IsUnique(); - - b.ToTable("Signatures"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogAttachedFile", b => - { - b.Property("FileId") - .HasColumnType("bigint"); - - b.Property("PostId") - .HasColumnType("bigint"); - - b.HasKey("FileId", "PostId"); - - b.HasIndex("PostId"); - - b.ToTable("BlogAttachedFiles"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Article") - .HasMaxLength(56224) - .HasColumnType("character varying(56224)"); - - b.Property("AuthorId") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Photo") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.ToTable("BlogSpot"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogTag", b => - { - b.Property("PostId") - .HasColumnType("bigint"); - - b.Property("TagId") - .HasColumnType("bigint"); - - b.HasKey("PostId", "TagId"); - - b.HasIndex("TagId"); - - b.ToTable("BlogTag"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Article") - .HasColumnType("text"); - - b.Property("AuthorId") - .IsRequired() - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ParentId") - .HasColumnType("bigint"); - - b.Property("ReceiverId") - .HasColumnType("bigint"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.Property("Visible") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("ParentId"); - - b.HasIndex("ReceiverId"); - - b.ToTable("Comment"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.UploadedFile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContentType") - .HasColumnType("text"); - - b.Property("Length") - .HasColumnType("bigint"); - - b.Property("Path") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("UploadedFiles"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.Property("BlogpostId") - .HasColumnType("bigint"); - - b.HasKey("BlogpostId"); - - b.ToTable("blogSpotPublications"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.Property("OwnerId") - .HasColumnType("text"); - - b.HasKey("OwnerId"); - - b.ToTable("Schedule"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.ScheduledEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PeriodEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("PeriodStart") - .HasColumnType("timestamp with time zone"); - - b.Property("Reccurence") - .HasColumnType("integer"); - - b.Property("ScheduleOwnerId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScheduleOwnerId"); - - b.HasIndex("PeriodStart", "PeriodEnd"); - - b.ToTable("ScheduledEvent"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatConnection", b => - { - b.Property("ConnectionId") - .HasColumnType("text"); - - b.Property("ApplicationUserId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Connected") - .HasColumnType("boolean"); - - b.Property("UserAgent") - .HasColumnType("text"); - - b.HasKey("ConnectionId"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("ChatConnection"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.Property("Name") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("LatestJoinPart") - .HasColumnType("timestamp with time zone"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Topic") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Name"); - - b.HasIndex("OwnerId"); - - b.ToTable("ChatRoom"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoomAccess", b => - { - b.Property("ChannelName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Level") - .HasColumnType("integer"); - - b.HasKey("ChannelName", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("ChatRoomAccess"); - }); - - modelBuilder.Entity("Yavsc.Models.Cratie.Option", b => - { - b.Property("Code") - .HasColumnType("text"); - - b.Property("CodeScrutin") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Code", "CodeScrutin"); - - b.ToTable("Option"); - }); - - modelBuilder.Entity("Yavsc.Models.Drawing.Color", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Blue") - .HasColumnType("smallint"); - - b.Property("Green") - .HasColumnType("smallint"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Red") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.ToTable("Color"); - }); - - modelBuilder.Entity("Yavsc.Models.Forms.Form", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("Summary") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Form"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("ActionDistance") - .HasColumnType("integer"); - - b.Property("CarePrice") - .HasColumnType("numeric"); - - b.Property("FlatFeeDiscount") - .HasColumnType("numeric"); - - b.Property("HalfBalayagePrice") - .HasColumnType("numeric"); - - b.Property("HalfBrushingPrice") - .HasColumnType("numeric"); - - b.Property("HalfColorPrice") - .HasColumnType("numeric"); - - b.Property("HalfDefrisPrice") - .HasColumnType("numeric"); - - b.Property("HalfFoldingPrice") - .HasColumnType("numeric"); - - b.Property("HalfMechPrice") - .HasColumnType("numeric"); - - b.Property("HalfMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("HalfPermanentPrice") - .HasColumnType("numeric"); - - b.Property("KidCutPrice") - .HasColumnType("numeric"); - - b.Property("LongBalayagePrice") - .HasColumnType("numeric"); - - b.Property("LongBrushingPrice") - .HasColumnType("numeric"); - - b.Property("LongColorPrice") - .HasColumnType("numeric"); - - b.Property("LongDefrisPrice") - .HasColumnType("numeric"); - - b.Property("LongFoldingPrice") - .HasColumnType("numeric"); - - b.Property("LongMechPrice") - .HasColumnType("numeric"); - - b.Property("LongMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("LongPermanentPrice") - .HasColumnType("numeric"); - - b.Property("ManBrushPrice") - .HasColumnType("numeric"); - - b.Property("ManCutPrice") - .HasColumnType("numeric"); - - b.Property("ScheduleOwnerId") - .HasColumnType("text"); - - b.Property("ShampooPrice") - .HasColumnType("numeric"); - - b.Property("ShortBalayagePrice") - .HasColumnType("numeric"); - - b.Property("ShortBrushingPrice") - .HasColumnType("numeric"); - - b.Property("ShortColorPrice") - .HasColumnType("numeric"); - - b.Property("ShortDefrisPrice") - .HasColumnType("numeric"); - - b.Property("ShortFoldingPrice") - .HasColumnType("numeric"); - - b.Property("ShortMechPrice") - .HasColumnType("numeric"); - - b.Property("ShortMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("ShortPermanentPrice") - .HasColumnType("numeric"); - - b.Property("WomenHalfCutPrice") - .HasColumnType("numeric"); - - b.Property("WomenLongCutPrice") - .HasColumnType("numeric"); - - b.Property("WomenShortCutPrice") - .HasColumnType("numeric"); - - b.HasKey("UserId"); - - b.HasIndex("ScheduleOwnerId"); - - b.ToTable("BrusherProfile"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("AdditionalInfo") - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("SelectedProfileUserId") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("LocationId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.HasIndex("PrestationId"); - - b.HasIndex("SelectedProfileUserId"); - - b.ToTable("HairCutQueries"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("LocationId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("HairMultiCutQueries"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Cares") - .HasColumnType("boolean"); - - b.Property("Cut") - .HasColumnType("boolean"); - - b.Property("Dressing") - .HasColumnType("integer"); - - b.Property("Gender") - .HasColumnType("integer"); - - b.Property("Length") - .HasColumnType("integer"); - - b.Property("Shampoo") - .HasColumnType("boolean"); - - b.Property("Tech") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("HairPrestation"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.Property("QueryId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("PrestationId"); - - b.HasIndex("QueryId"); - - b.ToTable("HairPrestationCollectionItem"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaint", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Brand") - .HasColumnType("text"); - - b.Property("ColorId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ColorId"); - - b.ToTable("HairTaint"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaintInstance", b => - { - b.Property("TaintId") - .HasColumnType("bigint"); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.HasKey("TaintId", "PrestationId"); - - b.HasIndex("PrestationId"); - - b.ToTable("HairTaintInstance"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Evolution.Feature", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("ShortName") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("Feature"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Fixing.Bug", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasMaxLength(10240) - .HasColumnType("character varying(10240)"); - - b.Property("FeatureId") - .HasColumnType("bigint"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("FeatureId"); - - b.ToTable("Bug"); - }); - - modelBuilder.Entity("Yavsc.Models.Identity.DeviceDeclaration", b => - { - b.Property("DeviceId") - .HasColumnType("text"); - - b.Property("DeclarationDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("LOCALTIMESTAMP"); - - b.Property("DeviceOwnerId") - .HasColumnType("text"); - - b.Property("LatestActivityUpdate") - .HasColumnType("timestamp with time zone"); - - b.Property("Model") - .HasColumnType("text"); - - b.Property("Platform") - .HasColumnType("text"); - - b.Property("Version") - .HasColumnType("text"); - - b.HasKey("DeviceId"); - - b.HasIndex("DeviceOwnerId"); - - b.ToTable("DeviceDeclaration"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.DeclarationFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DeclarationId") - .HasColumnType("bigint"); - - b.Property("MatchExcerpt") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("PatternId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("DeclarationId"); - - b.HasIndex("PatternId"); - - b.ToTable("DeclarationFlag"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.ModerationLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .HasColumnType("integer"); - - b.Property("DeclarationId") - .HasColumnType("bigint"); - - b.Property("ModeratorId") - .HasColumnType("text"); - - b.Property("ScoreDelta") - .HasColumnType("integer"); - - b.Property("Timestamp") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("DeclarationId"); - - b.HasIndex("ModeratorId"); - - b.HasIndex("Timestamp"); - - b.ToTable("ModerationLogs", t => - { - t.HasCheckConstraint("CK_ModerationLog_Immutable", "1=1"); - }); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.RegexAlertPattern", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("Pattern") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("Severity") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("IsActive"); - - b.ToTable("RegexAlertPatterns"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Content") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("DeclarantTokenId") - .HasColumnType("uuid"); - - b.Property("ScoreDelta") - .HasColumnType("integer"); - - b.Property("Sentiment") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TrustTokenId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("Status"); - - b.HasIndex("SubmittedAt"); - - b.HasIndex("TrustTokenId"); - - b.ToTable("TrustDeclarations"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustToken", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("TokenSource") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("TrustScore") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("TokenHash") - .IsUnique(); - - b.ToTable("TrustTokens"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Depth") - .HasColumnType("numeric"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Height") - .HasColumnType("numeric"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Price") - .HasColumnType("numeric"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.Property("Weight") - .HasColumnType("numeric"); - - b.Property("Width") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Service", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContextId") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("ContextId"); - - b.ToTable("Services"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.Announce", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("For") - .HasColumnType("smallint"); - - b.Property("Message") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Sender") - .HasColumnType("text"); - - b.Property("Topic") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("Announce"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.DismissClicked", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("NotificationId") - .HasColumnType("bigint"); - - b.HasKey("UserId", "NotificationId"); - - b.HasIndex("NotificationId"); - - b.ToTable("DismissClicked"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Instrument", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("Instrument"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.InstrumentRating", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("InstrumentId") - .HasColumnType("bigint"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasAlternateKey("InstrumentId", "OwnerId"); - - b.HasIndex("OwnerId"); - - b.ToTable("InstrumentRating"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalPreference", b => - { - b.Property("OwnerProfileId") - .HasColumnType("text"); - - b.Property("DjSettingsUserId") - .HasColumnType("text"); - - b.Property("MusicLoverSettingsUserId") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("TendencyId") - .HasColumnType("bigint"); - - b.HasKey("OwnerProfileId"); - - b.HasIndex("DjSettingsUserId"); - - b.HasIndex("MusicLoverSettingsUserId"); - - b.HasIndex("TendencyId"); - - b.ToTable("MusicalPreference"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalTendency", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("MusicalTendency"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.DjSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("SoundCloudId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("DjSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.Instrumentation", b => - { - b.Property("InstrumentId") - .HasColumnType("bigint"); - - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("InstrumentId", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("Instrumentation"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.MusicLoverSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("MusicLoverSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.Property("CreationToken") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ExecutorId") - .IsRequired() - .HasColumnType("text"); - - b.Property("OrderReference") - .HasColumnType("text"); - - b.Property("PaypalPayerId") - .HasColumnType("text"); - - b.Property("State") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("CreationToken"); - - b.HasIndex("ExecutorId"); - - b.ToTable("PayPalPayment"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApplicationUserId") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("Circle"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.CircleMember", b => - { - b.Property("MemberId") - .HasColumnType("text"); - - b.Property("CircleId") - .HasColumnType("bigint"); - - b.HasKey("MemberId", "CircleId"); - - b.HasIndex("CircleId"); - - b.ToTable("CircleMembers"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Contact", b => - { - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("AddressId") - .HasColumnType("bigint"); - - b.Property("ApplicationUserId") - .HasColumnType("text"); - - b.Property("EMail") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.HasKey("OwnerId", "UserId"); - - b.HasIndex("AddressId"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("Contact"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.HyperLink", b => - { - b.Property("HRef") - .HasColumnType("text"); - - b.Property("Method") - .HasColumnType("text"); - - b.Property("BrusherProfileUserId") - .HasColumnType("text"); - - b.Property("ContentType") - .HasColumnType("text"); - - b.Property("PayPalPaymentCreationToken") - .HasColumnType("text"); - - b.Property("Rel") - .HasColumnType("text"); - - b.HasKey("HRef", "Method"); - - b.HasIndex("BrusherProfileUserId"); - - b.HasIndex("PayPalPaymentCreationToken"); - - b.ToTable("HyperLink"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Location", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Address") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("Latitude") - .HasColumnType("double precision"); - - b.Property("Longitude") - .HasColumnType("double precision"); - - b.HasKey("Id"); - - b.ToTable("Locations"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.PostalAddress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("City") - .HasColumnType("text"); - - b.Property("Country") - .HasColumnType("text"); - - b.Property("PostalCode") - .HasColumnType("text"); - - b.Property("Province") - .HasColumnType("text"); - - b.Property("State") - .HasColumnType("text"); - - b.Property("Street1") - .HasColumnType("text"); - - b.Property("Street2") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Tag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Tags"); - }); - - modelBuilder.Entity("Yavsc.Models.Skill", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("SiteSkills"); - }); - - modelBuilder.Entity("Yavsc.Models.Streaming.LiveFlow", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DifferedFileName") - .HasColumnType("text"); - - b.Property("MediaType") - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Pitch") - .HasColumnType("text"); - - b.Property("SequenceNumber") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("LiveFlow"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.Property("Code") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Hidden") - .HasColumnType("boolean"); - - b.Property("Moderated") - .HasColumnType("boolean"); - - b.Property("ModeratorGroupName") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("ParentCode") - .HasColumnType("text"); - - b.Property("Photo") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("SettingsClassName") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Code"); - - b.HasIndex("ParentCode"); - - b.ToTable("Activities"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CoWorking", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FormationSettingsUserId") - .HasColumnType("text"); - - b.Property("PerformerId") - .HasColumnType("text"); - - b.Property("WorkingForId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("FormationSettingsUserId"); - - b.HasIndex("PerformerId"); - - b.HasIndex("WorkingForId"); - - b.ToTable("CoWorking"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CommandForm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionName") - .HasColumnType("text"); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.ToTable("CommandForm"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.Property("PerformerId") - .HasColumnType("text"); - - b.Property("AcceptNotifications") - .HasColumnType("boolean"); - - b.Property("AcceptPublicContact") - .HasColumnType("boolean"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("MaxDailyCost") - .HasColumnType("integer"); - - b.Property("MinDailyCost") - .HasColumnType("integer"); - - b.Property("OrganizationAddressId") - .HasColumnType("bigint"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("SIREN") - .IsRequired() - .HasColumnType("text"); - - b.Property("UseGeoLocalizationToReduceDistanceWithClients") - .HasColumnType("boolean"); - - b.Property("WebSite") - .HasColumnType("text"); - - b.HasKey("PerformerId"); - - b.HasIndex("OrganizationAddressId"); - - b.ToTable("Performers"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Profiles.FormationSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("FormationSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("LocationType") - .HasColumnType("integer"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Reason") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("LocationId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("RdvQueries"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => - { - b.Property("DoesCode") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Weight") - .HasColumnType("integer"); - - b.HasKey("DoesCode", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("UserActivities"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.Calendar.Period", b => - { - b.Property("Start") - .HasColumnType("timestamp with time zone"); - - b.Property("End") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Start", "End"); - - b.ToTable("Period"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.EMailing.MailingTemplate", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("Body") - .HasMaxLength(65536) - .HasColumnType("character varying(65536)"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ReplyToAddress") - .HasColumnType("text"); - - b.Property("ToSend") - .HasColumnType("integer"); - - b.Property("Topic") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("MailingTemplate"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("GitId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Version") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("GitId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("Project"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProjectId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ProjectId"); - - b.ToTable("ProjectBuildConfiguration"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Branch") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Path") - .IsRequired() - .HasColumnType("text"); - - b.Property("Url") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("GitRepositoryReference"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("UserClaims") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Properties") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Scopes") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Secrets") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", null) - .WithMany("UserClaims") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", "Scope") - .WithMany() - .HasForeignKey("ScopeId1"); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", null) - .WithMany("Properties") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", "Scope") - .WithMany() - .HasForeignKey("ScopeId1"); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("Claims") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("AllowedCorsOrigins") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedGrantTypes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("IdentityProviderRestrictions") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("PostLogoutRedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("Properties") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("RedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedScopes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientSecret", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("ClientSecrets") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("UserClaims") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("Properties") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Yavsc.Models.Access.Ban", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "TargetUser") - .WithMany() - .HasForeignKey("TargetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetUser"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.BlackListed", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("BlackList") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToBlogPost", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Target") - .WithMany("ACL") - .HasForeignKey("BlogPostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Circle", "Allowed") - .WithMany() - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Allowed"); - - b.Navigation("Target"); - }); - - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithOne("AccountBalance") - .HasForeignKey("Yavsc.Models.AccountBalance", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "PostalAddress") - .WithMany() - .HasForeignKey("PostalAddressId"); - - b.Navigation("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.BalanceImpact", b => - { - b.HasOne("Yavsc.Models.AccountBalance", "Balance") - .WithMany() - .HasForeignKey("BalanceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Balance"); - }); - - modelBuilder.Entity("Yavsc.Models.Bank.BankIdentity", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany("BankInfo") - .HasForeignKey("UserId"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.CommandLine", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", null) - .WithMany("Bill") - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Billing.EstimateTemplate", null) - .WithMany("Bill") - .HasForeignKey("EstimateTemplateId"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.RdvQuery", "Query") - .WithMany() - .HasForeignKey("CommandId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Client"); - - b.Navigation("Owner"); - - b.Navigation("Query"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", "Estimate") - .WithMany("Signatures") - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Signer") - .WithMany() - .HasForeignKey("SignerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Estimate"); - - b.Navigation("Signer"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogAttachedFile", b => - { - b.HasOne("Yavsc.Models.Blog.UploadedFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany() - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("File"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Author") - .WithMany("Posts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Author"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogTag", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany("Tags") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Tag", "Tag") - .WithMany() - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Post"); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Author") - .WithMany("BlogComments") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.Comment", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany("Comments") - .HasForeignKey("ReceiverId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Author"); - - b.Navigation("Parent"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "BlogPost") - .WithMany() - .HasForeignKey("BlogpostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("BlogPost"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.ScheduledEvent", b => - { - b.HasOne("Yavsc.Models.Calendar.Schedule", null) - .WithMany("Events") - .HasForeignKey("ScheduleOwnerId"); - - b.HasOne("Yavsc.Server.Models.Calendar.Period", "Period") - .WithMany() - .HasForeignKey("PeriodStart", "PeriodEnd"); - - b.Navigation("Period"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatConnection", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("Connections") - .HasForeignKey("ApplicationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("Rooms") - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoomAccess", b => - { - b.HasOne("Yavsc.Models.Chat.ChatRoom", "Room") - .WithMany("Moderation") - .HasForeignKey("ChannelName") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany("RoomAccess") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Room"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.HasOne("Yavsc.Models.Calendar.Schedule", "Schedule") - .WithMany() - .HasForeignKey("ScheduleOwnerId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "BaseProfile") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("BaseProfile"); - - b.Navigation("Schedule"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany() - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.BrusherProfile", "SelectedProfile") - .WithMany() - .HasForeignKey("SelectedProfileUserId"); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("Location"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Prestation"); - - b.Navigation("Regularization"); - - b.Navigation("SelectedProfile"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("Location"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b => - { - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany() - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairMultiCutQuery", "Query") - .WithMany("Prestations") - .HasForeignKey("QueryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Prestation"); - - b.Navigation("Query"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaint", b => - { - b.HasOne("Yavsc.Models.Drawing.Color", "Color") - .WithMany() - .HasForeignKey("ColorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Color"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaintInstance", b => - { - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany("Taints") - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairTaint", "Taint") - .WithMany() - .HasForeignKey("TaintId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Prestation"); - - b.Navigation("Taint"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Fixing.Bug", b => - { - b.HasOne("Yavsc.Models.IT.Evolution.Feature", "False") - .WithMany() - .HasForeignKey("FeatureId"); - - b.Navigation("False"); - }); - - modelBuilder.Entity("Yavsc.Models.Identity.DeviceDeclaration", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "DeviceOwner") - .WithMany("DeviceDeclaration") - .HasForeignKey("DeviceOwnerId"); - - b.Navigation("DeviceOwner"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.DeclarationFlag", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustDeclaration", "Declaration") - .WithMany("Flags") - .HasForeignKey("DeclarationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Kyc.RegexAlertPattern", "Pattern") - .WithMany() - .HasForeignKey("PatternId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Declaration"); - - b.Navigation("Pattern"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.ModerationLog", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustDeclaration", "Declaration") - .WithMany() - .HasForeignKey("DeclarationId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Declaration"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustToken", "Subject") - .WithMany("Declarations") - .HasForeignKey("TrustTokenId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Subject"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Service", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany("Services") - .HasForeignKey("ContextId"); - - b.Navigation("Context"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.Announce", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.DismissClicked", b => - { - b.HasOne("Yavsc.Abstract.Models.Messaging.Notification", "Notified") - .WithMany() - .HasForeignKey("NotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Notified"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.InstrumentRating", b => - { - b.HasOne("Yavsc.Models.Musical.Instrument", "Instrument") - .WithMany() - .HasForeignKey("InstrumentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Profile") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Instrument"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalPreference", b => - { - b.HasOne("Yavsc.Models.Musical.Profiles.DjSettings", null) - .WithMany("SoundColor") - .HasForeignKey("DjSettingsUserId"); - - b.HasOne("Yavsc.Models.Musical.Profiles.MusicLoverSettings", null) - .WithMany("SoundColor") - .HasForeignKey("MusicLoverSettingsUserId"); - - b.HasOne("Yavsc.Models.Musical.MusicalTendency", "MusicalTendency") - .WithMany() - .HasForeignKey("TendencyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("MusicalTendency"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.Instrumentation", b => - { - b.HasOne("Yavsc.Models.Musical.Instrument", "Tool") - .WithMany() - .HasForeignKey("InstrumentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Tool"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Executor") - .WithMany() - .HasForeignKey("ExecutorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Executor"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany("Circles") - .HasForeignKey("ApplicationUserId"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.CircleMember", b => - { - b.HasOne("Yavsc.Models.Relationship.Circle", "Circle") - .WithMany("Members") - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Member") - .WithMany("Membership") - .HasForeignKey("MemberId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Circle"); - - b.Navigation("Member"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Contact", b => - { - b.HasOne("Yavsc.Models.Relationship.PostalAddress", "PostalAddress") - .WithMany() - .HasForeignKey("AddressId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany("Book") - .HasForeignKey("ApplicationUserId"); - - b.Navigation("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.HyperLink", b => - { - b.HasOne("Yavsc.Models.Haircut.BrusherProfile", null) - .WithMany("Links") - .HasForeignKey("BrusherProfileUserId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", null) - .WithMany("Links") - .HasForeignKey("PayPalPaymentCreationToken"); - }); - - modelBuilder.Entity("Yavsc.Models.Streaming.LiveFlow", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentCode"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CoWorking", b => - { - b.HasOne("Yavsc.Models.Workflow.Profiles.FormationSettings", null) - .WithMany("CoWorking") - .HasForeignKey("FormationSettingsUserId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Performer") - .WithMany() - .HasForeignKey("PerformerId"); - - b.HasOne("Yavsc.Models.ApplicationUser", "WorkingFor") - .WithMany() - .HasForeignKey("WorkingForId"); - - b.Navigation("Performer"); - - b.Navigation("WorkingFor"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CommandForm", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany("Forms") - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Context"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "OrganizationAddress") - .WithMany() - .HasForeignKey("OrganizationAddressId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Performer") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationAddress"); - - b.Navigation("Performer"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("Location"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Does") - .WithMany() - .HasForeignKey("DoesCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "User") - .WithMany("Activity") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Does"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", "Repository") - .WithMany() - .HasForeignKey("GitId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - - b.Navigation("Repository"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => - { - b.HasOne("Yavsc.Server.Models.IT.Project", "TargetProject") - .WithMany("Configurations") - .HasForeignKey("ProjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetProject"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b => - { - b.Navigation("Properties"); - - b.Navigation("Scopes"); - - b.Navigation("Secrets"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b => - { - b.Navigation("AllowedCorsOrigins"); - - b.Navigation("AllowedGrantTypes"); - - b.Navigation("AllowedScopes"); - - b.Navigation("Claims"); - - b.Navigation("ClientSecrets"); - - b.Navigation("IdentityProviderRestrictions"); - - b.Navigation("PostLogoutRedirectUris"); - - b.Navigation("Properties"); - - b.Navigation("RedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResource", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.Navigation("AccountBalance"); - - b.Navigation("BankInfo"); - - b.Navigation("BlackList"); - - b.Navigation("BlogComments"); - - b.Navigation("Book"); - - b.Navigation("Circles"); - - b.Navigation("Connections"); - - b.Navigation("DeviceDeclaration"); - - b.Navigation("Membership"); - - b.Navigation("Posts"); - - b.Navigation("RoomAccess"); - - b.Navigation("Rooms"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.Navigation("Bill"); - - b.Navigation("Signatures"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b => - { - b.Navigation("Bill"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.Navigation("ACL"); - - b.Navigation("Comments"); - - b.Navigation("Tags"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.Navigation("Children"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.Navigation("Events"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.Navigation("Moderation"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.Navigation("Prestations"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => - { - b.Navigation("Taints"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.Navigation("Flags"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustToken", b => - { - b.Navigation("Declarations"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.DjSettings", b => - { - b.Navigation("SoundColor"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.MusicLoverSettings", b => - { - b.Navigation("SoundColor"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.Navigation("Children"); - - b.Navigation("Forms"); - - b.Navigation("Services"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.Navigation("Activity"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Profiles.FormationSettings", b => - { - b.Navigation("CoWorking"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.Navigation("Configurations"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260820232152_DropCommentFromCircleAuthorizationToBlogPost.cs b/src/Yavsc.Org/Migrations/20260820232152_DropCommentFromCircleAuthorizationToBlogPost.cs deleted file mode 100644 index f853889b9..000000000 --- a/src/Yavsc.Org/Migrations/20260820232152_DropCommentFromCircleAuthorizationToBlogPost.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Yavsc.Migrations -{ - /// - public partial class DropCommentFromCircleAuthorizationToBlogPost : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Comment", - table: "CircleAuthorizationToBlogPost"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Comment", - table: "CircleAuthorizationToBlogPost", - type: "boolean", - nullable: false, - defaultValue: false); - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260831040104_AddPerformerCountryValidation.Designer.cs b/src/Yavsc.Org/Migrations/20260831040104_AddPerformerCountryValidation.Designer.cs deleted file mode 100644 index 3348b9c4d..000000000 --- a/src/Yavsc.Org/Migrations/20260831040104_AddPerformerCountryValidation.Designer.cs +++ /dev/null @@ -1,4734 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Yavsc.Models; - -#nullable disable - -namespace Yavsc.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260831040104_AddPerformerCountryValidation")] - partial class AddPerformerCountryValidation - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("AllowedAccessTokenSigningAlgorithms") - .HasColumnType("text"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("ApiResources"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Scope") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceSecrets"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("ApiScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("ScopeId1") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.HasIndex("ScopeId1"); - - b.ToTable("ApiScopeClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("ScopeId1") - .HasColumnType("integer"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.HasIndex("ScopeId1"); - - b.ToTable("ApiScopeProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("AbsoluteRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenType") - .HasColumnType("integer"); - - b.Property("AllowAccessTokensViaBrowser") - .HasColumnType("boolean"); - - b.Property("AllowOfflineAccess") - .HasColumnType("boolean"); - - b.Property("AllowPlainTextPkce") - .HasColumnType("boolean"); - - b.Property("AllowRememberConsent") - .HasColumnType("boolean"); - - b.Property("AllowedIdentityTokenSigningAlgorithms") - .HasColumnType("text"); - - b.Property("AlwaysIncludeUserClaimsInIdToken") - .HasColumnType("boolean"); - - b.Property("AlwaysSendClientClaims") - .HasColumnType("boolean"); - - b.Property("AuthorizationCodeLifetime") - .HasColumnType("integer"); - - b.Property("BackChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("BackChannelLogoutUri") - .HasColumnType("text"); - - b.Property("ClientClaimsPrefix") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("ClientName") - .HasColumnType("text"); - - b.Property("ClientUri") - .HasColumnType("text"); - - b.Property("ConsentLifetime") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DeviceCodeLifetime") - .HasColumnType("integer"); - - b.Property("EnableLocalLogin") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutUri") - .HasColumnType("text"); - - b.Property("IdentityTokenLifetime") - .HasColumnType("integer"); - - b.Property("IncludeJwtId") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("LogoUri") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("PairWiseSubjectSalt") - .HasColumnType("text"); - - b.Property("ProtocolType") - .HasColumnType("text"); - - b.Property("RefreshTokenExpiration") - .HasColumnType("integer"); - - b.Property("RefreshTokenUsage") - .HasColumnType("integer"); - - b.Property("RequireClientSecret") - .HasColumnType("boolean"); - - b.Property("RequireConsent") - .HasColumnType("boolean"); - - b.Property("RequirePkce") - .HasColumnType("boolean"); - - b.Property("RequireRequestObject") - .HasColumnType("boolean"); - - b.Property("SlidingRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("UpdateAccessTokenClaimsOnRefresh") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.Property("UserCodeType") - .HasColumnType("text"); - - b.Property("UserSsoLifetime") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("Clients"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Origin") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientCorsOrigins"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("GrantType") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientGrantTypes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Provider") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientIdPRestrictions"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("PostLogoutRedirectUri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientPostLogoutRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("RedirectUri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Scope") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientSecrets"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.DeviceFlowCodes", b => - { - b.Property("UserCode") - .HasColumnType("text"); - - b.Property("DeviceCode") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasColumnType("text"); - - b.Property("SubjectId") - .HasColumnType("text"); - - b.HasKey("UserCode", "DeviceCode"); - - b.ToTable("DeviceFlowCodes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("IdentityResources"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.PersistedGrant", b => - { - b.Property("Key") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("ConsumedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasColumnType("text"); - - b.Property("SubjectId") - .HasColumnType("text"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Key"); - - b.ToTable("PersistedGrants"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("Yavsc.Abstract.Identity.ClientProviderInfo", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Avatar") - .HasColumnType("text"); - - b.Property("BillingAddressId") - .HasColumnType("bigint"); - - b.Property("EMail") - .HasColumnType("text"); - - b.Property("Phone") - .HasColumnType("text"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("ClientProviderInfo"); - }); - - modelBuilder.Entity("Yavsc.Abstract.Models.Messaging.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Target") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("body") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("click_action") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("color") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("icon") - .ValueGeneratedOnAdd() - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasDefaultValue("exclam"); - - b.Property("sound") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("tag") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("title") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.HasKey("Id"); - - b.ToTable("Notification"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.Ban", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.Property("TargetId") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("TargetId"); - - b.ToTable("Ban"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.BlackListed", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.HasIndex("UserId"); - - b.ToTable("BlackListed"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToBlogPost", b => - { - b.Property("CircleId") - .HasColumnType("bigint"); - - b.Property("BlogPostId") - .HasColumnType("bigint"); - - b.HasKey("CircleId", "BlogPostId"); - - b.HasIndex("BlogPostId"); - - b.ToTable("CircleAuthorizationToBlogPost"); - }); - - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("ContactCredits") - .HasColumnType("bigint"); - - b.Property("Credits") - .HasColumnType("numeric"); - - b.HasKey("UserId"); - - b.ToTable("BankStatus"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("AllowMonthlyEmail") - .HasColumnType("boolean"); - - b.Property("Avatar") - .ValueGeneratedOnAdd() - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasDefaultValue("/images/Users/icon_user.png"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("DedicatedGoogleCalendar") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("DiskQuota") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasDefaultValue(524288000L); - - b.Property("DiskUsage") - .HasColumnType("bigint"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("FullName") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("MaxFileSize") - .HasColumnType("bigint"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("PostalAddressId") - .HasColumnType("bigint"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasAlternateKey("Email"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("PostalAddressId"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Yavsc.Models.BalanceImpact", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BalanceId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExecDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Impact") - .HasColumnType("numeric"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("BalanceId"); - - b.ToTable("BalanceImpact"); - }); - - modelBuilder.Entity("Yavsc.Models.Bank.BankIdentity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountNumber") - .HasColumnType("text"); - - b.Property("BIC") - .HasColumnType("text"); - - b.Property("BankCode") - .HasColumnType("text"); - - b.Property("BankedKey") - .HasColumnType("integer"); - - b.Property("IBAN") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("WicketCode") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("BankIdentity"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.CommandLine", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Count") - .HasColumnType("integer"); - - b.Property("Currency") - .HasColumnType("text"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.Property("EstimateTemplateId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("UnitaryCost") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.HasIndex("EstimateId"); - - b.HasIndex("EstimateTemplateId"); - - b.ToTable("CommandLine"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AttachedFilesString") - .HasColumnType("text"); - - b.Property("AttachedGraphicsString") - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CommandId") - .HasColumnType("bigint"); - - b.Property("CommandType") - .IsRequired() - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("ProviderValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("CommandId"); - - b.HasIndex("OwnerId"); - - b.ToTable("Estimates"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EstimateTemplates"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.ExceptionSIREN", b => - { - b.Property("SIREN") - .HasColumnType("text"); - - b.HasKey("SIREN"); - - b.ToTable("ExceptionsSIREN"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CapturedAtUtc") - .HasColumnType("timestamp with time zone"); - - b.Property("CoordinateMax") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(10000); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.Property("FilePath") - .IsRequired() - .HasColumnType("text"); - - b.Property("SignerId") - .IsRequired() - .HasColumnType("text"); - - b.PrimitiveCollection("Strokes") - .IsRequired() - .HasColumnType("integer[]"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SignerId"); - - b.HasIndex("EstimateId", "Type") - .IsUnique(); - - b.ToTable("Signatures"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogAttachedFile", b => - { - b.Property("FileId") - .HasColumnType("bigint"); - - b.Property("PostId") - .HasColumnType("bigint"); - - b.HasKey("FileId", "PostId"); - - b.HasIndex("PostId"); - - b.ToTable("BlogAttachedFiles"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Article") - .HasMaxLength(56224) - .HasColumnType("character varying(56224)"); - - b.Property("AuthorId") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Photo") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.ToTable("BlogSpot"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogTag", b => - { - b.Property("PostId") - .HasColumnType("bigint"); - - b.Property("TagId") - .HasColumnType("bigint"); - - b.HasKey("PostId", "TagId"); - - b.HasIndex("TagId"); - - b.ToTable("BlogTag"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Article") - .HasColumnType("text"); - - b.Property("AuthorId") - .IsRequired() - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ParentId") - .HasColumnType("bigint"); - - b.Property("ReceiverId") - .HasColumnType("bigint"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.Property("Visible") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("ParentId"); - - b.HasIndex("ReceiverId"); - - b.ToTable("Comment"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.UploadedFile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContentType") - .HasColumnType("text"); - - b.Property("Length") - .HasColumnType("bigint"); - - b.Property("Path") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("UploadedFiles"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.Property("BlogpostId") - .HasColumnType("bigint"); - - b.HasKey("BlogpostId"); - - b.ToTable("blogSpotPublications"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.Property("OwnerId") - .HasColumnType("text"); - - b.HasKey("OwnerId"); - - b.ToTable("Schedule"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.ScheduledEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PeriodEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("PeriodStart") - .HasColumnType("timestamp with time zone"); - - b.Property("Reccurence") - .HasColumnType("integer"); - - b.Property("ScheduleOwnerId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScheduleOwnerId"); - - b.HasIndex("PeriodStart", "PeriodEnd"); - - b.ToTable("ScheduledEvent"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatConnection", b => - { - b.Property("ConnectionId") - .HasColumnType("text"); - - b.Property("ApplicationUserId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Connected") - .HasColumnType("boolean"); - - b.Property("UserAgent") - .HasColumnType("text"); - - b.HasKey("ConnectionId"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("ChatConnection"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.Property("Name") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("LatestJoinPart") - .HasColumnType("timestamp with time zone"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Topic") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Name"); - - b.HasIndex("OwnerId"); - - b.ToTable("ChatRoom"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoomAccess", b => - { - b.Property("ChannelName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Level") - .HasColumnType("integer"); - - b.HasKey("ChannelName", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("ChatRoomAccess"); - }); - - modelBuilder.Entity("Yavsc.Models.Cratie.Option", b => - { - b.Property("Code") - .HasColumnType("text"); - - b.Property("CodeScrutin") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Code", "CodeScrutin"); - - b.ToTable("Option"); - }); - - modelBuilder.Entity("Yavsc.Models.Drawing.Color", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Blue") - .HasColumnType("smallint"); - - b.Property("Green") - .HasColumnType("smallint"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Red") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.ToTable("Color"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("ActionDistance") - .HasColumnType("integer"); - - b.Property("CarePrice") - .HasColumnType("numeric"); - - b.Property("FlatFeeDiscount") - .HasColumnType("numeric"); - - b.Property("HalfBalayagePrice") - .HasColumnType("numeric"); - - b.Property("HalfBrushingPrice") - .HasColumnType("numeric"); - - b.Property("HalfColorPrice") - .HasColumnType("numeric"); - - b.Property("HalfDefrisPrice") - .HasColumnType("numeric"); - - b.Property("HalfFoldingPrice") - .HasColumnType("numeric"); - - b.Property("HalfMechPrice") - .HasColumnType("numeric"); - - b.Property("HalfMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("HalfPermanentPrice") - .HasColumnType("numeric"); - - b.Property("KidCutPrice") - .HasColumnType("numeric"); - - b.Property("LongBalayagePrice") - .HasColumnType("numeric"); - - b.Property("LongBrushingPrice") - .HasColumnType("numeric"); - - b.Property("LongColorPrice") - .HasColumnType("numeric"); - - b.Property("LongDefrisPrice") - .HasColumnType("numeric"); - - b.Property("LongFoldingPrice") - .HasColumnType("numeric"); - - b.Property("LongMechPrice") - .HasColumnType("numeric"); - - b.Property("LongMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("LongPermanentPrice") - .HasColumnType("numeric"); - - b.Property("ManBrushPrice") - .HasColumnType("numeric"); - - b.Property("ManCutPrice") - .HasColumnType("numeric"); - - b.Property("ScheduleOwnerId") - .HasColumnType("text"); - - b.Property("ShampooPrice") - .HasColumnType("numeric"); - - b.Property("ShortBalayagePrice") - .HasColumnType("numeric"); - - b.Property("ShortBrushingPrice") - .HasColumnType("numeric"); - - b.Property("ShortColorPrice") - .HasColumnType("numeric"); - - b.Property("ShortDefrisPrice") - .HasColumnType("numeric"); - - b.Property("ShortFoldingPrice") - .HasColumnType("numeric"); - - b.Property("ShortMechPrice") - .HasColumnType("numeric"); - - b.Property("ShortMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("ShortPermanentPrice") - .HasColumnType("numeric"); - - b.Property("WomenHalfCutPrice") - .HasColumnType("numeric"); - - b.Property("WomenLongCutPrice") - .HasColumnType("numeric"); - - b.Property("WomenShortCutPrice") - .HasColumnType("numeric"); - - b.HasKey("UserId"); - - b.HasIndex("ScheduleOwnerId"); - - b.ToTable("BrusherProfile"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("AdditionalInfo") - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("SelectedProfileUserId") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("LocationId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.HasIndex("PrestationId"); - - b.HasIndex("SelectedProfileUserId"); - - b.ToTable("HairCutQueries"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("LocationId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("HairMultiCutQueries"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Cares") - .HasColumnType("boolean"); - - b.Property("Cut") - .HasColumnType("boolean"); - - b.Property("Dressing") - .HasColumnType("integer"); - - b.Property("Gender") - .HasColumnType("integer"); - - b.Property("Length") - .HasColumnType("integer"); - - b.Property("Shampoo") - .HasColumnType("boolean"); - - b.Property("Tech") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("HairPrestation"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.Property("QueryId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("PrestationId"); - - b.HasIndex("QueryId"); - - b.ToTable("HairPrestationCollectionItem"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaint", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Brand") - .HasColumnType("text"); - - b.Property("ColorId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ColorId"); - - b.ToTable("HairTaint"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaintInstance", b => - { - b.Property("TaintId") - .HasColumnType("bigint"); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.HasKey("TaintId", "PrestationId"); - - b.HasIndex("PrestationId"); - - b.ToTable("HairTaintInstance"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Evolution.Feature", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("ShortName") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("Feature"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Fixing.Bug", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasMaxLength(10240) - .HasColumnType("character varying(10240)"); - - b.Property("FeatureId") - .HasColumnType("bigint"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("FeatureId"); - - b.ToTable("Bug"); - }); - - modelBuilder.Entity("Yavsc.Models.Identity.DeviceDeclaration", b => - { - b.Property("DeviceId") - .HasColumnType("text"); - - b.Property("DeclarationDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("LOCALTIMESTAMP"); - - b.Property("DeviceOwnerId") - .HasColumnType("text"); - - b.Property("LatestActivityUpdate") - .HasColumnType("timestamp with time zone"); - - b.Property("Model") - .HasColumnType("text"); - - b.Property("Platform") - .HasColumnType("text"); - - b.Property("Version") - .HasColumnType("text"); - - b.HasKey("DeviceId"); - - b.HasIndex("DeviceOwnerId"); - - b.ToTable("DeviceDeclaration"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.DeclarationFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DeclarationId") - .HasColumnType("bigint"); - - b.Property("MatchExcerpt") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("PatternId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("DeclarationId"); - - b.HasIndex("PatternId"); - - b.ToTable("DeclarationFlag"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.ModerationLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .HasColumnType("integer"); - - b.Property("DeclarationId") - .HasColumnType("bigint"); - - b.Property("ModeratorId") - .HasColumnType("text"); - - b.Property("ScoreDelta") - .HasColumnType("integer"); - - b.Property("Timestamp") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("DeclarationId"); - - b.HasIndex("ModeratorId"); - - b.HasIndex("Timestamp"); - - b.ToTable("ModerationLogs", t => - { - t.HasCheckConstraint("CK_ModerationLog_Immutable", "1=1"); - }); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.RegexAlertPattern", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("Pattern") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("Severity") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("IsActive"); - - b.ToTable("RegexAlertPatterns"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Content") - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("DeclarantTokenId") - .HasColumnType("uuid"); - - b.Property("ScoreDelta") - .HasColumnType("integer"); - - b.Property("Sentiment") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TrustTokenId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("Status"); - - b.HasIndex("SubmittedAt"); - - b.HasIndex("TrustTokenId"); - - b.ToTable("TrustDeclarations"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustToken", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("TokenSource") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("TrustScore") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("TokenHash") - .IsUnique(); - - b.ToTable("TrustTokens"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Depth") - .HasColumnType("numeric"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Height") - .HasColumnType("numeric"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Price") - .HasColumnType("numeric"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.Property("Weight") - .HasColumnType("numeric"); - - b.Property("Width") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Service", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContextId") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("ContextId"); - - b.ToTable("Services"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.Announce", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("For") - .HasColumnType("smallint"); - - b.Property("Message") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Sender") - .HasColumnType("text"); - - b.Property("Topic") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("Announce"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.DismissClicked", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("NotificationId") - .HasColumnType("bigint"); - - b.HasKey("UserId", "NotificationId"); - - b.HasIndex("NotificationId"); - - b.ToTable("DismissClicked"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Instrument", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("Instrument"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.InstrumentRating", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("InstrumentId") - .HasColumnType("bigint"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasAlternateKey("InstrumentId", "OwnerId"); - - b.HasIndex("OwnerId"); - - b.ToTable("InstrumentRating"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalPreference", b => - { - b.Property("OwnerProfileId") - .HasColumnType("text"); - - b.Property("DjSettingsUserId") - .HasColumnType("text"); - - b.Property("MusicLoverSettingsUserId") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("TendencyId") - .HasColumnType("bigint"); - - b.HasKey("OwnerProfileId"); - - b.HasIndex("DjSettingsUserId"); - - b.HasIndex("MusicLoverSettingsUserId"); - - b.HasIndex("TendencyId"); - - b.ToTable("MusicalPreference"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalTendency", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("MusicalTendency"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.DjSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("SoundCloudId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("DjSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.Instrumentation", b => - { - b.Property("InstrumentId") - .HasColumnType("bigint"); - - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("InstrumentId", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("Instrumentation"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.MusicLoverSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("MusicLoverSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.Property("CreationToken") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ExecutorId") - .IsRequired() - .HasColumnType("text"); - - b.Property("OrderReference") - .HasColumnType("text"); - - b.Property("PaypalPayerId") - .HasColumnType("text"); - - b.Property("State") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("CreationToken"); - - b.HasIndex("ExecutorId"); - - b.ToTable("PayPalPayment"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApplicationUserId") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("Circle"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.CircleMember", b => - { - b.Property("MemberId") - .HasColumnType("text"); - - b.Property("CircleId") - .HasColumnType("bigint"); - - b.HasKey("MemberId", "CircleId"); - - b.HasIndex("CircleId"); - - b.ToTable("CircleMembers"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Contact", b => - { - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("AddressId") - .HasColumnType("bigint"); - - b.Property("ApplicationUserId") - .HasColumnType("text"); - - b.Property("EMail") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.HasKey("OwnerId", "UserId"); - - b.HasIndex("AddressId"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("Contact"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.HyperLink", b => - { - b.Property("HRef") - .HasColumnType("text"); - - b.Property("Method") - .HasColumnType("text"); - - b.Property("BrusherProfileUserId") - .HasColumnType("text"); - - b.Property("ContentType") - .HasColumnType("text"); - - b.Property("PayPalPaymentCreationToken") - .HasColumnType("text"); - - b.Property("Rel") - .HasColumnType("text"); - - b.HasKey("HRef", "Method"); - - b.HasIndex("BrusherProfileUserId"); - - b.HasIndex("PayPalPaymentCreationToken"); - - b.ToTable("HyperLink"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Location", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Address") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("Latitude") - .HasColumnType("double precision"); - - b.Property("Longitude") - .HasColumnType("double precision"); - - b.HasKey("Id"); - - b.ToTable("Locations"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.PostalAddress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("City") - .HasColumnType("text"); - - b.Property("Country") - .HasColumnType("text"); - - b.Property("PostalCode") - .HasColumnType("text"); - - b.Property("Province") - .HasColumnType("text"); - - b.Property("State") - .HasColumnType("text"); - - b.Property("Street1") - .HasColumnType("text"); - - b.Property("Street2") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Tag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Tags"); - }); - - modelBuilder.Entity("Yavsc.Models.Skill", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("SiteSkills"); - }); - - modelBuilder.Entity("Yavsc.Models.Streaming.LiveFlow", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DifferedFileName") - .HasColumnType("text"); - - b.Property("MediaType") - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Pitch") - .HasColumnType("text"); - - b.Property("SequenceNumber") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("LiveFlow"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.Property("Code") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Hidden") - .HasColumnType("boolean"); - - b.Property("Moderated") - .HasColumnType("boolean"); - - b.Property("ModeratorGroupName") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("ParentCode") - .HasColumnType("text"); - - b.Property("Photo") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("SettingsClassName") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Code"); - - b.HasIndex("ParentCode"); - - b.ToTable("Activities"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CoWorking", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FormationSettingsUserId") - .HasColumnType("text"); - - b.Property("PerformerId") - .HasColumnType("text"); - - b.Property("WorkingForId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("FormationSettingsUserId"); - - b.HasIndex("PerformerId"); - - b.HasIndex("WorkingForId"); - - b.ToTable("CoWorking"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CommandForm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionName") - .HasColumnType("text"); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.ToTable("CommandForm"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Country", b => - { - b.Property("Code") - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.HasKey("Code"); - - b.ToTable("Countries"); - - b.HasData( - new - { - Code = "fr", - DisplayName = "France" - }, - new - { - Code = "en", - DisplayName = "England" - }, - new - { - Code = "pt", - DisplayName = "Portugal" - }); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CountryCode") - .IsRequired() - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("ErrorMessage") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("RegularExpression") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("CountryCode"); - - b.ToTable("PerformerCodeInputValidations"); - - b.HasData( - new - { - Id = 1L, - CountryCode = "fr", - ErrorMessage = "Le code FR doit contenir entre 9 et 14 chiffres.", - RegularExpression = "^[0-9]{9,14}$" - }, - new - { - Id = 2L, - CountryCode = "en", - ErrorMessage = "Le code EN doit contenir entre 8 et 14 caracteres alphanumeriques.", - RegularExpression = "^[A-Za-z0-9]{8,14}$" - }, - new - { - Id = 3L, - CountryCode = "pt", - ErrorMessage = "Le code PT doit contenir exactement 9 chiffres.", - RegularExpression = "^[0-9]{9}$" - }); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.Property("PerformerId") - .HasColumnType("text"); - - b.Property("AcceptNotifications") - .HasColumnType("boolean"); - - b.Property("AcceptPublicContact") - .HasColumnType("boolean"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("ExerciseCountryCode") - .IsRequired() - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("MaxDailyCost") - .HasColumnType("integer"); - - b.Property("MinDailyCost") - .HasColumnType("integer"); - - b.Property("OrganizationAddressId") - .HasColumnType("bigint"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("SIREN") - .IsRequired() - .HasColumnType("text"); - - b.Property("UseGeoLocalizationToReduceDistanceWithClients") - .HasColumnType("boolean"); - - b.Property("WebSite") - .HasColumnType("text"); - - b.HasKey("PerformerId"); - - b.HasIndex("OrganizationAddressId"); - - b.ToTable("Performers"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Profiles.FormationSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("FormationSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("LocationType") - .HasColumnType("integer"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Reason") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("LocationId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("RdvQueries"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => - { - b.Property("DoesCode") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Weight") - .HasColumnType("integer"); - - b.HasKey("DoesCode", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("UserActivities"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.Calendar.Period", b => - { - b.Property("Start") - .HasColumnType("timestamp with time zone"); - - b.Property("End") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Start", "End"); - - b.ToTable("Period"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.EMailing.MailingTemplate", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("Body") - .HasMaxLength(65536) - .HasColumnType("character varying(65536)"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ReplyToAddress") - .HasColumnType("text"); - - b.Property("ToSend") - .HasColumnType("integer"); - - b.Property("Topic") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("MailingTemplate"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("GitId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Version") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("GitId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("Project"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProjectId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ProjectId"); - - b.ToTable("ProjectBuildConfiguration"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Branch") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Path") - .IsRequired() - .HasColumnType("text"); - - b.Property("Url") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("GitRepositoryReference"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("UserClaims") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Properties") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Scopes") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Secrets") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", null) - .WithMany("UserClaims") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", "Scope") - .WithMany() - .HasForeignKey("ScopeId1"); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", null) - .WithMany("Properties") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", "Scope") - .WithMany() - .HasForeignKey("ScopeId1"); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("Claims") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("AllowedCorsOrigins") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedGrantTypes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("IdentityProviderRestrictions") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("PostLogoutRedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("Properties") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("RedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedScopes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientSecret", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("ClientSecrets") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("UserClaims") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("Properties") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Yavsc.Models.Access.Ban", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "TargetUser") - .WithMany() - .HasForeignKey("TargetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetUser"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.BlackListed", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("BlackList") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToBlogPost", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Target") - .WithMany("ACL") - .HasForeignKey("BlogPostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Circle", "Allowed") - .WithMany() - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Allowed"); - - b.Navigation("Target"); - }); - - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithOne("AccountBalance") - .HasForeignKey("Yavsc.Models.AccountBalance", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "PostalAddress") - .WithMany() - .HasForeignKey("PostalAddressId"); - - b.Navigation("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.BalanceImpact", b => - { - b.HasOne("Yavsc.Models.AccountBalance", "Balance") - .WithMany() - .HasForeignKey("BalanceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Balance"); - }); - - modelBuilder.Entity("Yavsc.Models.Bank.BankIdentity", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany("BankInfo") - .HasForeignKey("UserId"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.CommandLine", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", null) - .WithMany("Bill") - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Billing.EstimateTemplate", null) - .WithMany("Bill") - .HasForeignKey("EstimateTemplateId"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.RdvQuery", "Query") - .WithMany() - .HasForeignKey("CommandId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Client"); - - b.Navigation("Owner"); - - b.Navigation("Query"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", "Estimate") - .WithMany("Signatures") - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Signer") - .WithMany() - .HasForeignKey("SignerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Estimate"); - - b.Navigation("Signer"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogAttachedFile", b => - { - b.HasOne("Yavsc.Models.Blog.UploadedFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany() - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("File"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Author") - .WithMany("Posts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Author"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogTag", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany("Tags") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Tag", "Tag") - .WithMany() - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Post"); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Author") - .WithMany("BlogComments") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.Comment", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany("Comments") - .HasForeignKey("ReceiverId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Author"); - - b.Navigation("Parent"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "BlogPost") - .WithMany() - .HasForeignKey("BlogpostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("BlogPost"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.ScheduledEvent", b => - { - b.HasOne("Yavsc.Models.Calendar.Schedule", null) - .WithMany("Events") - .HasForeignKey("ScheduleOwnerId"); - - b.HasOne("Yavsc.Server.Models.Calendar.Period", "Period") - .WithMany() - .HasForeignKey("PeriodStart", "PeriodEnd"); - - b.Navigation("Period"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatConnection", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("Connections") - .HasForeignKey("ApplicationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("Rooms") - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoomAccess", b => - { - b.HasOne("Yavsc.Models.Chat.ChatRoom", "Room") - .WithMany("Moderation") - .HasForeignKey("ChannelName") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany("RoomAccess") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Room"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.HasOne("Yavsc.Models.Calendar.Schedule", "Schedule") - .WithMany() - .HasForeignKey("ScheduleOwnerId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "BaseProfile") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("BaseProfile"); - - b.Navigation("Schedule"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany() - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.BrusherProfile", "SelectedProfile") - .WithMany() - .HasForeignKey("SelectedProfileUserId"); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("Location"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Prestation"); - - b.Navigation("Regularization"); - - b.Navigation("SelectedProfile"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("Location"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b => - { - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany() - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairMultiCutQuery", "Query") - .WithMany("Prestations") - .HasForeignKey("QueryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Prestation"); - - b.Navigation("Query"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaint", b => - { - b.HasOne("Yavsc.Models.Drawing.Color", "Color") - .WithMany() - .HasForeignKey("ColorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Color"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaintInstance", b => - { - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany("Taints") - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairTaint", "Taint") - .WithMany() - .HasForeignKey("TaintId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Prestation"); - - b.Navigation("Taint"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Fixing.Bug", b => - { - b.HasOne("Yavsc.Models.IT.Evolution.Feature", "False") - .WithMany() - .HasForeignKey("FeatureId"); - - b.Navigation("False"); - }); - - modelBuilder.Entity("Yavsc.Models.Identity.DeviceDeclaration", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "DeviceOwner") - .WithMany("DeviceDeclaration") - .HasForeignKey("DeviceOwnerId"); - - b.Navigation("DeviceOwner"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.DeclarationFlag", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustDeclaration", "Declaration") - .WithMany("Flags") - .HasForeignKey("DeclarationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Kyc.RegexAlertPattern", "Pattern") - .WithMany() - .HasForeignKey("PatternId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Declaration"); - - b.Navigation("Pattern"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.ModerationLog", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustDeclaration", "Declaration") - .WithMany() - .HasForeignKey("DeclarationId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Declaration"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustToken", "Subject") - .WithMany("Declarations") - .HasForeignKey("TrustTokenId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Subject"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Service", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany("Services") - .HasForeignKey("ContextId"); - - b.Navigation("Context"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.Announce", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.DismissClicked", b => - { - b.HasOne("Yavsc.Abstract.Models.Messaging.Notification", "Notified") - .WithMany() - .HasForeignKey("NotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Notified"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.InstrumentRating", b => - { - b.HasOne("Yavsc.Models.Musical.Instrument", "Instrument") - .WithMany() - .HasForeignKey("InstrumentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Profile") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Instrument"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalPreference", b => - { - b.HasOne("Yavsc.Models.Musical.Profiles.DjSettings", null) - .WithMany("SoundColor") - .HasForeignKey("DjSettingsUserId"); - - b.HasOne("Yavsc.Models.Musical.Profiles.MusicLoverSettings", null) - .WithMany("SoundColor") - .HasForeignKey("MusicLoverSettingsUserId"); - - b.HasOne("Yavsc.Models.Musical.MusicalTendency", "MusicalTendency") - .WithMany() - .HasForeignKey("TendencyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("MusicalTendency"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.Instrumentation", b => - { - b.HasOne("Yavsc.Models.Musical.Instrument", "Tool") - .WithMany() - .HasForeignKey("InstrumentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Tool"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Executor") - .WithMany() - .HasForeignKey("ExecutorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Executor"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany("Circles") - .HasForeignKey("ApplicationUserId"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.CircleMember", b => - { - b.HasOne("Yavsc.Models.Relationship.Circle", "Circle") - .WithMany("Members") - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Member") - .WithMany("Membership") - .HasForeignKey("MemberId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Circle"); - - b.Navigation("Member"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Contact", b => - { - b.HasOne("Yavsc.Models.Relationship.PostalAddress", "PostalAddress") - .WithMany() - .HasForeignKey("AddressId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany("Book") - .HasForeignKey("ApplicationUserId"); - - b.Navigation("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.HyperLink", b => - { - b.HasOne("Yavsc.Models.Haircut.BrusherProfile", null) - .WithMany("Links") - .HasForeignKey("BrusherProfileUserId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", null) - .WithMany("Links") - .HasForeignKey("PayPalPaymentCreationToken"); - }); - - modelBuilder.Entity("Yavsc.Models.Streaming.LiveFlow", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentCode"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CoWorking", b => - { - b.HasOne("Yavsc.Models.Workflow.Profiles.FormationSettings", null) - .WithMany("CoWorking") - .HasForeignKey("FormationSettingsUserId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Performer") - .WithMany() - .HasForeignKey("PerformerId"); - - b.HasOne("Yavsc.Models.ApplicationUser", "WorkingFor") - .WithMany() - .HasForeignKey("WorkingForId"); - - b.Navigation("Performer"); - - b.Navigation("WorkingFor"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CommandForm", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany("Forms") - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Context"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b => - { - b.HasOne("Yavsc.Models.Workflow.Country", "Country") - .WithMany() - .HasForeignKey("CountryCode") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Country"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "OrganizationAddress") - .WithMany() - .HasForeignKey("OrganizationAddressId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Performer") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationAddress"); - - b.Navigation("Performer"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("Location"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Does") - .WithMany() - .HasForeignKey("DoesCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "User") - .WithMany("Activity") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Does"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", "Repository") - .WithMany() - .HasForeignKey("GitId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - - b.Navigation("Repository"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => - { - b.HasOne("Yavsc.Server.Models.IT.Project", "TargetProject") - .WithMany("Configurations") - .HasForeignKey("ProjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetProject"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b => - { - b.Navigation("Properties"); - - b.Navigation("Scopes"); - - b.Navigation("Secrets"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b => - { - b.Navigation("AllowedCorsOrigins"); - - b.Navigation("AllowedGrantTypes"); - - b.Navigation("AllowedScopes"); - - b.Navigation("Claims"); - - b.Navigation("ClientSecrets"); - - b.Navigation("IdentityProviderRestrictions"); - - b.Navigation("PostLogoutRedirectUris"); - - b.Navigation("Properties"); - - b.Navigation("RedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResource", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.Navigation("AccountBalance"); - - b.Navigation("BankInfo"); - - b.Navigation("BlackList"); - - b.Navigation("BlogComments"); - - b.Navigation("Book"); - - b.Navigation("Circles"); - - b.Navigation("Connections"); - - b.Navigation("DeviceDeclaration"); - - b.Navigation("Membership"); - - b.Navigation("Posts"); - - b.Navigation("RoomAccess"); - - b.Navigation("Rooms"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.Navigation("Bill"); - - b.Navigation("Signatures"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b => - { - b.Navigation("Bill"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.Navigation("ACL"); - - b.Navigation("Comments"); - - b.Navigation("Tags"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.Navigation("Children"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.Navigation("Events"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.Navigation("Moderation"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.Navigation("Prestations"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => - { - b.Navigation("Taints"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.Navigation("Flags"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustToken", b => - { - b.Navigation("Declarations"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.DjSettings", b => - { - b.Navigation("SoundColor"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.MusicLoverSettings", b => - { - b.Navigation("SoundColor"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.Navigation("Children"); - - b.Navigation("Forms"); - - b.Navigation("Services"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.Navigation("Activity"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Profiles.FormationSettings", b => - { - b.Navigation("CoWorking"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.Navigation("Configurations"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260831040104_AddPerformerCountryValidation.cs b/src/Yavsc.Org/Migrations/20260831040104_AddPerformerCountryValidation.cs deleted file mode 100644 index 9d65d19cb..000000000 --- a/src/Yavsc.Org/Migrations/20260831040104_AddPerformerCountryValidation.cs +++ /dev/null @@ -1,97 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional - -namespace Yavsc.Migrations -{ - /// - public partial class AddPerformerCountryValidation : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "ExerciseCountryCode", - table: "Performers", - type: "character varying(2)", - maxLength: 2, - nullable: false, - defaultValue: "fr"); - - migrationBuilder.CreateTable( - name: "Countries", - columns: table => new - { - Code = table.Column(type: "character varying(2)", maxLength: 2, nullable: false), - DisplayName = table.Column(type: "character varying(64)", maxLength: 64, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Countries", x => x.Code); - }); - - migrationBuilder.CreateTable( - name: "PerformerCodeInputValidations", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - CountryCode = table.Column(type: "character varying(2)", maxLength: 2, nullable: false), - RegularExpression = table.Column(type: "character varying(256)", maxLength: 256, nullable: false), - ErrorMessage = table.Column(type: "character varying(128)", maxLength: 128, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_PerformerCodeInputValidations", x => x.Id); - table.ForeignKey( - name: "FK_PerformerCodeInputValidations_Countries_CountryCode", - column: x => x.CountryCode, - principalTable: "Countries", - principalColumn: "Code", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.InsertData( - table: "Countries", - columns: new[] { "Code", "DisplayName" }, - values: new object[,] - { - { "en", "England" }, - { "fr", "France" }, - { "pt", "Portugal" } - }); - - migrationBuilder.InsertData( - table: "PerformerCodeInputValidations", - columns: new[] { "Id", "CountryCode", "ErrorMessage", "RegularExpression" }, - values: new object[,] - { - { 1L, "fr", "Le code FR doit contenir entre 9 et 14 chiffres.", "^[0-9]{9,14}$" }, - { 2L, "en", "Le code EN doit contenir entre 8 et 14 caracteres alphanumeriques.", "^[A-Za-z0-9]{8,14}$" }, - { 3L, "pt", "Le code PT doit contenir exactement 9 chiffres.", "^[0-9]{9}$" } - }); - - migrationBuilder.CreateIndex( - name: "IX_PerformerCodeInputValidations_CountryCode", - table: "PerformerCodeInputValidations", - column: "CountryCode"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "PerformerCodeInputValidations"); - - migrationBuilder.DropTable( - name: "Countries"); - - migrationBuilder.DropColumn( - name: "ExerciseCountryCode", - table: "Performers"); - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260907091755_AddCircleAuthorizationToFileAcl.Designer.cs b/src/Yavsc.Org/Migrations/20260907091755_AddCircleAuthorizationToFileAcl.Designer.cs deleted file mode 100644 index 096cf52f8..000000000 --- a/src/Yavsc.Org/Migrations/20260907091755_AddCircleAuthorizationToFileAcl.Designer.cs +++ /dev/null @@ -1,4778 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Yavsc.Models; - -#nullable disable - -namespace Yavsc.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260907091755_AddCircleAuthorizationToFileAcl")] - partial class AddCircleAuthorizationToFileAcl - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("AllowedAccessTokenSigningAlgorithms") - .HasColumnType("text"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("ApiResources"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Scope") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceSecrets"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("ApiScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("ScopeId1") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.HasIndex("ScopeId1"); - - b.ToTable("ApiScopeClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("ScopeId1") - .HasColumnType("integer"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.HasIndex("ScopeId1"); - - b.ToTable("ApiScopeProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("AbsoluteRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenType") - .HasColumnType("integer"); - - b.Property("AllowAccessTokensViaBrowser") - .HasColumnType("boolean"); - - b.Property("AllowOfflineAccess") - .HasColumnType("boolean"); - - b.Property("AllowPlainTextPkce") - .HasColumnType("boolean"); - - b.Property("AllowRememberConsent") - .HasColumnType("boolean"); - - b.Property("AllowedIdentityTokenSigningAlgorithms") - .HasColumnType("text"); - - b.Property("AlwaysIncludeUserClaimsInIdToken") - .HasColumnType("boolean"); - - b.Property("AlwaysSendClientClaims") - .HasColumnType("boolean"); - - b.Property("AuthorizationCodeLifetime") - .HasColumnType("integer"); - - b.Property("BackChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("BackChannelLogoutUri") - .HasColumnType("text"); - - b.Property("ClientClaimsPrefix") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("ClientName") - .HasColumnType("text"); - - b.Property("ClientUri") - .HasColumnType("text"); - - b.Property("ConsentLifetime") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DeviceCodeLifetime") - .HasColumnType("integer"); - - b.Property("EnableLocalLogin") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutUri") - .HasColumnType("text"); - - b.Property("IdentityTokenLifetime") - .HasColumnType("integer"); - - b.Property("IncludeJwtId") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("LogoUri") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("PairWiseSubjectSalt") - .HasColumnType("text"); - - b.Property("ProtocolType") - .HasColumnType("text"); - - b.Property("RefreshTokenExpiration") - .HasColumnType("integer"); - - b.Property("RefreshTokenUsage") - .HasColumnType("integer"); - - b.Property("RequireClientSecret") - .HasColumnType("boolean"); - - b.Property("RequireConsent") - .HasColumnType("boolean"); - - b.Property("RequirePkce") - .HasColumnType("boolean"); - - b.Property("RequireRequestObject") - .HasColumnType("boolean"); - - b.Property("SlidingRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("UpdateAccessTokenClaimsOnRefresh") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.Property("UserCodeType") - .HasColumnType("text"); - - b.Property("UserSsoLifetime") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("Clients"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Origin") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientCorsOrigins"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("GrantType") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientGrantTypes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Provider") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientIdPRestrictions"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("PostLogoutRedirectUri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientPostLogoutRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("RedirectUri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Scope") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientSecrets"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.DeviceFlowCodes", b => - { - b.Property("UserCode") - .HasColumnType("text"); - - b.Property("DeviceCode") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasColumnType("text"); - - b.Property("SubjectId") - .HasColumnType("text"); - - b.HasKey("UserCode", "DeviceCode"); - - b.ToTable("DeviceFlowCodes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("IdentityResources"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.PersistedGrant", b => - { - b.Property("Key") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("ConsumedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasColumnType("text"); - - b.Property("SubjectId") - .HasColumnType("text"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Key"); - - b.ToTable("PersistedGrants"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("Yavsc.Abstract.Identity.ClientProviderInfo", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Avatar") - .HasColumnType("text"); - - b.Property("BillingAddressId") - .HasColumnType("bigint"); - - b.Property("EMail") - .HasColumnType("text"); - - b.Property("Phone") - .HasColumnType("text"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("ClientProviderInfo"); - }); - - modelBuilder.Entity("Yavsc.Abstract.Models.Messaging.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Target") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("body") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("click_action") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("color") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("icon") - .ValueGeneratedOnAdd() - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasDefaultValue("exclam"); - - b.Property("sound") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("tag") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("title") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.HasKey("Id"); - - b.ToTable("Notification"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.Ban", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.Property("TargetId") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("TargetId"); - - b.ToTable("Ban"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.BlackListed", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.HasIndex("UserId"); - - b.ToTable("BlackListed"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToBlogPost", b => - { - b.Property("CircleId") - .HasColumnType("bigint"); - - b.Property("BlogPostId") - .HasColumnType("bigint"); - - b.HasKey("CircleId", "BlogPostId"); - - b.HasIndex("BlogPostId"); - - b.ToTable("CircleAuthorizationToBlogPost"); - }); - - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("ContactCredits") - .HasColumnType("bigint"); - - b.Property("Credits") - .HasColumnType("numeric"); - - b.HasKey("UserId"); - - b.ToTable("BankStatus"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("AllowMonthlyEmail") - .HasColumnType("boolean"); - - b.Property("Avatar") - .ValueGeneratedOnAdd() - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasDefaultValue("/images/Users/icon_user.png"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("DedicatedGoogleCalendar") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("DiskQuota") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasDefaultValue(524288000L); - - b.Property("DiskUsage") - .HasColumnType("bigint"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("FullName") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("MaxFileSize") - .HasColumnType("bigint"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("PostalAddressId") - .HasColumnType("bigint"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasAlternateKey("Email"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("PostalAddressId"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Yavsc.Models.BalanceImpact", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BalanceId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExecDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Impact") - .HasColumnType("numeric"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("BalanceId"); - - b.ToTable("BalanceImpact"); - }); - - modelBuilder.Entity("Yavsc.Models.Bank.BankIdentity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("BIC") - .IsRequired() - .HasColumnType("text"); - - b.Property("BankCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("BankedKey") - .HasColumnType("integer"); - - b.Property("IBAN") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.Property("WicketCode") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("BankIdentity"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.CommandLine", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Count") - .HasColumnType("integer"); - - b.Property("Currency") - .HasColumnType("text"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.Property("EstimateTemplateId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("UnitaryCost") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.HasIndex("EstimateId"); - - b.HasIndex("EstimateTemplateId"); - - b.ToTable("CommandLine"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AttachedFilesString") - .IsRequired() - .HasColumnType("text"); - - b.Property("AttachedGraphicsString") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CommandId") - .HasColumnType("bigint"); - - b.Property("CommandType") - .IsRequired() - .HasColumnType("text"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProviderValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Title") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("CommandId"); - - b.HasIndex("OwnerId"); - - b.ToTable("Estimates"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EstimateTemplates"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.ExceptionSIREN", b => - { - b.Property("SIREN") - .HasColumnType("text"); - - b.HasKey("SIREN"); - - b.ToTable("ExceptionsSIREN"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CapturedAtUtc") - .HasColumnType("timestamp with time zone"); - - b.Property("CoordinateMax") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(10000); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.Property("FilePath") - .IsRequired() - .HasColumnType("text"); - - b.Property("SignerId") - .IsRequired() - .HasColumnType("text"); - - b.PrimitiveCollection("Strokes") - .IsRequired() - .HasColumnType("integer[]"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SignerId"); - - b.HasIndex("EstimateId", "Type") - .IsUnique(); - - b.ToTable("Signatures"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogAttachedFile", b => - { - b.Property("FileId") - .HasColumnType("bigint"); - - b.Property("PostId") - .HasColumnType("bigint"); - - b.HasKey("FileId", "PostId"); - - b.HasIndex("PostId"); - - b.ToTable("BlogAttachedFiles"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Article") - .HasMaxLength(56224) - .HasColumnType("character varying(56224)"); - - b.Property("AuthorId") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Photo") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.ToTable("BlogSpot"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogTag", b => - { - b.Property("PostId") - .HasColumnType("bigint"); - - b.Property("TagId") - .HasColumnType("bigint"); - - b.HasKey("PostId", "TagId"); - - b.HasIndex("TagId"); - - b.ToTable("BlogTag"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Article") - .IsRequired() - .HasColumnType("text"); - - b.Property("AuthorId") - .IsRequired() - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ParentId") - .HasColumnType("bigint"); - - b.Property("ReceiverId") - .HasColumnType("bigint"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("Visible") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("ParentId"); - - b.HasIndex("ReceiverId"); - - b.ToTable("Comment"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.UploadedFile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContentType") - .HasColumnType("text"); - - b.Property("Length") - .HasColumnType("bigint"); - - b.Property("Path") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("UploadedFiles"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.Property("BlogpostId") - .HasColumnType("bigint"); - - b.HasKey("BlogpostId"); - - b.ToTable("blogSpotPublications"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.Property("OwnerId") - .HasColumnType("text"); - - b.HasKey("OwnerId"); - - b.ToTable("Schedule"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.ScheduledEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PeriodEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("PeriodStart") - .HasColumnType("timestamp with time zone"); - - b.Property("Reccurence") - .HasColumnType("integer"); - - b.Property("ScheduleOwnerId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScheduleOwnerId"); - - b.HasIndex("PeriodStart", "PeriodEnd"); - - b.ToTable("ScheduledEvent"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatConnection", b => - { - b.Property("ConnectionId") - .HasColumnType("text"); - - b.Property("ApplicationUserId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Connected") - .HasColumnType("boolean"); - - b.Property("UserAgent") - .HasColumnType("text"); - - b.HasKey("ConnectionId"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("ChatConnection"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.Property("Name") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("LatestJoinPart") - .HasColumnType("timestamp with time zone"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Topic") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Name"); - - b.HasIndex("OwnerId"); - - b.ToTable("ChatRoom"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoomAccess", b => - { - b.Property("ChannelName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Level") - .HasColumnType("integer"); - - b.HasKey("ChannelName", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("ChatRoomAccess"); - }); - - modelBuilder.Entity("Yavsc.Models.Cratie.Option", b => - { - b.Property("Code") - .HasColumnType("text"); - - b.Property("CodeScrutin") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Code", "CodeScrutin"); - - b.ToTable("Option"); - }); - - modelBuilder.Entity("Yavsc.Models.Drawing.Color", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Blue") - .HasColumnType("smallint"); - - b.Property("Green") - .HasColumnType("smallint"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Red") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.ToTable("Color"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("ActionDistance") - .HasColumnType("integer"); - - b.Property("CarePrice") - .HasColumnType("numeric"); - - b.Property("FlatFeeDiscount") - .HasColumnType("numeric"); - - b.Property("HalfBalayagePrice") - .HasColumnType("numeric"); - - b.Property("HalfBrushingPrice") - .HasColumnType("numeric"); - - b.Property("HalfColorPrice") - .HasColumnType("numeric"); - - b.Property("HalfDefrisPrice") - .HasColumnType("numeric"); - - b.Property("HalfFoldingPrice") - .HasColumnType("numeric"); - - b.Property("HalfMechPrice") - .HasColumnType("numeric"); - - b.Property("HalfMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("HalfPermanentPrice") - .HasColumnType("numeric"); - - b.Property("KidCutPrice") - .HasColumnType("numeric"); - - b.Property("LongBalayagePrice") - .HasColumnType("numeric"); - - b.Property("LongBrushingPrice") - .HasColumnType("numeric"); - - b.Property("LongColorPrice") - .HasColumnType("numeric"); - - b.Property("LongDefrisPrice") - .HasColumnType("numeric"); - - b.Property("LongFoldingPrice") - .HasColumnType("numeric"); - - b.Property("LongMechPrice") - .HasColumnType("numeric"); - - b.Property("LongMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("LongPermanentPrice") - .HasColumnType("numeric"); - - b.Property("ManBrushPrice") - .HasColumnType("numeric"); - - b.Property("ManCutPrice") - .HasColumnType("numeric"); - - b.Property("ScheduleOwnerId") - .HasColumnType("text"); - - b.Property("ShampooPrice") - .HasColumnType("numeric"); - - b.Property("ShortBalayagePrice") - .HasColumnType("numeric"); - - b.Property("ShortBrushingPrice") - .HasColumnType("numeric"); - - b.Property("ShortColorPrice") - .HasColumnType("numeric"); - - b.Property("ShortDefrisPrice") - .HasColumnType("numeric"); - - b.Property("ShortFoldingPrice") - .HasColumnType("numeric"); - - b.Property("ShortMechPrice") - .HasColumnType("numeric"); - - b.Property("ShortMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("ShortPermanentPrice") - .HasColumnType("numeric"); - - b.Property("WomenHalfCutPrice") - .HasColumnType("numeric"); - - b.Property("WomenLongCutPrice") - .HasColumnType("numeric"); - - b.Property("WomenShortCutPrice") - .HasColumnType("numeric"); - - b.HasKey("UserId"); - - b.HasIndex("ScheduleOwnerId"); - - b.ToTable("BrusherProfile"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("AdditionalInfo") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("SelectedProfileUserId") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("LocationId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.HasIndex("PrestationId"); - - b.HasIndex("SelectedProfileUserId"); - - b.ToTable("HairCutQueries"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("LocationId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("HairMultiCutQueries"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Cares") - .HasColumnType("boolean"); - - b.Property("Cut") - .HasColumnType("boolean"); - - b.Property("Dressing") - .HasColumnType("integer"); - - b.Property("Gender") - .HasColumnType("integer"); - - b.Property("Length") - .HasColumnType("integer"); - - b.Property("Shampoo") - .HasColumnType("boolean"); - - b.Property("Tech") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("HairPrestation"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.Property("QueryId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("PrestationId"); - - b.HasIndex("QueryId"); - - b.ToTable("HairPrestationCollectionItem"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaint", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Brand") - .HasColumnType("text"); - - b.Property("ColorId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ColorId"); - - b.ToTable("HairTaint"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaintInstance", b => - { - b.Property("TaintId") - .HasColumnType("bigint"); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.HasKey("TaintId", "PrestationId"); - - b.HasIndex("PrestationId"); - - b.ToTable("HairTaintInstance"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Evolution.Feature", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("ShortName") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("Feature"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Fixing.Bug", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(10240) - .HasColumnType("character varying(10240)"); - - b.Property("FeatureId") - .HasColumnType("bigint"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("Title") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("FeatureId"); - - b.ToTable("Bug"); - }); - - modelBuilder.Entity("Yavsc.Models.Identity.DeviceDeclaration", b => - { - b.Property("DeviceId") - .HasColumnType("text"); - - b.Property("DeclarationDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("LOCALTIMESTAMP"); - - b.Property("DeviceOwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("LatestActivityUpdate") - .HasColumnType("timestamp with time zone"); - - b.Property("Model") - .IsRequired() - .HasColumnType("text"); - - b.Property("Platform") - .IsRequired() - .HasColumnType("text"); - - b.Property("Version") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("DeviceId"); - - b.HasIndex("DeviceOwnerId"); - - b.ToTable("DeviceDeclaration"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.DeclarationFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DeclarationId") - .HasColumnType("bigint"); - - b.Property("MatchExcerpt") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("PatternId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("DeclarationId"); - - b.HasIndex("PatternId"); - - b.ToTable("DeclarationFlag"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.ModerationLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .HasColumnType("integer"); - - b.Property("DeclarationId") - .HasColumnType("bigint"); - - b.Property("ModeratorId") - .HasColumnType("text"); - - b.Property("ScoreDelta") - .HasColumnType("integer"); - - b.Property("Timestamp") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("DeclarationId"); - - b.HasIndex("ModeratorId"); - - b.HasIndex("Timestamp"); - - b.ToTable("ModerationLogs", t => - { - t.HasCheckConstraint("CK_ModerationLog_Immutable", "1=1"); - }); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.RegexAlertPattern", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("Pattern") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("Severity") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("IsActive"); - - b.ToTable("RegexAlertPatterns"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Content") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("DeclarantTokenId") - .HasColumnType("uuid"); - - b.Property("ScoreDelta") - .HasColumnType("integer"); - - b.Property("Sentiment") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TrustTokenId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("Status"); - - b.HasIndex("SubmittedAt"); - - b.HasIndex("TrustTokenId"); - - b.ToTable("TrustDeclarations"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustToken", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("TokenSource") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("TrustScore") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("TokenHash") - .IsUnique(); - - b.ToTable("TrustTokens"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Depth") - .HasColumnType("numeric"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Height") - .HasColumnType("numeric"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Price") - .HasColumnType("numeric"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.Property("Weight") - .HasColumnType("numeric"); - - b.Property("Width") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Service", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContextId") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("ContextId"); - - b.ToTable("Services"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.Announce", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("For") - .HasColumnType("smallint"); - - b.Property("Message") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Sender") - .HasColumnType("text"); - - b.Property("Topic") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("Announce"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.DismissClicked", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("NotificationId") - .HasColumnType("bigint"); - - b.HasKey("UserId", "NotificationId"); - - b.HasIndex("NotificationId"); - - b.ToTable("DismissClicked"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Instrument", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("Instrument"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.InstrumentRating", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("InstrumentId") - .HasColumnType("bigint"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasAlternateKey("InstrumentId", "OwnerId"); - - b.HasIndex("OwnerId"); - - b.ToTable("InstrumentRating"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalPreference", b => - { - b.Property("OwnerProfileId") - .HasColumnType("text"); - - b.Property("DjSettingsUserId") - .HasColumnType("text"); - - b.Property("MusicLoverSettingsUserId") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("TendencyId") - .HasColumnType("bigint"); - - b.HasKey("OwnerProfileId"); - - b.HasIndex("DjSettingsUserId"); - - b.HasIndex("MusicLoverSettingsUserId"); - - b.HasIndex("TendencyId"); - - b.ToTable("MusicalPreference"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalTendency", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("MusicalTendency"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.DjSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("SoundCloudId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("DjSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.Instrumentation", b => - { - b.Property("InstrumentId") - .HasColumnType("bigint"); - - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("InstrumentId", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("Instrumentation"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.MusicLoverSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("MusicLoverSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.Property("CreationToken") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ExecutorId") - .IsRequired() - .HasColumnType("text"); - - b.Property("OrderReference") - .HasColumnType("text"); - - b.Property("PaypalPayerId") - .HasColumnType("text"); - - b.Property("State") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("CreationToken"); - - b.HasIndex("ExecutorId"); - - b.ToTable("PayPalPayment"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApplicationUserId") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("Circle"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.CircleMember", b => - { - b.Property("MemberId") - .HasColumnType("text"); - - b.Property("CircleId") - .HasColumnType("bigint"); - - b.HasKey("MemberId", "CircleId"); - - b.HasIndex("CircleId"); - - b.ToTable("CircleMembers"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Contact", b => - { - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("AddressId") - .HasColumnType("bigint"); - - b.Property("ApplicationUserId") - .HasColumnType("text"); - - b.Property("EMail") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.HasKey("OwnerId", "UserId"); - - b.HasIndex("AddressId"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("Contact"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.HyperLink", b => - { - b.Property("HRef") - .HasColumnType("text"); - - b.Property("Method") - .HasColumnType("text"); - - b.Property("BrusherProfileUserId") - .HasColumnType("text"); - - b.Property("ContentType") - .HasColumnType("text"); - - b.Property("PayPalPaymentCreationToken") - .HasColumnType("text"); - - b.Property("Rel") - .HasColumnType("text"); - - b.HasKey("HRef", "Method"); - - b.HasIndex("BrusherProfileUserId"); - - b.HasIndex("PayPalPaymentCreationToken"); - - b.ToTable("HyperLink"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Location", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Address") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("Latitude") - .HasColumnType("double precision"); - - b.Property("Longitude") - .HasColumnType("double precision"); - - b.HasKey("Id"); - - b.ToTable("Locations"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.PostalAddress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("City") - .HasColumnType("text"); - - b.Property("Country") - .HasColumnType("text"); - - b.Property("PostalCode") - .HasColumnType("text"); - - b.Property("Province") - .HasColumnType("text"); - - b.Property("State") - .HasColumnType("text"); - - b.Property("Street1") - .HasColumnType("text"); - - b.Property("Street2") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Tag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Tags"); - }); - - modelBuilder.Entity("Yavsc.Models.Skill", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("SiteSkills"); - }); - - modelBuilder.Entity("Yavsc.Models.Streaming.LiveFlow", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DifferedFileName") - .HasColumnType("text"); - - b.Property("MediaType") - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Pitch") - .HasColumnType("text"); - - b.Property("SequenceNumber") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("LiveFlow"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.Property("Code") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Hidden") - .HasColumnType("boolean"); - - b.Property("Moderated") - .HasColumnType("boolean"); - - b.Property("ModeratorGroupName") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("ParentCode") - .HasColumnType("text"); - - b.Property("Photo") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("SettingsClassName") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Code"); - - b.HasIndex("ParentCode"); - - b.ToTable("Activities"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CoWorking", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FormationSettingsUserId") - .HasColumnType("text"); - - b.Property("PerformerId") - .HasColumnType("text"); - - b.Property("WorkingForId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("FormationSettingsUserId"); - - b.HasIndex("PerformerId"); - - b.HasIndex("WorkingForId"); - - b.ToTable("CoWorking"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CommandForm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionName") - .HasColumnType("text"); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.ToTable("CommandForm"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Country", b => - { - b.Property("Code") - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.HasKey("Code"); - - b.ToTable("Countries"); - - b.HasData( - new - { - Code = "fr", - DisplayName = "France" - }, - new - { - Code = "en", - DisplayName = "England" - }, - new - { - Code = "pt", - DisplayName = "Portugal" - }); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CountryCode") - .IsRequired() - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("ErrorMessage") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("RegularExpression") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("CountryCode"); - - b.ToTable("PerformerCodeInputValidations"); - - b.HasData( - new - { - Id = 1L, - CountryCode = "fr", - ErrorMessage = "Le code FR doit contenir entre 9 et 14 chiffres.", - RegularExpression = "^[0-9]{9,14}$" - }, - new - { - Id = 2L, - CountryCode = "en", - ErrorMessage = "Le code EN doit contenir entre 8 et 14 caracteres alphanumeriques.", - RegularExpression = "^[A-Za-z0-9]{8,14}$" - }, - new - { - Id = 3L, - CountryCode = "pt", - ErrorMessage = "Le code PT doit contenir exactement 9 chiffres.", - RegularExpression = "^[0-9]{9}$" - }); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.Property("PerformerId") - .HasColumnType("text"); - - b.Property("AcceptNotifications") - .HasColumnType("boolean"); - - b.Property("AcceptPublicContact") - .HasColumnType("boolean"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("ExerciseCountryCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("MaxDailyCost") - .HasColumnType("integer"); - - b.Property("MinDailyCost") - .HasColumnType("integer"); - - b.Property("OrganizationAddressId") - .HasColumnType("bigint"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("SIREN") - .IsRequired() - .HasColumnType("text"); - - b.Property("UseGeoLocalizationToReduceDistanceWithClients") - .HasColumnType("boolean"); - - b.Property("WebSite") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("PerformerId"); - - b.HasIndex("OrganizationAddressId"); - - b.ToTable("Performers"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Profiles.FormationSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("FormationSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("LocationType") - .HasColumnType("integer"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("LocationId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("RdvQueries"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => - { - b.Property("DoesCode") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Weight") - .HasColumnType("integer"); - - b.HasKey("DoesCode", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("UserActivities"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.Calendar.Period", b => - { - b.Property("Start") - .HasColumnType("timestamp with time zone"); - - b.Property("End") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Start", "End"); - - b.ToTable("Period"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.EMailing.MailingTemplate", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("Body") - .HasMaxLength(65536) - .HasColumnType("character varying(65536)"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ReplyToAddress") - .HasColumnType("text"); - - b.Property("ToSend") - .HasColumnType("integer"); - - b.Property("Topic") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("MailingTemplate"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("GitId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Version") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("GitId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("Project"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProjectId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ProjectId"); - - b.ToTable("ProjectBuildConfiguration"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Branch") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Path") - .IsRequired() - .HasColumnType("text"); - - b.Property("Url") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("GitRepositoryReference"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("UserClaims") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Properties") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Scopes") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Secrets") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", null) - .WithMany("UserClaims") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", "Scope") - .WithMany() - .HasForeignKey("ScopeId1"); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", null) - .WithMany("Properties") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", "Scope") - .WithMany() - .HasForeignKey("ScopeId1"); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("Claims") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("AllowedCorsOrigins") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedGrantTypes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("IdentityProviderRestrictions") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("PostLogoutRedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("Properties") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("RedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedScopes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientSecret", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("ClientSecrets") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("UserClaims") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("Properties") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Yavsc.Models.Access.Ban", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "TargetUser") - .WithMany() - .HasForeignKey("TargetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetUser"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.BlackListed", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("BlackList") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToBlogPost", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Target") - .WithMany("ACL") - .HasForeignKey("BlogPostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Circle", "Allowed") - .WithMany() - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Allowed"); - - b.Navigation("Target"); - }); - - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithOne("AccountBalance") - .HasForeignKey("Yavsc.Models.AccountBalance", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "PostalAddress") - .WithMany() - .HasForeignKey("PostalAddressId"); - - b.Navigation("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.BalanceImpact", b => - { - b.HasOne("Yavsc.Models.AccountBalance", "Balance") - .WithMany() - .HasForeignKey("BalanceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Balance"); - }); - - modelBuilder.Entity("Yavsc.Models.Bank.BankIdentity", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany("BankInfo") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.CommandLine", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", null) - .WithMany("Bill") - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Billing.EstimateTemplate", null) - .WithMany("Bill") - .HasForeignKey("EstimateTemplateId"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.RdvQuery", "Query") - .WithMany() - .HasForeignKey("CommandId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Owner"); - - b.Navigation("Query"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", "Estimate") - .WithMany("Signatures") - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Signer") - .WithMany() - .HasForeignKey("SignerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Estimate"); - - b.Navigation("Signer"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogAttachedFile", b => - { - b.HasOne("Yavsc.Models.Blog.UploadedFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany() - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("File"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Author") - .WithMany("Posts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Author"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogTag", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany("Tags") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Tag", "Tag") - .WithMany() - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Post"); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Author") - .WithMany("BlogComments") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.Comment", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany("Comments") - .HasForeignKey("ReceiverId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Author"); - - b.Navigation("Parent"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "BlogPost") - .WithMany() - .HasForeignKey("BlogpostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("BlogPost"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.ScheduledEvent", b => - { - b.HasOne("Yavsc.Models.Calendar.Schedule", null) - .WithMany("Events") - .HasForeignKey("ScheduleOwnerId"); - - b.HasOne("Yavsc.Server.Models.Calendar.Period", "Period") - .WithMany() - .HasForeignKey("PeriodStart", "PeriodEnd"); - - b.Navigation("Period"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatConnection", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("Connections") - .HasForeignKey("ApplicationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("Rooms") - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoomAccess", b => - { - b.HasOne("Yavsc.Models.Chat.ChatRoom", "Room") - .WithMany("Moderation") - .HasForeignKey("ChannelName") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany("RoomAccess") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Room"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.HasOne("Yavsc.Models.Calendar.Schedule", "Schedule") - .WithMany() - .HasForeignKey("ScheduleOwnerId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "BaseProfile") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("BaseProfile"); - - b.Navigation("Schedule"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany() - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.BrusherProfile", "SelectedProfile") - .WithMany() - .HasForeignKey("SelectedProfileUserId"); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("Location"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Prestation"); - - b.Navigation("Regularization"); - - b.Navigation("SelectedProfile"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("Location"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b => - { - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany() - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairMultiCutQuery", "Query") - .WithMany("Prestations") - .HasForeignKey("QueryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Prestation"); - - b.Navigation("Query"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaint", b => - { - b.HasOne("Yavsc.Models.Drawing.Color", "Color") - .WithMany() - .HasForeignKey("ColorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Color"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaintInstance", b => - { - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany("Taints") - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairTaint", "Taint") - .WithMany() - .HasForeignKey("TaintId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Prestation"); - - b.Navigation("Taint"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Fixing.Bug", b => - { - b.HasOne("Yavsc.Models.IT.Evolution.Feature", "False") - .WithMany() - .HasForeignKey("FeatureId"); - - b.Navigation("False"); - }); - - modelBuilder.Entity("Yavsc.Models.Identity.DeviceDeclaration", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "DeviceOwner") - .WithMany("DeviceDeclaration") - .HasForeignKey("DeviceOwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("DeviceOwner"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.DeclarationFlag", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustDeclaration", "Declaration") - .WithMany("Flags") - .HasForeignKey("DeclarationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Kyc.RegexAlertPattern", "Pattern") - .WithMany() - .HasForeignKey("PatternId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Declaration"); - - b.Navigation("Pattern"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.ModerationLog", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustDeclaration", "Declaration") - .WithMany() - .HasForeignKey("DeclarationId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Declaration"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustToken", "Subject") - .WithMany("Declarations") - .HasForeignKey("TrustTokenId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Subject"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Service", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany("Services") - .HasForeignKey("ContextId"); - - b.Navigation("Context"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.Announce", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.DismissClicked", b => - { - b.HasOne("Yavsc.Abstract.Models.Messaging.Notification", "Notified") - .WithMany() - .HasForeignKey("NotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Notified"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.InstrumentRating", b => - { - b.HasOne("Yavsc.Models.Musical.Instrument", "Instrument") - .WithMany() - .HasForeignKey("InstrumentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Profile") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Instrument"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalPreference", b => - { - b.HasOne("Yavsc.Models.Musical.Profiles.DjSettings", null) - .WithMany("SoundColor") - .HasForeignKey("DjSettingsUserId"); - - b.HasOne("Yavsc.Models.Musical.Profiles.MusicLoverSettings", null) - .WithMany("SoundColor") - .HasForeignKey("MusicLoverSettingsUserId"); - - b.HasOne("Yavsc.Models.Musical.MusicalTendency", "MusicalTendency") - .WithMany() - .HasForeignKey("TendencyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("MusicalTendency"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.Instrumentation", b => - { - b.HasOne("Yavsc.Models.Musical.Instrument", "Tool") - .WithMany() - .HasForeignKey("InstrumentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Tool"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Executor") - .WithMany() - .HasForeignKey("ExecutorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Executor"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany("Circles") - .HasForeignKey("ApplicationUserId"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.CircleMember", b => - { - b.HasOne("Yavsc.Models.Relationship.Circle", "Circle") - .WithMany("Members") - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Member") - .WithMany("Membership") - .HasForeignKey("MemberId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Circle"); - - b.Navigation("Member"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Contact", b => - { - b.HasOne("Yavsc.Models.Relationship.PostalAddress", "PostalAddress") - .WithMany() - .HasForeignKey("AddressId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany("Book") - .HasForeignKey("ApplicationUserId"); - - b.Navigation("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.HyperLink", b => - { - b.HasOne("Yavsc.Models.Haircut.BrusherProfile", null) - .WithMany("Links") - .HasForeignKey("BrusherProfileUserId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", null) - .WithMany("Links") - .HasForeignKey("PayPalPaymentCreationToken"); - }); - - modelBuilder.Entity("Yavsc.Models.Streaming.LiveFlow", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentCode"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CoWorking", b => - { - b.HasOne("Yavsc.Models.Workflow.Profiles.FormationSettings", null) - .WithMany("CoWorking") - .HasForeignKey("FormationSettingsUserId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Performer") - .WithMany() - .HasForeignKey("PerformerId"); - - b.HasOne("Yavsc.Models.ApplicationUser", "WorkingFor") - .WithMany() - .HasForeignKey("WorkingForId"); - - b.Navigation("Performer"); - - b.Navigation("WorkingFor"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CommandForm", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany("Forms") - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Context"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b => - { - b.HasOne("Yavsc.Models.Workflow.Country", "Country") - .WithMany() - .HasForeignKey("CountryCode") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Country"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "OrganizationAddress") - .WithMany() - .HasForeignKey("OrganizationAddressId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Performer") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationAddress"); - - b.Navigation("Performer"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("Location"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Does") - .WithMany() - .HasForeignKey("DoesCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "User") - .WithMany("Activity") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Does"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", "Repository") - .WithMany() - .HasForeignKey("GitId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - - b.Navigation("Repository"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => - { - b.HasOne("Yavsc.Server.Models.IT.Project", "TargetProject") - .WithMany("Configurations") - .HasForeignKey("ProjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetProject"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b => - { - b.Navigation("Properties"); - - b.Navigation("Scopes"); - - b.Navigation("Secrets"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b => - { - b.Navigation("AllowedCorsOrigins"); - - b.Navigation("AllowedGrantTypes"); - - b.Navigation("AllowedScopes"); - - b.Navigation("Claims"); - - b.Navigation("ClientSecrets"); - - b.Navigation("IdentityProviderRestrictions"); - - b.Navigation("PostLogoutRedirectUris"); - - b.Navigation("Properties"); - - b.Navigation("RedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResource", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.Navigation("AccountBalance"); - - b.Navigation("BankInfo"); - - b.Navigation("BlackList"); - - b.Navigation("BlogComments"); - - b.Navigation("Book"); - - b.Navigation("Circles"); - - b.Navigation("Connections"); - - b.Navigation("DeviceDeclaration"); - - b.Navigation("Membership"); - - b.Navigation("Posts"); - - b.Navigation("RoomAccess"); - - b.Navigation("Rooms"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.Navigation("Bill"); - - b.Navigation("Signatures"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b => - { - b.Navigation("Bill"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.Navigation("ACL"); - - b.Navigation("Comments"); - - b.Navigation("Tags"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.Navigation("Children"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.Navigation("Events"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.Navigation("Moderation"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.Navigation("Prestations"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => - { - b.Navigation("Taints"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.Navigation("Flags"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustToken", b => - { - b.Navigation("Declarations"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.DjSettings", b => - { - b.Navigation("SoundColor"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.MusicLoverSettings", b => - { - b.Navigation("SoundColor"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.Navigation("Children"); - - b.Navigation("Forms"); - - b.Navigation("Services"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.Navigation("Activity"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Profiles.FormationSettings", b => - { - b.Navigation("CoWorking"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.Navigation("Configurations"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260907091755_AddCircleAuthorizationToFileAcl.cs b/src/Yavsc.Org/Migrations/20260907091755_AddCircleAuthorizationToFileAcl.cs deleted file mode 100644 index b5112e7bf..000000000 --- a/src/Yavsc.Org/Migrations/20260907091755_AddCircleAuthorizationToFileAcl.cs +++ /dev/null @@ -1,825 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Yavsc.Migrations -{ - /// - public partial class AddCircleAuthorizationToFileAcl : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_BankIdentity_AspNetUsers_UserId", - table: "BankIdentity"); - - migrationBuilder.DropForeignKey( - name: "FK_DeviceDeclaration_AspNetUsers_DeviceOwnerId", - table: "DeviceDeclaration"); - - migrationBuilder.DropForeignKey( - name: "FK_Estimates_Performers_OwnerId", - table: "Estimates"); - - migrationBuilder.DropForeignKey( - name: "FK_HairMultiCutQueries_Locations_LocationId", - table: "HairMultiCutQueries"); - - migrationBuilder.DropForeignKey( - name: "FK_RdvQueries_Locations_LocationId", - table: "RdvQueries"); - - migrationBuilder.AlterColumn( - name: "Content", - table: "TrustDeclarations", - type: "character varying(2000)", - maxLength: 2000, - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "UserModified", - table: "RdvQueries", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "UserCreated", - table: "RdvQueries", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Reason", - table: "RdvQueries", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "LocationId", - table: "RdvQueries", - type: "bigint", - nullable: false, - defaultValue: 0L, - oldClrType: typeof(long), - oldType: "bigint", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Description", - table: "RdvQueries", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "UserModified", - table: "Project", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "UserCreated", - table: "Project", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "WebSite", - table: "Performers", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "ExerciseCountryCode", - table: "Performers", - type: "text", - nullable: false, - oldClrType: typeof(string), - oldType: "character varying(2)", - oldMaxLength: 2); - - migrationBuilder.AlterColumn( - name: "UserModified", - table: "HairMultiCutQueries", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "UserCreated", - table: "HairMultiCutQueries", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "LocationId", - table: "HairMultiCutQueries", - type: "bigint", - nullable: false, - defaultValue: 0L, - oldClrType: typeof(long), - oldType: "bigint", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Description", - table: "HairMultiCutQueries", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "UserModified", - table: "HairCutQueries", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "UserCreated", - table: "HairCutQueries", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Description", - table: "HairCutQueries", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "AdditionalInfo", - table: "HairCutQueries", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Title", - table: "Estimates", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "OwnerId", - table: "Estimates", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Description", - table: "Estimates", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "AttachedGraphicsString", - table: "Estimates", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "AttachedFilesString", - table: "Estimates", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Version", - table: "DeviceDeclaration", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Platform", - table: "DeviceDeclaration", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Model", - table: "DeviceDeclaration", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "DeviceOwnerId", - table: "DeviceDeclaration", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "UserModified", - table: "Comment", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "UserCreated", - table: "Comment", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Article", - table: "Comment", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Title", - table: "Bug", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "Description", - table: "Bug", - type: "character varying(10240)", - maxLength: 10240, - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "character varying(10240)", - oldMaxLength: 10240, - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "WicketCode", - table: "BankIdentity", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "UserId", - table: "BankIdentity", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "IBAN", - table: "BankIdentity", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "BankCode", - table: "BankIdentity", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "BIC", - table: "BankIdentity", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "AccountNumber", - table: "BankIdentity", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AddForeignKey( - name: "FK_BankIdentity_AspNetUsers_UserId", - table: "BankIdentity", - column: "UserId", - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_DeviceDeclaration_AspNetUsers_DeviceOwnerId", - table: "DeviceDeclaration", - column: "DeviceOwnerId", - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_Estimates_Performers_OwnerId", - table: "Estimates", - column: "OwnerId", - principalTable: "Performers", - principalColumn: "PerformerId", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_HairMultiCutQueries_Locations_LocationId", - table: "HairMultiCutQueries", - column: "LocationId", - principalTable: "Locations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_RdvQueries_Locations_LocationId", - table: "RdvQueries", - column: "LocationId", - principalTable: "Locations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_BankIdentity_AspNetUsers_UserId", - table: "BankIdentity"); - - migrationBuilder.DropForeignKey( - name: "FK_DeviceDeclaration_AspNetUsers_DeviceOwnerId", - table: "DeviceDeclaration"); - - migrationBuilder.DropForeignKey( - name: "FK_Estimates_Performers_OwnerId", - table: "Estimates"); - - migrationBuilder.DropForeignKey( - name: "FK_HairMultiCutQueries_Locations_LocationId", - table: "HairMultiCutQueries"); - - migrationBuilder.DropForeignKey( - name: "FK_RdvQueries_Locations_LocationId", - table: "RdvQueries"); - - migrationBuilder.AlterColumn( - name: "Content", - table: "TrustDeclarations", - type: "character varying(2000)", - maxLength: 2000, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(2000)", - oldMaxLength: 2000); - - migrationBuilder.AlterColumn( - name: "UserModified", - table: "RdvQueries", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "UserCreated", - table: "RdvQueries", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "Reason", - table: "RdvQueries", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "LocationId", - table: "RdvQueries", - type: "bigint", - nullable: true, - oldClrType: typeof(long), - oldType: "bigint"); - - migrationBuilder.AlterColumn( - name: "Description", - table: "RdvQueries", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "UserModified", - table: "Project", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "UserCreated", - table: "Project", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "WebSite", - table: "Performers", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "ExerciseCountryCode", - table: "Performers", - type: "character varying(2)", - maxLength: 2, - nullable: false, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "UserModified", - table: "HairMultiCutQueries", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "UserCreated", - table: "HairMultiCutQueries", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "LocationId", - table: "HairMultiCutQueries", - type: "bigint", - nullable: true, - oldClrType: typeof(long), - oldType: "bigint"); - - migrationBuilder.AlterColumn( - name: "Description", - table: "HairMultiCutQueries", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "UserModified", - table: "HairCutQueries", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "UserCreated", - table: "HairCutQueries", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "Description", - table: "HairCutQueries", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "AdditionalInfo", - table: "HairCutQueries", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "Title", - table: "Estimates", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "OwnerId", - table: "Estimates", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "Description", - table: "Estimates", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "AttachedGraphicsString", - table: "Estimates", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "AttachedFilesString", - table: "Estimates", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "Version", - table: "DeviceDeclaration", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "Platform", - table: "DeviceDeclaration", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "Model", - table: "DeviceDeclaration", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "DeviceOwnerId", - table: "DeviceDeclaration", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "UserModified", - table: "Comment", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "UserCreated", - table: "Comment", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "Article", - table: "Comment", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "Title", - table: "Bug", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "Description", - table: "Bug", - type: "character varying(10240)", - maxLength: 10240, - nullable: true, - oldClrType: typeof(string), - oldType: "character varying(10240)", - oldMaxLength: 10240); - - migrationBuilder.AlterColumn( - name: "WicketCode", - table: "BankIdentity", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "UserId", - table: "BankIdentity", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "IBAN", - table: "BankIdentity", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "BankCode", - table: "BankIdentity", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "BIC", - table: "BankIdentity", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "AccountNumber", - table: "BankIdentity", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AddForeignKey( - name: "FK_BankIdentity_AspNetUsers_UserId", - table: "BankIdentity", - column: "UserId", - principalTable: "AspNetUsers", - principalColumn: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_DeviceDeclaration_AspNetUsers_DeviceOwnerId", - table: "DeviceDeclaration", - column: "DeviceOwnerId", - principalTable: "AspNetUsers", - principalColumn: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_Estimates_Performers_OwnerId", - table: "Estimates", - column: "OwnerId", - principalTable: "Performers", - principalColumn: "PerformerId"); - - migrationBuilder.AddForeignKey( - name: "FK_HairMultiCutQueries_Locations_LocationId", - table: "HairMultiCutQueries", - column: "LocationId", - principalTable: "Locations", - principalColumn: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_RdvQueries_Locations_LocationId", - table: "RdvQueries", - column: "LocationId", - principalTable: "Locations", - principalColumn: "Id"); - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260907100133_fileACL.Designer.cs b/src/Yavsc.Org/Migrations/20260907100133_fileACL.Designer.cs deleted file mode 100644 index 186e1a66d..000000000 --- a/src/Yavsc.Org/Migrations/20260907100133_fileACL.Designer.cs +++ /dev/null @@ -1,4818 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Yavsc.Models; - -#nullable disable - -namespace Yavsc.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260907100133_fileACL")] - partial class fileACL - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("AllowedAccessTokenSigningAlgorithms") - .HasColumnType("text"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("ApiResources"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Scope") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceSecrets"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("ApiScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("ScopeId1") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.HasIndex("ScopeId1"); - - b.ToTable("ApiScopeClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("ScopeId1") - .HasColumnType("integer"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.HasIndex("ScopeId1"); - - b.ToTable("ApiScopeProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("AbsoluteRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenType") - .HasColumnType("integer"); - - b.Property("AllowAccessTokensViaBrowser") - .HasColumnType("boolean"); - - b.Property("AllowOfflineAccess") - .HasColumnType("boolean"); - - b.Property("AllowPlainTextPkce") - .HasColumnType("boolean"); - - b.Property("AllowRememberConsent") - .HasColumnType("boolean"); - - b.Property("AllowedIdentityTokenSigningAlgorithms") - .HasColumnType("text"); - - b.Property("AlwaysIncludeUserClaimsInIdToken") - .HasColumnType("boolean"); - - b.Property("AlwaysSendClientClaims") - .HasColumnType("boolean"); - - b.Property("AuthorizationCodeLifetime") - .HasColumnType("integer"); - - b.Property("BackChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("BackChannelLogoutUri") - .HasColumnType("text"); - - b.Property("ClientClaimsPrefix") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("ClientName") - .HasColumnType("text"); - - b.Property("ClientUri") - .HasColumnType("text"); - - b.Property("ConsentLifetime") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DeviceCodeLifetime") - .HasColumnType("integer"); - - b.Property("EnableLocalLogin") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutUri") - .HasColumnType("text"); - - b.Property("IdentityTokenLifetime") - .HasColumnType("integer"); - - b.Property("IncludeJwtId") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("LogoUri") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("PairWiseSubjectSalt") - .HasColumnType("text"); - - b.Property("ProtocolType") - .HasColumnType("text"); - - b.Property("RefreshTokenExpiration") - .HasColumnType("integer"); - - b.Property("RefreshTokenUsage") - .HasColumnType("integer"); - - b.Property("RequireClientSecret") - .HasColumnType("boolean"); - - b.Property("RequireConsent") - .HasColumnType("boolean"); - - b.Property("RequirePkce") - .HasColumnType("boolean"); - - b.Property("RequireRequestObject") - .HasColumnType("boolean"); - - b.Property("SlidingRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("UpdateAccessTokenClaimsOnRefresh") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.Property("UserCodeType") - .HasColumnType("text"); - - b.Property("UserSsoLifetime") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("Clients"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Origin") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientCorsOrigins"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("GrantType") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientGrantTypes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Provider") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientIdPRestrictions"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("PostLogoutRedirectUri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientPostLogoutRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("RedirectUri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Scope") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientSecrets"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.DeviceFlowCodes", b => - { - b.Property("UserCode") - .HasColumnType("text"); - - b.Property("DeviceCode") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasColumnType("text"); - - b.Property("SubjectId") - .HasColumnType("text"); - - b.HasKey("UserCode", "DeviceCode"); - - b.ToTable("DeviceFlowCodes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("IdentityResources"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.PersistedGrant", b => - { - b.Property("Key") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("ConsumedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasColumnType("text"); - - b.Property("SubjectId") - .HasColumnType("text"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Key"); - - b.ToTable("PersistedGrants"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("Yavsc.Abstract.Identity.ClientProviderInfo", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Avatar") - .HasColumnType("text"); - - b.Property("BillingAddressId") - .HasColumnType("bigint"); - - b.Property("EMail") - .HasColumnType("text"); - - b.Property("Phone") - .HasColumnType("text"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("ClientProviderInfo"); - }); - - modelBuilder.Entity("Yavsc.Abstract.Models.Messaging.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Target") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("body") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("click_action") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("color") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("icon") - .ValueGeneratedOnAdd() - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasDefaultValue("exclam"); - - b.Property("sound") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("tag") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("title") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.HasKey("Id"); - - b.ToTable("Notification"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.Ban", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.Property("TargetId") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("TargetId"); - - b.ToTable("Ban"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.BlackListed", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.HasIndex("UserId"); - - b.ToTable("BlackListed"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToBlogPost", b => - { - b.Property("CircleId") - .HasColumnType("bigint"); - - b.Property("BlogPostId") - .HasColumnType("bigint"); - - b.HasKey("CircleId", "BlogPostId"); - - b.HasIndex("BlogPostId"); - - b.ToTable("CircleAuthorizationToBlogPost"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToFile", b => - { - b.Property("CircleId") - .HasColumnType("bigint"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Access") - .HasColumnType("smallint"); - - b.HasKey("CircleId", "Path", "OwnerId"); - - b.HasIndex("OwnerId"); - - b.ToTable("CircleAuthorizationToFile"); - }); - - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("ContactCredits") - .HasColumnType("bigint"); - - b.Property("Credits") - .HasColumnType("numeric"); - - b.HasKey("UserId"); - - b.ToTable("BankStatus"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("AllowMonthlyEmail") - .HasColumnType("boolean"); - - b.Property("Avatar") - .ValueGeneratedOnAdd() - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasDefaultValue("/images/Users/icon_user.png"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("DedicatedGoogleCalendar") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("DiskQuota") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasDefaultValue(524288000L); - - b.Property("DiskUsage") - .HasColumnType("bigint"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("FullName") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("MaxFileSize") - .HasColumnType("bigint"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("PostalAddressId") - .HasColumnType("bigint"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasAlternateKey("Email"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("PostalAddressId"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Yavsc.Models.BalanceImpact", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BalanceId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExecDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Impact") - .HasColumnType("numeric"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("BalanceId"); - - b.ToTable("BalanceImpact"); - }); - - modelBuilder.Entity("Yavsc.Models.Bank.BankIdentity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("BIC") - .IsRequired() - .HasColumnType("text"); - - b.Property("BankCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("BankedKey") - .HasColumnType("integer"); - - b.Property("IBAN") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.Property("WicketCode") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("BankIdentity"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.CommandLine", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Count") - .HasColumnType("integer"); - - b.Property("Currency") - .HasColumnType("text"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.Property("EstimateTemplateId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("UnitaryCost") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.HasIndex("EstimateId"); - - b.HasIndex("EstimateTemplateId"); - - b.ToTable("CommandLine"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AttachedFilesString") - .IsRequired() - .HasColumnType("text"); - - b.Property("AttachedGraphicsString") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CommandId") - .HasColumnType("bigint"); - - b.Property("CommandType") - .IsRequired() - .HasColumnType("text"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProviderValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Title") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("CommandId"); - - b.HasIndex("OwnerId"); - - b.ToTable("Estimates"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EstimateTemplates"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.ExceptionSIREN", b => - { - b.Property("SIREN") - .HasColumnType("text"); - - b.HasKey("SIREN"); - - b.ToTable("ExceptionsSIREN"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CapturedAtUtc") - .HasColumnType("timestamp with time zone"); - - b.Property("CoordinateMax") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(10000); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.Property("FilePath") - .IsRequired() - .HasColumnType("text"); - - b.Property("SignerId") - .IsRequired() - .HasColumnType("text"); - - b.PrimitiveCollection("Strokes") - .IsRequired() - .HasColumnType("integer[]"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SignerId"); - - b.HasIndex("EstimateId", "Type") - .IsUnique(); - - b.ToTable("Signatures"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogAttachedFile", b => - { - b.Property("FileId") - .HasColumnType("bigint"); - - b.Property("PostId") - .HasColumnType("bigint"); - - b.HasKey("FileId", "PostId"); - - b.HasIndex("PostId"); - - b.ToTable("BlogAttachedFiles"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Article") - .HasMaxLength(56224) - .HasColumnType("character varying(56224)"); - - b.Property("AuthorId") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Photo") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.ToTable("BlogSpot"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogTag", b => - { - b.Property("PostId") - .HasColumnType("bigint"); - - b.Property("TagId") - .HasColumnType("bigint"); - - b.HasKey("PostId", "TagId"); - - b.HasIndex("TagId"); - - b.ToTable("BlogTag"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Article") - .IsRequired() - .HasColumnType("text"); - - b.Property("AuthorId") - .IsRequired() - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ParentId") - .HasColumnType("bigint"); - - b.Property("ReceiverId") - .HasColumnType("bigint"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("Visible") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("ParentId"); - - b.HasIndex("ReceiverId"); - - b.ToTable("Comment"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.UploadedFile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContentType") - .HasColumnType("text"); - - b.Property("Length") - .HasColumnType("bigint"); - - b.Property("Path") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("UploadedFiles"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.Property("BlogpostId") - .HasColumnType("bigint"); - - b.HasKey("BlogpostId"); - - b.ToTable("blogSpotPublications"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.Property("OwnerId") - .HasColumnType("text"); - - b.HasKey("OwnerId"); - - b.ToTable("Schedule"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.ScheduledEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PeriodEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("PeriodStart") - .HasColumnType("timestamp with time zone"); - - b.Property("Reccurence") - .HasColumnType("integer"); - - b.Property("ScheduleOwnerId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScheduleOwnerId"); - - b.HasIndex("PeriodStart", "PeriodEnd"); - - b.ToTable("ScheduledEvent"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatConnection", b => - { - b.Property("ConnectionId") - .HasColumnType("text"); - - b.Property("ApplicationUserId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Connected") - .HasColumnType("boolean"); - - b.Property("UserAgent") - .HasColumnType("text"); - - b.HasKey("ConnectionId"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("ChatConnection"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.Property("Name") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("LatestJoinPart") - .HasColumnType("timestamp with time zone"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Topic") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Name"); - - b.HasIndex("OwnerId"); - - b.ToTable("ChatRoom"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoomAccess", b => - { - b.Property("ChannelName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Level") - .HasColumnType("integer"); - - b.HasKey("ChannelName", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("ChatRoomAccess"); - }); - - modelBuilder.Entity("Yavsc.Models.Cratie.Option", b => - { - b.Property("Code") - .HasColumnType("text"); - - b.Property("CodeScrutin") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Code", "CodeScrutin"); - - b.ToTable("Option"); - }); - - modelBuilder.Entity("Yavsc.Models.Drawing.Color", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Blue") - .HasColumnType("smallint"); - - b.Property("Green") - .HasColumnType("smallint"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Red") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.ToTable("Color"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("ActionDistance") - .HasColumnType("integer"); - - b.Property("CarePrice") - .HasColumnType("numeric"); - - b.Property("FlatFeeDiscount") - .HasColumnType("numeric"); - - b.Property("HalfBalayagePrice") - .HasColumnType("numeric"); - - b.Property("HalfBrushingPrice") - .HasColumnType("numeric"); - - b.Property("HalfColorPrice") - .HasColumnType("numeric"); - - b.Property("HalfDefrisPrice") - .HasColumnType("numeric"); - - b.Property("HalfFoldingPrice") - .HasColumnType("numeric"); - - b.Property("HalfMechPrice") - .HasColumnType("numeric"); - - b.Property("HalfMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("HalfPermanentPrice") - .HasColumnType("numeric"); - - b.Property("KidCutPrice") - .HasColumnType("numeric"); - - b.Property("LongBalayagePrice") - .HasColumnType("numeric"); - - b.Property("LongBrushingPrice") - .HasColumnType("numeric"); - - b.Property("LongColorPrice") - .HasColumnType("numeric"); - - b.Property("LongDefrisPrice") - .HasColumnType("numeric"); - - b.Property("LongFoldingPrice") - .HasColumnType("numeric"); - - b.Property("LongMechPrice") - .HasColumnType("numeric"); - - b.Property("LongMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("LongPermanentPrice") - .HasColumnType("numeric"); - - b.Property("ManBrushPrice") - .HasColumnType("numeric"); - - b.Property("ManCutPrice") - .HasColumnType("numeric"); - - b.Property("ScheduleOwnerId") - .HasColumnType("text"); - - b.Property("ShampooPrice") - .HasColumnType("numeric"); - - b.Property("ShortBalayagePrice") - .HasColumnType("numeric"); - - b.Property("ShortBrushingPrice") - .HasColumnType("numeric"); - - b.Property("ShortColorPrice") - .HasColumnType("numeric"); - - b.Property("ShortDefrisPrice") - .HasColumnType("numeric"); - - b.Property("ShortFoldingPrice") - .HasColumnType("numeric"); - - b.Property("ShortMechPrice") - .HasColumnType("numeric"); - - b.Property("ShortMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("ShortPermanentPrice") - .HasColumnType("numeric"); - - b.Property("WomenHalfCutPrice") - .HasColumnType("numeric"); - - b.Property("WomenLongCutPrice") - .HasColumnType("numeric"); - - b.Property("WomenShortCutPrice") - .HasColumnType("numeric"); - - b.HasKey("UserId"); - - b.HasIndex("ScheduleOwnerId"); - - b.ToTable("BrusherProfile"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("AdditionalInfo") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("SelectedProfileUserId") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("LocationId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.HasIndex("PrestationId"); - - b.HasIndex("SelectedProfileUserId"); - - b.ToTable("HairCutQueries"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("LocationId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("HairMultiCutQueries"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Cares") - .HasColumnType("boolean"); - - b.Property("Cut") - .HasColumnType("boolean"); - - b.Property("Dressing") - .HasColumnType("integer"); - - b.Property("Gender") - .HasColumnType("integer"); - - b.Property("Length") - .HasColumnType("integer"); - - b.Property("Shampoo") - .HasColumnType("boolean"); - - b.Property("Tech") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("HairPrestation"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.Property("QueryId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("PrestationId"); - - b.HasIndex("QueryId"); - - b.ToTable("HairPrestationCollectionItem"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaint", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Brand") - .HasColumnType("text"); - - b.Property("ColorId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ColorId"); - - b.ToTable("HairTaint"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaintInstance", b => - { - b.Property("TaintId") - .HasColumnType("bigint"); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.HasKey("TaintId", "PrestationId"); - - b.HasIndex("PrestationId"); - - b.ToTable("HairTaintInstance"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Evolution.Feature", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("ShortName") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("Feature"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Fixing.Bug", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(10240) - .HasColumnType("character varying(10240)"); - - b.Property("FeatureId") - .HasColumnType("bigint"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("Title") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("FeatureId"); - - b.ToTable("Bug"); - }); - - modelBuilder.Entity("Yavsc.Models.Identity.DeviceDeclaration", b => - { - b.Property("DeviceId") - .HasColumnType("text"); - - b.Property("DeclarationDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("LOCALTIMESTAMP"); - - b.Property("DeviceOwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("LatestActivityUpdate") - .HasColumnType("timestamp with time zone"); - - b.Property("Model") - .IsRequired() - .HasColumnType("text"); - - b.Property("Platform") - .IsRequired() - .HasColumnType("text"); - - b.Property("Version") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("DeviceId"); - - b.HasIndex("DeviceOwnerId"); - - b.ToTable("DeviceDeclaration"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.DeclarationFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DeclarationId") - .HasColumnType("bigint"); - - b.Property("MatchExcerpt") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("PatternId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("DeclarationId"); - - b.HasIndex("PatternId"); - - b.ToTable("DeclarationFlag"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.ModerationLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .HasColumnType("integer"); - - b.Property("DeclarationId") - .HasColumnType("bigint"); - - b.Property("ModeratorId") - .HasColumnType("text"); - - b.Property("ScoreDelta") - .HasColumnType("integer"); - - b.Property("Timestamp") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("DeclarationId"); - - b.HasIndex("ModeratorId"); - - b.HasIndex("Timestamp"); - - b.ToTable("ModerationLogs", t => - { - t.HasCheckConstraint("CK_ModerationLog_Immutable", "1=1"); - }); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.RegexAlertPattern", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("Pattern") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("Severity") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("IsActive"); - - b.ToTable("RegexAlertPatterns"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Content") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("DeclarantTokenId") - .HasColumnType("uuid"); - - b.Property("ScoreDelta") - .HasColumnType("integer"); - - b.Property("Sentiment") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TrustTokenId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("Status"); - - b.HasIndex("SubmittedAt"); - - b.HasIndex("TrustTokenId"); - - b.ToTable("TrustDeclarations"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustToken", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("TokenSource") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("TrustScore") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("TokenHash") - .IsUnique(); - - b.ToTable("TrustTokens"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Depth") - .HasColumnType("numeric"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Height") - .HasColumnType("numeric"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Price") - .HasColumnType("numeric"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.Property("Weight") - .HasColumnType("numeric"); - - b.Property("Width") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Service", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContextId") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("ContextId"); - - b.ToTable("Services"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.Announce", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("For") - .HasColumnType("smallint"); - - b.Property("Message") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Sender") - .HasColumnType("text"); - - b.Property("Topic") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("Announce"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.DismissClicked", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("NotificationId") - .HasColumnType("bigint"); - - b.HasKey("UserId", "NotificationId"); - - b.HasIndex("NotificationId"); - - b.ToTable("DismissClicked"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Instrument", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("Instrument"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.InstrumentRating", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("InstrumentId") - .HasColumnType("bigint"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasAlternateKey("InstrumentId", "OwnerId"); - - b.HasIndex("OwnerId"); - - b.ToTable("InstrumentRating"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalPreference", b => - { - b.Property("OwnerProfileId") - .HasColumnType("text"); - - b.Property("DjSettingsUserId") - .HasColumnType("text"); - - b.Property("MusicLoverSettingsUserId") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("TendencyId") - .HasColumnType("bigint"); - - b.HasKey("OwnerProfileId"); - - b.HasIndex("DjSettingsUserId"); - - b.HasIndex("MusicLoverSettingsUserId"); - - b.HasIndex("TendencyId"); - - b.ToTable("MusicalPreference"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalTendency", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("MusicalTendency"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.DjSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("SoundCloudId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("DjSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.Instrumentation", b => - { - b.Property("InstrumentId") - .HasColumnType("bigint"); - - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("InstrumentId", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("Instrumentation"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.MusicLoverSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("MusicLoverSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.Property("CreationToken") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ExecutorId") - .IsRequired() - .HasColumnType("text"); - - b.Property("OrderReference") - .HasColumnType("text"); - - b.Property("PaypalPayerId") - .HasColumnType("text"); - - b.Property("State") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("CreationToken"); - - b.HasIndex("ExecutorId"); - - b.ToTable("PayPalPayment"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApplicationUserId") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("Circle"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.CircleMember", b => - { - b.Property("MemberId") - .HasColumnType("text"); - - b.Property("CircleId") - .HasColumnType("bigint"); - - b.HasKey("MemberId", "CircleId"); - - b.HasIndex("CircleId"); - - b.ToTable("CircleMembers"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Contact", b => - { - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("AddressId") - .HasColumnType("bigint"); - - b.Property("ApplicationUserId") - .HasColumnType("text"); - - b.Property("EMail") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.HasKey("OwnerId", "UserId"); - - b.HasIndex("AddressId"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("Contact"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.HyperLink", b => - { - b.Property("HRef") - .HasColumnType("text"); - - b.Property("Method") - .HasColumnType("text"); - - b.Property("BrusherProfileUserId") - .HasColumnType("text"); - - b.Property("ContentType") - .HasColumnType("text"); - - b.Property("PayPalPaymentCreationToken") - .HasColumnType("text"); - - b.Property("Rel") - .HasColumnType("text"); - - b.HasKey("HRef", "Method"); - - b.HasIndex("BrusherProfileUserId"); - - b.HasIndex("PayPalPaymentCreationToken"); - - b.ToTable("HyperLink"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Location", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Address") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("Latitude") - .HasColumnType("double precision"); - - b.Property("Longitude") - .HasColumnType("double precision"); - - b.HasKey("Id"); - - b.ToTable("Locations"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.PostalAddress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("City") - .HasColumnType("text"); - - b.Property("Country") - .HasColumnType("text"); - - b.Property("PostalCode") - .HasColumnType("text"); - - b.Property("Province") - .HasColumnType("text"); - - b.Property("State") - .HasColumnType("text"); - - b.Property("Street1") - .HasColumnType("text"); - - b.Property("Street2") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Tag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Tags"); - }); - - modelBuilder.Entity("Yavsc.Models.Skill", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("SiteSkills"); - }); - - modelBuilder.Entity("Yavsc.Models.Streaming.LiveFlow", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DifferedFileName") - .HasColumnType("text"); - - b.Property("MediaType") - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Pitch") - .HasColumnType("text"); - - b.Property("SequenceNumber") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("LiveFlow"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.Property("Code") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Hidden") - .HasColumnType("boolean"); - - b.Property("Moderated") - .HasColumnType("boolean"); - - b.Property("ModeratorGroupName") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("ParentCode") - .HasColumnType("text"); - - b.Property("Photo") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("SettingsClassName") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Code"); - - b.HasIndex("ParentCode"); - - b.ToTable("Activities"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CoWorking", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FormationSettingsUserId") - .HasColumnType("text"); - - b.Property("PerformerId") - .HasColumnType("text"); - - b.Property("WorkingForId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("FormationSettingsUserId"); - - b.HasIndex("PerformerId"); - - b.HasIndex("WorkingForId"); - - b.ToTable("CoWorking"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CommandForm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionName") - .HasColumnType("text"); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.ToTable("CommandForm"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Country", b => - { - b.Property("Code") - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.HasKey("Code"); - - b.ToTable("Countries"); - - b.HasData( - new - { - Code = "fr", - DisplayName = "France" - }, - new - { - Code = "en", - DisplayName = "England" - }, - new - { - Code = "pt", - DisplayName = "Portugal" - }); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CountryCode") - .IsRequired() - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("ErrorMessage") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("RegularExpression") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("CountryCode"); - - b.ToTable("PerformerCodeInputValidations"); - - b.HasData( - new - { - Id = 1L, - CountryCode = "fr", - ErrorMessage = "Le code FR doit contenir entre 9 et 14 chiffres.", - RegularExpression = "^[0-9]{9,14}$" - }, - new - { - Id = 2L, - CountryCode = "en", - ErrorMessage = "Le code EN doit contenir entre 8 et 14 caracteres alphanumeriques.", - RegularExpression = "^[A-Za-z0-9]{8,14}$" - }, - new - { - Id = 3L, - CountryCode = "pt", - ErrorMessage = "Le code PT doit contenir exactement 9 chiffres.", - RegularExpression = "^[0-9]{9}$" - }); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.Property("PerformerId") - .HasColumnType("text"); - - b.Property("AcceptNotifications") - .HasColumnType("boolean"); - - b.Property("AcceptPublicContact") - .HasColumnType("boolean"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("ExerciseCountryCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("MaxDailyCost") - .HasColumnType("integer"); - - b.Property("MinDailyCost") - .HasColumnType("integer"); - - b.Property("OrganizationAddressId") - .HasColumnType("bigint"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("SIREN") - .IsRequired() - .HasColumnType("text"); - - b.Property("UseGeoLocalizationToReduceDistanceWithClients") - .HasColumnType("boolean"); - - b.Property("WebSite") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("PerformerId"); - - b.HasIndex("OrganizationAddressId"); - - b.ToTable("Performers"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Profiles.FormationSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("FormationSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("LocationType") - .HasColumnType("integer"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("LocationId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("RdvQueries"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => - { - b.Property("DoesCode") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Weight") - .HasColumnType("integer"); - - b.HasKey("DoesCode", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("UserActivities"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.Calendar.Period", b => - { - b.Property("Start") - .HasColumnType("timestamp with time zone"); - - b.Property("End") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Start", "End"); - - b.ToTable("Period"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.EMailing.MailingTemplate", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("Body") - .HasMaxLength(65536) - .HasColumnType("character varying(65536)"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ReplyToAddress") - .HasColumnType("text"); - - b.Property("ToSend") - .HasColumnType("integer"); - - b.Property("Topic") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("MailingTemplate"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("GitId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Version") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("GitId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("Project"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProjectId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ProjectId"); - - b.ToTable("ProjectBuildConfiguration"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Branch") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Path") - .IsRequired() - .HasColumnType("text"); - - b.Property("Url") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("GitRepositoryReference"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("UserClaims") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Properties") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Scopes") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Secrets") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", null) - .WithMany("UserClaims") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", "Scope") - .WithMany() - .HasForeignKey("ScopeId1"); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", null) - .WithMany("Properties") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", "Scope") - .WithMany() - .HasForeignKey("ScopeId1"); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("Claims") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("AllowedCorsOrigins") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedGrantTypes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("IdentityProviderRestrictions") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("PostLogoutRedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("Properties") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("RedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedScopes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientSecret", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("ClientSecrets") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("UserClaims") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("Properties") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Yavsc.Models.Access.Ban", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "TargetUser") - .WithMany() - .HasForeignKey("TargetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetUser"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.BlackListed", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("BlackList") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToBlogPost", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Target") - .WithMany("ACL") - .HasForeignKey("BlogPostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Circle", "Allowed") - .WithMany() - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Allowed"); - - b.Navigation("Target"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToFile", b => - { - b.HasOne("Yavsc.Models.Relationship.Circle", "Allowed") - .WithMany() - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Allowed"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithOne("AccountBalance") - .HasForeignKey("Yavsc.Models.AccountBalance", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "PostalAddress") - .WithMany() - .HasForeignKey("PostalAddressId"); - - b.Navigation("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.BalanceImpact", b => - { - b.HasOne("Yavsc.Models.AccountBalance", "Balance") - .WithMany() - .HasForeignKey("BalanceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Balance"); - }); - - modelBuilder.Entity("Yavsc.Models.Bank.BankIdentity", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany("BankInfo") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.CommandLine", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", null) - .WithMany("Bill") - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Billing.EstimateTemplate", null) - .WithMany("Bill") - .HasForeignKey("EstimateTemplateId"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.RdvQuery", "Query") - .WithMany() - .HasForeignKey("CommandId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Owner"); - - b.Navigation("Query"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", "Estimate") - .WithMany("Signatures") - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Signer") - .WithMany() - .HasForeignKey("SignerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Estimate"); - - b.Navigation("Signer"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogAttachedFile", b => - { - b.HasOne("Yavsc.Models.Blog.UploadedFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany() - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("File"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Author") - .WithMany("Posts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Author"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogTag", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany("Tags") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Tag", "Tag") - .WithMany() - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Post"); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Author") - .WithMany("BlogComments") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.Comment", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany("Comments") - .HasForeignKey("ReceiverId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Author"); - - b.Navigation("Parent"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "BlogPost") - .WithMany() - .HasForeignKey("BlogpostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("BlogPost"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.ScheduledEvent", b => - { - b.HasOne("Yavsc.Models.Calendar.Schedule", null) - .WithMany("Events") - .HasForeignKey("ScheduleOwnerId"); - - b.HasOne("Yavsc.Server.Models.Calendar.Period", "Period") - .WithMany() - .HasForeignKey("PeriodStart", "PeriodEnd"); - - b.Navigation("Period"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatConnection", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("Connections") - .HasForeignKey("ApplicationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("Rooms") - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoomAccess", b => - { - b.HasOne("Yavsc.Models.Chat.ChatRoom", "Room") - .WithMany("Moderation") - .HasForeignKey("ChannelName") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany("RoomAccess") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Room"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.HasOne("Yavsc.Models.Calendar.Schedule", "Schedule") - .WithMany() - .HasForeignKey("ScheduleOwnerId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "BaseProfile") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("BaseProfile"); - - b.Navigation("Schedule"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany() - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.BrusherProfile", "SelectedProfile") - .WithMany() - .HasForeignKey("SelectedProfileUserId"); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("Location"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Prestation"); - - b.Navigation("Regularization"); - - b.Navigation("SelectedProfile"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("Location"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b => - { - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany() - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairMultiCutQuery", "Query") - .WithMany("Prestations") - .HasForeignKey("QueryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Prestation"); - - b.Navigation("Query"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaint", b => - { - b.HasOne("Yavsc.Models.Drawing.Color", "Color") - .WithMany() - .HasForeignKey("ColorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Color"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaintInstance", b => - { - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany("Taints") - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairTaint", "Taint") - .WithMany() - .HasForeignKey("TaintId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Prestation"); - - b.Navigation("Taint"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Fixing.Bug", b => - { - b.HasOne("Yavsc.Models.IT.Evolution.Feature", "False") - .WithMany() - .HasForeignKey("FeatureId"); - - b.Navigation("False"); - }); - - modelBuilder.Entity("Yavsc.Models.Identity.DeviceDeclaration", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "DeviceOwner") - .WithMany("DeviceDeclaration") - .HasForeignKey("DeviceOwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("DeviceOwner"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.DeclarationFlag", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustDeclaration", "Declaration") - .WithMany("Flags") - .HasForeignKey("DeclarationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Kyc.RegexAlertPattern", "Pattern") - .WithMany() - .HasForeignKey("PatternId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Declaration"); - - b.Navigation("Pattern"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.ModerationLog", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustDeclaration", "Declaration") - .WithMany() - .HasForeignKey("DeclarationId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Declaration"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustToken", "Subject") - .WithMany("Declarations") - .HasForeignKey("TrustTokenId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Subject"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Service", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany("Services") - .HasForeignKey("ContextId"); - - b.Navigation("Context"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.Announce", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.DismissClicked", b => - { - b.HasOne("Yavsc.Abstract.Models.Messaging.Notification", "Notified") - .WithMany() - .HasForeignKey("NotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Notified"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.InstrumentRating", b => - { - b.HasOne("Yavsc.Models.Musical.Instrument", "Instrument") - .WithMany() - .HasForeignKey("InstrumentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Profile") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Instrument"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalPreference", b => - { - b.HasOne("Yavsc.Models.Musical.Profiles.DjSettings", null) - .WithMany("SoundColor") - .HasForeignKey("DjSettingsUserId"); - - b.HasOne("Yavsc.Models.Musical.Profiles.MusicLoverSettings", null) - .WithMany("SoundColor") - .HasForeignKey("MusicLoverSettingsUserId"); - - b.HasOne("Yavsc.Models.Musical.MusicalTendency", "MusicalTendency") - .WithMany() - .HasForeignKey("TendencyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("MusicalTendency"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.Instrumentation", b => - { - b.HasOne("Yavsc.Models.Musical.Instrument", "Tool") - .WithMany() - .HasForeignKey("InstrumentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Tool"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Executor") - .WithMany() - .HasForeignKey("ExecutorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Executor"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany("Circles") - .HasForeignKey("ApplicationUserId"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.CircleMember", b => - { - b.HasOne("Yavsc.Models.Relationship.Circle", "Circle") - .WithMany("Members") - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Member") - .WithMany("Membership") - .HasForeignKey("MemberId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Circle"); - - b.Navigation("Member"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Contact", b => - { - b.HasOne("Yavsc.Models.Relationship.PostalAddress", "PostalAddress") - .WithMany() - .HasForeignKey("AddressId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany("Book") - .HasForeignKey("ApplicationUserId"); - - b.Navigation("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.HyperLink", b => - { - b.HasOne("Yavsc.Models.Haircut.BrusherProfile", null) - .WithMany("Links") - .HasForeignKey("BrusherProfileUserId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", null) - .WithMany("Links") - .HasForeignKey("PayPalPaymentCreationToken"); - }); - - modelBuilder.Entity("Yavsc.Models.Streaming.LiveFlow", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentCode"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CoWorking", b => - { - b.HasOne("Yavsc.Models.Workflow.Profiles.FormationSettings", null) - .WithMany("CoWorking") - .HasForeignKey("FormationSettingsUserId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Performer") - .WithMany() - .HasForeignKey("PerformerId"); - - b.HasOne("Yavsc.Models.ApplicationUser", "WorkingFor") - .WithMany() - .HasForeignKey("WorkingForId"); - - b.Navigation("Performer"); - - b.Navigation("WorkingFor"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CommandForm", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany("Forms") - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Context"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b => - { - b.HasOne("Yavsc.Models.Workflow.Country", "Country") - .WithMany() - .HasForeignKey("CountryCode") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Country"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "OrganizationAddress") - .WithMany() - .HasForeignKey("OrganizationAddressId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Performer") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationAddress"); - - b.Navigation("Performer"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("Location"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Does") - .WithMany() - .HasForeignKey("DoesCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "User") - .WithMany("Activity") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Does"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", "Repository") - .WithMany() - .HasForeignKey("GitId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - - b.Navigation("Repository"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => - { - b.HasOne("Yavsc.Server.Models.IT.Project", "TargetProject") - .WithMany("Configurations") - .HasForeignKey("ProjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetProject"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b => - { - b.Navigation("Properties"); - - b.Navigation("Scopes"); - - b.Navigation("Secrets"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b => - { - b.Navigation("AllowedCorsOrigins"); - - b.Navigation("AllowedGrantTypes"); - - b.Navigation("AllowedScopes"); - - b.Navigation("Claims"); - - b.Navigation("ClientSecrets"); - - b.Navigation("IdentityProviderRestrictions"); - - b.Navigation("PostLogoutRedirectUris"); - - b.Navigation("Properties"); - - b.Navigation("RedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResource", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.Navigation("AccountBalance"); - - b.Navigation("BankInfo"); - - b.Navigation("BlackList"); - - b.Navigation("BlogComments"); - - b.Navigation("Book"); - - b.Navigation("Circles"); - - b.Navigation("Connections"); - - b.Navigation("DeviceDeclaration"); - - b.Navigation("Membership"); - - b.Navigation("Posts"); - - b.Navigation("RoomAccess"); - - b.Navigation("Rooms"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.Navigation("Bill"); - - b.Navigation("Signatures"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b => - { - b.Navigation("Bill"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.Navigation("ACL"); - - b.Navigation("Comments"); - - b.Navigation("Tags"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.Navigation("Children"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.Navigation("Events"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.Navigation("Moderation"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.Navigation("Prestations"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => - { - b.Navigation("Taints"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.Navigation("Flags"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustToken", b => - { - b.Navigation("Declarations"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.DjSettings", b => - { - b.Navigation("SoundColor"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.MusicLoverSettings", b => - { - b.Navigation("SoundColor"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.Navigation("Children"); - - b.Navigation("Forms"); - - b.Navigation("Services"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.Navigation("Activity"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Profiles.FormationSettings", b => - { - b.Navigation("CoWorking"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.Navigation("Configurations"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260907100133_fileACL.cs b/src/Yavsc.Org/Migrations/20260907100133_fileACL.cs deleted file mode 100644 index 2ab242764..000000000 --- a/src/Yavsc.Org/Migrations/20260907100133_fileACL.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Yavsc.Migrations -{ - /// - public partial class fileACL : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "CircleAuthorizationToFile", - columns: table => new - { - CircleId = table.Column(type: "bigint", nullable: false), - Path = table.Column(type: "text", nullable: false), - OwnerId = table.Column(type: "text", nullable: false), - Access = table.Column(type: "smallint", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_CircleAuthorizationToFile", x => new { x.CircleId, x.Path, x.OwnerId }); - table.ForeignKey( - name: "FK_CircleAuthorizationToFile_AspNetUsers_OwnerId", - column: x => x.OwnerId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_CircleAuthorizationToFile_Circle_CircleId", - column: x => x.CircleId, - principalTable: "Circle", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_CircleAuthorizationToFile_OwnerId", - table: "CircleAuthorizationToFile", - column: "OwnerId"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "CircleAuthorizationToFile"); - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260912114726_genericEstimate.Designer.cs b/src/Yavsc.Org/Migrations/20260912114726_genericEstimate.Designer.cs deleted file mode 100644 index 2b2240e1d..000000000 --- a/src/Yavsc.Org/Migrations/20260912114726_genericEstimate.Designer.cs +++ /dev/null @@ -1,4714 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Yavsc.Models; - -#nullable disable - -namespace Yavsc.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260912114726_genericEstimate")] - partial class genericEstimate - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("AllowedAccessTokenSigningAlgorithms") - .HasColumnType("text"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("ApiResources"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Scope") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceSecrets"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("ApiScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("ScopeId1") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.HasIndex("ScopeId1"); - - b.ToTable("ApiScopeClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("ScopeId1") - .HasColumnType("integer"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.HasIndex("ScopeId1"); - - b.ToTable("ApiScopeProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("AbsoluteRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenType") - .HasColumnType("integer"); - - b.Property("AllowAccessTokensViaBrowser") - .HasColumnType("boolean"); - - b.Property("AllowOfflineAccess") - .HasColumnType("boolean"); - - b.Property("AllowPlainTextPkce") - .HasColumnType("boolean"); - - b.Property("AllowRememberConsent") - .HasColumnType("boolean"); - - b.Property("AllowedIdentityTokenSigningAlgorithms") - .HasColumnType("text"); - - b.Property("AlwaysIncludeUserClaimsInIdToken") - .HasColumnType("boolean"); - - b.Property("AlwaysSendClientClaims") - .HasColumnType("boolean"); - - b.Property("AuthorizationCodeLifetime") - .HasColumnType("integer"); - - b.Property("BackChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("BackChannelLogoutUri") - .HasColumnType("text"); - - b.Property("ClientClaimsPrefix") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("ClientName") - .HasColumnType("text"); - - b.Property("ClientUri") - .HasColumnType("text"); - - b.Property("ConsentLifetime") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DeviceCodeLifetime") - .HasColumnType("integer"); - - b.Property("EnableLocalLogin") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutUri") - .HasColumnType("text"); - - b.Property("IdentityTokenLifetime") - .HasColumnType("integer"); - - b.Property("IncludeJwtId") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("LogoUri") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("PairWiseSubjectSalt") - .HasColumnType("text"); - - b.Property("ProtocolType") - .HasColumnType("text"); - - b.Property("RefreshTokenExpiration") - .HasColumnType("integer"); - - b.Property("RefreshTokenUsage") - .HasColumnType("integer"); - - b.Property("RequireClientSecret") - .HasColumnType("boolean"); - - b.Property("RequireConsent") - .HasColumnType("boolean"); - - b.Property("RequirePkce") - .HasColumnType("boolean"); - - b.Property("RequireRequestObject") - .HasColumnType("boolean"); - - b.Property("SlidingRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("UpdateAccessTokenClaimsOnRefresh") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.Property("UserCodeType") - .HasColumnType("text"); - - b.Property("UserSsoLifetime") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("Clients"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Origin") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientCorsOrigins"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("GrantType") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientGrantTypes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Provider") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientIdPRestrictions"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("PostLogoutRedirectUri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientPostLogoutRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("RedirectUri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Scope") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientSecrets"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.DeviceFlowCodes", b => - { - b.Property("UserCode") - .HasColumnType("text"); - - b.Property("DeviceCode") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasColumnType("text"); - - b.Property("SubjectId") - .HasColumnType("text"); - - b.HasKey("UserCode", "DeviceCode"); - - b.ToTable("DeviceFlowCodes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("IdentityResources"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.PersistedGrant", b => - { - b.Property("Key") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("ConsumedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasColumnType("text"); - - b.Property("SubjectId") - .HasColumnType("text"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Key"); - - b.ToTable("PersistedGrants"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("Yavsc.Abstract.Identity.ClientProviderInfo", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Avatar") - .HasColumnType("text"); - - b.Property("BillingAddressId") - .HasColumnType("bigint"); - - b.Property("EMail") - .HasColumnType("text"); - - b.Property("Phone") - .HasColumnType("text"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("ClientProviderInfo"); - }); - - modelBuilder.Entity("Yavsc.Abstract.Models.Messaging.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Target") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("body") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("click_action") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("color") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("icon") - .ValueGeneratedOnAdd() - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasDefaultValue("exclam"); - - b.Property("sound") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("tag") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("title") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.HasKey("Id"); - - b.ToTable("Notification"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.Ban", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.Property("TargetId") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("TargetId"); - - b.ToTable("Ban"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.BlackListed", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.HasIndex("UserId"); - - b.ToTable("BlackListed"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToBlogPost", b => - { - b.Property("CircleId") - .HasColumnType("bigint"); - - b.Property("BlogPostId") - .HasColumnType("bigint"); - - b.HasKey("CircleId", "BlogPostId"); - - b.HasIndex("BlogPostId"); - - b.ToTable("CircleAuthorizationToBlogPost"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToFile", b => - { - b.Property("CircleId") - .HasColumnType("bigint"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Access") - .HasColumnType("smallint"); - - b.HasKey("CircleId", "Path", "OwnerId"); - - b.HasIndex("OwnerId"); - - b.ToTable("CircleAuthorizationToFile"); - }); - - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("ContactCredits") - .HasColumnType("bigint"); - - b.Property("Credits") - .HasColumnType("numeric"); - - b.HasKey("UserId"); - - b.ToTable("BankStatus"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("AllowMonthlyEmail") - .HasColumnType("boolean"); - - b.Property("Avatar") - .ValueGeneratedOnAdd() - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasDefaultValue("/images/Users/icon_user.png"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("DedicatedGoogleCalendar") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("DiskQuota") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasDefaultValue(524288000L); - - b.Property("DiskUsage") - .HasColumnType("bigint"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("FullName") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("MaxFileSize") - .HasColumnType("bigint"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("PostalAddressId") - .HasColumnType("bigint"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasAlternateKey("Email"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("PostalAddressId"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Yavsc.Models.BalanceImpact", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BalanceId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExecDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Impact") - .HasColumnType("numeric"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("BalanceId"); - - b.ToTable("BalanceImpact"); - }); - - modelBuilder.Entity("Yavsc.Models.Bank.BankIdentity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("BIC") - .IsRequired() - .HasColumnType("text"); - - b.Property("BankCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("BankedKey") - .HasColumnType("integer"); - - b.Property("IBAN") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.Property("WicketCode") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("BankIdentity"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.CommandLine", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Count") - .HasColumnType("integer"); - - b.Property("Currency") - .HasColumnType("text"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.Property("EstimateTemplateId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("UnitaryCost") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.HasIndex("EstimateId"); - - b.HasIndex("EstimateTemplateId"); - - b.ToTable("CommandLine"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AttachedFilesString") - .IsRequired() - .HasColumnType("text"); - - b.Property("AttachedGraphicsString") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CommandId") - .HasColumnType("bigint"); - - b.Property("CommandType") - .IsRequired() - .HasColumnType("text"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProviderValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Title") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("CommandId"); - - b.HasIndex("OwnerId"); - - b.ToTable("Estimates"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EstimateTemplates"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.ExceptionSIREN", b => - { - b.Property("SIREN") - .HasColumnType("text"); - - b.HasKey("SIREN"); - - b.ToTable("ExceptionsSIREN"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.NominativeServiceCommand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("character varying(34)"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("NominativeServiceCommand"); - - b.HasDiscriminator("Discriminator").HasValue("NominativeServiceCommand"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CapturedAtUtc") - .HasColumnType("timestamp with time zone"); - - b.Property("CoordinateMax") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(10000); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.Property("FilePath") - .IsRequired() - .HasColumnType("text"); - - b.Property("SignerId") - .IsRequired() - .HasColumnType("text"); - - b.PrimitiveCollection("Strokes") - .IsRequired() - .HasColumnType("integer[]"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SignerId"); - - b.HasIndex("EstimateId", "Type") - .IsUnique(); - - b.ToTable("Signatures"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogAttachedFile", b => - { - b.Property("FileId") - .HasColumnType("bigint"); - - b.Property("PostId") - .HasColumnType("bigint"); - - b.HasKey("FileId", "PostId"); - - b.HasIndex("PostId"); - - b.ToTable("BlogAttachedFiles"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Article") - .HasMaxLength(56224) - .HasColumnType("character varying(56224)"); - - b.Property("AuthorId") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Photo") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.ToTable("BlogSpot"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogTag", b => - { - b.Property("PostId") - .HasColumnType("bigint"); - - b.Property("TagId") - .HasColumnType("bigint"); - - b.HasKey("PostId", "TagId"); - - b.HasIndex("TagId"); - - b.ToTable("BlogTag"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Article") - .IsRequired() - .HasColumnType("text"); - - b.Property("AuthorId") - .IsRequired() - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ParentId") - .HasColumnType("bigint"); - - b.Property("ReceiverId") - .HasColumnType("bigint"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("Visible") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("ParentId"); - - b.HasIndex("ReceiverId"); - - b.ToTable("Comment"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.UploadedFile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContentType") - .HasColumnType("text"); - - b.Property("Length") - .HasColumnType("bigint"); - - b.Property("Path") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("UploadedFiles"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.Property("BlogpostId") - .HasColumnType("bigint"); - - b.HasKey("BlogpostId"); - - b.ToTable("blogSpotPublications"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.Property("OwnerId") - .HasColumnType("text"); - - b.HasKey("OwnerId"); - - b.ToTable("Schedule"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.ScheduledEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PeriodEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("PeriodStart") - .HasColumnType("timestamp with time zone"); - - b.Property("Reccurence") - .HasColumnType("integer"); - - b.Property("ScheduleOwnerId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScheduleOwnerId"); - - b.HasIndex("PeriodStart", "PeriodEnd"); - - b.ToTable("ScheduledEvent"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatConnection", b => - { - b.Property("ConnectionId") - .HasColumnType("text"); - - b.Property("ApplicationUserId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Connected") - .HasColumnType("boolean"); - - b.Property("UserAgent") - .HasColumnType("text"); - - b.HasKey("ConnectionId"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("ChatConnection"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.Property("Name") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("LatestJoinPart") - .HasColumnType("timestamp with time zone"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Topic") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Name"); - - b.HasIndex("OwnerId"); - - b.ToTable("ChatRoom"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoomAccess", b => - { - b.Property("ChannelName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Level") - .HasColumnType("integer"); - - b.HasKey("ChannelName", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("ChatRoomAccess"); - }); - - modelBuilder.Entity("Yavsc.Models.Cratie.Option", b => - { - b.Property("Code") - .HasColumnType("text"); - - b.Property("CodeScrutin") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Code", "CodeScrutin"); - - b.ToTable("Option"); - }); - - modelBuilder.Entity("Yavsc.Models.Drawing.Color", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Blue") - .HasColumnType("smallint"); - - b.Property("Green") - .HasColumnType("smallint"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Red") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.ToTable("Color"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("ActionDistance") - .HasColumnType("integer"); - - b.Property("CarePrice") - .HasColumnType("numeric"); - - b.Property("FlatFeeDiscount") - .HasColumnType("numeric"); - - b.Property("HalfBalayagePrice") - .HasColumnType("numeric"); - - b.Property("HalfBrushingPrice") - .HasColumnType("numeric"); - - b.Property("HalfColorPrice") - .HasColumnType("numeric"); - - b.Property("HalfDefrisPrice") - .HasColumnType("numeric"); - - b.Property("HalfFoldingPrice") - .HasColumnType("numeric"); - - b.Property("HalfMechPrice") - .HasColumnType("numeric"); - - b.Property("HalfMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("HalfPermanentPrice") - .HasColumnType("numeric"); - - b.Property("KidCutPrice") - .HasColumnType("numeric"); - - b.Property("LongBalayagePrice") - .HasColumnType("numeric"); - - b.Property("LongBrushingPrice") - .HasColumnType("numeric"); - - b.Property("LongColorPrice") - .HasColumnType("numeric"); - - b.Property("LongDefrisPrice") - .HasColumnType("numeric"); - - b.Property("LongFoldingPrice") - .HasColumnType("numeric"); - - b.Property("LongMechPrice") - .HasColumnType("numeric"); - - b.Property("LongMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("LongPermanentPrice") - .HasColumnType("numeric"); - - b.Property("ManBrushPrice") - .HasColumnType("numeric"); - - b.Property("ManCutPrice") - .HasColumnType("numeric"); - - b.Property("ScheduleOwnerId") - .HasColumnType("text"); - - b.Property("ShampooPrice") - .HasColumnType("numeric"); - - b.Property("ShortBalayagePrice") - .HasColumnType("numeric"); - - b.Property("ShortBrushingPrice") - .HasColumnType("numeric"); - - b.Property("ShortColorPrice") - .HasColumnType("numeric"); - - b.Property("ShortDefrisPrice") - .HasColumnType("numeric"); - - b.Property("ShortFoldingPrice") - .HasColumnType("numeric"); - - b.Property("ShortMechPrice") - .HasColumnType("numeric"); - - b.Property("ShortMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("ShortPermanentPrice") - .HasColumnType("numeric"); - - b.Property("WomenHalfCutPrice") - .HasColumnType("numeric"); - - b.Property("WomenLongCutPrice") - .HasColumnType("numeric"); - - b.Property("WomenShortCutPrice") - .HasColumnType("numeric"); - - b.HasKey("UserId"); - - b.HasIndex("ScheduleOwnerId"); - - b.ToTable("BrusherProfile"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Cares") - .HasColumnType("boolean"); - - b.Property("Cut") - .HasColumnType("boolean"); - - b.Property("Dressing") - .HasColumnType("integer"); - - b.Property("Gender") - .HasColumnType("integer"); - - b.Property("Length") - .HasColumnType("integer"); - - b.Property("Shampoo") - .HasColumnType("boolean"); - - b.Property("Tech") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("HairPrestation"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.Property("QueryId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("PrestationId"); - - b.HasIndex("QueryId"); - - b.ToTable("HairPrestationCollectionItem"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaint", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Brand") - .HasColumnType("text"); - - b.Property("ColorId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ColorId"); - - b.ToTable("HairTaint"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaintInstance", b => - { - b.Property("TaintId") - .HasColumnType("bigint"); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.HasKey("TaintId", "PrestationId"); - - b.HasIndex("PrestationId"); - - b.ToTable("HairTaintInstance"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Evolution.Feature", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("ShortName") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("Feature"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Fixing.Bug", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(10240) - .HasColumnType("character varying(10240)"); - - b.Property("FeatureId") - .HasColumnType("bigint"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("Title") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("FeatureId"); - - b.ToTable("Bug"); - }); - - modelBuilder.Entity("Yavsc.Models.Identity.DeviceDeclaration", b => - { - b.Property("DeviceId") - .HasColumnType("text"); - - b.Property("DeclarationDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("LOCALTIMESTAMP"); - - b.Property("DeviceOwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("LatestActivityUpdate") - .HasColumnType("timestamp with time zone"); - - b.Property("Model") - .IsRequired() - .HasColumnType("text"); - - b.Property("Platform") - .IsRequired() - .HasColumnType("text"); - - b.Property("Version") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("DeviceId"); - - b.HasIndex("DeviceOwnerId"); - - b.ToTable("DeviceDeclaration"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.DeclarationFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DeclarationId") - .HasColumnType("bigint"); - - b.Property("MatchExcerpt") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("PatternId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("DeclarationId"); - - b.HasIndex("PatternId"); - - b.ToTable("DeclarationFlag"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.ModerationLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .HasColumnType("integer"); - - b.Property("DeclarationId") - .HasColumnType("bigint"); - - b.Property("ModeratorId") - .HasColumnType("text"); - - b.Property("ScoreDelta") - .HasColumnType("integer"); - - b.Property("Timestamp") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("DeclarationId"); - - b.HasIndex("ModeratorId"); - - b.HasIndex("Timestamp"); - - b.ToTable("ModerationLogs", t => - { - t.HasCheckConstraint("CK_ModerationLog_Immutable", "1=1"); - }); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.RegexAlertPattern", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("Pattern") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("Severity") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("IsActive"); - - b.ToTable("RegexAlertPatterns"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Content") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("DeclarantTokenId") - .HasColumnType("uuid"); - - b.Property("ScoreDelta") - .HasColumnType("integer"); - - b.Property("Sentiment") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TrustTokenId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("Status"); - - b.HasIndex("SubmittedAt"); - - b.HasIndex("TrustTokenId"); - - b.ToTable("TrustDeclarations"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustToken", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("TokenSource") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("TrustScore") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("TokenHash") - .IsUnique(); - - b.ToTable("TrustTokens"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Depth") - .HasColumnType("numeric"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Height") - .HasColumnType("numeric"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Price") - .HasColumnType("numeric"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.Property("Weight") - .HasColumnType("numeric"); - - b.Property("Width") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Service", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContextId") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("ContextId"); - - b.ToTable("Services"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.Announce", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("For") - .HasColumnType("smallint"); - - b.Property("Message") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Sender") - .HasColumnType("text"); - - b.Property("Topic") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("Announce"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.DismissClicked", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("NotificationId") - .HasColumnType("bigint"); - - b.HasKey("UserId", "NotificationId"); - - b.HasIndex("NotificationId"); - - b.ToTable("DismissClicked"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Instrument", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("Instrument"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.InstrumentRating", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("InstrumentId") - .HasColumnType("bigint"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasAlternateKey("InstrumentId", "OwnerId"); - - b.HasIndex("OwnerId"); - - b.ToTable("InstrumentRating"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalPreference", b => - { - b.Property("OwnerProfileId") - .HasColumnType("text"); - - b.Property("DjSettingsUserId") - .HasColumnType("text"); - - b.Property("MusicLoverSettingsUserId") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("TendencyId") - .HasColumnType("bigint"); - - b.HasKey("OwnerProfileId"); - - b.HasIndex("DjSettingsUserId"); - - b.HasIndex("MusicLoverSettingsUserId"); - - b.HasIndex("TendencyId"); - - b.ToTable("MusicalPreference"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalTendency", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("MusicalTendency"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.DjSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("SoundCloudId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("DjSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.Instrumentation", b => - { - b.Property("InstrumentId") - .HasColumnType("bigint"); - - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("InstrumentId", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("Instrumentation"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.MusicLoverSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("MusicLoverSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.Property("CreationToken") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ExecutorId") - .IsRequired() - .HasColumnType("text"); - - b.Property("OrderReference") - .HasColumnType("text"); - - b.Property("PaypalPayerId") - .HasColumnType("text"); - - b.Property("State") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("CreationToken"); - - b.HasIndex("ExecutorId"); - - b.ToTable("PayPalPayment"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApplicationUserId") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("Circle"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.CircleMember", b => - { - b.Property("MemberId") - .HasColumnType("text"); - - b.Property("CircleId") - .HasColumnType("bigint"); - - b.HasKey("MemberId", "CircleId"); - - b.HasIndex("CircleId"); - - b.ToTable("CircleMembers"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Contact", b => - { - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("AddressId") - .HasColumnType("bigint"); - - b.Property("ApplicationUserId") - .HasColumnType("text"); - - b.Property("EMail") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.HasKey("OwnerId", "UserId"); - - b.HasIndex("AddressId"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("Contact"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.HyperLink", b => - { - b.Property("HRef") - .HasColumnType("text"); - - b.Property("Method") - .HasColumnType("text"); - - b.Property("BrusherProfileUserId") - .HasColumnType("text"); - - b.Property("ContentType") - .HasColumnType("text"); - - b.Property("PayPalPaymentCreationToken") - .HasColumnType("text"); - - b.Property("Rel") - .HasColumnType("text"); - - b.HasKey("HRef", "Method"); - - b.HasIndex("BrusherProfileUserId"); - - b.HasIndex("PayPalPaymentCreationToken"); - - b.ToTable("HyperLink"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Location", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Address") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("Latitude") - .HasColumnType("double precision"); - - b.Property("Longitude") - .HasColumnType("double precision"); - - b.HasKey("Id"); - - b.ToTable("Locations"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.PostalAddress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("City") - .HasColumnType("text"); - - b.Property("Country") - .HasColumnType("text"); - - b.Property("PostalCode") - .HasColumnType("text"); - - b.Property("Province") - .HasColumnType("text"); - - b.Property("State") - .HasColumnType("text"); - - b.Property("Street1") - .HasColumnType("text"); - - b.Property("Street2") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Tag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Tags"); - }); - - modelBuilder.Entity("Yavsc.Models.Skill", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("SiteSkills"); - }); - - modelBuilder.Entity("Yavsc.Models.Streaming.LiveFlow", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DifferedFileName") - .HasColumnType("text"); - - b.Property("MediaType") - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Pitch") - .HasColumnType("text"); - - b.Property("SequenceNumber") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("LiveFlow"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.Property("Code") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Hidden") - .HasColumnType("boolean"); - - b.Property("Moderated") - .HasColumnType("boolean"); - - b.Property("ModeratorGroupName") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("ParentCode") - .HasColumnType("text"); - - b.Property("Photo") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("SettingsClassName") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Code"); - - b.HasIndex("ParentCode"); - - b.ToTable("Activities"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CoWorking", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FormationSettingsUserId") - .HasColumnType("text"); - - b.Property("PerformerId") - .HasColumnType("text"); - - b.Property("WorkingForId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("FormationSettingsUserId"); - - b.HasIndex("PerformerId"); - - b.HasIndex("WorkingForId"); - - b.ToTable("CoWorking"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CommandForm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionName") - .HasColumnType("text"); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.ToTable("CommandForm"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Country", b => - { - b.Property("Code") - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.HasKey("Code"); - - b.ToTable("Countries"); - - b.HasData( - new - { - Code = "fr", - DisplayName = "France" - }, - new - { - Code = "en", - DisplayName = "England" - }, - new - { - Code = "pt", - DisplayName = "Portugal" - }); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.DictionnaireMetier", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DomaineActiviteCode") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("Langue") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Nom") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("DomaineActiviteCode", "Langue", "Nom") - .IsUnique(); - - b.ToTable("DictionnaireMetier"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CountryCode") - .IsRequired() - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("ErrorMessage") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("RegularExpression") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("CountryCode"); - - b.ToTable("PerformerCodeInputValidations"); - - b.HasData( - new - { - Id = 1L, - CountryCode = "fr", - ErrorMessage = "Le code FR doit contenir entre 9 et 14 chiffres.", - RegularExpression = "^[0-9]{9,14}$" - }, - new - { - Id = 2L, - CountryCode = "en", - ErrorMessage = "Le code EN doit contenir entre 8 et 14 caracteres alphanumeriques.", - RegularExpression = "^[A-Za-z0-9]{8,14}$" - }, - new - { - Id = 3L, - CountryCode = "pt", - ErrorMessage = "Le code PT doit contenir exactement 9 chiffres.", - RegularExpression = "^[0-9]{9}$" - }); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.Property("PerformerId") - .HasColumnType("text"); - - b.Property("AcceptNotifications") - .HasColumnType("boolean"); - - b.Property("AcceptPublicContact") - .HasColumnType("boolean"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("ExerciseCountryCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("MaxDailyCost") - .HasColumnType("integer"); - - b.Property("MinDailyCost") - .HasColumnType("integer"); - - b.Property("OrganizationAddressId") - .HasColumnType("bigint"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("SIREN") - .IsRequired() - .HasColumnType("text"); - - b.Property("UseGeoLocalizationToReduceDistanceWithClients") - .HasColumnType("boolean"); - - b.Property("WebSite") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("PerformerId"); - - b.HasIndex("OrganizationAddressId"); - - b.ToTable("Performers"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Profiles.FormationSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("FormationSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.TermeMetier", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DateSoumission") - .HasColumnType("timestamp with time zone"); - - b.Property("DateValidation") - .HasColumnType("timestamp with time zone"); - - b.Property("Definition") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("DictionnaireMetierId") - .HasColumnType("bigint"); - - b.Property("Langue") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Mot") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ProposeParId") - .HasMaxLength(450) - .HasColumnType("character varying(450)"); - - b.Property("StatutValidation") - .HasColumnType("integer"); - - b.Property("ValideParId") - .HasMaxLength(450) - .HasColumnType("character varying(450)"); - - b.HasKey("Id"); - - b.HasIndex("ProposeParId"); - - b.HasIndex("ValideParId"); - - b.HasIndex("DictionnaireMetierId", "Langue", "Mot") - .IsUnique(); - - b.ToTable("TermeMetier"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => - { - b.Property("DoesCode") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Weight") - .HasColumnType("integer"); - - b.HasKey("DoesCode", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("UserActivities"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.Calendar.Period", b => - { - b.Property("Start") - .HasColumnType("timestamp with time zone"); - - b.Property("End") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Start", "End"); - - b.ToTable("Period"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.EMailing.MailingTemplate", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("Body") - .HasMaxLength(65536) - .HasColumnType("character varying(65536)"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ReplyToAddress") - .HasColumnType("text"); - - b.Property("ToSend") - .HasColumnType("integer"); - - b.Property("Topic") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("MailingTemplate"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProjectId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ProjectId"); - - b.ToTable("ProjectBuildConfiguration"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Branch") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Path") - .IsRequired() - .HasColumnType("text"); - - b.Property("Url") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("GitRepositoryReference"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand"); - - b.Property("AdditionalInfo") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.Property("SelectedProfileUserId") - .HasColumnType("text"); - - b.HasIndex("LocationId"); - - b.HasIndex("PrestationId"); - - b.HasIndex("SelectedProfileUserId"); - - b.HasDiscriminator().HasValue("HairCutQuery"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.HasIndex("LocationId"); - - b.ToTable("NominativeServiceCommand", t => - { - t.Property("EventDate") - .HasColumnName("HairMultiCutQuery_EventDate"); - - t.Property("LocationId") - .HasColumnName("HairMultiCutQuery_LocationId"); - }); - - b.HasDiscriminator().HasValue("HairMultiCutQuery"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("LocationType") - .HasColumnType("integer"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.HasIndex("LocationId"); - - b.ToTable("NominativeServiceCommand", t => - { - t.Property("EventDate") - .HasColumnName("RdvQuery_EventDate"); - - t.Property("LocationId") - .HasColumnName("RdvQuery_LocationId"); - }); - - b.HasDiscriminator().HasValue("RdvQuery"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand"); - - b.Property("GitId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Version") - .HasColumnType("text"); - - b.HasIndex("GitId"); - - b.HasDiscriminator().HasValue("Project"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("UserClaims") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Properties") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Scopes") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Secrets") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", null) - .WithMany("UserClaims") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", "Scope") - .WithMany() - .HasForeignKey("ScopeId1"); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", null) - .WithMany("Properties") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", "Scope") - .WithMany() - .HasForeignKey("ScopeId1"); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("Claims") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("AllowedCorsOrigins") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedGrantTypes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("IdentityProviderRestrictions") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("PostLogoutRedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("Properties") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("RedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedScopes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientSecret", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("ClientSecrets") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("UserClaims") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("Properties") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Yavsc.Models.Access.Ban", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "TargetUser") - .WithMany() - .HasForeignKey("TargetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetUser"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.BlackListed", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("BlackList") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToBlogPost", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Target") - .WithMany("ACL") - .HasForeignKey("BlogPostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Circle", "Allowed") - .WithMany() - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Allowed"); - - b.Navigation("Target"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToFile", b => - { - b.HasOne("Yavsc.Models.Relationship.Circle", "Allowed") - .WithMany() - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Allowed"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithOne("AccountBalance") - .HasForeignKey("Yavsc.Models.AccountBalance", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "PostalAddress") - .WithMany() - .HasForeignKey("PostalAddressId"); - - b.Navigation("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.BalanceImpact", b => - { - b.HasOne("Yavsc.Models.AccountBalance", "Balance") - .WithMany() - .HasForeignKey("BalanceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Balance"); - }); - - modelBuilder.Entity("Yavsc.Models.Bank.BankIdentity", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany("BankInfo") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.CommandLine", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", null) - .WithMany("Bill") - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Billing.EstimateTemplate", null) - .WithMany("Bill") - .HasForeignKey("EstimateTemplateId"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Billing.NominativeServiceCommand", "Query") - .WithMany() - .HasForeignKey("CommandId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Owner"); - - b.Navigation("Query"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.NominativeServiceCommand", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", "Estimate") - .WithMany("Signatures") - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Signer") - .WithMany() - .HasForeignKey("SignerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Estimate"); - - b.Navigation("Signer"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogAttachedFile", b => - { - b.HasOne("Yavsc.Models.Blog.UploadedFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany() - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("File"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Author") - .WithMany("Posts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Author"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogTag", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany("Tags") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Tag", "Tag") - .WithMany() - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Post"); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Author") - .WithMany("BlogComments") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.Comment", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany("Comments") - .HasForeignKey("ReceiverId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Author"); - - b.Navigation("Parent"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "BlogPost") - .WithMany() - .HasForeignKey("BlogpostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("BlogPost"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.ScheduledEvent", b => - { - b.HasOne("Yavsc.Models.Calendar.Schedule", null) - .WithMany("Events") - .HasForeignKey("ScheduleOwnerId"); - - b.HasOne("Yavsc.Server.Models.Calendar.Period", "Period") - .WithMany() - .HasForeignKey("PeriodStart", "PeriodEnd"); - - b.Navigation("Period"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatConnection", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("Connections") - .HasForeignKey("ApplicationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("Rooms") - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoomAccess", b => - { - b.HasOne("Yavsc.Models.Chat.ChatRoom", "Room") - .WithMany("Moderation") - .HasForeignKey("ChannelName") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany("RoomAccess") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Room"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.HasOne("Yavsc.Models.Calendar.Schedule", "Schedule") - .WithMany() - .HasForeignKey("ScheduleOwnerId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "BaseProfile") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("BaseProfile"); - - b.Navigation("Schedule"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b => - { - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany() - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairMultiCutQuery", "Query") - .WithMany("Prestations") - .HasForeignKey("QueryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Prestation"); - - b.Navigation("Query"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaint", b => - { - b.HasOne("Yavsc.Models.Drawing.Color", "Color") - .WithMany() - .HasForeignKey("ColorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Color"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaintInstance", b => - { - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany("Taints") - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairTaint", "Taint") - .WithMany() - .HasForeignKey("TaintId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Prestation"); - - b.Navigation("Taint"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Fixing.Bug", b => - { - b.HasOne("Yavsc.Models.IT.Evolution.Feature", "False") - .WithMany() - .HasForeignKey("FeatureId"); - - b.Navigation("False"); - }); - - modelBuilder.Entity("Yavsc.Models.Identity.DeviceDeclaration", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "DeviceOwner") - .WithMany("DeviceDeclaration") - .HasForeignKey("DeviceOwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("DeviceOwner"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.DeclarationFlag", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustDeclaration", "Declaration") - .WithMany("Flags") - .HasForeignKey("DeclarationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Kyc.RegexAlertPattern", "Pattern") - .WithMany() - .HasForeignKey("PatternId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Declaration"); - - b.Navigation("Pattern"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.ModerationLog", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustDeclaration", "Declaration") - .WithMany() - .HasForeignKey("DeclarationId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Declaration"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustToken", "Subject") - .WithMany("Declarations") - .HasForeignKey("TrustTokenId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Subject"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Service", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany("Services") - .HasForeignKey("ContextId"); - - b.Navigation("Context"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.Announce", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.DismissClicked", b => - { - b.HasOne("Yavsc.Abstract.Models.Messaging.Notification", "Notified") - .WithMany() - .HasForeignKey("NotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Notified"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.InstrumentRating", b => - { - b.HasOne("Yavsc.Models.Musical.Instrument", "Instrument") - .WithMany() - .HasForeignKey("InstrumentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Profile") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Instrument"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalPreference", b => - { - b.HasOne("Yavsc.Models.Musical.Profiles.DjSettings", null) - .WithMany("SoundColor") - .HasForeignKey("DjSettingsUserId"); - - b.HasOne("Yavsc.Models.Musical.Profiles.MusicLoverSettings", null) - .WithMany("SoundColor") - .HasForeignKey("MusicLoverSettingsUserId"); - - b.HasOne("Yavsc.Models.Musical.MusicalTendency", "MusicalTendency") - .WithMany() - .HasForeignKey("TendencyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("MusicalTendency"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.Instrumentation", b => - { - b.HasOne("Yavsc.Models.Musical.Instrument", "Tool") - .WithMany() - .HasForeignKey("InstrumentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Tool"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Executor") - .WithMany() - .HasForeignKey("ExecutorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Executor"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany("Circles") - .HasForeignKey("ApplicationUserId"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.CircleMember", b => - { - b.HasOne("Yavsc.Models.Relationship.Circle", "Circle") - .WithMany("Members") - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Member") - .WithMany("Membership") - .HasForeignKey("MemberId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Circle"); - - b.Navigation("Member"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Contact", b => - { - b.HasOne("Yavsc.Models.Relationship.PostalAddress", "PostalAddress") - .WithMany() - .HasForeignKey("AddressId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany("Book") - .HasForeignKey("ApplicationUserId"); - - b.Navigation("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.HyperLink", b => - { - b.HasOne("Yavsc.Models.Haircut.BrusherProfile", null) - .WithMany("Links") - .HasForeignKey("BrusherProfileUserId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", null) - .WithMany("Links") - .HasForeignKey("PayPalPaymentCreationToken"); - }); - - modelBuilder.Entity("Yavsc.Models.Streaming.LiveFlow", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentCode"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CoWorking", b => - { - b.HasOne("Yavsc.Models.Workflow.Profiles.FormationSettings", null) - .WithMany("CoWorking") - .HasForeignKey("FormationSettingsUserId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Performer") - .WithMany() - .HasForeignKey("PerformerId"); - - b.HasOne("Yavsc.Models.ApplicationUser", "WorkingFor") - .WithMany() - .HasForeignKey("WorkingForId"); - - b.Navigation("Performer"); - - b.Navigation("WorkingFor"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CommandForm", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany("Forms") - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Context"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.DictionnaireMetier", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "DomaineActivite") - .WithMany() - .HasForeignKey("DomaineActiviteCode") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("DomaineActivite"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b => - { - b.HasOne("Yavsc.Models.Workflow.Country", "Country") - .WithMany() - .HasForeignKey("CountryCode") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Country"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "OrganizationAddress") - .WithMany() - .HasForeignKey("OrganizationAddressId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Performer") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationAddress"); - - b.Navigation("Performer"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.TermeMetier", b => - { - b.HasOne("Yavsc.Models.Workflow.DictionnaireMetier", "DictionnaireMetier") - .WithMany("Termes") - .HasForeignKey("DictionnaireMetierId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "ProposePar") - .WithMany() - .HasForeignKey("ProposeParId"); - - b.HasOne("Yavsc.Models.ApplicationUser", "ValidePar") - .WithMany() - .HasForeignKey("ValideParId"); - - b.Navigation("DictionnaireMetier"); - - b.Navigation("ProposePar"); - - b.Navigation("ValidePar"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Does") - .WithMany() - .HasForeignKey("DoesCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "User") - .WithMany("Activity") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Does"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => - { - b.HasOne("Yavsc.Server.Models.IT.Project", "TargetProject") - .WithMany("Configurations") - .HasForeignKey("ProjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetProject"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId"); - - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany() - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.BrusherProfile", "SelectedProfile") - .WithMany() - .HasForeignKey("SelectedProfileUserId"); - - b.Navigation("Location"); - - b.Navigation("Prestation"); - - b.Navigation("SelectedProfile"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Location"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Location"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.HasOne("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", "Repository") - .WithMany() - .HasForeignKey("GitId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Repository"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b => - { - b.Navigation("Properties"); - - b.Navigation("Scopes"); - - b.Navigation("Secrets"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b => - { - b.Navigation("AllowedCorsOrigins"); - - b.Navigation("AllowedGrantTypes"); - - b.Navigation("AllowedScopes"); - - b.Navigation("Claims"); - - b.Navigation("ClientSecrets"); - - b.Navigation("IdentityProviderRestrictions"); - - b.Navigation("PostLogoutRedirectUris"); - - b.Navigation("Properties"); - - b.Navigation("RedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResource", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.Navigation("AccountBalance"); - - b.Navigation("BankInfo"); - - b.Navigation("BlackList"); - - b.Navigation("BlogComments"); - - b.Navigation("Book"); - - b.Navigation("Circles"); - - b.Navigation("Connections"); - - b.Navigation("DeviceDeclaration"); - - b.Navigation("Membership"); - - b.Navigation("Posts"); - - b.Navigation("RoomAccess"); - - b.Navigation("Rooms"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.Navigation("Bill"); - - b.Navigation("Signatures"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b => - { - b.Navigation("Bill"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.Navigation("ACL"); - - b.Navigation("Comments"); - - b.Navigation("Tags"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.Navigation("Children"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.Navigation("Events"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.Navigation("Moderation"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => - { - b.Navigation("Taints"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.Navigation("Flags"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustToken", b => - { - b.Navigation("Declarations"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.DjSettings", b => - { - b.Navigation("SoundColor"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.MusicLoverSettings", b => - { - b.Navigation("SoundColor"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.Navigation("Children"); - - b.Navigation("Forms"); - - b.Navigation("Services"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.DictionnaireMetier", b => - { - b.Navigation("Termes"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.Navigation("Activity"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Profiles.FormationSettings", b => - { - b.Navigation("CoWorking"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.Navigation("Prestations"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.Navigation("Configurations"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260912114726_genericEstimate.cs b/src/Yavsc.Org/Migrations/20260912114726_genericEstimate.cs deleted file mode 100644 index cc463e401..000000000 --- a/src/Yavsc.Org/Migrations/20260912114726_genericEstimate.cs +++ /dev/null @@ -1,944 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace Yavsc.Migrations -{ - /// - public partial class genericEstimate : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Estimates_RdvQueries_CommandId", - table: "Estimates"); - - migrationBuilder.DropForeignKey( - name: "FK_HairPrestationCollectionItem_HairMultiCutQueries_QueryId", - table: "HairPrestationCollectionItem"); - - migrationBuilder.DropForeignKey( - name: "FK_ProjectBuildConfiguration_Project_ProjectId", - table: "ProjectBuildConfiguration"); - - migrationBuilder.DropForeignKey( - name: "FK_RdvQueries_Activities_ActivityCode", - table: "RdvQueries"); - - migrationBuilder.DropForeignKey( - name: "FK_RdvQueries_AspNetUsers_ClientId", - table: "RdvQueries"); - - migrationBuilder.DropForeignKey( - name: "FK_RdvQueries_Locations_LocationId", - table: "RdvQueries"); - - migrationBuilder.DropForeignKey( - name: "FK_RdvQueries_PayPalPayment_PaymentId", - table: "RdvQueries"); - - migrationBuilder.DropForeignKey( - name: "FK_RdvQueries_Performers_PerformerId", - table: "RdvQueries"); - - migrationBuilder.DropTable( - name: "HairCutQueries"); - - migrationBuilder.DropTable( - name: "HairMultiCutQueries"); - - migrationBuilder.DropTable( - name: "Project"); - - migrationBuilder.DropPrimaryKey( - name: "PK_RdvQueries", - table: "RdvQueries"); - - migrationBuilder.RenameTable( - name: "RdvQueries", - newName: "NominativeServiceCommand"); - - migrationBuilder.RenameIndex( - name: "IX_RdvQueries_PerformerId", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_PerformerId"); - - migrationBuilder.RenameIndex( - name: "IX_RdvQueries_PaymentId", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_PaymentId"); - - migrationBuilder.RenameIndex( - name: "IX_RdvQueries_LocationId", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_LocationId"); - - migrationBuilder.RenameIndex( - name: "IX_RdvQueries_ClientId", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_ClientId"); - - migrationBuilder.RenameIndex( - name: "IX_RdvQueries_ActivityCode", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_ActivityCode"); - - migrationBuilder.AlterColumn( - name: "Reason", - table: "NominativeServiceCommand", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "LocationType", - table: "NominativeServiceCommand", - type: "integer", - nullable: true, - oldClrType: typeof(int), - oldType: "integer"); - - migrationBuilder.AlterColumn( - name: "LocationId", - table: "NominativeServiceCommand", - type: "bigint", - nullable: true, - oldClrType: typeof(long), - oldType: "bigint"); - - migrationBuilder.AlterColumn( - name: "EventDate", - table: "NominativeServiceCommand", - type: "timestamp with time zone", - nullable: true, - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone"); - - migrationBuilder.AddColumn( - name: "AdditionalInfo", - table: "NominativeServiceCommand", - type: "text", - nullable: true); - - migrationBuilder.AddColumn( - name: "Discriminator", - table: "NominativeServiceCommand", - type: "character varying(34)", - maxLength: 34, - nullable: false, - defaultValue: ""); - - migrationBuilder.AddColumn( - name: "GitId", - table: "NominativeServiceCommand", - type: "bigint", - nullable: true); - - migrationBuilder.AddColumn( - name: "HairMultiCutQuery_EventDate", - table: "NominativeServiceCommand", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "HairMultiCutQuery_LocationId", - table: "NominativeServiceCommand", - type: "bigint", - nullable: true); - - migrationBuilder.AddColumn( - name: "Name", - table: "NominativeServiceCommand", - type: "text", - nullable: true); - - migrationBuilder.AddColumn( - name: "OwnerId", - table: "NominativeServiceCommand", - type: "text", - nullable: true); - - migrationBuilder.AddColumn( - name: "PrestationId", - table: "NominativeServiceCommand", - type: "bigint", - nullable: true); - - migrationBuilder.AddColumn( - name: "RdvQuery_EventDate", - table: "NominativeServiceCommand", - type: "timestamp with time zone", - nullable: true); - - migrationBuilder.AddColumn( - name: "RdvQuery_LocationId", - table: "NominativeServiceCommand", - type: "bigint", - nullable: true); - - migrationBuilder.AddColumn( - name: "SelectedProfileUserId", - table: "NominativeServiceCommand", - type: "text", - nullable: true); - - migrationBuilder.AddColumn( - name: "Version", - table: "NominativeServiceCommand", - type: "text", - nullable: true); - - migrationBuilder.AddPrimaryKey( - name: "PK_NominativeServiceCommand", - table: "NominativeServiceCommand", - column: "Id"); - - migrationBuilder.CreateTable( - name: "DictionnaireMetier", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Nom = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - Langue = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - DomaineActiviteCode = table.Column(type: "character varying(128)", maxLength: 128, nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_DictionnaireMetier", x => x.Id); - table.ForeignKey( - name: "FK_DictionnaireMetier_Activities_DomaineActiviteCode", - column: x => x.DomaineActiviteCode, - principalTable: "Activities", - principalColumn: "Code", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "TermeMetier", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - DictionnaireMetierId = table.Column(type: "bigint", nullable: false), - Mot = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), - Definition = table.Column(type: "character varying(2000)", maxLength: 2000, nullable: false), - Langue = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), - StatutValidation = table.Column(type: "integer", nullable: false), - ProposeParId = table.Column(type: "character varying(450)", maxLength: 450, nullable: true), - ValideParId = table.Column(type: "character varying(450)", maxLength: 450, nullable: true), - DateSoumission = table.Column(type: "timestamp with time zone", nullable: false), - DateValidation = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_TermeMetier", x => x.Id); - table.ForeignKey( - name: "FK_TermeMetier_AspNetUsers_ProposeParId", - column: x => x.ProposeParId, - principalTable: "AspNetUsers", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_TermeMetier_AspNetUsers_ValideParId", - column: x => x.ValideParId, - principalTable: "AspNetUsers", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_TermeMetier_DictionnaireMetier_DictionnaireMetierId", - column: x => x.DictionnaireMetierId, - principalTable: "DictionnaireMetier", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_NominativeServiceCommand_GitId", - table: "NominativeServiceCommand", - column: "GitId"); - - migrationBuilder.CreateIndex( - name: "IX_NominativeServiceCommand_HairMultiCutQuery_LocationId", - table: "NominativeServiceCommand", - column: "HairMultiCutQuery_LocationId"); - - migrationBuilder.CreateIndex( - name: "IX_NominativeServiceCommand_PrestationId", - table: "NominativeServiceCommand", - column: "PrestationId"); - - migrationBuilder.CreateIndex( - name: "IX_NominativeServiceCommand_RdvQuery_LocationId", - table: "NominativeServiceCommand", - column: "RdvQuery_LocationId"); - - migrationBuilder.CreateIndex( - name: "IX_NominativeServiceCommand_SelectedProfileUserId", - table: "NominativeServiceCommand", - column: "SelectedProfileUserId"); - - migrationBuilder.CreateIndex( - name: "IX_DictionnaireMetier_DomaineActiviteCode_Langue_Nom", - table: "DictionnaireMetier", - columns: new[] { "DomaineActiviteCode", "Langue", "Nom" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_TermeMetier_DictionnaireMetierId_Langue_Mot", - table: "TermeMetier", - columns: new[] { "DictionnaireMetierId", "Langue", "Mot" }, - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_TermeMetier_ProposeParId", - table: "TermeMetier", - column: "ProposeParId"); - - migrationBuilder.CreateIndex( - name: "IX_TermeMetier_ValideParId", - table: "TermeMetier", - column: "ValideParId"); - - migrationBuilder.AddForeignKey( - name: "FK_Estimates_NominativeServiceCommand_CommandId", - table: "Estimates", - column: "CommandId", - principalTable: "NominativeServiceCommand", - principalColumn: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_HairPrestationCollectionItem_NominativeServiceCommand_Query~", - table: "HairPrestationCollectionItem", - column: "QueryId", - principalTable: "NominativeServiceCommand", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_Activities_ActivityCode", - table: "NominativeServiceCommand", - column: "ActivityCode", - principalTable: "Activities", - principalColumn: "Code", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_AspNetUsers_ClientId", - table: "NominativeServiceCommand", - column: "ClientId", - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_BrusherProfile_SelectedProfileUser~", - table: "NominativeServiceCommand", - column: "SelectedProfileUserId", - principalTable: "BrusherProfile", - principalColumn: "UserId"); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_GitRepositoryReference_GitId", - table: "NominativeServiceCommand", - column: "GitId", - principalTable: "GitRepositoryReference", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_HairPrestation_PrestationId", - table: "NominativeServiceCommand", - column: "PrestationId", - principalTable: "HairPrestation", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_Locations_HairMultiCutQuery_Locati~", - table: "NominativeServiceCommand", - column: "HairMultiCutQuery_LocationId", - principalTable: "Locations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_Locations_LocationId", - table: "NominativeServiceCommand", - column: "LocationId", - principalTable: "Locations", - principalColumn: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_Locations_RdvQuery_LocationId", - table: "NominativeServiceCommand", - column: "RdvQuery_LocationId", - principalTable: "Locations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_PayPalPayment_PaymentId", - table: "NominativeServiceCommand", - column: "PaymentId", - principalTable: "PayPalPayment", - principalColumn: "CreationToken"); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_Performers_PerformerId", - table: "NominativeServiceCommand", - column: "PerformerId", - principalTable: "Performers", - principalColumn: "PerformerId", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_ProjectBuildConfiguration_NominativeServiceCommand_ProjectId", - table: "ProjectBuildConfiguration", - column: "ProjectId", - principalTable: "NominativeServiceCommand", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Estimates_NominativeServiceCommand_CommandId", - table: "Estimates"); - - migrationBuilder.DropForeignKey( - name: "FK_HairPrestationCollectionItem_NominativeServiceCommand_Query~", - table: "HairPrestationCollectionItem"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_Activities_ActivityCode", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_AspNetUsers_ClientId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_BrusherProfile_SelectedProfileUser~", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_GitRepositoryReference_GitId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_HairPrestation_PrestationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_Locations_HairMultiCutQuery_Locati~", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_Locations_LocationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_Locations_RdvQuery_LocationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_PayPalPayment_PaymentId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_Performers_PerformerId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_ProjectBuildConfiguration_NominativeServiceCommand_ProjectId", - table: "ProjectBuildConfiguration"); - - migrationBuilder.DropTable( - name: "TermeMetier"); - - migrationBuilder.DropTable( - name: "DictionnaireMetier"); - - migrationBuilder.DropPrimaryKey( - name: "PK_NominativeServiceCommand", - table: "NominativeServiceCommand"); - - migrationBuilder.DropIndex( - name: "IX_NominativeServiceCommand_GitId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropIndex( - name: "IX_NominativeServiceCommand_HairMultiCutQuery_LocationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropIndex( - name: "IX_NominativeServiceCommand_PrestationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropIndex( - name: "IX_NominativeServiceCommand_RdvQuery_LocationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropIndex( - name: "IX_NominativeServiceCommand_SelectedProfileUserId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "AdditionalInfo", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "Discriminator", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "GitId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "HairMultiCutQuery_EventDate", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "HairMultiCutQuery_LocationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "Name", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "OwnerId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "PrestationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "RdvQuery_EventDate", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "RdvQuery_LocationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "SelectedProfileUserId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "Version", - table: "NominativeServiceCommand"); - - migrationBuilder.RenameTable( - name: "NominativeServiceCommand", - newName: "RdvQueries"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_PerformerId", - table: "RdvQueries", - newName: "IX_RdvQueries_PerformerId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_PaymentId", - table: "RdvQueries", - newName: "IX_RdvQueries_PaymentId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_LocationId", - table: "RdvQueries", - newName: "IX_RdvQueries_LocationId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_ClientId", - table: "RdvQueries", - newName: "IX_RdvQueries_ClientId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_ActivityCode", - table: "RdvQueries", - newName: "IX_RdvQueries_ActivityCode"); - - migrationBuilder.AlterColumn( - name: "Reason", - table: "RdvQueries", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "LocationType", - table: "RdvQueries", - type: "integer", - nullable: false, - defaultValue: 0, - oldClrType: typeof(int), - oldType: "integer", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "LocationId", - table: "RdvQueries", - type: "bigint", - nullable: false, - defaultValue: 0L, - oldClrType: typeof(long), - oldType: "bigint", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "EventDate", - table: "RdvQueries", - type: "timestamp with time zone", - nullable: false, - defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), - oldClrType: typeof(DateTime), - oldType: "timestamp with time zone", - oldNullable: true); - - migrationBuilder.AddPrimaryKey( - name: "PK_RdvQueries", - table: "RdvQueries", - column: "Id"); - - migrationBuilder.CreateTable( - name: "HairCutQueries", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ActivityCode = table.Column(type: "text", nullable: false), - ClientId = table.Column(type: "text", nullable: false), - LocationId = table.Column(type: "bigint", nullable: true), - PaymentId = table.Column(type: "text", nullable: true), - PerformerId = table.Column(type: "text", nullable: false), - PrestationId = table.Column(type: "bigint", nullable: false), - SelectedProfileUserId = table.Column(type: "text", nullable: true), - AdditionalInfo = table.Column(type: "text", nullable: false), - Consent = table.Column(type: "boolean", nullable: false), - DateCreated = table.Column(type: "timestamp with time zone", nullable: false), - DateModified = table.Column(type: "timestamp with time zone", nullable: false), - Description = table.Column(type: "text", nullable: false), - EventDate = table.Column(type: "timestamp with time zone", nullable: true), - Provisional = table.Column(type: "numeric", nullable: true), - Status = table.Column(type: "integer", nullable: false), - UserCreated = table.Column(type: "text", nullable: false), - UserModified = table.Column(type: "text", nullable: false), - ValidationDate = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_HairCutQueries", x => x.Id); - table.ForeignKey( - name: "FK_HairCutQueries_Activities_ActivityCode", - column: x => x.ActivityCode, - principalTable: "Activities", - principalColumn: "Code", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_HairCutQueries_AspNetUsers_ClientId", - column: x => x.ClientId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_HairCutQueries_BrusherProfile_SelectedProfileUserId", - column: x => x.SelectedProfileUserId, - principalTable: "BrusherProfile", - principalColumn: "UserId"); - table.ForeignKey( - name: "FK_HairCutQueries_HairPrestation_PrestationId", - column: x => x.PrestationId, - principalTable: "HairPrestation", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_HairCutQueries_Locations_LocationId", - column: x => x.LocationId, - principalTable: "Locations", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_HairCutQueries_PayPalPayment_PaymentId", - column: x => x.PaymentId, - principalTable: "PayPalPayment", - principalColumn: "CreationToken"); - table.ForeignKey( - name: "FK_HairCutQueries_Performers_PerformerId", - column: x => x.PerformerId, - principalTable: "Performers", - principalColumn: "PerformerId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "HairMultiCutQueries", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ActivityCode = table.Column(type: "text", nullable: false), - ClientId = table.Column(type: "text", nullable: false), - LocationId = table.Column(type: "bigint", nullable: false), - PaymentId = table.Column(type: "text", nullable: true), - PerformerId = table.Column(type: "text", nullable: false), - Consent = table.Column(type: "boolean", nullable: false), - DateCreated = table.Column(type: "timestamp with time zone", nullable: false), - DateModified = table.Column(type: "timestamp with time zone", nullable: false), - Description = table.Column(type: "text", nullable: false), - EventDate = table.Column(type: "timestamp with time zone", nullable: false), - Provisional = table.Column(type: "numeric", nullable: true), - Status = table.Column(type: "integer", nullable: false), - UserCreated = table.Column(type: "text", nullable: false), - UserModified = table.Column(type: "text", nullable: false), - ValidationDate = table.Column(type: "timestamp with time zone", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_HairMultiCutQueries", x => x.Id); - table.ForeignKey( - name: "FK_HairMultiCutQueries_Activities_ActivityCode", - column: x => x.ActivityCode, - principalTable: "Activities", - principalColumn: "Code", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_HairMultiCutQueries_AspNetUsers_ClientId", - column: x => x.ClientId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_HairMultiCutQueries_Locations_LocationId", - column: x => x.LocationId, - principalTable: "Locations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_HairMultiCutQueries_PayPalPayment_PaymentId", - column: x => x.PaymentId, - principalTable: "PayPalPayment", - principalColumn: "CreationToken"); - table.ForeignKey( - name: "FK_HairMultiCutQueries_Performers_PerformerId", - column: x => x.PerformerId, - principalTable: "Performers", - principalColumn: "PerformerId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Project", - columns: table => new - { - Id = table.Column(type: "bigint", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - ActivityCode = table.Column(type: "text", nullable: false), - ClientId = table.Column(type: "text", nullable: false), - GitId = table.Column(type: "bigint", nullable: false), - PaymentId = table.Column(type: "text", nullable: true), - PerformerId = table.Column(type: "text", nullable: false), - Consent = table.Column(type: "boolean", nullable: false), - DateCreated = table.Column(type: "timestamp with time zone", nullable: false), - DateModified = table.Column(type: "timestamp with time zone", nullable: false), - Description = table.Column(type: "text", nullable: true), - Name = table.Column(type: "text", nullable: false), - OwnerId = table.Column(type: "text", nullable: true), - Provisional = table.Column(type: "numeric", nullable: true), - Status = table.Column(type: "integer", nullable: false), - UserCreated = table.Column(type: "text", nullable: false), - UserModified = table.Column(type: "text", nullable: false), - ValidationDate = table.Column(type: "timestamp with time zone", nullable: true), - Version = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Project", x => x.Id); - table.ForeignKey( - name: "FK_Project_Activities_ActivityCode", - column: x => x.ActivityCode, - principalTable: "Activities", - principalColumn: "Code", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Project_AspNetUsers_ClientId", - column: x => x.ClientId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Project_GitRepositoryReference_GitId", - column: x => x.GitId, - principalTable: "GitRepositoryReference", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_Project_PayPalPayment_PaymentId", - column: x => x.PaymentId, - principalTable: "PayPalPayment", - principalColumn: "CreationToken"); - table.ForeignKey( - name: "FK_Project_Performers_PerformerId", - column: x => x.PerformerId, - principalTable: "Performers", - principalColumn: "PerformerId", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_HairCutQueries_ActivityCode", - table: "HairCutQueries", - column: "ActivityCode"); - - migrationBuilder.CreateIndex( - name: "IX_HairCutQueries_ClientId", - table: "HairCutQueries", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_HairCutQueries_LocationId", - table: "HairCutQueries", - column: "LocationId"); - - migrationBuilder.CreateIndex( - name: "IX_HairCutQueries_PaymentId", - table: "HairCutQueries", - column: "PaymentId"); - - migrationBuilder.CreateIndex( - name: "IX_HairCutQueries_PerformerId", - table: "HairCutQueries", - column: "PerformerId"); - - migrationBuilder.CreateIndex( - name: "IX_HairCutQueries_PrestationId", - table: "HairCutQueries", - column: "PrestationId"); - - migrationBuilder.CreateIndex( - name: "IX_HairCutQueries_SelectedProfileUserId", - table: "HairCutQueries", - column: "SelectedProfileUserId"); - - migrationBuilder.CreateIndex( - name: "IX_HairMultiCutQueries_ActivityCode", - table: "HairMultiCutQueries", - column: "ActivityCode"); - - migrationBuilder.CreateIndex( - name: "IX_HairMultiCutQueries_ClientId", - table: "HairMultiCutQueries", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_HairMultiCutQueries_LocationId", - table: "HairMultiCutQueries", - column: "LocationId"); - - migrationBuilder.CreateIndex( - name: "IX_HairMultiCutQueries_PaymentId", - table: "HairMultiCutQueries", - column: "PaymentId"); - - migrationBuilder.CreateIndex( - name: "IX_HairMultiCutQueries_PerformerId", - table: "HairMultiCutQueries", - column: "PerformerId"); - - migrationBuilder.CreateIndex( - name: "IX_Project_ActivityCode", - table: "Project", - column: "ActivityCode"); - - migrationBuilder.CreateIndex( - name: "IX_Project_ClientId", - table: "Project", - column: "ClientId"); - - migrationBuilder.CreateIndex( - name: "IX_Project_GitId", - table: "Project", - column: "GitId"); - - migrationBuilder.CreateIndex( - name: "IX_Project_PaymentId", - table: "Project", - column: "PaymentId"); - - migrationBuilder.CreateIndex( - name: "IX_Project_PerformerId", - table: "Project", - column: "PerformerId"); - - migrationBuilder.AddForeignKey( - name: "FK_Estimates_RdvQueries_CommandId", - table: "Estimates", - column: "CommandId", - principalTable: "RdvQueries", - principalColumn: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_HairPrestationCollectionItem_HairMultiCutQueries_QueryId", - table: "HairPrestationCollectionItem", - column: "QueryId", - principalTable: "HairMultiCutQueries", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_ProjectBuildConfiguration_Project_ProjectId", - table: "ProjectBuildConfiguration", - column: "ProjectId", - principalTable: "Project", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_RdvQueries_Activities_ActivityCode", - table: "RdvQueries", - column: "ActivityCode", - principalTable: "Activities", - principalColumn: "Code", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_RdvQueries_AspNetUsers_ClientId", - table: "RdvQueries", - column: "ClientId", - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_RdvQueries_Locations_LocationId", - table: "RdvQueries", - column: "LocationId", - principalTable: "Locations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_RdvQueries_PayPalPayment_PaymentId", - table: "RdvQueries", - column: "PaymentId", - principalTable: "PayPalPayment", - principalColumn: "CreationToken"); - - migrationBuilder.AddForeignKey( - name: "FK_RdvQueries_Performers_PerformerId", - table: "RdvQueries", - column: "PerformerId", - principalTable: "Performers", - principalColumn: "PerformerId", - onDelete: ReferentialAction.Cascade); - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260913022000_cleanupLegacyNominativeServiceCommandLocationId.cs b/src/Yavsc.Org/Migrations/20260913022000_cleanupLegacyNominativeServiceCommandLocationId.cs deleted file mode 100644 index dc68aa9f1..000000000 --- a/src/Yavsc.Org/Migrations/20260913022000_cleanupLegacyNominativeServiceCommandLocationId.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Yavsc.Models; - -#nullable disable - -namespace Yavsc.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260913022000_cleanupLegacyNominativeServiceCommandLocationId")] - public partial class cleanupLegacyNominativeServiceCommandLocationId : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.Sql(@" -UPDATE ""NominativeServiceCommand"" -SET ""RdvQuery_LocationId"" = COALESCE(""RdvQuery_LocationId"", ""LocationId"") -WHERE ""Discriminator"" = 'RdvQuery' - AND ""LocationId"" IS NOT NULL; -"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_Locations_LocationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropIndex( - name: "IX_NominativeServiceCommand_LocationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropColumn( - name: "LocationId", - table: "NominativeServiceCommand"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "LocationId", - table: "NominativeServiceCommand", - type: "bigint", - nullable: true); - - migrationBuilder.Sql(@" -UPDATE ""NominativeServiceCommand"" -SET ""LocationId"" = ""RdvQuery_LocationId"" -WHERE ""Discriminator"" = 'RdvQuery' - AND ""RdvQuery_LocationId"" IS NOT NULL; -"); - - migrationBuilder.CreateIndex( - name: "IX_NominativeServiceCommand_LocationId", - table: "NominativeServiceCommand", - column: "LocationId"); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_Locations_LocationId", - table: "NominativeServiceCommand", - column: "LocationId", - principalTable: "Locations", - principalColumn: "Id"); - } - } -} \ No newline at end of file diff --git a/src/Yavsc.Org/Migrations/20260913191414_NominativeServiceCommand.Designer.cs b/src/Yavsc.Org/Migrations/20260913191414_NominativeServiceCommand.Designer.cs deleted file mode 100644 index be80669af..000000000 --- a/src/Yavsc.Org/Migrations/20260913191414_NominativeServiceCommand.Designer.cs +++ /dev/null @@ -1,4714 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Yavsc.Models; - -#nullable disable - -namespace Yavsc.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260913191414_NominativeServiceCommand")] - partial class NominativeServiceCommand - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.9") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("AllowedAccessTokenSigningAlgorithms") - .HasColumnType("text"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("ApiResources"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Scope") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApiResourceId") - .HasColumnType("integer"); - - b.Property("ApiResourceId1") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ApiResourceId"); - - b.HasIndex("ApiResourceId1"); - - b.ToTable("ApiResourceSecrets"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.ToTable("ApiScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("ScopeId1") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.HasIndex("ScopeId1"); - - b.ToTable("ApiScopeClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("ScopeId") - .HasColumnType("integer"); - - b.Property("ScopeId1") - .HasColumnType("integer"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScopeId"); - - b.HasIndex("ScopeId1"); - - b.ToTable("ApiScopeProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("AbsoluteRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenLifetime") - .HasColumnType("integer"); - - b.Property("AccessTokenType") - .HasColumnType("integer"); - - b.Property("AllowAccessTokensViaBrowser") - .HasColumnType("boolean"); - - b.Property("AllowOfflineAccess") - .HasColumnType("boolean"); - - b.Property("AllowPlainTextPkce") - .HasColumnType("boolean"); - - b.Property("AllowRememberConsent") - .HasColumnType("boolean"); - - b.Property("AllowedIdentityTokenSigningAlgorithms") - .HasColumnType("text"); - - b.Property("AlwaysIncludeUserClaimsInIdToken") - .HasColumnType("boolean"); - - b.Property("AlwaysSendClientClaims") - .HasColumnType("boolean"); - - b.Property("AuthorizationCodeLifetime") - .HasColumnType("integer"); - - b.Property("BackChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("BackChannelLogoutUri") - .HasColumnType("text"); - - b.Property("ClientClaimsPrefix") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("ClientName") - .HasColumnType("text"); - - b.Property("ClientUri") - .HasColumnType("text"); - - b.Property("ConsentLifetime") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DeviceCodeLifetime") - .HasColumnType("integer"); - - b.Property("EnableLocalLogin") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutSessionRequired") - .HasColumnType("boolean"); - - b.Property("FrontChannelLogoutUri") - .HasColumnType("text"); - - b.Property("IdentityTokenLifetime") - .HasColumnType("integer"); - - b.Property("IncludeJwtId") - .HasColumnType("boolean"); - - b.Property("LastAccessed") - .HasColumnType("timestamp with time zone"); - - b.Property("LogoUri") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("PairWiseSubjectSalt") - .HasColumnType("text"); - - b.Property("ProtocolType") - .HasColumnType("text"); - - b.Property("RefreshTokenExpiration") - .HasColumnType("integer"); - - b.Property("RefreshTokenUsage") - .HasColumnType("integer"); - - b.Property("RequireClientSecret") - .HasColumnType("boolean"); - - b.Property("RequireConsent") - .HasColumnType("boolean"); - - b.Property("RequirePkce") - .HasColumnType("boolean"); - - b.Property("RequireRequestObject") - .HasColumnType("boolean"); - - b.Property("SlidingRefreshTokenLifetime") - .HasColumnType("integer"); - - b.Property("UpdateAccessTokenClaimsOnRefresh") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.Property("UserCodeType") - .HasColumnType("text"); - - b.Property("UserSsoLifetime") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("Clients"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Origin") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientCorsOrigins"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("GrantType") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientGrantTypes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Provider") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientIdPRestrictions"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("PostLogoutRedirectUri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientPostLogoutRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("RedirectUri") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientRedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("Scope") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.ToTable("ClientScopes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientSecret", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); - - b.Property("ClientId") - .HasColumnType("integer"); - - b.Property("ClientId1") - .HasColumnType("integer"); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("Type") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("ClientId1"); - - b.ToTable("ClientSecrets"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.DeviceFlowCodes", b => - { - b.Property("UserCode") - .HasColumnType("text"); - - b.Property("DeviceCode") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasColumnType("text"); - - b.Property("SubjectId") - .HasColumnType("text"); - - b.HasKey("UserCode", "DeviceCode"); - - b.ToTable("DeviceFlowCodes"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResource", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Created") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.Property("Emphasize") - .HasColumnType("boolean"); - - b.Property("Enabled") - .HasColumnType("boolean"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("NonEditable") - .HasColumnType("boolean"); - - b.Property("Required") - .HasColumnType("boolean"); - - b.Property("ShowInDiscoveryDocument") - .HasColumnType("boolean"); - - b.Property("Updated") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.ToTable("IdentityResources"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("IdentityResourceId") - .HasColumnType("integer"); - - b.Property("Key") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("IdentityResourceId"); - - b.ToTable("IdentityResourceProperties"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.PersistedGrant", b => - { - b.Property("Key") - .HasColumnType("text"); - - b.Property("ClientId") - .HasColumnType("text"); - - b.Property("ConsumedTime") - .HasColumnType("timestamp with time zone"); - - b.Property("CreationTime") - .HasColumnType("timestamp with time zone"); - - b.Property("Data") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Expiration") - .HasColumnType("timestamp with time zone"); - - b.Property("SessionId") - .HasColumnType("text"); - - b.Property("SubjectId") - .HasColumnType("text"); - - b.Property("Type") - .HasColumnType("text"); - - b.HasKey("Key"); - - b.ToTable("PersistedGrants"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("Yavsc.Abstract.Identity.ClientProviderInfo", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Avatar") - .HasColumnType("text"); - - b.Property("BillingAddressId") - .HasColumnType("bigint"); - - b.Property("EMail") - .HasColumnType("text"); - - b.Property("Phone") - .HasColumnType("text"); - - b.Property("UserName") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("ClientProviderInfo"); - }); - - modelBuilder.Entity("Yavsc.Abstract.Models.Messaging.Notification", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Target") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("body") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("click_action") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("color") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("icon") - .ValueGeneratedOnAdd() - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasDefaultValue("exclam"); - - b.Property("sound") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("tag") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("title") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.HasKey("Id"); - - b.ToTable("Notification"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.Ban", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.Property("TargetId") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("TargetId"); - - b.ToTable("Ban"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.BlackListed", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.HasIndex("UserId"); - - b.ToTable("BlackListed"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToBlogPost", b => - { - b.Property("CircleId") - .HasColumnType("bigint"); - - b.Property("BlogPostId") - .HasColumnType("bigint"); - - b.HasKey("CircleId", "BlogPostId"); - - b.HasIndex("BlogPostId"); - - b.ToTable("CircleAuthorizationToBlogPost"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToFile", b => - { - b.Property("CircleId") - .HasColumnType("bigint"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Access") - .HasColumnType("smallint"); - - b.HasKey("CircleId", "Path", "OwnerId"); - - b.HasIndex("OwnerId"); - - b.ToTable("CircleAuthorizationToFile"); - }); - - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("ContactCredits") - .HasColumnType("bigint"); - - b.Property("Credits") - .HasColumnType("numeric"); - - b.HasKey("UserId"); - - b.ToTable("BankStatus"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("AllowMonthlyEmail") - .HasColumnType("boolean"); - - b.Property("Avatar") - .ValueGeneratedOnAdd() - .HasMaxLength(512) - .HasColumnType("character varying(512)") - .HasDefaultValue("/images/Users/icon_user.png"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("DedicatedGoogleCalendar") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("DiskQuota") - .ValueGeneratedOnAdd() - .HasColumnType("bigint") - .HasDefaultValue(524288000L); - - b.Property("DiskUsage") - .HasColumnType("bigint"); - - b.Property("Email") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("FullName") - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("MaxFileSize") - .HasColumnType("bigint"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("PostalAddressId") - .HasColumnType("bigint"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasAlternateKey("Email"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.HasIndex("PostalAddressId"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Yavsc.Models.BalanceImpact", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("BalanceId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ExecDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Impact") - .HasColumnType("numeric"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("BalanceId"); - - b.ToTable("BalanceImpact"); - }); - - modelBuilder.Entity("Yavsc.Models.Bank.BankIdentity", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AccountNumber") - .IsRequired() - .HasColumnType("text"); - - b.Property("BIC") - .IsRequired() - .HasColumnType("text"); - - b.Property("BankCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("BankedKey") - .HasColumnType("integer"); - - b.Property("IBAN") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.Property("WicketCode") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("BankIdentity"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.CommandLine", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Count") - .HasColumnType("integer"); - - b.Property("Currency") - .HasColumnType("text"); - - b.Property("Description") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.Property("EstimateTemplateId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("UnitaryCost") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.HasIndex("EstimateId"); - - b.HasIndex("EstimateTemplateId"); - - b.ToTable("CommandLine"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("AttachedFilesString") - .IsRequired() - .HasColumnType("text"); - - b.Property("AttachedGraphicsString") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("CommandId") - .HasColumnType("bigint"); - - b.Property("CommandType") - .IsRequired() - .HasColumnType("text"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProviderValidationDate") - .HasColumnType("timestamp with time zone"); - - b.Property("Title") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ClientId"); - - b.HasIndex("CommandId"); - - b.HasIndex("OwnerId"); - - b.ToTable("Estimates"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("EstimateTemplates"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.ExceptionSIREN", b => - { - b.Property("SIREN") - .HasColumnType("text"); - - b.HasKey("SIREN"); - - b.ToTable("ExceptionsSIREN"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.NominativeServiceCommand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("character varying(34)"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("NominativeServiceCommands"); - - b.HasDiscriminator("Discriminator").HasValue("NominativeServiceCommand"); - - b.UseTphMappingStrategy(); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CapturedAtUtc") - .HasColumnType("timestamp with time zone"); - - b.Property("CoordinateMax") - .ValueGeneratedOnAdd() - .HasColumnType("integer") - .HasDefaultValue(10000); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.Property("FilePath") - .IsRequired() - .HasColumnType("text"); - - b.Property("SignerId") - .IsRequired() - .HasColumnType("text"); - - b.PrimitiveCollection("Strokes") - .IsRequired() - .HasColumnType("integer[]"); - - b.Property("Type") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("SignerId"); - - b.HasIndex("EstimateId", "Type") - .IsUnique(); - - b.ToTable("Signatures"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogAttachedFile", b => - { - b.Property("FileId") - .HasColumnType("bigint"); - - b.Property("PostId") - .HasColumnType("bigint"); - - b.HasKey("FileId", "PostId"); - - b.HasIndex("PostId"); - - b.ToTable("BlogAttachedFiles"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Article") - .HasMaxLength(56224) - .HasColumnType("character varying(56224)"); - - b.Property("AuthorId") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Photo") - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("Title") - .IsRequired() - .HasMaxLength(1024) - .HasColumnType("character varying(1024)"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.ToTable("BlogSpot"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogTag", b => - { - b.Property("PostId") - .HasColumnType("bigint"); - - b.Property("TagId") - .HasColumnType("bigint"); - - b.HasKey("PostId", "TagId"); - - b.HasIndex("TagId"); - - b.ToTable("BlogTag"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Article") - .IsRequired() - .HasColumnType("text"); - - b.Property("AuthorId") - .IsRequired() - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ParentId") - .HasColumnType("bigint"); - - b.Property("ReceiverId") - .HasColumnType("bigint"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("Visible") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("AuthorId"); - - b.HasIndex("ParentId"); - - b.HasIndex("ReceiverId"); - - b.ToTable("Comment"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.UploadedFile", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContentType") - .HasColumnType("text"); - - b.Property("Length") - .HasColumnType("bigint"); - - b.Property("Path") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("UploadedFiles"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.Property("BlogpostId") - .HasColumnType("bigint"); - - b.HasKey("BlogpostId"); - - b.ToTable("blogSpotPublications"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.Property("OwnerId") - .HasColumnType("text"); - - b.HasKey("OwnerId"); - - b.ToTable("Schedule"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.ScheduledEvent", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PeriodEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("PeriodStart") - .HasColumnType("timestamp with time zone"); - - b.Property("Reccurence") - .HasColumnType("integer"); - - b.Property("ScheduleOwnerId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ScheduleOwnerId"); - - b.HasIndex("PeriodStart", "PeriodEnd"); - - b.ToTable("ScheduledEvent"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatConnection", b => - { - b.Property("ConnectionId") - .HasColumnType("text"); - - b.Property("ApplicationUserId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Connected") - .HasColumnType("boolean"); - - b.Property("UserAgent") - .HasColumnType("text"); - - b.HasKey("ConnectionId"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("ChatConnection"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.Property("Name") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("LatestJoinPart") - .HasColumnType("timestamp with time zone"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Topic") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Name"); - - b.HasIndex("OwnerId"); - - b.ToTable("ChatRoom"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoomAccess", b => - { - b.Property("ChannelName") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Level") - .HasColumnType("integer"); - - b.HasKey("ChannelName", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("ChatRoomAccess"); - }); - - modelBuilder.Entity("Yavsc.Models.Cratie.Option", b => - { - b.Property("Code") - .HasColumnType("text"); - - b.Property("CodeScrutin") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Code", "CodeScrutin"); - - b.ToTable("Option"); - }); - - modelBuilder.Entity("Yavsc.Models.Drawing.Color", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Blue") - .HasColumnType("smallint"); - - b.Property("Green") - .HasColumnType("smallint"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Red") - .HasColumnType("smallint"); - - b.HasKey("Id"); - - b.ToTable("Color"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("ActionDistance") - .HasColumnType("integer"); - - b.Property("CarePrice") - .HasColumnType("numeric"); - - b.Property("FlatFeeDiscount") - .HasColumnType("numeric"); - - b.Property("HalfBalayagePrice") - .HasColumnType("numeric"); - - b.Property("HalfBrushingPrice") - .HasColumnType("numeric"); - - b.Property("HalfColorPrice") - .HasColumnType("numeric"); - - b.Property("HalfDefrisPrice") - .HasColumnType("numeric"); - - b.Property("HalfFoldingPrice") - .HasColumnType("numeric"); - - b.Property("HalfMechPrice") - .HasColumnType("numeric"); - - b.Property("HalfMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("HalfPermanentPrice") - .HasColumnType("numeric"); - - b.Property("KidCutPrice") - .HasColumnType("numeric"); - - b.Property("LongBalayagePrice") - .HasColumnType("numeric"); - - b.Property("LongBrushingPrice") - .HasColumnType("numeric"); - - b.Property("LongColorPrice") - .HasColumnType("numeric"); - - b.Property("LongDefrisPrice") - .HasColumnType("numeric"); - - b.Property("LongFoldingPrice") - .HasColumnType("numeric"); - - b.Property("LongMechPrice") - .HasColumnType("numeric"); - - b.Property("LongMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("LongPermanentPrice") - .HasColumnType("numeric"); - - b.Property("ManBrushPrice") - .HasColumnType("numeric"); - - b.Property("ManCutPrice") - .HasColumnType("numeric"); - - b.Property("ScheduleOwnerId") - .HasColumnType("text"); - - b.Property("ShampooPrice") - .HasColumnType("numeric"); - - b.Property("ShortBalayagePrice") - .HasColumnType("numeric"); - - b.Property("ShortBrushingPrice") - .HasColumnType("numeric"); - - b.Property("ShortColorPrice") - .HasColumnType("numeric"); - - b.Property("ShortDefrisPrice") - .HasColumnType("numeric"); - - b.Property("ShortFoldingPrice") - .HasColumnType("numeric"); - - b.Property("ShortMechPrice") - .HasColumnType("numeric"); - - b.Property("ShortMultiColorPrice") - .HasColumnType("numeric"); - - b.Property("ShortPermanentPrice") - .HasColumnType("numeric"); - - b.Property("WomenHalfCutPrice") - .HasColumnType("numeric"); - - b.Property("WomenLongCutPrice") - .HasColumnType("numeric"); - - b.Property("WomenShortCutPrice") - .HasColumnType("numeric"); - - b.HasKey("UserId"); - - b.HasIndex("ScheduleOwnerId"); - - b.ToTable("BrusherProfile"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Cares") - .HasColumnType("boolean"); - - b.Property("Cut") - .HasColumnType("boolean"); - - b.Property("Dressing") - .HasColumnType("integer"); - - b.Property("Gender") - .HasColumnType("integer"); - - b.Property("Length") - .HasColumnType("integer"); - - b.Property("Shampoo") - .HasColumnType("boolean"); - - b.Property("Tech") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("HairPrestation"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.Property("QueryId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("PrestationId"); - - b.HasIndex("QueryId"); - - b.ToTable("HairPrestationCollectionItem"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaint", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Brand") - .HasColumnType("text"); - - b.Property("ColorId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ColorId"); - - b.ToTable("HairTaint"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaintInstance", b => - { - b.Property("TaintId") - .HasColumnType("bigint"); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.HasKey("TaintId", "PrestationId"); - - b.HasIndex("PrestationId"); - - b.ToTable("HairTaintInstance"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Evolution.Feature", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("ShortName") - .HasColumnType("text"); - - b.Property("Status") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("Feature"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Fixing.Bug", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .IsRequired() - .HasMaxLength(10240) - .HasColumnType("character varying(10240)"); - - b.Property("FeatureId") - .HasColumnType("bigint"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("Title") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("FeatureId"); - - b.ToTable("Bug"); - }); - - modelBuilder.Entity("Yavsc.Models.Identity.DeviceDeclaration", b => - { - b.Property("DeviceId") - .HasColumnType("text"); - - b.Property("DeclarationDate") - .ValueGeneratedOnAdd() - .HasColumnType("timestamp with time zone") - .HasDefaultValueSql("LOCALTIMESTAMP"); - - b.Property("DeviceOwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("LatestActivityUpdate") - .HasColumnType("timestamp with time zone"); - - b.Property("Model") - .IsRequired() - .HasColumnType("text"); - - b.Property("Platform") - .IsRequired() - .HasColumnType("text"); - - b.Property("Version") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("DeviceId"); - - b.HasIndex("DeviceOwnerId"); - - b.ToTable("DeviceDeclaration"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.DeclarationFlag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DeclarationId") - .HasColumnType("bigint"); - - b.Property("MatchExcerpt") - .HasMaxLength(100) - .HasColumnType("character varying(100)"); - - b.Property("PatternId") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("DeclarationId"); - - b.HasIndex("PatternId"); - - b.ToTable("DeclarationFlag"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.ModerationLog", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Action") - .HasColumnType("integer"); - - b.Property("DeclarationId") - .HasColumnType("bigint"); - - b.Property("ModeratorId") - .HasColumnType("text"); - - b.Property("ScoreDelta") - .HasColumnType("integer"); - - b.Property("Timestamp") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("DeclarationId"); - - b.HasIndex("ModeratorId"); - - b.HasIndex("Timestamp"); - - b.ToTable("ModerationLogs", t => - { - t.HasCheckConstraint("CK_ModerationLog_Immutable", "1=1"); - }); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.RegexAlertPattern", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Description") - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("Pattern") - .IsRequired() - .HasMaxLength(500) - .HasColumnType("character varying(500)"); - - b.Property("Severity") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("IsActive"); - - b.ToTable("RegexAlertPatterns"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Content") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("DeclarantTokenId") - .HasColumnType("uuid"); - - b.Property("ScoreDelta") - .HasColumnType("integer"); - - b.Property("Sentiment") - .HasColumnType("integer"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("SubmittedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TrustTokenId") - .HasColumnType("uuid"); - - b.HasKey("Id"); - - b.HasIndex("Status"); - - b.HasIndex("SubmittedAt"); - - b.HasIndex("TrustTokenId"); - - b.ToTable("TrustDeclarations"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustToken", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid"); - - b.Property("CreatedAt") - .HasColumnType("timestamp with time zone"); - - b.Property("TokenHash") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("TokenSource") - .IsRequired() - .HasMaxLength(32) - .HasColumnType("character varying(32)"); - - b.Property("TrustScore") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasIndex("TokenHash") - .IsUnique(); - - b.ToTable("TrustTokens"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Product", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Depth") - .HasColumnType("numeric"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Height") - .HasColumnType("numeric"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Price") - .HasColumnType("numeric"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.Property("Weight") - .HasColumnType("numeric"); - - b.Property("Width") - .HasColumnType("numeric"); - - b.HasKey("Id"); - - b.ToTable("Products"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Service", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ContextId") - .HasColumnType("text"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("ContextId"); - - b.ToTable("Services"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.Announce", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("For") - .HasColumnType("smallint"); - - b.Property("Message") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Sender") - .HasColumnType("text"); - - b.Property("Topic") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("Announce"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.DismissClicked", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("NotificationId") - .HasColumnType("bigint"); - - b.HasKey("UserId", "NotificationId"); - - b.HasIndex("NotificationId"); - - b.ToTable("DismissClicked"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Instrument", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("Instrument"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.InstrumentRating", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("InstrumentId") - .HasColumnType("bigint"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.HasAlternateKey("InstrumentId", "OwnerId"); - - b.HasIndex("OwnerId"); - - b.ToTable("InstrumentRating"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalPreference", b => - { - b.Property("OwnerProfileId") - .HasColumnType("text"); - - b.Property("DjSettingsUserId") - .HasColumnType("text"); - - b.Property("MusicLoverSettingsUserId") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("TendencyId") - .HasColumnType("bigint"); - - b.HasKey("OwnerProfileId"); - - b.HasIndex("DjSettingsUserId"); - - b.HasIndex("MusicLoverSettingsUserId"); - - b.HasIndex("TendencyId"); - - b.ToTable("MusicalPreference"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalTendency", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasMaxLength(255) - .HasColumnType("character varying(255)"); - - b.HasKey("Id"); - - b.ToTable("MusicalTendency"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.DjSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("SoundCloudId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("DjSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.Instrumentation", b => - { - b.Property("InstrumentId") - .HasColumnType("bigint"); - - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("InstrumentId", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("Instrumentation"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.MusicLoverSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("MusicLoverSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.Property("CreationToken") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ExecutorId") - .IsRequired() - .HasColumnType("text"); - - b.Property("OrderReference") - .HasColumnType("text"); - - b.Property("PaypalPayerId") - .HasColumnType("text"); - - b.Property("State") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("CreationToken"); - - b.HasIndex("ExecutorId"); - - b.ToTable("PayPalPayment"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ApplicationUserId") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Public") - .HasColumnType("boolean"); - - b.HasKey("Id"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("Circle"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.CircleMember", b => - { - b.Property("MemberId") - .HasColumnType("text"); - - b.Property("CircleId") - .HasColumnType("bigint"); - - b.HasKey("MemberId", "CircleId"); - - b.HasIndex("CircleId"); - - b.ToTable("CircleMembers"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Contact", b => - { - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("AddressId") - .HasColumnType("bigint"); - - b.Property("ApplicationUserId") - .HasColumnType("text"); - - b.Property("EMail") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.HasKey("OwnerId", "UserId"); - - b.HasIndex("AddressId"); - - b.HasIndex("ApplicationUserId"); - - b.ToTable("Contact"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.HyperLink", b => - { - b.Property("HRef") - .HasColumnType("text"); - - b.Property("Method") - .HasColumnType("text"); - - b.Property("BrusherProfileUserId") - .HasColumnType("text"); - - b.Property("ContentType") - .HasColumnType("text"); - - b.Property("PayPalPaymentCreationToken") - .HasColumnType("text"); - - b.Property("Rel") - .HasColumnType("text"); - - b.HasKey("HRef", "Method"); - - b.HasIndex("BrusherProfileUserId"); - - b.HasIndex("PayPalPaymentCreationToken"); - - b.ToTable("HyperLink"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Location", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Address") - .IsRequired() - .HasMaxLength(512) - .HasColumnType("character varying(512)"); - - b.Property("Latitude") - .HasColumnType("double precision"); - - b.Property("Longitude") - .HasColumnType("double precision"); - - b.HasKey("Id"); - - b.ToTable("Locations"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.PostalAddress", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("City") - .HasColumnType("text"); - - b.Property("Country") - .HasColumnType("text"); - - b.Property("PostalCode") - .HasColumnType("text"); - - b.Property("Province") - .HasColumnType("text"); - - b.Property("State") - .HasColumnType("text"); - - b.Property("Street1") - .HasColumnType("text"); - - b.Property("Street2") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Tag", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("Tags"); - }); - - modelBuilder.Entity("Yavsc.Models.Skill", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.HasKey("Id"); - - b.ToTable("SiteSkills"); - }); - - modelBuilder.Entity("Yavsc.Models.Streaming.LiveFlow", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DifferedFileName") - .HasColumnType("text"); - - b.Property("MediaType") - .HasColumnType("text"); - - b.Property("OwnerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Pitch") - .HasColumnType("text"); - - b.Property("SequenceNumber") - .HasColumnType("integer"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("LiveFlow"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.Property("Code") - .HasColumnType("text"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .HasColumnType("text"); - - b.Property("Hidden") - .HasColumnType("boolean"); - - b.Property("Moderated") - .HasColumnType("boolean"); - - b.Property("ModeratorGroupName") - .HasColumnType("text"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("ParentCode") - .HasColumnType("text"); - - b.Property("Photo") - .HasColumnType("text"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("SettingsClassName") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Code"); - - b.HasIndex("ParentCode"); - - b.ToTable("Activities"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CoWorking", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("FormationSettingsUserId") - .HasColumnType("text"); - - b.Property("PerformerId") - .HasColumnType("text"); - - b.Property("WorkingForId") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("FormationSettingsUserId"); - - b.HasIndex("PerformerId"); - - b.HasIndex("WorkingForId"); - - b.ToTable("CoWorking"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CommandForm", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActionName") - .HasColumnType("text"); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("Title") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.ToTable("CommandForm"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Country", b => - { - b.Property("Code") - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.HasKey("Code"); - - b.ToTable("Countries"); - - b.HasData( - new - { - Code = "fr", - DisplayName = "France" - }, - new - { - Code = "en", - DisplayName = "England" - }, - new - { - Code = "pt", - DisplayName = "Portugal" - }); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.DictionnaireMetier", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DomaineActiviteCode") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("Langue") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Nom") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("DomaineActiviteCode", "Langue", "Nom") - .IsUnique(); - - b.ToTable("DictionnaireMetier"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CountryCode") - .IsRequired() - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("ErrorMessage") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("RegularExpression") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("CountryCode"); - - b.ToTable("PerformerCodeInputValidations"); - - b.HasData( - new - { - Id = 1L, - CountryCode = "fr", - ErrorMessage = "Le code FR doit contenir entre 9 et 14 chiffres.", - RegularExpression = "^[0-9]{9,14}$" - }, - new - { - Id = 2L, - CountryCode = "en", - ErrorMessage = "Le code EN doit contenir entre 8 et 14 caracteres alphanumeriques.", - RegularExpression = "^[A-Za-z0-9]{8,14}$" - }, - new - { - Id = 3L, - CountryCode = "pt", - ErrorMessage = "Le code PT doit contenir exactement 9 chiffres.", - RegularExpression = "^[0-9]{9}$" - }); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.Property("PerformerId") - .HasColumnType("text"); - - b.Property("AcceptNotifications") - .HasColumnType("boolean"); - - b.Property("AcceptPublicContact") - .HasColumnType("boolean"); - - b.Property("Active") - .HasColumnType("boolean"); - - b.Property("ExerciseCountryCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("MaxDailyCost") - .HasColumnType("integer"); - - b.Property("MinDailyCost") - .HasColumnType("integer"); - - b.Property("OrganizationAddressId") - .HasColumnType("bigint"); - - b.Property("Rate") - .HasColumnType("integer"); - - b.Property("SIREN") - .IsRequired() - .HasColumnType("text"); - - b.Property("UseGeoLocalizationToReduceDistanceWithClients") - .HasColumnType("boolean"); - - b.Property("WebSite") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("PerformerId"); - - b.HasIndex("OrganizationAddressId"); - - b.ToTable("Performers"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Profiles.FormationSettings", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("DisplayName") - .HasColumnType("text"); - - b.HasKey("UserId"); - - b.ToTable("FormationSettings"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.TermeMetier", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DateSoumission") - .HasColumnType("timestamp with time zone"); - - b.Property("DateValidation") - .HasColumnType("timestamp with time zone"); - - b.Property("Definition") - .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); - - b.Property("DictionnaireMetierId") - .HasColumnType("bigint"); - - b.Property("Langue") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Mot") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ProposeParId") - .HasMaxLength(450) - .HasColumnType("character varying(450)"); - - b.Property("StatutValidation") - .HasColumnType("integer"); - - b.Property("ValideParId") - .HasMaxLength(450) - .HasColumnType("character varying(450)"); - - b.HasKey("Id"); - - b.HasIndex("ProposeParId"); - - b.HasIndex("ValideParId"); - - b.HasIndex("DictionnaireMetierId", "Langue", "Mot") - .IsUnique(); - - b.ToTable("TermeMetier"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => - { - b.Property("DoesCode") - .HasColumnType("text"); - - b.Property("UserId") - .HasColumnType("text"); - - b.Property("Weight") - .HasColumnType("integer"); - - b.HasKey("DoesCode", "UserId"); - - b.HasIndex("UserId"); - - b.ToTable("UserActivities"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.Calendar.Period", b => - { - b.Property("Start") - .HasColumnType("timestamp with time zone"); - - b.Property("End") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Start", "End"); - - b.ToTable("Period"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.EMailing.MailingTemplate", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("Body") - .HasMaxLength(65536) - .HasColumnType("character varying(65536)"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("ReplyToAddress") - .HasColumnType("text"); - - b.Property("ToSend") - .HasColumnType("integer"); - - b.Property("Topic") - .HasColumnType("text"); - - b.Property("UserCreated") - .HasColumnType("text"); - - b.Property("UserModified") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.ToTable("MailingTemplate"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("ProjectId") - .HasColumnType("bigint"); - - b.HasKey("Id"); - - b.HasIndex("ProjectId"); - - b.ToTable("ProjectBuildConfiguration"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("Branch") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Path") - .IsRequired() - .HasColumnType("text"); - - b.Property("Url") - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("GitRepositoryReference"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand"); - - b.Property("AdditionalInfo") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.HasIndex("LocationId"); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.Property("SelectedProfileUserId") - .HasColumnType("text"); - - b.HasIndex("PrestationId"); - - b.HasIndex("SelectedProfileUserId"); - - b.HasDiscriminator().HasValue("HairCutQuery"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.HasIndex("LocationId"); - - b.ToTable("NominativeServiceCommands", t => - { - t.Property("EventDate") - .HasColumnName("HairMultiCutQuery_EventDate"); - - t.Property("LocationId") - .HasColumnName("HairMultiCutQuery_LocationId"); - }); - - b.HasDiscriminator().HasValue("HairMultiCutQuery"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("LocationType") - .HasColumnType("integer"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.HasIndex("LocationId"); - - b.ToTable("NominativeServiceCommands", t => - { - t.Property("EventDate") - .HasColumnName("RdvQuery_EventDate"); - - t.Property("LocationId") - .HasColumnName("RdvQuery_LocationId"); - }); - - b.HasDiscriminator().HasValue("RdvQuery"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand"); - - b.Property("GitId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Version") - .HasColumnType("text"); - - b.HasIndex("GitId"); - - b.HasDiscriminator().HasValue("Project"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("UserClaims") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Properties") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Scopes") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) - .WithMany("Secrets") - .HasForeignKey("ApiResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", "ApiResource") - .WithMany() - .HasForeignKey("ApiResourceId1"); - - b.Navigation("ApiResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", null) - .WithMany("UserClaims") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", "Scope") - .WithMany() - .HasForeignKey("ScopeId1"); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", null) - .WithMany("Properties") - .HasForeignKey("ScopeId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.ApiScope", "Scope") - .WithMany() - .HasForeignKey("ScopeId1"); - - b.Navigation("Scope"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("Claims") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientCorsOrigin", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("AllowedCorsOrigins") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedGrantTypes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientIdPRestriction", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("IdentityProviderRestrictions") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("PostLogoutRedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("Properties") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("RedirectUris") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany("AllowedScopes") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientSecret", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) - .WithMany("ClientSecrets") - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") - .WithMany() - .HasForeignKey("ClientId1"); - - b.Navigation("Client"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceClaim", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("UserClaims") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceProperty", b => - { - b.HasOne("IdentityServer8.EntityFramework.Entities.IdentityResource", "IdentityResource") - .WithMany("Properties") - .HasForeignKey("IdentityResourceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("IdentityResource"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Yavsc.Models.Access.Ban", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "TargetUser") - .WithMany() - .HasForeignKey("TargetId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetUser"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.BlackListed", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("BlackList") - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToBlogPost", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Target") - .WithMany("ACL") - .HasForeignKey("BlogPostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Circle", "Allowed") - .WithMany() - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Allowed"); - - b.Navigation("Target"); - }); - - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToFile", b => - { - b.HasOne("Yavsc.Models.Relationship.Circle", "Allowed") - .WithMany() - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Allowed"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithOne("AccountBalance") - .HasForeignKey("Yavsc.Models.AccountBalance", "UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "PostalAddress") - .WithMany() - .HasForeignKey("PostalAddressId"); - - b.Navigation("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.BalanceImpact", b => - { - b.HasOne("Yavsc.Models.AccountBalance", "Balance") - .WithMany() - .HasForeignKey("BalanceId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Balance"); - }); - - modelBuilder.Entity("Yavsc.Models.Bank.BankIdentity", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany("BankInfo") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.CommandLine", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", null) - .WithMany("Bill") - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Billing.EstimateTemplate", null) - .WithMany("Bill") - .HasForeignKey("EstimateTemplateId"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Billing.NominativeServiceCommand", "Query") - .WithMany() - .HasForeignKey("CommandId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Owner"); - - b.Navigation("Query"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.NominativeServiceCommand", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", "Estimate") - .WithMany("Signatures") - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Signer") - .WithMany() - .HasForeignKey("SignerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Estimate"); - - b.Navigation("Signer"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogAttachedFile", b => - { - b.HasOne("Yavsc.Models.Blog.UploadedFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany() - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("File"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Author") - .WithMany("Posts") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Restrict); - - b.Navigation("Author"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogTag", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany("Tags") - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Relationship.Tag", "Tag") - .WithMany() - .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Post"); - - b.Navigation("Tag"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Author") - .WithMany("BlogComments") - .HasForeignKey("AuthorId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.Comment", "Parent") - .WithMany("Children") - .HasForeignKey("ParentId"); - - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithMany("Comments") - .HasForeignKey("ReceiverId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Author"); - - b.Navigation("Parent"); - - b.Navigation("Post"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "BlogPost") - .WithMany() - .HasForeignKey("BlogpostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("BlogPost"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.ScheduledEvent", b => - { - b.HasOne("Yavsc.Models.Calendar.Schedule", null) - .WithMany("Events") - .HasForeignKey("ScheduleOwnerId"); - - b.HasOne("Yavsc.Server.Models.Calendar.Period", "Period") - .WithMany() - .HasForeignKey("PeriodStart", "PeriodEnd"); - - b.Navigation("Period"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatConnection", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("Connections") - .HasForeignKey("ApplicationUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany("Rooms") - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoomAccess", b => - { - b.HasOne("Yavsc.Models.Chat.ChatRoom", "Room") - .WithMany("Moderation") - .HasForeignKey("ChannelName") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany("RoomAccess") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Room"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.HasOne("Yavsc.Models.Calendar.Schedule", "Schedule") - .WithMany() - .HasForeignKey("ScheduleOwnerId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "BaseProfile") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("BaseProfile"); - - b.Navigation("Schedule"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b => - { - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany() - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairMultiCutQuery", "Query") - .WithMany("Prestations") - .HasForeignKey("QueryId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Prestation"); - - b.Navigation("Query"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaint", b => - { - b.HasOne("Yavsc.Models.Drawing.Color", "Color") - .WithMany() - .HasForeignKey("ColorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Color"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairTaintInstance", b => - { - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany("Taints") - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.HairTaint", "Taint") - .WithMany() - .HasForeignKey("TaintId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Prestation"); - - b.Navigation("Taint"); - }); - - modelBuilder.Entity("Yavsc.Models.IT.Fixing.Bug", b => - { - b.HasOne("Yavsc.Models.IT.Evolution.Feature", "False") - .WithMany() - .HasForeignKey("FeatureId"); - - b.Navigation("False"); - }); - - modelBuilder.Entity("Yavsc.Models.Identity.DeviceDeclaration", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "DeviceOwner") - .WithMany("DeviceDeclaration") - .HasForeignKey("DeviceOwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("DeviceOwner"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.DeclarationFlag", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustDeclaration", "Declaration") - .WithMany("Flags") - .HasForeignKey("DeclarationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Kyc.RegexAlertPattern", "Pattern") - .WithMany() - .HasForeignKey("PatternId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Declaration"); - - b.Navigation("Pattern"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.ModerationLog", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustDeclaration", "Declaration") - .WithMany() - .HasForeignKey("DeclarationId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Declaration"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.HasOne("Yavsc.Models.Kyc.TrustToken", "Subject") - .WithMany("Declarations") - .HasForeignKey("TrustTokenId") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Subject"); - }); - - modelBuilder.Entity("Yavsc.Models.Market.Service", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany("Services") - .HasForeignKey("ContextId"); - - b.Navigation("Context"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.Announce", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Messaging.DismissClicked", b => - { - b.HasOne("Yavsc.Abstract.Models.Messaging.Notification", "Notified") - .WithMany() - .HasForeignKey("NotificationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Notified"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.InstrumentRating", b => - { - b.HasOne("Yavsc.Models.Musical.Instrument", "Instrument") - .WithMany() - .HasForeignKey("InstrumentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Profile") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Instrument"); - - b.Navigation("Profile"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.MusicalPreference", b => - { - b.HasOne("Yavsc.Models.Musical.Profiles.DjSettings", null) - .WithMany("SoundColor") - .HasForeignKey("DjSettingsUserId"); - - b.HasOne("Yavsc.Models.Musical.Profiles.MusicLoverSettings", null) - .WithMany("SoundColor") - .HasForeignKey("MusicLoverSettingsUserId"); - - b.HasOne("Yavsc.Models.Musical.MusicalTendency", "MusicalTendency") - .WithMany() - .HasForeignKey("TendencyId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("MusicalTendency"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.Instrumentation", b => - { - b.HasOne("Yavsc.Models.Musical.Instrument", "Tool") - .WithMany() - .HasForeignKey("InstrumentId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Tool"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Executor") - .WithMany() - .HasForeignKey("ExecutorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Executor"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany("Circles") - .HasForeignKey("ApplicationUserId"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.CircleMember", b => - { - b.HasOne("Yavsc.Models.Relationship.Circle", "Circle") - .WithMany("Members") - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Member") - .WithMany("Membership") - .HasForeignKey("MemberId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Circle"); - - b.Navigation("Member"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Contact", b => - { - b.HasOne("Yavsc.Models.Relationship.PostalAddress", "PostalAddress") - .WithMany() - .HasForeignKey("AddressId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", null) - .WithMany("Book") - .HasForeignKey("ApplicationUserId"); - - b.Navigation("PostalAddress"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.HyperLink", b => - { - b.HasOne("Yavsc.Models.Haircut.BrusherProfile", null) - .WithMany("Links") - .HasForeignKey("BrusherProfileUserId"); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", null) - .WithMany("Links") - .HasForeignKey("PayPalPaymentCreationToken"); - }); - - modelBuilder.Entity("Yavsc.Models.Streaming.LiveFlow", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Parent") - .WithMany("Children") - .HasForeignKey("ParentCode"); - - b.Navigation("Parent"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CoWorking", b => - { - b.HasOne("Yavsc.Models.Workflow.Profiles.FormationSettings", null) - .WithMany("CoWorking") - .HasForeignKey("FormationSettingsUserId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Performer") - .WithMany() - .HasForeignKey("PerformerId"); - - b.HasOne("Yavsc.Models.ApplicationUser", "WorkingFor") - .WithMany() - .HasForeignKey("WorkingForId"); - - b.Navigation("Performer"); - - b.Navigation("WorkingFor"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.CommandForm", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany("Forms") - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Context"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.DictionnaireMetier", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "DomaineActivite") - .WithMany() - .HasForeignKey("DomaineActiviteCode") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("DomaineActivite"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b => - { - b.HasOne("Yavsc.Models.Workflow.Country", "Country") - .WithMany() - .HasForeignKey("CountryCode") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Country"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "OrganizationAddress") - .WithMany() - .HasForeignKey("OrganizationAddressId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Performer") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("OrganizationAddress"); - - b.Navigation("Performer"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.TermeMetier", b => - { - b.HasOne("Yavsc.Models.Workflow.DictionnaireMetier", "DictionnaireMetier") - .WithMany("Termes") - .HasForeignKey("DictionnaireMetierId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "ProposePar") - .WithMany() - .HasForeignKey("ProposeParId"); - - b.HasOne("Yavsc.Models.ApplicationUser", "ValidePar") - .WithMany() - .HasForeignKey("ValideParId"); - - b.Navigation("DictionnaireMetier"); - - b.Navigation("ProposePar"); - - b.Navigation("ValidePar"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Does") - .WithMany() - .HasForeignKey("DoesCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "User") - .WithMany("Activity") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Does"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => - { - b.HasOne("Yavsc.Server.Models.IT.Project", "TargetProject") - .WithMany("Configurations") - .HasForeignKey("ProjectId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("TargetProject"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId"); - - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany() - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.BrusherProfile", "SelectedProfile") - .WithMany() - .HasForeignKey("SelectedProfileUserId"); - - b.Navigation("Location"); - - b.Navigation("Prestation"); - - b.Navigation("SelectedProfile"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Location"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Location"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.HasOne("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", "Repository") - .WithMany() - .HasForeignKey("GitId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Repository"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b => - { - b.Navigation("Properties"); - - b.Navigation("Scopes"); - - b.Navigation("Secrets"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b => - { - b.Navigation("AllowedCorsOrigins"); - - b.Navigation("AllowedGrantTypes"); - - b.Navigation("AllowedScopes"); - - b.Navigation("Claims"); - - b.Navigation("ClientSecrets"); - - b.Navigation("IdentityProviderRestrictions"); - - b.Navigation("PostLogoutRedirectUris"); - - b.Navigation("Properties"); - - b.Navigation("RedirectUris"); - }); - - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResource", b => - { - b.Navigation("Properties"); - - b.Navigation("UserClaims"); - }); - - modelBuilder.Entity("Yavsc.Models.ApplicationUser", b => - { - b.Navigation("AccountBalance"); - - b.Navigation("BankInfo"); - - b.Navigation("BlackList"); - - b.Navigation("BlogComments"); - - b.Navigation("Book"); - - b.Navigation("Circles"); - - b.Navigation("Connections"); - - b.Navigation("DeviceDeclaration"); - - b.Navigation("Membership"); - - b.Navigation("Posts"); - - b.Navigation("RoomAccess"); - - b.Navigation("Rooms"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.Estimate", b => - { - b.Navigation("Bill"); - - b.Navigation("Signatures"); - }); - - modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b => - { - b.Navigation("Bill"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.BlogPost", b => - { - b.Navigation("ACL"); - - b.Navigation("Comments"); - - b.Navigation("Tags"); - }); - - modelBuilder.Entity("Yavsc.Models.Blog.Comment", b => - { - b.Navigation("Children"); - }); - - modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => - { - b.Navigation("Events"); - }); - - modelBuilder.Entity("Yavsc.Models.Chat.ChatRoom", b => - { - b.Navigation("Moderation"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => - { - b.Navigation("Taints"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustDeclaration", b => - { - b.Navigation("Flags"); - }); - - modelBuilder.Entity("Yavsc.Models.Kyc.TrustToken", b => - { - b.Navigation("Declarations"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.DjSettings", b => - { - b.Navigation("SoundColor"); - }); - - modelBuilder.Entity("Yavsc.Models.Musical.Profiles.MusicLoverSettings", b => - { - b.Navigation("SoundColor"); - }); - - modelBuilder.Entity("Yavsc.Models.Payment.PayPalPayment", b => - { - b.Navigation("Links"); - }); - - modelBuilder.Entity("Yavsc.Models.Relationship.Circle", b => - { - b.Navigation("Members"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Activity", b => - { - b.Navigation("Children"); - - b.Navigation("Forms"); - - b.Navigation("Services"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.DictionnaireMetier", b => - { - b.Navigation("Termes"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => - { - b.Navigation("Activity"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.Profiles.FormationSettings", b => - { - b.Navigation("CoWorking"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.Navigation("Prestations"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.Navigation("Configurations"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260913191414_NominativeServiceCommand.cs b/src/Yavsc.Org/Migrations/20260913191414_NominativeServiceCommand.cs deleted file mode 100644 index 9d65e5523..000000000 --- a/src/Yavsc.Org/Migrations/20260913191414_NominativeServiceCommand.cs +++ /dev/null @@ -1,423 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Yavsc.Migrations -{ - /// - public partial class NominativeServiceCommand : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Estimates_NominativeServiceCommand_CommandId", - table: "Estimates"); - - migrationBuilder.DropForeignKey( - name: "FK_HairPrestationCollectionItem_NominativeServiceCommand_Query~", - table: "HairPrestationCollectionItem"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_Activities_ActivityCode", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_AspNetUsers_ClientId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_BrusherProfile_SelectedProfileUser~", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_GitRepositoryReference_GitId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_HairPrestation_PrestationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_Locations_HairMultiCutQuery_Locati~", - table: "NominativeServiceCommand"); - - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_Locations_RdvQuery_LocationId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_PayPalPayment_PaymentId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommand_Performers_PerformerId", - table: "NominativeServiceCommand"); - - migrationBuilder.DropForeignKey( - name: "FK_ProjectBuildConfiguration_NominativeServiceCommand_ProjectId", - table: "ProjectBuildConfiguration"); - - migrationBuilder.DropPrimaryKey( - name: "PK_NominativeServiceCommand", - table: "NominativeServiceCommand"); - - migrationBuilder.RenameTable( - name: "NominativeServiceCommand", - newName: "NominativeServiceCommands"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_SelectedProfileUserId", - table: "NominativeServiceCommands", - newName: "IX_NominativeServiceCommands_SelectedProfileUserId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_RdvQuery_LocationId", - table: "NominativeServiceCommands", - newName: "IX_NominativeServiceCommands_RdvQuery_LocationId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_PrestationId", - table: "NominativeServiceCommands", - newName: "IX_NominativeServiceCommands_PrestationId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_PerformerId", - table: "NominativeServiceCommands", - newName: "IX_NominativeServiceCommands_PerformerId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_PaymentId", - table: "NominativeServiceCommands", - newName: "IX_NominativeServiceCommands_PaymentId"); - - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_HairMultiCutQuery_LocationId", - table: "NominativeServiceCommands", - newName: "IX_NominativeServiceCommands_HairMultiCutQuery_LocationId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_GitId", - table: "NominativeServiceCommands", - newName: "IX_NominativeServiceCommands_GitId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_ClientId", - table: "NominativeServiceCommands", - newName: "IX_NominativeServiceCommands_ClientId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommand_ActivityCode", - table: "NominativeServiceCommands", - newName: "IX_NominativeServiceCommands_ActivityCode"); - - migrationBuilder.AddPrimaryKey( - name: "PK_NominativeServiceCommands", - table: "NominativeServiceCommands", - column: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_Estimates_NominativeServiceCommands_CommandId", - table: "Estimates", - column: "CommandId", - principalTable: "NominativeServiceCommands", - principalColumn: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_HairPrestationCollectionItem_NominativeServiceCommands_Quer~", - table: "HairPrestationCollectionItem", - column: "QueryId", - principalTable: "NominativeServiceCommands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommands_Activities_ActivityCode", - table: "NominativeServiceCommands", - column: "ActivityCode", - principalTable: "Activities", - principalColumn: "Code", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommands_AspNetUsers_ClientId", - table: "NominativeServiceCommands", - column: "ClientId", - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommands_BrusherProfile_SelectedProfileUse~", - table: "NominativeServiceCommands", - column: "SelectedProfileUserId", - principalTable: "BrusherProfile", - principalColumn: "UserId"); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommands_GitRepositoryReference_GitId", - table: "NominativeServiceCommands", - column: "GitId", - principalTable: "GitRepositoryReference", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommands_HairPrestation_PrestationId", - table: "NominativeServiceCommands", - column: "PrestationId", - principalTable: "HairPrestation", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommands_Locations_HairMultiCutQuery_Locat~", - table: "NominativeServiceCommands", - column: "HairMultiCutQuery_LocationId", - principalTable: "Locations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommands_Locations_RdvQuery_LocationId", - table: "NominativeServiceCommands", - column: "RdvQuery_LocationId", - principalTable: "Locations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommands_PayPalPayment_PaymentId", - table: "NominativeServiceCommands", - column: "PaymentId", - principalTable: "PayPalPayment", - principalColumn: "CreationToken"); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommands_Performers_PerformerId", - table: "NominativeServiceCommands", - column: "PerformerId", - principalTable: "Performers", - principalColumn: "PerformerId", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_ProjectBuildConfiguration_NominativeServiceCommands_Project~", - table: "ProjectBuildConfiguration", - column: "ProjectId", - principalTable: "NominativeServiceCommands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_Estimates_NominativeServiceCommands_CommandId", - table: "Estimates"); - - migrationBuilder.DropForeignKey( - name: "FK_HairPrestationCollectionItem_NominativeServiceCommands_Quer~", - table: "HairPrestationCollectionItem"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommands_Activities_ActivityCode", - table: "NominativeServiceCommands"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommands_AspNetUsers_ClientId", - table: "NominativeServiceCommands"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommands_BrusherProfile_SelectedProfileUse~", - table: "NominativeServiceCommands"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommands_GitRepositoryReference_GitId", - table: "NominativeServiceCommands"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommands_HairPrestation_PrestationId", - table: "NominativeServiceCommands"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommands_Locations_HairMultiCutQuery_Locat~", - table: "NominativeServiceCommands"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommands_Locations_LocationId", - table: "NominativeServiceCommands"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommands_Locations_RdvQuery_LocationId", - table: "NominativeServiceCommands"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommands_PayPalPayment_PaymentId", - table: "NominativeServiceCommands"); - - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommands_Performers_PerformerId", - table: "NominativeServiceCommands"); - - migrationBuilder.DropForeignKey( - name: "FK_ProjectBuildConfiguration_NominativeServiceCommands_Project~", - table: "ProjectBuildConfiguration"); - - migrationBuilder.DropPrimaryKey( - name: "PK_NominativeServiceCommands", - table: "NominativeServiceCommands"); - - migrationBuilder.RenameTable( - name: "NominativeServiceCommands", - newName: "NominativeServiceCommand"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommands_SelectedProfileUserId", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_SelectedProfileUserId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommands_RdvQuery_LocationId", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_RdvQuery_LocationId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommands_PrestationId", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_PrestationId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommands_PerformerId", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_PerformerId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommands_PaymentId", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_PaymentId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommands_HairMultiCutQuery_LocationId", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_HairMultiCutQuery_LocationId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommands_GitId", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_GitId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommands_ClientId", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_ClientId"); - - migrationBuilder.RenameIndex( - name: "IX_NominativeServiceCommands_ActivityCode", - table: "NominativeServiceCommand", - newName: "IX_NominativeServiceCommand_ActivityCode"); - - migrationBuilder.AddPrimaryKey( - name: "PK_NominativeServiceCommand", - table: "NominativeServiceCommand", - column: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_Estimates_NominativeServiceCommand_CommandId", - table: "Estimates", - column: "CommandId", - principalTable: "NominativeServiceCommand", - principalColumn: "Id"); - - migrationBuilder.AddForeignKey( - name: "FK_HairPrestationCollectionItem_NominativeServiceCommand_Query~", - table: "HairPrestationCollectionItem", - column: "QueryId", - principalTable: "NominativeServiceCommand", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_Activities_ActivityCode", - table: "NominativeServiceCommand", - column: "ActivityCode", - principalTable: "Activities", - principalColumn: "Code", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_AspNetUsers_ClientId", - table: "NominativeServiceCommand", - column: "ClientId", - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_BrusherProfile_SelectedProfileUser~", - table: "NominativeServiceCommand", - column: "SelectedProfileUserId", - principalTable: "BrusherProfile", - principalColumn: "UserId"); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_GitRepositoryReference_GitId", - table: "NominativeServiceCommand", - column: "GitId", - principalTable: "GitRepositoryReference", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_HairPrestation_PrestationId", - table: "NominativeServiceCommand", - column: "PrestationId", - principalTable: "HairPrestation", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_Locations_HairMultiCutQuery_Locati~", - table: "NominativeServiceCommand", - column: "HairMultiCutQuery_LocationId", - principalTable: "Locations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_Locations_RdvQuery_LocationId", - table: "NominativeServiceCommand", - column: "RdvQuery_LocationId", - principalTable: "Locations", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_PayPalPayment_PaymentId", - table: "NominativeServiceCommand", - column: "PaymentId", - principalTable: "PayPalPayment", - principalColumn: "CreationToken"); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommand_Performers_PerformerId", - table: "NominativeServiceCommand", - column: "PerformerId", - principalTable: "Performers", - principalColumn: "PerformerId", - onDelete: ReferentialAction.Cascade); - - migrationBuilder.AddForeignKey( - name: "FK_ProjectBuildConfiguration_NominativeServiceCommand_ProjectId", - table: "ProjectBuildConfiguration", - column: "ProjectId", - principalTable: "NominativeServiceCommand", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260913220000_AddHairCutQueryLocationId.cs b/src/Yavsc.Org/Migrations/20260913220000_AddHairCutQueryLocationId.cs deleted file mode 100644 index cc76bd9a4..000000000 --- a/src/Yavsc.Org/Migrations/20260913220000_AddHairCutQueryLocationId.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Yavsc.Models; - -#nullable disable - -namespace Yavsc.Migrations -{ - [DbContext(typeof(ApplicationDbContext))] - [Migration("20260913220000_AddHairCutQueryLocationId")] - public partial class AddHairCutQueryLocationId : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "LocationId", - table: "NominativeServiceCommands", - type: "bigint", - nullable: true); - - migrationBuilder.CreateIndex( - name: "IX_NominativeServiceCommands_LocationId", - table: "NominativeServiceCommands", - column: "LocationId"); - - migrationBuilder.AddForeignKey( - name: "FK_NominativeServiceCommands_Locations_LocationId", - table: "NominativeServiceCommands", - column: "LocationId", - principalTable: "Locations", - principalColumn: "Id"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_NominativeServiceCommands_Locations_LocationId", - table: "NominativeServiceCommands"); - - migrationBuilder.DropIndex( - name: "IX_NominativeServiceCommands_LocationId", - table: "NominativeServiceCommands"); - - migrationBuilder.DropColumn( - name: "LocationId", - table: "NominativeServiceCommands"); - } - } -} diff --git a/src/Yavsc.Org/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Yavsc.Org/Migrations/ApplicationDbContextModelSnapshot.cs index 84960c5bc..ef96638cc 100644 --- a/src/Yavsc.Org/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Yavsc.Org/Migrations/ApplicationDbContextModelSnapshot.cs @@ -476,6 +476,9 @@ namespace Yavsc.Migrations b.Property("ClientId") .HasColumnType("integer"); + b.Property("ClientId1") + .HasColumnType("integer"); + b.Property("GrantType") .HasColumnType("text"); @@ -483,6 +486,8 @@ namespace Yavsc.Migrations b.HasIndex("ClientId"); + b.HasIndex("ClientId1"); + b.ToTable("ClientGrantTypes"); }); @@ -578,6 +583,9 @@ namespace Yavsc.Migrations b.Property("ClientId") .HasColumnType("integer"); + b.Property("ClientId1") + .HasColumnType("integer"); + b.Property("RedirectUri") .HasColumnType("text"); @@ -585,6 +593,8 @@ namespace Yavsc.Migrations b.HasIndex("ClientId"); + b.HasIndex("ClientId1"); + b.ToTable("ClientRedirectUris"); }); @@ -599,6 +609,9 @@ namespace Yavsc.Migrations b.Property("ClientId") .HasColumnType("integer"); + b.Property("ClientId1") + .HasColumnType("integer"); + b.Property("Scope") .HasColumnType("text"); @@ -606,6 +619,8 @@ namespace Yavsc.Migrations b.HasIndex("ClientId"); + b.HasIndex("ClientId1"); + b.ToTable("ClientScopes"); }); @@ -1081,6 +1096,9 @@ namespace Yavsc.Migrations b.Property("BlogPostId") .HasColumnType("bigint"); + b.Property("Comment") + .HasColumnType("boolean"); + b.HasKey("CircleId", "BlogPostId"); b.HasIndex("BlogPostId"); @@ -1088,27 +1106,6 @@ namespace Yavsc.Migrations b.ToTable("CircleAuthorizationToBlogPost"); }); - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToFile", b => - { - b.Property("CircleId") - .HasColumnType("bigint"); - - b.Property("Path") - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Access") - .HasColumnType("smallint"); - - b.HasKey("CircleId", "Path", "OwnerId"); - - b.HasIndex("OwnerId"); - - b.ToTable("CircleAuthorizationToFile"); - }); - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => { b.Property("UserId") @@ -1263,30 +1260,24 @@ namespace Yavsc.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("AccountNumber") - .IsRequired() .HasColumnType("text"); b.Property("BIC") - .IsRequired() .HasColumnType("text"); b.Property("BankCode") - .IsRequired() .HasColumnType("text"); b.Property("BankedKey") .HasColumnType("integer"); b.Property("IBAN") - .IsRequired() .HasColumnType("text"); b.Property("UserId") - .IsRequired() .HasColumnType("text"); b.Property("WicketCode") - .IsRequired() .HasColumnType("text"); b.HasKey("Id"); @@ -1347,11 +1338,9 @@ namespace Yavsc.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("AttachedFilesString") - .IsRequired() .HasColumnType("text"); b.Property("AttachedGraphicsString") - .IsRequired() .HasColumnType("text"); b.Property("ClientId") @@ -1369,18 +1358,15 @@ namespace Yavsc.Migrations .HasColumnType("text"); b.Property("Description") - .IsRequired() .HasColumnType("text"); b.Property("OwnerId") - .IsRequired() .HasColumnType("text"); b.Property("ProviderValidationDate") .HasColumnType("timestamp with time zone"); b.Property("Title") - .IsRequired() .HasColumnType("text"); b.HasKey("Id"); @@ -1427,81 +1413,6 @@ namespace Yavsc.Migrations b.ToTable("ExceptionsSIREN"); }); - modelBuilder.Entity("Yavsc.Models.Billing.NominativeServiceCommand", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ActivityCode") - .IsRequired() - .HasColumnType("text"); - - b.Property("ClientId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Consent") - .HasColumnType("boolean"); - - b.Property("DateCreated") - .HasColumnType("timestamp with time zone"); - - b.Property("DateModified") - .HasColumnType("timestamp with time zone"); - - b.Property("Description") - .IsRequired() - .HasColumnType("text"); - - b.Property("Discriminator") - .IsRequired() - .HasMaxLength(34) - .HasColumnType("character varying(34)"); - - b.Property("PaymentId") - .HasColumnType("text"); - - b.Property("PerformerId") - .IsRequired() - .HasColumnType("text"); - - b.Property("Provisional") - .HasColumnType("numeric"); - - b.Property("Status") - .HasColumnType("integer"); - - b.Property("UserCreated") - .IsRequired() - .HasColumnType("text"); - - b.Property("UserModified") - .IsRequired() - .HasColumnType("text"); - - b.Property("ValidationDate") - .HasColumnType("timestamp with time zone"); - - b.HasKey("Id"); - - b.HasIndex("ActivityCode"); - - b.HasIndex("ClientId"); - - b.HasIndex("PaymentId"); - - b.HasIndex("PerformerId"); - - b.ToTable("NominativeServiceCommands"); - - b.HasDiscriminator("Discriminator").HasValue("NominativeServiceCommand"); - - b.UseTphMappingStrategy(); - }); - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => { b.Property("Id") @@ -1628,7 +1539,6 @@ namespace Yavsc.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Article") - .IsRequired() .HasColumnType("text"); b.Property("AuthorId") @@ -1648,11 +1558,9 @@ namespace Yavsc.Migrations .HasColumnType("bigint"); b.Property("UserCreated") - .IsRequired() .HasColumnType("text"); b.Property("UserModified") - .IsRequired() .HasColumnType("text"); b.Property("Visible") @@ -1872,6 +1780,19 @@ namespace Yavsc.Migrations b.ToTable("Color"); }); + modelBuilder.Entity("Yavsc.Models.Forms.Form", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Summary") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Form"); + }); + modelBuilder.Entity("Yavsc.Models.Haircut.BrusherProfile", b => { b.Property("UserId") @@ -1989,6 +1910,161 @@ namespace Yavsc.Migrations b.ToTable("BrusherProfile"); }); + modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActivityCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("AdditionalInfo") + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Consent") + .HasColumnType("boolean"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("EventDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationId") + .HasColumnType("bigint"); + + b.Property("PaymentId") + .HasColumnType("text"); + + b.Property("PerformerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("PrestationId") + .HasColumnType("bigint"); + + b.Property("Provisional") + .HasColumnType("numeric"); + + b.Property("SelectedProfileUserId") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserCreated") + .HasColumnType("text"); + + b.Property("UserModified") + .HasColumnType("text"); + + b.Property("ValidationDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ActivityCode"); + + b.HasIndex("ClientId"); + + b.HasIndex("LocationId"); + + b.HasIndex("PaymentId"); + + b.HasIndex("PerformerId"); + + b.HasIndex("PrestationId"); + + b.HasIndex("SelectedProfileUserId"); + + b.ToTable("HairCutQueries"); + }); + + modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActivityCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Consent") + .HasColumnType("boolean"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("EventDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationId") + .HasColumnType("bigint"); + + b.Property("PaymentId") + .HasColumnType("text"); + + b.Property("PerformerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Provisional") + .HasColumnType("numeric"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserCreated") + .HasColumnType("text"); + + b.Property("UserModified") + .HasColumnType("text"); + + b.Property("ValidationDate") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ActivityCode"); + + b.HasIndex("ClientId"); + + b.HasIndex("LocationId"); + + b.HasIndex("PaymentId"); + + b.HasIndex("PerformerId"); + + b.ToTable("HairMultiCutQueries"); + }); + modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => { b.Property("Id") @@ -2113,7 +2189,6 @@ namespace Yavsc.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Description") - .IsRequired() .HasMaxLength(10240) .HasColumnType("character varying(10240)"); @@ -2124,7 +2199,6 @@ namespace Yavsc.Migrations .HasColumnType("integer"); b.Property("Title") - .IsRequired() .HasColumnType("text"); b.HasKey("Id"); @@ -2145,22 +2219,18 @@ namespace Yavsc.Migrations .HasDefaultValueSql("LOCALTIMESTAMP"); b.Property("DeviceOwnerId") - .IsRequired() .HasColumnType("text"); b.Property("LatestActivityUpdate") .HasColumnType("timestamp with time zone"); b.Property("Model") - .IsRequired() .HasColumnType("text"); b.Property("Platform") - .IsRequired() .HasColumnType("text"); b.Property("Version") - .IsRequired() .HasColumnType("text"); b.HasKey("DeviceId"); @@ -2273,7 +2343,6 @@ namespace Yavsc.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("Content") - .IsRequired() .HasMaxLength(2000) .HasColumnType("character varying(2000)"); @@ -2946,123 +3015,6 @@ namespace Yavsc.Migrations b.ToTable("CommandForm"); }); - modelBuilder.Entity("Yavsc.Models.Workflow.Country", b => - { - b.Property("Code") - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("DisplayName") - .IsRequired() - .HasMaxLength(64) - .HasColumnType("character varying(64)"); - - b.HasKey("Code"); - - b.ToTable("Countries"); - - b.HasData( - new - { - Code = "fr", - DisplayName = "France" - }, - new - { - Code = "en", - DisplayName = "England" - }, - new - { - Code = "pt", - DisplayName = "Portugal" - }); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.DictionnaireMetier", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("DomaineActiviteCode") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("Langue") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Nom") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.HasKey("Id"); - - b.HasIndex("DomaineActiviteCode", "Langue", "Nom") - .IsUnique(); - - b.ToTable("DictionnaireMetier"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("bigint"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("CountryCode") - .IsRequired() - .HasMaxLength(2) - .HasColumnType("character varying(2)"); - - b.Property("ErrorMessage") - .IsRequired() - .HasMaxLength(128) - .HasColumnType("character varying(128)"); - - b.Property("RegularExpression") - .IsRequired() - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("CountryCode"); - - b.ToTable("PerformerCodeInputValidations"); - - b.HasData( - new - { - Id = 1L, - CountryCode = "fr", - ErrorMessage = "Le code FR doit contenir entre 9 et 14 chiffres.", - RegularExpression = "^[0-9]{9,14}$" - }, - new - { - Id = 2L, - CountryCode = "en", - ErrorMessage = "Le code EN doit contenir entre 8 et 14 caracteres alphanumeriques.", - RegularExpression = "^[A-Za-z0-9]{8,14}$" - }, - new - { - Id = 3L, - CountryCode = "pt", - ErrorMessage = "Le code PT doit contenir exactement 9 chiffres.", - RegularExpression = "^[0-9]{9}$" - }); - }); - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => { b.Property("PerformerId") @@ -3077,10 +3029,6 @@ namespace Yavsc.Migrations b.Property("Active") .HasColumnType("boolean"); - b.Property("ExerciseCountryCode") - .IsRequired() - .HasColumnType("text"); - b.Property("MaxDailyCost") .HasColumnType("integer"); @@ -3101,7 +3049,6 @@ namespace Yavsc.Migrations .HasColumnType("boolean"); b.Property("WebSite") - .IsRequired() .HasColumnType("text"); b.HasKey("PerformerId"); @@ -3124,7 +3071,7 @@ namespace Yavsc.Migrations b.ToTable("FormationSettings"); }); - modelBuilder.Entity("Yavsc.Models.Workflow.TermeMetier", b => + modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -3132,51 +3079,73 @@ namespace Yavsc.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("DateSoumission") - .HasColumnType("timestamp with time zone"); - - b.Property("DateValidation") - .HasColumnType("timestamp with time zone"); - - b.Property("Definition") + b.Property("ActivityCode") .IsRequired() - .HasMaxLength(2000) - .HasColumnType("character varying(2000)"); + .HasColumnType("text"); - b.Property("DictionnaireMetierId") + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Consent") + .HasColumnType("boolean"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("EventDate") + .HasColumnType("timestamp with time zone"); + + b.Property("LocationId") .HasColumnType("bigint"); - b.Property("Langue") - .IsRequired() - .HasMaxLength(20) - .HasColumnType("character varying(20)"); - - b.Property("Mot") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)"); - - b.Property("ProposeParId") - .HasMaxLength(450) - .HasColumnType("character varying(450)"); - - b.Property("StatutValidation") + b.Property("LocationType") .HasColumnType("integer"); - b.Property("ValideParId") - .HasMaxLength(450) - .HasColumnType("character varying(450)"); + b.Property("PaymentId") + .HasColumnType("text"); + + b.Property("PerformerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Provisional") + .HasColumnType("numeric"); + + b.Property("Reason") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserCreated") + .HasColumnType("text"); + + b.Property("UserModified") + .HasColumnType("text"); + + b.Property("ValidationDate") + .HasColumnType("timestamp with time zone"); b.HasKey("Id"); - b.HasIndex("ProposeParId"); + b.HasIndex("ActivityCode"); - b.HasIndex("ValideParId"); + b.HasIndex("ClientId"); - b.HasIndex("DictionnaireMetierId", "Langue", "Mot") - .IsUnique(); + b.HasIndex("LocationId"); - b.ToTable("TermeMetier"); + b.HasIndex("PaymentId"); + + b.HasIndex("PerformerId"); + + b.ToTable("RdvQueries"); }); modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => @@ -3245,6 +3214,84 @@ namespace Yavsc.Migrations b.ToTable("MailingTemplate"); }); + modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ActivityCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Consent") + .HasColumnType("boolean"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone"); + + b.Property("DateModified") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("GitId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("OwnerId") + .HasColumnType("text"); + + b.Property("PaymentId") + .HasColumnType("text"); + + b.Property("PerformerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Provisional") + .HasColumnType("numeric"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UserCreated") + .HasColumnType("text"); + + b.Property("UserModified") + .HasColumnType("text"); + + b.Property("ValidationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Version") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ActivityCode"); + + b.HasIndex("ClientId"); + + b.HasIndex("GitId"); + + b.HasIndex("PaymentId"); + + b.HasIndex("PerformerId"); + + b.ToTable("Project"); + }); + modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => { b.Property("Id") @@ -3295,112 +3342,6 @@ namespace Yavsc.Migrations b.ToTable("GitRepositoryReference"); }); - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand"); - - b.Property("AdditionalInfo") - .IsRequired() - .HasColumnType("text"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("PrestationId") - .HasColumnType("bigint"); - - b.Property("SelectedProfileUserId") - .HasColumnType("text"); - - b.HasIndex("LocationId"); - - b.HasIndex("PrestationId"); - - b.HasIndex("SelectedProfileUserId"); - - b.HasDiscriminator().HasValue("HairCutQuery"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.HasIndex("LocationId"); - - b.ToTable("NominativeServiceCommands", t => - { - t.Property("EventDate") - .HasColumnName("HairMultiCutQuery_EventDate"); - - t.Property("LocationId") - .HasColumnName("HairMultiCutQuery_LocationId"); - }); - - b.HasDiscriminator().HasValue("HairMultiCutQuery"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand"); - - b.Property("EventDate") - .HasColumnType("timestamp with time zone"); - - b.Property("LocationId") - .HasColumnType("bigint"); - - b.Property("LocationType") - .HasColumnType("integer"); - - b.Property("Reason") - .IsRequired() - .HasColumnType("text"); - - b.HasIndex("LocationId"); - - b.ToTable("NominativeServiceCommands", t => - { - t.Property("EventDate") - .HasColumnName("RdvQuery_EventDate"); - - t.Property("LocationId") - .HasColumnName("RdvQuery_LocationId"); - }); - - b.HasDiscriminator().HasValue("RdvQuery"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.HasBaseType("Yavsc.Models.Billing.NominativeServiceCommand"); - - b.Property("GitId") - .HasColumnType("bigint"); - - b.Property("Name") - .IsRequired() - .HasColumnType("text"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Version") - .HasColumnType("text"); - - b.HasIndex("GitId"); - - b.HasDiscriminator().HasValue("Project"); - }); - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b => { b.HasOne("IdentityServer8.EntityFramework.Entities.ApiResource", null) @@ -3519,12 +3460,16 @@ namespace Yavsc.Migrations modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b => { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") + b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) .WithMany("AllowedGrantTypes") .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") + .WithMany() + .HasForeignKey("ClientId1"); + b.Navigation("Client"); }); @@ -3575,23 +3520,31 @@ namespace Yavsc.Migrations modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b => { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") + b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) .WithMany("RedirectUris") .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") + .WithMany() + .HasForeignKey("ClientId1"); + b.Navigation("Client"); }); modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b => { - b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") + b.HasOne("IdentityServer8.EntityFramework.Entities.Client", null) .WithMany("AllowedScopes") .HasForeignKey("ClientId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + b.HasOne("IdentityServer8.EntityFramework.Entities.Client", "Client") + .WithMany() + .HasForeignKey("ClientId1"); + b.Navigation("Client"); }); @@ -3732,25 +3685,6 @@ namespace Yavsc.Migrations b.Navigation("Target"); }); - modelBuilder.Entity("Yavsc.Models.Access.CircleAuthorizationToFile", b => - { - b.HasOne("Yavsc.Models.Relationship.Circle", "Allowed") - .WithMany() - .HasForeignKey("CircleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Allowed"); - - b.Navigation("Owner"); - }); - modelBuilder.Entity("Yavsc.Models.AccountBalance", b => { b.HasOne("Yavsc.Models.ApplicationUser", "Owner") @@ -3786,9 +3720,7 @@ namespace Yavsc.Migrations { b.HasOne("Yavsc.Models.ApplicationUser", "User") .WithMany("BankInfo") - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("UserId"); b.Navigation("User"); }); @@ -3814,15 +3746,13 @@ namespace Yavsc.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Yavsc.Models.Billing.NominativeServiceCommand", "Query") + b.HasOne("Yavsc.Models.Workflow.RdvQuery", "Query") .WithMany() .HasForeignKey("CommandId"); b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "Owner") .WithMany() - .HasForeignKey("OwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("OwnerId"); b.Navigation("Client"); @@ -3831,39 +3761,6 @@ namespace Yavsc.Migrations b.Navigation("Query"); }); - modelBuilder.Entity("Yavsc.Models.Billing.NominativeServiceCommand", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "Context") - .WithMany() - .HasForeignKey("ActivityCode") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.ApplicationUser", "Client") - .WithMany() - .HasForeignKey("ClientId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") - .WithMany() - .HasForeignKey("PaymentId"); - - b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") - .WithMany() - .HasForeignKey("PerformerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Client"); - - b.Navigation("Context"); - - b.Navigation("PerformerProfile"); - - b.Navigation("Regularization"); - }); - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => { b.HasOne("Yavsc.Models.Billing.Estimate", "Estimate") @@ -4047,6 +3944,98 @@ namespace Yavsc.Migrations b.Navigation("Schedule"); }); + modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => + { + b.HasOne("Yavsc.Models.Workflow.Activity", "Context") + .WithMany() + .HasForeignKey("ActivityCode") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yavsc.Models.ApplicationUser", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yavsc.Models.Relationship.Location", "Location") + .WithMany() + .HasForeignKey("LocationId"); + + b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") + .WithMany() + .HasForeignKey("PaymentId"); + + b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") + .WithMany() + .HasForeignKey("PerformerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") + .WithMany() + .HasForeignKey("PrestationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yavsc.Models.Haircut.BrusherProfile", "SelectedProfile") + .WithMany() + .HasForeignKey("SelectedProfileUserId"); + + b.Navigation("Client"); + + b.Navigation("Context"); + + b.Navigation("Location"); + + b.Navigation("PerformerProfile"); + + b.Navigation("Prestation"); + + b.Navigation("Regularization"); + + b.Navigation("SelectedProfile"); + }); + + modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => + { + b.HasOne("Yavsc.Models.Workflow.Activity", "Context") + .WithMany() + .HasForeignKey("ActivityCode") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yavsc.Models.ApplicationUser", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yavsc.Models.Relationship.Location", "Location") + .WithMany() + .HasForeignKey("LocationId"); + + b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") + .WithMany() + .HasForeignKey("PaymentId"); + + b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") + .WithMany() + .HasForeignKey("PerformerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Context"); + + b.Navigation("Location"); + + b.Navigation("PerformerProfile"); + + b.Navigation("Regularization"); + }); + modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestationCollectionItem", b => { b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") @@ -4109,9 +4098,7 @@ namespace Yavsc.Migrations { b.HasOne("Yavsc.Models.ApplicationUser", "DeviceOwner") .WithMany("DeviceDeclaration") - .HasForeignKey("DeviceOwnerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .HasForeignKey("DeviceOwnerId"); b.Navigation("DeviceOwner"); }); @@ -4364,28 +4351,6 @@ namespace Yavsc.Migrations b.Navigation("Context"); }); - modelBuilder.Entity("Yavsc.Models.Workflow.DictionnaireMetier", b => - { - b.HasOne("Yavsc.Models.Workflow.Activity", "DomaineActivite") - .WithMany() - .HasForeignKey("DomaineActiviteCode") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("DomaineActivite"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerCodeInputValidation", b => - { - b.HasOne("Yavsc.Models.Workflow.Country", "Country") - .WithMany() - .HasForeignKey("CountryCode") - .OnDelete(DeleteBehavior.Restrict) - .IsRequired(); - - b.Navigation("Country"); - }); - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => { b.HasOne("Yavsc.Models.Relationship.Location", "OrganizationAddress") @@ -4405,27 +4370,43 @@ namespace Yavsc.Migrations b.Navigation("Performer"); }); - modelBuilder.Entity("Yavsc.Models.Workflow.TermeMetier", b => + modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => { - b.HasOne("Yavsc.Models.Workflow.DictionnaireMetier", "DictionnaireMetier") - .WithMany("Termes") - .HasForeignKey("DictionnaireMetierId") + b.HasOne("Yavsc.Models.Workflow.Activity", "Context") + .WithMany() + .HasForeignKey("ActivityCode") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.HasOne("Yavsc.Models.ApplicationUser", "ProposePar") + b.HasOne("Yavsc.Models.ApplicationUser", "Client") .WithMany() - .HasForeignKey("ProposeParId"); + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.HasOne("Yavsc.Models.ApplicationUser", "ValidePar") + b.HasOne("Yavsc.Models.Relationship.Location", "Location") .WithMany() - .HasForeignKey("ValideParId"); + .HasForeignKey("LocationId"); - b.Navigation("DictionnaireMetier"); + b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") + .WithMany() + .HasForeignKey("PaymentId"); - b.Navigation("ProposePar"); + b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") + .WithMany() + .HasForeignKey("PerformerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.Navigation("ValidePar"); + b.Navigation("Client"); + + b.Navigation("Context"); + + b.Navigation("Location"); + + b.Navigation("PerformerProfile"); + + b.Navigation("Regularization"); }); modelBuilder.Entity("Yavsc.Models.Workflow.UserActivity", b => @@ -4447,6 +4428,47 @@ namespace Yavsc.Migrations b.Navigation("User"); }); + modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => + { + b.HasOne("Yavsc.Models.Workflow.Activity", "Context") + .WithMany() + .HasForeignKey("ActivityCode") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yavsc.Models.ApplicationUser", "Client") + .WithMany() + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", "Repository") + .WithMany() + .HasForeignKey("GitId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Yavsc.Models.Payment.PayPalPayment", "Regularization") + .WithMany() + .HasForeignKey("PaymentId"); + + b.HasOne("Yavsc.Models.Workflow.PerformerProfile", "PerformerProfile") + .WithMany() + .HasForeignKey("PerformerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Client"); + + b.Navigation("Context"); + + b.Navigation("PerformerProfile"); + + b.Navigation("Regularization"); + + b.Navigation("Repository"); + }); + modelBuilder.Entity("Yavsc.Server.Models.IT.ProjectBuildConfiguration", b => { b.HasOne("Yavsc.Server.Models.IT.Project", "TargetProject") @@ -4467,62 +4489,6 @@ namespace Yavsc.Migrations b.Navigation("Owner"); }); - modelBuilder.Entity("Yavsc.Models.Haircut.HairCutQuery", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId"); - - b.HasOne("Yavsc.Models.Haircut.HairPrestation", "Prestation") - .WithMany() - .HasForeignKey("PrestationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Haircut.BrusherProfile", "SelectedProfile") - .WithMany() - .HasForeignKey("SelectedProfileUserId"); - - b.Navigation("Location"); - - b.Navigation("Prestation"); - - b.Navigation("SelectedProfile"); - }); - - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Location"); - }); - - modelBuilder.Entity("Yavsc.Models.Workflow.RdvQuery", b => - { - b.HasOne("Yavsc.Models.Relationship.Location", "Location") - .WithMany() - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Location"); - }); - - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => - { - b.HasOne("Yavsc.Server.Models.IT.SourceCode.GitRepositoryReference", "Repository") - .WithMany() - .HasForeignKey("GitId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Repository"); - }); - modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b => { b.Navigation("Properties"); @@ -4637,6 +4603,11 @@ namespace Yavsc.Migrations b.Navigation("Links"); }); + modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => + { + b.Navigation("Prestations"); + }); + modelBuilder.Entity("Yavsc.Models.Haircut.HairPrestation", b => { b.Navigation("Taints"); @@ -4681,11 +4652,6 @@ namespace Yavsc.Migrations b.Navigation("Services"); }); - modelBuilder.Entity("Yavsc.Models.Workflow.DictionnaireMetier", b => - { - b.Navigation("Termes"); - }); - modelBuilder.Entity("Yavsc.Models.Workflow.PerformerProfile", b => { b.Navigation("Activity"); @@ -4696,11 +4662,6 @@ namespace Yavsc.Migrations b.Navigation("CoWorking"); }); - modelBuilder.Entity("Yavsc.Models.Haircut.HairMultiCutQuery", b => - { - b.Navigation("Prestations"); - }); - modelBuilder.Entity("Yavsc.Server.Models.IT.Project", b => { b.Navigation("Configurations"); diff --git a/src/Yavsc.Org/Migrations/ConfigurationDb/20260301200548_init.cs b/src/Yavsc.Org/Migrations/ConfigurationDb/20260301200548_init.cs index df95a4437..a82613118 100644 --- a/src/Yavsc.Org/Migrations/ConfigurationDb/20260301200548_init.cs +++ b/src/Yavsc.Org/Migrations/ConfigurationDb/20260301200548_init.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using System; +using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable diff --git a/src/Yavsc.Org/Migrations/PersistedGrantDb/20260301200508_init.cs b/src/Yavsc.Org/Migrations/PersistedGrantDb/20260301200508_init.cs index b12978677..0c240b201 100644 --- a/src/Yavsc.Org/Migrations/PersistedGrantDb/20260301200508_init.cs +++ b/src/Yavsc.Org/Migrations/PersistedGrantDb/20260301200508_init.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using System; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable diff --git a/src/Yavsc.Org/Program.cs b/src/Yavsc.Org/Program.cs index 7f8e38b1c..a0ef57d34 100644 --- a/src/Yavsc.Org/Program.cs +++ b/src/Yavsc.Org/Program.cs @@ -1,3 +1,5 @@ +using Anthropic.SDK; +using Yavsc.Abstract.Interfaces; using Yavsc.Extensions; using Yavsc.Server.Helpers; @@ -16,6 +18,6 @@ namespace Yavsc app.Run(); } - + } -} +} \ No newline at end of file diff --git a/src/Yavsc.Org/Services/BlogSpotService.cs b/src/Yavsc.Org/Services/BlogSpotService.cs index 39e9329f1..76858c9c1 100644 --- a/src/Yavsc.Org/Services/BlogSpotService.cs +++ b/src/Yavsc.Org/Services/BlogSpotService.cs @@ -2,9 +2,9 @@ using System.Diagnostics; using System.Security.Claims; using Microsoft.AspNetCore.Authorization; using Microsoft.EntityFrameworkCore; +using Yavsc; using Yavsc.Blogspot; using Yavsc.Models; -using Yavsc.Models.Access; using Yavsc.Models.Blog; using Yavsc.Server.Exceptions; using Yavsc.Server.Helpers; @@ -28,7 +28,7 @@ public class OldBlogSpotService this.fileSystemAuthManager = fileSystemAuthManager; } - public Yavsc.Models.Blog.BlogPost Create(string userId, Yavsc.Models.Blog.BlogPost post, IFormFileCollection files) + public BlogPost Create(string userId, BlogPost post, IFormFileCollection files) { // Sauvegarder le post d'abord pour obtenir son ID _context.BlogSpot.Add(post); @@ -98,14 +98,13 @@ public class OldBlogSpotService throw new AuthorizationFailureException(auth); } var pub = await _context.blogSpotPublications.AnyAsync(x => x.BlogpostId == blog.Id); - ScrubAclForViewer(blog, user); return new BlogPostEditViewModel(blog, pub); } - public async Task Details(ClaimsPrincipal user, long blogPostId) + public async Task Details(ClaimsPrincipal user, long blogPostId) { - Yavsc.Models.Blog.BlogPost blog = await _context.BlogSpot + BlogPost blog = await _context.BlogSpot .Include(p => p.Author) .Include(p => p.Tags) .Include(p => p.Comments) @@ -120,7 +119,6 @@ public class OldBlogSpotService { throw new AuthorizationFailureException(auth); } - ScrubAclForViewer(blog, user); foreach (var c in blog.Comments) { c.Author = _context.Users.First(u => u.Id == c.AuthorId); @@ -167,7 +165,7 @@ public class OldBlogSpotService _context.SaveChanges(user.GetUserId()); } - public async Task Modify(ClaimsPrincipal user, Yavsc.Models.Blog.BlogPost blog) + public async Task Modify(ClaimsPrincipal user, BlogPost blog) { var existing = await _context.BlogSpot.Include(b => b.ACL).SingleOrDefaultAsync(b => b.Id == blog.Id); if (existing == null) @@ -192,14 +190,13 @@ public class OldBlogSpotService public async Task> Index(ClaimsPrincipal user, string id, int skip = 0, int take = 25) { - string? viewerId = user.Identity?.IsAuthenticated == true ? user.GetUserId() : null; IEnumerable posts; if (user.Identity.IsAuthenticated) { - string viewerIdNonNull = viewerId!; + string viewerId = user.GetUserId(); long[] userCircles = await _context.Circle.Include(c => c.Members). - Where(c => c.Members.Any(m => m.MemberId == viewerIdNonNull)) + Where(c => c.Members.Any(m => m.MemberId == viewerId)) .Select(c => c.Id).ToArrayAsync(); posts = _context.BlogSpot @@ -209,7 +206,7 @@ public class OldBlogSpotService .Include(p => p.Comments) .Where(p => p.ACL == null || p.ACL.Count == 0 - || (p.AuthorId == viewerIdNonNull) + || (p.AuthorId == viewerId) || (userCircles != null && p.ACL.Any(a => userCircles.Contains(a.CircleId))) ); @@ -227,11 +224,7 @@ public class OldBlogSpotService .Select(p => p.BlogPost).ToArray(); } - var materialised = posts.ToList(); - foreach (var post in materialised.OfType()) - ScrubAclForViewer(post, user); - - var data = materialised.OrderByDescending(p => p.DateModified) + var data = posts.OrderByDescending(p => p.DateModified) .Skip(skip) .Take(take); return data; @@ -240,25 +233,21 @@ public class OldBlogSpotService public async Task Delete(ClaimsPrincipal user, long id) { var uid = user.GetUserId(); - Yavsc.Models.Blog.BlogPost blog = _context.BlogSpot.Single(m => m.Id == id); + BlogPost blog = _context.BlogSpot.Single(m => m.Id == id); _context.BlogSpot.Remove(blog); _context.SaveChanges(user.GetUserId()); } - public async Task> UserPosts( + public async Task> UserPosts( string posterName, string? readerId, int pageLen = 10, int pageNum = 0) { string? posterId = (await _context.Users.SingleOrDefaultAsync(u => u.UserName == posterName))?.Id ?? null; - if (posterId == null) return Array.Empty(); - var posts = _context.UserPosts(posterId, readerId).ToList(); - var viewerId = string.Equals(readerId, posterId, StringComparison.Ordinal) ? readerId : null; - foreach (var post in posts) - ScrubAclForViewer(post, viewerId); - return posts; + if (posterId == null) return Array.Empty(); + return _context.UserPosts(posterId, readerId); } public object? GetTitle(string title) @@ -270,7 +259,7 @@ public class OldBlogSpotService ).ToList(); } - public async Task GetBlogPostAsync(long value) + public async Task GetBlogPostAsync(long value) { return await _context.BlogSpot .Include(b => b.Author) @@ -278,39 +267,4 @@ public class OldBlogSpotService .SingleOrDefaultAsync(x => x.Id == value); } - private static void ScrubAclForViewer(Yavsc.Models.Blog.BlogPost post, ClaimsPrincipal? user) - { - if (!IsOwner(post, user)) - post.ACL = new List(); - } - - private static void ScrubAclForViewer(Yavsc.Models.Blog.BlogPost post, string? viewerId) - { - if (!string.Equals(post.AuthorId, viewerId, StringComparison.Ordinal) - && !string.Equals(post.Author?.Id, viewerId, StringComparison.Ordinal)) - post.ACL = new List(); - } - - private static bool IsOwner(Yavsc.Models.Blog.BlogPost post, ClaimsPrincipal? user) - { - if (user?.Identity?.IsAuthenticated != true) return false; - - var viewerId = user.GetUserId(); - var viewerName = user.GetUserName() ?? user.Identity?.Name; - - if (!string.IsNullOrWhiteSpace(viewerId)) - { - if (string.Equals(post.AuthorId, viewerId, StringComparison.Ordinal)) return true; - if (string.Equals(post.Author?.Id, viewerId, StringComparison.Ordinal)) return true; - } - - if (!string.IsNullOrWhiteSpace(viewerName)) - { - if (string.Equals(post.AuthorId, viewerName, StringComparison.OrdinalIgnoreCase)) return true; - if (string.Equals(post.Author?.UserName, viewerName, StringComparison.OrdinalIgnoreCase)) return true; - } - - return false; - } - } diff --git a/src/Yavsc.Server/Services/HubConnectionManager.cs b/src/Yavsc.Org/Services/ChatHubConnexionManager.cs similarity index 82% rename from src/Yavsc.Server/Services/HubConnectionManager.cs rename to src/Yavsc.Org/Services/ChatHubConnexionManager.cs index 05ed49dda..43365366f 100644 --- a/src/Yavsc.Server/Services/HubConnectionManager.cs +++ b/src/Yavsc.Org/Services/ChatHubConnexionManager.cs @@ -1,4 +1,10 @@ + +using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Input; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; @@ -121,10 +127,10 @@ namespace Yavsc.Services public bool Part(string cxId, string roomName, string reason) { - ChatRoomInfo channelInfo; - if (Channels.TryGetValue(roomName, out channelInfo)) + ChatRoomInfo chanInfo; + if (Channels.TryGetValue(roomName, out chanInfo)) { - if (!channelInfo.Users.Contains(cxId)) + if (!chanInfo.Users.Contains(cxId)) { // TODO NotifyErrorToCaller(roomName, "you didn't join."); return false; @@ -132,11 +138,11 @@ namespace Yavsc.Services // FIXME only remove cx, not username, // as long as he might be connected // from another device, to the same room - channelInfo.Users.Remove(cxId); - if (channelInfo.Users.Count == 0) + chanInfo.Users.Remove(cxId); + if (chanInfo.Users.Count == 0) { - ChatRoomInfo deadChannelInfo; - if (Channels.TryRemove(roomName, out deadChannelInfo)) + ChatRoomInfo deadchanInfo; + if (Channels.TryRemove(roomName, out deadchanInfo)) { var room = _dbContext.ChatRoom.FirstOrDefault(r => r.Name == roomName); room.LatestJoinPart = DateTime.UtcNow; @@ -157,67 +163,67 @@ namespace Yavsc.Services var userName = ChatUserNames[cxId]; _logger.LogInformation($"Join: {userName}=>{roomName}"); - ChatRoomInfo channelInfo; + ChatRoomInfo chanInfo; // if channel already is open if (Channels.ContainsKey(roomName)) { - if (Channels.TryGetValue(roomName, out channelInfo)) + if (Channels.TryGetValue(roomName, out chanInfo)) { if (IsPresent(roomName, userName)) { // TODO implement some unique connection sharing protocol // between all terminals from a single user. - return channelInfo; + return chanInfo; } else { if (IsCop(userName)) { - channelInfo.Ops.Add(cxId); + chanInfo.Ops.Add(cxId); } else{ - channelInfo.Users.Add(cxId); + chanInfo.Users.Add(cxId); } _logger.LogInformation($"existing room joint: {userName}=>{roomName}"); if (!ChatRoomPresence[userName].Contains(roomName)) ChatRoomPresence[userName].Add(roomName); - return channelInfo; + return chanInfo; } } else { - string msg = "room seemed to be available ... but we could get no info on it."; + string msg = "room seemd to be avaible ... but we could get no info on it."; _errorHandler(roomName, msg); return null; } } // room was closed. var room = _dbContext.ChatRoom.FirstOrDefault(r => r.Name == roomName); - channelInfo = new ChatRoomInfo(); + chanInfo = new ChatRoomInfo(); if (room != null) { - channelInfo.Topic = room.Topic; - channelInfo.Name = room.Name; - channelInfo.Users.Add(cxId); + chanInfo.Topic = room.Topic; + chanInfo.Name = room.Name; + chanInfo.Users.Add(cxId); } else { // a first join, we create it. - channelInfo.Name = roomName; - channelInfo.Topic = _localizer.GetString(ChatHubConstants.JustCreatedBy)+userName; - channelInfo.Ops.Add(cxId); + chanInfo.Name = roomName; + chanInfo.Topic = _localizer.GetString(ChatHubConstants.JustCreatedBy)+userName; + chanInfo.Ops.Add(cxId); } - if (Channels.TryAdd(roomName, channelInfo)) + if (Channels.TryAdd(roomName, chanInfo)) { ChatRoomPresence[userName].Add(roomName); _logger.LogInformation("new room joint"); - return (channelInfo); + return (chanInfo); } else { - string msg = "Chan create failed unexpectedly..."; + string msg = "Chan create failed unexpectly..."; _errorHandler(roomName, msg); return null; } @@ -228,7 +234,7 @@ namespace Yavsc.Services throw new System.NotImplementedException(); } - public bool DeOp(string roomName, string userName) + public bool Deop(string roomName, string userName) { throw new System.NotImplementedException(); } @@ -248,9 +254,9 @@ namespace Yavsc.Services return ChatUserNames[cxId]; } - public bool TryGetChanInfo(string room, out ChatRoomInfo channelInfo) + public bool TryGetChanInfo(string room, out ChatRoomInfo chanInfo) { - return Channels.TryGetValue(room, out channelInfo); + return Channels.TryGetValue(room, out chanInfo); } public IEnumerable ListChannels(string pattern) @@ -279,22 +285,22 @@ namespace Yavsc.Services public bool Kick(string cxId, string userName, string roomName, string reason) { - ChatRoomInfo channelInfo; + ChatRoomInfo chanInfo; if (!Channels.ContainsKey(roomName)) { _errorHandler(roomName, _localizer.GetString(ChatHubConstants.LabNoSuchChan).ToString()); return false; } - if (!Channels.TryGetValue(roomName, out channelInfo)) + if (!Channels.TryGetValue(roomName, out chanInfo)) { _errorHandler(roomName, _localizer.GetString(ChatHubConstants.LabNoSuchChan).ToString()); return false; } var kickerName = GetUserName(cxId); - if (!channelInfo.Ops.Contains(cxId)) - if (!channelInfo.Hops.Contains(cxId)) + if (!chanInfo.Ops.Contains(cxId)) + if (!chanInfo.Hops.Contains(cxId)) { _errorHandler(roomName, _localizer.GetString(ChatHubConstants.LabYouNotOp).ToString()); return false; @@ -305,9 +311,9 @@ namespace Yavsc.Services _errorHandler(roomName, _localizer.GetString(ChatHubConstants.LabNoSuchUser).ToString()); return false; } - var userConnectionIds = GetConnexionIds(userName); - if (channelInfo.Hops.Contains(cxId)) - if (channelInfo.Ops.Any(c => userConnectionIds.Contains(c))) + var ucxs = GetConnexionIds(userName); + if (chanInfo.Hops.Contains(cxId)) + if (chanInfo.Ops.Any(c => ucxs.Contains(c))) { _errorHandler(roomName, _localizer.GetString(ChatHubConstants.HopWontKickOp).ToString()); return false; @@ -319,15 +325,15 @@ namespace Yavsc.Services } // all good, time to kick :-) - foreach (var ucx in userConnectionIds) { - if (channelInfo.Users.Contains(ucx)) - channelInfo.Users.Remove(ucx); + foreach (var ucx in ucxs) { + if (chanInfo.Users.Contains(ucx)) + chanInfo.Users.Remove(ucx); - else if (channelInfo.Ops.Contains(ucx)) - channelInfo.Ops.Remove(ucx); + else if (chanInfo.Ops.Contains(ucx)) + chanInfo.Ops.Remove(ucx); - else if (channelInfo.Hops.Contains(ucx)) - channelInfo.Hops.Remove(ucx); + else if (chanInfo.Hops.Contains(ucx)) + chanInfo.Hops.Remove(ucx); } return true; diff --git a/src/Yavsc.Server/Services/YavscMessageSender.cs b/src/Yavsc.Org/Services/YavscMessageSender.cs similarity index 99% rename from src/Yavsc.Server/Services/YavscMessageSender.cs rename to src/Yavsc.Org/Services/YavscMessageSender.cs index 3ab43fbee..7c7f6faa7 100644 --- a/src/Yavsc.Server/Services/YavscMessageSender.cs +++ b/src/Yavsc.Org/Services/YavscMessageSender.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.SignalR; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Newtonsoft.Json; using Yavsc.Interface; diff --git a/src/Yavsc.Org/Services/YavscTemplateEngine.cs b/src/Yavsc.Org/Services/YavscTemplateEngine.cs index 904f203af..9b29d853d 100644 --- a/src/Yavsc.Org/Services/YavscTemplateEngine.cs +++ b/src/Yavsc.Org/Services/YavscTemplateEngine.cs @@ -10,9 +10,15 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Yavsc.Models; +using Yavsc.Services; +using System.Reflection; using Yavsc.Abstract.Templates; using Microsoft.AspNetCore.Identity; +using RazorEngine.Configuration; +using Yavsc.Interface; +using Microsoft.Extensions.Logging; using System.Diagnostics; +using RazorEngine.Compilation.ImpromptuInterface.Optimization; using RazorEngine.Compilation.ImpromptuInterface; namespace Yavsc.Lib @@ -24,7 +30,7 @@ namespace Yavsc.Lib "Yavsc.Templates" , "Yavsc.Models", "Yavsc.Models.Identity"}; - + readonly IStringLocalizer stringLocalizer; readonly ApplicationDbContext dbContext; @@ -138,14 +144,14 @@ namespace Yavsc.Lib var template = result.CallActLike(user); return template.GeneratedText; } - + /* result.CallActLike<> inMemoryAssembly.Seek(0, SeekOrigin.Begin); Assembly assembly = Assembly.Load(inMemoryAssembly.ToArray()); // UserOrientedTemplate userOrientedTemplate = (UserOrientedTemplate) // FIXME Activator.CreateInstance(Type.GetType(templateInfo.TemplateType)); - + foreach (var user in dbContext.ApplicationUser.Where( u => u.AllowMonthlyEmail )) @@ -154,7 +160,7 @@ namespace Yavsc.Lib userOrientedTemplate.Init(); userOrientedTemplate.User = user; */ throw new NotImplementedException(); - + } } } diff --git a/src/Yavsc.Org/ViewComponents/CalendarViewComponent.cs b/src/Yavsc.Org/ViewComponents/CalendarViewComponent.cs index 9b0fd6a09..ed8b7acde 100644 --- a/src/Yavsc.Org/ViewComponents/CalendarViewComponent.cs +++ b/src/Yavsc.Org/ViewComponents/CalendarViewComponent.cs @@ -1,4 +1,7 @@ +using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Yavsc.Models; using Yavsc.Services; namespace Yavsc.ViewComponents diff --git a/src/Yavsc.Org/ViewComponents/CirclesControlViewComponent.cs b/src/Yavsc.Org/ViewComponents/CirclesControlViewComponent.cs index 58aab3b86..c7af7c86a 100644 --- a/src/Yavsc.Org/ViewComponents/CirclesControlViewComponent.cs +++ b/src/Yavsc.Org/ViewComponents/CirclesControlViewComponent.cs @@ -1,3 +1,4 @@ +using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Rendering; diff --git a/src/Yavsc.Org/ViewComponents/CommentViewComponent.cs b/src/Yavsc.Org/ViewComponents/CommentViewComponent.cs index 6f85a953f..62d3bb7a5 100644 --- a/src/Yavsc.Org/ViewComponents/CommentViewComponent.cs +++ b/src/Yavsc.Org/ViewComponents/CommentViewComponent.cs @@ -1,7 +1,9 @@ +using System.Diagnostics; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; using Yavsc.Models; +using Yavsc.Models.Blog; namespace Yavsc.ViewComponents { diff --git a/src/Yavsc.Org/ViewComponents/DirectoryViewComponent.cs b/src/Yavsc.Org/ViewComponents/DirectoryViewComponent.cs index fea78afb0..af7f436b1 100644 --- a/src/Yavsc.Org/ViewComponents/DirectoryViewComponent.cs +++ b/src/Yavsc.Org/ViewComponents/DirectoryViewComponent.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Yavsc.Helpers; using Yavsc.Models; using Yavsc.Server.Helpers; using Yavsc.ViewModels.UserFiles; diff --git a/src/Yavsc.Org/ViewComponents/TaggerComponent.cs b/src/Yavsc.Org/ViewComponents/TaggerComponent.cs index d9f26e607..946552371 100644 --- a/src/Yavsc.Org/ViewComponents/TaggerComponent.cs +++ b/src/Yavsc.Org/ViewComponents/TaggerComponent.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; using Yavsc.Interfaces; +using Yavsc.Models; namespace Yavsc.ViewComponents { diff --git a/src/Yavsc.Org/ViewModels/Account/SendCodeViewModel.cs b/src/Yavsc.Org/ViewModels/Account/SendCodeViewModel.cs index ea4dfba5f..a10bda89c 100644 --- a/src/Yavsc.Org/ViewModels/Account/SendCodeViewModel.cs +++ b/src/Yavsc.Org/ViewModels/Account/SendCodeViewModel.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Rendering; namespace Yavsc.ViewModels.Account diff --git a/src/Yavsc.Org/ViewModels/Administration/EnrolerViewModel.cs b/src/Yavsc.Org/ViewModels/Administration/EnrolerViewModel.cs index 471a080ef..1963e357b 100644 --- a/src/Yavsc.Org/ViewModels/Administration/EnrolerViewModel.cs +++ b/src/Yavsc.Org/ViewModels/Administration/EnrolerViewModel.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Yavsc.Attributes.Validation; namespace Yavsc.ViewModels { diff --git a/src/Yavsc.Org/ViewModels/Administration/FireViewModel.cs b/src/Yavsc.Org/ViewModels/Administration/FireViewModel.cs index 53b63badf..522301374 100644 --- a/src/Yavsc.Org/ViewModels/Administration/FireViewModel.cs +++ b/src/Yavsc.Org/ViewModels/Administration/FireViewModel.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Yavsc.Attributes.Validation; namespace Yavsc.ViewModels { diff --git a/src/Yavsc.Org/ViewModels/Gen/PdfGenerationViewModel.cs b/src/Yavsc.Org/ViewModels/Gen/PdfGenerationViewModel.cs index fe934e3ac..cbc99b0a3 100644 --- a/src/Yavsc.Org/ViewModels/Gen/PdfGenerationViewModel.cs +++ b/src/Yavsc.Org/ViewModels/Gen/PdfGenerationViewModel.cs @@ -1,5 +1,7 @@ using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; +using Yavsc.Attributes.Validation; namespace Yavsc.ViewModels.Gen { diff --git a/src/Yavsc.Org/ViewModels/Manage/ConfigureTwoFactorViewModel.cs b/src/Yavsc.Org/ViewModels/Manage/ConfigureTwoFactorViewModel.cs index 57212785d..685d11be7 100644 --- a/src/Yavsc.Org/ViewModels/Manage/ConfigureTwoFactorViewModel.cs +++ b/src/Yavsc.Org/ViewModels/Manage/ConfigureTwoFactorViewModel.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Rendering; namespace Yavsc.ViewModels.Manage diff --git a/src/Yavsc.Org/ViewModels/Manage/IndexViewModel.cs b/src/Yavsc.Org/ViewModels/Manage/IndexViewModel.cs index 03cc42d80..b54bebccd 100644 --- a/src/Yavsc.Org/ViewModels/Manage/IndexViewModel.cs +++ b/src/Yavsc.Org/ViewModels/Manage/IndexViewModel.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Microsoft.AspNetCore.Identity; namespace Yavsc.ViewModels.Manage diff --git a/src/Yavsc.Org/ViewModels/Manage/ManageLoginsViewModel.cs b/src/Yavsc.Org/ViewModels/Manage/ManageLoginsViewModel.cs index 2b77adcdb..d29107c39 100644 --- a/src/Yavsc.Org/ViewModels/Manage/ManageLoginsViewModel.cs +++ b/src/Yavsc.Org/ViewModels/Manage/ManageLoginsViewModel.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Microsoft.AspNetCore.Identity; namespace Yavsc.ViewModels.Manage diff --git a/src/Yavsc.Org/ViewModels/Manage/SetUserNameViewModel.cs b/src/Yavsc.Org/ViewModels/Manage/SetUserNameViewModel.cs index 9cef16032..69507b92f 100644 --- a/src/Yavsc.Org/ViewModels/Manage/SetUserNameViewModel.cs +++ b/src/Yavsc.Org/ViewModels/Manage/SetUserNameViewModel.cs @@ -1,12 +1,13 @@ using System.ComponentModel.DataAnnotations; +using Yavsc.Attributes.Validation; namespace Yavsc.ViewModels.Manage { public class SetUserNameViewModel { [Required] - [Display(Name = "User name"),RegularExpression(Constants.UserNameRegExp)] + [Display(Name = "User name"),RegularExpression(YavscConstants.UserNameRegExp)] public string UserName { get; set; } } diff --git a/src/Yavsc.Org/Views/Blogspot/Index.cshtml b/src/Yavsc.Org/Views/Blogspot/Index.cshtml index 1582041cf..52cf3b88a 100644 --- a/src/Yavsc.Org/Views/Blogspot/Index.cshtml +++ b/src/Yavsc.Org/Views/Blogspot/Index.cshtml @@ -70,7 +70,7 @@
@if ((await AuthorizationService.AuthorizeAsync(User, post, new ReadPermission())).Succeeded) { - Details + Details } else { diff --git a/src/Yavsc.Org/Views/Home/About.pt.cshtml b/src/Yavsc.Org/Views/Home/About.pt.cshtml index 487a1ab36..cca60a94d 100755 --- a/src/Yavsc.Org/Views/Home/About.pt.cshtml +++ b/src/Yavsc.Org/Views/Home/About.pt.cshtml @@ -93,8 +93,8 @@ A operação é anulável até duas semanas após a sua programação. Este é o meu site perso, uma configuração de _Yavsc_ (outro negócio muito pequeno). -* [README](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/README.md) -* [licença: GNU GPL v3](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/LICENSE) +* [README](https://github.com/pazof/yavsc/blob/vnext/README.md) +* [licença: GNU GPL v3](https://github.com/pazof/yavsc/blob/vnext/LICENSE) Outras instalações: @@ -109,8 +109,8 @@ Outras instalações: Yet Another Very Small Company ... -* [README](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/README.md) -* [license: GNU FPL v3](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/LICENSE) +* [README](https://github.com/pazof/yavsc/blob/vnext/README.md) +* [license: GNU FPL v3](https://github.com/pazof/yavsc/blob/vnext/LICENSE) @@ -118,8 +118,8 @@ Outras instalações: ## Yet Another Very Small Company : -* [README](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/README.md) -* [license: GNU FPL v3](https://forgejo.pschneider.fr/notazof/yavsc/blob/vnext/LICENSE) +* [README](https://github.com/pazof/yavsc/blob/vnext/README.md) +* [license: GNU FPL v3](https://github.com/pazof/yavsc/blob/vnext/LICENSE) En production: diff --git a/src/Yavsc.Org/Views/Manage/Index.cshtml b/src/Yavsc.Org/Views/Manage/Index.cshtml index 13b45339d..2624c2843 100755 --- a/src/Yavsc.Org/Views/Manage/Index.cshtml +++ b/src/Yavsc.Org/Views/Manage/Index.cshtml @@ -2,9 +2,6 @@ @using System.Security.Claims @{ ViewBag.Title = "Manage your account"; - var avatarSrc = string.IsNullOrWhiteSpace(Model.UserName) - ? Yavsc.Constants.DefaultAvatar - : $"{Yavsc.Constants.AvatarsPath}/{Model.UserName}.s.png"; }

@ViewBag.Title

@@ -14,15 +11,15 @@
UserName:
- -
+ +
@Model.UserName [modifier]
E-mail
- -
+ +
@Model.EMail @if (Model.EmailConfirmed) { @@ -49,22 +46,22 @@
FullName:
-
- @Html.DisplayFor(m=>m.FullName) +
+ @Html.DisplayFor(m=>m.FullName) [modifier]
@if (Model.Roles.Count()>0) {
Roles:
-
+
@string.Join(", ",Model.Roles)
}
Password:
[@{if (Model.HasPassword) - {Change} else - {Create}}]
External Logins:
@@ -79,19 +76,19 @@ { Html.DisplayText("Set"); } - else + else { Html.DisplayText("Modify"); } } ] - +
Avatar:
- + [Modify] -
+
Vos cercles
(WIP) Ajouter suprimer des cercles @@ -118,8 +115,8 @@ Html.DisplayText("Modify");
Your posts:
@Model.PostsCounter
- -
TwoFactorAuthentication:
+ +
TwoFactorAuthentication:
@if (Model.TwoFactor) { @@ -142,19 +139,19 @@ Html.DisplayText("Modify"); } }
-
Calendar
-
+
Calendar
+
@Html.DisplayText(Model.HasDedicatedCalendar?"Yes":"No" ) @{ - if (Model.HasDedicatedCalendar) { + if (Model.HasDedicatedCalendar) { : @Model.DedicatedCalendarId } } [Select a Google calendar]
-
Credits:
-
+
Credits:
+
@(Model.Balance?.Credits ?? 0) € [Manage]
@@ -165,9 +162,9 @@ Html.DisplayText("Modify"); @if (Model.DiskQuota>0) { - @(((double)Model.DiskUsage/Model.DiskQuota).ToString("%#0")) : + @(((double)Model.DiskUsage/Model.DiskQuota).ToString("%#0")) : - } + } @(Model.DiskUsage.ToString("0,#")) / @(Model.DiskQuota.ToString("0,#")) diff --git a/src/Yavsc.Org/Views/Manage/SetActivity.cshtml b/src/Yavsc.Org/Views/Manage/SetActivity.cshtml index 198361100..486903597 100644 --- a/src/Yavsc.Org/Views/Manage/SetActivity.cshtml +++ b/src/Yavsc.Org/Views/Manage/SetActivity.cshtml @@ -1,5 +1,4 @@ @model PerformerProfile -@using System.Text.Json @{ ViewBag.Title = "Your performer profile"; } @section header {