diff --git a/.env.sample b/.env.sample index e94748a2e..65926e57f 100644 --- a/.env.sample +++ b/.env.sample @@ -15,7 +15,7 @@ POSTGRES_HOST=localhost POSTGRES_PORT=5432 POSTGRES_DB=db POSTGRES_USER=pguser -POSTGRES_PASSWORD= +POSTGRES_PASSWORD=pgpassword ANTHROPIC_API_KEY=none ANTHROPIC_MAX_TOKENS=0 diff --git a/.forgejo/workflows/buildAndTest.yml b/.forgejo/workflows/buildAndTest.yml index e1e936204..120ccf259 100644 --- a/.forgejo/workflows/buildAndTest.yml +++ b/.forgejo/workflows/buildAndTest.yml @@ -49,29 +49,9 @@ jobs: run: | echo "🏗️ Building the solution..." cd /src/_src && dotnet build --verbosity normal - - name: install dependencies - run: | - echo "📦 Installing dependencies..." - apt install -y texlive-binaries texlive-luatex texlive-pictures \ - texlive-fonts-extra texlive-lang-french texlive-lang-english \ - texlive-lang-portuguese texlive-lang-spanish \ - texlive-latex-recommended texlive-latex-extra - name: Test - artifacts: - name: test-results - path: test-results.html - run: | echo "🚀 Lancement des tests..." cd /src/_src && dotnet test \ - --report-xunit \ - --report-xunit-html \ - --report-xunit-html-filename "test-results.html" - TEST_EXIT_CODE=$? - echo "Test exit code: $TEST_EXIT_CODE" - if [ $TEST_EXIT_CODE -ne 0 ]; then - echo "❌ Tests failed with exit code $TEST_EXIT_CODE" - exit $TEST_EXIT_CODE - else - echo "✅ All tests passed successfully." - fi + --verbosity normal \ + --filter="Category!=Platform-Android" diff --git a/.gitignore b/.gitignore index 32d9c064d..056cf69d2 100644 --- a/.gitignore +++ b/.gitignore @@ -43,8 +43,3 @@ DataDir/ *.log -# gitleaks scan output (local secret-scan report, not for the repo) -/gitleaks-report.json - -# Exclude test reports (local test report, not for the repo) -/test-reports diff --git a/.vscode/launch.json b/.vscode/launch.json index 87cf40dbb..d5088f01d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,9 +22,11 @@ }, { "name": "Android Debug", - "type": "maui", - "request": "launch", - "project": "${workspaceFolder}/src/PostIt/PostIt.Android/PostIt.Android.csproj" + "type": "mono", + "preLaunchTask": "run-debug-android", + "request": "attach", + "address": "localhost", + "port": 55555 }, { "name": "Android Attach - Debug", @@ -41,15 +43,9 @@ }, { "name": "Yavsc Org", - "type": "coreclr", + "type": "dotnet", "request": "launch", - "program": "${workspaceFolder}/src/Yavsc.Org/bin/Debug/net10.0/Yavsc.Org.dll", - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "cwd": "${workspaceFolder}/src/Yavsc.Org", - "preLaunchTask": "build-web-yavsc-org" - + "projectPath": "${workspaceFolder}/src/Yavsc.Org/Yavsc.Org.csproj", }, { "name": "Yavsc Blogs", diff --git a/.vscode/settings.json b/.vscode/settings.json index e6f2aee1d..83a17ae38 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -45,10 +45,5 @@ ] } }, - "dotnet.defaultSolution": "yavsc.sln", - "axaml.selectedSolution": "/home/paul/Workspace/yavsc/yavsc.sln", - "chat.tools.terminal.autoApprove": { - "dotnet build": true, - "dotnet test": true - } + "dotnet.defaultSolution": "yavsc.sln" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 0b0bb63a5..71384cd8c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,17 +1,27 @@ { "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": "build-debug-android", + "label": "run-debug-android", "command": "dotnet", - "detail": "Build the PostIt Android project in debug mode", "type": "shell", - "runOptions": { - - }, - "problemMatcher": ["$msCompile"], "options": { "cwd": "${workspaceFolder}/src/PostIt/PostIt.Android", "env": { @@ -33,7 +43,6 @@ "label": "build", "command": "dotnet", "type": "process", - "problemMatcher": ["$msCompile"], "args": ["build", "/property:GenerateFullPaths=true"], "group": "build", "isBuildCommand": true, @@ -93,34 +102,6 @@ }, "isBackground": true }, - { - "label": "build-web-yavsc-org", - "type": "process", - "problemMatcher": ["$msCompile"], - "command": "dotnet", - "args": ["build"], - "options": { - "cwd": "src/Yavsc.Org" - }, - "group": { - "kind": "build" - }, - "isBackground": true - }, - { - "label": "build-web-yavsc-blogs", - "type": "process", - "problemMatcher": ["$msCompile"], - "command": "dotnet", - "args": ["build"], - "options": { - "cwd": "src/Yavsc.Blogs" - }, - "group": { - "kind": "build" - }, - "isBackground": true - }, { "label": "dotnet: build-postit-desktop", "type": "process", @@ -128,7 +109,6 @@ "isTestCommand": false, "isBackground": true, "command": "dotnet", - "problemMatcher": ["$msCompile"], "args": ["build", "/property:GenerateFullPaths=true"], "options": { "cwd": "src/PostIt/PostIt.Desktop" diff --git a/CHANGELOG.md b/CHANGELOG.md index e36d75dd3..3f8253c70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,251 +1,5 @@ # Changelog - -## [1.0.8-rc19] - Blogs UX - -### Fixed - -La saisie du billet est maintenant plutôt possible, -en petit écran. - -## [1.0.8-rc18] - unstable - -* [PostIt] Page "About" avec lines vers le dépôt et les issues Forgejo - -* Accès aux fichiers personnels PostIt rendu fonctionnel de bout en bout -(arbre de stockage indexé par le login, pas par le `sub`), boutons -d'action des listes réparés (binding `$parent`), revendication `name` -enfin présente dans les access tokens (branchement du `ProfileService`), -et négociation HTTP/3 côté client PostIt. - - -### Added - -* [PostIt] Négociation HTTP/3 (QUIC) pour les appels API Yavsc : - `DefaultRequestVersion = 3.0`, `DefaultVersionPolicy = - RequestVersionOrLower` sur le `HttpClient` partagé de `YavscApiClient`, - avec repli h2/h1.1 où QUIC n'est pas disponible (WASM fetch, Android/ - desktop sans libmsquic). -* [Tests] `MyFilesPageHeadlessTests` : monte `MyFilesPage` via - `NavigationPage` + `Window.Show()` et vérifie que chaque bouton de - ligne (Télécharger / Supprimer) a sa `Command` liée, est armé - (`CanExecute`) et déclenche l'appel API au clic. -* [Tests] `Remoting.PasswordToken_carries_name_claim_equal_to_login` : - mint un vrai JWT via le token endpoint IdentityServer (password grant, - `openid profile`) et asserte que l'access token porte `name`=login et - que `GetUserName()` résout vers ce login. -* [Tests] `FileSystemApiTests.Get_fs_lists_files_stored_under_the_user_login` : - découple `sub` et login, téléverse sous le login, et asserte que - `GET /api/v1/fs` retourne le fichier (et que le répertoire nommé d'après - le `sub` n'existe pas). - -### Changed - -* [Yavsc.Org] Branchement du `ProfileService` custom - (`AddProfileService()` après - `AddAspNetIdentity()`). La classe était du code mort : - IdentityServer8 retombait sur son profile service par défaut, qui - n'émet que les claims listés dans les `UserClaims` des API resources. - `name` n'en faisait jamais partie, donc tout access token était sans - `name` et `GetUserName()` résolvait à `null` sur les hosts ressources - (Blogs/Api) — cause du 500 sur l'upload MyFiles et du - `User.Identity.Name` injoignable. Le service émet désormais - `name`=`user.UserName`, `email` et les rôles pour tout client dont - les `AllowedScopes` contiennent `profile`, indépendamment du caller. -* [Yavsc.Server] `ServiceExtensions` : `NameClaimType = - JwtClaimTypes.Name`, `MapInboundClaims = false` (conserver les noms - courts de claims tels qu'émis). -* [Yavsc.Blogs] `FileSystemHelpers.EnsureDestinationDirectory` utilise - `GetUserName()` (claim `name`) au lieu de `User.Identity.Name` pour - la racine de stockage par utilisateur. -* [Tests] `WebServerFixture` : `AccessTokenType = 0` (Jwt dans IS8 ; - 1 = Reference, l'inverse de Duende) ; `openid`+`profile` sur le client - de test ; réensemencement des identity resources openid/profile dans - `ReseedAuthTestData` (un `ResetAndMigrateDatabase` vide le store - in-memory partagé et les laissait absentes → `invalid_scope`). -* [Tests] `TestTokenIssuer.Issue` gagne un paramètre `name` optionnel - (défaut = subject) pour refléter la forme production où - `sub` (GUID) ≠ `name` (login). -* [Tests] `BillingServiceTests` : `[Collection("Yavsc Server")]` pour - sérialiser avec le build d'hôte qui mute les registres statiques - `BillingService`/`Config` (race parallèle pré-existante). - -### Fixed - -* [Yavsc.Blogs] La liste `GET /api/v1/fs` cherchait sous - `{Blog}/{sub-GUID}` (`User.GetUserId()`), alors que l'arbre de - stockage perso est indexé par le login partout ailleurs - (`EnsureDestinationDirectory` écrit sous `{Blog}/{name-claim}`, - le endpoint de téléchargement et les avatars utilisent - `user.UserName`). Une fois l'upload corrigé, les fichiers - atterrissaient sous `{Blog}/{login}` tandis que la liste cherchait - sous `{Blog}/{GUID}` — un arbre vide — d'où la page PostIt - « Mes fichiers » qui ne listait rien. `GetDir` résout désormais - l'utilisateur en base et liste sous `user.UserName`. - `UploadedFile.OwnerId` reste `sub`, donc `EnrichWithFileIds` - interpole toujours par `uid`. -* [PostIt] Boutons d'action des lignes de liste grisés et inactifs au - clic (Télécharger / Supprimer / Joindre sur MyFiles ; download/detach - sur les listes de pièces jointes de `BillingQueryDetailsPage` et - `EstimateEditionPage`). Le binding `$parent[vm:XxxViewModel].Cmd` - cherchait un ancêtre CONTRÔLE de type `XxxViewModel` — un viewmodel, - pas un contrôle — donc résolvait à `null` et le `Button` restait - désactivé (grisé) et inerte au clic. Corrigé en - `$parent[ContentPage].((vm:XxxViewModel)DataContext).Cmd` (syntaxe - déjà en vigueur dans `CirclesPage` / `EstimateListPage`). -* [PostIt] Les messages de la barre de statut (`StatusBar`) n'étaient - pas sélectionnables/copiables : le texte était un `TextBlock` (non - sélectionnable dans Avalonia). Remplacé par `SelectableTextBlock`. -* [Yavsc.Blogs] La suppression d'un fichier (`DELETE api/v1/fs/{name}`) - renvoyait un 40x alors que le fichier était bien supprimé du disque. - `DeleteUserDirOrFile` (et `DeleteUserFile`) lisaient - `FileInfo.Length` **après** `fi.Delete()` ; or `FileInfo.Length` - lève `FileNotFoundException` une fois le fichier disparu — le `catch` - du contrôleur transformait cette exception en 400, d'où le message - perçu « le fichier n'existe pas sur le serveur » tandis que la - suppression avait réussi. La taille est désormais capturée avant la - suppression. -* PostIt default settings - -## [1.0.8-rc17] - unstable - -Fichiers utilisateur et pièces jointes sur les devis/demandes (PostIt + -API), système de génération `contrib` (vhosts nginx HTTP/3 edge + -appsettings jq + QUIC logiciel Kestrel), et correction du bug de -production où `IsPublished` disparaissait du payload de liste du blog API. - -### Added - -* [PostIt] Espace de stockage personnel (« MyFiles ») : page de gestion - des fichiers de l'utilisateur, entrée depuis la page d'accueil. -* [PostIt] Pièces jointes aux devis et aux demandes : attachement depuis - le détail de demande et la page d'édition de devis. -* [Yavsc.Api] Endpoints d'attachement de fichiers sur les devis - (`EstimateApiController`) et les demandes front-office - (`FrontOfficeApiController`). -* [Yavsc.Api.Client] `UserFilesApiClient` + DTOs associés ; méthodes - d'attachement sur `EstimateApiClient` et `FrontOfficeApiClient`. -* [Yavsc.Server] Modèles `EstimateAttachedFile`, `QueryAttachedFile`, - `BlogAttachedFile`, `AttachFileRequest` ; migration EF Core - `UserFileAttachments`. -* [contrib] Système de génération `Makefile` : vhosts nginx HTTP/3 edge - (`template.vhost`, `QUIC_OPTS`), appsettings générés via jq - (`template.appsettings-{org,api,blogs}.json`, échappement JSON-safe des - secrets), QUIC logiciel Kestrel (`Http1AndHttp2AndHttp3`), et - `README.nginx.md`. -* [Tests] Projet `Yavsc.Server.Tests` : tests du service - `BlogSpotService.Details` + `ServerServicesFixture` (SQLite `:memory:` - par défaut, Npgsql opt-in via `YAVSC_SERVER_TEST_DB_PROVIDER`). -* [Tests] `IBlogPostWireShapeTests` (forme du wire blog), - `EstimateAttachmentsApiTests`, `FrontOfficeQueryAttachmentsApiTests`, - `FileSystemApiTests` (pièces jointes et stockage perso). -* [CI] Intégration Gitleaks / analyse de secrets (`.pre-commit-config.yaml`). - -### Changed - -* [contrib] `QUIC_OPTS` (`reuseport`) désormais activé uniquement en - production sur le vhost Org. La preprod et la prod partageant la même - IP, `reuseport` — légal une seule fois par `addr:port` (443/udp) — - serait sinon en conflit entre preprod-Org et prod-Org et ferait refuser - la config par nginx. -* [Yavsc.Api.Test] `ApiWebServerFixture` : la connexion admin PostgreSQL - provient de la variable d'environnement - `YAVSC_API_TEST_NPGSQL_ADMIN_CONNECTION` ; plus de mot de passe en - source. - -### Fixed - -* [Yavsc.Blogs] Bug de production : `IsPublished` était absent du payload - de la liste du blog API. Le endpoint `GetBlogspot` retourne - `IEnumerable` sérialisé par System.Text.Json d'après le type - déclaré (l'interface), qui omet les propriétés du type concret — - `IsPublished` vivait sur `BlogPost` / `BlogPostDto` mais pas sur - `IBlogPost`. PostIt lisait donc `IsPublished == false` pour tout billet, - y compris publiés (https://yavsc.pschneider.fr/BlogSpot/Details/13). - Corrigé en déclarant `IsPublished` sur `IBlogPost`. -* [Yavsc.Org] Accès au Swagger UI : les actions MVC scaffoldées de - `CommentsController` (Index/Details/…) atterrissaient sur la même route - `POST api/v1/blogcomments` que l'action d'API `Post`, provoquant une - `SwaggerGeneratorException` (conflit method/path) au chargement de - `/swagger`. Corrigé par `[ApiExplorerSettings(IgnoreApi = true)]` sur - les actions scaffoldées. - -## [1.0.8-rc16] - unstable - -Flux de signature et de génération de devis : un devis peut désormais être -signé par les deux parties (fournisseur et client) puis téléchargé en PDF -ou TeX depuis PostIt, avec les signatures dessinées en vecteur. - -### Added - -* [PostIt] Pad de signature sur la page de validation d'un devis : on valide - ou on refuse en joignant une signature (côté fournisseur ou client), capturée - au format filaire PostIt (strokes) et envoyée sur l'acceptation. -* [Yavsc.Api] Finalisation de `POST /api/v1/front/query/accept` et `.../reject` : - autorisation selon le rôle (fournisseur/client/admin), signature optionnelle - persistée sur l'estimate avec horodatage `ProviderValidationDate` / - `ClientValidationDate` selon le côté signataire. -* [Yavsc.Api] `QueryStatus.Accepted` éclaté en `ProAccepted` / `ClientAccepted` - pour distinguer l'acceptation de chaque partie. -* [Yavsc.Api] Endpoints de devis `GET /api/v1/front/query/{id}/estimate.tex` et - `estimate.pdf` (clé par id de query). Le flux de génération TeX/PDF est - rapatrié de Yavsc.Org vers Yavsc.Api (`TeXHelpers`, `PdfGenerationViewModel`, - `BillViewComponent`, modèles Bill/Estimate, `_ViewImports` ; `Program.cs` - câble `AddControllersWithViews` + `SiteSettings`). -* [Yavsc.Api] Signatures du devis dessinées en TikZ vectoriel - (`TeXHelpers.SignatureToTikz`) en bas du document, fournisseur et client - côte à côte dans deux minipages ; plus de dépendance à un PNG de signature. -* [Yavsc.Api.Client] `DownloadAsync` sur le client + `GetEstimateTexAsync` / - `GetEstimatePdfAsync` ; boutons « Télécharger PDF » / « Télécharger TeX » - dans PostIt sur la page de validation du devis et le détail de commande, - avec un helper `StorageProvider` d'enregistrement de fichier. -* [PostIt] La liste des devis se rafraîchit au retour de navigation (pop-back), - de sorte qu'un devis validé quitte immédiatement la liste du signataire. -* [PostIt] Renommage des titres de liste : « Devis en attente de signature - fournisseur » / « Devis en attente de signature client ». -* [Yavsc.Abstract] `SignatureType` déplacé dans `Yavsc.Abstract.Billing`. -* [CLI] Option `-c configuration-file.json` au lancement. -* [Tests] `EstimatePdfGenerationTests` : tests unitaires du pipeline - signature → TikZ → PDF (devis signé des deux côtés), assertant que le PDF - existe au chemin de sortie attendu et que seul le PDF persiste (aux/log - nettoyés) ; skip propre si `lualatex` est absent. -* [README] Dépendances système au runtime pour la génération PDF : - `texlive-binaries` (lualatex), `texlive-luatex` (luaotfload), - `texlive-pictures` (tikz), `texlive-fonts-extra` (bera). - -### Changed - -* [Yavsc.Api] `GetOngoingEstimatesAsProvider` filtre désormais sur - `ProviderValidationDate` : un devis signé par le fournisseur quitte sa liste - « en attente ». -* [Yavsc.Api] La génération PDF compile via le `lualatex` système, le TeX fourni - sur l'entrée standard (`/dev/stdin`), le PDF écrit directement dans le - répertoire des factures (`Site.Bills`), les artefacts `.aux`/`.log` nettoyés - afin que seul le `.pdf` persiste. - -### Fixed - -* [Yavsc.Api] `Emergency stop` à la génération PDF : `lualatex` sans argument - fichier lit stdin en mode terminal et s'arrête après la première ligne d'un - document multi-lignes. Corrigé en passant `/dev/stdin` comme nom de fichier - d'entrée — stdin est alors traité comme un fichier régulier et le document - multi-lignes compile. C'était la cause racine de l'échec en production, pas - `bera` ni `luaotfload` (problèmes réels mais séparés, comblés côté paquets). -* [Yavsc.Api] `I can't write on file 'estimate-1.log'` : le chemin relatif - `Site.Bills` (`"bills"`) était imbriqué quand il servait à la fois de - `-output-directory` et de répertoire de travail ; résolu en chemin absolu. -* [Yavsc.Api] NRE dans `RenderViewToString` (RouteData / `ViewData.Model` - nuls) : reconstruit sur le `ControllerContext` du contrôleur ; `IViewEngine` - résolu via `IRazorViewEngine` (.NET 10 n'enregistre pas de `IViewEngine` - unique). -* [Yavsc.Api] `Layout = "null"` (chaîne) dans les modèles TeX cherchait - `null.cshtml` ; corrigé en `Layout = null` (null C#). -* [Yavsc.Api] `texi2pdf` (outil Texinfo, inadapté au LaTeX) remplacé par - `lualatex`. -* [Yavsc.Api] Les diagnostics lualatex étaient perdus (écrits dans le `.log` - qu'on supprimait) ; stdout/stderr sont désormais capturés dans le message - d'erreur, et le `.log` est conservé en cas d'échec. - ## [1.0.8-rc15] - unstable ### Added @@ -615,10 +369,6 @@ the ACL now comes along with the article, the same user-visible switch without a schema change. [Unreleased]: https://forgejo.pschneider.fr/notazof/yavsc/compare/HEAD -[1.0.8-rc16]: https://forgejo.pschneider.fr/notazof/yavsc/compare/1.0.8-rc15...1.0.8-rc16 -[1.0.8-rc17]: https://forgejo.pschneider.fr/notazof/yavsc/compare/1.0.8-rc16...1.0.8-rc17 -[1.0.8-rc18]: https://forgejo.pschneider.fr/notazof/yavsc/compare/1.0.8-rc17...1.0.8-rc18 -[1.0.8-rc15]: https://forgejo.pschneider.fr/notazof/yavsc/compare/1.0.8-rc14...1.0.8-rc15 [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 diff --git a/Directory.Packages.props b/Directory.Packages.props index b0b055b98..feea66ec9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,69 +6,44 @@ - + - + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - + - + + + + - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8ebbb73f0..795b70ab3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,24 +24,41 @@ # ===================================================================== # syntax=docker/dockerfile:1.7 -ARG BUILD_ENV_TAG=debian13-dotnet10-android36-jdk21-v2 +ARG BUILD_ENV_TAG=debian12-dotnet10-android36-v1 FROM pazof/yavsc-build-env:${BUILD_ENV_TAG} AS build-env WORKDIR /src +# (1) Manifest + fichiers de projet pour le cache de restore +COPY *.sln ./ +COPY Directory.*.props ./ +COPY src/Yavsc.Org/*.csproj ./src/Yavsc.Org/ +COPY src/Yavsc.Abstract/*.csproj ./src/Yavsc.Abstract/ +COPY src/Yavsc.Server/*.csproj ./src/Yavsc.Server/ +COPY src/Yavsc.Api/*.csproj ./src/Yavsc.Api/ +COPY src/Yavsc.Blogs/*.csproj ./src/Yavsc.Blogs/ +COPY src/cli/*.csproj ./src/cli/ +COPY src/Yavsc.Org.Tests/*.csproj ./src/Yavsc.Org.Tests/ +COPY src/PostIt/PostIt/*.csproj ./src/PostIt/PostIt/ +COPY src/PostIt/PostIt.Android/*.csproj ./src/PostIt/PostIt.Android/ +COPY src/PostIt/PostIt.Browser/*.csproj ./src/PostIt/PostIt.Browser/ +COPY src/PostIt/PostIt.Desktop/*.csproj ./src/PostIt/PostIt.Desktop/ + +# (2) Tout le code source COPY . . +# (4) Restore RUN dotnet restore -# Build des trois services web principaux +# (5) Build des trois services web principaux RUN dotnet build src/Yavsc.Org/Yavsc.Org.csproj -c Release --no-restore -clp:ErrorsOnly RUN dotnet build src/Yavsc.Api/Yavsc.Api.csproj -c Release --no-restore -clp:ErrorsOnly RUN dotnet build src/Yavsc.Blogs/Yavsc.Blogs.csproj -c Release --no-restore -clp:ErrorsOnly -# Build APK Android (utilisé par le workflow docker-publish-android). +# (6) Build APK Android (utilisé par le workflow docker-publish-android). ARG ANDROID_TARGET_RID=android-arm64 RUN dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj -c Release --no-restore -clp:ErrorsOnly -r ${ANDROID_TARGET_RID} -# Publish des trois services web (les artifacts sont consommés par +# (7) Publish des trois services web (les artifacts sont consommés par # les stages publish-org / publish-api / publish-blogs ci-dessous). RUN mkdir -p /app/publish RUN dotnet publish src/Yavsc.Org/Yavsc.Org.csproj -c Release --no-build -o /app/publish/Yavsc.Org diff --git a/Makefile b/Makefile index 1893a910e..d99fe5e5e 100644 --- a/Makefile +++ b/Makefile @@ -13,37 +13,11 @@ all: clean: dotnet clean -c $(CONFIG) -brutal_clean: - @find -name "obj" -exec rm -rf {} \; - @find -name "bin" -exec rm -rf {} \; - src/Yavsc/bin/output/wwwroot: dotnet --project src/Yavsc.Org/Yavsc.Org.csproj publish test: - ASPNETCORE_ENVIRONMENT=Development dotnet test --report-xunit \ - --report-xunit-html \ - --results-directory "test-reports" - -test-postit: - ASPNETCORE_ENVIRONMENT=Development dotnet test \ - --report-xunit \ - --report-xunit-html \ - --results-directory "test-reports" \ - --report-xunit-html-filename "postit-test-results.html" \ - --project src/PostIt/PostIt.Tests/PostIt.Tests.csproj - -backend-tests: Blogs-backend-test Org-backend-test Api-backend-test -Api-backend-test: -Blogs-backend-test: -Org-backend-test: -%-backend-test: - ASPNETCORE_ENVIRONMENT=Development dotnet test \ - --report-xunit \ - --report-xunit-html \ - --results-directory "test-reports" \ - --report-xunit-html-filename "$*-backend-test-results.html" \ - --project src/test/Yavsc.$*.Tests/Yavsc.$*.Tests.csproj + ASPNETCORE_ENVIRONMENT=Development dotnet test watch: dotnet watch -p:Configuration=$(CONFIG) --project src/Yavsc/Yavsc.csproj diff --git a/README.md b/README.md index fefbdd48d..549348f4a 100644 --- a/README.md +++ b/README.md @@ -61,40 +61,9 @@ make test ### les services et l'API -#### Dépendances système au runtime - -La génération des devis et factures en PDF (`/api/v1/front/query/{id}/estimate.pdf`, -`BillingController` → `BillViewComponent`) compile un source LaTeX via la commande -système `lualatex` : le TeX est fourni sur l'entrée standard et le PDF est écrit -directement dans le répertoire des factures (`Site.Bills`). `lualatex` doit donc -être installé et opérationnel sur l'hôte qui exécute l'API — typiquement le service -systemd `yavscApi`. - -Sous Debian/Ubuntu : - -```bash -sudo apt install texlive-binaries texlive-luatex texlive-pictures texlive-fonts-extra -``` - -- `texlive-binaries` fournit l'exécutable `lualatex` (collection basic). -- `texlive-luatex` fournit les paquets de support chargés au démarrage de lualatex, - notamment `luaotfload`. Sans lui, lualatex démarre mais s'arrête sur - `module 'luaotfload-main' not found … Emergency stop` et ne produit aucun PDF. - (Sur les Debian récents, `luaotfload` a migré de `texlive-latex-extra` vers - `texlive-luatex` — avoir `texlive-latex-extra` seul ne suffit plus.) -- `texlive-pictures` fournit `tikz`/`pgf`, utilisé pour dessiner les signatures - du client et du fournisseur en bas du devis (vecteur, pas de PNG). -- `texlive-fonts-extra` fournit la fonte `bera` chargée par le modèle de devis. - Sans elle, lualatex s'arrête sur `! LaTeX Error: File 'bera.sty' not found` - (`Emergency stop`, aucun PDF). - -Sur un hôte sans `lualatex`, les endpoints PDF renvoient une erreur 500 « PDF -generation failed » ; l'endpoint TeX (`estimate.tex`) reste utilisable et permet -de compiler le document hors-ligne. - ### La Prod -`cd srv/Yavsc/contrib` : `make reinstall CONFIGURATION=Release APP_INSTALL_ENV=production`. +`cd srv/Yavsc` : `make pushInProd CONFIGURATION=Release`. puis, pour une première installation `make install_service`. diff --git a/contrib/Makefile b/contrib/Makefile index deb55bcc6..4e4ef4df6 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -1,32 +1,23 @@ -DESTDIR= -DOTNET_FRAMEWORK=net10.0 APP_PROJECT_NAMES=Org Blogs Api SLNDIR=.. -APP_INSTALL_ENV=preprod -APP_INSTALL_PREFIX=preprod- -include .$(APP_INSTALL_ENV).env +include $(SLNDIR)/.env +include .env -reinstall: copy-binaries - @for project in $(APP_PROJECT_NAMES); do \ - echo "🚀 Starting $(APP_INSTALL_PREFIX)yavsc$${project}" ; \ - sudo systemctl start $(APP_INSTALL_PREFIX)yavsc$${project} ; done - @echo "💯 backend started" +generated/: + @mkdir -p $@ -reinstall-prod: - make reinstall APP_INSTALL_ENV= APP_INSTALL_PREFIX= APP_INSTALL_ENV=production +generated/yavscOrg.service: +generated/yavscBlogs.service: +generated/yavscApi.service: -generated/$(APP_INSTALL_PREFIX)yavscOrg.service: -generated/$(APP_INSTALL_PREFIX)yavscBlogs.service: -generated/$(APP_INSTALL_PREFIX)yavscApi.service: - -generated/$(APP_INSTALL_PREFIX)yavsc%.service: generated/ template.service $(SLNDIR)/.env +generated/yavsc%.service: generated/ template.service $(SLNDIR)/.env @cat template.service | APP_NAME="$*" \ DESTDIR="$(DESTDIR)" \ HTTP_HOST="$(HTTP_HOST)" \ - HTTP_PORT="$($*_PORT)" \ - APPDIR="$(APPDIR)" \ - APP_SYSLOG_ID="$(APP_INSTALL_PREFIX)yavsc$*" \ + HTTP_PORT="$*_$(HTTP_PORT)" \ + BASEAPPDIR="$(BASEAPPDIR)" \ + APP_SYSLOG_ID="yavsc$*" \ POSTGRES_HOST="$(POSTGRES_HOST)" \ POSTGRES_PORT="$(POSTGRES_PORT)" \ POSTGRES_DB="$(POSTGRES_DB)" \ @@ -43,83 +34,11 @@ generated/$(APP_INSTALL_PREFIX)yavsc%.service: generated/ template.service $(SLN envsubst > $@ @echo Created service file: $@ -generate-service-units: generated/$(APP_INSTALL_PREFIX)yavscOrg.service generated/$(APP_INSTALL_PREFIX)yavscBlogs.service generated/$(APP_INSTALL_PREFIX)yavscApi.service -# --- Vhosts nginx (générés par envsubst, à l'image des unités systemd) --- -# make generate-nginx -> generated/nginx/preprod-yavsc{Org,Blogs,Api} -# make generate-nginx APP_INSTALL_ENV=production APP_INSTALL_PREFIX= -> prod (yavsc/api/blogs.pschneider.fr) -# -# HTTP/3 (QUIC) termine au edge nginx (plan contrib/quic.md). Requiert nginx -# avec http_v3_module + UDP 443 ouvert. reuseport sur Org uniquement -# (QUIC_OPTS). Les vhosts générés vont dans generated/nginx/ (gitignoré). -NGINX_TEMPLATE := template.vhost -NGINX_OUT_DIR := generated/nginx -NGINX_HOSTS := Org Blogs Api -DOMAIN_ROOT ?= pschneider.fr -# Schéma backend Kestrel : https en prod (Kestrel écoute HTTPS, cert Let's -# Encrypt), http en preprod (Kestrel écoute HTTP simple, cf. .preprod.env). -BACKEND_SCHEME ?= $(if $(filter production,$(APP_INSTALL_ENV)),https,http) -# sous-domaine par hôte : Org -> yavsc, Api -> api, Blogs -> blogs -nginx_subdomain = $(if $(filter Org,$1),yavsc,$(if $(filter Api,$1),api,$(if $(filter Blogs,$1),blogs,$1))) -# domaine complet : . -# prod Org -> yavsc.pschneider.fr -# preprod Org -> preprod-yavsc.pschneider.fr -nginx_domain = $(APP_INSTALL_PREFIX)$(call nginx_subdomain,$1).$(DOMAIN_ROOT) - -# QUIC_OPTS : "reuseport" n'est légal qu'une seule fois par addr:port -# (443/udp). preprod et prod partagent aujourd'hui la même IP, donc on ne -# l'active qu'en production, sur Org uniquement. En preprod, QUIC_OPTS est -# vide sur tous les vhosts (sinon nginx refuse la config : duplicate -# reuseport sur le même socket 443/udp entre preprod-Org et prod-Org). -$(NGINX_OUT_DIR)/$(APP_INSTALL_PREFIX)yavsc%: $(NGINX_OUT_DIR) $(NGINX_TEMPLATE) .$(APP_INSTALL_ENV).env - @cat $(NGINX_TEMPLATE) \ - | DOMAIN="$(call nginx_domain,$*)" \ - BACKEND_SCHEME="$(BACKEND_SCHEME)" \ - HTTP_HOST="$(HTTP_HOST)" \ - BACKEND_PORT="$($*_PORT)" \ - QUIC_OPTS="$(if $(filter production,$(APP_INSTALL_ENV)),$(if $(filter Org,$*), reuseport,),)" \ - envsubst '$$DOMAIN $$BACKEND_SCHEME $$HTTP_HOST $$BACKEND_PORT $$QUIC_OPTS' > $@ - @echo Created nginx vhost: $@ - -generate-nginx: $(addprefix $(NGINX_OUT_DIR)/$(APP_INSTALL_PREFIX)yavsc,$(NGINX_HOSTS)) - -generate-nginx-prod: - make generate-nginx APP_INSTALL_ENV=production APP_INSTALL_PREFIX= - -$(NGINX_OUT_DIR): - @mkdir -p $@ - -# --- appsettings (générés depuis template.appsettings-*.json) --- -# make generate-appsettings -> generated/appsettings/appsettings-{org,api,blogs}.json -# -# Substitue $DOMAIN (racine du domaine, cf. DOMAIN_ROOT) dans toutes les -# chaînes, et $SMTP_PASSWORD (secret du .env) dans Smtp.Password. -# On utilise jq (et non envsubst) car envsubst n'échappe pas le JSON : un -# secret contenant \ ou " casserait le JSON (le mot de passe SMTP actuel -# contient un \). jq lit le secret depuis l'environnement (export) -> -# échappement JSON correct, aucun shell-quoting ni exposition en ligne de cmd. -# Les templates actuels sont issus de la preprod (préfixe preprod-, ports -# 88-93) ; pour la prod il faudra un template prod (ports 82-87, sans préfixe). -export ASPNETCORE_Smtp__Password -APPSETTINGS_OUT_DIR := generated/appsettings -APPSETTINGS_HOSTS := org api blogs - -$(APPSETTINGS_OUT_DIR)/appsettings-%.json: $(APPSETTINGS_OUT_DIR) template.appsettings-%.json .$(APP_INSTALL_ENV).env - @jq --arg domain "$(DOMAIN_ROOT)" \ - 'walk(if type=="string" then gsub("\\$$DOMAIN"; $$domain) else . end) | if has("Smtp") then .Smtp.Password = env["ASPNETCORE_Smtp__Password"] else . end' \ - template.appsettings-$*.json > $@ - @echo Created appsettings: $@ - -generate-appsettings: $(addprefix $(APPSETTINGS_OUT_DIR)/appsettings-,$(addsuffix .json,$(APPSETTINGS_HOSTS))) - -$(APPSETTINGS_OUT_DIR): - @mkdir -p $@ - -install-service-%: /etc/systemd/system/$(APP_INSTALL_PREFIX)yavsc%.service - sudo systemctl daemon-reload - sudo systemctl enable $(APP_INSTALL_PREFIX)yavsc$* - -install-services: install-service-Org install-service-Blogs install-service-Api +copy-services: copy-service-Org copy-service-Blogs copy-service-Api +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-binaries: build_publish_Org build_publish_Blogs build_publish_Api stop-services @for project in $(APP_PROJECT_NAMES); \ @@ -133,36 +52,39 @@ copy-binaries: build_publish_Org build_publish_Blogs build_publish_Api stop-serv rm -f $${BINDIR}/appsettings-$${LCAPI}.json ; \ sync ; \ ls $${BINDIR}/appsettings* 2>&1 ; \ - sudo cp -a $${BINDIR}/* $(DESTDIR)$(APPDIR) ; \ + sudo cp -a $${BINDIR}/* $(BASEAPPDIR) ; \ done - @sudo chown -R $(USER_AND_GROUP) $(DESTDIR)$(APPDIR) - @sync + @sudo chown -R $(USER_AND_GROUP) $(BASEAPPDIR) -$(DESTDIR)/etc/systemd/system/$(APP_INSTALL_PREFIX)yavsc%.service: generated/$(APP_INSTALL_PREFIX)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: copy-binaries copy-services +install: build_publish copy-binaries copy-services @sudo systemctl daemon-reload @for project in $(APP_PROJECT_NAMES); \ do \ - echo "🖒 enable $(APP_INSTALL_PREFIX)yavsc$${project}" ; \ - sudo systemctl enable $(APP_INSTALL_PREFIX)yavsc$${project} ; \ - echo "🚀 start $(APP_INSTALL_PREFIX)yavsc$${project}" ; \ - sudo systemctl start $(APP_INSTALL_PREFIX)yavsc$${project} ; \ + sudo systemctl enable yavsc$${project} ; \ + sudo systemctl start yavsc$${project} ; \ done +reinstall: copy-binaries + @sync + @for project in $(APP_PROJECT_NAMES); do \ + sudo systemctl start yavsc$${project} ; done + stop-services: @for project in $(APP_PROJECT_NAMES); \ do \ - echo "🔥 Stopping $(APP_INSTALL_PREFIX)yavsc$${project}" ; \ - sudo systemctl stop $(APP_INSTALL_PREFIX)yavsc$${project} ; \ + sudo systemctl stop yavsc$${project} ; \ done $(SLNDIR)/src/Yavsc.Org/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish: build_publish @@ -171,7 +93,7 @@ $(SLNDIR)/src/Yavsc.Api/bin/$(CONFIGURATION)/$(DOTNET_FRAMEWORK)/publish: build_ showConfig: @echo CONFIGURATION: $(CONFIGURATION) - @echo APPDIR: $(APPDIR) + @echo BASEAPPDIR: $(BASEAPPDIR) showApiLogs: @sudo journalctl -u yavscApi.service -S "2 min ago" | tee yavscApi.log @@ -185,5 +107,3 @@ showBlogsLogs: clean: @rm -rf generated -generated/: - @mkdir -p $@ diff --git a/contrib/README.nginx.md b/contrib/README.nginx.md deleted file mode 100644 index 9564d4ffe..000000000 --- a/contrib/README.nginx.md +++ /dev/null @@ -1,134 +0,0 @@ -# Vhosts nginx — génération par `make generate-nginx` - -Les vhosts nginx sont **générés** à l'image des unités de service systemd -(`template.service` + `envsubst` → `generated/*.service`) : un template -unique `contrib/template.vhost` est substitué par `envsubst` vers -`contrib/generated/nginx/`, qui est **gitignoré**. - -## Rôle de nginx : edge HTTP/3 (plan `contrib/quic.md`) - -nginx termine HTTP/3 (QUIC) au edge sur 443/udp, en coexistence avec -h1/h2 sur 443/tcp. Kestrel reste backend h1/h2 (`proxy_http_version 1.1`). -Le « QUIC logiciel » Kestrel (`Protocols: Http1AndHttp2AndHttp3` dans les -`template.appsettings-*.json`) est une capacité complémentaire — non -joignable par les clients derrière nginx (nginx OSS = upstream h1.1 -uniquement), mais disponible si Kestrel est un jour exposé en direct. - -Directives générées (par vhost) : -```nginx -listen 443 ssl; listen [::]:443 ssl; http2 on; -listen 443 quic $QUIC_OPTS; listen [::]:443 quic $QUIC_OPTS; http3 on; -add_header Alt-Svc 'h3=":443"; ma=86400' always; -ssl_protocols TLSv1.3; -``` -`$QUIC_OPTS = reuseport` sur **Org uniquement**, vide sur Api/Blogs (règle -`contrib/quic.md` : `reuseport` une seule fois par addr:port, tous vhosts -confondus). - -## Prérequis côté host - -1. **nginx avec `http_v3_module`** (≥ 1.25 ; la directive `http3 on;` - exige ≥ 1.27.4 — sur une version 1.25–1.27.3, retirer `http3 on;`, - `listen ... quic` suffit à activer h3) : - ```bash - nginx -V 2>&1 | grep -o 'http_v3_module' # doit être non vide - ``` - Debian 13 « trixie » fournit `nginx` avec `http_v3_module`. Sinon, - backports ou `nginx-full`. -2. **UDP 443 ouvert** : - ```bash - sudo ufw allow 443/udp - sudo ufw status | grep '443/udp' # → ALLOW - ``` -3. **IPv6** : le template émet `listen [::]:443 ...` (et `[::]:80`). Si le - host n'a pas d'IPv6, retirer/commenter ces lignes (sinon `nginx -t` - échoue sur le bind IPv6). -4. Certbot / certs Let's Encrypt déjà en place (chemins - `/etc/letsencrypt/live//{fullchain,privkey}.pem` référencés). -5. (Optionnel, pour le QUIC logiciel Kestrel) `apt install libmsquic` — - sinon Kestrel désactive h3 silencieusement (fallback h1/h2 gracieux). - -## Génération - -```bash -cd contrib - -# Preprod (défaut) -> generated/nginx/preprod-yavsc{Org,Blogs,Api} -make generate-nginx - -# Production -> generated/nginx/yavsc{Org,Blogs,Api} -make generate-nginx-prod -# ou manuellement : -make generate-nginx APP_INSTALL_ENV=production APP_INSTALL_PREFIX= -``` - -Ports backend et `HTTP_HOST` depuis `.preprod.env` / `.production.env` -(prod Org=83, Api=87, Blogs=85 ; preprod 89/93/91). Schéma backend -`https` en prod, `http` en preprod. - -| Hôte | Sous-domaine | `reuseport` | Backend prod | Backend preprod | -|-------|--------------|-------------|-------------------------|------------------------| -| Org | yavsc | **oui** | `https://localhost:83` | `http://localhost:89` | -| Api | api | non | `https://localhost:87` | `http://localhost:93` | -| Blogs | blogs | non | `https://localhost:85` | `http://localhost:91` | - -Domaines : `.pschneider.fr` -(prod `yavsc.pschneider.fr` ; preprod `preprod-yavsc.pschneider.fr`). - -## Déploiement sur le host - -```bash -sudo cp generated/nginx/yavscOrg /etc/nginx/sites-available/yavscOrg -sudo ln -sf /etc/nginx/sites-available/yavscOrg /etc/nginx/sites-enabled/yavscOrg -# (idem yavscApi, yavscBlogs) -sudo nginx -t && sudo systemctl reload nginx -``` - -## Vérification (sur le host) - -```bash -# 1. Module h3 présent -nginx -V 2>&1 | grep -o 'http_v3_module' - -# 2. UDP 443 ouvert -sudo ufw status | grep '443/udp' - -# 3. En-tête Alt-Svc annoncé -curl -sI https://yavsc.pschneider.fr/ | grep -i alt-svc # → h3=":443" - -# 4. Requête HTTP/3 effective (curl compilé avec HTTP/3) -curl -v --http3 https://yavsc.pschneider.fr/ 2>&1 | grep -i 'HTTP/3' - -# 5. Fallback h2 et h1 toujours fonctionnels -curl --http2 https://yavsc.pschneider.fr/ -o /dev/null -w '%{http_code}\n' -curl --http1.1 https://yavsc.pschneider.fr/ -o /dev/null -w '%{http_code}\n' -``` -Répéter 3-5 pour `api.pschneider.fr` et `blogs.pschneider.fr` (prod) et -les trois domaines preprod. Sans curl HTTP/3, un navigateur : DevTools → -Network → colonne « Protocole » → `h3` après mise à niveau. - -## Comment ça marche (template + envsubst) - -`contrib/template.vhost` contient les placeholders `$DOMAIN`, -`$BACKEND_SCHEME`, `$HTTP_HOST`, `$BACKEND_PORT`, `$QUIC_OPTS`. La règle -`generated/nginx/$(APP_INSTALL_PREFIX)yavsc%` du `Makefile` les substitue -via `envsubst` avec une **allowlist** : - -```makefile -envsubst '$DOMAIN $BACKEND_SCHEME $HTTP_HOST $BACKEND_PORT $QUIC_OPTS' -``` - -Cette allowlist limite la substitution aux 5 variables citées, de sorte -que les variables nginx intrinsèques (`$host`, `$scheme`, `$remote_addr`, -`$proxy_add_x_forwarded_for`, `$request_uri`) survivent intactes. Sans -allowlist, `envsubst` détruirait ces variables nginx. - -## Hors périmètre - -- `docker-compose` / `Dockerfile` : non touchés (déploiement bare-metal - systemd + nginx). -- Backend HTTP/2 vers Kestrel : nginx open-source ne supporte que - HTTP/1.1 en upstream ; on garde `proxy_http_version 1.1`. -- `ssl_protocols TLSv1.3` (suivant `contrib/quic.md`) désactive TLS 1.2 - côté TCP aussi. Pour conserver la compat TLS 1.2 sur le fallback TCP, - relâcher en `ssl_protocols TLSv1.2 TLSv1.3;` (QUIC reste en 1.3). \ No newline at end of file diff --git a/contrib/template.appsettings-api.json b/contrib/template.appsettings-api.json deleted file mode 100644 index b364eeef1..000000000 --- a/contrib/template.appsettings-api.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "Site": { - "Authority": "https://preprod-yavsc.$DOMAIN", - "Audience": ["api"], - "CorsAllowedOrigins": [ - "https://preprod-blogs.$DOMAIN" - ], - "Bills": "bills", - "TempDir": "temp" - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "Kestrel": { - "Endpoints": { - "Http": { - "Url": "http://localhost:92" - }, - "Https": { - "Url": "https://localhost:93", - "Certificate": { - "Path": "/etc/letsencrypt/live/preprod-api.$DOMAIN/fullchain.pem", - "KeyPath": "/etc/letsencrypt/live/preprod-api.$DOMAIN/privkey.pem" - }, - "Protocols": "Http1AndHttp2AndHttp3" - } - } - } -} diff --git a/contrib/template.appsettings-blogs.json b/contrib/template.appsettings-blogs.json deleted file mode 100644 index f522feaca..000000000 --- a/contrib/template.appsettings-blogs.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "Site": { - "Title": "Yavsc Blogs", - "Description": "A collection of blogs about software development, technology, and programming.", - "Keywords": "software development, technology, programming, coding, blogs", - "Authority": "https://preprod-yavsc.$DOMAIN", - "Audience": ["blogs"], - "CorsAllowedOrigins": [ - "https://preprod-api.$DOMAIN" - ], - "Blog": "blog" - }, - "ConnectionStrings": { - "YavscConnection": "Server=localhost;Port=5432;Database=lame-db-name;Username=lame-user-name;Password=lame-password;" - }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information", - "Microsoft.AspNetCore.Authentication": "Debug" - } - }, - "AllowedHosts": "*", - "Kestrel": { - "Endpoints": { - "Http": { - "Url": "http://localhost:90" - }, - "Https": { - "Url": "https://localhost:91", - "Certificate": { - "Path": "/etc/letsencrypt/live/preprod-blogs.$DOMAIN/fullchain.pem", - "KeyPath": "/etc/letsencrypt/live/preprod-blogs.$DOMAIN/privkey.pem" - }, - "Protocols": "Http1AndHttp2AndHttp3" - } - } - } -} diff --git a/contrib/template.appsettings-org.json b/contrib/template.appsettings-org.json deleted file mode 100644 index 76203d9e9..000000000 --- a/contrib/template.appsettings-org.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information" - } - }, - "AllowedHosts": "*", - "Site": { - "Title": "yavsc", - "Slogan": "Yavsc!", - "StyleSheet": "/css/default.css", - "Authority": "https://preprod-yavsc.$DOMAIN", - "Audience": [ - "blogs", - "api" - ], - "ExternalUrl": "https://preprod-yavsc.$DOMAIN", - "ApiUrl": "https://preprod-api.$DOMAIN", - "CorsAllowedOrigins": [ - "https://preprod-blogs.$DOMAIN", - "https://preprod-api.$DOMAIN" - ], - "Banner": "/images/banner-dewang.jpg", - "Owner": { - "Name": "Paul Schneider", - "EMail": "paul@pschneider.fr" - }, - "Admin": { - "Name": "Paul Schneider", - "EMail": "paul@pschneider.fr" - }, - "Quota": 200000000, - "Blog": "blog", - "Avatars": "avatars", - "Bills": "bills", - "TempDir": "temp", - "DataDir": "data", - "GitRepository": "git" - }, - "Smtp": { - "Host": "mercure.pschneider.fr", - "Port": 465, - "SenderName": "Paul Schneider", - "SenderEmail": "paul@pschneider.fr", - "UserName": "paul", - "Password": "$SMTP_PASSWORD" - }, - "DataProtection": { - "Keys": { - "Dir": "DataProtection" - }, - "RSAParamFile": "RSA-Params.json", - "ExpiresInHours": 168 - }, - "Moderation": { - "AutoApproveThreshold": 0.7, - "AutoRejectThreshold": 0.9 - }, - "Kestrel": { - "Endpoints": { - "Http": { - "Url": "http://localhost:88" - }, - "Https": { - "Url": "https://localhost:89", - "Certificate": { - "Path": "/etc/letsencrypt/live/preprod-yavsc.$DOMAIN/fullchain.pem", - "KeyPath": "/etc/letsencrypt/live/preprod-yavsc.$DOMAIN/privkey.pem" - }, - "Protocols": "Http1AndHttp2AndHttp3" - } - } - } -} diff --git a/contrib/template.service b/contrib/template.service index 3959c7151..b075c4da1 100644 --- a/contrib/template.service +++ b/contrib/template.service @@ -10,10 +10,10 @@ RestartSec=5s Type=simple User=yavsc Group=yavsc -WorkingDirectory=$APPDIR -ExecStart=$APPDIR/Yavsc.$APP_NAME +WorkingDirectory=$DESTDIR +ExecStart=$DESTDIR/Yavsc.$APP_NAME Restart=always -Environment="HOME=$APPDIR" +Environment="HOME=$DESTDIR" Environment="ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" Environment="ANTHROPIC_MAX_TOKENS=$ANTHROPIC_MAX_TOKENS" Environment="ASPNETCORE_Environment=$ENV" @@ -29,6 +29,8 @@ Environment="ASPNETCORE_Smtp__Password=\"$ASPNETCORE_Smtp__Password\"" CapabilityBoundingSet=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE +StandardOutput=syslog +StandardError=syslog SyslogIdentifier=$APP_SYSLOG_ID [Install] diff --git a/contrib/template.vhost b/contrib/template.vhost deleted file mode 100644 index ea2c2cb6d..000000000 --- a/contrib/template.vhost +++ /dev/null @@ -1,61 +0,0 @@ -# Généré depuis contrib/template.vhost par `make generate-nginx` — NE PAS ÉDITER. -# Vhost $DOMAIN — backend $BACKEND_SCHEME://$HTTP_HOST:$BACKEND_PORT -# -# HTTP/3 (QUIC) termine au edge nginx (plan contrib/quic.md). Requiert un -# nginx compilé avec http_v3_module (≥ 1.25 ; `http3 on` ≥ 1.27.4) + UDP 443 -# ouvert. Kestrel reste backend h1/h2 (mais configure aussi h3 côté logiciel, -# cf. template.appsettings-*.json — capacité, non joignable derrière nginx). -# -# Variables substituées par envsubst (allowlist) : -# DOMAIN BACKEND_SCHEME HTTP_HOST BACKEND_PORT QUIC_OPTS -# $QUIC_OPTS = "reuseport" sur un seul vhost (Org) ET en production -# uniquement ; vide en preprod et sur les autres hôtes. Règle : reuseport -# une seule fois par addr:port (443/udp) — or preprod et prod partagent la -# même IP, donc seule la prod (Org) l'active. -# Les variables nginx intrinsèques ($host, $scheme, $remote_addr, …) sont -# préservées (hors allowlist). - -# Port 80 : redirection vers HTTPS. (Certbot/nginx-plugin gère les challenges -# ACME ; ne pas servir l'app en HTTP clair.) -server { - listen 80; - listen [::]:80; - server_name $DOMAIN; - return 301 https://$host$request_uri; -} - -server { - # 1. TCP : HTTPS classique + HTTP/2 - listen 443 ssl; - listen [::]:443 ssl; - http2 on; - - # 2. UDP : HTTP/3 (QUIC). reuseport sur UN seul vhost (Org via QUIC_OPTS). - listen 443 quic$QUIC_OPTS; - listen [::]:443 quic$QUIC_OPTS; - http3 on; - - server_name $DOMAIN; - - # 3. Certificats Let's Encrypt (Certbot). - ssl_certificate /etc/letsencrypt/live/$DOMAIN/fullchain.pem; # managed by Certbot - ssl_certificate_key /etc/letsencrypt/live/$DOMAIN/privkey.pem; # managed by Certbot - # QUIC exige TLS 1.3. (TLS 1.2 conservable pour le fallback TCP : - # `ssl_protocols TLSv1.2 TLSv1.3;` — QUIC reste en 1.3.) - ssl_protocols TLSv1.3; - - # 4. Annonce h3 au navigateur sur 443/udp. `always` = émis même en erreur. - add_header Alt-Svc 'h3=":443"; ma=86400' always; - - location / { - proxy_pass $BACKEND_SCHEME://$HTTP_HOST:$BACKEND_PORT; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_read_timeout 100s; - proxy_buffering off; - } - include server-status.conf; -} \ No newline at end of file diff --git a/contrib/yavsc-org b/contrib/yavsc-org new file mode 100644 index 000000000..dc65aa9fe --- /dev/null +++ b/contrib/yavsc-org @@ -0,0 +1,54 @@ + + +server { + if ($host = yavsc.pschneider.fr) { + return 301 https://$host$request_uri; + } # managed by Certbot + + + + listen 80; + server_name yavsc.pschneider.fr; + location / { + root /srv/www/yavsc/wwwroot; + dav_methods PUT; + + proxy_pass http://127.0.0.1:3003; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection keep-alive; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + +} + +server { + listen 443 ssl; + server_name yavsc.pschneider.fr; + ssl_certificate /etc/letsencrypt/live/yavsc.pschneider.fr-0001/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/yavsc.pschneider.fr-0001/privkey.pem; # managed by Certbot + # Configure the SignalR Endpoint + location / { + # App server url + proxy_pass http://localhost:3003; + + proxy_cache off; + # WebSockets were implemented after http/1.0 + proxy_http_version 1.1; + + # Configuration for ServerSentEvents + proxy_buffering off; + + # Configuration for LongPolling or if your KeepAliveInterval is longer than 60 seconds + proxy_read_timeout 100s; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} + diff --git a/docker-compose.yaml b/docker-compose.yaml index ea08e9009..f478dcc9f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -26,7 +26,7 @@ services: dockerfile: Dockerfile target: web-runtime args: - BUILD_ENV_TAG: debian13-dotnet10-android36-jdk21-v2 + BUILD_ENV_TAG: debian12-dotnet10-android36-v1 secrets: - source: yavsc_appsettings target: yavsc_appsettings @@ -53,7 +53,9 @@ services: depends_on: db: condition: service_healthy - network_mode: host + networks: + - yavsc-internal + - yavsc-public # volumes: # # Indispensable pour que Yavsc.Org (IdentityServer8) démarre. # # Sans ce montage, 'docker compose up' échoue côté web avec @@ -71,7 +73,7 @@ services: dockerfile: Dockerfile target: api-runtime args: - BUILD_ENV_TAG: debian13-dotnet10-android36-jdk21-v2 + BUILD_ENV_TAG: debian12-dotnet10-android36-v1 secrets: - source: yavsc_appsettings target: yavsc_appsettings @@ -88,7 +90,9 @@ services: depends_on: db: condition: service_healthy - network_mode: host + networks: + - yavsc-internal + - yavsc-public # volumes: # - /etc/letsencrypt:/etc/letsencrypt:ro @@ -101,7 +105,7 @@ services: dockerfile: Dockerfile target: blogs-runtime args: - BUILD_ENV_TAG: debian13-dotnet10-android36-jdk21-v2 + BUILD_ENV_TAG: debian12-dotnet10-android36-v1 secrets: - source: yavsc_appsettings target: yavsc_appsettings @@ -118,7 +122,9 @@ services: depends_on: db: condition: service_healthy - network_mode: host + networks: + - yavsc-internal + - yavsc-public # volumes: # - /etc/letsencrypt:/etc/letsencrypt:ro diff --git a/global.json b/global.json deleted file mode 100644 index 9009caf0b..000000000 --- a/global.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "test": { - "runner": "Microsoft.Testing.Platform" - } -} diff --git a/src/PostIt/Directory.Packages.props b/src/PostIt/Directory.Packages.props index 42d09cd55..e8804a8bf 100644 --- a/src/PostIt/Directory.Packages.props +++ b/src/PostIt/Directory.Packages.props @@ -4,31 +4,28 @@ true - 12.1.2 + 12.1.1 - - + - + - - - - - + + + - + \ No newline at end of file diff --git a/src/PostIt/PostIt.Android/MainActivity.cs b/src/PostIt/PostIt.Android/MainActivity.cs index f8fd5a0fd..549100804 100644 --- a/src/PostIt/PostIt.Android/MainActivity.cs +++ b/src/PostIt/PostIt.Android/MainActivity.cs @@ -2,7 +2,6 @@ using Android.App; using Android.Content; using Android.Content.PM; -using Android.Views; using AndroidX.Core.Provider; using AndroidX.Emoji2.Text; using Avalonia.Android; @@ -16,9 +15,7 @@ namespace PostIt.Android; Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, - WindowSoftInputMode = SoftInput.AdjustResize, - ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode) - ] + ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)] public class MainActivity : AvaloniaMainActivity { /// diff --git a/src/PostIt/PostIt.Android/PostIt.Android.csproj b/src/PostIt/PostIt.Android/PostIt.Android.csproj index 18bfec459..a6d04cb33 100644 --- a/src/PostIt/PostIt.Android/PostIt.Android.csproj +++ b/src/PostIt/PostIt.Android/PostIt.Android.csproj @@ -11,8 +11,8 @@ false 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 @@ -27,14 +27,4 @@ - - - - - - - + \ No newline at end of file diff --git a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj index f2b2a2bbf..ff2f3f2b8 100644 --- a/src/PostIt/PostIt.Browser/PostIt.Browser.csproj +++ b/src/PostIt/PostIt.Browser/PostIt.Browser.csproj @@ -6,8 +6,8 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 diff --git a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj index 46df5025f..36ed1251e 100644 --- a/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj +++ b/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj @@ -7,8 +7,8 @@ enable 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 app.manifest diff --git a/src/PostIt/PostIt.Desktop/Program.cs b/src/PostIt/PostIt.Desktop/Program.cs index a2b8cd66f..0de3bd69e 100644 --- a/src/PostIt/PostIt.Desktop/Program.cs +++ b/src/PostIt/PostIt.Desktop/Program.cs @@ -12,8 +12,6 @@ sealed class Program [STAThread] public static void Main(string[] args) { - CLIOptionHandler.HandleArgs(args); - // 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 diff --git a/src/PostIt/PostIt.Tests/EstimateEditionPageViewModelTests.cs b/src/PostIt/PostIt.Tests/EstimateEditionPageViewModelTests.cs index 17efd391b..db70e25fa 100644 --- a/src/PostIt/PostIt.Tests/EstimateEditionPageViewModelTests.cs +++ b/src/PostIt/PostIt.Tests/EstimateEditionPageViewModelTests.cs @@ -22,8 +22,7 @@ public class EstimateEditionPageViewModelTests private static EstimateEditionPageViewModel CreateViewModel(StubEstimateApi api, BillingQuerySummaryDto? query = null) { var client = new EstimateApiClient(api, "https://business.example/api/v1/"); - var fsClient = new UserFilesApiClient(api, "https://blogs.example/api/v1/"); - return new EstimateEditionPageViewModel(query ?? SampleQuery(), client, fsClient); + return new EstimateEditionPageViewModel(query ?? SampleQuery(), client); } [Fact] @@ -144,8 +143,10 @@ public class EstimateEditionPageViewModelTests await vm.SendCommand.ExecuteAsync(null); - var postCall = Assert.Single(api.Calls, c => c.Method == HttpMethod.Post && c.Path == "https://business.example/api/v1/estimate"); - var payload = Assert.IsType(postCall.Body); + 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); @@ -215,8 +216,7 @@ public class EstimateEditionPageViewModelTests await vm.SendCommand.ExecuteAsync(null); - var postCall = Assert.Single(api.Calls, c => c.Method == HttpMethod.Post && c.Path == "https://business.example/api/v1/estimate"); - var payload = Assert.IsType(postCall.Body); + var payload = Assert.IsType(api.LastBody); Assert.Equal(2, payload.Bill.Count); Assert.Equal(-5m, payload.Bill[1].UnitaryCost); Assert.True(vm.HasSent); @@ -230,20 +230,11 @@ public class EstimateEditionPageViewModelTests public object? LastBody { get; private set; } public Exception? Failure { get; init; } - /// Historique complet des appels (le plus récent en dernier). - public List<(HttpMethod Method, string Path, object? Body)> Calls { get; } = new(); - - private void Record(HttpMethod method, string path, object? body) + public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) { LastMethod = method; LastPath = path; LastBody = body; - Calls.Add((method, path, body)); - } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - Record(method, path, body); if (Failure is not null) { @@ -262,7 +253,9 @@ public class EstimateEditionPageViewModelTests public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) { - Record(method, path, body); + LastMethod = method; + LastPath = path; + LastBody = body; return Task.CompletedTask; } @@ -272,12 +265,6 @@ public class EstimateEditionPageViewModelTests public Task CallAsync(HttpMethod method, string path, Func contentFactory, CancellationToken ct = default) => CallAsync(method, path, (object?)null, ct); - public Task DownloadAsync(HttpMethod method, string path, CancellationToken ct = default) - { - Record(method, path, null); - return Task.FromResult(Array.Empty()); - } - public ValueTask DisposeAsync() => ValueTask.CompletedTask; } } diff --git a/src/PostIt/PostIt.Tests/EstimateListPageViewModelTests.cs b/src/PostIt/PostIt.Tests/EstimateListPageViewModelTests.cs index eefd59f20..6158b4132 100644 --- a/src/PostIt/PostIt.Tests/EstimateListPageViewModelTests.cs +++ b/src/PostIt/PostIt.Tests/EstimateListPageViewModelTests.cs @@ -11,13 +11,12 @@ public class EstimateListPageViewModelTests { var api = new StubEstimateListApi(); var client = new EstimateApiClient(api, "https://business.example/api/v1/"); - var front = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - var vm = new EstimateListPageViewModel(client, EstimateListPerspective.Client, front); + var vm = new EstimateListPageViewModel(client, EstimateListPerspective.Client); await vm.InitializeAsync(); Assert.Contains("https://business.example/api/v1/estimate/asclient", api.Paths); - Assert.Equal("Devis en attente de signature client", vm.Title); + Assert.Equal("Mes devis à valider", vm.Title); Assert.Equal(2, vm.Estimates.Count); // Most recent first Assert.Equal(2, vm.Estimates[0].Id); @@ -29,13 +28,12 @@ public class EstimateListPageViewModelTests { var api = new StubEstimateListApi(); var client = new EstimateApiClient(api, "https://business.example/api/v1/"); - var front = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - var vm = new EstimateListPageViewModel(client, EstimateListPerspective.Provider, front); + var vm = new EstimateListPageViewModel(client, EstimateListPerspective.Provider); await vm.InitializeAsync(); Assert.Contains("https://business.example/api/v1/estimate/asprovider", api.Paths); - Assert.Equal("Devis en attente de signature fournisseur", vm.Title); + Assert.Equal("Mes devis en attente", vm.Title); Assert.Equal(2, vm.Estimates.Count); } @@ -44,8 +42,7 @@ public class EstimateListPageViewModelTests { var api = new StubEstimateListApi(); var client = new EstimateApiClient(api, "https://business.example/api/v1/"); - var front = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - var vm = new EstimateListPageViewModel(client, EstimateListPerspective.Provider, front); + var vm = new EstimateListPageViewModel(client, EstimateListPerspective.Provider); await vm.InitializeAsync(); @@ -66,8 +63,7 @@ public class EstimateListPageViewModelTests { var api = new StubEstimateListApi { Empty = true }; var client = new EstimateApiClient(api, "https://business.example/api/v1/"); - var front = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - var vm = new EstimateListPageViewModel(client, EstimateListPerspective.Client, front); + var vm = new EstimateListPageViewModel(client, EstimateListPerspective.Client); await vm.InitializeAsync(); diff --git a/src/PostIt/PostIt.Tests/EstimateValidationPageViewModelTests.cs b/src/PostIt/PostIt.Tests/EstimateValidationPageViewModelTests.cs deleted file mode 100644 index 02a6c64bd..000000000 --- a/src/PostIt/PostIt.Tests/EstimateValidationPageViewModelTests.cs +++ /dev/null @@ -1,255 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using PostIt.Controls; -using PostIt.ViewModels; -using Yavsc.Api.Client; -using Yavsc.Models.Billing; - -namespace PostIt.Tests; - -/// -/// Verifies wires the two -/// signature pads (provider + client) into the right -/// call. The view (XAML, rendering) -/// is not tested here; the VM is driven through the same -/// test hooks the signature tests use, -/// and the API client through a recording fake -/// . The test host's -/// TestApp makes GoBackAsync a no-op, so the success paths -/// run to completion without navigation side effects. -/// -public class EstimateValidationPageViewModelTests -{ - private static EstimateDto SampleEstimate(long? commandId = 42) => new() - { - Id = 7, - CommandId = commandId, - CommandType = "Rdv", - Title = "Devis plomberie", - Description = "Réparation", - ClientId = "bob", - OwnerId = "alice", - }; - - private static void DrawOneStroke(SignaturePadControl pad) - { - pad.AppendPointForTest(100, 200); - pad.AppendPointForTest(300, 400); - pad.SealStrokeForTest(); - } - - [Fact] - public async Task Validate_calls_accept_with_strokes_and_command_id() - { - var api = new RecordingApi(); - var front = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - var vm = new EstimateValidationPageViewModel( - SampleEstimate(), EstimateListPerspective.Provider, front); - - var proPad = new SignaturePadControl(); - var clientPad = new SignaturePadControl(); - vm.Attach(proPad, clientPad); - DrawOneStroke(proPad); // provider signs on their writable pad - - await vm.ValidateAsync(); - - Assert.Equal(HttpMethod.Post, api.LastMethod); - Assert.Contains("front/query/accept", api.LastPath); - Assert.Contains("billingCode=Rdv", api.LastPath); - Assert.Contains("queryId=42", api.LastPath); - var body = Assert.IsType(api.LastBody); - Assert.NotNull(body.Strokes); - Assert.NotEmpty(body.Strokes); - Assert.Equal(10_000, body.CoordinateMax); - // The provider perspective declares the Pro side, so the - // server stores the signature under the provider's author - // regardless of role inference. - Assert.Equal(SignatureType.Pro, body.SignatureType); - } - - [Fact] - public async Task Validate_from_client_perspective_declares_client_side() - { - var api = new RecordingApi(); - var front = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - var vm = new EstimateValidationPageViewModel( - SampleEstimate(), EstimateListPerspective.Client, front); - - var cliPad = new SignaturePadControl(); - vm.Attach(new SignaturePadControl(), cliPad); - DrawOneStroke(cliPad); // client signs on their writable pad - - await vm.ValidateAsync(); - - var body = Assert.IsType(api.LastBody); - Assert.Equal(SignatureType.Client, body.SignatureType); // Client side - } - - [Fact] - public async Task Reject_calls_reject_endpoint_with_no_body() - { - var api = new RecordingApi(); - var front = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - var vm = new EstimateValidationPageViewModel( - SampleEstimate(), EstimateListPerspective.Client, front); - - vm.Attach(new SignaturePadControl(), new SignaturePadControl()); - - await vm.RejectAsync(); - - Assert.Equal(HttpMethod.Post, api.LastMethod); - Assert.Contains("front/query/reject", api.LastPath); - Assert.Contains("queryId=42", api.LastPath); - Assert.Null(api.LastBody); - } - - [Fact] - public async Task Validate_without_signature_warns_and_does_not_call() - { - var api = new RecordingApi(); - var front = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - var vm = new EstimateValidationPageViewModel( - SampleEstimate(), EstimateListPerspective.Provider, front); - - vm.Attach(new SignaturePadControl(), new SignaturePadControl()); // empty pads - - await vm.ValidateAsync(); // no throw: returns before GoBack - - Assert.Null(api.LastMethod); - Assert.Contains("signer", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void Validate_command_gates_on_signature_and_command() - { - var api = new RecordingApi(); - var front = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - var vm = new EstimateValidationPageViewModel( - SampleEstimate(commandId: null), EstimateListPerspective.Provider, front); - - var proPad = new SignaturePadControl(); - vm.Attach(proPad, new SignaturePadControl()); - - // No CommandId ⇒ cannot validate even after signing. - DrawOneStroke(proPad); - Assert.False(vm.ValidateCommand.CanExecute(null)); - - var vm2 = new EstimateValidationPageViewModel( - SampleEstimate(), EstimateListPerspective.Provider, front); - var proPad2 = new SignaturePadControl(); - vm2.Attach(proPad2, new SignaturePadControl()); - Assert.False(vm2.ValidateCommand.CanExecute(null)); // no signature yet - DrawOneStroke(proPad2); - Assert.True(vm2.ValidateCommand.CanExecute(null)); - } - - [Fact] - public void RoleLabel_reflects_perspective() - { - var front = new FrontOfficeApiClient(new RecordingApi(), "https://business.example/api/v1/"); - var pro = new EstimateValidationPageViewModel( - SampleEstimate(), EstimateListPerspective.Provider, front); - var cli = new EstimateValidationPageViewModel( - SampleEstimate(), EstimateListPerspective.Client, front); - - Assert.Contains("prestataire", pro.RoleLabel, StringComparison.OrdinalIgnoreCase); - Assert.Contains("client", cli.RoleLabel, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void Only_the_author_pad_is_writable() - { - var front = new FrontOfficeApiClient(new RecordingApi(), "https://business.example/api/v1/"); - - var proVm = new EstimateValidationPageViewModel( - SampleEstimate(), EstimateListPerspective.Provider, front); - var proPad = new SignaturePadControl(); - var cliPad = new SignaturePadControl(); - proVm.Attach(proPad, cliPad); - Assert.False(proPad.IsReadOnly); // provider writes the Pro pad - Assert.True(cliPad.IsReadOnly); - - var cliVm = new EstimateValidationPageViewModel( - SampleEstimate(), EstimateListPerspective.Client, front); - var proPad2 = new SignaturePadControl(); - var cliPad2 = new SignaturePadControl(); - cliVm.Attach(proPad2, cliPad2); - Assert.True(proPad2.IsReadOnly); - Assert.False(cliPad2.IsReadOnly); // client writes the Client pad - } - - [Fact] - public void Load_existing_signatures_renders_both_sides_in_their_pads() - { - var proStrokes = new[] { 1, 1000, 1000 }; - var cliStrokes = new[] { 1, 9000, 9000 }; - var front = new FrontOfficeApiClient(new RecordingApi(), "https://business.example/api/v1/"); - var estimate = SampleEstimate(); - estimate.SignaturePro = new EstimateSignatureDto { Strokes = proStrokes }; - estimate.SignatureClient = new EstimateSignatureDto { Strokes = cliStrokes }; - - // Client perspective: both pads show the stored signatures, but - // only the client pad is writable. - var vm = new EstimateValidationPageViewModel(estimate, EstimateListPerspective.Client, front); - var proPad = new SignaturePadControl(); - var cliPad = new SignaturePadControl(); - vm.Attach(proPad, cliPad); - - Assert.Equal(proStrokes, proPad.Snapshot().Strokes); - Assert.Equal(cliStrokes, cliPad.Snapshot().Strokes); - Assert.True(vm.HasProSignature); - Assert.True(vm.HasClientSignature); - } - - [Fact] - public void Load_existing_pro_signature_renders_in_pro_pad_only() - { - var strokes = new[] { 1, 5000, 5000 }; - var front = new FrontOfficeApiClient(new RecordingApi(), "https://business.example/api/v1/"); - var estimate = SampleEstimate(); - estimate.SignaturePro = new EstimateSignatureDto { Strokes = strokes, CoordinateMax = 10_000 }; - var vm = new EstimateValidationPageViewModel(estimate, EstimateListPerspective.Provider, front); - - var proPad = new SignaturePadControl(); - var cliPad = new SignaturePadControl(); - vm.Attach(proPad, cliPad); - - Assert.Equal(strokes, proPad.Snapshot().Strokes); - Assert.True(cliPad.Snapshot().IsEmpty); - Assert.True(vm.HasProSignature); - Assert.False(vm.HasClientSignature); - } - - 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 Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - LastMethod = method; - LastPath = path; - LastBody = body; - 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; - } -} \ No newline at end of file diff --git a/src/PostIt/PostIt.Tests/FrontOfficeApiClientTests.cs b/src/PostIt/PostIt.Tests/FrontOfficeApiClientTests.cs deleted file mode 100644 index 02f6ae149..000000000 --- a/src/PostIt/PostIt.Tests/FrontOfficeApiClientTests.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System.Net.Http; -using Yavsc.Api.Client; - -namespace PostIt.Tests; - -/// -/// Verifies issues the right -/// api/v1/front/query/accept / .../reject requests with -/// the PostIt signature payload, using a recording fake -/// (no network). -/// -public class FrontOfficeApiClientTests -{ - private static QueryAcceptanceRequestDto SampleBody() => new() - { - Strokes = new[] { 2, 100, 200, 300, 400 }, - CoordinateMax = 10_000, - CapturedAtUtc = new DateTime(2026, 9, 21, 12, 0, 0, DateTimeKind.Utc), - }; - - [Fact] - public async Task Accept_posts_to_accept_endpoint_with_signature_body() - { - var api = new RecordingApi(); - var client = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - - var body = SampleBody(); - await client.AcceptQueryAsync("Rdv", 42, body); - - Assert.Equal(HttpMethod.Post, api.LastMethod); - Assert.Equal( - "https://business.example/api/v1/front/query/accept?billingCode=Rdv&queryId=42", - api.LastPath); - Assert.Same(body, api.LastBody); - } - - [Fact] - public async Task Reject_posts_to_reject_endpoint_with_no_body() - { - var api = new RecordingApi(); - var client = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - - await client.RejectQueryAsync("Rdv", 42); - - Assert.Equal(HttpMethod.Post, api.LastMethod); - Assert.Equal( - "https://business.example/api/v1/front/query/reject?billingCode=Rdv&queryId=42", - api.LastPath); - Assert.Null(api.LastBody); - } - - [Fact] - public async Task Accept_returns_deserialized_response() - { - var api = new RecordingApi - { - Response = new QueryAcceptanceResponseDto - { - QueryId = 42, - Status = "Accepted", - Signature = new QuerySignatureRefDto { Id = 9, EstimateId = 5, Type = "Pro" }, - }, - }; - var client = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - - var response = await client.AcceptQueryAsync("Rdv", 42, SampleBody()); - - Assert.Equal(42, response.QueryId); - Assert.Equal("Accepted", response.Status); - Assert.NotNull(response.Signature); - Assert.Equal("Pro", response.Signature!.Type); - } - - [Fact] - public async Task Accept_rejects_invalid_arguments() - { - var api = new RecordingApi(); - var client = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - - await Assert.ThrowsAsync(() => client.AcceptQueryAsync("", 42, SampleBody())); - await Assert.ThrowsAsync(() => client.AcceptQueryAsync("Rdv", 0, SampleBody())); - await Assert.ThrowsAsync(() => client.AcceptQueryAsync("Rdv", 42, null!)); - await Assert.ThrowsAsync(() => client.RejectQueryAsync("", 42)); - await Assert.ThrowsAsync(() => client.RejectQueryAsync("Rdv", 0)); - } - - [Fact] - public async Task GetEstimatePdf_gets_estimate_pdf_endpoint() - { - var api = new RecordingApi(); - var client = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - - await client.GetEstimatePdfAsync(42); - - Assert.Equal(HttpMethod.Get, api.LastMethod); - Assert.Equal( - "https://business.example/api/v1/front/query/42/estimate.pdf", - api.LastPath); - } - - [Fact] - public async Task GetEstimateTex_gets_estimate_tex_endpoint() - { - var api = new RecordingApi(); - var client = new FrontOfficeApiClient(api, "https://business.example/api/v1/"); - - await client.GetEstimateTexAsync(42); - - Assert.Equal(HttpMethod.Get, api.LastMethod); - Assert.Equal( - "https://business.example/api/v1/front/query/42/estimate.tex", - api.LastPath); - } - - 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 QueryAcceptanceResponseDto? Response { get; set; } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - LastMethod = method; - LastPath = path; - LastBody = body; - - var response = Response ?? new QueryAcceptanceResponseDto(); - return Task.FromResult((T)(object)response); - } - - 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 Task DownloadAsync(HttpMethod method, string path, CancellationToken ct = default) - { - LastMethod = method; - LastPath = path; - LastBody = null; - return Task.FromResult(Array.Empty()); - } - - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - } -} \ No newline at end of file diff --git a/src/PostIt/PostIt.Tests/MyFilesPageHeadlessTests.cs b/src/PostIt/PostIt.Tests/MyFilesPageHeadlessTests.cs deleted file mode 100644 index 72619f3c5..000000000 --- a/src/PostIt/PostIt.Tests/MyFilesPageHeadlessTests.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System.Net.Http; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Headless.XUnit; -using Avalonia.VisualTree; -using CommunityToolkit.Mvvm.Input; -using PostIt.ViewModels; -using PostIt.Views; -using Yavsc.Api.Client; - -namespace PostIt.Tests; - -/// -/// Headless UI coverage for the "Mes fichiers" page -/// (). The user reported that once a file -/// is listed, "l'item est inerte au click, et ses boutons restent -/// grisés, désactivés" — neither the row nor its action buttons -/// (Télécharger / Supprimer) respond to a click. -/// -/// These tests mount a real under -/// the Avalonia headless platform with a stub -/// that lists one file, then walk the -/// realised visual tree to find the row's action buttons and -/// assert the two things that would silently break and reproduce -/// the "grayed / inert" symptom: -/// -/// The Command binding -/// ($parent[vm:MyFilesViewModel].DownloadFileCommand) -/// resolved to a non-null command — a null command leaves the -/// button disabled (grayed) and click-silent. -/// The command is armed -/// (CanExecute true) for the listed file — an -/// AsyncRelayCommand with -/// AllowConcurrentExecutions=false (the -/// -/// default) disables itself while running; a button stuck -/// "running" is exactly the grayed-and-inert report. -/// Executing the command actually invokes the -/// file download / delete on the API client — the row -/// "activates" on click instead of being inert. -/// -/// The OS save picker is absent under headless, so -/// FileSaveHelpers.SaveAsync throws or cancels and the -/// download command's catch sets an error status — but the -/// API download is awaited before the picker, so the call -/// is recorded regardless. We do not await the command's -/// ExecutionTask to completion, to avoid blocking on the -/// (platform-dependent) picker. -/// -public class MyFilesPageHeadlessTests -{ - private static (MyFilesPage page, MyFilesViewModel vm, StubFsApi api) CreatePage() - { - var api = new StubFsApi(); - var fsClient = new UserFilesApiClient(api, "https://blogs.example/api/v1/"); - var vm = new MyFilesViewModel(fsClient); - var page = new MyFilesPage { DataContext = vm }; - return (page, vm, api); - } - - /// - /// Mount the page the way the app does — pushed onto a - /// shown in a - /// (a realizes its content template via - /// the navigation host, cf. RdvPageHeadlessTests) — after - /// listing a single file by awaiting - /// . Populating - /// before the first layout - /// pass lets the ItemsControl realise its item templates - /// under headless. - /// - private static async Task<(MyFilesPage page, MyFilesViewModel vm, StubFsApi api)> MountAsync() - { - var (page, vm, api) = CreatePage(); - await vm.InitializeAsync(); - var nav = new NavigationPage(); - nav.PushAsync(page).GetAwaiter().GetResult(); - var window = new Window { Content = nav }; - window.Show(); - // Let bindings / the ItemsControl item templates apply. - await Task.Delay(50); - return (page, vm, api); - } - - /// - /// Find the first action button in a file row whose content - /// matches . The file rows live in an - /// ItemsControl DataTemplate (no x:Name), so we - /// walk the visual tree. - /// - private static Button? FindRowButton(MyFilesPage page, string content) - => page.GetVisualDescendants() - .OfType public enum QueryStatus: int { - Inserted = 0, - Rejected = 1, - Accepted = 2, - InProgress = 3, + Inserted, + Rejected, + Accepted, + InProgress, // final states - Failed = 4, - Success = 5, - // party-specific acceptance (see summary) - ProAccepted = 6, - ClientAccepted = 7, + Failed, + Success } -} \ No newline at end of file +} diff --git a/src/Yavsc.Abstract/Yavsc.Abstract.csproj b/src/Yavsc.Abstract/Yavsc.Abstract.csproj index 3de58d2db..4df6c53c2 100644 --- a/src/Yavsc.Abstract/Yavsc.Abstract.csproj +++ b/src/Yavsc.Abstract/Yavsc.Abstract.csproj @@ -11,7 +11,7 @@ latest 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 \ No newline at end of file diff --git a/src/Yavsc.Api.Client/Dtos/EstimateDtos.cs b/src/Yavsc.Api.Client/Dtos/EstimateDtos.cs index 3e7a15052..43c09d94a 100644 --- a/src/Yavsc.Api.Client/Dtos/EstimateDtos.cs +++ b/src/Yavsc.Api.Client/Dtos/EstimateDtos.cs @@ -39,39 +39,6 @@ public sealed class EstimateDto public string CommandType { get; set; } = string.Empty; public DateTime ProviderValidationDate { get; set; } public DateTime ClientValidationDate { get; set; } - - /// - /// The provider's signature on this estimate, or null when the - /// provider has not signed. Carries the PostIt wire-format - /// so the pad can - /// repaint a previously drawn signature on reopen without a second - /// round-trip. - /// - public EstimateSignatureDto? SignaturePro { get; set; } - - /// - /// The client's signature on this estimate, or null when the - /// client has not signed. - /// - public EstimateSignatureDto? SignatureClient { get; set; } -} - -/// -/// One stored signature on an estimate, mirroring the JSON shape of -/// the server-side Yavsc.Models.Billing.Signature entity. The -/// are the same PostIt wire-format payload that -/// was captured and are loaded back into the pad unchanged. The side -/// (Pro/Client) is implicit in which property of -/// carries this DTO, so no Type -/// field is needed. -/// -public sealed class EstimateSignatureDto -{ - public long Id { get; set; } - public long EstimateId { get; set; } - public int CoordinateMax { get; set; } - public DateTime CapturedAtUtc { get; set; } - public int[]? Strokes { get; set; } } /// diff --git a/src/Yavsc.Api.Client/Dtos/QueryAcceptanceDto.cs b/src/Yavsc.Api.Client/Dtos/QueryAcceptanceDto.cs deleted file mode 100644 index dffbbf3ad..000000000 --- a/src/Yavsc.Api.Client/Dtos/QueryAcceptanceDto.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using Yavsc.Models.Billing; - -namespace Yavsc.Api.Client; - -/// -/// Body of POST api/v1/front/query/accept and -/// .../query/reject, mirroring the server-side -/// Yavsc.ViewModels.FrontOffice.QueryAcceptanceRequest. -/// is optional: when non-empty on accept it is -/// the PostIt wire-format signature captured with the acceptance; null -/// or empty means no signature (the only valid value on reject). -/// -public sealed class QueryAcceptanceRequestDto -{ - public int[]? Strokes { get; set; } - - public int CoordinateMax { get; set; } = 10_000; - - public DateTime? CapturedAtUtc { get; set; } - - /// - /// Which signature is being submitted: - /// (the provider) or - /// . The PostIt client sets this - /// from the perspective the page was opened in (provider signs Pro, - /// client signs Client), so the server stores the signature under - /// the right author even when one user is both parties. Null lets - /// the server infer the role from the caller's identity. - /// - public SignatureType? SignatureType { get; set; } -} - -/// -/// Response of POST .../query/accept / .../query/reject -/// (Ok(new { queryId, status, signature = ... })). The -/// ref is non-null only when a signature was -/// staged on accept. -/// -public sealed class QueryAcceptanceResponseDto -{ - public long QueryId { get; set; } - - public string? Status { get; set; } - - public QuerySignatureRefDto? Signature { get; set; } -} - -public sealed class QuerySignatureRefDto -{ - public long Id { get; set; } - - public long EstimateId { get; set; } - - public string? Type { get; set; } -} \ No newline at end of file diff --git a/src/Yavsc.Api.Client/Dtos/UserFilesDtos.cs b/src/Yavsc.Api.Client/Dtos/UserFilesDtos.cs deleted file mode 100644 index 2243bbad2..000000000 --- a/src/Yavsc.Api.Client/Dtos/UserFilesDtos.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Yavsc.Api.Client; - -/// -/// Représentation d'un répertoire de l'espace de stockage personnel, -/// renvoyée par GET api/v1/fs/{subdir} (host Blogs). Ne dépend pas -/// des helpers serveur : c'est un pur DTO de désérialisation. -/// -public sealed class UserDirectoryDto -{ - public string? UserName { get; set; } - public string? SubPath { get; set; } - public FileEntryDto[] Files { get; set; } = Array.Empty(); - public DirEntryDto[] SubDirectories { get; set; } = Array.Empty(); -} - -public sealed class FileEntryDto -{ - /// Identifiant de la ligne UploadedFile, pour l'attachement par référence. - public long? Id { get; set; } - public string? Name { get; set; } - public long Size { get; set; } - public DateTime LastModified { get; set; } -} - -public sealed class DirEntryDto -{ - public string? Name { get; set; } -} - -/// -/// Métadonnées d'une pièce jointe (fichier de l'espace perso rattaché à -/// un devis ou une demande), renvoyées par les endpoints d'attachement. -/// -public sealed class AttachmentDto -{ - public long FileId { get; set; } - /// Chemin relatif dans l'espace perso du propriétaire. - public string? Path { get; set; } - public long Size { get; set; } - public string? ContentType { get; set; } - public string? OwnerId { get; set; } -} - -/// -/// Métadonnées d'un fichier reçu après un téléversement multipart vers -/// POST api/v1/fs/{subdir}. Porte notamment l'identifiant -/// de la ligne UploadedFile créée côté serveur. -/// -public sealed class FileReceivedDto -{ - public long? FileId { get; set; } - public string? FileName { get; set; } - public long Length { get; set; } - public string? ContentType { get; set; } - public bool QuotaOffense { get; set; } - public bool Overridden { get; set; } -} - -/// -/// Contenu d'un fichier à téléverser (nom, octets, type MIME), à l'image -/// de mais pour l'espace perso générique. -/// -public sealed record UploadFile(string FileName, byte[] Content, string? ContentType); \ No newline at end of file diff --git a/src/Yavsc.Api.Client/EstimateApiClient.cs b/src/Yavsc.Api.Client/EstimateApiClient.cs index fc3d41fc9..4a80bbe62 100644 --- a/src/Yavsc.Api.Client/EstimateApiClient.cs +++ b/src/Yavsc.Api.Client/EstimateApiClient.cs @@ -113,41 +113,6 @@ public sealed class EstimateApiClient return _api.CallAsync(HttpMethod.Delete, Absolute($"{PathPrefix}/{id}"), ct: ct); } - /// - /// Liste les pièces jointes (fichiers de l'espace perso du fournisseur) - /// rattachées au devis par référence - /// (GET estimate/{id}/attachments). - /// - public Task> GetAttachmentsAsync(long id, CancellationToken ct = default) - { - if (id <= 0) throw new ArgumentOutOfRangeException(nameof(id)); - return _api.CallAsync>(HttpMethod.Get, Absolute($"{PathPrefix}/{id}/attachments"), ct: ct); - } - - /// - /// Attache un fichier de l'espace perso du fournisseur au devis - /// par référence (POST estimate/{id}/attachments). - /// Réservé au fournisseur côté serveur. - /// - public Task AttachFileAsync(long id, long fileId, CancellationToken ct = default) - { - if (id <= 0) throw new ArgumentOutOfRangeException(nameof(id)); - if (fileId <= 0) throw new ArgumentOutOfRangeException(nameof(fileId)); - return _api.CallAsync(HttpMethod.Post, Absolute($"{PathPrefix}/{id}/attachments"), - body: new { fileId }, ct: ct); - } - - /// - /// Détache un fichier du devis - /// (DELETE estimate/{id}/attachments/{fileId}). Réservé au fournisseur. - /// - public Task DetachFileAsync(long id, long fileId, CancellationToken ct = default) - { - if (id <= 0) throw new ArgumentOutOfRangeException(nameof(id)); - if (fileId <= 0) throw new ArgumentOutOfRangeException(nameof(fileId)); - return _api.CallAsync(HttpMethod.Delete, Absolute($"{PathPrefix}/{id}/attachments/{fileId}"), ct: ct); - } - private string Absolute(string relativePath) => new Uri(ResolveBusinessBaseAddress(), relativePath).ToString(); private Uri ResolveBusinessBaseAddress() diff --git a/src/Yavsc.Api.Client/FrontOfficeApiClient.cs b/src/Yavsc.Api.Client/FrontOfficeApiClient.cs deleted file mode 100644 index 2e3877ef3..000000000 --- a/src/Yavsc.Api.Client/FrontOfficeApiClient.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; - -namespace Yavsc.Api.Client; - -/// -/// HTTP client for the front-office query routes -/// (api/v1/front/query/accept / .../reject, served by -/// ). Same -/// DTO↔path mapper shape as : transport -/// (base URL, JSON, Bearer auth, silent refresh on 401) is delegated -/// to . -/// -/// The server resolves the caller's role (provider vs client vs admin) -/// from the authenticated user compared to the query's -/// PerformerId/ClientId; the client does not declare a -/// role. is the estimate's -/// and -/// is its . -/// -public sealed class FrontOfficeApiClient -{ - private const string PathPrefix = "front"; - - private readonly IYavscApiClient _api; - private readonly Func _businessBaseAddress; - - public FrontOfficeApiClient(IYavscApiClient api, string businessBaseAddress) - : this(api, () => businessBaseAddress) - { - } - - public FrontOfficeApiClient(IYavscApiClient api, Func businessBaseAddress) - { - _api = api ?? throw new ArgumentNullException(nameof(api)); - _businessBaseAddress = businessBaseAddress ?? throw new ArgumentNullException(nameof(businessBaseAddress)); - _ = ResolveBusinessBaseAddress(); - } - - /// - /// Accept (validate) the query under - /// billing code . When - /// carries a non-empty signature, the - /// server stages it on the query's linked estimate and stamps the - /// matching validation date. - /// - public async Task AcceptQueryAsync( - string billingCode, - long queryId, - QueryAcceptanceRequestDto body, - CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(billingCode)) - throw new ArgumentException("billingCode is required.", nameof(billingCode)); - if (queryId <= 0) - throw new ArgumentOutOfRangeException(nameof(queryId)); - if (body is null) - throw new ArgumentNullException(nameof(body)); - - var path = $"{PathPrefix}/query/accept?billingCode={Uri.EscapeDataString(billingCode)}&queryId={queryId}"; - var response = await _api.CallAsync( - HttpMethod.Post, - Absolute(path), - body: body, - ct: ct).ConfigureAwait(false); - - return response ?? new QueryAcceptanceResponseDto { QueryId = queryId }; - } - - /// - /// Reject the query under billing code - /// . A signature is not meaningful on - /// a rejection; the server refuses one with 400, so this sends no - /// body. - /// - public async Task RejectQueryAsync( - string billingCode, - long queryId, - CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(billingCode)) - throw new ArgumentException("billingCode is required.", nameof(billingCode)); - if (queryId <= 0) - throw new ArgumentOutOfRangeException(nameof(queryId)); - - var path = $"{PathPrefix}/query/reject?billingCode={Uri.EscapeDataString(billingCode)}&queryId={queryId}"; - var response = await _api.CallAsync( - HttpMethod.Post, - Absolute(path), - body: null, - ct: ct).ConfigureAwait(false); - - return response ?? new QueryAcceptanceResponseDto { QueryId = queryId }; - } - - /// - /// Download the estimate linked to query - /// as a LaTeX (.tex) source document - /// (GET api/v1/front/query/{queryId}/estimate.tex). Returns - /// the raw TeX bytes (UTF-8 text). - /// - public Task GetEstimateTexAsync(long queryId, CancellationToken ct = default) - { - if (queryId <= 0) throw new ArgumentOutOfRangeException(nameof(queryId)); - var path = $"{PathPrefix}/query/{queryId}/estimate.tex"; - return _api.DownloadAsync(HttpMethod.Get, Absolute(path), ct); - } - - /// - /// Download the estimate linked to query - /// as a compiled PDF document - /// (GET api/v1/front/query/{queryId}/estimate.pdf). Returns - /// the raw PDF bytes. Requires texi2pdf on the server host; - /// a non-2xx response throws . - /// - public Task GetEstimatePdfAsync(long queryId, CancellationToken ct = default) - { - if (queryId <= 0) throw new ArgumentOutOfRangeException(nameof(queryId)); - var path = $"{PathPrefix}/query/{queryId}/estimate.pdf"; - return _api.DownloadAsync(HttpMethod.Get, Absolute(path), ct); - } - - /// - /// Liste les pièces jointes (fichiers de l'espace perso du client) - /// rattachées à la demande par référence - /// (GET front/query/{queryId}/attachments). - /// - public Task> GetQueryAttachmentsAsync(string billingCode, long queryId, CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(billingCode)) - throw new ArgumentException("billingCode is required.", nameof(billingCode)); - if (queryId <= 0) throw new ArgumentOutOfRangeException(nameof(queryId)); - var path = $"{PathPrefix}/query/{queryId}/attachments?billingCode={Uri.EscapeDataString(billingCode)}"; - return _api.CallAsync>(HttpMethod.Get, Absolute(path), ct: ct); - } - - /// - /// Attache un fichier de l'espace perso du client à sa demande - /// (POST front/query/{queryId}/attachments). Réservé au client. - /// - public Task AttachQueryFileAsync(string billingCode, long queryId, long fileId, CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(billingCode)) - throw new ArgumentException("billingCode is required.", nameof(billingCode)); - if (queryId <= 0) throw new ArgumentOutOfRangeException(nameof(queryId)); - if (fileId <= 0) throw new ArgumentOutOfRangeException(nameof(fileId)); - var path = $"{PathPrefix}/query/{queryId}/attachments?billingCode={Uri.EscapeDataString(billingCode)}"; - return _api.CallAsync(HttpMethod.Post, Absolute(path), body: new { fileId }, ct: ct); - } - - /// - /// Détache un fichier de la demande - /// (DELETE front/query/{queryId}/attachments/{fileId}). Réservé au client. - /// - public Task DetachQueryFileAsync(string billingCode, long queryId, long fileId, CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(billingCode)) - throw new ArgumentException("billingCode is required.", nameof(billingCode)); - if (queryId <= 0) throw new ArgumentOutOfRangeException(nameof(queryId)); - if (fileId <= 0) throw new ArgumentOutOfRangeException(nameof(fileId)); - var path = $"{PathPrefix}/query/{queryId}/attachments/{fileId}?billingCode={Uri.EscapeDataString(billingCode)}"; - return _api.CallAsync(HttpMethod.Delete, Absolute(path), 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); - } -} \ No newline at end of file diff --git a/src/Yavsc.Api.Client/IYavscApiClient.cs b/src/Yavsc.Api.Client/IYavscApiClient.cs index e2dd56acd..1d470b682 100644 --- a/src/Yavsc.Api.Client/IYavscApiClient.cs +++ b/src/Yavsc.Api.Client/IYavscApiClient.cs @@ -73,23 +73,4 @@ public interface IYavscApiClient : IAsyncDisposable string path, Func contentFactory, CancellationToken ct = default); - - /// - /// Download a binary resource (PDF, TeX, image, …). Unlike - /// , - /// which JSON-decodes the response, this returns the raw response - /// bytes. The same Bearer-token attach + 401-refresh-retry-once - /// contract applies. - /// - /// - /// A default interface implementation is provided so that test - /// fakes that do not exercise binary download still compile without - /// declaring the member; the real - /// overrides it with the authenticated transport. - /// - Task DownloadAsync( - HttpMethod method, - string path, - CancellationToken ct = default) - => Task.FromResult(Array.Empty()); } diff --git a/src/Yavsc.Api.Client/UserFilesApiClient.cs b/src/Yavsc.Api.Client/UserFilesApiClient.cs deleted file mode 100644 index 0b7e63671..000000000 --- a/src/Yavsc.Api.Client/UserFilesApiClient.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; - -namespace Yavsc.Api.Client; - -/// -/// Client HTTP de l'espace de stockage personnel -/// (api/v1/fs, servi par le host Yavsc.Blogs). Même forme -/// que : le transport (URL de base, JSON, -/// Bearer, refresh silencieux sur 401) est délégué à -/// ; cette classe n'est qu'un mappeur -/// DTO↔chemin. -/// -/// Convention d'URL. Le pathPrefix est relatif au -/// segment de version déjà porté par l'URL de base -/// (…/api/v1/) : "fs" résout en …/api/v1/fs, qui -/// correspond au [Route(APIPrefix + "/fs")] de -/// FileSystemApiController. Ne pas ré-inclure api/. -/// -public sealed class UserFilesApiClient -{ - private const string PathPrefix = "fs"; - - private readonly IYavscApiClient _api; - private readonly Uri _baseAddress; - - public UserFilesApiClient(IYavscApiClient api, string blogsBaseAddress) - { - _api = api ?? throw new ArgumentNullException(nameof(api)); - if (string.IsNullOrEmpty(blogsBaseAddress)) - throw new ArgumentException("Base address is required.", nameof(blogsBaseAddress)); - - _baseAddress = new Uri(blogsBaseAddress); - _api.Http.BaseAddress = _baseAddress; - } - - /// - /// Liste le contenu du sous-répertoire de - /// l'espace perso de l'utilisateur (GET fs/{subdir}). - /// - public Task ListAsync(string? subdir = null, CancellationToken ct = default) - { - var path = string.IsNullOrWhiteSpace(subdir) ? PathPrefix : $"{PathPrefix}/{subdir.TrimStart('/')}"; - return _api.CallAsync(HttpMethod.Get, path, ct: ct); - } - - /// - /// Téléverse des fichiers dans le sous-répertoire - /// (POST fs/{subdir} multipart). - /// Renvoie les métadonnées des fichiers reçus, dont l'identifiant - /// servant à l'attachement. - /// - public Task> UploadAsync(string subdir, IReadOnlyCollection files, CancellationToken ct = default) - { - if (files is null || files.Count == 0) - throw new ArgumentException("At least one file is required.", nameof(files)); - - var path = string.IsNullOrWhiteSpace(subdir) - ? PathPrefix - : $"{PathPrefix}/{subdir.TrimStart('/')}"; - - return _api.CallAsync>(HttpMethod.Post, path, () => CreateMultipartContent(files), ct); - } - - /// - /// Supprime un fichier ou répertoire de l'espace perso - /// (DELETE fs/{path}). - /// - public Task DeleteAsync(string path, CancellationToken ct = default) - { - if (string.IsNullOrWhiteSpace(path)) - throw new ArgumentException("Path is required.", nameof(path)); - return _api.CallAsync(HttpMethod.Delete, $"{PathPrefix}/{path.TrimStart('/')}", ct: ct); - } - - /// - /// Télécharge une pièce jointe référencée par son identifiant - /// / - /// (GET fs/file/{fileId}). Renvoie les octets bruts ; le serveur - /// (host Blogs) autorise l'appelant (propriétaire ou partie au - /// devis/demande auquel le fichier est rattaché). - /// - public Task DownloadFileAsync(long fileId, CancellationToken ct = default) - { - if (fileId <= 0) throw new ArgumentOutOfRangeException(nameof(fileId)); - return _api.DownloadAsync(HttpMethod.Get, $"{PathPrefix}/file/{fileId}", ct); - } - - /// - /// Construit le corps multipart/form-data d'un téléversement : - /// une part file par fichier (type MIME retombant sur - /// application/octet-stream). Exposé public pour les - /// tests backend qui assertent la forme filaire. - /// - public static HttpContent CreateMultipartContent(IReadOnlyCollection files) - { - var content = new MultipartFormDataContent(); - 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; - } -} \ No newline at end of file diff --git a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj index 80cdf0ae5..f0cdb55c9 100644 --- a/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj +++ b/src/Yavsc.Api.Client/Yavsc.Api.Client.csproj @@ -17,8 +17,8 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 diff --git a/src/test/Yavsc.Api.Tests/ActivityApiControllerTests.cs b/src/Yavsc.Api.Test/ActivityApiControllerTests.cs similarity index 100% rename from src/test/Yavsc.Api.Tests/ActivityApiControllerTests.cs rename to src/Yavsc.Api.Test/ActivityApiControllerTests.cs diff --git a/src/test/Yavsc.Api.Tests/ApiCollection.cs b/src/Yavsc.Api.Test/ApiCollection.cs similarity index 100% rename from src/test/Yavsc.Api.Tests/ApiCollection.cs rename to src/Yavsc.Api.Test/ApiCollection.cs diff --git a/src/test/Yavsc.Api.Tests/BillingControllerTests.cs b/src/Yavsc.Api.Test/BillingControllerTests.cs similarity index 100% rename from src/test/Yavsc.Api.Tests/BillingControllerTests.cs rename to src/Yavsc.Api.Test/BillingControllerTests.cs diff --git a/src/Yavsc.Api.Test/Directory.Packages.props b/src/Yavsc.Api.Test/Directory.Packages.props new file mode 100644 index 000000000..c2edf9eae --- /dev/null +++ b/src/Yavsc.Api.Test/Directory.Packages.props @@ -0,0 +1,7 @@ + + + + diff --git a/src/test/Yavsc.Api.Tests/EstimateApiControllerTests.cs b/src/Yavsc.Api.Test/EstimateApiControllerTests.cs similarity index 85% rename from src/test/Yavsc.Api.Tests/EstimateApiControllerTests.cs rename to src/Yavsc.Api.Test/EstimateApiControllerTests.cs index 568175610..cb945f51a 100644 --- a/src/test/Yavsc.Api.Tests/EstimateApiControllerTests.cs +++ b/src/Yavsc.Api.Test/EstimateApiControllerTests.cs @@ -209,12 +209,9 @@ public sealed class EstimateApiControllerTests : IClassFixture /// Seeds an estimate owned by alice (the provider) for the given - /// client, optionally already validated by the client. By default the - /// estimate is also provider-validated (i.e. the provider has submitted - /// it); pass = false to seed a - /// freshly inserted request the provider hasn't worked on yet. + /// client, optionally already validated by the client. /// - private long SeedEstimate(string clientId, bool clientValidated, bool providerValidated = true) + private long SeedEstimate(string clientId, bool clientValidated) { using var scope = _fixture.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); @@ -232,7 +229,6 @@ public sealed class EstimateApiControllerTests : IClassFixture 5104; @@ -167,32 +168,25 @@ public sealed class ApiWebServerFixture : WebHostFixture private static string BuildAdminConnectionString() { - // The admin connection string (host + credentials) is supplied - // via environment, never embedded in source — committing a - // real dev password would leak it into git history. The Npgsql - // path is opt-in, so requiring the env var is fine: a missing - // value produces a clear failure rather than a silent fallback - // onto someone's local dev database. var configured = Environment.GetEnvironmentVariable(NpgsqlAdminConnectionEnvVar); - if (string.IsNullOrWhiteSpace(configured)) - throw new InvalidOperationException( - $"{NpgsqlAdminConnectionEnvVar} is not set. The Npgsql test " - + "provider requires an admin PostgreSQL connection string " - + "in that environment variable (e.g. " - + "'Server=localhost;Port=5432;Database=postgres;" - + "Username=yavscdev;Password=...;Include Error Detail=true')."); + var source = string.IsNullOrWhiteSpace(configured) + ? DefaultDevelopmentConnectionString + : configured; - var builder = new NpgsqlConnectionStringBuilder(configured) + var builder = new NpgsqlConnectionStringBuilder(source) { Pooling = false, IncludeErrorDetail = true }; - // The admin connection targets the maintenance database - // (postgres) so we can CREATE/DROP the dedicated test database; - // whatever database the env var names is overridden here. - if (string.IsNullOrWhiteSpace(builder.Database)) + if (string.IsNullOrWhiteSpace(configured)) + { builder.Database = "postgres"; + } + else if (string.IsNullOrWhiteSpace(builder.Database)) + { + builder.Database = "postgres"; + } return builder.ToString(); } diff --git a/src/Yavsc.Api.Test/FrontOfficeApiControllerTests.cs b/src/Yavsc.Api.Test/FrontOfficeApiControllerTests.cs new file mode 100644 index 000000000..97ed2e61c --- /dev/null +++ b/src/Yavsc.Api.Test/FrontOfficeApiControllerTests.cs @@ -0,0 +1,62 @@ +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/test/Yavsc.Api.Tests/HairCutQueryApiControllerTests.cs b/src/Yavsc.Api.Test/HairCutQueryApiControllerTests.cs similarity index 100% rename from src/test/Yavsc.Api.Tests/HairCutQueryApiControllerTests.cs rename to src/Yavsc.Api.Test/HairCutQueryApiControllerTests.cs diff --git a/src/test/Yavsc.Api.Tests/HairMultiCutQueryApiControllerTests.cs b/src/Yavsc.Api.Test/HairMultiCutQueryApiControllerTests.cs similarity index 100% rename from src/test/Yavsc.Api.Tests/HairMultiCutQueryApiControllerTests.cs rename to src/Yavsc.Api.Test/HairMultiCutQueryApiControllerTests.cs diff --git a/src/test/Yavsc.Api.Tests/HomeControllerTests.cs b/src/Yavsc.Api.Test/HomeControllerTests.cs similarity index 100% rename from src/test/Yavsc.Api.Tests/HomeControllerTests.cs rename to src/Yavsc.Api.Test/HomeControllerTests.cs diff --git a/src/test/Yavsc.Api.Tests/RdvQueryApiControllerTests.cs b/src/Yavsc.Api.Test/RdvQueryApiControllerTests.cs similarity index 100% rename from src/test/Yavsc.Api.Tests/RdvQueryApiControllerTests.cs rename to src/Yavsc.Api.Test/RdvQueryApiControllerTests.cs diff --git a/src/test/Yavsc.Api.Tests/Yavsc.Api.Tests.csproj b/src/Yavsc.Api.Test/Yavsc.Api.Test.csproj similarity index 63% rename from src/test/Yavsc.Api.Tests/Yavsc.Api.Tests.csproj rename to src/Yavsc.Api.Test/Yavsc.Api.Test.csproj index cf41a7d4b..ebe96dfa8 100644 --- a/src/test/Yavsc.Api.Tests/Yavsc.Api.Tests.csproj +++ b/src/Yavsc.Api.Test/Yavsc.Api.Test.csproj @@ -4,12 +4,12 @@ enable enable false - Yavsc.Api.Tests + Yavsc.Api.Test true 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 @@ -19,12 +19,13 @@ + - - - - + + + + diff --git a/src/Yavsc.Api/Controllers/Business/BillingController.cs b/src/Yavsc.Api/Controllers/Business/BillingController.cs index aa195712d..ba099e7c7 100644 --- a/src/Yavsc.Api/Controllers/Business/BillingController.cs +++ b/src/Yavsc.Api/Controllers/Business/BillingController.cs @@ -157,8 +157,6 @@ namespace Yavsc.ApiControllers .Where(q => allowedActivityCodes.Contains(q.ActivityCode)) .Where(q => q.Status == QueryStatus.Inserted || q.Status == QueryStatus.Accepted - || q.Status == QueryStatus.ProAccepted - || q.Status == QueryStatus.ClientAccepted || q.Status == QueryStatus.InProgress) .Cast() .ToList() @@ -171,8 +169,6 @@ namespace Yavsc.ApiControllers .Where(q => allowedActivityCodes.Contains(q.ActivityCode)) .Where(q => q.Status == QueryStatus.Inserted || q.Status == QueryStatus.Accepted - || q.Status == QueryStatus.ProAccepted - || q.Status == QueryStatus.ClientAccepted || q.Status == QueryStatus.InProgress) .Cast() .ToList() @@ -185,8 +181,6 @@ namespace Yavsc.ApiControllers .Where(q => allowedActivityCodes.Contains(q.ActivityCode)) .Where(q => q.Status == QueryStatus.Inserted || q.Status == QueryStatus.Accepted - || q.Status == QueryStatus.ProAccepted - || q.Status == QueryStatus.ClientAccepted || q.Status == QueryStatus.InProgress) .Cast() .ToList() @@ -335,9 +329,9 @@ namespace Yavsc.ApiControllers return new ChallengeResult(); } - var filename = AbstractFileSystemHelpers.SignFileNameFormat("cli", billingCode, id); + var filename = AbstractFileSystemHelpers.SignFileNameFormat("pro", billingCode, id); FileInfo fi = new FileInfo(Path.Combine(siteSettings.Bills, filename)); - if (!fi.Exists) return NotFound(new { Error = "Client signature not found" }); + if (!fi.Exists) return NotFound(new { Error = "Professional signature not found" }); return File(fi.OpenRead(), "application/x-pdf", filename); } @@ -407,12 +401,79 @@ namespace Yavsc.ApiControllers Strokes = body.Strokes, }; - // The Signature row is staged by the shared persister; we - // SaveChanges here so the signature write is one - // transaction. The Strokes column is the source of truth — - // there is no on-disk copy to roll back on failure. - var signature = await EstimateSignaturePersister.StageAsync( - dbContext, id, type, userId, payload, token); + // Disk write first: a disk failure shouldn't leave + // a Signature row pointing at a file that doesn't + // exist. The file helper throws on filesystem + // problems and propagates here. + FileReceivedInfo fi; + try + { + fi = await User.ReceiveEstimateSignatureAsync(id, type, payload, + siteSettings, token); + } + catch (Exception ex) + { + logger.LogError(ex, "estimate {Id}: signature file write failed", id); + return BadRequest(new { Error = "file write failed", Detail = ex.Message }); + } + + // Find-or-add: the (EstimateId, Type) pair is + // unique, so a second POST for the same side of the + // estimate replaces the previous signature. EF + // translates this into a single UPDATE when the + // row exists and an INSERT otherwise; the unique + // index in ApplicationDbContext is the + // database-level guarantee that the contract + // holds if two requests race. + var signature = await dbContext.Signatures + .FirstOrDefaultAsync(s => s.EstimateId == id && s.Type == type, token); + + if (signature is null) + { + signature = new Signature + { + EstimateId = id, + SignerId = userId, + Type = type, + }; + dbContext.Signatures.Add(signature); + } + else + { + // Roll the signer's quota back by the size of + // the file we're about to orphan: the old + // FilePath is no longer referenced once we + // overwrite FilePath below. + try + { + var orphan = new FileInfo(signature.FilePath); + if (orphan.Exists) + { + var signerForOrphan = await dbContext.Users + .FirstOrDefaultAsync(u => u.Id == userId, token); + if (signerForOrphan is not null) + signerForOrphan.DiskUsage = + Math.Max(0, signerForOrphan.DiskUsage - orphan.Length); + } + } + catch { /* best effort — the file is being replaced anyway */ } + } + + signature.SignerId = userId; + signature.CoordinateMax = payload.CoordinateMax; + signature.Strokes = payload.Strokes; + signature.CapturedAtUtc = payload.CapturedAtUtc; + signature.FilePath = Path.Combine(fi.DestDir, fi.FileName); + + // Bump the signer's quota. The Signature row's + // SignerId is the IdentityUser.Id (a string), so we + // look up by Id and not by username. + var signer = await dbContext.Users + .FirstOrDefaultAsync(u => u.Id == userId, token); + if (signer is not null) + { + signer.DiskUsage += new FileInfo(signature.FilePath).Length; + } try { @@ -421,6 +482,10 @@ namespace Yavsc.ApiControllers catch (Exception ex) { logger.LogError(ex, "estimate {Id}: signature db write failed", id); + // Best-effort rollback: remove the file we wrote + // so disk and db don't disagree. + try { System.IO.File.Delete(signature.FilePath); } + catch { /* swallow — the row will be re-orphaned, the user re-signs */ } return BadRequest(new { Error = "db write failed", Detail = ex.Message }); } diff --git a/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs b/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs index 92e89e28e..b72c6d7a2 100644 --- a/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs +++ b/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs @@ -5,7 +5,6 @@ using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; using Yavsc.Models; using Yavsc.Models.Billing; -using Yavsc.Models.Blog; using Yavsc.Server.Helpers; namespace Yavsc.Controllers @@ -41,7 +40,7 @@ namespace Yavsc.Controllers else if (!UserIsAdminOrThis(ownerId)) // throw new Exception("Not authorized") ; // or just do nothing return new StatusCodeResult(StatusCodes.Status403Forbidden); - return Ok(_context.Estimates.Include(e => e.Bill).Include(e => e.Signatures).Where(e => e.OwnerId == ownerId)); + return Ok(_context.Estimates.Include(e => e.Bill).Where(e => e.OwnerId == ownerId)); } // GET: api/Estimate/5 [HttpGet("{id}", Name = "GetEstimate")] @@ -52,7 +51,7 @@ namespace Yavsc.Controllers return BadRequest(ModelState); } - Estimate estimate = _context.Estimates.Include(e => e.Bill).Include(e => e.Signatures).Single(m => m.Id == id); + Estimate estimate = _context.Estimates.Include(e => e.Bill).Single(m => m.Id == id); if (estimate == null) { @@ -202,31 +201,23 @@ namespace Yavsc.Controllers // GET: api/estimate/asclient // Ongoing estimates (not yet validated by the client) where the // current user is the client who initiated the request. - // An estimate only becomes visible to the client once the provider - // has actually established and submitted it (ProviderValidationDate); - // a freshly inserted request the provider hasn't worked on yet is - // not something the client can validate, so it's excluded. [HttpGet("asclient")] public IActionResult GetOngoingEstimatesAsClient() { var uid = User.GetUserId(); - return Ok(_context.Estimates.Include(e => e.Bill).Include(e => e.Signatures) - .Where(e => e.ClientId == uid - && e.ProviderValidationDate != default - && e.ClientValidationDate == default)); + return Ok(_context.Estimates.Include(e => e.Bill) + .Where(e => e.ClientId == uid && e.ClientValidationDate == default)); } // GET: api/estimate/asprovider - // Estimates awaiting the provider's signature, established by the - // current user as the provider. Once the provider validates - // (ProviderValidationDate stamped), the estimate leaves this list - // and appears in the client's "awaiting client signature" list. + // Ongoing estimates (not yet validated by the client) established + // by the current user as the provider. [HttpGet("asprovider")] public IActionResult GetOngoingEstimatesAsProvider() { var uid = User.GetUserId(); - return Ok(_context.Estimates.Include(e => e.Bill).Include(e => e.Signatures) - .Where(e => e.OwnerId == uid && e.ProviderValidationDate == default)); + return Ok(_context.Estimates.Include(e => e.Bill) + .Where(e => e.OwnerId == uid && e.ClientValidationDate == default)); } protected override void Dispose(bool disposing) @@ -238,96 +229,6 @@ namespace Yavsc.Controllers base.Dispose(disposing); } - // GET: api/v1/estimate/{id}/attachments - // - // Liste les fichiers de l'espace perso du fournisseur rattachés au - // devis, par référence (UploadedFile). Lecture ouverte aux deux - // parties (fournisseur/client) du devis et aux admin, comme - // GetEstimate : la contrepartie doit pouvoir voir et télécharger - // les pièces (le téléchargement des octets reste servi par le host - // Blogs, qui autorise via cette même table de jointure). - [HttpGet("{id}/attachments")] - public IActionResult GetAttachments(long id) - { - var estimate = _context.Estimates.FirstOrDefault(e => e.Id == id); - if (estimate == null) return NotFound(); - if (!UserIsAdminOrInThese(estimate.ClientId, estimate.OwnerId)) - return new StatusCodeResult(StatusCodes.Status403Forbidden); - - var attached = _context.EstimateAttachedFiles - .Where(a => a.EstimateId == id) - .Select(a => new - { - fileId = a.FileId, - path = a.File.Path, - size = a.File.Length, - contentType = a.File.ContentType, - ownerId = a.File.OwnerId - }) - .ToList(); - return Ok(attached); - } - - // POST: api/v1/estimate/{id}/attachments { fileId } - // - // Attache un fichier de l'espace perso du fournisseur au devis, par - // référence. Réservé au fournisseur (estimate.OwnerId == caller) : - // seul le fournisseur constitue le devis et y joint ses documents - // (dont les documents à faire signer par le client, per README). - // Le fichier doit appartenir au fournisseur (UploadedFile.OwnerId). - [HttpPost("{id}/attachments")] - public IActionResult AttachFile(long id, [FromBody] AttachFileRequest body) - { - if (!ModelState.IsValid) return BadRequest(ModelState); - var estimate = _context.Estimates.FirstOrDefault(e => e.Id == id); - if (estimate == null) return NotFound(); - - var uid = User.GetUserId(); - if (!User.IsInRole(Constants.AdminGroupName) && uid != estimate.OwnerId) - return new StatusCodeResult(StatusCodes.Status403Forbidden); - - if (body == null || body.FileId <= 0) - return BadRequest(new { error = "fileId required" }); - - var file = _context.UploadedFiles.FirstOrDefault(f => f.Id == body.FileId); - if (file == null) return BadRequest(new { error = "file not found" }); - if (file.OwnerId != uid) - return new StatusCodeResult(StatusCodes.Status403Forbidden); - - var existing = _context.EstimateAttachedFiles - .FirstOrDefault(a => a.EstimateId == id && a.FileId == body.FileId); - if (existing != null) return Ok(new { estimateId = id, fileId = body.FileId }); - - _context.EstimateAttachedFiles.Add(new EstimateAttachedFile - { - EstimateId = id, - FileId = body.FileId - }); - _context.SaveChanges(User.GetUserId()); - return Ok(new { estimateId = id, fileId = body.FileId }); - } - - // DELETE: api/v1/estimate/{id}/attachments/{fileId} - // - // Détache un fichier du devis. Réservé au fournisseur. - [HttpDelete("{id}/attachments/{fileId}")] - public IActionResult DetachFile(long id, long fileId) - { - var estimate = _context.Estimates.FirstOrDefault(e => e.Id == id); - if (estimate == null) return NotFound(); - - var uid = User.GetUserId(); - if (!User.IsInRole(Constants.AdminGroupName) && uid != estimate.OwnerId) - return new StatusCodeResult(StatusCodes.Status403Forbidden); - - var link = _context.EstimateAttachedFiles - .FirstOrDefault(a => a.EstimateId == id && a.FileId == fileId); - if (link == null) return NotFound(); - _context.EstimateAttachedFiles.Remove(link); - _context.SaveChanges(User.GetUserId()); - return Ok(new { estimateId = id, fileId }); - } - private bool EstimateExists(long id) { return _context.Estimates.Count(e => e.Id == id) > 0; diff --git a/src/Yavsc.Api/Controllers/Business/FrontOfficeApiController.cs b/src/Yavsc.Api/Controllers/Business/FrontOfficeApiController.cs index e5b89c4a9..3adac5b03 100644 --- a/src/Yavsc.Api/Controllers/Business/FrontOfficeApiController.cs +++ b/src/Yavsc.Api/Controllers/Business/FrontOfficeApiController.cs @@ -1,18 +1,10 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Razor; -using Microsoft.AspNetCore.Mvc.ViewEngines; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Options; -using Yavsc.Abstract.Workflow; using Yavsc.Helpers; using Yavsc.Models; -using Yavsc.Models.Billing; -using Yavsc.Models.Blog; using Yavsc.Services; using Yavsc.Server.Helpers; using Yavsc.ViewModels.FrontOffice; -using Yavsc.ViewModels.Gen; namespace Yavsc.ApiControllers { @@ -21,38 +13,13 @@ namespace Yavsc.ApiControllers public class FrontOfficeApiController : Controller { ApplicationDbContext dbContext; - private readonly IBillingService billing; - private readonly ILogger logger; - private readonly SiteSettings siteSettings; - private readonly IRazorViewEngine viewEngine; - // The view-rendering dependencies (siteSettings, viewEngine) are - // optional: only EstimateTex/EstimatePdf use them, and those run in - // the Api host where Program.cs wires AddControllersWithViews + - // SiteSettings. The accept/reject endpoints (and the Api integration - // test host, which composes with AddControllers alone) never touch - // them, so they must not be required for controller activation. - // - // .NET 10 does not register a single IViewEngine service (the view - // engine is registered as IRazorViewEngine and surfaced through - // MvcViewOptions.ViewEngines). IRazorViewEngine (RazorViewEngine) - // implements IViewEngine, so we inject that and hand it to - // TeXHelpers.RenderViewToString as an IViewEngine. RenderViewToString - // builds its ActionContext from the controller's own ControllerContext, - // so no IActionContextAccessor is needed (and that type is deprecated - // in .NET 10). - public FrontOfficeApiController( - ApplicationDbContext context, - ILoggerFactory loggerFactory, - IOptions siteSettings = null, - IRazorViewEngine viewEngine = null, - IBillingService billing = null) + private IBillingService billing; + + public FrontOfficeApiController(ApplicationDbContext context, IBillingService billing = null) { dbContext = context; this.billing = billing ?? new BillingService(context); - logger = loggerFactory.CreateLogger(); - this.siteSettings = siteSettings?.Value; - this.viewEngine = viewEngine; } [HttpGet("profiles/{actCode}")] @@ -61,402 +28,29 @@ namespace Yavsc.ApiControllers return await dbContext.ListPerformersAsync(billing, actCode); } - // GET: api/front/query/{queryId}/estimate.tex - // - // Renders the estimate linked to the query (Estimate.CommandId == - // queryId) as a LaTeX source document. The view is a generation - // template (Layout = "null"), not a web page: it is returned with - // a text/x-tex content type so the caller can save it as a .tex - // file. Either party (client or provider) or an admin may read it. - [HttpGet("query/{queryId}/estimate.tex")] - public async Task EstimateTex(long queryId, CancellationToken token) - { - if (viewEngine is null || siteSettings is null) - return Problem("TeX rendering is not configured on this host.", - statusCode: StatusCodes.Status503ServiceUnavailable); - - var estimate = await LoadEstimateForRenderAsync(queryId, token); - if (estimate is null) return NotFound(new { Error = "no estimate linked to this query" }); - - var uid = User.GetUserId(); - if (!User.IsInRole(Constants.AdminGroupName) - && uid != estimate.ClientId && uid != estimate.OwnerId) - return Forbid(); - - PrepareEstimateViewBag(estimate); - Response.ContentType = "text/x-tex"; - return View("Estimate_tex", estimate); - } - - // GET: api/front/query/{queryId}/estimate.pdf - // - // Renders the same LaTeX template to a string, compiles it to PDF - // via texi2pdf (see SiteSettings.GenerateEstimatePdf), and streams - // the generated PDF bytes back. Requires /usr/bin/texi2pdf on the - // server host; on hosts without it the endpoint returns a problem. - [HttpGet("query/{queryId}/estimate.pdf")] - public async Task EstimatePdf(long queryId, CancellationToken token) - { - if (viewEngine is null || siteSettings is null) - { - string msg = "PDF rendering is not configured on this host."; - logger.LogError(msg); - return Problem(msg, - statusCode: StatusCodes.Status503ServiceUnavailable); - - } - - var estimate = await LoadEstimateForRenderAsync(queryId, token); - if (estimate is null) return NotFound(new { Error = "no estimate linked to this query" }); - - var uid = User.GetUserId(); - if (!User.IsInRole(Constants.AdminGroupName) - && uid != estimate.ClientId && uid != estimate.OwnerId) - return Forbid(); - - PrepareEstimateViewBag(estimate); - - var baseFileName = $"estimate-{estimate.Id}"; - // Resolve the bills dir to an absolute path once: appsettings - // configures it as a relative path ("bills"), and both the - // generation (GenerateEstimatePdf) and the read-back below must - // agree on the same location regardless of the host's cwd. - var billsDir = new System.IO.DirectoryInfo(siteSettings.Bills).FullName; - var model = new PdfGenerationViewModel - { - BaseFileName = baseFileName, - DestDir = billsDir, - Temp = siteSettings.TempDir, - }; - - try - { - // 1. Render the LaTeX template (Estimate_tex) to a TeX source - // string, using the estimate as the view model. - model.TeXSource = this.RenderViewToString(viewEngine, "Estimate_tex", estimate); - // 2. Render the generation view (Estimate_pdf). Its @{} block - // calls SiteSettings.GenerateEstimatePdf(Model), which - // compiles the TeX to PDF on disk as a side effect of the - // render. The HTML the view emits is not what we return — - // we stream the generated PDF bytes — but driving the - // generation through the view keeps the template as the - // output producer, per the original design. - if (model.GenerateEstimatePdf() is FileInfo fo && fo.Exists) - { - return File(fo.OpenRead(), "application/pdf"); - } - string msg = "TeX render failed: " + model.GenerationErrorMessage; - logger.LogError(msg); - return Problem(msg); - - } - catch (Exception ex) - { - logger.LogError(ex, "estimate {QueryId}: TeX/PDF render failed", queryId); - return Problem("TeX render failed: " + ex.Message); - } - } - - // Loads the estimate linked to a query with the navigation the - // Estimate_tex template reads (client + performer profile + bill - // + the captured signatures, drawn as tikz at the foot of the devis). - private Task LoadEstimateForRenderAsync(long queryId, CancellationToken token) - => dbContext.Estimates - .Where(e => e.CommandId == queryId) - .OrderByDescending(e => e.Id) - .Include(e => e.Bill) - .Include(e => e.Signatures) - .Include(e => e.Query).ThenInclude(q => q!.Client).ThenInclude(c => c!.PostalAddress) - .Include(e => e.Query).ThenInclude(q => q!.PerformerProfile).ThenInclude(p => p!.OrganizationAddress) - .Include(e => e.Query).ThenInclude(q => q!.PerformerProfile).ThenInclude(p => p!.Performer) - .FirstOrDefaultAsync(token); - - // Sets the ViewBag keys the Estimate_tex template reads. A devis - // is never a bill (AsBill=false) and is never acquitted. - private void PrepareEstimateViewBag(Estimate estimate) - { - ViewBag.AsBill = false; - ViewBag.Acquitted = false; - ViewBag.BillsDir = new System.IO.DirectoryInfo(siteSettings.Bills).FullName; - ViewBag.AvatarsDir = new System.IO.DirectoryInfo(siteSettings.Avatars).FullName; - } - - // POST: api/front/query/accept - // - // Either party of the query may accept: the provider - // (PerformerId) accepts the client's request, or the client - // (ClientId) accepts the provider's estimate. An optional - // signature (the PostIt wire format) may be sent along; it is - // persisted on the estimate linked to the query - // (Estimate.CommandId == queryId), and the matching validation - // date is stamped — ProviderValidationDate when the provider - // accepts, ClientValidationDate when the client accepts. - [HttpPost("query/accept")] - public async Task AcceptQuery( - string billingCode, - long queryId, - [FromBody] QueryAcceptanceRequest? body, - CancellationToken token) - { - var (query, role, error) = await ResolveAndAuthorizeAsync(billingCode, queryId, token); - if (error is not null) return error; - if (query is null) return BadRequest(new { Error = "query not found" }); - - Signature? signature = null; - // actingRole is the role the caller is signing / accepting - // as. It defaults to the role inferred from the caller's - // identity, but when a signature is submitted the caller - // declares the side (Pro/Client) and the server honors it - // only if the caller is that party. This disambiguates a - // user who is both parties (where inference would always - // pick the provider) and makes "write only for its author" - // explicit. - ActorRole actingRole = role; - if (HasSignature(body)) - { - // Admins bypass authorization but are neither party of - // the estimate, so they cannot sign. - if (role == ActorRole.Admin) - return BadRequest(new { Error = "admin cannot sign on behalf of a party" }); - - var estimate = await dbContext.Estimates - .Where(e => e.CommandId == queryId) - .OrderByDescending(e => e.Id) - .FirstOrDefaultAsync(token); - if (estimate is null) - return BadRequest(new { Error = "no estimate linked to this query" }); - - var uid = User.GetUserId(); - bool isProvider = query.PerformerId == uid; - bool isClient = query.ClientId == uid; - - // The caller declares which side they sign as. Null - // falls back to the role inferred from identity. - var declared = body!.SignatureType; - if (declared.HasValue) - { - if (declared.Value == SignatureType.Pro && !isProvider) - return Forbid(); - if (declared.Value == SignatureType.Client && !isClient) - return Forbid(); - actingRole = declared.Value == SignatureType.Pro - ? ActorRole.Provider - : ActorRole.Client; - } - - var type = actingRole == ActorRole.Provider - ? SignatureType.Pro - : SignatureType.Client; - - var payload = new SignaturePadPayload - { - CoordinateMax = body.CoordinateMax, - CapturedAtUtc = body.CapturedAtUtc ?? DateTime.UtcNow, - Strokes = body.Strokes!, - }; - - try - { - signature = await EstimateSignaturePersister.StageAsync( - dbContext, estimate.Id, type, uid, payload, token); - } - catch (Exception ex) - { - logger.LogError(ex, "query {QueryId}: signature stage failed", queryId); - return BadRequest(new { Error = "signature stage failed", Detail = ex.Message }); - } - - if (actingRole == ActorRole.Provider) - estimate.ProviderValidationDate = DateTime.UtcNow; - else - estimate.ClientValidationDate = DateTime.UtcNow; - } - - // The party-specific status records who accepted: the - // provider (ProAccepted) or the client (ClientAccepted). - // An admin accepting (neither party) falls back to the - // legacy generic Accepted. - query.Status = actingRole switch - { - ActorRole.Provider => QueryStatus.ProAccepted, - ActorRole.Client => QueryStatus.ClientAccepted, - _ => QueryStatus.Accepted, - }; - - try - { - await dbContext.SaveChangesAsync(User.GetUserId(), token); - } - catch (Exception ex) - { - logger.LogError(ex, "query {QueryId}: accept db write failed", queryId); - return BadRequest(new { Error = "db write failed", Detail = ex.Message }); - } - - return Ok(new - { - queryId, - status = query.Status.ToString(), - signature = signature is null - ? null - : new - { - id = signature.Id, - estimateId = signature.EstimateId, - type = signature.Type.ToString(), - }, - }); - } - - // POST: api/front/query/reject - // - // Either party may reject. A signature is not meaningful on a - // rejection, so one is refused with 400. [HttpPost("query/reject")] - public async Task RejectQuery( - string billingCode, - long queryId, - [FromBody] QueryAcceptanceRequest? body, - CancellationToken token) + public IActionResult RejectQuery(string billingCode, long queryId) { - if (HasSignature(body)) - return BadRequest(new { Error = "signature not allowed on reject" }); + if (billingCode == null) return BadRequest("billingCode"); + if (queryId == 0) return BadRequest("queryId"); + var billing = BillingService.GetBillable(dbContext, billingCode, queryId); + if (billing == null) return BadRequest(); - var (query, role, error) = await ResolveAndAuthorizeAsync(billingCode, queryId, token); - if (error is not null) return error; - if (query is null) return BadRequest(new { Error = "query not found" }); - - query.Status = QueryStatus.Rejected; - await dbContext.SaveChangesAsync(User.GetUserId(), token); - - return Ok(new - { - queryId, - status = query.Status.ToString(), - }); + billing.Status = QueryStatus.Rejected; + dbContext.SaveChanges(User.GetUserId()); + return Ok(); } - // Loads the query and checks that the caller is a party to it - // (provider or client) or an admin. Returns the role the caller - // is acting in; Admin is only returned when the caller is - // neither party but is an admin. - private async Task<(IQuery? Query, ActorRole Role, IActionResult? Error)> - ResolveAndAuthorizeAsync(string billingCode, long queryId, CancellationToken token) + [HttpPost("query/accept")] + public IActionResult AcceptQuery(string billingCode, long queryId) { - if (string.IsNullOrWhiteSpace(billingCode)) - return (null, ActorRole.None, BadRequest("billingCode")); - if (queryId == 0) - return (null, ActorRole.None, BadRequest("queryId")); - - var query = await billing.GetBillAsync(billingCode, queryId); - if (query is null) - return (null, ActorRole.None, BadRequest()); - - var uid = User.GetUserId(); - bool isProvider = query.PerformerId == uid; - bool isClient = query.ClientId == uid; - - if (isProvider) return (query, ActorRole.Provider, null); - if (isClient) return (query, ActorRole.Client, null); - - if (User.IsInRole(Constants.AdminGroupName)) - return (query, ActorRole.Admin, null); - - return (null, ActorRole.None, Forbid()); - } - - private static bool HasSignature(QueryAcceptanceRequest? body) - => body is not null && body.Strokes is not null && body.Strokes.Length > 0; - - // GET: api/v1/front/query/{queryId}/attachments?billingCode=… - // - // Liste les fichiers de l'espace perso du client rattachés à la - // demande, par référence (UploadedFile). Lecture ouverte aux deux - // parties (client/fournisseur) de la demande et aux admin ; le - // téléchargement des octets est servi par le host Blogs. - [HttpGet("query/{queryId}/attachments")] - public async Task GetQueryAttachments( - string billingCode, long queryId, CancellationToken token) - { - var (query, role, error) = await ResolveAndAuthorizeAsync(billingCode, queryId, token); - if (error is not null) return error; - if (query is null) return BadRequest(new { Error = "query not found" }); - - var attached = await dbContext.QueryAttachedFiles - .Where(a => a.CommandId == queryId) - .Select(a => new - { - fileId = a.FileId, - path = a.File.Path, - size = a.File.Length, - contentType = a.File.ContentType, - ownerId = a.File.OwnerId - }) - .ToListAsync(token); - return Ok(attached); - } - - // POST: api/v1/front/query/{queryId}/attachments?billingCode=… { fileId } - // - // Attache un fichier de l'espace perso du client à sa demande, par - // référence. Réservé au client (rôle Client) : c'est lui qui - // documente sa demande. Le fichier doit lui appartenir. - [HttpPost("query/{queryId}/attachments")] - public async Task AttachQueryFile( - string billingCode, long queryId, [FromBody] AttachFileRequest? body, CancellationToken token) - { - var (query, role, error) = await ResolveAndAuthorizeAsync(billingCode, queryId, token); - if (error is not null) return error; - if (query is null) return BadRequest(new { Error = "query not found" }); - - if (role != ActorRole.Client && !User.IsInRole(Constants.AdminGroupName)) - return new StatusCodeResult(StatusCodes.Status403Forbidden); - - if (body == null || body.FileId <= 0) - return BadRequest(new { error = "fileId required" }); - - var uid = User.GetUserId(); - var file = await dbContext.UploadedFiles - .FirstOrDefaultAsync(f => f.Id == body.FileId, token); - if (file == null) return BadRequest(new { error = "file not found" }); - if (file.OwnerId != uid && !User.IsInRole(Constants.AdminGroupName)) - return new StatusCodeResult(StatusCodes.Status403Forbidden); - - var existing = await dbContext.QueryAttachedFiles - .FirstOrDefaultAsync(a => a.CommandId == queryId && a.FileId == body.FileId, token); - if (existing != null) return Ok(new { queryId, fileId = body.FileId }); - - dbContext.QueryAttachedFiles.Add(new QueryAttachedFile - { - CommandId = queryId, - FileId = body.FileId - }); - await dbContext.SaveChangesAsync(User.GetUserId(), token); - return Ok(new { queryId, fileId = body.FileId }); - } - - // DELETE: api/v1/front/query/{queryId}/attachments/{fileId}?billingCode=… - // - // Détache un fichier de la demande. Réservé au client. - [HttpDelete("query/{queryId}/attachments/{fileId}")] - public async Task DetachQueryFile( - string billingCode, long queryId, long fileId, CancellationToken token) - { - var (query, role, error) = await ResolveAndAuthorizeAsync(billingCode, queryId, token); - if (error is not null) return error; - if (query is null) return BadRequest(new { Error = "query not found" }); - - if (role != ActorRole.Client && !User.IsInRole(Constants.AdminGroupName)) - return new StatusCodeResult(StatusCodes.Status403Forbidden); - - var link = await dbContext.QueryAttachedFiles - .FirstOrDefaultAsync(a => a.CommandId == queryId && a.FileId == fileId, token); - if (link == null) return NotFound(); - dbContext.QueryAttachedFiles.Remove(link); - await dbContext.SaveChangesAsync(User.GetUserId(), token); - return Ok(new { queryId, fileId }); + if (billingCode == null) return BadRequest("billingCode"); + if (queryId == 0) return BadRequest("queryId"); + var billing = BillingService.GetBillable(dbContext, billingCode, queryId); + if (billing == null) return BadRequest(); + billing.Status = QueryStatus.Accepted; + dbContext.SaveChanges(User.GetUserId()); + return Ok(); } } - - internal enum ActorRole { None, Provider, Client, Admin } } diff --git a/src/Yavsc.Api/Helpers/TeXHelpers.cs b/src/Yavsc.Api/Helpers/TeXHelpers.cs deleted file mode 100644 index c35806bda..000000000 --- a/src/Yavsc.Api/Helpers/TeXHelpers.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System.Diagnostics; -using System.Globalization; -using System.Text; -using Microsoft.AspNetCore.Html; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.ViewEngines; -using Microsoft.AspNetCore.Mvc.ViewFeatures; -using Yavsc.Models.Billing; - -namespace Yavsc.Helpers -{ - using Microsoft.AspNetCore.Mvc.Razor; - using ViewModels.Gen; - public class TeXString : HtmlString - { - - public TeXString(TeXString teXString): base(teXString.ToString()) - { - } - - public TeXString(string str) : base(str) - { - - - } - - public static TeXString operator+ (TeXString a, TeXString b) { - return new TeXString(a.ToString()+b.ToString()); - } - - } - public class Replacement - { - string target; - string replacement; - public Replacement(string target, string replacement) - { - this.target = target; - this.replacement = replacement; - } - public string Execute(string source) - { - return source?.Replace(target, replacement) ?? null; - } - } - public static class TeXHelpers - { - public static readonly Replacement[] SpecialCharsDefaultRendering = - { - new Replacement("<","\\textless"), - new Replacement(">","\\textgreater"), - new Replacement("©","\\copyright"), - new Replacement("®","\\textregistered"), - new Replacement("\\","\\textbackslash"), - new Replacement("™","\\texttrademark"), - new Replacement("¶","\\P"), - new Replacement("|","\\textbar"), - new Replacement("%","\\%"), - new Replacement("{","\\{"), - new Replacement("}","\\}"), - new Replacement("_","\\_"), - new Replacement("#","\\#"), - new Replacement("$","\\$"), - new Replacement("_","\\_"), - new Replacement("¿","\\textquestiondown"), - new Replacement("§","\\S"), - new Replacement("£","\\pounds"), - new Replacement("&","\\&"), - new Replacement("¡","\\textexclamdown"), - new Replacement("†","\\dag"), - new Replacement("–","\\textendash"), - new Replacement("°","\\textdegree") - }; - - public static TeXString ToTeX(this string source, string defaultValue="\\textit{néant}") - { - if (source==null) return new TeXString(defaultValue); - string result=source; - foreach (var r in SpecialCharsDefaultRendering) - { - result = r.Execute(result); - } - return new TeXString(result); - } - - public static TeXString ToTeXCell(this string source, string defaultValue="\\textit{néant}") - { - if (source==null) return new TeXString(defaultValue); - string result=source; - foreach (var r in SpecialCharsDefaultRendering) - { - result = r.Execute(result); - } - result = result.Replace("\n","\\tabularnewline "); - return new TeXString(result); - } - - - public static string NewLinesWith(this string target, string separator) - { - var items = target.Split(new char[] { '\n' }).Where( - s => !string.IsNullOrWhiteSpace(s)); - - return string.Join(separator, items); - } - - public static TeXString ToTeXLines(this string source, string defaultValue, string lineSeparator = "\n\\\\") - { - if (source == null) return new TeXString(defaultValue); - return new TeXString( source.ToTeX().ToString().NewLinesWith(lineSeparator) ); - } - - public static TeXString SplitAddressToTeX (this string source, string lineSeparator = "\n\\\\", string defaultValue = "\\textit{pas d'adresse postale}") - { - if (string.IsNullOrWhiteSpace(source)) return new TeXString(defaultValue); - var alines = source.Split(','); - var texlines = alines.Select(l=>l.ToTeX().ToString()); - return new TeXString(string.Join(lineSeparator,texlines)); - } - - /// - /// Renders a captured signature as TikZ \draw/\fill - /// paths, suitable for embedding in a tikzpicture at the - /// foot of a devis. The signature is the PostIt wire format — a - /// length-prefixed sequence of strokes, each stroke being - /// [k, x0, y0, x1, y1, …] with coordinates normalised to - /// [0, CoordinateMax] and y growing downward (screen - /// orientation). We denormalise to a × - /// canvas and flip y so the signature - /// appears upright on paper. A single-point stroke (a pen-down - /// tap) is rendered as a filled dot, mirroring the round-capped - /// Polyline the Avalonia pad draws for it. - /// - /// The persisted signature. Null/empty yields - /// , so the caller can gate the - /// tikzpicture on a non-empty result. - public static HtmlString SignatureToTikz( - this Signature sig, double widthCm, double heightCm) - { - if (sig is null || sig.Strokes is null || sig.Strokes.Length == 0) - return HtmlString.Empty; - - double max = sig.CoordinateMax > 0 ? sig.CoordinateMax : 10_000.0; - var strokes = sig.Strokes; - var sb = new StringBuilder(); - - int i = 0; - while (i < strokes.Length) - { - int k = strokes[i]; - if (k <= 0) break; - if (i + 1 + 2 * k > strokes.Length) break; - i++; // skip the length prefix - - // A single-point stroke: a tap. Draw a round dot so it - // shows instead of a zero-length, invisible \draw. - if (k == 1) - { - double px = strokes[i] / max * widthCm; - double py = heightCm - strokes[i + 1] / max * heightCm; - sb.Append(CultureInfo.InvariantCulture, - $"\\fill ({px:F3},{py:F3}) circle (0.8pt); "); - i += 2; - continue; - } - - sb.Append("\\draw[line cap=round, line join=round, line width=1.5pt] "); - for (int p = 0; p < k; p++) - { - double px = strokes[i + 2 * p] / max * widthCm; - double py = heightCm - strokes[i + 2 * p + 1] / max * heightCm; - if (p == 0) sb.Append(CultureInfo.InvariantCulture, - $"({px:F3},{py:F3})"); - else sb.Append(CultureInfo.InvariantCulture, - $" -- ({px:F3},{py:F3})"); - } - sb.Append("; "); - i += 2 * k; - } - - return new HtmlString(sb.ToString()); - } - - public static FileInfo GenerateEstimatePdf(this PdfGenerationViewModel Model) - { - string errorMsg = null; - // Resolve to an absolute path: SiteSettings.Bills may be a - // relative path (e.g. "bills"), and lualatex resolves - // -output-directory against its working directory — passing a - // relative path to both would nest them (/bills/bills) and - // the log/pdf write would fail. - var billdir = new DirectoryInfo(Model.DestDir).FullName; - string name = Model.BaseFileName; - FileInfo fo = new FileInfo(System.IO.Path.Combine(billdir, name + ".pdf")); - System.IO.Directory.CreateDirectory(billdir); - - // Compile the LaTeX source with the system lualatex, feeding it - // the TeX on standard input (no intermediate .tex file) and - // directing the resulting .pdf straight into the bills - // directory. lualatex is a file-based engine — it cannot emit - // the PDF on stdout — so the pdf lands at its final destination - // and we only clean the transient aux/log artifacts beside it. - // - // We pass "/dev/stdin" as the input filename so lualatex treats - // stdin as a regular input *file* rather than the terminal: with - // no filename argument it reads stdin in terminal mode, which - // emergency-stops after the first line of a multi-line document. - // /dev/stdin makes the full multi-line TeX compile cleanly. (The - // API host is Linux — the systemd service and lualatex dep are - // Linux-only too.) - // - // We capture stdout/stderr so a LaTeX error (missing .sty, - // missing \includegraphics target, …) is surfaced in - // GenerationErrorMessage instead of being buried in the .log - // we then delete. - int exitCode; - string stdout = null, stderr = null; - try - { - using (Process p = new Process()) - { - p.StartInfo = new ProcessStartInfo - { - UseShellExecute = false, - WorkingDirectory = billdir, - FileName = "lualatex", - Arguments = $"-interaction=nonstopmode -halt-on-error" - + $" -jobname=\"{name}\" -output-directory=\"{billdir}\"" - + " /dev/stdin", - RedirectStandardInput = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - }; - p.Start(); - // Write the TeX source, then drain stdout/stderr. Read - // them after WaitForExit — lualatex's output is small - // (a few KB), well under the pipe buffer, so no deadlock. - using (var stdin = p.StandardInput) - { - stdin.Write(Model.TeXSource); - } - p.WaitForExit(); - exitCode = p.ExitCode; - stdout = p.StandardOutput.ReadToEnd(); - stderr = p.StandardError.ReadToEnd(); - } - } - catch (Exception ex) - { - errorMsg = "lualatex invocation failed: " + ex.Message; - exitCode = -1; - } - - if (exitCode != 0 && errorMsg == null) - { - // Surface the engine's own diagnostics (stderr carries the - // "! Emergency stop" / "! LaTeX Error: …" lines; stdout has - // the transcript banner) so the caller can see why it failed - // without having to find the (soon-deleted) .log file. - var diag = string.IsNullOrWhiteSpace(stderr) ? stdout : stderr; - errorMsg = $"Pdf generation failed with exit code {exitCode}: {diag}"; - } - else if (!fo.Exists && errorMsg == null) - errorMsg = "Pdf generation produced no output"; - - // Remove the LaTeX build artifacts left beside the pdf so only - // .pdf persists in the bills directory. Keep the .log on - // failure so it can be inspected. - foreach (var ext in new[] { ".aux", ".log", ".out", ".fls", - ".fdb_latexmk", ".synctex.gz", ".toc" }) - { - if (ext == ".log" && !fo.Exists) continue; - var fileName = name + ext; - var f = new FileInfo(System.IO.Path.Combine(billdir, fileName)); - if (f.Exists) { try { f.Delete(); } catch { } } - } - - Model.Generated = fo.Exists; - Model.GenerationErrorMessage = new HtmlString(errorMsg); - return fo; - } - - public static string RenderViewToString( - this Controller controller, IRazorViewEngine engine, - string viewName, object model, bool isMainPage = true) - { - if (engine == null) - throw new InvalidOperationException("no engine"); - - // Build an ActionContext from the controller's own context — - // .NET 10 deprecated IActionContextAccessor, and the controller - // already holds the live request context on its ControllerContext. - var controllerContext = controller.ControllerContext; - var actionContext = new ActionContext( - controllerContext.HttpContext, - controllerContext.RouteData, - controllerContext.ActionDescriptor); - - ViewEngineResult viewResult = engine.FindView(actionContext, viewName, isMainPage); - if (!viewResult.Success) - throw new InvalidOperationException( - $"View '{viewName}' not found. Searched locations: " - + string.Join(", ", viewResult.SearchedLocations)); - - // Hand the model to the view through ViewData so the template's - // @model directive resolves it (the previous implementation never - // set ViewData.Model, leaving Model null inside the template). - controller.ViewData.Model = model; - - using (var sw = new StringWriter()) - { - var viewContext = new ViewContext( - actionContext, - viewResult.View, - controller.ViewData, - controller.TempData, - sw, - new HtmlHelperOptions()); - viewResult.View.RenderAsync(viewContext).GetAwaiter().GetResult(); - return sw.GetStringBuilder().ToString(); - } - } - } -} diff --git a/src/Yavsc.Api/Program.cs b/src/Yavsc.Api/Program.cs index 8daff34d9..be4ae6579 100644 --- a/src/Yavsc.Api/Program.cs +++ b/src/Yavsc.Api/Program.cs @@ -55,11 +55,7 @@ internal class Program }); }) .AddYavscCors(builder.Configuration) - .AddControllersWithViews(); - - // Binds the "Site" section (Bills, TempDir, Avatars, …) used by - // the document-generation flow and BillingController. - services.Configure(builder.Configuration.GetSection("Site")); + .AddControllers(); // accepts any access token issued by identity server services.AddAuthentication("Bearer") diff --git a/src/Yavsc.Api/ViewModels/FrontOffice/QueryAcceptanceRequest.cs b/src/Yavsc.Api/ViewModels/FrontOffice/QueryAcceptanceRequest.cs deleted file mode 100644 index 41e4dd650..000000000 --- a/src/Yavsc.Api/ViewModels/FrontOffice/QueryAcceptanceRequest.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Yavsc.Models.Billing; - -namespace Yavsc.ViewModels.FrontOffice -{ - /// - /// Body of POST /api/v1/front/query/accept and - /// .../reject. The field is optional: - /// when present (non-empty) on accept, it is the PostIt wire-format - /// signature captured with the acceptance. All fields are optional - /// so a caller may post {} to accept/reject without a - /// signature. - /// - public class QueryAcceptanceRequest - { - /// - /// Wire-format strokes. See - /// PostIt.Models.SignaturePadData. Null or empty means - /// no signature. - /// - public int[]? Strokes { get; set; } - - public int CoordinateMax { get; set; } = 10_000; - - public DateTime? CapturedAtUtc { get; set; } - - /// - /// Which signature is being submitted: - /// (the provider) or - /// . When - /// is present this declares the side the caller is signing as; - /// the server honors it only if the caller is that party - /// (query.PerformerId for Pro, query.ClientId - /// for Client). This disambiguates the case where one user is - /// both parties, and makes "write only for its author" - /// explicit. When null, the server falls back to the role - /// inferred from the caller's identity. - /// - public SignatureType? SignatureType { get; set; } - } -} diff --git a/src/Yavsc.Api/Views/_ViewImports.cshtml b/src/Yavsc.Api/Views/_ViewImports.cshtml deleted file mode 100644 index 788aed5ab..000000000 --- a/src/Yavsc.Api/Views/_ViewImports.cshtml +++ /dev/null @@ -1,12 +0,0 @@ -@using Yavsc -@using Yavsc.Helpers -@using Yavsc.Billing -@using Yavsc.Models -@using Yavsc.Models.Billing -@using Yavsc.ViewModels.Gen -@using Microsoft.AspNetCore.Mvc -@using Microsoft.AspNetCore.Html - -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers - -@inject Microsoft.Extensions.Options.IOptions SiteSettings \ No newline at end of file diff --git a/src/Yavsc.Api/Yavsc.Api.csproj b/src/Yavsc.Api/Yavsc.Api.csproj index be545e5b0..1a2accfc8 100644 --- a/src/Yavsc.Api/Yavsc.Api.csproj +++ b/src/Yavsc.Api/Yavsc.Api.csproj @@ -7,8 +7,8 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 diff --git a/src/test/Yavsc.Blogs.Tests/BlogAclApiTests.cs b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs similarity index 98% rename from src/test/Yavsc.Blogs.Tests/BlogAclApiTests.cs rename to src/Yavsc.Blogs.Tests/BlogAclApiTests.cs index e47ab4e57..174838173 100644 --- a/src/test/Yavsc.Blogs.Tests/BlogAclApiTests.cs +++ b/src/Yavsc.Blogs.Tests/BlogAclApiTests.cs @@ -13,7 +13,7 @@ using static Yavsc.Constants; namespace Yavsc.Blogs.Tests; /// -/// Behavioral tests for BlogAclApiController.PostCircleAuthorizationToBlogPost: +/// Behavioural tests for BlogAclApiController.PostCircleAuthorizationToBlogPost: /// POST /api/v1/blogacl with a JSON body of /// CircleAuthorizationToBlogPost (CircleId + BlogPostId). /// @@ -84,7 +84,7 @@ public sealed class BlogAclApiTests : IClassFixture // rather than being rewritten to ClaimTypes.NameIdentifier. // The controller, however, reads the user id via // User.FindFirstValue(ClaimTypes.NameIdentifier), so we add - // an explicit NameIdentifier claim to keep the legacy lookup happy. + // an explicit nameid claim to keep the legacy lookup happy. http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( "Bearer", @@ -106,7 +106,7 @@ public sealed class BlogAclApiTests : IClassFixture /// when POSTs the /// shape { "circleId": <id> } — the exact body the /// PostIt client builds from - /// (which only carries CircleId). The server deserializes + /// (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 diff --git a/src/test/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs b/src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs similarity index 100% rename from src/test/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs rename to src/Yavsc.Blogs.Tests/BlogApiMappedClaimsTests.cs diff --git a/src/test/Yavsc.Blogs.Tests/BlogApiSmokeTests.cs b/src/Yavsc.Blogs.Tests/BlogApiSmokeTests.cs similarity index 100% rename from src/test/Yavsc.Blogs.Tests/BlogApiSmokeTests.cs rename to src/Yavsc.Blogs.Tests/BlogApiSmokeTests.cs diff --git a/src/test/Yavsc.Blogs.Tests/BlogApiTests.cs b/src/Yavsc.Blogs.Tests/BlogApiTests.cs similarity index 100% rename from src/test/Yavsc.Blogs.Tests/BlogApiTests.cs rename to src/Yavsc.Blogs.Tests/BlogApiTests.cs diff --git a/src/test/Yavsc.Blogs.Tests/CircleMembersApiTests.cs b/src/Yavsc.Blogs.Tests/CircleMembersApiTests.cs similarity index 100% rename from src/test/Yavsc.Blogs.Tests/CircleMembersApiTests.cs rename to src/Yavsc.Blogs.Tests/CircleMembersApiTests.cs diff --git a/src/test/Yavsc.Blogs.Tests/CreateMultipartContentTests.cs b/src/Yavsc.Blogs.Tests/CreateMultipartContentTests.cs similarity index 100% rename from src/test/Yavsc.Blogs.Tests/CreateMultipartContentTests.cs rename to src/Yavsc.Blogs.Tests/CreateMultipartContentTests.cs diff --git a/src/Yavsc.Blogs.Tests/Directory.Packages.props b/src/Yavsc.Blogs.Tests/Directory.Packages.props new file mode 100644 index 000000000..eea5d34f9 --- /dev/null +++ b/src/Yavsc.Blogs.Tests/Directory.Packages.props @@ -0,0 +1,7 @@ + + + + diff --git a/src/test/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs similarity index 100% rename from src/test/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs rename to src/Yavsc.Blogs.Tests/Fixtures/BlogsWebServerFixture.cs diff --git a/src/test/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs b/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs similarity index 97% rename from src/test/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs rename to src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs index df977dc4e..ab4418c57 100644 --- a/src/test/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs +++ b/src/Yavsc.Blogs.Tests/Fixtures/MappedClaimsBlogsWebServerFixture.cs @@ -33,7 +33,7 @@ public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFix JwtSecurityTokenHandler.DefaultInboundClaimTypeMap["sub"] = ClaimTypes.NameIdentifier; var builder = WebApplication.CreateBuilder(); - builder.WebHost.UseUrls("http://127.0.0.1:5105"); + builder.WebHost.UseUrls("http://127.0.0.1:5104"); builder.Services.AddDbContext(opt => opt.UseInMemoryDatabase("Yavsc.Blogs.Tests.MappedClaims", _inMemoryRoot)); @@ -88,7 +88,7 @@ public sealed class MappedClaimsBlogsWebServerFixture : IDisposable, IBackendFix _app.MapControllers(); _app.StartAsync().GetAwaiter().GetResult(); - Addresses = ["http://127.0.0.1:5105"]; + Addresses = ["http://127.0.0.1:5104"]; Services = _app.Services; } diff --git a/src/test/Yavsc.Blogs.Tests/Fixtures/TestApiTransport.cs b/src/Yavsc.Blogs.Tests/Fixtures/TestApiTransport.cs similarity index 100% rename from src/test/Yavsc.Blogs.Tests/Fixtures/TestApiTransport.cs rename to src/Yavsc.Blogs.Tests/Fixtures/TestApiTransport.cs diff --git a/src/test/Yavsc.Blogs.Tests/JwtClaimMappingCollection.cs b/src/Yavsc.Blogs.Tests/JwtClaimMappingCollection.cs similarity index 100% rename from src/test/Yavsc.Blogs.Tests/JwtClaimMappingCollection.cs rename to src/Yavsc.Blogs.Tests/JwtClaimMappingCollection.cs diff --git a/src/test/Yavsc.Blogs.Tests/PublishEndpointTests.cs b/src/Yavsc.Blogs.Tests/PublishEndpointTests.cs similarity index 100% rename from src/test/Yavsc.Blogs.Tests/PublishEndpointTests.cs rename to src/Yavsc.Blogs.Tests/PublishEndpointTests.cs diff --git a/src/test/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj similarity index 67% rename from src/test/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj rename to src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj index 28934efaf..590f27867 100644 --- a/src/test/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj +++ b/src/Yavsc.Blogs.Tests/Yavsc.Blogs.Tests.csproj @@ -9,8 +9,8 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 @@ -21,15 +21,16 @@ + - - - - + + + + - + \ No newline at end of file diff --git a/src/Yavsc.Blogs/Controllers/FileSystemApiController.cs b/src/Yavsc.Blogs/Controllers/FileSystemApiController.cs index 2ca4250c0..b714af4e0 100644 --- a/src/Yavsc.Blogs/Controllers/FileSystemApiController.cs +++ b/src/Yavsc.Blogs/Controllers/FileSystemApiController.cs @@ -8,14 +8,10 @@ namespace Yavsc.Blogs.Controllers { using Yavsc.Attributes.Validation; using Yavsc.Models; - using Yavsc.Models.Blog; - using Yavsc.Models.Billing; using Yavsc.Exceptions; using Yavsc.Server.Helpers; using Yavsc.Abstract.Helpers; using Yavsc.Server.Models.FileSystem; - using Yavsc.ViewModels.UserFiles; - using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; [Authorize,Route(APIPrefix + "/fs")] @@ -38,66 +34,21 @@ namespace Yavsc.Blogs.Controllers this.siteSettings = siteSettings.Value; } - // GET: api/v1/fs [HttpGet()] public IActionResult Get() { return GetDir(null); } - // GET: api/v1/fs/{subdir} - // Liste les fichiers du sous-répertoire personnel de l'utilisateur, - // en enrichissant chaque entrée de l'identifiant de métadonnées - // UploadedFile.Id quand une ligne existe (cf. POST ci-dessous). [HttpGet("{*subdir}")] public IActionResult GetDir([ValidRemoteUserFilePath] string subdir="") { if (!ModelState.IsValid) return new BadRequestObjectResult(ModelState); - var uid = User.GetUserId(); - // The personal-storage tree is keyed by the user's login - // (UserName): EnsureDestinationDirectory writes under - // {Blog}/{UserName}, the download endpoint and avatar paths - // use user.UserName too. GetUserId() returns the "sub" claim - // (a GUID in production), so passing uid here made the listing - // look under {Blog}/{sub-GUID} — an empty tree, hence the - // "Mes fichiers" page listing nothing. Resolve the login and - // list under it. OwnerId on UploadedFile stays "sub" (uid), - // so EnrichWithFileIds still queries by uid. - var user = dbContext.Users.SingleOrDefault(u => u.Id == uid); - if (user == null) return NotFound(); - var files = FileSystemHelpers.GetUserFiles(siteSettings, user.UserName, subdir); - EnrichWithFileIds(files, uid, subdir); + // _logger.LogInformation($"listing files from {User.Identity.Name}{subdir}"); + var files = FileSystemHelpers.GetUserFiles(siteSettings, User.GetUserId(), subdir); return Ok(files); } - // Reporte l'UploadedFile.Id (quand il existe) sur chaque entrée - // retournée par la liste, pour que le client lourd puisse attacher - // le fichier à un devis/demande par référence. - void EnrichWithFileIds(UserDirectoryInfo dir, string ownerId, string subdir) - { - if (dir?.Files == null || dir.Files.Length == 0) return; - var relPaths = dir.Files - .Select(f => RelPath(subdir, f.Name)) - .ToHashSet(); - var byPath = dbContext.UploadedFiles - .Where(u => u.OwnerId == ownerId && relPaths.Contains(u.Path)) - .ToDictionary(u => u.Path, u => u.Id); - foreach (var f in dir.Files) - { - var rel = RelPath(subdir, f.Name); - if (byPath.TryGetValue(rel, out var id)) f.Id = id; - } - } - - // Chemin relatif au racine de l'espace perso de l'utilisateur, - // tel que stocké dans UploadedFile.Path (sans segment utilisateur). - static string RelPath(string subdir, string fileName) - { - // Normalize with '/' for cross-platform consistency in DB. - if (string.IsNullOrWhiteSpace(subdir)) return fileName; - return $"{subdir.TrimStart('/')}/{fileName}"; - } - [HttpPost("{*subdir}")] public IActionResult Post([ValidRemoteUserFilePath] string subdir="") { @@ -127,38 +78,7 @@ namespace Yavsc.Blogs.Controllers foreach (var f in Request.Form.Files) { var item = user.ReceiveUserFile(destDir, f); - if (!item.QuotaOffense) - { - // Persiste une ligne de métadonnées UploadedFile, - // propriétaire = l'utilisateur courant, chemin relatif - // à son espace perso. Upsert sur (OwnerId, Path) pour - // le re-téléversement (même fichier écrasé sur disque). - var rel = RelPath(subdir, item.FileName); - var existing = dbContext.UploadedFiles - .FirstOrDefault(u => u.OwnerId == uid && u.Path == rel); - if (existing == null) - { - var uploaded = new UploadedFile - { - OwnerId = uid, - Path = rel, - ContentType = f.ContentType, - Length = f.Length - }; - dbContext.UploadedFiles.Add(uploaded); - dbContext.SaveChanges(User.GetUserId()); - item.FileId = uploaded.Id; - } - else - { - existing.ContentType = f.ContentType; - existing.Length = f.Length; - dbContext.SaveChanges(User.GetUserId()); - item.FileId = existing.Id; - } - item.Length = f.Length; - item.ContentType = f.ContentType; - } + dbContext.SaveChanges(User.GetUserId()); received.Add(item); _logger.LogInformation($"Received '{item.FileName}'."); if (item.QuotaOffense) @@ -168,62 +88,6 @@ namespace Yavsc.Blogs.Controllers return Ok(received); } - // GET: api/v1/fs/file/{fileId} - // Télécharge une pièce jointe référencée par son UploadedFile.Id. - // Autorise : le propriétaire du fichier, ou toute partie (client / - // fournisseur) d'un devis ou d'une demande auquel ce fichier est - // rattaché via EstimateAttachedFile / QueryAttachedFile. Les octets - // sont lus depuis l'espace perso du propriétaire, sur le disque de - // ce host Blogs (le host API ne possède pas ces octets). - [HttpGet("file/{fileId:long}")] - public IActionResult GetFile(long fileId) - { - var file = dbContext.UploadedFiles.FirstOrDefault(u => u.Id == fileId); - if (file == null) return NotFound(new { error = "file not found" }); - - var uid = User.GetUserId(); - if (!CanReadFile(file, uid)) - return Forbid(); - - var ownerUserName = dbContext.Users - .Where(u => u.Id == file.OwnerId) - .Select(u => u.UserName) - .FirstOrDefault(); - if (string.IsNullOrEmpty(ownerUserName)) - return NotFound(new { error = "owner not found" }); - - var physicalPath = Path.Combine(siteSettings.Blog, ownerUserName, file.Path ?? ""); - var fi = new FileInfo(physicalPath); - if (!fi.Exists) return NotFound(new { error = "file not on disk" }); - - var contentType = string.IsNullOrWhiteSpace(file.ContentType) - ? "application/octet-stream" : file.ContentType; - var stream = fi.OpenRead(); - return File(stream, contentType, fi.Name); - } - - // Le propriétaire peut toujours lire son fichier ; sinon, il faut - // que le fichier soit rattaché à un devis ou une demande dont - // l'appelant est partie (client ou fournisseur). Les admin sont - // autorisés comme pour le reste du workflow. - bool CanReadFile(UploadedFile file, string uid) - { - if (User.IsInRole(Constants.AdminGroupName)) return true; - if (file.OwnerId == uid) return true; - - if (dbContext.EstimateAttachedFiles.Any(a => - a.FileId == file.Id && - (a.Estimate.ClientId == uid || a.Estimate.OwnerId == uid))) - return true; - - if (dbContext.QueryAttachedFiles.Any(a => - a.FileId == file.Id && - (a.Command.ClientId == uid || a.Command.PerformerId == uid))) - return true; - - return false; - } - [Route("/api/fsc/addquota/{uname}/{len}")] [Authorize("AdministratorOnly")] public IActionResult AddQuota(string uname, int len) diff --git a/src/Yavsc.Blogs/Yavsc.Blogs.csproj b/src/Yavsc.Blogs/Yavsc.Blogs.csproj index 38941be79..28b5841c9 100644 --- a/src/Yavsc.Blogs/Yavsc.Blogs.csproj +++ b/src/Yavsc.Blogs/Yavsc.Blogs.csproj @@ -8,8 +8,8 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 diff --git a/src/Yavsc.Blogs/appsettings-blogs.json b/src/Yavsc.Blogs/appsettings-blogs.json index 5361bfc3b..34134c937 100644 --- a/src/Yavsc.Blogs/appsettings-blogs.json +++ b/src/Yavsc.Blogs/appsettings-blogs.json @@ -26,10 +26,10 @@ "Kestrel": { "Endpoints": { "Http": { - "Url": "http://blogs.pschneider.fr" + "Url": "http://localhost:5002" }, "Https": { - "Url": "https://blogs.pschneider.fr" + "Url": "https://localhost:5003" } } } diff --git a/src/test/Yavsc.Org.Tests/ComputeKidTests.cs b/src/Yavsc.Org.Tests/ComputeKidTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/ComputeKidTests.cs rename to src/Yavsc.Org.Tests/ComputeKidTests.cs diff --git a/src/test/Yavsc.Org.Tests/Controllers/ClientControllerCollectionTests.cs b/src/Yavsc.Org.Tests/Controllers/ClientControllerCollectionTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/Controllers/ClientControllerCollectionTests.cs rename to src/Yavsc.Org.Tests/Controllers/ClientControllerCollectionTests.cs diff --git a/src/test/Yavsc.Org.Tests/Controllers/CommandFormsControllerTests.cs b/src/Yavsc.Org.Tests/Controllers/CommandFormsControllerTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/Controllers/CommandFormsControllerTests.cs rename to src/Yavsc.Org.Tests/Controllers/CommandFormsControllerTests.cs diff --git a/src/test/Yavsc.Org.Tests/Controllers/CommentsApiIntegrationTests.cs b/src/Yavsc.Org.Tests/Controllers/CommentsApiIntegrationTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/Controllers/CommentsApiIntegrationTests.cs rename to src/Yavsc.Org.Tests/Controllers/CommentsApiIntegrationTests.cs diff --git a/src/test/Yavsc.Org.Tests/Controllers/CommentsControllerTests.cs b/src/Yavsc.Org.Tests/Controllers/CommentsControllerTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/Controllers/CommentsControllerTests.cs rename to src/Yavsc.Org.Tests/Controllers/CommentsControllerTests.cs diff --git a/src/test/Yavsc.Org.Tests/Controllers/TestWebApplicationFactoryIsolationTests.cs b/src/Yavsc.Org.Tests/Controllers/TestWebApplicationFactoryIsolationTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/Controllers/TestWebApplicationFactoryIsolationTests.cs rename to src/Yavsc.Org.Tests/Controllers/TestWebApplicationFactoryIsolationTests.cs diff --git a/src/test/Yavsc.Org.Tests/DictionnaireMetierTests.cs b/src/Yavsc.Org.Tests/DictionnaireMetierTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/DictionnaireMetierTests.cs rename to src/Yavsc.Org.Tests/DictionnaireMetierTests.cs diff --git a/src/Yavsc.Org.Tests/Directory.Packages.props b/src/Yavsc.Org.Tests/Directory.Packages.props new file mode 100644 index 000000000..af2ef948c --- /dev/null +++ b/src/Yavsc.Org.Tests/Directory.Packages.props @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/test/Yavsc.Org.Tests/DumpHtml.cs b/src/Yavsc.Org.Tests/DumpHtml.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/DumpHtml.cs rename to src/Yavsc.Org.Tests/DumpHtml.cs diff --git a/src/Yavsc.Org.Tests/EstimateSignatureFileHelperTests.cs b/src/Yavsc.Org.Tests/EstimateSignatureFileHelperTests.cs new file mode 100644 index 000000000..b7b4d3591 --- /dev/null +++ b/src/Yavsc.Org.Tests/EstimateSignatureFileHelperTests.cs @@ -0,0 +1,136 @@ +using System.Security.Claims; +using System.Text.Json; +using Xunit; +using Yavsc.Models.Billing; +using Yavsc.Server.Helpers; +using Yavsc.Server.Models.FileSystem; + +namespace Yavsc.Org.Tests; + +/// +/// Tests for the static +/// helper. Scope is intentionally narrow: the file-naming format, +/// the strokes counter, and the on-disk write path. The controller +/// (authz, db persistence, signalR notification) is out of scope +/// for this commit and will get a dedicated integration test once +/// the Yavsc.Api test project is set up. +/// +public class EstimateSignatureFileHelperTests : IDisposable +{ + private readonly string _tempRoot; + private SiteSettings siteSettings; + + public EstimateSignatureFileHelperTests() + { + // UserFilesDirName is a process-wide static; we redirect + // it to a per-test temp dir so concurrent tests don't + // collide and the host filesystem is not littered. + _tempRoot = Path.Combine( + Path.GetTempPath(), + "yavsc-sig-tests-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(_tempRoot); + this.siteSettings = new SiteSettings { Blog = _tempRoot }; + } + + public void Dispose() + { + try { Directory.Delete(_tempRoot, recursive: true); } + catch { /* best effort — the OS will clean Temp eventually */ } + } + + [Fact] + public void FileNameFormat_lowercases_type_and_includes_estimateId_and_ticks() + { + var name = EstimateSignatureFileHelper.FileNameFormat( + SignatureType.Pro, 42, 638_000_000_000_000_000L); + Assert.Equal("sign-pro-42-638000000000000000.json", name); + + var cli = EstimateSignatureFileHelper.FileNameFormat( + SignatureType.Client, 7, 1L); + Assert.Equal("sign-client-7-1.json", cli); + } + + [Theory] + [InlineData(new int[] { }, 0)] + [InlineData(new[] { 1, 100, 200 }, 1)] + [InlineData(new[] { 2, 1, 2, 3, 4 }, 1)] + [InlineData(new[] { 1, 1, 1, 2, 2, 3, 3 }, 2)] + [InlineData(new[] { 0, 1, 2, 3 }, 0)] // malformed k=0: short-circuit + public void ReceiveEstimateSignatureAsync_writes_a_v1_envelope(int[] strokes, int expectedStrokeCount) + { + // We don't read the count back from the helper (it's a + // private method), but the JSON envelope must reflect + // it; this verifies the public behaviour end-to-end. + _ = expectedStrokeCount; + // Arrange + var user = MakeUser("alice"); + var payload = new SignaturePadPayload + { + CoordinateMax = 10_000, + CapturedAtUtc = new DateTime(2026, 7, 4, 12, 0, 0, DateTimeKind.Utc), + Strokes = strokes, + }; + + // Act + var fi = Run(user, 123L, SignatureType.Pro, payload, this.siteSettings); + + // Assert: file exists, sits under the user's root, and + // parses as a yavsc.signature/v1 envelope. + var fullPath = Path.Combine(fi.DestDir, fi.FileName); + Assert.True(File.Exists(fullPath), $"missing: {fullPath}"); + + using var doc = JsonDocument.Parse(File.ReadAllText(fullPath)); + var root = doc.RootElement; + Assert.Equal("yavsc.signature/v1", root.GetProperty("format").GetString()); + Assert.Equal(10_000, root.GetProperty("coordinateMax").GetInt32()); + Assert.Equal(123L, root.GetProperty("estimateId").GetInt64()); + Assert.Equal("Pro", root.GetProperty("type").GetString()); + Assert.Equal("alice", root.GetProperty("signerName").GetString()); + Assert.Equal(expectedStrokeCount, root.GetProperty("strokeCount").GetInt32()); + } + + [Fact] + 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!, + siteSettings, token: ct)); + } + + [Fact] + public async Task ReceiveEstimateSignatureAsync_rejects_non_positive_estimateId() + { + 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, this.siteSettings, token: ct)); + } + + // --- helpers ---------------------------------------------------- + + private static FileReceivedInfo Run( + ClaimsPrincipal user, long estimateId, SignatureType type, SignaturePadPayload payload, SiteSettings siteSettings) + { + // The helper is async; tests that don't care about the + // result can call it sync via .GetAwaiter().GetResult() + // because we know it never throws in the happy path. + return EstimateSignatureFileHelper + .ReceiveEstimateSignatureAsync(user, estimateId, type, payload, siteSettings, CancellationToken.None) + .GetAwaiter().GetResult(); + } + + private static ClaimsPrincipal MakeUser(string username) + { + return new ClaimsPrincipal(new ClaimsIdentity( + new[] { new Claim(ClaimTypes.Name, username) }, + authenticationType: "test")); + } +} diff --git a/src/test/Yavsc.Org.Tests/Fakes/RecordingSmtpClient.cs b/src/Yavsc.Org.Tests/Fakes/RecordingSmtpClient.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/Fakes/RecordingSmtpClient.cs rename to src/Yavsc.Org.Tests/Fakes/RecordingSmtpClient.cs diff --git a/src/test/Yavsc.Org.Tests/Fakes/RecordingSmtpClientFactory.cs b/src/Yavsc.Org.Tests/Fakes/RecordingSmtpClientFactory.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/Fakes/RecordingSmtpClientFactory.cs rename to src/Yavsc.Org.Tests/Fakes/RecordingSmtpClientFactory.cs diff --git a/src/test/Yavsc.Org.Tests/Mandatory/BatchTests.cs b/src/Yavsc.Org.Tests/Mandatory/BatchTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/Mandatory/BatchTests.cs rename to src/Yavsc.Org.Tests/Mandatory/BatchTests.cs diff --git a/src/test/Yavsc.Org.Tests/Mandatory/Remoting.cs b/src/Yavsc.Org.Tests/Mandatory/Remoting.cs similarity index 59% rename from src/test/Yavsc.Org.Tests/Mandatory/Remoting.cs rename to src/Yavsc.Org.Tests/Mandatory/Remoting.cs index 5caf905b4..7def1a43f 100644 --- a/src/test/Yavsc.Org.Tests/Mandatory/Remoting.cs +++ b/src/Yavsc.Org.Tests/Mandatory/Remoting.cs @@ -1,10 +1,7 @@ using System.Security.Cryptography.X509Certificates; using System.Net.Security; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; using IdentityModel.Client; using Xunit; -using Yavsc.Server.Helpers; namespace Yavsc.Org.Tests { @@ -73,72 +70,6 @@ namespace Yavsc.Org.Tests } - /// - /// Regression for the MyFiles upload 500 / null - /// User.Identity.Name: the access token minted by the - /// real IdentityServer (resource-owner password grant) must - /// carry a name claim whose value is the user's login - /// (UserName), and - /// — which reads FindFirstValue("name") — must resolve - /// to it on a principal built the way - /// AddYavscJwtBearer builds one - /// (MapInboundClaims=false, NameClaimType="name"). - /// - /// Prerequisites wired by : - /// the test client's AllowedScopes include - /// openid+profile, and the standard identity - /// resources are seeded by the production pipeline. Requesting - /// openid profile makes ProfileService emit - /// name = user.UserName. - /// - [Fact] - public async Task PasswordToken_carries_name_claim_equal_to_login() - { - var serverUrl = GetServerUrl(); - var cancellationToken = TestContext.Current.CancellationToken; - - var client = NewHttpClient(); - var tokenEndpoint = await ResolveTokenEndpointAsync(client, serverUrl, cancellationToken); - - var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest - { - Address = tokenEndpoint, - ClientId = RequireNonEmpty(_serverFixture.TestClientId, nameof(_serverFixture.TestClientId)), - ClientSecret = RequireNonEmpty(_serverFixture.TestClientSecret, nameof(_serverFixture.TestClientSecret)), - UserName = RequireNonEmpty(_serverFixture.TestingUserName, nameof(_serverFixture.TestingUserName)), - Password = RequireNonEmpty(_serverFixture.TestingUserPassword, nameof(_serverFixture.TestingUserPassword)), - // "openid profile" triggers ProfileService to emit the - // "name" claim (user.UserName). Without profile, the - // token carries only sub + scopes and GetUserName() - // resolves to null — the exact regression behind the - // MyFiles upload 500. "test" is the API scope, so a real - // JWT access token (not just an id_token) is issued. - Scope = "openid profile test" - }, cancellationToken); - - Assert.False(response.IsError, - $"Token request failed: {response.Error} ({response.ErrorDescription})"); - - // Decode the access token straight from the payload so short - // claim names ("name", "sub") stay as-emitted — mirrors - // AddYavscJwtBearer with MapInboundClaims=false. - var jwt = new JwtSecurityToken(response.AccessToken); - var nameClaim = jwt.Claims.FirstOrDefault(c => c.Type == "name"); - Assert.True(nameClaim is not null, - "access_token has no 'name' claim; ProfileService did not emit it " + - "(is the 'profile' scope in the client's AllowedScopes and requested?)."); - Assert.Equal(_serverFixture.TestingUserName, nameClaim!.Value); - - // Rebuild the principal the way a bearer consumer host does - // and exercise the real GetUserName() helper used by the - // file-system / avatar paths. - var identity = new ClaimsIdentity( - jwt.Claims, authenticationType: "Bearer", - nameType: "name", roleType: null); - var principal = new ClaimsPrincipal(identity); - Assert.Equal(_serverFixture.TestingUserName, principal.GetUserName()); - } - [Fact] public async Task GetSignin_returns_a_page() { diff --git a/src/test/Yavsc.Org.Tests/Mandatory/Resources.cs b/src/Yavsc.Org.Tests/Mandatory/Resources.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/Mandatory/Resources.cs rename to src/Yavsc.Org.Tests/Mandatory/Resources.cs diff --git a/src/test/Yavsc.Org.Tests/Mandatory/Services.cs b/src/Yavsc.Org.Tests/Mandatory/Services.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/Mandatory/Services.cs rename to src/Yavsc.Org.Tests/Mandatory/Services.cs diff --git a/src/Yavsc.Org.Tests/MarkdownTests.cs b/src/Yavsc.Org.Tests/MarkdownTests.cs new file mode 100644 index 000000000..1bd0db9c5 --- /dev/null +++ b/src/Yavsc.Org.Tests/MarkdownTests.cs @@ -0,0 +1,34 @@ +using DotMarkdown.Linq; +using Markdig; +using Xunit; +using static DotMarkdown.Linq.MFactory; + + +public class MarkdownTests +{ + [Fact] + public void TestMarkdownRendering() + { + MDocument document = Document( + Heading1("Markdown Sample"), + Heading2("Bullet List"), + BulletList( + "text", + Bold("bold text")), + HorizontalRule(), + Heading2("IndentedCodeBlock"), + IndentedCodeBlock("string s = null;")); + + Console.WriteLine(document.ToString()); + + } + + [Fact] + public void TestAnotherMarkdownRendering() + { + var result = Markdown.ToHtml("This is a text with some *emphasis*"); + + + Console.WriteLine(result); + } +} diff --git a/src/test/Yavsc.Org.Tests/NonRegression/AbstractTests.cs b/src/Yavsc.Org.Tests/NonRegression/AbstractTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/NonRegression/AbstractTests.cs rename to src/Yavsc.Org.Tests/NonRegression/AbstractTests.cs diff --git a/src/test/Yavsc.Org.Tests/NonRegression/ApplicationUserDisplayTemplateTests.cs b/src/Yavsc.Org.Tests/NonRegression/ApplicationUserDisplayTemplateTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/NonRegression/ApplicationUserDisplayTemplateTests.cs rename to src/Yavsc.Org.Tests/NonRegression/ApplicationUserDisplayTemplateTests.cs diff --git a/src/test/Yavsc.Org.Tests/NonRegression/AvatarFallbackTests.cs b/src/Yavsc.Org.Tests/NonRegression/AvatarFallbackTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/NonRegression/AvatarFallbackTests.cs rename to src/Yavsc.Org.Tests/NonRegression/AvatarFallbackTests.cs diff --git a/src/test/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs b/src/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs similarity index 71% rename from src/test/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs rename to src/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs index 6504be24d..4e8ae0d6f 100644 --- a/src/test/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs +++ b/src/Yavsc.Org.Tests/NonRegression/BillingServiceTests.cs @@ -8,16 +8,6 @@ using Xunit; namespace Yavsc { - // BillingService and Config expose static mutable registries that - // WorkflowHelpers.ConfigureBillingService() also mutates from the - // WebServerFixture host build (HostingExtensions.cs). Without a - // collection, xUnit runs this class in parallel with the - // "Yavsc Server" collection, and the host build's ConfigureBilling - // call can land between this test's two ConfigureBilling calls, - // breaking the idempotency count assertion (a flaky failure that - // surfaces whenever the fixture's timing shifts). Pinning the class - // to the same collection serializes it with the host build. - [Collection("Yavsc Server")] [Trait("regression", "II")] public class BillingServiceTests { diff --git a/src/test/Yavsc.Org.Tests/NonRegression/Database.cs b/src/Yavsc.Org.Tests/NonRegression/Database.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/NonRegression/Database.cs rename to src/Yavsc.Org.Tests/NonRegression/Database.cs diff --git a/src/test/Yavsc.Org.Tests/NonRegression/EMailling.cs b/src/Yavsc.Org.Tests/NonRegression/EMailling.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/NonRegression/EMailling.cs rename to src/Yavsc.Org.Tests/NonRegression/EMailling.cs diff --git a/src/test/Yavsc.Org.Tests/NonRegression/FileServerUrlHelpersTests.cs b/src/Yavsc.Org.Tests/NonRegression/FileServerUrlHelpersTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/NonRegression/FileServerUrlHelpersTests.cs rename to src/Yavsc.Org.Tests/NonRegression/FileServerUrlHelpersTests.cs diff --git a/src/test/Yavsc.Org.Tests/NonRegression/OidcSeedTests.cs b/src/Yavsc.Org.Tests/NonRegression/OidcSeedTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/NonRegression/OidcSeedTests.cs rename to src/Yavsc.Org.Tests/NonRegression/OidcSeedTests.cs diff --git a/src/test/Yavsc.Org.Tests/NonRegression/PerformerCodeInputValidationTests.cs b/src/Yavsc.Org.Tests/NonRegression/PerformerCodeInputValidationTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/NonRegression/PerformerCodeInputValidationTests.cs rename to src/Yavsc.Org.Tests/NonRegression/PerformerCodeInputValidationTests.cs diff --git a/src/test/Yavsc.Org.Tests/NonRegression/SetActivityCountryValidationViewTests.cs b/src/Yavsc.Org.Tests/NonRegression/SetActivityCountryValidationViewTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/NonRegression/SetActivityCountryValidationViewTests.cs rename to src/Yavsc.Org.Tests/NonRegression/SetActivityCountryValidationViewTests.cs diff --git a/src/test/Yavsc.Org.Tests/NonRegression/UserDisplayHelpersTests.cs b/src/Yavsc.Org.Tests/NonRegression/UserDisplayHelpersTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/NonRegression/UserDisplayHelpersTests.cs rename to src/Yavsc.Org.Tests/NonRegression/UserDisplayHelpersTests.cs diff --git a/src/test/Yavsc.Org.Tests/Resources/Test.TestResources.resx b/src/Yavsc.Org.Tests/Resources/Test.TestResources.resx similarity index 100% rename from src/test/Yavsc.Org.Tests/Resources/Test.TestResources.resx rename to src/Yavsc.Org.Tests/Resources/Test.TestResources.resx diff --git a/src/test/Yavsc.Org.Tests/Services/FileSystemAuthManagerTests.cs b/src/Yavsc.Org.Tests/Services/FileSystemAuthManagerTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/Services/FileSystemAuthManagerTests.cs rename to src/Yavsc.Org.Tests/Services/FileSystemAuthManagerTests.cs diff --git a/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs b/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs new file mode 100644 index 000000000..61d774c57 --- /dev/null +++ b/src/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs @@ -0,0 +1,43 @@ +using IdentityServer8.Stores; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Yavsc.Org.Tests.Smoke; + +/// +/// Smoke for the Account BC (login / registration / OIDC discovery). +/// Hits the in-memory test server through +/// : no sockets, no +/// self-signed certificates, no real DB (the +/// AddInMemoryCollection in WebServerFixture.SetupHost +/// points the EF context at the in-memory provider). +/// +/// GET /signin is the public login endpoint served by +/// Yavsc.Org/Controllers/Accounting/AccountController +/// (decorated [HttpGet(YavscConstants.SigninPath)] with +/// YavscConstants.SigninPath = "~/signin"). A 200 means the +/// entire pipeline (routing + Razor + IdentityServer + EF + DI) +/// is wired correctly end-to-end. +/// +public class AccountSmokeTests : IClassFixture +{ + private readonly TestWebApplicationFactory _factory; + + public AccountSmokeTests(TestWebApplicationFactory factory) + { + _factory = factory; + } + + + + [Fact] + public async Task ResourceStore_get_all_resources_does_not_throw() + { + using var scope = _factory.Services.CreateScope(); + var resourceStore = scope.ServiceProvider.GetRequiredService(); + + var exception = await Record.ExceptionAsync(resourceStore.GetAllResourcesAsync); + + Assert.Null(exception); + } +} diff --git a/src/test/Yavsc.Org.Tests/Smoke/BlogSmokeTests.cs b/src/Yavsc.Org.Tests/Smoke/BlogSmokeTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/Smoke/BlogSmokeTests.cs rename to src/Yavsc.Org.Tests/Smoke/BlogSmokeTests.cs diff --git a/src/test/Yavsc.Org.Tests/StaticAssetsPathsTests.cs b/src/Yavsc.Org.Tests/StaticAssetsPathsTests.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/StaticAssetsPathsTests.cs rename to src/Yavsc.Org.Tests/StaticAssetsPathsTests.cs diff --git a/src/test/Yavsc.Org.Tests/TestUserMiddleware.cs b/src/Yavsc.Org.Tests/TestUserMiddleware.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/TestUserMiddleware.cs rename to src/Yavsc.Org.Tests/TestUserMiddleware.cs diff --git a/src/test/Yavsc.Org.Tests/TestUserStartupFilter.cs b/src/Yavsc.Org.Tests/TestUserStartupFilter.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/TestUserStartupFilter.cs rename to src/Yavsc.Org.Tests/TestUserStartupFilter.cs diff --git a/src/test/Yavsc.Org.Tests/TestWebApplicationFactory.cs b/src/Yavsc.Org.Tests/TestWebApplicationFactory.cs similarity index 100% rename from src/test/Yavsc.Org.Tests/TestWebApplicationFactory.cs rename to src/Yavsc.Org.Tests/TestWebApplicationFactory.cs diff --git a/src/test/Yavsc.Org.Tests/WebServerFixture.cs b/src/Yavsc.Org.Tests/WebServerFixture.cs similarity index 87% rename from src/test/Yavsc.Org.Tests/WebServerFixture.cs rename to src/Yavsc.Org.Tests/WebServerFixture.cs index 522103023..5ca1daa06 100644 --- a/src/test/Yavsc.Org.Tests/WebServerFixture.cs +++ b/src/Yavsc.Org.Tests/WebServerFixture.cs @@ -1,5 +1,4 @@ using IdentityServer8.EntityFramework.Entities; -using IdentityServer8.EntityFramework.Mappers; using IdentityServer8.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; @@ -239,26 +238,6 @@ public sealed class WebServerFixture : WebHostFixture AddAuthorizedClient(scope, TestClientId, TestClientSecret); } - // EnsureDefaultApplicationScopes seeds the standard identity - // resources (openid, profile) once at host startup, but a - // ResetAndMigrateDatabase call does EnsureDeleted on the shared - // in-memory store, which wipes them — and ReseedAuthTestData - // previously only re-added the client. Without openid/profile - // present, any token request asking for those scopes returns - // invalid_scope, so a password-grant token carrying the "name" - // claim (scope "openid profile") could not be minted after a - // reset. Re-seed them here to mirror the startup seed. - var identityResources = configDb.Set(); - if (!identityResources.Any(r => r.Name == "openid")) - { - identityResources.Add(new IdentityResources.OpenId().ToEntity()); - } - if (!identityResources.Any(r => r.Name == "profile")) - { - identityResources.Add(new IdentityResources.Profile().ToEntity()); - } - configDb.SaveChanges(); - _sharedTestClientId = TestClientId; _sharedTestClientSecret = TestClientSecret; _sharedTestingUserName = TestingUserName; @@ -305,12 +284,7 @@ public sealed class WebServerFixture : WebHostFixture { ClientId = testClientId, AccessTokenLifetime = 3600000, - // AccessTokenType = 0 → Jwt in IdentityServer8 (1 = Reference, - // the inverse of Duende's enum). The real PostIt client issues - // JWT access tokens so the Blogs/Api bearer hosts can validate - // them with JwtBearer; mirror that here so the token can be - // decoded and its "name" claim asserted. - AccessTokenType = 0, + AccessTokenType = 1, ClientName = "Testing client", Enabled = true, RequireClientSecret = true @@ -353,23 +327,6 @@ public sealed class WebServerFixture : WebHostFixture ClientId = testingClient.Id, Scope = "test" }); - // Allow the identity scopes so a resource-owner-password token - // can request "openid profile" and trigger ProfileService to - // emit the "name" claim (user.UserName). Without profile in the - // client's AllowedScopes, IdentityServer strips the scope from - // the request and the access_token carries no "name" — the - // regression that left GetUserName() / the MyFiles upload root - // null. Additive only: existing tests request "test" alone. - configDb.Set().Add(new ClientScope - { - ClientId = testingClient.Id, - Scope = "openid" - }); - configDb.Set().Add(new ClientScope - { - ClientId = testingClient.Id, - Scope = "profile" - }); configDb.SaveChanges(); } diff --git a/src/test/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj similarity index 89% rename from src/test/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj rename to src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj index 7a364f2d6..bdacafc5c 100644 --- a/src/test/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj +++ b/src/Yavsc.Org.Tests/Yavsc.Org.Tests.csproj @@ -11,8 +11,8 @@ $(MSBuildProjectDirectory)\test.runsettings 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 @@ -30,6 +30,7 @@ + @@ -42,9 +43,9 @@ - - - + + + diff --git a/src/test/Yavsc.Org.Tests/appsettings.json b/src/Yavsc.Org.Tests/appsettings.json similarity index 100% rename from src/test/Yavsc.Org.Tests/appsettings.json rename to src/Yavsc.Org.Tests/appsettings.json diff --git a/src/test/Yavsc.Org.Tests/test.runsettings b/src/Yavsc.Org.Tests/test.runsettings similarity index 100% rename from src/test/Yavsc.Org.Tests/test.runsettings rename to src/Yavsc.Org.Tests/test.runsettings diff --git a/src/test/Yavsc.Org.Tests/xunit.runner.json b/src/Yavsc.Org.Tests/xunit.runner.json similarity index 100% rename from src/test/Yavsc.Org.Tests/xunit.runner.json rename to src/Yavsc.Org.Tests/xunit.runner.json diff --git a/src/Yavsc.Org/Controllers/Accounting/AccountController.cs b/src/Yavsc.Org/Controllers/Accounting/AccountController.cs index f8fa7f0d2..04f60aa1d 100644 --- a/src/Yavsc.Org/Controllers/Accounting/AccountController.cs +++ b/src/Yavsc.Org/Controllers/Accounting/AccountController.cs @@ -1061,7 +1061,6 @@ IHtmlLocalizerFactory htmlLocalizerFactory, } return View(); } - [HttpPost, Authorize] public async Task Delete(UnregisterViewModel model) { diff --git a/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs b/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs index 31142cd60..b55171879 100644 --- a/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs +++ b/src/Yavsc.Org/Controllers/Communicating/BlogspotController.cs @@ -90,6 +90,73 @@ namespace Yavsc.Org.Controllers ); } + // GET: Blog/Create + [Authorize()] + public IActionResult Create(string title) + { + var result = new BlogPostEditViewModel + (new Models.Blog.BlogPost + { + Title = title + }, true); + SetLangItems(); + return View(result); + } + + // POST: Blog/Create + [HttpPost, Authorize, ValidateAntiForgeryToken] + public IActionResult Create(Models.Blog.BlogPost blogInput) + { + if (ModelState.IsValid) + { + var uid = User.GetUserId(); + Models.Blog.BlogPost post = blogSpotService.Create(uid, blogInput); + blogSpotService.AttachFiles(Request.Form.Files, uid, post.Id); + return RedirectToAction("Index"); + } + return View("Edit", blogInput); + } + + [Authorize()] + // GET: Blog/Edit/5 + public async Task Edit(long? id) + { + if (id == null) + { + return NotFound(); + } + try + { + var blog = await blogSpotService.GetPostForEdition(User, id.Value); + if (blog == null) + { + return NotFound(); + } + + + SetLangItems(); + return View(blog); + + } + catch (AuthorizationFailureException) + { + return new ChallengeResult(); + } + } + + // POST: Blog/Edit/5 + [HttpPost] + [ValidateAntiForgeryToken, Authorize()] + public async Task Edit(BlogPostEditViewModel blogEdit) + { + if (ModelState.IsValid) + { + await blogSpotService.Modify(User, blogEdit); + ViewBag.StatusMessage = "Post modified"; + return RedirectToAction("Index"); + } + return View(blogEdit); + } // GET: Blog/Delete/5 [ActionName("Delete"), Authorize()] diff --git a/src/Yavsc.Org/Controllers/Communicating/CommentsController.cs b/src/Yavsc.Org/Controllers/Communicating/CommentsController.cs index 569b5a6d7..02dda32a1 100644 --- a/src/Yavsc.Org/Controllers/Communicating/CommentsController.cs +++ b/src/Yavsc.Org/Controllers/Communicating/CommentsController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.EntityFrameworkCore; using Yavsc.Models; @@ -20,7 +19,7 @@ namespace Yavsc.Controllers public CommentsController(ApplicationDbContext context) { - _context = context; + _context = context; } [HttpGet("{id:long}", Name = "GetComment")] @@ -83,17 +82,8 @@ namespace Yavsc.Controllers return CreatedAtRoute("GetComment", new { id = comment.Id }, new { id = comment.Id, dateCreated = comment.DateCreated }); } - // --- Actions MVC scaffoldées (vues CRUD) --------------------- - // Ces actions ne font pas partie de la surface d'API documentée - // par Swagger. Elles sont exclues de l'ApiExplorer : sinon, comme - // le contrôleur porte [Route("~/api/v1/blogcomments")], leurs - // [HttpPost] sans template propre atterrissent sur la même route - // "POST api/v1/blogcomments" que l'action Post ci-dessus, et - // Swashbuckle lève une SwaggerGeneratorException (conflicting - // method/path) au démarrage de /swagger. // GET: Comments [HttpGet] - [ApiExplorerSettings(IgnoreApi = true)] public async Task Index() { var applicationDbContext = _context.Comment.Include(c => c.Post); @@ -101,7 +91,6 @@ namespace Yavsc.Controllers } // GET: Comments/Details/5 - [ApiExplorerSettings(IgnoreApi = true)] public async Task Details(long? id) { if (id == null) @@ -120,7 +109,6 @@ namespace Yavsc.Controllers // GET: Comments/Create (MVC form endpoint) [HttpGet("form")] - [ApiExplorerSettings(IgnoreApi = true)] public IActionResult Create() { ViewBag.ReceiverId = new SelectList(_context.BlogSpot, "Id", "Title"); @@ -130,7 +118,6 @@ namespace Yavsc.Controllers // POST: Comments/Create (MVC form endpoint) [HttpPost("form")] [ValidateAntiForgeryToken] - [ApiExplorerSettings(IgnoreApi = true)] public async Task Create(Comment comment) { comment.UserCreated = User.GetUserId(); @@ -139,7 +126,7 @@ namespace Yavsc.Controllers // remove the stale binding error so a valid authenticated POST // does not fall into the invalid branch. ModelState.Remove(nameof(Comment.AuthorId)); - + if (ModelState.IsValid) { _context.Comment.Add(comment); @@ -151,7 +138,6 @@ namespace Yavsc.Controllers } // GET: Comments/Edit/5 - [ApiExplorerSettings(IgnoreApi = true)] public async Task Edit(long? id) { if (id == null) @@ -171,7 +157,6 @@ namespace Yavsc.Controllers // POST: Comments/Edit/5 [HttpPost] [ValidateAntiForgeryToken] - [ApiExplorerSettings(IgnoreApi = true)] public async Task Edit(Comment comment) { if (ModelState.IsValid) @@ -186,7 +171,6 @@ namespace Yavsc.Controllers // GET: Comments/Delete/5 [ActionName("Delete")] - [ApiExplorerSettings(IgnoreApi = true)] public async Task Delete(long? id) { if (id == null) @@ -206,7 +190,6 @@ namespace Yavsc.Controllers // POST: Comments/Delete/5 [HttpPost, ActionName("Delete")] [ValidateAntiForgeryToken] - [ApiExplorerSettings(IgnoreApi = true)] public async Task DeleteConfirmed(long id) { Comment comment = await _context.Comment.SingleAsync(m => m.Id == id); diff --git a/src/Yavsc.Org/Controllers/Contracting/FrontOfficeController.cs b/src/Yavsc.Org/Controllers/Contracting/FrontOfficeController.cs index ac97193d0..549d80942 100644 --- a/src/Yavsc.Org/Controllers/Contracting/FrontOfficeController.cs +++ b/src/Yavsc.Org/Controllers/Contracting/FrontOfficeController.cs @@ -8,6 +8,7 @@ namespace Yavsc.Controllers using Helpers; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Localization; + using Microsoft.Extensions.Options; using Models; using ViewModels.FrontOffice; using Yavsc.Server.Helpers; @@ -20,20 +21,22 @@ namespace Yavsc.Controllers readonly ILogger _logger; readonly IStringLocalizer _SR; private readonly IBillingService _billing; + private readonly SiteSettings _siteSettings; public FrontOfficeController(ApplicationDbContext context, UserManager userManager, IBillingService billing, ILoggerFactory loggerFactory, - IStringLocalizer SR) + IStringLocalizer SR, + IOptions siteSettings) { _context = context; _userManager = userManager; _logger = loggerFactory.CreateLogger(); _SR = SR; _billing = billing; + _siteSettings = siteSettings.Value; } - public ActionResult Index() { var uid = User.FindFirstValue(ClaimTypes.NameIdentifier); @@ -43,9 +46,9 @@ namespace Yavsc.Controllers { EstimateToProduceCount = _context.RdvQueries.Where(c => c.PerformerId == uid && c.EventDate > now && c.Status == QueryStatus.Inserted && c.ValidationDate == null && !_context.Estimates.Any(e => e.CommandId == c.Id)).Count(), - EstimateToHonorAsProCount = _context.RdvQueries.Where(c => c.PerformerId == uid && c.EventDate > now && (c.Status == QueryStatus.ProAccepted || c.Status == QueryStatus.Accepted) + EstimateToHonorAsProCount = _context.RdvQueries.Where(c => c.PerformerId == uid && c.EventDate > now && c.Status == QueryStatus.Accepted && c.ValidationDate == null && _context.Estimates.Any(e => e.CommandId == c.Id )).Count(), - EstimateToSignAsCliCount = _context.Estimates.Where(e => e.ClientId == uid && (e.Query.Status == QueryStatus.ProAccepted || e.Query.Status == QueryStatus.Accepted)).Count(), + EstimateToSignAsCliCount = _context.Estimates.Where(e => e.ClientId == uid && e.Query.Status == QueryStatus.Accepted).Count(), BillToSignAsCliCount = 0, NewPayementsCount = 0 @@ -77,5 +80,61 @@ namespace Yavsc.Controllers return View(result); } + + [Produces("text/x-tex"), Authorize, Route("estimate-{id}.tex")] + [HttpGet] + public ViewResult EstimateTex(long id) + { + var estimate = _context.Estimates.Include(x => x.Query) + .Include(x => x.Query.Client) + .Include(x => x.Query.PerformerProfile) + .Include(x => x.Query.PerformerProfile.OrganizationAddress) + .Include(x => x.Query.PerformerProfile.Performer) + .Include(e => e.Bill).FirstOrDefault(x => x.Id == id); + Response.ContentType = "text/x-tex"; + return View("Estimate.tex", estimate); + } + + [Authorize, Route("Estimate-{id}.pdf")] + [HttpGet] + public IActionResult EstimatePdf(long id) + { + ViewBag.TempDir = _siteSettings.TempDir; + ViewBag.BillsDir = _siteSettings.Bills; + var estimate = _context.Estimates.Include(x => x.Query) + .Include(x => x.Query.Client) + .Include(x => x.Query.PerformerProfile) + .Include(x => x.Query.PerformerProfile.OrganizationAddress) + .Include(x => x.Query.PerformerProfile.Performer) + .Include(e => e.Bill).FirstOrDefault(x => x.Id == id); + if (estimate == null) + throw new Exception("No data"); + return View("Estimate.pdf", estimate); + } + + [Authorize] + public IActionResult EstimateProValidation() + { + throw new NotImplementedException(); + + } + + [Authorize] + public IActionResult EstimateClientValidation() + { + throw new NotImplementedException(); + + } + [Authorize] + public IActionResult BillValidation() + { + throw new NotImplementedException(); + + } + [Authorize] + public IActionResult BillAcquitment() + { + throw new NotImplementedException(); + } } } diff --git a/src/Yavsc.Org/Extensions/HostingExtensions.cs b/src/Yavsc.Org/Extensions/HostingExtensions.cs index b5bb65cc5..8c1db2a73 100644 --- a/src/Yavsc.Org/Extensions/HostingExtensions.cs +++ b/src/Yavsc.Org/Extensions/HostingExtensions.cs @@ -215,18 +215,6 @@ public static class HostingExtensions services.AddScoped, UserClaimsPrincipalFactory>(); - // The default application cookie LoginPath is "/Account/Login", which - // has no controller here — anonymous access to a restricted page must - // redirect to the actual sign-in endpoint instead. The paths mirror - // the route attributes on AccountController (Constants.SigninPath etc., - // which carry a "~/" prefix that the cookie middleware does not want). - services.ConfigureApplicationCookie(options => - { - options.LoginPath = "/signin"; - options.LogoutPath = "/signout"; - options.AccessDeniedPath = "/Account/AccessDenied"; - }); - // Dev-only: Chromium rejects SameSite=None without Secure on http:// // (e.g. http://localhost:5000). The default Identity cookie policy // sets SameSite=None, which is invalid without Secure. Force Lax in @@ -368,17 +356,6 @@ public static class HostingExtensions }) .AddAspNetIdentity() - // Register the custom ProfileService so IdentityServer actually - // calls it instead of the default UserProfileService. Without - // this, the class is dead code: IS8 falls back to the default, - // which only emits claims listed in each API resource's - // UserClaims (RequestedClaimTypes) — "name" is never among - // them, so every access token lacks "name" and the Blogs/Api - // bearer hosts get GetUserName() == null (the MyFiles upload - // 500). Our ProfileService emits name/email/roles for any - // user grant whose client is allowed "profile", independent - // of the caller, so the access token carries the login. - .AddProfileService() .AddClientStore() .AddClientConfigurationValidator() .AddCorsPolicyService() diff --git a/src/Yavsc.Org/Helpers/AsciiDocTagHelper.cs b/src/Yavsc.Org/Helpers/AsciiDocTagHelper.cs index 778cf5cfe..96391990a 100644 --- a/src/Yavsc.Org/Helpers/AsciiDocTagHelper.cs +++ b/src/Yavsc.Org/Helpers/AsciiDocTagHelper.cs @@ -17,8 +17,8 @@ namespace Yavsc.Helpers if (string.IsNullOrWhiteSpace(text)) return; - try + try { if (context.AllAttributes.ContainsName("summary")) { diff --git a/src/Yavsc.Org/Helpers/TeXHelpers.cs b/src/Yavsc.Org/Helpers/TeXHelpers.cs new file mode 100644 index 000000000..f7cc812fb --- /dev/null +++ b/src/Yavsc.Org/Helpers/TeXHelpers.cs @@ -0,0 +1,198 @@ +using System.Diagnostics; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewEngines; + +namespace Yavsc.Helpers +{ + using Microsoft.AspNetCore.Mvc.Infrastructure; + using ViewModels.Gen; + public class TeXString : HtmlString + { + + public TeXString(TeXString teXString): base(teXString.ToString()) + { + } + + public TeXString(string str) : base(str) + { + + + } + + public static TeXString operator+ (TeXString a, TeXString b) { + return new TeXString(a.ToString()+b.ToString()); + } + + } + public class Replacement + { + string target; + string replacement; + public Replacement(string target, string replacement) + { + this.target = target; + this.replacement = replacement; + } + public string Execute(string source) + { + return source?.Replace(target, replacement) ?? null; + } + } + public static class TeXHelpers + { + public static readonly Replacement[] SpecialCharsDefaultRendering = + { + new Replacement("<","\\textless"), + new Replacement(">","\\textgreater"), + new Replacement("©","\\copyright"), + new Replacement("®","\\textregistered"), + new Replacement("\\","\\textbackslash"), + new Replacement("™","\\texttrademark"), + new Replacement("¶","\\P"), + new Replacement("|","\\textbar"), + new Replacement("%","\\%"), + new Replacement("{","\\{"), + new Replacement("}","\\}"), + new Replacement("_","\\_"), + new Replacement("#","\\#"), + new Replacement("$","\\$"), + new Replacement("_","\\_"), + new Replacement("¿","\\textquestiondown"), + new Replacement("§","\\S"), + new Replacement("£","\\pounds"), + new Replacement("&","\\&"), + new Replacement("¡","\\textexclamdown"), + new Replacement("†","\\dag"), + new Replacement("–","\\textendash"), + new Replacement("°","\\textdegree") + }; + + public static TeXString ToTeX(this string source, string defaultValue="\\textit{néant}") + { + if (source==null) return new TeXString(defaultValue); + string result=source; + foreach (var r in SpecialCharsDefaultRendering) + { + result = r.Execute(result); + } + return new TeXString(result); + } + + public static TeXString ToTeXCell(this string source, string defaultValue="\\textit{néant}") + { + if (source==null) return new TeXString(defaultValue); + string result=source; + foreach (var r in SpecialCharsDefaultRendering) + { + result = r.Execute(result); + } + result = result.Replace("\n","\\tabularnewline "); + return new TeXString(result); + } + + + public static string NewLinesWith(this string target, string separator) + { + var items = target.Split(new char[] { '\n' }).Where( + s => !string.IsNullOrWhiteSpace(s)); + + return string.Join(separator, items); + } + + public static TeXString ToTeXLines(this string source, string defaultValue, string lineSeparator = "\n\\\\") + { + if (source == null) return new TeXString(defaultValue); + return new TeXString( source.ToTeX().ToString().NewLinesWith(lineSeparator) ); + } + + public static TeXString SplitAddressToTeX (this string source, string lineSeparator = "\n\\\\", string defaultValue = "\\textit{pas d'adresse postale}") + { + if (string.IsNullOrWhiteSpace(source)) return new TeXString(defaultValue); + var alines = source.Split(','); + var texlines = alines.Select(l=>l.ToTeX().ToString()); + return new TeXString(string.Join(lineSeparator,texlines)); + } + + public static bool GenerateEstimatePdf(this SiteSettings settings, PdfGenerationViewModel Model) + { + string errorMsg = null; + var billdir = Model.DestDir; + var tempdir = settings.TempDir; + string name = Model.BaseFileName; + string fullname = new FileInfo( + System.IO.Path.Combine(tempdir, name)).FullName; + string ofullname = new FileInfo( + System.IO.Path.Combine(billdir, name)).FullName; + + FileInfo fi = new FileInfo(fullname + ".tex"); + FileInfo fo = new FileInfo(ofullname + ".pdf"); + using (StreamWriter sw = new StreamWriter(fi.FullName)) + { + sw.Write(Model.TeXSource); + } + if (!fi.Exists) + { + errorMsg = "Source write failed"; + } + else + { + using (Process p = new Process()) + { + p.StartInfo.WorkingDirectory = tempdir; + p.StartInfo = new ProcessStartInfo + { + UseShellExecute = false, + WorkingDirectory = tempdir, + FileName = "/usr/bin/texi2pdf", + Arguments = $"--batch --build-dir=. -o {fo.FullName} {fi.FullName}" + }; + p.Start(); + p.WaitForExit(); + if (p.ExitCode != 0) + { + errorMsg = $"Pdf generation failed with exit code: {p.ExitCode}"; + } + else + { + fi.Delete(); + var di = new DirectoryInfo(Path.Combine(tempdir,$"{Model.BaseFileName}.t2d")); + di.Delete(true); + + } + } + } + Model.Generated = fo.Exists; + Model.GenerationErrorMessage = new HtmlString(errorMsg); + return fo.Exists; + } + + public static string RenderViewToString( + this Controller controller, IViewEngine engine, + IActionContextAccessor contextAccessor, + string viewName, object model, bool isMainPage = true) + { + using (var sw = new StringWriter()) + { + if (engine == null) + throw new InvalidOperationException("no engine"); + + // try to find the specified view + controller.TryValidateModel(model); + ViewEngineResult viewResult = engine.FindView(contextAccessor.ActionContext, viewName, isMainPage); + + // create the associated context + ViewContext viewContext = new ViewContext(); + viewContext.ActionDescriptor = contextAccessor.ActionContext.ActionDescriptor; + viewContext.HttpContext = contextAccessor.ActionContext.HttpContext; + viewContext.TempData = controller.TempData; + viewContext.View = viewResult.View; + viewContext.Writer = sw; + // write the render view with the given context to the stringwriter + viewResult.View.RenderAsync(viewContext).Wait(); + return sw.GetStringBuilder().ToString(); + } + } + } +} diff --git a/src/Yavsc.Org/Migrations/20260919195608_blogPostId.Designer.cs b/src/Yavsc.Org/Migrations/20260919195608_blogPostId.Designer.cs deleted file mode 100644 index 903838f1d..000000000 --- a/src/Yavsc.Org/Migrations/20260919195608_blogPostId.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("20260919195608_blogPostId")] - partial class blogPostId - { - /// - 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("PostId") - .HasColumnType("bigint"); - - b.HasKey("PostId"); - - 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("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", "Post") - .WithMany() - .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Post"); - }); - - 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/20260919195608_blogPostId.cs b/src/Yavsc.Org/Migrations/20260919195608_blogPostId.cs deleted file mode 100644 index 624571209..000000000 --- a/src/Yavsc.Org/Migrations/20260919195608_blogPostId.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Yavsc.Migrations -{ - /// - public partial class blogPostId : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_blogSpotPublications_BlogSpot_BlogpostId", - table: "blogSpotPublications"); - - migrationBuilder.RenameColumn( - name: "BlogpostId", - table: "blogSpotPublications", - newName: "PostId"); - - migrationBuilder.AddForeignKey( - name: "FK_blogSpotPublications_BlogSpot_PostId", - table: "blogSpotPublications", - column: "PostId", - principalTable: "BlogSpot", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_blogSpotPublications_BlogSpot_PostId", - table: "blogSpotPublications"); - - migrationBuilder.RenameColumn( - name: "PostId", - table: "blogSpotPublications", - newName: "BlogpostId"); - - migrationBuilder.AddForeignKey( - name: "FK_blogSpotPublications_BlogSpot_BlogpostId", - table: "blogSpotPublications", - column: "BlogpostId", - principalTable: "BlogSpot", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260921145454_DropSignatureFilePath.Designer.cs b/src/Yavsc.Org/Migrations/20260921145454_DropSignatureFilePath.Designer.cs deleted file mode 100644 index 04b9fdc68..000000000 --- a/src/Yavsc.Org/Migrations/20260921145454_DropSignatureFilePath.Designer.cs +++ /dev/null @@ -1,4713 +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("20260921145454_DropSignatureFilePath")] - partial class DropSignatureFilePath - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.12") - .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("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("PostId") - .HasColumnType("bigint"); - - b.HasKey("PostId"); - - 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("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", "Post") - .WithOne("Publication") - .HasForeignKey("Yavsc.Models.BlogSpotPublication", "PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Post"); - }); - - 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("Publication") - .IsRequired(); - - 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/20260921145454_DropSignatureFilePath.cs b/src/Yavsc.Org/Migrations/20260921145454_DropSignatureFilePath.cs deleted file mode 100644 index d29c153cb..000000000 --- a/src/Yavsc.Org/Migrations/20260921145454_DropSignatureFilePath.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Yavsc.Migrations -{ - /// - public partial class DropSignatureFilePath : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "FilePath", - table: "Signatures"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "FilePath", - table: "Signatures", - type: "text", - nullable: false, - defaultValue: ""); - } - } -} diff --git a/src/Yavsc.Org/Migrations/20260924150433_UserFileAttachments.Designer.cs b/src/Yavsc.Org/Migrations/20260924150433_UserFileAttachments.Designer.cs deleted file mode 100644 index a05e3b2c2..000000000 --- a/src/Yavsc.Org/Migrations/20260924150433_UserFileAttachments.Designer.cs +++ /dev/null @@ -1,4797 +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("20260924150433_UserFileAttachments")] - partial class UserFileAttachments - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.12") - .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.EstimateAttachedFile", b => - { - b.Property("FileId") - .HasColumnType("bigint"); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.HasKey("FileId", "EstimateId"); - - b.HasIndex("EstimateId"); - - b.ToTable("EstimateAttachedFiles"); - }); - - 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.QueryAttachedFile", b => - { - b.Property("FileId") - .HasColumnType("bigint"); - - b.Property("CommandId") - .HasColumnType("bigint"); - - b.HasKey("FileId", "CommandId"); - - b.HasIndex("CommandId"); - - b.ToTable("QueryAttachedFiles"); - }); - - 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("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") - .IsRequired() - .HasColumnType("text"); - - b.Property("Length") - .HasColumnType("bigint"); - - b.Property("OwnerId") - .HasColumnType("text"); - - b.Property("Path") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("OwnerId"); - - b.ToTable("UploadedFiles"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.Property("PostId") - .HasColumnType("bigint"); - - b.HasKey("PostId"); - - 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("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.EstimateAttachedFile", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", "Estimate") - .WithMany() - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.UploadedFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Estimate"); - - b.Navigation("File"); - }); - - 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.QueryAttachedFile", b => - { - b.HasOne("Yavsc.Models.Billing.NominativeServiceCommand", "Command") - .WithMany() - .HasForeignKey("CommandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.UploadedFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Command"); - - b.Navigation("File"); - }); - - 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.Blog.UploadedFile", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => - { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithOne("Publication") - .HasForeignKey("Yavsc.Models.BlogSpotPublication", "PostId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Post"); - }); - - 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("Publication") - .IsRequired(); - - 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/20260924150433_UserFileAttachments.cs b/src/Yavsc.Org/Migrations/20260924150433_UserFileAttachments.cs deleted file mode 100644 index 593bf0b26..000000000 --- a/src/Yavsc.Org/Migrations/20260924150433_UserFileAttachments.cs +++ /dev/null @@ -1,148 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace Yavsc.Migrations -{ - /// - public partial class UserFileAttachments : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Path", - table: "UploadedFiles", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AlterColumn( - name: "ContentType", - table: "UploadedFiles", - type: "text", - nullable: false, - defaultValue: "", - oldClrType: typeof(string), - oldType: "text", - oldNullable: true); - - migrationBuilder.AddColumn( - name: "OwnerId", - table: "UploadedFiles", - type: "text", - nullable: true); - - migrationBuilder.CreateTable( - name: "EstimateAttachedFiles", - columns: table => new - { - FileId = table.Column(type: "bigint", nullable: false), - EstimateId = table.Column(type: "bigint", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_EstimateAttachedFiles", x => new { x.FileId, x.EstimateId }); - table.ForeignKey( - name: "FK_EstimateAttachedFiles_Estimates_EstimateId", - column: x => x.EstimateId, - principalTable: "Estimates", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_EstimateAttachedFiles_UploadedFiles_FileId", - column: x => x.FileId, - principalTable: "UploadedFiles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "QueryAttachedFiles", - columns: table => new - { - FileId = table.Column(type: "bigint", nullable: false), - CommandId = table.Column(type: "bigint", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_QueryAttachedFiles", x => new { x.FileId, x.CommandId }); - table.ForeignKey( - name: "FK_QueryAttachedFiles_NominativeServiceCommands_CommandId", - column: x => x.CommandId, - principalTable: "NominativeServiceCommands", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_QueryAttachedFiles_UploadedFiles_FileId", - column: x => x.FileId, - principalTable: "UploadedFiles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_UploadedFiles_OwnerId", - table: "UploadedFiles", - column: "OwnerId"); - - migrationBuilder.CreateIndex( - name: "IX_EstimateAttachedFiles_EstimateId", - table: "EstimateAttachedFiles", - column: "EstimateId"); - - migrationBuilder.CreateIndex( - name: "IX_QueryAttachedFiles_CommandId", - table: "QueryAttachedFiles", - column: "CommandId"); - - migrationBuilder.AddForeignKey( - name: "FK_UploadedFiles_AspNetUsers_OwnerId", - table: "UploadedFiles", - column: "OwnerId", - principalTable: "AspNetUsers", - principalColumn: "Id"); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_UploadedFiles_AspNetUsers_OwnerId", - table: "UploadedFiles"); - - migrationBuilder.DropTable( - name: "EstimateAttachedFiles"); - - migrationBuilder.DropTable( - name: "QueryAttachedFiles"); - - migrationBuilder.DropIndex( - name: "IX_UploadedFiles_OwnerId", - table: "UploadedFiles"); - - migrationBuilder.DropColumn( - name: "OwnerId", - table: "UploadedFiles"); - - migrationBuilder.AlterColumn( - name: "Path", - table: "UploadedFiles", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - - migrationBuilder.AlterColumn( - name: "ContentType", - table: "UploadedFiles", - type: "text", - nullable: true, - oldClrType: typeof(string), - oldType: "text"); - } - } -} diff --git a/src/Yavsc.Org/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Yavsc.Org/Migrations/ApplicationDbContextModelSnapshot.cs index 711408cd4..84960c5bc 100644 --- a/src/Yavsc.Org/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Yavsc.Org/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Yavsc.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "10.0.12") + .HasAnnotation("ProductVersion", "10.0.9") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -1394,21 +1394,6 @@ namespace Yavsc.Migrations b.ToTable("Estimates"); }); - modelBuilder.Entity("Yavsc.Models.Billing.EstimateAttachedFile", b => - { - b.Property("FileId") - .HasColumnType("bigint"); - - b.Property("EstimateId") - .HasColumnType("bigint"); - - b.HasKey("FileId", "EstimateId"); - - b.HasIndex("EstimateId"); - - b.ToTable("EstimateAttachedFiles"); - }); - modelBuilder.Entity("Yavsc.Models.Billing.EstimateTemplate", b => { b.Property("Id") @@ -1517,21 +1502,6 @@ namespace Yavsc.Migrations b.UseTphMappingStrategy(); }); - modelBuilder.Entity("Yavsc.Models.Billing.QueryAttachedFile", b => - { - b.Property("FileId") - .HasColumnType("bigint"); - - b.Property("CommandId") - .HasColumnType("bigint"); - - b.HasKey("FileId", "CommandId"); - - b.HasIndex("CommandId"); - - b.ToTable("QueryAttachedFiles"); - }); - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => { b.Property("Id") @@ -1551,6 +1521,10 @@ namespace Yavsc.Migrations b.Property("EstimateId") .HasColumnType("bigint"); + b.Property("FilePath") + .IsRequired() + .HasColumnType("text"); + b.Property("SignerId") .IsRequired() .HasColumnType("text"); @@ -1704,32 +1678,25 @@ namespace Yavsc.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); b.Property("ContentType") - .IsRequired() .HasColumnType("text"); b.Property("Length") .HasColumnType("bigint"); - b.Property("OwnerId") - .HasColumnType("text"); - b.Property("Path") - .IsRequired() .HasColumnType("text"); b.HasKey("Id"); - b.HasIndex("OwnerId"); - b.ToTable("UploadedFiles"); }); modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => { - b.Property("PostId") + b.Property("BlogpostId") .HasColumnType("bigint"); - b.HasKey("PostId"); + b.HasKey("BlogpostId"); b.ToTable("blogSpotPublications"); }); @@ -3864,25 +3831,6 @@ namespace Yavsc.Migrations b.Navigation("Query"); }); - modelBuilder.Entity("Yavsc.Models.Billing.EstimateAttachedFile", b => - { - b.HasOne("Yavsc.Models.Billing.Estimate", "Estimate") - .WithMany() - .HasForeignKey("EstimateId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.UploadedFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Estimate"); - - b.Navigation("File"); - }); - modelBuilder.Entity("Yavsc.Models.Billing.NominativeServiceCommand", b => { b.HasOne("Yavsc.Models.Workflow.Activity", "Context") @@ -3916,25 +3864,6 @@ namespace Yavsc.Migrations b.Navigation("Regularization"); }); - modelBuilder.Entity("Yavsc.Models.Billing.QueryAttachedFile", b => - { - b.HasOne("Yavsc.Models.Billing.NominativeServiceCommand", "Command") - .WithMany() - .HasForeignKey("CommandId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("Yavsc.Models.Blog.UploadedFile", "File") - .WithMany() - .HasForeignKey("FileId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Command"); - - b.Navigation("File"); - }); - modelBuilder.Entity("Yavsc.Models.Billing.Signature", b => { b.HasOne("Yavsc.Models.Billing.Estimate", "Estimate") @@ -4027,24 +3956,15 @@ namespace Yavsc.Migrations b.Navigation("Post"); }); - modelBuilder.Entity("Yavsc.Models.Blog.UploadedFile", b => - { - b.HasOne("Yavsc.Models.ApplicationUser", "Owner") - .WithMany() - .HasForeignKey("OwnerId"); - - b.Navigation("Owner"); - }); - modelBuilder.Entity("Yavsc.Models.BlogSpotPublication", b => { - b.HasOne("Yavsc.Models.Blog.BlogPost", "Post") - .WithOne("Publication") - .HasForeignKey("Yavsc.Models.BlogSpotPublication", "PostId") + b.HasOne("Yavsc.Models.Blog.BlogPost", "BlogPost") + .WithMany() + .HasForeignKey("BlogpostId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("Post"); + b.Navigation("BlogPost"); }); modelBuilder.Entity("Yavsc.Models.Calendar.Schedule", b => @@ -4694,9 +4614,6 @@ namespace Yavsc.Migrations b.Navigation("Comments"); - b.Navigation("Publication") - .IsRequired(); - b.Navigation("Tags"); }); diff --git a/src/Yavsc.Org/ViewComponents/BillViewComponent.cs b/src/Yavsc.Org/ViewComponents/BillViewComponent.cs new file mode 100644 index 000000000..0217ebcf2 --- /dev/null +++ b/src/Yavsc.Org/ViewComponents/BillViewComponent.cs @@ -0,0 +1,108 @@ + +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Localization; +using Yavsc.Billing; +using Yavsc.Helpers; +using Yavsc.Models; +using Yavsc.ViewModels; +using Yavsc.ViewModels.Gen; +using Yavsc.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; + +namespace Yavsc.ViewComponents +{ + public class BillViewComponent : ViewComponent + { + readonly ApplicationDbContext dbContext; + readonly IBillingService billing; + readonly IStringLocalizer localizer; + private readonly SiteSettings siteSettings; + + public BillViewComponent(ApplicationDbContext dbContext, + IStringLocalizer localizer, + IBillingService billing, + IOptions siteSettings) + { + this.billing = billing; + this.dbContext = dbContext; + this.localizer = localizer; + this.siteSettings = siteSettings.Value; + } + + public async Task InvokeAsync( + string code, + IBillable billable, + OutputFormat format, + bool asBill, + SiteSettings settings + ) + { + var di = new DirectoryInfo(settings.Bills); + var dia = new DirectoryInfo(settings.Avatars); + ViewBag.BillsDir = di.FullName; + ViewBag.AvatarsDir = dia.FullName; + ViewBag.AsBill = asBill; // vrai pour une facture, sinon, c'est un devis + ViewBag.Acquitted = billable.GetIsAcquitted(); + + ViewBag.BillingCode = code; + var client = await dbContext.Users + .Include(u=>u.PostalAddress) + .SingleAsync(u=>u.Id == billable.ClientId); + ViewBag.Client = client; + var performer = await dbContext.Users + .Include(u=>u.BankInfo) + .Include(u=>u.PostalAddress) + .SingleAsync(u=>u.Id == billable.PerformerId); + ViewBag.Performer = performer; + string clientAddress = client.PostalAddress?.Address ?? null; + ViewBag.ClientAddress = clientAddress.SplitAddressToTeX(); + + + var profile = await dbContext.Performers + .Include(p=>p.OrganizationAddress) + .SingleAsync(p=>p.PerformerId == billable.PerformerId); + ViewBag.PerformerProfile = profile; + ViewBag.ActivityLabel = (await dbContext.Activities.SingleAsync(a => a.Code == billable.ActivityCode)).Name; + + var proAddr = profile.OrganizationAddress.Address; + ViewBag.PerformerOrganizationAddress = proAddr.SplitAddressToTeX() ; + ViewBag.FooterPerformerOrganizationAddress = proAddr.SplitAddressToTeX(", "); + + ViewBag.PerformerAddress = performer.PostalAddress?.Address.SplitAddressToTeX() ; + switch (format) { + case OutputFormat.LaTeX : + return this.View("Bill_tex", billable); + case OutputFormat.Pdf : + string tex = null; + var oldWriter = ViewComponentContext.ViewContext.Writer; + using (var writer = new StringWriter()) + { + this.ViewComponentContext.ViewContext.Writer = writer; + var resultTex = View("Bill_tex", billable); + await resultTex.ExecuteAsync(this.ViewComponentContext); + tex = writer.ToString(); + } + ViewComponentContext.ViewContext.Writer = oldWriter; + + var pdfGenerationViewModel = new PdfGenerationViewModel + { + Temp = siteSettings.TempDir, + TeXSource = tex, + DestDir = siteSettings.Bills, + BaseFileName = billable.GetFileBaseName(billing) + }; + if (settings.GenerateEstimatePdf(pdfGenerationViewModel)) { + return this.View(new { Generated = pdfGenerationViewModel.BaseFileName+".pdf" }); + } else { + return View(new { Error = pdfGenerationViewModel.GenerationErrorMessage } ); + } + } + ViewBag.BillFileInfo = billable.GetBillInfo(billing, siteSettings); + ViewBag.Settings = settings; + return View("Default",billable); + + } + + } +} diff --git a/src/Yavsc.Org/ViewComponents/CommentViewComponent.cs b/src/Yavsc.Org/ViewComponents/CommentViewComponent.cs index 32b7a14b2..6f85a953f 100644 --- a/src/Yavsc.Org/ViewComponents/CommentViewComponent.cs +++ b/src/Yavsc.Org/ViewComponents/CommentViewComponent.cs @@ -18,10 +18,7 @@ namespace Yavsc.ViewComponents } public async Task InvokeAsync(long id) { - var comment = await context.Comment - .Include(c => c.Children) - .Include(c => c.Author) - .FirstOrDefaultAsync(c => c.Id==id); + var comment = await context.Comment.Include(c=>c.Children).FirstOrDefaultAsync(c => c.Id==id); if (comment == null) throw new InvalidOperationException(); ViewBag.apictlr = "/api/v1/blogcomments"; diff --git a/src/Yavsc.Api/ViewModels/Gen/PdfGenerationViewModel.cs b/src/Yavsc.Org/ViewModels/Gen/PdfGenerationViewModel.cs similarity index 98% rename from src/Yavsc.Api/ViewModels/Gen/PdfGenerationViewModel.cs rename to src/Yavsc.Org/ViewModels/Gen/PdfGenerationViewModel.cs index 841e8d5dd..fe934e3ac 100644 --- a/src/Yavsc.Api/ViewModels/Gen/PdfGenerationViewModel.cs +++ b/src/Yavsc.Org/ViewModels/Gen/PdfGenerationViewModel.cs @@ -5,7 +5,7 @@ namespace Yavsc.ViewModels.Gen { public class PdfGenerationViewModel { - + [Required] public string TeXSource { get; set; } [Required] diff --git a/src/Yavsc.Org/Views/Account/Delete.cshtml b/src/Yavsc.Org/Views/Account/Delete.cshtml deleted file mode 100644 index 7443d3d2a..000000000 --- a/src/Yavsc.Org/Views/Account/Delete.cshtml +++ /dev/null @@ -1,21 +0,0 @@ -@model UnregisterViewModel -@{ - ViewBag.Title = "Supprimer mon compte"; -} - -

@ViewBag.Title

- -
-
-

- Cette action est immédiate et irréversible. Toutes les données - associées à votre compte seront supprimées, et vous serez déconnecté. -

- -
- @Html.Hidden("UserId") - - Annuler -
-
-
\ No newline at end of file diff --git a/src/Yavsc.Org/Views/Blogspot/Create.cshtml b/src/Yavsc.Org/Views/Blogspot/Create.cshtml new file mode 100644 index 000000000..6831b0461 --- /dev/null +++ b/src/Yavsc.Org/Views/Blogspot/Create.cshtml @@ -0,0 +1,88 @@ +@model BlogPostEditViewModel + +@{ + ViewBag.Title = "Blog post edition"; +} + +@section header { + + + + + + @{ await Html.RenderPartialAsync("_FSScriptsPartial"); } + +} + +

Blog post

+ + +
+ + +

@Model.Title

+ +
@Model.Article
+ +
+
+
+ +
+ +
+ +
+ + + +
+
+ +
+ +
+ + + +
+
+ +
+ +
+ + + +
+
+ +
+ +
+ @await Component.InvokeAsync("CirclesControl", Model) +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+
+@await Component.InvokeAsync(name: "Directory",arguments: new {subdir="."}) +
+ @{ await Html.RenderPartialAsync("_PostFilesPartial"); } +
+ diff --git a/src/Yavsc.Org/Views/Blogspot/Details.cshtml b/src/Yavsc.Org/Views/Blogspot/Details.cshtml index 0b07b0d94..dc0bea8a8 100644 --- a/src/Yavsc.Org/Views/Blogspot/Details.cshtml +++ b/src/Yavsc.Org/Views/Blogspot/Details.cshtml @@ -1,235 +1,105 @@ @model BlogPost @{ - ViewBag.Title = Model.Title; + ViewBag.Title=Model.Title; } -@section header { +@section scripts { + + + } -@section scripts { - -} - -
-
- @if (!string.IsNullOrEmpty(Model.Photo)) - { - @Model.Title - } -
-

@Model.Title

- -
-
- @Model.Article +
+
+
+ +

@Model.Title

+ + @Html.DisplayFor(m=>m.Author) + @Html.DisplayFor(m=>m.Article) +
- @if (Model.Comments != null) - { - foreach (var comment in Model.Comments.Where(c => c.ParentId == null)) - { + @if (Model.Comments!=null) { + foreach (var comment in Model.Comments.Where(c=>c.ParentId==null)) { @await Component.InvokeAsync("Comment", new { id = comment.Id }) } }
- - @if (User.GetUserId() != null) - { -
-
- - - - - -
+@if (User.GetUserId()!=null) { +
+
+ + + + +
+ +
} - else - { -

Vous devez être identifié pour commenter.

+ else { +

Vous devez être identifié pour commenter.

+ } + + @if ((await AuthorizationService.AuthorizeAsync(User, Model, new EditPermission())).Succeeded) { + Edit } - Back to List -
\ No newline at end of file + diff --git a/src/Yavsc.Org/Views/Blogspot/Edit.cshtml b/src/Yavsc.Org/Views/Blogspot/Edit.cshtml new file mode 100644 index 000000000..4645d45db --- /dev/null +++ b/src/Yavsc.Org/Views/Blogspot/Edit.cshtml @@ -0,0 +1,120 @@ +@model BlogPostEditViewModel +@{ + ViewBag.Title = "Blog post edition"; +} + +@section header { + + + + + + + + @{ await Html.RenderPartialAsync("_FSScriptsPartial"); } + +} + +

Blog post

+ + +
+ + + + + +

@Model.Title

+ +
+
+ +
+ @Html.HiddenFor(m=>m.Id) + +
+ +
+ + + +
+
+ +
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+ +
+ @await Component.InvokeAsync("CirclesControl", Model) +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+
+@await Component.InvokeAsync("Directory","") + + +
+ +
+ @{ await Html.RenderPartialAsync("_PostFilesPartial"); } +
+ diff --git a/src/Yavsc.Org/Views/Blogspot/Index.cshtml b/src/Yavsc.Org/Views/Blogspot/Index.cshtml index 9f29a9417..1582041cf 100644 --- a/src/Yavsc.Org/Views/Blogspot/Index.cshtml +++ b/src/Yavsc.Org/Views/Blogspot/Index.cshtml @@ -1,155 +1,92 @@ + @model IEnumerable @{ ViewBag.Title = "Blogs, l'index"; - - // Pagination reflects the Index(string id, int skip, int take) - // call profile. The service applies Skip/Take server-side, so - // the view only needs the current page params to build Prev/Next - // links — it does not receive a total count. - int currentSkip = Context.Request.Query.TryGetValue("skip", out var skipVal) && int.TryParse(skipVal, out var s) ? s : 0; - int currentTake = Context.Request.Query.TryGetValue("take", out var takeVal) && int.TryParse(takeVal, out var t) ? t : 25; - - var posts = Model.ToList(); - bool hasPrev = currentSkip > 0; - bool hasNext = posts.Count >= currentTake; - - int prevSkip = Math.Max(0, currentSkip - currentTake); - int nextSkip = currentSkip + currentTake; - - int maxSummaryLen = 256; } @section header { + +} +@section scripts { + }

@ViewBag.Title

@ViewBag.StatusMessage

- -@if (!posts.Any()) -{ -

Néant

+@if (User.IsSignedIn()) { +

+ Create a new article +

} -else -{ -
- @foreach (var post in posts) - { - var canEdit = (await AuthorizationService.AuthorizeAsync(User, post, new EditPermission())).Succeeded; -
- @if (!string.IsNullOrEmpty(post.Photo)) - { - - @post.Title - - } -
-

- @post.Title -

-
- - @post.Article - -
- -
- Details - @if (canEdit) - { - Delete - } -
-
-
- } -
- -} \ No newline at end of file +
+@{ + int maxTextLen = 75; + foreach (var post in Model) { +
+ + + +
+

@post.Title

+
+
+ + @post.Article + @Html.DisplayFor(m => post.Author) + + posté le @post.DateCreated.ToString("dddd d MMM yyyy à H:mm") + @if ((post.DateModified - post.DateCreated).Minutes > 0){  + @:- Modifié le @post.DateModified.ToString("dddd d MMM yyyy à H:mm") + } + +
+
+ @if ((await AuthorizationService.AuthorizeAsync(User, post, new ReadPermission())).Succeeded) + { + Details + } + else + { + Details + } + @if ((await AuthorizationService.AuthorizeAsync(User, post, new EditPermission())).Succeeded) + { + Edit + + Delete + + } +
+
+ } +} +
+ +@if(Model.Count()==0){

Néant

} diff --git a/src/Yavsc.Org/Views/Blogspot/Title.cshtml b/src/Yavsc.Org/Views/Blogspot/Title.cshtml index 9421a5b01..7bdf0468c 100644 --- a/src/Yavsc.Org/Views/Blogspot/Title.cshtml +++ b/src/Yavsc.Org/Views/Blogspot/Title.cshtml @@ -3,24 +3,28 @@

@ViewBag.StatusMessage

- +

+ Poster au même titre +

+
+ @foreach (var item in Model) { -
- @((item.Article?.Length > 256) ? item.Article.Substring(0, 256) + " ..." : item.Article) + @((item.Article?.Length > 256) ? item.Article.Substring(0, 256) + " ..." : item.Article) (@item.Author.UserName , posté le @item.DateCreated.ToString("dddd d MMM yyyy à H:mm") - @if ((item.DateModified - item.DateCreated).Minutes > 0){ + @if ((item.DateModified - item.DateCreated).Minutes > 0){  @:- Modifié le @item.DateModified.ToString("dddd d MMM yyyy à H:mm") }) +
    @if ((await AuthorizationService.AuthorizeAsync(User, item, new ReadPermission())).Succeeded) {
  • @@ -28,7 +32,8 @@
  • } @if ((await AuthorizationService.AuthorizeAsync(User, item, new EditPermission())).Succeeded) { - +
  • Edit +
  • Delete
  • } diff --git a/src/Yavsc.Org/Views/HairCutCommand/CGV.cshtml b/src/Yavsc.Org/Views/HairCutCommand/CGV.cshtml index 08d01cf63..1428ee674 100644 --- a/src/Yavsc.Org/Views/HairCutCommand/CGV.cshtml +++ b/src/Yavsc.Org/Views/HairCutCommand/CGV.cshtml @@ -1,13 +1,13 @@ -@{ +@{ ViewBag.Title = "Conditions Générales de Vente"; }

    @ViewBag.Title

    - + Le paiement intervient à la commande, quelque soit la date d'intervention. -Vous pouvez annuler votre commande depuis la [liste de vos commande en cours](/HairCutCommand/), - -Vous pouvez demander le remboursement d'un paiement, dans le cadre d'une réclamation sur l'execution +Vous pouvez annuler votre commande depuis la [liste de vos commande en cours](/HairCutCommand/), + +Vous pouvez demander le remboursement d'un paiement, dans le cadre d'une réclamation sur l'execution de votre commande, en nous contactant via courrier éléctronique ou postal, [que vour retrouverez sur la page de contact](/Home/Contact), votre demande sera rapidement traitée. - + diff --git a/src/Yavsc.Org/Views/Home/About.pt.cshtml b/src/Yavsc.Org/Views/Home/About.pt.cshtml index a805999ce..487a1ab36 100755 --- a/src/Yavsc.Org/Views/Home/About.pt.cshtml +++ b/src/Yavsc.Org/Views/Home/About.pt.cshtml @@ -5,7 +5,7 @@

    @SiteSettings.Value.Title - objetivo

    - + = O objetivo Esta aplicação é construída para conectar artistas @@ -87,51 +87,51 @@ que imediatamente desativa as publicações associadas às suas informações, e planeia a eliminação completa desta informação no prazo de quinze dias do aplicativo, a menos que seja um pedido contraditório. 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). +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) Outras instalações: - - + + * [Coiffure](http://coiffure.pschneider.fr) * [ZicMoove](http://linkmuse.pschneider.fr) * [Yavsc](http://yavsc.pschneider.fr) - + - + 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) - + - -## Yet Another Very Small Company : + +## 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) En production: - + * [Lua](https://lua.pschneider.fr) * [Yavsc](https://yavsc.pschneider.fr) - + - + Você está no site da ordem em cabeleireiro em casa de Soraya Boudjouraf, um ás de cabeleireiro, que trabalha na região de Paris. @@ -139,11 +139,11 @@ Ao validar um formulário de pedido aqui, é para ela que você notifica sua sol Você pode [Deixe-lhe o seu número de telefone] (/ HairCutCommand / HairCut? ActivityCode = Brush & performerId = 1bd841ab-c305-4971-940d-7ddca818310c) e / ou detalhes sobre o seu pedido, -ela vai ligar de volta. +ela vai ligar de volta. - + ## Este é um site de desenvolvimento. Este recurso só diz respeito ao desenvolvimento do software que o implementa. @@ -156,7 +156,7 @@ A "pré-produção" exibe os seguintes sites: * [Yavsc](https://yavsc.pschneider.fr) * [Lua](https://lua.pschneider.fr) - +

    @Model

    diff --git a/src/Yavsc.Org/Views/Home/AboutAccess.cshtml b/src/Yavsc.Org/Views/Home/AboutAccess.cshtml index 6aaec7ff9..05331032b 100644 --- a/src/Yavsc.Org/Views/Home/AboutAccess.cshtml +++ b/src/Yavsc.Org/Views/Home/AboutAccess.cshtml @@ -3,13 +3,13 @@ }

    @ViewBag.Title

    - + # Blog et fichiers utilisateurs Par défaut, un envoi à destination du serveur est privé, visible seulement pour son possesseur (et l'administration). -L'utilisateur peu publier chacun de ces derniers à l'adresse : +L'utilisateur peu publier chacun de ces derniers à l'adresse : * de tout le monde, * ou d'une liste de cercles d'utilisateurs, @@ -17,8 +17,8 @@ L'utilisateur peu publier chacun de ces derniers à l'adresse : Quand il s'agit d'un fichier, son adresse permanente relative à la racine du serveur web est: `/UserFiles/(Nom Utilisateur)/(cheminVersLeFichier)` -Les restrictions d'accès à ce fichier sont celles associées à tous les fichiers du même dossier. -Il n'y a pas de regle assiciée à chaque fichier individuellement, +Les restrictions d'accès à ce fichier sont celles associées à tous les fichiers du même dossier. +Il n'y a pas de regle assiciée à chaque fichier individuellement, elles appartiennent au dossier qui les contient. Les billet de blog sont regroupés par utilisateur, à l'Url @@ -35,6 +35,6 @@ Et ils possèdent une addresse permanente de la forme : - + diff --git a/src/Yavsc.Org/Views/Home/AboutMarkdown.cshtml b/src/Yavsc.Org/Views/Home/AboutMarkdown.cshtml index f39aaf536..f025c111e 100644 --- a/src/Yavsc.Org/Views/Home/AboutMarkdown.cshtml +++ b/src/Yavsc.Org/Views/Home/AboutMarkdown.cshtml @@ -3,7 +3,7 @@ }

    @ViewBag.Title

    - + Quelques extensions à un Markdown de base : * les video et audio: `![video: Ep. 28 - La France en commun - Une invitation à écrire un nouveau projet d'émancipation humaine](/images/testVideo.mp4)` @@ -16,4 +16,4 @@ Quelques extensions à un Markdown de base : * le tag "Titre d'article": "#1_great_title" * le tag "User" = "@@(John Doe)" ou "@@jdoe" - + diff --git a/src/Yavsc.Org/Views/Home/CGV.cshtml b/src/Yavsc.Org/Views/Home/CGV.cshtml index 08d01cf63..1428ee674 100644 --- a/src/Yavsc.Org/Views/Home/CGV.cshtml +++ b/src/Yavsc.Org/Views/Home/CGV.cshtml @@ -1,13 +1,13 @@ -@{ +@{ ViewBag.Title = "Conditions Générales de Vente"; }

    @ViewBag.Title

    - + Le paiement intervient à la commande, quelque soit la date d'intervention. -Vous pouvez annuler votre commande depuis la [liste de vos commande en cours](/HairCutCommand/), - -Vous pouvez demander le remboursement d'un paiement, dans le cadre d'une réclamation sur l'execution +Vous pouvez annuler votre commande depuis la [liste de vos commande en cours](/HairCutCommand/), + +Vous pouvez demander le remboursement d'un paiement, dans le cadre d'une réclamation sur l'execution de votre commande, en nous contactant via courrier éléctronique ou postal, [que vour retrouverez sur la page de contact](/Home/Contact), votre demande sera rapidement traitée. - + diff --git a/src/Yavsc.Org/Views/Home/Index.cshtml b/src/Yavsc.Org/Views/Home/Index.cshtml index a18b0727b..f867b2bfc 100755 --- a/src/Yavsc.Org/Views/Home/Index.cshtml +++ b/src/Yavsc.Org/Views/Home/Index.cshtml @@ -2,6 +2,8 @@ @{ ViewBag.Title = @Localizer["Page d'accueil"]; + int i=0; + bool multipleact = Model.Count()>1; } diff --git a/src/Yavsc.Org/Views/Home/Privacy.cshtml b/src/Yavsc.Org/Views/Home/Privacy.cshtml index a9a6e7cf4..34ae18037 100644 --- a/src/Yavsc.Org/Views/Home/Privacy.cshtml +++ b/src/Yavsc.Org/Views/Home/Privacy.cshtml @@ -2,15 +2,15 @@ ViewBag.Title = Localizer["Home"]; } - -# La confidentialité + += La confidentialité +:hide-uri-scheme: -À aucun moment, aucune adresse postale, -aucune adresse e-mail ni aucun numéro de téléphone -ne sont transmis à personne. Seul le système - et son link:Contact[possesseur] ont accès - à ces informations. +xref:Contact[Get Report] -De plus, le droit de retrait est permanent -et sa [mise en oeuvre est immédiate](/Account/Delete). - +À aucun moment, aucune adresse postale, aucune adresse e-mail ni aucun numéro de téléphone +ne sont transmis à personne. Seul le système et son link:Contact[possesseur] ont accès à ces informations. + +De plus, le droit de retrait est permanent et sa mise en oeuvre link:/Account/Delete[immédiate]. + + \ No newline at end of file diff --git a/src/Yavsc.Org/Views/Home/Todo.cshtml b/src/Yavsc.Org/Views/Home/Todo.cshtml index c63500479..448251a48 100755 --- a/src/Yavsc.Org/Views/Home/Todo.cshtml +++ b/src/Yavsc.Org/Views/Home/Todo.cshtml @@ -4,14 +4,14 @@

    @ViewBag.Title

    Linkmuse(trox) - + Les tags. La librairie, les lives. - + @{ ViewBag.Title = @Localizer["TODO"]; }

    @ViewBag.Title

    Faster, stronger, shorter - - + + diff --git a/src/Yavsc.Org/Views/Shared/Components/Bill/Bill_pdf.cshtml b/src/Yavsc.Org/Views/Shared/Components/Bill/Bill_pdf.cshtml new file mode 100644 index 000000000..f49fad802 --- /dev/null +++ b/src/Yavsc.Org/Views/Shared/Components/Bill/Bill_pdf.cshtml @@ -0,0 +1,21 @@ +@using System.Reflection +@using System.IO +@using Microsoft.Extensions.WebEncoders +@using System.Diagnostics +@using System.Text +@using Yavsc.Helpers +@model Yavsc.ViewModels.Gen.PdfGenerationViewModel + +@{ + ViewBag.Settings.GenerateEstimatePdf(Model); +} +@if (Model.Generated) { +
    + @(Model.BaseFileName).pdf was generated +
    +} else { +
    + @Model.GenerationErrorMessage +

    Something went wrong ...

    +
    +} diff --git a/src/Yavsc.Api/Views/Shared/Bill_tex.cshtml b/src/Yavsc.Org/Views/Shared/Components/Bill/Bill_tex.cshtml similarity index 99% rename from src/Yavsc.Api/Views/Shared/Bill_tex.cshtml rename to src/Yavsc.Org/Views/Shared/Components/Bill/Bill_tex.cshtml index f0a3d2ad3..353d124e0 100644 --- a/src/Yavsc.Api/Views/Shared/Bill_tex.cshtml +++ b/src/Yavsc.Org/Views/Shared/Components/Bill/Bill_tex.cshtml @@ -3,7 +3,7 @@ @using System.Globalization @model IBillable @{ - Layout = null; + Layout = "null"; var pro = ViewBag.PerformerProfile; var from = ViewBag.Performer; var to = ViewBag.Client; diff --git a/src/Yavsc.Org/Views/Shared/Components/Bill/Default.cshtml b/src/Yavsc.Org/Views/Shared/Components/Bill/Default.cshtml new file mode 100644 index 000000000..f2d0e0163 --- /dev/null +++ b/src/Yavsc.Org/Views/Shared/Components/Bill/Default.cshtml @@ -0,0 +1,33 @@ + +@model IBillable +
    +@if (ViewBag.BillFileInfo.Exists) { +La facture au format Pdf +} else { + +
    +
    + + +
    +
    +
    +
    +} +
    diff --git a/src/Yavsc.Org/Views/Shared/Components/Comment/Default.cshtml b/src/Yavsc.Org/Views/Shared/Components/Comment/Default.cshtml index e83ea14c6..6dfec029c 100644 --- a/src/Yavsc.Org/Views/Shared/Components/Comment/Default.cshtml +++ b/src/Yavsc.Org/Views/Shared/Components/Comment/Default.cshtml @@ -1,30 +1,16 @@ @model Comment -
    -
    - @Model.Article -
    - - — @(Model.Author?.UserName ?? "Anonyme"), - posté le @Model.DateCreated.ToString("dddd d MMM yyyy à H:mm") - - @if (User.GetUserId() != null) - { - - } +
    + @Model.Article + + @if (Model.Children!=null) {
    - @if (Model.Children != null) - { - foreach (var comment in Model.Children) + @foreach (var comment in Model.Children) { - @await this.Component.InvokeAsync("Comment", new { id = comment.Id }) + @await this.Component.InvokeAsync("Comment", new { id = comment.Id}); } - }
    -
    \ No newline at end of file +} +
    + diff --git a/src/Yavsc.Org/Views/Shared/Components/Estimate/Default.cshtml b/src/Yavsc.Org/Views/Shared/Components/Estimate/Default.cshtml new file mode 100644 index 000000000..b9a1ab26d --- /dev/null +++ b/src/Yavsc.Org/Views/Shared/Components/Estimate/Default.cshtml @@ -0,0 +1,13 @@ + +@model Estimate + diff --git a/src/Yavsc.Org/Views/Shared/Components/Estimate/Estimate_pdf.cshtml b/src/Yavsc.Org/Views/Shared/Components/Estimate/Estimate_pdf.cshtml new file mode 100644 index 000000000..bef19b35a --- /dev/null +++ b/src/Yavsc.Org/Views/Shared/Components/Estimate/Estimate_pdf.cshtml @@ -0,0 +1,21 @@ +@using System.Reflection +@using System.IO +@using Microsoft.Extensions.WebEncoders +@using System.Diagnostics +@using System.Text +@using Yavsc.Helpers +@model Yavsc.ViewModels.Gen.PdfGenerationViewModel + +@{ + SiteSettings.Value.GenerateEstimatePdf(Model); +} +@if (Model.Generated) { +
    + @(Model.BaseFileName).pdf was generated +
    +} else { +
    + @Model.GenerationErrorMessage +

    Something went wrong ...

    +
    +} diff --git a/src/Yavsc.Api/Views/Shared/Estimate_tex.cshtml b/src/Yavsc.Org/Views/Shared/Components/Estimate/Estimate_tex.cshtml similarity index 72% rename from src/Yavsc.Api/Views/Shared/Estimate_tex.cshtml rename to src/Yavsc.Org/Views/Shared/Components/Estimate/Estimate_tex.cshtml index bbe4326b4..4c35d4bdd 100644 --- a/src/Yavsc.Api/Views/Shared/Estimate_tex.cshtml +++ b/src/Yavsc.Org/Views/Shared/Components/Estimate/Estimate_tex.cshtml @@ -3,7 +3,7 @@ @using System.Globalization @model Estimate @{ - Layout = null; + Layout = "null"; var pro = Model.Query.PerformerProfile; var from = Model.Query.PerformerProfile.Performer; var to = Model.Query.Client; @@ -11,6 +11,7 @@ var proaddr = Model.Query.PerformerProfile.OrganizationAddress.Address.SplitAddressToTeX(); var proaddrm = new HtmlString(Model.Query.PerformerProfile.OrganizationAddress.Address); var isestimate = !ViewBag.AsBill; + var prosign = new FileInfo($"{ViewBag.BillsDir}/estimate-prosign-{Model.Id}.png"); } \documentclass[french,11pt]{article} @@ -24,7 +25,6 @@ \usepackage{graphicx} \usepackage{fancyhdr} \usepackage{fp} -\usepackage{tikz} \def\TVA{20} % Taux de la TVA @@ -49,7 +49,7 @@ \global\let\TotalHT\TotalHT \global\let\TotalTVA\TotalTVA \global\let\TotalTTC\TotalTTC - \cr + \cr \hline \textbf{Total} & & & \TotalHT } @@ -77,12 +77,12 @@ % Infos Client \def\ClientNom{@TeXHelpers.ToTeX(to.UserName)} % Nom du client -\def\ClientAdresse{ +\def\ClientAdresse{ % Adresse du client @PostalAddress @if (PostalAddress!=null) {@PostalAddress} @if (!string.IsNullOrWhiteSpace(to.PhoneNumber)) {@TeXHelpers.ToTeX(to.PhoneNumber)\\ - } + } E-mail: @TeXHelpers.ToTeX(to.Email) } @@ -105,7 +105,7 @@ \renewcommand{\headrulewidth}{0pt} \cfoot{ @TeXHelpers.ToTeX(from.UserName) @if (proaddrm!=null) { - @proaddrm } \newline - \small{ E-mail: @TeXHelpers.ToTeX(from.Email) @if (!string.IsNullOrWhiteSpace(from.PhoneNumber)) { - Téléphone fixe: @TeXHelpers.ToTeX(from.PhoneNumber) } + \small{ E-mail: @TeXHelpers.ToTeX(from.Email) @if (!string.IsNullOrWhiteSpace(from.PhoneNumber)) { - Téléphone fixe: @TeXHelpers.ToTeX(from.PhoneNumber) } } } @@ -113,10 +113,10 @@ % Logo de la société @if (from.Avatar != null) { -\includegraphics[height=60pt, viewport=0 0 128 128]{@(ViewBag.AvatarsDir)/@(from.UserName).png} +\includegraphics[height=60pt]{@(ViewBag.AvatarsDir)/@(from.UserName).png} -} else { -%\includegraphics[height=60pt, viewport=0 0 128 128]{logo.png} +} else { +%\includegraphics{logo.png} } % Nom et adresse de la société @@ -170,12 +170,12 @@ \begin{center} \begin{tabular}{|c c c c|} @if (!string.IsNullOrWhiteSpace(bi.BankCode) && !string.IsNullOrWhiteSpace(bi.WicketCode) - && !string.IsNullOrWhiteSpace(bi.AccountNumber) ) { + && !string.IsNullOrWhiteSpace(bi.AccountNumber) ) { \hline \textbf{Code banque} & \textbf{Code guichet} & \textbf{N° de Compte} & \textbf{Clé RIB} \\ @bi.BankCode & @bi.WicketCode & @bi.AccountNumber & @bi.BankedKey \\ } - @if (!string.IsNullOrWhiteSpace(@bi.IBAN) && !string.IsNullOrWhiteSpace(@bi.BIC)) { + @if (!string.IsNullOrWhiteSpace(@bi.IBAN) && !string.IsNullOrWhiteSpace(@bi.BIC)) { \hline \textbf{IBAN N°} & \multicolumn{3}{|l|}{ @bi.IBAN } \\ \hline \textbf{Code BIC} & \multicolumn{3}{|l|}{ @bi.BIC } @@ -188,46 +188,19 @@ } -@{ - // Captured signatures, if any. The Estimate.Signatures collection - // is loaded by the controller; SignaturePro/SignatureClient project - // the row by type. We draw them as vector TikZ at the foot of the - // devis (see TeXHelpers.SignatureToTikz), so there is no PNG to - // produce or \includegraphics to fail on. - var fr = CultureInfo.CreateSpecificCulture("fr-FR"); - var proSig = Model.SignaturePro; - var cliSig = Model.SignatureClient; +@if (!(Model.ProviderValidationDate==null)) { + +\begin{flushright} + @(new HtmlString(isestimate?"Devis validé":"Facture validée")) le @TeXHelpers.ToTeX(Model.ProviderValidationDate.ToString("dddd dd MMMM yyyy")) +\end{flushright} +@if (prosign.Exists) { + +\begin{center} + \hspace{263pt} + \includegraphics[height=60pt]{@(ViewBag.BillsDir)/estimate-prosign-@(Model.Id).png} +\end{center} + } - -\vspace{2em} -\noindent -\begin{minipage}[t]{0.45\textwidth} - \textbf{Signature du fournisseur}\\ -@if (Model.ProviderValidationDate != default(DateTime)) { -\small Validé le @TeXHelpers.ToTeX(Model.ProviderValidationDate.ToString("dddd dd MMMM yyyy", fr))\\ + } -@if (proSig != null && proSig.Strokes != null && proSig.Strokes.Length > 0) { -\begin{tikzpicture} - \useasboundingbox (0,0) rectangle (5,2.5); - @proSig.SignatureToTikz(5, 2.5) -\end{tikzpicture} -} else { -\rule[-2.2cm]{5cm}{0.4pt} -} -\end{minipage}\hfill -\begin{minipage}[t]{0.45\textwidth} - \textbf{Signature du client}\\ -@if (Model.ClientValidationDate != default(DateTime)) { -\small Validé le @TeXHelpers.ToTeX(Model.ClientValidationDate.ToString("dddd dd MMMM yyyy", fr))\\ -} -@if (cliSig != null && cliSig.Strokes != null && cliSig.Strokes.Length > 0) { -\begin{tikzpicture} - \useasboundingbox (0,0) rectangle (5,2.5); - @cliSig.SignatureToTikz(5, 2.5) -\end{tikzpicture} -} else { -\rule[-2.2cm]{5cm}{0.4pt} -} -\end{minipage} - \end{document} diff --git a/src/Yavsc.Org/Views/Shared/_Layout.cshtml b/src/Yavsc.Org/Views/Shared/_Layout.cshtml index 653ffae0a..9515ac2b4 100644 --- a/src/Yavsc.Org/Views/Shared/_Layout.cshtml +++ b/src/Yavsc.Org/Views/Shared/_Layout.cshtml @@ -31,7 +31,7 @@

    @((n.click_action == null) ? Localizer["Fermer"] : Localizer[n.click_action]) - @n.body + @n.body } } diff --git a/src/Yavsc.Org/Views/Shared/_Nav.cshtml b/src/Yavsc.Org/Views/Shared/_Nav.cshtml index 4af820fc8..385884fc0 100644 --- a/src/Yavsc.Org/Views/Shared/_Nav.cshtml +++ b/src/Yavsc.Org/Views/Shared/_Nav.cshtml @@ -2,7 +2,7 @@ style="background-image: url('@SiteSettings.Value.Banner'); background-position: 0%; background-attachment: fixed;"> - Yavsc @SiteSettings.Value.Title diff --git a/src/Yavsc.Org/Yavsc.Org.csproj b/src/Yavsc.Org/Yavsc.Org.csproj index 01c38378c..88fb3c7be 100644 --- a/src/Yavsc.Org/Yavsc.Org.csproj +++ b/src/Yavsc.Org/Yavsc.Org.csproj @@ -9,8 +9,8 @@ https://github.com/pazof/yavsc 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 @@ -39,6 +39,8 @@ + + diff --git a/src/Yavsc.Org/appsettings-org-template.json b/src/Yavsc.Org/appsettings-org-template.json new file mode 100644 index 000000000..85593635f --- /dev/null +++ b/src/Yavsc.Org/appsettings-org-template.json @@ -0,0 +1,104 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.AspNetCore.SignalR": "Debug", + "Microsoft.AspNetCore.Http.Connections": "Debug" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "YavscConnection": "*** via dotnet user-secrets ou variable d'environnement *** Server=[YOURSERVERNAME];Port=5432;Database=[YOURDBNAME];Username=[YOURDBUSERNAME];Password=[YOURDBPASSW];" + }, + "Site": { + "Title": "yavsc", + "Slogan": "Yavsc!", + "StyleSheet": "/css/default.css", + "Authority": "https://[Your domaine name]", + "ExternalUrl": "https://[Your domaine name]", + "CorsAllowedOrigins": [ + "https://your.api.url.com/api/v1", + "https://localhost:5003/api/v1", + "https://localhost:5005/api/v1" + ], + "Audience": [ "api", "blogs"], + "Banner": "/images/banner-dewang.jpg", + "Owner": { + "Name": "[Site owner's name]", + "EMail": "[Site owner's e-mail address]" + }, + "Admin": { + "Name": "[a name for a site admin]", + "EMail": "[an e-mail for a site admin]" + }, + "Avatars": "avatars", + "Quota": 200000000, + "Bills": "bills", + "Blog": "blog", + "TempDir": "temp", + "DataDir": "data" + }, + "Smtp": { + "Host": "[YOURSMTPHOST]", + "Port": 465, + "Username": "[YOURSMTPUSERNAME]", + "Password": "[YOURSMTPPASSWORD]" + }, + "DataProtection": { + "Keys": { + "Dir": "DataProtection" + }, + "RSAParamFile": "RSA-Params.json", + "ExpiresInHours": 168 + }, + "Authentication": { + "PayPal": { + "Mode": "[sandbox|production]", + "ClientId": "[Your PayPal REST API ClientId]", + "Secret": "[Your PayPal REST API Secret]" + }, + "Google": { + "ApiKey": "[Your ApiKey]", + "ClientId": "[Your ClientId]", + "ClientSecret": "[Your ClientSecret]", + "ServiceAccount": { + "project_id": "[Yours]", + "private_key_id": "[Yours]", + "private_key": "-----[Yours]---\n", + "client_email": "[Yours]@appspot.gserviceaccount.com", + "client_id": "[Yours]", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://accounts.google.com/o/oauth2/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www41.googleapis.com/robot/v1/[Yours].com" + } + }, + "Facebook": { + "ClientId": "[Your ClientId]", + "ClientSecret": "[Your ClientSecret]" + }, + "Twitter": { + "ClientId": "[Your ClientId]", + "ClientSecret": "[Your ClientSecret]" + } + }, + "Moderation": { + "AutoApproveThreshold": 0.7, + "AutoRejectThreshold": 0.9 + }, + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://localhost:82" + }, + "Https": { + "Url": "https://localhost:83", + "Certificate": { + "Path": "/etc/letsencrypt/live/[Your domaine name]/cert.pem", + "KeyPath": "/etc/letsencrypt/live/[Your domaine name]/privkey.pem" + } + } + } + } +} diff --git a/src/Yavsc.Org/wwwroot/lib/bootstrap.quartz.css b/src/Yavsc.Org/wwwroot/lib/bootstrap.quartz.css index 466c6e28b..84fb5efe4 100644 --- a/src/Yavsc.Org/wwwroot/lib/bootstrap.quartz.css +++ b/src/Yavsc.Org/wwwroot/lib/bootstrap.quartz.css @@ -3924,7 +3924,7 @@ fieldset:disabled .btn { --bs-dropdown-box-shadow: var(--bs-box-shadow); --bs-dropdown-link-color: var(--bs-body-color); --bs-dropdown-link-hover-color: #fff; - --bs-dropdown-link-hover-bg: rgba(0, 0, 0, 0.4); + --bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.4); --bs-dropdown-link-active-color: #fff; --bs-dropdown-link-active-bg: #e83283; --bs-dropdown-link-disabled-color: var(--bs-tertiary-color); @@ -5033,8 +5033,7 @@ fieldset:disabled .btn { background-color: var(--bs-card-bg); background-clip: border-box; border: var(--bs-card-border-width) solid var(--bs-card-border-color); - border-radius: var(--bs-card-border-radius); - padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x); + border-radius: var(--bs-card-border-radius) } .card>hr { diff --git a/src/Yavsc.Org/wwwroot/lib/bootstrap.quartz.min.css b/src/Yavsc.Org/wwwroot/lib/bootstrap.quartz.min.css index 32df6b5e2..390637ef1 100644 --- a/src/Yavsc.Org/wwwroot/lib/bootstrap.quartz.min.css +++ b/src/Yavsc.Org/wwwroot/lib/bootstrap.quartz.min.css @@ -4,4 +4,4 @@ * Licensed under MIT * Based on Bootstrap*//*!* Bootstrap v5.3.8 (https://getbootstrap.com/) * Copyright 2011-2025 The Bootstrap Authors -* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)*/:root,[data-bs-theme=light]{--bs-blue:#3a8fd9;--bs-indigo:#6610f2;--bs-purple:#686dc3;--bs-pink:#e83283;--bs-red:#fc346f;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#41d7a7;--bs-teal:#528fb3;--bs-cyan:#39cbfb;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9e9e8;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#e83283;--bs-secondary:rgba(255, 255, 255, 0.4);--bs-success:#41d7a7;--bs-info:#39cbfb;--bs-warning:#ffc107;--bs-danger:#fd7e14;--bs-light:#e9e9e8;--bs-dark:#212529;--bs-primary-rgb:232, 50, 131;--bs-secondary-rgb:255, 255, 255;--bs-success-rgb:65, 215, 167;--bs-info-rgb:57, 203, 251;--bs-warning-rgb:255, 193, 7;--bs-danger-rgb:253, 126, 20;--bs-light-rgb:233, 233, 232;--bs-dark-rgb:33, 37, 41;--bs-primary-text-emphasis:#5d1434;--bs-secondary-text-emphasis:rgba(36, 36, 36, 0.76);--bs-success-text-emphasis:#1a5643;--bs-info-text-emphasis:#175164;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#653208;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#fad6e6;--bs-secondary-bg-subtle:rgba(255, 255, 255, 0.88);--bs-success-bg-subtle:#d9f7ed;--bs-info-bg-subtle:#d7f5fe;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#ffe5d0;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#f6adcd;--bs-secondary-border-subtle:rgba(255, 255, 255, 0.76);--bs-success-border-subtle:#b3efdc;--bs-info-border-subtle:#b0eafd;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#fecba1;--bs-light-border-subtle:#e9e9e8;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255, 255, 255;--bs-black-rgb:0, 0, 0;--bs-font-sans-serif:system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#fff;--bs-body-color-rgb:255, 255, 255;--bs-body-bg:#686dc3;--bs-body-bg-rgb:104, 109, 195;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0, 0, 0;--bs-secondary-color:rgba(255, 255, 255, 0.75);--bs-secondary-color-rgb:255, 255, 255;--bs-secondary-bg:#e9e9e8;--bs-secondary-bg-rgb:233, 233, 232;--bs-tertiary-color:rgba(255, 255, 255, 0.5);--bs-tertiary-color-rgb:255, 255, 255;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248, 249, 250;--bs-heading-color:inherit;--bs-link-color:#fff;--bs-link-color-rgb:255, 255, 255;--bs-link-decoration:underline;--bs-link-hover-color:#cccccc;--bs-link-hover-color-rgb:204, 204, 204;--bs-code-color:#e83283;--bs-highlight-color:#fff;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:rgba(255, 255, 255, 0.2);--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.5rem;--bs-border-radius-sm:0.6rem;--bs-border-radius-lg:0.7rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:1px 3px 24px -1px rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 1px 1px rgba(0, 0, 0, 0.1);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(232, 50, 131, 0.25);--bs-form-valid-color:#41d7a7;--bs-form-valid-border-color:#41d7a7;--bs-form-invalid-color:#fd7e14;--bs-form-invalid-border-color:#fd7e14}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222, 226, 230;--bs-body-bg:#212529;--bs-body-bg-rgb:33, 37, 41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255, 255, 255;--bs-secondary-color:rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb:222, 226, 230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52, 58, 64;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222, 226, 230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43, 48, 53;--bs-primary-text-emphasis:#f184b5;--bs-secondary-text-emphasis:rgba(255, 255, 255, 0.64);--bs-success-text-emphasis:#8de7ca;--bs-info-text-emphasis:#88e0fd;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#feb272;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#2e0a1a;--bs-secondary-bg-subtle:rgba(15, 15, 15, 0.88);--bs-success-bg-subtle:#0d2b21;--bs-info-bg-subtle:#0b2932;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#331904;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#8b1e4f;--bs-secondary-border-subtle:rgba(70, 70, 70, 0.64);--bs-success-border-subtle:#278164;--bs-info-border-subtle:#227a97;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#984c0c;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#f184b5;--bs-link-hover-color:#f49dc4;--bs-link-color-rgb:241, 132, 181;--bs-link-hover-color-rgb:244, 157, 196;--bs-code-color:#f184b5;--bs-highlight-color:#dee2e6;--bs-highlight-bg:#664d03;--bs-border-color:#495057;--bs-border-color-translucent:rgba(255, 255, 255, 0.15);--bs-form-valid-color:#8de7ca;--bs-form-valid-border-color:#8de7ca;--bs-form-invalid-color:#fd85a9;--bs-form-invalid-border-color:#fd85a9}*,::after,::before{box-sizing:border-box}@media(prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:2rem 0;color:inherit;border:0;border-top:var(--bs-border-width)solid;opacity:.25}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:1rem;font-weight:700;line-height:1.2;color:var(--bs-heading-color)}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media(min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media(min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media(min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media(min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.1875em;color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1));text-decoration:underline}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.6rem}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-secondary-color);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;line-height:inherit;font-size:calc(1.275rem + .3vw)}@media(min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button{cursor:pointer;filter:grayscale(1)}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-weight:300;line-height:1.2;font-size:calc(1.625rem + 4.5vw)}@media(min-width:1200px){.display-1{font-size:5rem}}.display-2{font-weight:300;line-height:1.2;font-size:calc(1.575rem + 3.9vw)}@media(min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-weight:300;line-height:1.2;font-size:calc(1.525rem + 3.3vw)}@media(min-width:1200px){.display-3{font-size:4rem}}.display-4{font-weight:300;line-height:1.2;font-size:calc(1.475rem + 2.7vw)}@media(min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-weight:300;line-height:1.2;font-size:calc(1.425rem + 2.1vw)}@media(min-width:1200px){.display-5{font-size:3rem}}.display-6{font-weight:300;line-height:1.2;font-size:calc(1.375rem + 1.5vw)}@media(min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:2rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-2rem;margin-bottom:2rem;font-size:.875em;color:rgba(255,255,255,.7)}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:var(--bs-body-bg);border:var(--bs-border-width)solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:1rem;line-height:1}.figure-caption{font-size:.875em;color:var(--bs-secondary-color)}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-right:auto;margin-left:auto}@media(min-width:576px){.container,.container-sm{max-width:540px}}@media(min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media(min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media(min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media(min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0}.row-cols-auto>*{flex:none;width:auto}.row-cols-1>*{flex:none;width:100%}.row-cols-2>*{flex:none;width:50%}.row-cols-3>*{flex:none;width:33.33333333%}.row-cols-4>*{flex:none;width:25%}.row-cols-5>*{flex:none;width:20%}.row-cols-6>*{flex:none;width:16.66666667%}.col-auto{flex:none;width:auto}.col-1{flex:none;width:8.33333333%}.col-2{flex:none;width:16.66666667%}.col-3{flex:none;width:25%}.col-4{flex:none;width:33.33333333%}.col-5{flex:none;width:41.66666667%}.col-6{flex:none;width:50%}.col-7{flex:none;width:58.33333333%}.col-8{flex:none;width:66.66666667%}.col-9{flex:none;width:75%}.col-10{flex:none;width:83.33333333%}.col-11{flex:none;width:91.66666667%}.col-12{flex:none;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.5rem}.g-1,.gy-1{--bs-gutter-y:0.5rem}.g-2,.gx-2{--bs-gutter-x:1rem}.g-2,.gy-2{--bs-gutter-y:1rem}.g-3,.gx-3{--bs-gutter-x:2rem}.g-3,.gy-3{--bs-gutter-y:2rem}.g-4,.gx-4{--bs-gutter-x:3rem}.g-4,.gy-4{--bs-gutter-y:3rem}.g-5,.gx-5{--bs-gutter-x:6rem}.g-5,.gy-5{--bs-gutter-y:6rem}@media(min-width:576px){.col-sm{flex:1 0}.row-cols-sm-auto>*{flex:none;width:auto}.row-cols-sm-1>*{flex:none;width:100%}.row-cols-sm-2>*{flex:none;width:50%}.row-cols-sm-3>*{flex:none;width:33.33333333%}.row-cols-sm-4>*{flex:none;width:25%}.row-cols-sm-5>*{flex:none;width:20%}.row-cols-sm-6>*{flex:none;width:16.66666667%}.col-sm-auto{flex:none;width:auto}.col-sm-1{flex:none;width:8.33333333%}.col-sm-2{flex:none;width:16.66666667%}.col-sm-3{flex:none;width:25%}.col-sm-4{flex:none;width:33.33333333%}.col-sm-5{flex:none;width:41.66666667%}.col-sm-6{flex:none;width:50%}.col-sm-7{flex:none;width:58.33333333%}.col-sm-8{flex:none;width:66.66666667%}.col-sm-9{flex:none;width:75%}.col-sm-10{flex:none;width:83.33333333%}.col-sm-11{flex:none;width:91.66666667%}.col-sm-12{flex:none;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.5rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.5rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:1rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:1rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:2rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:2rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:3rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:3rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:6rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:6rem}}@media(min-width:768px){.col-md{flex:1 0}.row-cols-md-auto>*{flex:none;width:auto}.row-cols-md-1>*{flex:none;width:100%}.row-cols-md-2>*{flex:none;width:50%}.row-cols-md-3>*{flex:none;width:33.33333333%}.row-cols-md-4>*{flex:none;width:25%}.row-cols-md-5>*{flex:none;width:20%}.row-cols-md-6>*{flex:none;width:16.66666667%}.col-md-auto{flex:none;width:auto}.col-md-1{flex:none;width:8.33333333%}.col-md-2{flex:none;width:16.66666667%}.col-md-3{flex:none;width:25%}.col-md-4{flex:none;width:33.33333333%}.col-md-5{flex:none;width:41.66666667%}.col-md-6{flex:none;width:50%}.col-md-7{flex:none;width:58.33333333%}.col-md-8{flex:none;width:66.66666667%}.col-md-9{flex:none;width:75%}.col-md-10{flex:none;width:83.33333333%}.col-md-11{flex:none;width:91.66666667%}.col-md-12{flex:none;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.5rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.5rem}.g-md-2,.gx-md-2{--bs-gutter-x:1rem}.g-md-2,.gy-md-2{--bs-gutter-y:1rem}.g-md-3,.gx-md-3{--bs-gutter-x:2rem}.g-md-3,.gy-md-3{--bs-gutter-y:2rem}.g-md-4,.gx-md-4{--bs-gutter-x:3rem}.g-md-4,.gy-md-4{--bs-gutter-y:3rem}.g-md-5,.gx-md-5{--bs-gutter-x:6rem}.g-md-5,.gy-md-5{--bs-gutter-y:6rem}}@media(min-width:992px){.col-lg{flex:1 0}.row-cols-lg-auto>*{flex:none;width:auto}.row-cols-lg-1>*{flex:none;width:100%}.row-cols-lg-2>*{flex:none;width:50%}.row-cols-lg-3>*{flex:none;width:33.33333333%}.row-cols-lg-4>*{flex:none;width:25%}.row-cols-lg-5>*{flex:none;width:20%}.row-cols-lg-6>*{flex:none;width:16.66666667%}.col-lg-auto{flex:none;width:auto}.col-lg-1{flex:none;width:8.33333333%}.col-lg-2{flex:none;width:16.66666667%}.col-lg-3{flex:none;width:25%}.col-lg-4{flex:none;width:33.33333333%}.col-lg-5{flex:none;width:41.66666667%}.col-lg-6{flex:none;width:50%}.col-lg-7{flex:none;width:58.33333333%}.col-lg-8{flex:none;width:66.66666667%}.col-lg-9{flex:none;width:75%}.col-lg-10{flex:none;width:83.33333333%}.col-lg-11{flex:none;width:91.66666667%}.col-lg-12{flex:none;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.5rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.5rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:1rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:1rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:2rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:2rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:3rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:3rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:6rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:6rem}}@media(min-width:1200px){.col-xl{flex:1 0}.row-cols-xl-auto>*{flex:none;width:auto}.row-cols-xl-1>*{flex:none;width:100%}.row-cols-xl-2>*{flex:none;width:50%}.row-cols-xl-3>*{flex:none;width:33.33333333%}.row-cols-xl-4>*{flex:none;width:25%}.row-cols-xl-5>*{flex:none;width:20%}.row-cols-xl-6>*{flex:none;width:16.66666667%}.col-xl-auto{flex:none;width:auto}.col-xl-1{flex:none;width:8.33333333%}.col-xl-2{flex:none;width:16.66666667%}.col-xl-3{flex:none;width:25%}.col-xl-4{flex:none;width:33.33333333%}.col-xl-5{flex:none;width:41.66666667%}.col-xl-6{flex:none;width:50%}.col-xl-7{flex:none;width:58.33333333%}.col-xl-8{flex:none;width:66.66666667%}.col-xl-9{flex:none;width:75%}.col-xl-10{flex:none;width:83.33333333%}.col-xl-11{flex:none;width:91.66666667%}.col-xl-12{flex:none;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.5rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.5rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:1rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:1rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:2rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:2rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:3rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:3rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:6rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:6rem}}@media(min-width:1400px){.col-xxl{flex:1 0}.row-cols-xxl-auto>*{flex:none;width:auto}.row-cols-xxl-1>*{flex:none;width:100%}.row-cols-xxl-2>*{flex:none;width:50%}.row-cols-xxl-3>*{flex:none;width:33.33333333%}.row-cols-xxl-4>*{flex:none;width:25%}.row-cols-xxl-5>*{flex:none;width:20%}.row-cols-xxl-6>*{flex:none;width:16.66666667%}.col-xxl-auto{flex:none;width:auto}.col-xxl-1{flex:none;width:8.33333333%}.col-xxl-2{flex:none;width:16.66666667%}.col-xxl-3{flex:none;width:25%}.col-xxl-4{flex:none;width:33.33333333%}.col-xxl-5{flex:none;width:41.66666667%}.col-xxl-6{flex:none;width:50%}.col-xxl-7{flex:none;width:58.33333333%}.col-xxl-8{flex:none;width:66.66666667%}.col-xxl-9{flex:none;width:75%}.col-xxl-10{flex:none;width:83.33333333%}.col-xxl-11{flex:none;width:91.66666667%}.col-xxl-12{flex:none;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.5rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.5rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:1rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:1rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:2rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:2rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:3rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:3rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:6rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:6rem}}.table{--bs-table-color-type:initial;--bs-table-bg-type:initial;--bs-table-color-state:initial;--bs-table-bg-state:initial;--bs-table-color:initial;--bs-table-bg:var(--bs-body-bg);--bs-table-border-color:var(--bs-border-color);--bs-table-accent-bg:transparent;--bs-table-striped-color:initial;--bs-table-striped-bg:rgba(var(--bs-emphasis-color-rgb), 0.05);--bs-table-active-color:initial;--bs-table-active-bg:rgba(var(--bs-emphasis-color-rgb), 0.1);--bs-table-hover-color:initial;--bs-table-hover-bg:rgba(var(--bs-emphasis-color-rgb), 0.075);width:100%;margin-bottom:2rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem;color:var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state,var(--bs-table-bg-type,var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2)solid}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width)0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-active{--bs-table-color-state:var(--bs-table-active-color);--bs-table-bg-state:var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state:var(--bs-table-hover-color);--bs-table-bg-state:var(--bs-table-hover-bg)}.table-primary{--bs-table-color:#fff;--bs-table-bg:#e83283;--bs-table-border-color:#ed5b9c;--bs-table-striped-bg:#e93c89;--bs-table-striped-color:#fff;--bs-table-active-bg:#ea478f;--bs-table-active-color:#fff;--bs-table-hover-bg:#ea418c;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#fff;--bs-table-bg:rgba(255, 255, 255, 0.4);--bs-table-border-color:rgba(255, 255, 255, 0.52);--bs-table-striped-bg:rgba(255, 255, 255, 0.43);--bs-table-striped-color:#000;--bs-table-active-bg:rgba(255, 255, 255, 0.46);--bs-table-active-color:#000;--bs-table-hover-bg:rgba(255, 255, 255, 0.445);--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#fff;--bs-table-bg:#41d7a7;--bs-table-border-color:#67dfb9;--bs-table-striped-bg:#4bd9ab;--bs-table-striped-color:#fff;--bs-table-active-bg:#54dbb0;--bs-table-active-color:#fff;--bs-table-hover-bg:#4fdaae;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#fff;--bs-table-bg:#39cbfb;--bs-table-border-color:#61d5fc;--bs-table-striped-bg:#43cefb;--bs-table-striped-color:#fff;--bs-table-active-bg:#4dd0fb;--bs-table-active-color:#fff;--bs-table-hover-bg:#48cffb;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#fff;--bs-table-bg:#ffc107;--bs-table-border-color:#ffcd39;--bs-table-striped-bg:#ffc413;--bs-table-striped-color:#fff;--bs-table-active-bg:#ffc720;--bs-table-active-color:#fff;--bs-table-hover-bg:#ffc61a;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#fff;--bs-table-bg:#fd7e14;--bs-table-border-color:#fd9843;--bs-table-striped-bg:#fd8420;--bs-table-striped-color:#fff;--bs-table-active-bg:#fd8b2c;--bs-table-active-color:#fff;--bs-table-hover-bg:#fd8826;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#e9e9e8;--bs-table-border-color:#bababa;--bs-table-striped-bg:#dddddc;--bs-table-striped-color:#000;--bs-table-active-bg:#d2d2d1;--bs-table-active-color:#fff;--bs-table-hover-bg:#d8d8d7;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#212529;--bs-table-border-color:#4d5154;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.75rem + 1px);padding-bottom:calc(.75rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:var(--bs-secondary-color)}.form-control{display:block;width:100%;padding:.75rem 1.5rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:initial;background-clip:padding-box;border:1px solid rgba(255,255,255,.4);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:initial;border-color:rgba(255,255,255,.4);outline:0;box-shadow:none}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::-moz-placeholder{color:rgba(255,255,255,.7);opacity:1}.form-control::placeholder{color:rgba(255,255,255,.7);opacity:1}.form-control:disabled{background-color:rgba(255,255,255,.1);opacity:1}.form-control::-webkit-file-upload-button{padding:.75rem 1.5rem;margin:-.75rem -1.5rem;-webkit-margin-end:1.5rem;margin-inline-end:1.5rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.75rem 1.5rem;margin:-.75rem -1.5rem;-webkit-margin-end:1.5rem;margin-inline-end:1.5rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:var(--bs-secondary-bg)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{display:block;width:100%;padding:.75rem 0;margin-bottom:0;line-height:1.5;color:var(--bs-body-color);background-color:initial;border:solid transparent;border-width:1px 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(1px * 2));padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(1px * 2));padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 1.5rem + calc(1px * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(1px * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(1px * 2))}.form-control-color{width:3rem;height:calc(1.5em + 1.5rem + calc(1px * 2));padding:.75rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color::-webkit-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(1px * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(1px * 2))}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.75rem 4.5rem .75rem 1.5rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:initial;background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon,none);background-repeat:no-repeat;background-position:right 1.5rem center;background-size:16px 12px;border:1px solid rgba(255,255,255,.4);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:rgba(255,255,255,.4);outline:0;box-shadow:none}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:1.5rem;background-image:none}.form-select:disabled{color:rgba(255,255,255,.7);background-color:rgba(255,255,255,.1)}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-right:1.5em;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:-1.5em;margin-left:0}.form-check-input{--bs-form-check-bg:rgba(255, 255, 255, 0.3);flex-shrink:0;width:1em;height:1em;margin-top:.25em;vertical-align:top;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:50%;background-size:contain;border:1px solid rgba(255,255,255,.2);-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:rgba(255,255,255,.4);outline:0;box-shadow:0 0 0 .25rem rgba(232,50,131,.25)}.form-check-input:checked{background-color:#e83283;border-color:#e83283}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#e83283;border-color:#e83283;--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:0;border-radius:2em;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:100%;--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:initial}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #686dc3,none}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #686dc3,none}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;-webkit-appearance:none;appearance:none;background-color:#e83283;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#f8c2da}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:rgba(0,0,0,.2);border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;-moz-appearance:none;appearance:none;background-color:#e83283;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#f8c2da}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:rgba(0,0,0,.2);border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(1px * 2));min-height:calc(3.5rem + calc(1px * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;max-width:100%;height:100%;padding:1rem 1.5rem;overflow:hidden;color:rgba(var(--bs-body-color-rgb),.65);text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem 1.5rem}.form-floating>.form-control-plaintext::-moz-placeholder,.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control-plaintext::placeholder,.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control-plaintext:not(:-moz-placeholder-shown),.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:-webkit-autofill,.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem;padding-left:1.5rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{transform:scale(.85)translateY(-.5rem)translateX(.15rem)}.form-floating>.form-control-plaintext~label,.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{transform:scale(.85)translateY(-.5rem)translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{transform:scale(.85)translateY(-.5rem)translateX(.15rem)}.form-floating>textarea:not(:-moz-placeholder-shown)~label::after{position:absolute;inset:1rem .75rem;z-index:-1;height:1.5em;content:"";background-color:initial;border-radius:var(--bs-border-radius)}.form-floating>textarea:focus~label::after,.form-floating>textarea:not(:placeholder-shown)~label::after{position:absolute;inset:1rem .75rem;z-index:-1;height:1.5em;content:"";background-color:initial;border-radius:var(--bs-border-radius)}.form-floating>textarea:disabled~label::after{background-color:rgba(255,255,255,.1)}.form-floating>.form-control-plaintext~label{border-width:1px 0}.form-floating>.form-control:disabled~label,.form-floating>:disabled~label{color:#6c757d}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-floating,.input-group>.form-select{position:relative;flex:auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-floating:focus-within,.input-group>.form-select:focus{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;align-items:center;padding:.75rem 1.5rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:initial;border:1px solid rgba(255,255,255,.4);border-radius:var(--bs-border-radius)}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:6rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(-1 * 1px);border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-valid-color)}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem 1rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius)}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:var(--bs-form-valid-border-color);padding-right:calc(1.5em + 1.5rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2341d7a7' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .375rem)center;background-size:calc(.75em + .75rem)calc(.75em + .75rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 1.5rem);background-position:top calc(.375em + .375rem)right calc(.375em + .375rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:var(--bs-form-valid-border-color)}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2341d7a7' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");padding-right:8.25rem;background-position:right 1.5rem center,center right 4.5rem;background-size:16px 12px,calc(.75em + .75rem)calc(.75em + .75rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-control-color.is-valid,.was-validated .form-control-color:valid{width:calc(3rem + calc(1.5em + 1.5rem))}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:var(--bs-form-valid-border-color)}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:var(--bs-form-valid-color)}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-valid,.input-group>.form-floating:not(:focus-within).is-valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-control:not(:focus):valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.was-validated .input-group>.form-select:not(:focus):valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-invalid-color)}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem 1rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius)}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:var(--bs-form-invalid-border-color);padding-right:calc(1.5em + 1.5rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23fd7e14'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23fd7e14' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .375rem)center;background-size:calc(.75em + .75rem)calc(.75em + .75rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 1.5rem);background-position:top calc(.375em + .375rem)right calc(.375em + .375rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:var(--bs-form-invalid-border-color)}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23fd7e14'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23fd7e14' stroke='none'/%3e%3c/svg%3e");padding-right:8.25rem;background-position:right 1.5rem center,center right 4.5rem;background-size:16px 12px,calc(.75em + .75rem)calc(.75em + .75rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-control-color.is-invalid,.was-validated .form-control-color:invalid{width:calc(3rem + calc(1.5em + 1.5rem))}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:var(--bs-form-invalid-border-color)}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:var(--bs-form-invalid-color)}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-invalid,.input-group>.form-floating:not(:focus-within).is-invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-control:not(:focus):invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.was-validated .input-group>.form-select:not(:focus):invalid{z-index:4}.btn{--bs-btn-padding-x:1.5rem;--bs-btn-padding-y:0.75rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:1px 3px 24px -1px rgba(0, 0, 0, 0.15);--bs-btn-disabled-opacity:0.65;--bs-btn-focus-box-shadow:0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y)var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;border:var(--bs-btn-border-width)solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,.btn.active,.btn.show,.btn:first-child:active,:not(.btn-check)+.btn:active{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn:first-child:active:focus-visible,:not(.btn-check)+.btn:active:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked:focus-visible+.btn{box-shadow:var(--bs-btn-focus-box-shadow)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#e83283;--bs-btn-border-color:#e83283;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#c52b6f;--bs-btn-hover-border-color:#ba2869;--bs-btn-focus-shadow-rgb:235, 81, 150;--bs-btn-active-color:#fff;--bs-btn-active-bg:#ba2869;--bs-btn-active-border-color:#ae2662;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#e83283;--bs-btn-disabled-border-color:#e83283}.btn-secondary{--bs-btn-color:#000;--bs-btn-bg:rgba(255, 255, 255, 0.4);--bs-btn-border-color:rgba(255, 255, 255, 0.4);--bs-btn-hover-color:#000;--bs-btn-hover-bg:rgba(255, 255, 255, 0.49);--bs-btn-hover-border-color:rgba(255, 255, 255, 0.46);--bs-btn-focus-shadow-rgb:149, 149, 149;--bs-btn-active-color:#000;--bs-btn-active-bg:rgba(255, 255, 255, 0.52);--bs-btn-active-border-color:rgba(255, 255, 255, 0.46);--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:rgba(255, 255, 255, 0.4);--bs-btn-disabled-border-color:rgba(255, 255, 255, 0.4)}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#41d7a7;--bs-btn-border-color:#41d7a7;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#37b78e;--bs-btn-hover-border-color:#34ac86;--bs-btn-focus-shadow-rgb:94, 221, 180;--bs-btn-active-color:#fff;--bs-btn-active-bg:#34ac86;--bs-btn-active-border-color:#31a17d;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#41d7a7;--bs-btn-disabled-border-color:#41d7a7}.btn-info{--bs-btn-color:#fff;--bs-btn-bg:#39cbfb;--bs-btn-border-color:#39cbfb;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#30add5;--bs-btn-hover-border-color:#2ea2c9;--bs-btn-focus-shadow-rgb:87, 211, 252;--bs-btn-active-color:#fff;--bs-btn-active-bg:#2ea2c9;--bs-btn-active-border-color:#2b98bc;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#39cbfb;--bs-btn-disabled-border-color:#39cbfb}.btn-warning{--bs-btn-color:#fff;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#d9a406;--bs-btn-hover-border-color:#cc9a06;--bs-btn-focus-shadow-rgb:255, 202, 44;--bs-btn-active-color:#fff;--bs-btn-active-bg:#cc9a06;--bs-btn-active-border-color:#bf9105;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#fd7e14;--bs-btn-border-color:#fd7e14;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#d76b11;--bs-btn-hover-border-color:#ca6510;--bs-btn-focus-shadow-rgb:253, 145, 55;--bs-btn-active-color:#fff;--bs-btn-active-bg:#ca6510;--bs-btn-active-border-color:#be5f0f;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#fd7e14;--bs-btn-disabled-border-color:#fd7e14}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#e9e9e8;--bs-btn-border-color:#e9e9e8;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#c6c6c5;--bs-btn-hover-border-color:#bababa;--bs-btn-focus-shadow-rgb:198, 198, 197;--bs-btn-active-color:#fff;--bs-btn-active-bg:#bababa;--bs-btn-active-border-color:#afafae;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#e9e9e8;--bs-btn-disabled-border-color:#e9e9e8}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#424649;--bs-btn-hover-border-color:#373b3e;--bs-btn-focus-shadow-rgb:66, 70, 73;--bs-btn-active-color:#fff;--bs-btn-active-bg:#4d5154;--bs-btn-active-border-color:#373b3e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#212529;--bs-btn-disabled-border-color:#212529}.btn-outline-primary{--bs-btn-color:#e83283;--bs-btn-border-color:#e83283;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#e83283;--bs-btn-hover-border-color:#e83283;--bs-btn-focus-shadow-rgb:232, 50, 131;--bs-btn-active-color:#fff;--bs-btn-active-bg:#e83283;--bs-btn-active-border-color:#e83283;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#e83283;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#e83283;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:rgba(255, 255, 255, 0.4);--bs-btn-border-color:rgba(255, 255, 255, 0.4);--bs-btn-hover-color:#000;--bs-btn-hover-bg:rgba(255, 255, 255, 0.4);--bs-btn-hover-border-color:rgba(255, 255, 255, 0.4);--bs-btn-focus-shadow-rgb:255, 255, 255;--bs-btn-active-color:#000;--bs-btn-active-bg:rgba(255, 255, 255, 0.4);--bs-btn-active-border-color:rgba(255, 255, 255, 0.4);--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:rgba(255, 255, 255, 0.4);--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:rgba(255, 255, 255, 0.4);--bs-gradient:none}.btn-outline-success{--bs-btn-color:#41d7a7;--bs-btn-border-color:#41d7a7;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#41d7a7;--bs-btn-hover-border-color:#41d7a7;--bs-btn-focus-shadow-rgb:65, 215, 167;--bs-btn-active-color:#fff;--bs-btn-active-bg:#41d7a7;--bs-btn-active-border-color:#41d7a7;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#41d7a7;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#41d7a7;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#39cbfb;--bs-btn-border-color:#39cbfb;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#39cbfb;--bs-btn-hover-border-color:#39cbfb;--bs-btn-focus-shadow-rgb:57, 203, 251;--bs-btn-active-color:#fff;--bs-btn-active-bg:#39cbfb;--bs-btn-active-border-color:#39cbfb;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#39cbfb;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#39cbfb;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255, 193, 7;--bs-btn-active-color:#fff;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#fd7e14;--bs-btn-border-color:#fd7e14;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#fd7e14;--bs-btn-hover-border-color:#fd7e14;--bs-btn-focus-shadow-rgb:253, 126, 20;--bs-btn-active-color:#fff;--bs-btn-active-bg:#fd7e14;--bs-btn-active-border-color:#fd7e14;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fd7e14;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#fd7e14;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#e9e9e8;--bs-btn-border-color:#e9e9e8;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#e9e9e8;--bs-btn-hover-border-color:#e9e9e8;--bs-btn-focus-shadow-rgb:233, 233, 232;--bs-btn-active-color:#000;--bs-btn-active-bg:#e9e9e8;--bs-btn-active-border-color:#e9e9e8;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#e9e9e8;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#e9e9e8;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#212529;--bs-btn-hover-border-color:#212529;--bs-btn-focus-shadow-rgb:33, 37, 41;--bs-btn-active-color:#fff;--bs-btn-active-bg:#212529;--bs-btn-active-border-color:#212529;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#212529;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#212529;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:217, 217, 217;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-group-lg>.btn,.btn-lg{--bs-btn-padding-y:0.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-group-sm>.btn,.btn-sm{--bs-btn-padding-y:0.25rem;--bs-btn-padding-x:0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media(prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropdown-center,.dropend,.dropstart,.dropup,.dropup-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:0.5rem;--bs-dropdown-spacer:0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:rgba(255, 255, 255, 0.2);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:rgba(255, 255, 255, 0.2);--bs-dropdown-divider-margin-y:1rem;--bs-dropdown-box-shadow:var(--bs-box-shadow);--bs-dropdown-link-color:var(--bs-body-color);--bs-dropdown-link-hover-color:#fff;--bs-dropdown-link-hover-bg:rgba(0, 0, 0, 0.4);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#e83283;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:2rem;--bs-dropdown-item-padding-y:0.5rem;--bs-dropdown-header-color:#6c757d;--bs-dropdown-header-padding-x:2rem;--bs-dropdown-header-padding-y:0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y)var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width)solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y)0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y)var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;white-space:nowrap;background-color:initial;border:0;border-radius:var(--bs-dropdown-item-border-radius,0)}.dropdown-item:focus,.dropdown-item:hover{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:initial}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y)var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y)var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:rgba(255, 255, 255, 0.2);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:rgba(255, 255, 255, 0.2);--bs-dropdown-link-hover-bg:rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#e83283;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>.btn-group:not(:first-child),.btn-group>:not(.btn-check:first-child)+.btn{margin-left:calc(-1 * var(--bs-border-width))}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:1.125rem;padding-left:1.125rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:calc(-1 * var(--bs-border-width))}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:nth-child(n+3),.btn-group-vertical>:not(.btn-check)+.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:rgba(255, 255, 255, 0.7);display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y)var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;background:0 0;border:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(232,50,131,.25)}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:0;--bs-nav-tabs-border-color:var(--bs-border-color);--bs-nav-tabs-border-radius:0;--bs-nav-tabs-link-hover-border-color:var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color:#343a40;--bs-nav-tabs-link-active-bg:#fff;--bs-nav-tabs-link-active-border-color:#dee2e6 #dee2e6 #fff;border-bottom:var(--bs-nav-tabs-border-width)solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width)solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius:50rem;--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#e83283}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:0.125rem;--bs-nav-underline-link-active-color:#fff;gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width)solid transparent}.nav-underline .nav-link:focus,.nav-underline .nav-link:hover{border-bottom-color:initial}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:initial}.nav-fill .nav-item,.nav-fill>.nav-link{flex:auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-grow:1;flex-basis:0;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:1rem;--bs-navbar-color:rgba(var(--bs-emphasis-color-rgb), 0.65);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb), 0.8);--bs-navbar-disabled-color:rgba(var(--bs-emphasis-color-rgb), 0.3);--bs-navbar-active-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y:0.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x:0.5rem;--bs-navbar-toggler-padding-y:0.25rem;--bs-navbar-toggler-padding-x:0.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb), 0.15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:0.25rem;--bs-navbar-toggler-transition:box-shadow 0.15s ease-in-out;position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:var(--bs-navbar-padding-y)var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:focus,.navbar-text a:hover{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-grow:1;flex-basis:100%;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y)var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:initial;border:var(--bs-border-width)solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition)}@media(prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:50%;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media(min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:initial!important;border:0!important;transform:none!important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media(min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:initial!important;border:0!important;transform:none!important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media(min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:initial!important;border:0!important;transform:none!important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media(min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:initial!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media(min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:initial!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:initial!important;border:0!important;transform:none!important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color:rgba(255, 255, 255, 0.55);--bs-navbar-hover-color:rgba(255, 255, 255, 0.75);--bs-navbar-disabled-color:rgba(255, 255, 255, 0.25);--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:rgba(255, 255, 255, 0.1);--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1.75rem;--bs-card-spacer-x:2rem;--bs-card-title-spacer-y:1rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:var(--bs-border-width);--bs-card-border-color:rgba(255, 255, 255, 0.2);--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-card-cap-padding-y:0.875rem;--bs-card-cap-padding-x:2rem;--bs-card-cap-bg:transparent;--bs-card-cap-color:#fff;--bs-card-height: ;--bs-card-color:#fff;--bs-card-bg:transparent;--bs-card-img-overlay-padding:2rem;--bs-card-group-margin:0.75rem;position:relative;display:flex;flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width)solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:auto;padding:var(--bs-card-spacer-y)var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y)var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width)solid var(--bs-card-border-color)}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius)var(--bs-card-inner-border-radius)0 0}.card-footer{padding:var(--bs-card-cap-padding-y)var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width)solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius)var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media(min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child)>.card-header,.card-group>.card:not(:last-child)>.card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child)>.card-footer,.card-group>.card:not(:last-child)>.card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child)>.card-header,.card-group>.card:not(:first-child)>.card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child)>.card-footer,.card-group>.card:not(:first-child)>.card-img-bottom{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:rgba(255, 255, 255, 0.4);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform 0.2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%235d1434' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-focus-box-shadow:0 0 0 0.25rem rgba(232, 50, 131, 0.25);--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:#fff;--bs-accordion-active-bg:#e83283}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y)var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;border-radius:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media(prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width))0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media(prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width)solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type>.accordion-header .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type>.accordion-header .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type>.accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y)var(--bs-accordion-body-padding-x)}.accordion-flush>.accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush>.accordion-item:first-child{border-top:0}.accordion-flush>.accordion-item:last-child{border-bottom:0}.accordion-flush>.accordion-item>.accordion-collapse,.accordion-flush>.accordion-item>.accordion-header .accordion-button,.accordion-flush>.accordion-item>.accordion-header .accordion-button.collapsed{border-radius:0}[data-bs-theme=dark] .accordion-button::after{--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f184b5'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f184b5'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:#fff;--bs-breadcrumb-item-padding-x:0.5rem;--bs-breadcrumb-item-active-color:#fff;display:flex;flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y)var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider,"/")}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:0.75rem;--bs-pagination-padding-y:0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:rgba(255, 255, 255, 0.3);--bs-pagination-border-width:0;--bs-pagination-border-color:var(--bs-border-color);--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:#fff;--bs-pagination-hover-bg:rgba(255, 255, 255, 0.4);--bs-pagination-hover-border-color:var(--bs-border-color);--bs-pagination-focus-color:#fff;--bs-pagination-focus-bg:rgba(255, 255, 255, 0.4);--bs-pagination-focus-box-shadow:none;--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#e83283;--bs-pagination-active-border-color:#e83283;--bs-pagination-disabled-color:rgba(255, 255, 255, 0.7);--bs-pagination-disabled-bg:rgba(255, 255, 255, 0.3);--bs-pagination-disabled-border-color:var(--bs-border-color);display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y)var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width)solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.active>.page-link,.page-link.active{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.disabled>.page-link,.page-link.disabled{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(-1 * 0)}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:0.5rem;--bs-pagination-padding-y:0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:0.65em;--bs-badge-padding-y:0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);display:inline-block;padding:var(--bs-badge-padding-y)var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius)}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:2rem;--bs-alert-padding-y:2rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;position:relative;padding:var(--bs-alert-padding-y)var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:6rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:2.5rem 2rem}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:var(--bs-progress-height)}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:0.75rem;--bs-progress-bg:rgba(0, 0, 0, 0.2);--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#e83283;--bs-progress-bar-transition:width 0.6s ease;display:flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius)}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media(prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:var(--bs-progress-height)var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:#fff;--bs-list-group-bg:transparent;--bs-list-group-border-color:rgba(255, 255, 255, 0.2);--bs-list-group-border-width:0;--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:2rem;--bs-list-group-item-padding-y:1rem;--bs-list-group-action-color:#fff;--bs-list-group-action-hover-color:#fff;--bs-list-group-action-hover-bg:rgba(255, 255, 255, 0.4);--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:rgba(255, 255, 255, 0.7);--bs-list-group-disabled-bg:transparent;--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#e83283;--bs-list-group-active-border-color:#e83283;display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:var(--bs-list-group-border-radius)}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section,".")". ";counter-increment:section}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y)var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width)solid var(--bs-list-group-border-color)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:not(.active):focus,.list-group-item-action:not(.active):hover{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:not(.active):active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media(min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:#fff;--bs-btn-close-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414'/%3e%3c/svg%3e");--bs-btn-close-opacity:0.5;--bs-btn-close-hover-opacity:0.75;--bs-btn-close-focus-shadow:0 0 0 0.25rem rgba(232, 50, 131, 0.25);--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:0.25;box-sizing:content-box;width:1em;height:1em;padding:.25em;color:var(--bs-btn-close-color);background:var(--bs-btn-close-bg)50%/1em no-repeat;filter:var(--bs-btn-close-filter);border:0;border-radius:.5rem;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}:root,[data-bs-theme=light]{--bs-btn-close-filter: }[data-bs-theme=dark]{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:0.75rem;--bs-toast-padding-y:0.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-border-width:var(--bs-border-width);--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:#fff;--bs-toast-header-bg:transparent;--bs-toast-header-border-color:rgba(255, 255, 255, 0.2);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width)solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;position:absolute;z-index:var(--bs-toast-zindex);width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;align-items:center;padding:var(--bs-toast-padding-y)var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width)solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width))}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:2rem;--bs-modal-margin:0.5rem;--bs-modal-color:var(--bs-body-color);--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:var(--bs-box-shadow-sm);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x:2rem;--bs-modal-header-padding-y:2rem;--bs-modal-header-padding:2rem 2rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:var(--bs-border-width);position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transform:translate(0,-50px);transition:transform .3s ease-out}@media(prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - var(--bs-modal-margin) * 2)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width)solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);outline:0}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;flex-shrink:0;align-items:center;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width)solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5)calc(var(--bs-modal-header-padding-x) * .5);margin-top:calc(-.5 * var(--bs-modal-header-padding-y));margin-right:calc(-.5 * var(--bs-modal-header-padding-x));margin-bottom:calc(-.5 * var(--bs-modal-header-padding-y));margin-left:auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width)solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media(min-width:576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:var(--bs-box-shadow)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width:300px}}@media(min-width:992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media(min-width:1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-footer,.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media(max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-footer,.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media(max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-footer,.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media(max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-footer,.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media(max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-footer,.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media(max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-footer,.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:1rem;--bs-tooltip-padding-y:0.5rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:0.7;--bs-tooltip-arrow-width:0.8rem;--bs-tooltip-arrow-height:0.4rem;z-index:var(--bs-tooltip-zindex);display:block;margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height)calc(var(--bs-tooltip-arrow-width) * .5)0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5)var(--bs-tooltip-arrow-height)calc(var(--bs-tooltip-arrow-width) * .5)0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width) * .5)var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5)0 calc(var(--bs-tooltip-arrow-width) * .5)var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y)var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:0.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:var(--bs-box-shadow);--bs-popover-header-padding-x:2rem;--bs-popover-header-padding-y:0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color:inherit;--bs-popover-header-bg:transparent;--bs-popover-body-padding-x:2rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:#fff;--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:0.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width)solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid;border-width:0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-top>.popover-arrow::before{border-width:var(--bs-popover-arrow-height)calc(var(--bs-popover-arrow-width) * .5)0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-end>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5)var(--bs-popover-arrow-height)calc(var(--bs-popover-arrow-width) * .5)0}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::before{border-width:0 calc(var(--bs-popover-arrow-width) * .5)var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width)solid var(--bs-popover-header-bg)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-start>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5)0 calc(var(--bs-popover-arrow-width) * .5)var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y)var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width)solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y)var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;filter:var(--bs-carousel-control-icon-filter);border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:initial;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:var(--bs-carousel-indicator-active-bg);background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:var(--bs-carousel-caption-color);text-align:center}.carousel-dark{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}:root,[data-bs-theme=light]{--bs-carousel-indicator-active-bg:#fff;--bs-carousel-caption-color:#fff;--bs-carousel-control-icon-filter: }[data-bs-theme=dark]{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}.spinner-border,.spinner-grow{display:inline-block;flex-shrink:0;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed)linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-border-width:0.25em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width)solid;border-right-color:transparent}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media(prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:2rem;--bs-offcanvas-padding-y:2rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:var(--bs-box-shadow-sm);--bs-offcanvas-transition:transform 0.3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media(max-width:575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width:575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media(max-width:575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.show:not(.hiding),.offcanvas-sm.showing{transform:none}.offcanvas-sm.hiding,.offcanvas-sm.show,.offcanvas-sm.showing{visibility:visible}}@media(min-width:576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:initial!important}}@media(max-width:767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width:767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media(max-width:767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.show:not(.hiding),.offcanvas-md.showing{transform:none}.offcanvas-md.hiding,.offcanvas-md.show,.offcanvas-md.showing{visibility:visible}}@media(min-width:768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:initial!important}}@media(max-width:991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width:991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media(max-width:991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.show:not(.hiding),.offcanvas-lg.showing{transform:none}.offcanvas-lg.hiding,.offcanvas-lg.show,.offcanvas-lg.showing{visibility:visible}}@media(min-width:992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:initial!important}}@media(max-width:1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width:1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media(max-width:1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.show:not(.hiding),.offcanvas-xl.showing{transform:none}.offcanvas-xl.hiding,.offcanvas-xl.show,.offcanvas-xl.showing{visibility:visible}}@media(min-width:1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:initial!important}}@media(max-width:1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width:1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media(max-width:1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.show:not(.hiding),.offcanvas-xxl.showing{transform:none}.offcanvas-xxl.hiding,.offcanvas-xxl.show,.offcanvas-xxl.showing{visibility:visible}}@media(min-width:1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:initial!important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media(prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.show:not(.hiding),.offcanvas.showing{transform:none}.offcanvas.hiding,.offcanvas.show,.offcanvas.showing{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;padding:var(--bs-offcanvas-padding-y)var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5)calc(var(--bs-offcanvas-padding-x) * .5);margin-top:calc(-.5 * var(--bs-offcanvas-padding-y));margin-right:calc(-.5 * var(--bs-offcanvas-padding-x));margin-bottom:calc(-.5 * var(--bs-offcanvas-padding-y));margin-left:auto}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;padding:var(--bs-offcanvas-padding-y)var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-primary{color:#fff!important;background-color:RGBA(var(--bs-primary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#000!important;background-color:RGBA(var(--bs-secondary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(var(--bs-success-rgb),var(--bs-bg-opacity,1))!important}.text-bg-info{color:#fff!important;background-color:RGBA(var(--bs-info-rgb),var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#fff!important;background-color:RGBA(var(--bs-warning-rgb),var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(var(--bs-danger-rgb),var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(var(--bs-light-rgb),var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(var(--bs-dark-rgb),var(--bs-bg-opacity,1))!important}.link-primary{color:RGBA(var(--bs-primary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important}.link-primary:focus,.link-primary:hover{color:RGBA(186,40,105,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(186,40,105,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(186,40,105,var(--bs-link-underline-opacity,1))!important}.link-secondary{color:RGBA(var(--bs-secondary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important}.link-secondary:focus,.link-secondary:hover{color:RGBA(255,255,255,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(255,255,255,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(255,255,255,var(--bs-link-underline-opacity,1))!important}.link-success{color:RGBA(var(--bs-success-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important}.link-success:focus,.link-success:hover{color:RGBA(52,172,134,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(52,172,134,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(52,172,134,var(--bs-link-underline-opacity,1))!important}.link-info{color:RGBA(var(--bs-info-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important}.link-info:focus,.link-info:hover{color:RGBA(46,162,201,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(46,162,201,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(46,162,201,var(--bs-link-underline-opacity,1))!important}.link-warning{color:RGBA(var(--bs-warning-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important}.link-warning:focus,.link-warning:hover{color:RGBA(204,154,6,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(204,154,6,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(204,154,6,var(--bs-link-underline-opacity,1))!important}.link-danger{color:RGBA(var(--bs-danger-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important}.link-danger:focus,.link-danger:hover{color:RGBA(202,101,16,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(202,101,16,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(202,101,16,var(--bs-link-underline-opacity,1))!important}.link-light{color:RGBA(var(--bs-light-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important}.link-light:focus,.link-light:hover{color:RGBA(237,237,237,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(237,237,237,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(237,237,237,var(--bs-link-underline-opacity,1))!important}.link-dark{color:RGBA(var(--bs-dark-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important}.link-dark:focus,.link-dark:hover{color:RGBA(26,30,33,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-body-emphasis:focus,.link-body-emphasis:hover{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,.75))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,.75))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,.75))!important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x,0)var(--bs-focus-ring-y,0)var(--bs-focus-ring-blur,0)var(--bs-focus-ring-width)var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,.5));text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,.5));text-underline-offset:.25em;-webkit-backface-visibility:hidden;backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:.2s ease-in-out transform}@media(prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:focus-visible>.bi,.icon-link-hover:hover>.bi{transform:var(--bs-icon-link-transform,translate3d(.25em,0,0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}@media(min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media(min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media(min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media(min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media(min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption),.visually-hidden:not(caption){position:absolute!important}.visually-hidden *,.visually-hidden-focusable:not(:focus):not(:focus-within) *{overflow:hidden!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:var(--bs-border-width);min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-none{-o-object-fit:none!important;object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:var(--bs-box-shadow)!important}.shadow-sm{box-shadow:var(--bs-box-shadow-sm)!important}.shadow-lg{box-shadow:var(--bs-box-shadow-lg)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width)var(--bs-border-style)var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width)var(--bs-border-style)var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width)var(--bs-border-style)var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width)var(--bs-border-style)var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width)var(--bs-border-style)var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb),var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb),var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb),var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb),var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb),var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb),var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb),var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb),var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb),var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb),var(--bs-border-opacity))!important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:0.1}.border-opacity-25{--bs-border-opacity:0.25}.border-opacity-50{--bs-border-opacity:0.5}.border-opacity-75{--bs-border-opacity:0.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.5rem!important}.m-2{margin:1rem!important}.m-3{margin:2rem!important}.m-4{margin:3rem!important}.m-5{margin:6rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.5rem!important;margin-left:.5rem!important}.mx-2{margin-right:1rem!important;margin-left:1rem!important}.mx-3{margin-right:2rem!important;margin-left:2rem!important}.mx-4{margin-right:3rem!important;margin-left:3rem!important}.mx-5{margin-right:6rem!important;margin-left:6rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-2{margin-top:1rem!important;margin-bottom:1rem!important}.my-3{margin-top:2rem!important;margin-bottom:2rem!important}.my-4{margin-top:3rem!important;margin-bottom:3rem!important}.my-5{margin-top:6rem!important;margin-bottom:6rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.5rem!important}.mt-2{margin-top:1rem!important}.mt-3{margin-top:2rem!important}.mt-4{margin-top:3rem!important}.mt-5{margin-top:6rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.5rem!important}.me-2{margin-right:1rem!important}.me-3{margin-right:2rem!important}.me-4{margin-right:3rem!important}.me-5{margin-right:6rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.5rem!important}.mb-2{margin-bottom:1rem!important}.mb-3{margin-bottom:2rem!important}.mb-4{margin-bottom:3rem!important}.mb-5{margin-bottom:6rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.5rem!important}.ms-2{margin-left:1rem!important}.ms-3{margin-left:2rem!important}.ms-4{margin-left:3rem!important}.ms-5{margin-left:6rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.5rem!important}.p-2{padding:1rem!important}.p-3{padding:2rem!important}.p-4{padding:3rem!important}.p-5{padding:6rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.5rem!important;padding-left:.5rem!important}.px-2{padding-right:1rem!important;padding-left:1rem!important}.px-3{padding-right:2rem!important;padding-left:2rem!important}.px-4{padding-right:3rem!important;padding-left:3rem!important}.px-5{padding-right:6rem!important;padding-left:6rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-2{padding-top:1rem!important;padding-bottom:1rem!important}.py-3{padding-top:2rem!important;padding-bottom:2rem!important}.py-4{padding-top:3rem!important;padding-bottom:3rem!important}.py-5{padding-top:6rem!important;padding-bottom:6rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.5rem!important}.pt-2{padding-top:1rem!important}.pt-3{padding-top:2rem!important}.pt-4{padding-top:3rem!important}.pt-5{padding-top:6rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.5rem!important}.pe-2{padding-right:1rem!important}.pe-3{padding-right:2rem!important}.pe-4{padding-right:3rem!important}.pe-5{padding-right:6rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.5rem!important}.pb-2{padding-bottom:1rem!important}.pb-3{padding-bottom:2rem!important}.pb-4{padding-bottom:3rem!important}.pb-5{padding-bottom:6rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.5rem!important}.ps-2{padding-left:1rem!important}.ps-3{padding-left:2rem!important}.ps-4{padding-left:3rem!important}.ps-5{padding-left:6rem!important}.gap-0{gap:0!important}.gap-1{gap:.5rem!important}.gap-2{gap:1rem!important}.gap-3{gap:2rem!important}.gap-4{gap:3rem!important}.gap-5{gap:6rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.5rem!important}.row-gap-2{row-gap:1rem!important}.row-gap-3{row-gap:2rem!important}.row-gap-4{row-gap:3rem!important}.row-gap-5{row-gap:6rem!important}.column-gap-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-1{-moz-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-2{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-3{-moz-column-gap:2rem!important;column-gap:2rem!important}.column-gap-4{-moz-column-gap:3rem!important;column-gap:3rem!important}.column-gap-5{-moz-column-gap:6rem!important;column-gap:6rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-body-secondary{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis)!important}.link-opacity-10{--bs-link-opacity:0.1}.link-opacity-10-hover:hover{--bs-link-opacity:0.1}.link-opacity-25{--bs-link-opacity:0.25}.link-opacity-25-hover:hover{--bs-link-opacity:0.25}.link-opacity-50{--bs-link-opacity:0.5}.link-opacity-50-hover:hover{--bs-link-opacity:0.5}.link-opacity-75{--bs-link-opacity:0.75}.link-opacity-75-hover:hover{--bs-link-opacity:0.75}.link-opacity-100{--bs-link-opacity:1}.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1{text-underline-offset:.125em!important}.link-offset-1-hover:hover{text-underline-offset:.125em!important}.link-offset-2{text-underline-offset:.25em!important}.link-offset-2-hover:hover{text-underline-offset:.25em!important}.link-offset-3{text-underline-offset:.375em!important}.link-offset-3-hover:hover{text-underline-offset:.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0{--bs-link-underline-opacity:0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10{--bs-link-underline-opacity:0.1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:0.1}.link-underline-opacity-25{--bs-link-underline-opacity:0.25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:0.25}.link-underline-opacity-50{--bs-link-underline-opacity:0.5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:0.5}.link-underline-opacity-75{--bs-link-underline-opacity:0.75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:0.75}.link-underline-opacity-100{--bs-link-underline-opacity:1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:initial!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-bg-rgb),var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(var(--bs-tertiary-bg-rgb),var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-top-right-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-right-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-bottom-left-radius:0!important;border-top-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-bottom-left-radius:50%!important;border-top-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media(min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-sm-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-sm-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-sm-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-sm-none{-o-object-fit:none!important;object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.5rem!important}.m-sm-2{margin:1rem!important}.m-sm-3{margin:2rem!important}.m-sm-4{margin:3rem!important}.m-sm-5{margin:6rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-2{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-3{margin-right:2rem!important;margin-left:2rem!important}.mx-sm-4{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-5{margin-right:6rem!important;margin-left:6rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-2{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-3{margin-top:2rem!important;margin-bottom:2rem!important}.my-sm-4{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-5{margin-top:6rem!important;margin-bottom:6rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.5rem!important}.mt-sm-2{margin-top:1rem!important}.mt-sm-3{margin-top:2rem!important}.mt-sm-4{margin-top:3rem!important}.mt-sm-5{margin-top:6rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.5rem!important}.me-sm-2{margin-right:1rem!important}.me-sm-3{margin-right:2rem!important}.me-sm-4{margin-right:3rem!important}.me-sm-5{margin-right:6rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.5rem!important}.mb-sm-2{margin-bottom:1rem!important}.mb-sm-3{margin-bottom:2rem!important}.mb-sm-4{margin-bottom:3rem!important}.mb-sm-5{margin-bottom:6rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.5rem!important}.ms-sm-2{margin-left:1rem!important}.ms-sm-3{margin-left:2rem!important}.ms-sm-4{margin-left:3rem!important}.ms-sm-5{margin-left:6rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.5rem!important}.p-sm-2{padding:1rem!important}.p-sm-3{padding:2rem!important}.p-sm-4{padding:3rem!important}.p-sm-5{padding:6rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-2{padding-right:1rem!important;padding-left:1rem!important}.px-sm-3{padding-right:2rem!important;padding-left:2rem!important}.px-sm-4{padding-right:3rem!important;padding-left:3rem!important}.px-sm-5{padding-right:6rem!important;padding-left:6rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-2{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-3{padding-top:2rem!important;padding-bottom:2rem!important}.py-sm-4{padding-top:3rem!important;padding-bottom:3rem!important}.py-sm-5{padding-top:6rem!important;padding-bottom:6rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.5rem!important}.pt-sm-2{padding-top:1rem!important}.pt-sm-3{padding-top:2rem!important}.pt-sm-4{padding-top:3rem!important}.pt-sm-5{padding-top:6rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.5rem!important}.pe-sm-2{padding-right:1rem!important}.pe-sm-3{padding-right:2rem!important}.pe-sm-4{padding-right:3rem!important}.pe-sm-5{padding-right:6rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.5rem!important}.pb-sm-2{padding-bottom:1rem!important}.pb-sm-3{padding-bottom:2rem!important}.pb-sm-4{padding-bottom:3rem!important}.pb-sm-5{padding-bottom:6rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.5rem!important}.ps-sm-2{padding-left:1rem!important}.ps-sm-3{padding-left:2rem!important}.ps-sm-4{padding-left:3rem!important}.ps-sm-5{padding-left:6rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.5rem!important}.gap-sm-2{gap:1rem!important}.gap-sm-3{gap:2rem!important}.gap-sm-4{gap:3rem!important}.gap-sm-5{gap:6rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.5rem!important}.row-gap-sm-2{row-gap:1rem!important}.row-gap-sm-3{row-gap:2rem!important}.row-gap-sm-4{row-gap:3rem!important}.row-gap-sm-5{row-gap:6rem!important}.column-gap-sm-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-sm-1{-moz-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-sm-2{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-sm-3{-moz-column-gap:2rem!important;column-gap:2rem!important}.column-gap-sm-4{-moz-column-gap:3rem!important;column-gap:3rem!important}.column-gap-sm-5{-moz-column-gap:6rem!important;column-gap:6rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media(min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-md-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-md-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-md-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-md-none{-o-object-fit:none!important;object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.5rem!important}.m-md-2{margin:1rem!important}.m-md-3{margin:2rem!important}.m-md-4{margin:3rem!important}.m-md-5{margin:6rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-2{margin-right:1rem!important;margin-left:1rem!important}.mx-md-3{margin-right:2rem!important;margin-left:2rem!important}.mx-md-4{margin-right:3rem!important;margin-left:3rem!important}.mx-md-5{margin-right:6rem!important;margin-left:6rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-2{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-3{margin-top:2rem!important;margin-bottom:2rem!important}.my-md-4{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-5{margin-top:6rem!important;margin-bottom:6rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.5rem!important}.mt-md-2{margin-top:1rem!important}.mt-md-3{margin-top:2rem!important}.mt-md-4{margin-top:3rem!important}.mt-md-5{margin-top:6rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.5rem!important}.me-md-2{margin-right:1rem!important}.me-md-3{margin-right:2rem!important}.me-md-4{margin-right:3rem!important}.me-md-5{margin-right:6rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.5rem!important}.mb-md-2{margin-bottom:1rem!important}.mb-md-3{margin-bottom:2rem!important}.mb-md-4{margin-bottom:3rem!important}.mb-md-5{margin-bottom:6rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.5rem!important}.ms-md-2{margin-left:1rem!important}.ms-md-3{margin-left:2rem!important}.ms-md-4{margin-left:3rem!important}.ms-md-5{margin-left:6rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.5rem!important}.p-md-2{padding:1rem!important}.p-md-3{padding:2rem!important}.p-md-4{padding:3rem!important}.p-md-5{padding:6rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-2{padding-right:1rem!important;padding-left:1rem!important}.px-md-3{padding-right:2rem!important;padding-left:2rem!important}.px-md-4{padding-right:3rem!important;padding-left:3rem!important}.px-md-5{padding-right:6rem!important;padding-left:6rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-2{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-3{padding-top:2rem!important;padding-bottom:2rem!important}.py-md-4{padding-top:3rem!important;padding-bottom:3rem!important}.py-md-5{padding-top:6rem!important;padding-bottom:6rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.5rem!important}.pt-md-2{padding-top:1rem!important}.pt-md-3{padding-top:2rem!important}.pt-md-4{padding-top:3rem!important}.pt-md-5{padding-top:6rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.5rem!important}.pe-md-2{padding-right:1rem!important}.pe-md-3{padding-right:2rem!important}.pe-md-4{padding-right:3rem!important}.pe-md-5{padding-right:6rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.5rem!important}.pb-md-2{padding-bottom:1rem!important}.pb-md-3{padding-bottom:2rem!important}.pb-md-4{padding-bottom:3rem!important}.pb-md-5{padding-bottom:6rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.5rem!important}.ps-md-2{padding-left:1rem!important}.ps-md-3{padding-left:2rem!important}.ps-md-4{padding-left:3rem!important}.ps-md-5{padding-left:6rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.5rem!important}.gap-md-2{gap:1rem!important}.gap-md-3{gap:2rem!important}.gap-md-4{gap:3rem!important}.gap-md-5{gap:6rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.5rem!important}.row-gap-md-2{row-gap:1rem!important}.row-gap-md-3{row-gap:2rem!important}.row-gap-md-4{row-gap:3rem!important}.row-gap-md-5{row-gap:6rem!important}.column-gap-md-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-md-1{-moz-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-md-2{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-md-3{-moz-column-gap:2rem!important;column-gap:2rem!important}.column-gap-md-4{-moz-column-gap:3rem!important;column-gap:3rem!important}.column-gap-md-5{-moz-column-gap:6rem!important;column-gap:6rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media(min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-lg-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-lg-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-lg-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-lg-none{-o-object-fit:none!important;object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.5rem!important}.m-lg-2{margin:1rem!important}.m-lg-3{margin:2rem!important}.m-lg-4{margin:3rem!important}.m-lg-5{margin:6rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-2{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-3{margin-right:2rem!important;margin-left:2rem!important}.mx-lg-4{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-5{margin-right:6rem!important;margin-left:6rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-2{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-3{margin-top:2rem!important;margin-bottom:2rem!important}.my-lg-4{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-5{margin-top:6rem!important;margin-bottom:6rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.5rem!important}.mt-lg-2{margin-top:1rem!important}.mt-lg-3{margin-top:2rem!important}.mt-lg-4{margin-top:3rem!important}.mt-lg-5{margin-top:6rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.5rem!important}.me-lg-2{margin-right:1rem!important}.me-lg-3{margin-right:2rem!important}.me-lg-4{margin-right:3rem!important}.me-lg-5{margin-right:6rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.5rem!important}.mb-lg-2{margin-bottom:1rem!important}.mb-lg-3{margin-bottom:2rem!important}.mb-lg-4{margin-bottom:3rem!important}.mb-lg-5{margin-bottom:6rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.5rem!important}.ms-lg-2{margin-left:1rem!important}.ms-lg-3{margin-left:2rem!important}.ms-lg-4{margin-left:3rem!important}.ms-lg-5{margin-left:6rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.5rem!important}.p-lg-2{padding:1rem!important}.p-lg-3{padding:2rem!important}.p-lg-4{padding:3rem!important}.p-lg-5{padding:6rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-2{padding-right:1rem!important;padding-left:1rem!important}.px-lg-3{padding-right:2rem!important;padding-left:2rem!important}.px-lg-4{padding-right:3rem!important;padding-left:3rem!important}.px-lg-5{padding-right:6rem!important;padding-left:6rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-2{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-3{padding-top:2rem!important;padding-bottom:2rem!important}.py-lg-4{padding-top:3rem!important;padding-bottom:3rem!important}.py-lg-5{padding-top:6rem!important;padding-bottom:6rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.5rem!important}.pt-lg-2{padding-top:1rem!important}.pt-lg-3{padding-top:2rem!important}.pt-lg-4{padding-top:3rem!important}.pt-lg-5{padding-top:6rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.5rem!important}.pe-lg-2{padding-right:1rem!important}.pe-lg-3{padding-right:2rem!important}.pe-lg-4{padding-right:3rem!important}.pe-lg-5{padding-right:6rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.5rem!important}.pb-lg-2{padding-bottom:1rem!important}.pb-lg-3{padding-bottom:2rem!important}.pb-lg-4{padding-bottom:3rem!important}.pb-lg-5{padding-bottom:6rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.5rem!important}.ps-lg-2{padding-left:1rem!important}.ps-lg-3{padding-left:2rem!important}.ps-lg-4{padding-left:3rem!important}.ps-lg-5{padding-left:6rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.5rem!important}.gap-lg-2{gap:1rem!important}.gap-lg-3{gap:2rem!important}.gap-lg-4{gap:3rem!important}.gap-lg-5{gap:6rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.5rem!important}.row-gap-lg-2{row-gap:1rem!important}.row-gap-lg-3{row-gap:2rem!important}.row-gap-lg-4{row-gap:3rem!important}.row-gap-lg-5{row-gap:6rem!important}.column-gap-lg-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-lg-1{-moz-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-lg-2{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-lg-3{-moz-column-gap:2rem!important;column-gap:2rem!important}.column-gap-lg-4{-moz-column-gap:3rem!important;column-gap:3rem!important}.column-gap-lg-5{-moz-column-gap:6rem!important;column-gap:6rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media(min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xl-none{-o-object-fit:none!important;object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.5rem!important}.m-xl-2{margin:1rem!important}.m-xl-3{margin:2rem!important}.m-xl-4{margin:3rem!important}.m-xl-5{margin:6rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-2{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-3{margin-right:2rem!important;margin-left:2rem!important}.mx-xl-4{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-5{margin-right:6rem!important;margin-left:6rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-2{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-3{margin-top:2rem!important;margin-bottom:2rem!important}.my-xl-4{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-5{margin-top:6rem!important;margin-bottom:6rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.5rem!important}.mt-xl-2{margin-top:1rem!important}.mt-xl-3{margin-top:2rem!important}.mt-xl-4{margin-top:3rem!important}.mt-xl-5{margin-top:6rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.5rem!important}.me-xl-2{margin-right:1rem!important}.me-xl-3{margin-right:2rem!important}.me-xl-4{margin-right:3rem!important}.me-xl-5{margin-right:6rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.5rem!important}.mb-xl-2{margin-bottom:1rem!important}.mb-xl-3{margin-bottom:2rem!important}.mb-xl-4{margin-bottom:3rem!important}.mb-xl-5{margin-bottom:6rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.5rem!important}.ms-xl-2{margin-left:1rem!important}.ms-xl-3{margin-left:2rem!important}.ms-xl-4{margin-left:3rem!important}.ms-xl-5{margin-left:6rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.5rem!important}.p-xl-2{padding:1rem!important}.p-xl-3{padding:2rem!important}.p-xl-4{padding:3rem!important}.p-xl-5{padding:6rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-2{padding-right:1rem!important;padding-left:1rem!important}.px-xl-3{padding-right:2rem!important;padding-left:2rem!important}.px-xl-4{padding-right:3rem!important;padding-left:3rem!important}.px-xl-5{padding-right:6rem!important;padding-left:6rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-2{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-3{padding-top:2rem!important;padding-bottom:2rem!important}.py-xl-4{padding-top:3rem!important;padding-bottom:3rem!important}.py-xl-5{padding-top:6rem!important;padding-bottom:6rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.5rem!important}.pt-xl-2{padding-top:1rem!important}.pt-xl-3{padding-top:2rem!important}.pt-xl-4{padding-top:3rem!important}.pt-xl-5{padding-top:6rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.5rem!important}.pe-xl-2{padding-right:1rem!important}.pe-xl-3{padding-right:2rem!important}.pe-xl-4{padding-right:3rem!important}.pe-xl-5{padding-right:6rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.5rem!important}.pb-xl-2{padding-bottom:1rem!important}.pb-xl-3{padding-bottom:2rem!important}.pb-xl-4{padding-bottom:3rem!important}.pb-xl-5{padding-bottom:6rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.5rem!important}.ps-xl-2{padding-left:1rem!important}.ps-xl-3{padding-left:2rem!important}.ps-xl-4{padding-left:3rem!important}.ps-xl-5{padding-left:6rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.5rem!important}.gap-xl-2{gap:1rem!important}.gap-xl-3{gap:2rem!important}.gap-xl-4{gap:3rem!important}.gap-xl-5{gap:6rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.5rem!important}.row-gap-xl-2{row-gap:1rem!important}.row-gap-xl-3{row-gap:2rem!important}.row-gap-xl-4{row-gap:3rem!important}.row-gap-xl-5{row-gap:6rem!important}.column-gap-xl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xl-1{-moz-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-xl-2{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xl-3{-moz-column-gap:2rem!important;column-gap:2rem!important}.column-gap-xl-4{-moz-column-gap:3rem!important;column-gap:3rem!important}.column-gap-xl-5{-moz-column-gap:6rem!important;column-gap:6rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media(min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xxl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xxl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xxl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xxl-none{-o-object-fit:none!important;object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.5rem!important}.m-xxl-2{margin:1rem!important}.m-xxl-3{margin:2rem!important}.m-xxl-4{margin:3rem!important}.m-xxl-5{margin:6rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-2{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-3{margin-right:2rem!important;margin-left:2rem!important}.mx-xxl-4{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-5{margin-right:6rem!important;margin-left:6rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-2{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-3{margin-top:2rem!important;margin-bottom:2rem!important}.my-xxl-4{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-5{margin-top:6rem!important;margin-bottom:6rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.5rem!important}.mt-xxl-2{margin-top:1rem!important}.mt-xxl-3{margin-top:2rem!important}.mt-xxl-4{margin-top:3rem!important}.mt-xxl-5{margin-top:6rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.5rem!important}.me-xxl-2{margin-right:1rem!important}.me-xxl-3{margin-right:2rem!important}.me-xxl-4{margin-right:3rem!important}.me-xxl-5{margin-right:6rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.5rem!important}.mb-xxl-2{margin-bottom:1rem!important}.mb-xxl-3{margin-bottom:2rem!important}.mb-xxl-4{margin-bottom:3rem!important}.mb-xxl-5{margin-bottom:6rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.5rem!important}.ms-xxl-2{margin-left:1rem!important}.ms-xxl-3{margin-left:2rem!important}.ms-xxl-4{margin-left:3rem!important}.ms-xxl-5{margin-left:6rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.5rem!important}.p-xxl-2{padding:1rem!important}.p-xxl-3{padding:2rem!important}.p-xxl-4{padding:3rem!important}.p-xxl-5{padding:6rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-2{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-3{padding-right:2rem!important;padding-left:2rem!important}.px-xxl-4{padding-right:3rem!important;padding-left:3rem!important}.px-xxl-5{padding-right:6rem!important;padding-left:6rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-2{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-3{padding-top:2rem!important;padding-bottom:2rem!important}.py-xxl-4{padding-top:3rem!important;padding-bottom:3rem!important}.py-xxl-5{padding-top:6rem!important;padding-bottom:6rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.5rem!important}.pt-xxl-2{padding-top:1rem!important}.pt-xxl-3{padding-top:2rem!important}.pt-xxl-4{padding-top:3rem!important}.pt-xxl-5{padding-top:6rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.5rem!important}.pe-xxl-2{padding-right:1rem!important}.pe-xxl-3{padding-right:2rem!important}.pe-xxl-4{padding-right:3rem!important}.pe-xxl-5{padding-right:6rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.5rem!important}.pb-xxl-2{padding-bottom:1rem!important}.pb-xxl-3{padding-bottom:2rem!important}.pb-xxl-4{padding-bottom:3rem!important}.pb-xxl-5{padding-bottom:6rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.5rem!important}.ps-xxl-2{padding-left:1rem!important}.ps-xxl-3{padding-left:2rem!important}.ps-xxl-4{padding-left:3rem!important}.ps-xxl-5{padding-left:6rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.5rem!important}.gap-xxl-2{gap:1rem!important}.gap-xxl-3{gap:2rem!important}.gap-xxl-4{gap:3rem!important}.gap-xxl-5{gap:6rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.5rem!important}.row-gap-xxl-2{row-gap:1rem!important}.row-gap-xxl-3{row-gap:2rem!important}.row-gap-xxl-4{row-gap:3rem!important}.row-gap-xxl-5{row-gap:6rem!important}.column-gap-xxl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xxl-1{-moz-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-xxl-2{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xxl-3{-moz-column-gap:2rem!important;column-gap:2rem!important}.column-gap-xxl-4{-moz-column-gap:3rem!important;column-gap:3rem!important}.column-gap-xxl-5{-moz-column-gap:6rem!important;column-gap:6rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media(min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}body{background-image:linear-gradient(90deg,#21373f,#5e62b0,#700936)}[data-bs-theme=dark] body{color:#fff}.table-secondary{--bs-table-hover-color:$white}.btn-secondary{color:#fff;border:none}.btn-secondary:focus,.btn-secondary:hover{color:#fff}.btn-secondary.disabled{color:#fff}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:0}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:0}.bg-light .btn{background-color:rgba(0,0,0,.2)}.form-control::-webkit-file-upload-button{background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);border:none}.form-control::file-selector-button,.input-group-text{background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);border:none}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:rgba(255,255,255,.1)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:rgba(255,255,255,.1)}.input-group-text{box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1)}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:0;border-left:none}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-right:none}.form-select:not([multiple]){transition:border-color .15s ease-in-out}.form-select:not([multiple]) option{color:#000}.bg-light .form-control{color:#343a40;border-color:rgba(0,0,0,.2)}.bg-light .form-control::-moz-placeholder{color:rgba(0,0,0,.2)}.bg-light .form-control::placeholder{color:rgba(0,0,0,.2)}.dropdown-menu{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.dropdown-menu a{color:#fff}.dropdown-menu .text-muted{color:rgba(255,255,255,.7)!important}.nav-tabs .nav-link{transition:none}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{color:#fff;isolation:auto}.nav-tabs .nav-link,.nav-tabs .nav-link.disabled{background-color:rgba(0,0,0,.2)}.nav-tabs .nav-item:first-child>.nav-link{border-radius:.5rem 0 0 .5rem}.nav-tabs .nav-item:last-child>.nav-link{border-radius:0 .5rem .5rem 0}.nav-tabs .nav-item .nav-link.active,.nav-tabs .nav-item.show .nav-link{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,#fff,rgba(255,255,255,.9) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);border-radius:.5rem;transform:scale(1.1)}.nav-tabs .nav-item .nav-link.active a,.nav-tabs .nav-item.show .nav-link a{color:#fff}.nav-tabs .nav-item .nav-link.active .text-muted,.nav-tabs .nav-item.show .nav-link .text-muted{color:rgba(255,255,255,.7)!important}.nav-tabs .nav-item .nav-link.active:focus,.nav-tabs .nav-item .nav-link.active:hover,.nav-tabs .nav-item.show .nav-link:focus,.nav-tabs .nav-item.show .nav-link:hover{color:#343a40}.nav-tabs .dropdown-menu{border-radius:.5rem}.nav-pills .nav-link{transition:none}.nav-pills .nav-link:focus,.nav-pills .nav-link:hover{color:#fff;isolation:auto}.alert{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);position:relative;overflow:hidden;color:#fff}.alert a{color:#fff}.alert .text-muted{color:rgba(255,255,255,.7)!important}.alert-primary::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#e83283}.alert-secondary::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:rgba(255,255,255,.4)}.alert-success::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#41d7a7}.alert-info::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#39cbfb}.alert-warning::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#ffc107}.alert-danger::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#fd7e14}.alert-light::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#e9e9e8}.alert-dark::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#212529}.alert .alert-link{color:#fff}.badge.bg-secondary{--bs-bg-opacity:.4}.badge.bg-light{color:#343a40}.list-group{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.list-group a{color:#fff}.list-group .text-muted{color:rgba(255,255,255,.7)!important}.card{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);--bs-secondary-rgb:transparent}.card a{color:#fff}.card .text-muted{color:rgba(255,255,255,.7)!important}.card.bg-primary{background-image:none}.card.bg-success{background-image:none}.card.bg-info{background-image:none}.card.bg-warning{background-image:none}.card.bg-danger{background-image:none}.card.bg-light{background-image:none}.card.bg-dark{background-image:none}.card.bg-light .card-body,.card.bg-light .card-header{color:#343a40}.card.border-primary{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-primary a{color:#fff}.card.border-primary .text-muted{color:rgba(255,255,255,.7)!important}.card.border-primary .card-header{background-color:#e83283!important;border-bottom:none}.card.border-secondary{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-secondary a{color:#fff}.card.border-secondary .text-muted{color:rgba(255,255,255,.7)!important}.card.border-secondary .card-header{background-color:rgba(255,255,255,.4)!important;border-bottom:none}.card.border-success{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-success a{color:#fff}.card.border-success .text-muted{color:rgba(255,255,255,.7)!important}.card.border-success .card-header{background-color:#41d7a7!important;border-bottom:none}.card.border-info{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-info a{color:#fff}.card.border-info .text-muted{color:rgba(255,255,255,.7)!important}.card.border-info .card-header{background-color:#39cbfb!important;border-bottom:none}.card.border-warning{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-warning a{color:#fff}.card.border-warning .text-muted{color:rgba(255,255,255,.7)!important}.card.border-warning .card-header{background-color:#ffc107!important;border-bottom:none}.card.border-danger{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-danger a{color:#fff}.card.border-danger .text-muted{color:rgba(255,255,255,.7)!important}.card.border-danger .card-header{background-color:#fd7e14!important;border-bottom:none}.card.border-light{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-light a{color:#fff}.card.border-light .text-muted{color:rgba(255,255,255,.7)!important}.card.border-light .card-header{background-color:#e9e9e8!important;border-bottom:none;color:#343a40}.card.border-dark{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-dark a{color:#fff}.card.border-dark .text-muted{color:rgba(255,255,255,.7)!important}.card.border-dark .card-header{background-color:#212529!important;border-bottom:none}.card-header{font-weight:700}.accordion-item{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);box-shadow:none}.accordion-item a{color:#fff}.accordion-item .text-muted{color:rgba(255,255,255,.7)!important}.toast{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.toast a{color:#fff}.toast .text-muted{color:rgba(255,255,255,.7)!important}.popover{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.popover a{color:#fff}.popover .text-muted{color:rgba(255,255,255,.7)!important}.popover-header{border-bottom-color:rgba(255,255,255,.2)}.tooltip{-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.modal-content{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.modal-content a{color:#fff}.modal-content .text-muted{color:rgba(255,255,255,.7)!important}.offcanvas{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.offcanvas a{color:#fff}.offcanvas .text-muted{color:rgba(255,255,255,.7)!important}/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file +* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)*/:root,[data-bs-theme=light]{--bs-blue:#3a8fd9;--bs-indigo:#6610f2;--bs-purple:#686dc3;--bs-pink:#e83283;--bs-red:#fc346f;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#41d7a7;--bs-teal:#528fb3;--bs-cyan:#39cbfb;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9e9e8;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#e83283;--bs-secondary:rgba(255, 255, 255, 0.4);--bs-success:#41d7a7;--bs-info:#39cbfb;--bs-warning:#ffc107;--bs-danger:#fd7e14;--bs-light:#e9e9e8;--bs-dark:#212529;--bs-primary-rgb:232, 50, 131;--bs-secondary-rgb:255, 255, 255;--bs-success-rgb:65, 215, 167;--bs-info-rgb:57, 203, 251;--bs-warning-rgb:255, 193, 7;--bs-danger-rgb:253, 126, 20;--bs-light-rgb:233, 233, 232;--bs-dark-rgb:33, 37, 41;--bs-primary-text-emphasis:#5d1434;--bs-secondary-text-emphasis:rgba(36, 36, 36, 0.76);--bs-success-text-emphasis:#1a5643;--bs-info-text-emphasis:#175164;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#653208;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#fad6e6;--bs-secondary-bg-subtle:rgba(255, 255, 255, 0.88);--bs-success-bg-subtle:#d9f7ed;--bs-info-bg-subtle:#d7f5fe;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#ffe5d0;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#f6adcd;--bs-secondary-border-subtle:rgba(255, 255, 255, 0.76);--bs-success-border-subtle:#b3efdc;--bs-info-border-subtle:#b0eafd;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#fecba1;--bs-light-border-subtle:#e9e9e8;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255, 255, 255;--bs-black-rgb:0, 0, 0;--bs-font-sans-serif:system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace:SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#fff;--bs-body-color-rgb:255, 255, 255;--bs-body-bg:#686dc3;--bs-body-bg-rgb:104, 109, 195;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0, 0, 0;--bs-secondary-color:rgba(255, 255, 255, 0.75);--bs-secondary-color-rgb:255, 255, 255;--bs-secondary-bg:#e9e9e8;--bs-secondary-bg-rgb:233, 233, 232;--bs-tertiary-color:rgba(255, 255, 255, 0.5);--bs-tertiary-color-rgb:255, 255, 255;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248, 249, 250;--bs-heading-color:inherit;--bs-link-color:#fff;--bs-link-color-rgb:255, 255, 255;--bs-link-decoration:underline;--bs-link-hover-color:#cccccc;--bs-link-hover-color-rgb:204, 204, 204;--bs-code-color:#e83283;--bs-highlight-color:#fff;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:rgba(255, 255, 255, 0.2);--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.5rem;--bs-border-radius-sm:0.6rem;--bs-border-radius-lg:0.7rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:1px 3px 24px -1px rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 1px 1px rgba(0, 0, 0, 0.1);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(232, 50, 131, 0.25);--bs-form-valid-color:#41d7a7;--bs-form-valid-border-color:#41d7a7;--bs-form-invalid-color:#fd7e14;--bs-form-invalid-border-color:#fd7e14}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222, 226, 230;--bs-body-bg:#212529;--bs-body-bg-rgb:33, 37, 41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255, 255, 255;--bs-secondary-color:rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb:222, 226, 230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52, 58, 64;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222, 226, 230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43, 48, 53;--bs-primary-text-emphasis:#f184b5;--bs-secondary-text-emphasis:rgba(255, 255, 255, 0.64);--bs-success-text-emphasis:#8de7ca;--bs-info-text-emphasis:#88e0fd;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#feb272;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#2e0a1a;--bs-secondary-bg-subtle:rgba(15, 15, 15, 0.88);--bs-success-bg-subtle:#0d2b21;--bs-info-bg-subtle:#0b2932;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#331904;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#8b1e4f;--bs-secondary-border-subtle:rgba(70, 70, 70, 0.64);--bs-success-border-subtle:#278164;--bs-info-border-subtle:#227a97;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#984c0c;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#f184b5;--bs-link-hover-color:#f49dc4;--bs-link-color-rgb:241, 132, 181;--bs-link-hover-color-rgb:244, 157, 196;--bs-code-color:#f184b5;--bs-highlight-color:#dee2e6;--bs-highlight-bg:#664d03;--bs-border-color:#495057;--bs-border-color-translucent:rgba(255, 255, 255, 0.15);--bs-form-valid-color:#8de7ca;--bs-form-valid-border-color:#8de7ca;--bs-form-invalid-color:#fd85a9;--bs-form-invalid-border-color:#fd85a9}*,::after,::before{box-sizing:border-box}@media(prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:2rem 0;color:inherit;border:0;border-top:var(--bs-border-width)solid;opacity:.25}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:1rem;font-weight:700;line-height:1.2;color:var(--bs-heading-color)}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media(min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media(min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media(min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media(min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.1875em;color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1));text-decoration:underline}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.6rem}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-secondary-color);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;line-height:inherit;font-size:calc(1.275rem + .3vw)}@media(min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button{cursor:pointer;filter:grayscale(1)}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-weight:300;line-height:1.2;font-size:calc(1.625rem + 4.5vw)}@media(min-width:1200px){.display-1{font-size:5rem}}.display-2{font-weight:300;line-height:1.2;font-size:calc(1.575rem + 3.9vw)}@media(min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-weight:300;line-height:1.2;font-size:calc(1.525rem + 3.3vw)}@media(min-width:1200px){.display-3{font-size:4rem}}.display-4{font-weight:300;line-height:1.2;font-size:calc(1.475rem + 2.7vw)}@media(min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-weight:300;line-height:1.2;font-size:calc(1.425rem + 2.1vw)}@media(min-width:1200px){.display-5{font-size:3rem}}.display-6{font-weight:300;line-height:1.2;font-size:calc(1.375rem + 1.5vw)}@media(min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:2rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-2rem;margin-bottom:2rem;font-size:.875em;color:rgba(255,255,255,.7)}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:var(--bs-body-bg);border:var(--bs-border-width)solid var(--bs-border-color);border-radius:var(--bs-border-radius);max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:1rem;line-height:1}.figure-caption{font-size:.875em;color:var(--bs-secondary-color)}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{--bs-gutter-x:1.5rem;--bs-gutter-y:0;width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-right:auto;margin-left:auto}@media(min-width:576px){.container,.container-sm{max-width:540px}}@media(min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media(min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media(min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media(min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}:root{--bs-breakpoint-xs:0;--bs-breakpoint-sm:576px;--bs-breakpoint-md:768px;--bs-breakpoint-lg:992px;--bs-breakpoint-xl:1200px;--bs-breakpoint-xxl:1400px}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0}.row-cols-auto>*{flex:none;width:auto}.row-cols-1>*{flex:none;width:100%}.row-cols-2>*{flex:none;width:50%}.row-cols-3>*{flex:none;width:33.33333333%}.row-cols-4>*{flex:none;width:25%}.row-cols-5>*{flex:none;width:20%}.row-cols-6>*{flex:none;width:16.66666667%}.col-auto{flex:none;width:auto}.col-1{flex:none;width:8.33333333%}.col-2{flex:none;width:16.66666667%}.col-3{flex:none;width:25%}.col-4{flex:none;width:33.33333333%}.col-5{flex:none;width:41.66666667%}.col-6{flex:none;width:50%}.col-7{flex:none;width:58.33333333%}.col-8{flex:none;width:66.66666667%}.col-9{flex:none;width:75%}.col-10{flex:none;width:83.33333333%}.col-11{flex:none;width:91.66666667%}.col-12{flex:none;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.5rem}.g-1,.gy-1{--bs-gutter-y:0.5rem}.g-2,.gx-2{--bs-gutter-x:1rem}.g-2,.gy-2{--bs-gutter-y:1rem}.g-3,.gx-3{--bs-gutter-x:2rem}.g-3,.gy-3{--bs-gutter-y:2rem}.g-4,.gx-4{--bs-gutter-x:3rem}.g-4,.gy-4{--bs-gutter-y:3rem}.g-5,.gx-5{--bs-gutter-x:6rem}.g-5,.gy-5{--bs-gutter-y:6rem}@media(min-width:576px){.col-sm{flex:1 0}.row-cols-sm-auto>*{flex:none;width:auto}.row-cols-sm-1>*{flex:none;width:100%}.row-cols-sm-2>*{flex:none;width:50%}.row-cols-sm-3>*{flex:none;width:33.33333333%}.row-cols-sm-4>*{flex:none;width:25%}.row-cols-sm-5>*{flex:none;width:20%}.row-cols-sm-6>*{flex:none;width:16.66666667%}.col-sm-auto{flex:none;width:auto}.col-sm-1{flex:none;width:8.33333333%}.col-sm-2{flex:none;width:16.66666667%}.col-sm-3{flex:none;width:25%}.col-sm-4{flex:none;width:33.33333333%}.col-sm-5{flex:none;width:41.66666667%}.col-sm-6{flex:none;width:50%}.col-sm-7{flex:none;width:58.33333333%}.col-sm-8{flex:none;width:66.66666667%}.col-sm-9{flex:none;width:75%}.col-sm-10{flex:none;width:83.33333333%}.col-sm-11{flex:none;width:91.66666667%}.col-sm-12{flex:none;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.5rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.5rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:1rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:1rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:2rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:2rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:3rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:3rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:6rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:6rem}}@media(min-width:768px){.col-md{flex:1 0}.row-cols-md-auto>*{flex:none;width:auto}.row-cols-md-1>*{flex:none;width:100%}.row-cols-md-2>*{flex:none;width:50%}.row-cols-md-3>*{flex:none;width:33.33333333%}.row-cols-md-4>*{flex:none;width:25%}.row-cols-md-5>*{flex:none;width:20%}.row-cols-md-6>*{flex:none;width:16.66666667%}.col-md-auto{flex:none;width:auto}.col-md-1{flex:none;width:8.33333333%}.col-md-2{flex:none;width:16.66666667%}.col-md-3{flex:none;width:25%}.col-md-4{flex:none;width:33.33333333%}.col-md-5{flex:none;width:41.66666667%}.col-md-6{flex:none;width:50%}.col-md-7{flex:none;width:58.33333333%}.col-md-8{flex:none;width:66.66666667%}.col-md-9{flex:none;width:75%}.col-md-10{flex:none;width:83.33333333%}.col-md-11{flex:none;width:91.66666667%}.col-md-12{flex:none;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.5rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.5rem}.g-md-2,.gx-md-2{--bs-gutter-x:1rem}.g-md-2,.gy-md-2{--bs-gutter-y:1rem}.g-md-3,.gx-md-3{--bs-gutter-x:2rem}.g-md-3,.gy-md-3{--bs-gutter-y:2rem}.g-md-4,.gx-md-4{--bs-gutter-x:3rem}.g-md-4,.gy-md-4{--bs-gutter-y:3rem}.g-md-5,.gx-md-5{--bs-gutter-x:6rem}.g-md-5,.gy-md-5{--bs-gutter-y:6rem}}@media(min-width:992px){.col-lg{flex:1 0}.row-cols-lg-auto>*{flex:none;width:auto}.row-cols-lg-1>*{flex:none;width:100%}.row-cols-lg-2>*{flex:none;width:50%}.row-cols-lg-3>*{flex:none;width:33.33333333%}.row-cols-lg-4>*{flex:none;width:25%}.row-cols-lg-5>*{flex:none;width:20%}.row-cols-lg-6>*{flex:none;width:16.66666667%}.col-lg-auto{flex:none;width:auto}.col-lg-1{flex:none;width:8.33333333%}.col-lg-2{flex:none;width:16.66666667%}.col-lg-3{flex:none;width:25%}.col-lg-4{flex:none;width:33.33333333%}.col-lg-5{flex:none;width:41.66666667%}.col-lg-6{flex:none;width:50%}.col-lg-7{flex:none;width:58.33333333%}.col-lg-8{flex:none;width:66.66666667%}.col-lg-9{flex:none;width:75%}.col-lg-10{flex:none;width:83.33333333%}.col-lg-11{flex:none;width:91.66666667%}.col-lg-12{flex:none;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.5rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.5rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:1rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:1rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:2rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:2rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:3rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:3rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:6rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:6rem}}@media(min-width:1200px){.col-xl{flex:1 0}.row-cols-xl-auto>*{flex:none;width:auto}.row-cols-xl-1>*{flex:none;width:100%}.row-cols-xl-2>*{flex:none;width:50%}.row-cols-xl-3>*{flex:none;width:33.33333333%}.row-cols-xl-4>*{flex:none;width:25%}.row-cols-xl-5>*{flex:none;width:20%}.row-cols-xl-6>*{flex:none;width:16.66666667%}.col-xl-auto{flex:none;width:auto}.col-xl-1{flex:none;width:8.33333333%}.col-xl-2{flex:none;width:16.66666667%}.col-xl-3{flex:none;width:25%}.col-xl-4{flex:none;width:33.33333333%}.col-xl-5{flex:none;width:41.66666667%}.col-xl-6{flex:none;width:50%}.col-xl-7{flex:none;width:58.33333333%}.col-xl-8{flex:none;width:66.66666667%}.col-xl-9{flex:none;width:75%}.col-xl-10{flex:none;width:83.33333333%}.col-xl-11{flex:none;width:91.66666667%}.col-xl-12{flex:none;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.5rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.5rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:1rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:1rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:2rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:2rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:3rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:3rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:6rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:6rem}}@media(min-width:1400px){.col-xxl{flex:1 0}.row-cols-xxl-auto>*{flex:none;width:auto}.row-cols-xxl-1>*{flex:none;width:100%}.row-cols-xxl-2>*{flex:none;width:50%}.row-cols-xxl-3>*{flex:none;width:33.33333333%}.row-cols-xxl-4>*{flex:none;width:25%}.row-cols-xxl-5>*{flex:none;width:20%}.row-cols-xxl-6>*{flex:none;width:16.66666667%}.col-xxl-auto{flex:none;width:auto}.col-xxl-1{flex:none;width:8.33333333%}.col-xxl-2{flex:none;width:16.66666667%}.col-xxl-3{flex:none;width:25%}.col-xxl-4{flex:none;width:33.33333333%}.col-xxl-5{flex:none;width:41.66666667%}.col-xxl-6{flex:none;width:50%}.col-xxl-7{flex:none;width:58.33333333%}.col-xxl-8{flex:none;width:66.66666667%}.col-xxl-9{flex:none;width:75%}.col-xxl-10{flex:none;width:83.33333333%}.col-xxl-11{flex:none;width:91.66666667%}.col-xxl-12{flex:none;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.5rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.5rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:1rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:1rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:2rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:2rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:3rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:3rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:6rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:6rem}}.table{--bs-table-color-type:initial;--bs-table-bg-type:initial;--bs-table-color-state:initial;--bs-table-bg-state:initial;--bs-table-color:initial;--bs-table-bg:var(--bs-body-bg);--bs-table-border-color:var(--bs-border-color);--bs-table-accent-bg:transparent;--bs-table-striped-color:initial;--bs-table-striped-bg:rgba(var(--bs-emphasis-color-rgb), 0.05);--bs-table-active-color:initial;--bs-table-active-bg:rgba(var(--bs-emphasis-color-rgb), 0.1);--bs-table-hover-color:initial;--bs-table-hover-bg:rgba(var(--bs-emphasis-color-rgb), 0.075);width:100%;margin-bottom:2rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem;color:var(--bs-table-color-state,var(--bs-table-color-type,var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:var(--bs-border-width);box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state,var(--bs-table-bg-type,var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(var(--bs-border-width) * 2)solid}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem}.table-bordered>:not(caption)>*{border-width:var(--bs-border-width)0}.table-bordered>:not(caption)>*>*{border-width:0 var(--bs-border-width)}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(2n){--bs-table-color-type:var(--bs-table-striped-color);--bs-table-bg-type:var(--bs-table-striped-bg)}.table-active{--bs-table-color-state:var(--bs-table-active-color);--bs-table-bg-state:var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state:var(--bs-table-hover-color);--bs-table-bg-state:var(--bs-table-hover-bg)}.table-primary{--bs-table-color:#fff;--bs-table-bg:#e83283;--bs-table-border-color:#ed5b9c;--bs-table-striped-bg:#e93c89;--bs-table-striped-color:#fff;--bs-table-active-bg:#ea478f;--bs-table-active-color:#fff;--bs-table-hover-bg:#ea418c;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color:#fff;--bs-table-bg:rgba(255, 255, 255, 0.4);--bs-table-border-color:rgba(255, 255, 255, 0.52);--bs-table-striped-bg:rgba(255, 255, 255, 0.43);--bs-table-striped-color:#000;--bs-table-active-bg:rgba(255, 255, 255, 0.46);--bs-table-active-color:#000;--bs-table-hover-bg:rgba(255, 255, 255, 0.445);--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color:#fff;--bs-table-bg:#41d7a7;--bs-table-border-color:#67dfb9;--bs-table-striped-bg:#4bd9ab;--bs-table-striped-color:#fff;--bs-table-active-bg:#54dbb0;--bs-table-active-color:#fff;--bs-table-hover-bg:#4fdaae;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color:#fff;--bs-table-bg:#39cbfb;--bs-table-border-color:#61d5fc;--bs-table-striped-bg:#43cefb;--bs-table-striped-color:#fff;--bs-table-active-bg:#4dd0fb;--bs-table-active-color:#fff;--bs-table-hover-bg:#48cffb;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color:#fff;--bs-table-bg:#ffc107;--bs-table-border-color:#ffcd39;--bs-table-striped-bg:#ffc413;--bs-table-striped-color:#fff;--bs-table-active-bg:#ffc720;--bs-table-active-color:#fff;--bs-table-hover-bg:#ffc61a;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color:#fff;--bs-table-bg:#fd7e14;--bs-table-border-color:#fd9843;--bs-table-striped-bg:#fd8420;--bs-table-striped-color:#fff;--bs-table-active-bg:#fd8b2c;--bs-table-active-color:#fff;--bs-table-hover-bg:#fd8826;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color:#000;--bs-table-bg:#e9e9e8;--bs-table-border-color:#bababa;--bs-table-striped-bg:#dddddc;--bs-table-striped-color:#000;--bs-table-active-bg:#d2d2d1;--bs-table-active-color:#fff;--bs-table-hover-bg:#d8d8d7;--bs-table-hover-color:#000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color:#fff;--bs-table-bg:#212529;--bs-table-border-color:#4d5154;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.75rem + 1px);padding-bottom:calc(.75rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:var(--bs-secondary-color)}.form-control{display:block;width:100%;padding:.75rem 1.5rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:initial;background-clip:padding-box;border:1px solid rgba(255,255,255,.4);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:var(--bs-body-color);background-color:initial;border-color:rgba(255,255,255,.4);outline:0;box-shadow:none}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::-moz-placeholder{color:rgba(255,255,255,.7);opacity:1}.form-control::placeholder{color:rgba(255,255,255,.7);opacity:1}.form-control:disabled{background-color:rgba(255,255,255,.1);opacity:1}.form-control::-webkit-file-upload-button{padding:.75rem 1.5rem;margin:-.75rem -1.5rem;-webkit-margin-end:1.5rem;margin-inline-end:1.5rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.75rem 1.5rem;margin:-.75rem -1.5rem;-webkit-margin-end:1.5rem;margin-inline-end:1.5rem;color:var(--bs-body-color);background-color:var(--bs-tertiary-bg);pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:var(--bs-secondary-bg)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:var(--bs-secondary-bg)}.form-control-plaintext{display:block;width:100%;padding:.75rem 0;margin-bottom:0;line-height:1.5;color:var(--bs-body-color);background-color:initial;border:solid transparent;border-width:1px 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + calc(1px * 2));padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(1px * 2));padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 1.5rem + calc(1px * 2))}textarea.form-control-sm{min-height:calc(1.5em + .5rem + calc(1px * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(1px * 2))}.form-control-color{width:3rem;height:calc(1.5em + 1.5rem + calc(1px * 2));padding:.75rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color::-webkit-color-swatch{border:0!important;border-radius:var(--bs-border-radius)}.form-control-color.form-control-sm{height:calc(1.5em + .5rem + calc(1px * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(1px * 2))}.form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.75rem 4.5rem .75rem 1.5rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:initial;background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon,none);background-repeat:no-repeat;background-position:right 1.5rem center;background-size:16px 12px;border:1px solid rgba(255,255,255,.4);border-radius:var(--bs-border-radius);transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:rgba(255,255,255,.4);outline:0;box-shadow:none}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:1.5rem;background-image:none}.form-select:disabled{color:rgba(255,255,255,.7);background-color:rgba(255,255,255,.1)}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 var(--bs-body-color)}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-reverse{padding-right:1.5em;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:-1.5em;margin-left:0}.form-check-input{--bs-form-check-bg:rgba(255, 255, 255, 0.3);flex-shrink:0;width:1em;height:1em;margin-top:.25em;vertical-align:top;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:50%;background-size:contain;border:1px solid rgba(255,255,255,.2);-webkit-print-color-adjust:exact;color-adjust:exact;print-color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:rgba(255,255,255,.4);outline:0;box-shadow:0 0 0 .25rem rgba(232,50,131,.25)}.form-check-input:checked{background-color:#e83283;border-color:#e83283}.form-check-input:checked[type=checkbox]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#e83283;border-color:#e83283;--bs-form-check-bg-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{cursor:default;opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:0;border-radius:2em;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:100%;--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:initial}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #686dc3,none}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #686dc3,none}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;-webkit-appearance:none;appearance:none;background-color:#e83283;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#f8c2da}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:rgba(0,0,0,.2);border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;-moz-appearance:none;appearance:none;background-color:#e83283;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#f8c2da}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:rgba(0,0,0,.2);border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:var(--bs-secondary-color)}.form-range:disabled::-moz-range-thumb{background-color:var(--bs-secondary-color)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(1px * 2));min-height:calc(3.5rem + calc(1px * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;max-width:100%;height:100%;padding:1rem 1.5rem;overflow:hidden;color:rgba(var(--bs-body-color-rgb),.65);text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem 1.5rem}.form-floating>.form-control-plaintext::-moz-placeholder,.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control-plaintext::placeholder,.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control-plaintext:not(:-moz-placeholder-shown),.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown),.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control-plaintext:-webkit-autofill,.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem;padding-left:1.5rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{transform:scale(.85)translateY(-.5rem)translateX(.15rem)}.form-floating>.form-control-plaintext~label,.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{transform:scale(.85)translateY(-.5rem)translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{transform:scale(.85)translateY(-.5rem)translateX(.15rem)}.form-floating>textarea:not(:-moz-placeholder-shown)~label::after{position:absolute;inset:1rem .75rem;z-index:-1;height:1.5em;content:"";background-color:initial;border-radius:var(--bs-border-radius)}.form-floating>textarea:focus~label::after,.form-floating>textarea:not(:placeholder-shown)~label::after{position:absolute;inset:1rem .75rem;z-index:-1;height:1.5em;content:"";background-color:initial;border-radius:var(--bs-border-radius)}.form-floating>textarea:disabled~label::after{background-color:rgba(255,255,255,.1)}.form-floating>.form-control-plaintext~label{border-width:1px 0}.form-floating>.form-control:disabled~label,.form-floating>:disabled~label{color:#6c757d}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-floating,.input-group>.form-select{position:relative;flex:auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-floating:focus-within,.input-group>.form-select:focus{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;align-items:center;padding:.75rem 1.5rem;font-size:1rem;font-weight:400;line-height:1.5;color:var(--bs-body-color);text-align:center;white-space:nowrap;background-color:initial;border:1px solid rgba(255,255,255,.4);border-radius:var(--bs-border-radius)}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:var(--bs-border-radius-lg)}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:var(--bs-border-radius-sm)}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:6rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select,.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select,.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(-1 * 1px);border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-valid-color)}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem 1rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-success);border-radius:var(--bs-border-radius)}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:var(--bs-form-valid-border-color);padding-right:calc(1.5em + 1.5rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2341d7a7' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .375rem)center;background-size:calc(.75em + .75rem)calc(.75em + .75rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 1.5rem);background-position:top calc(.375em + .375rem)right calc(.375em + .375rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:var(--bs-form-valid-border-color)}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2341d7a7' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1'/%3e%3c/svg%3e");padding-right:8.25rem;background-position:right 1.5rem center,center right 4.5rem;background-size:16px 12px,calc(.75em + .75rem)calc(.75em + .75rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:var(--bs-form-valid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-control-color.is-valid,.was-validated .form-control-color:valid{width:calc(3rem + calc(1.5em + 1.5rem))}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:var(--bs-form-valid-border-color)}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:var(--bs-form-valid-color)}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-success-rgb),.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:var(--bs-form-valid-color)}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-valid,.input-group>.form-floating:not(:focus-within).is-valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-control:not(:focus):valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.was-validated .input-group>.form-select:not(:focus):valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:var(--bs-form-invalid-color)}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem 1rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:var(--bs-danger);border-radius:var(--bs-border-radius)}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:var(--bs-form-invalid-border-color);padding-right:calc(1.5em + 1.5rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23fd7e14'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23fd7e14' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .375rem)center;background-size:calc(.75em + .75rem)calc(.75em + .75rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 1.5rem);background-position:top calc(.375em + .375rem)right calc(.375em + .375rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:var(--bs-form-invalid-border-color)}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23fd7e14'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23fd7e14' stroke='none'/%3e%3c/svg%3e");padding-right:8.25rem;background-position:right 1.5rem center,center right 4.5rem;background-size:16px 12px,calc(.75em + .75rem)calc(.75em + .75rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:var(--bs-form-invalid-border-color);box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-control-color.is-invalid,.was-validated .form-control-color:invalid{width:calc(3rem + calc(1.5em + 1.5rem))}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:var(--bs-form-invalid-border-color)}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:var(--bs-form-invalid-color)}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(var(--bs-danger-rgb),.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:var(--bs-form-invalid-color)}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group>.form-control:not(:focus).is-invalid,.input-group>.form-floating:not(:focus-within).is-invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-control:not(:focus):invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.was-validated .input-group>.form-select:not(:focus):invalid{z-index:4}.btn{--bs-btn-padding-x:1.5rem;--bs-btn-padding-y:0.75rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight:400;--bs-btn-line-height:1.5;--bs-btn-color:var(--bs-body-color);--bs-btn-bg:transparent;--bs-btn-border-width:var(--bs-border-width);--bs-btn-border-color:transparent;--bs-btn-border-radius:var(--bs-border-radius);--bs-btn-hover-border-color:transparent;--bs-btn-box-shadow:1px 3px 24px -1px rgba(0, 0, 0, 0.15);--bs-btn-disabled-opacity:0.65;--bs-btn-focus-box-shadow:0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y)var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;border:var(--bs-btn-border-width)solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,.btn.active,.btn.show,.btn:first-child:active,:not(.btn-check)+.btn:active{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible,.btn:first-child:active:focus-visible,:not(.btn-check)+.btn:active:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked:focus-visible+.btn{box-shadow:var(--bs-btn-focus-box-shadow)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#e83283;--bs-btn-border-color:#e83283;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#c52b6f;--bs-btn-hover-border-color:#ba2869;--bs-btn-focus-shadow-rgb:235, 81, 150;--bs-btn-active-color:#fff;--bs-btn-active-bg:#ba2869;--bs-btn-active-border-color:#ae2662;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#e83283;--bs-btn-disabled-border-color:#e83283}.btn-secondary{--bs-btn-color:#000;--bs-btn-bg:rgba(255, 255, 255, 0.4);--bs-btn-border-color:rgba(255, 255, 255, 0.4);--bs-btn-hover-color:#000;--bs-btn-hover-bg:rgba(255, 255, 255, 0.49);--bs-btn-hover-border-color:rgba(255, 255, 255, 0.46);--bs-btn-focus-shadow-rgb:149, 149, 149;--bs-btn-active-color:#000;--bs-btn-active-bg:rgba(255, 255, 255, 0.52);--bs-btn-active-border-color:rgba(255, 255, 255, 0.46);--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:rgba(255, 255, 255, 0.4);--bs-btn-disabled-border-color:rgba(255, 255, 255, 0.4)}.btn-success{--bs-btn-color:#fff;--bs-btn-bg:#41d7a7;--bs-btn-border-color:#41d7a7;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#37b78e;--bs-btn-hover-border-color:#34ac86;--bs-btn-focus-shadow-rgb:94, 221, 180;--bs-btn-active-color:#fff;--bs-btn-active-bg:#34ac86;--bs-btn-active-border-color:#31a17d;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#41d7a7;--bs-btn-disabled-border-color:#41d7a7}.btn-info{--bs-btn-color:#fff;--bs-btn-bg:#39cbfb;--bs-btn-border-color:#39cbfb;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#30add5;--bs-btn-hover-border-color:#2ea2c9;--bs-btn-focus-shadow-rgb:87, 211, 252;--bs-btn-active-color:#fff;--bs-btn-active-bg:#2ea2c9;--bs-btn-active-border-color:#2b98bc;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#39cbfb;--bs-btn-disabled-border-color:#39cbfb}.btn-warning{--bs-btn-color:#fff;--bs-btn-bg:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#d9a406;--bs-btn-hover-border-color:#cc9a06;--bs-btn-focus-shadow-rgb:255, 202, 44;--bs-btn-active-color:#fff;--bs-btn-active-bg:#cc9a06;--bs-btn-active-border-color:#bf9105;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#ffc107;--bs-btn-disabled-border-color:#ffc107}.btn-danger{--bs-btn-color:#fff;--bs-btn-bg:#fd7e14;--bs-btn-border-color:#fd7e14;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#d76b11;--bs-btn-hover-border-color:#ca6510;--bs-btn-focus-shadow-rgb:253, 145, 55;--bs-btn-active-color:#fff;--bs-btn-active-bg:#ca6510;--bs-btn-active-border-color:#be5f0f;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#fd7e14;--bs-btn-disabled-border-color:#fd7e14}.btn-light{--bs-btn-color:#000;--bs-btn-bg:#e9e9e8;--bs-btn-border-color:#e9e9e8;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#c6c6c5;--bs-btn-hover-border-color:#bababa;--bs-btn-focus-shadow-rgb:198, 198, 197;--bs-btn-active-color:#fff;--bs-btn-active-bg:#bababa;--bs-btn-active-border-color:#afafae;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#000;--bs-btn-disabled-bg:#e9e9e8;--bs-btn-disabled-border-color:#e9e9e8}.btn-dark{--bs-btn-color:#fff;--bs-btn-bg:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#424649;--bs-btn-hover-border-color:#373b3e;--bs-btn-focus-shadow-rgb:66, 70, 73;--bs-btn-active-color:#fff;--bs-btn-active-bg:#4d5154;--bs-btn-active-border-color:#373b3e;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#212529;--bs-btn-disabled-border-color:#212529}.btn-outline-primary{--bs-btn-color:#e83283;--bs-btn-border-color:#e83283;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#e83283;--bs-btn-hover-border-color:#e83283;--bs-btn-focus-shadow-rgb:232, 50, 131;--bs-btn-active-color:#fff;--bs-btn-active-bg:#e83283;--bs-btn-active-border-color:#e83283;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#e83283;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#e83283;--bs-gradient:none}.btn-outline-secondary{--bs-btn-color:rgba(255, 255, 255, 0.4);--bs-btn-border-color:rgba(255, 255, 255, 0.4);--bs-btn-hover-color:#000;--bs-btn-hover-bg:rgba(255, 255, 255, 0.4);--bs-btn-hover-border-color:rgba(255, 255, 255, 0.4);--bs-btn-focus-shadow-rgb:255, 255, 255;--bs-btn-active-color:#000;--bs-btn-active-bg:rgba(255, 255, 255, 0.4);--bs-btn-active-border-color:rgba(255, 255, 255, 0.4);--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:rgba(255, 255, 255, 0.4);--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:rgba(255, 255, 255, 0.4);--bs-gradient:none}.btn-outline-success{--bs-btn-color:#41d7a7;--bs-btn-border-color:#41d7a7;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#41d7a7;--bs-btn-hover-border-color:#41d7a7;--bs-btn-focus-shadow-rgb:65, 215, 167;--bs-btn-active-color:#fff;--bs-btn-active-bg:#41d7a7;--bs-btn-active-border-color:#41d7a7;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#41d7a7;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#41d7a7;--bs-gradient:none}.btn-outline-info{--bs-btn-color:#39cbfb;--bs-btn-border-color:#39cbfb;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#39cbfb;--bs-btn-hover-border-color:#39cbfb;--bs-btn-focus-shadow-rgb:57, 203, 251;--bs-btn-active-color:#fff;--bs-btn-active-bg:#39cbfb;--bs-btn-active-border-color:#39cbfb;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#39cbfb;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#39cbfb;--bs-gradient:none}.btn-outline-warning{--bs-btn-color:#ffc107;--bs-btn-border-color:#ffc107;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#ffc107;--bs-btn-hover-border-color:#ffc107;--bs-btn-focus-shadow-rgb:255, 193, 7;--bs-btn-active-color:#fff;--bs-btn-active-bg:#ffc107;--bs-btn-active-border-color:#ffc107;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#ffc107;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#ffc107;--bs-gradient:none}.btn-outline-danger{--bs-btn-color:#fd7e14;--bs-btn-border-color:#fd7e14;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#fd7e14;--bs-btn-hover-border-color:#fd7e14;--bs-btn-focus-shadow-rgb:253, 126, 20;--bs-btn-active-color:#fff;--bs-btn-active-bg:#fd7e14;--bs-btn-active-border-color:#fd7e14;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#fd7e14;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#fd7e14;--bs-gradient:none}.btn-outline-light{--bs-btn-color:#e9e9e8;--bs-btn-border-color:#e9e9e8;--bs-btn-hover-color:#000;--bs-btn-hover-bg:#e9e9e8;--bs-btn-hover-border-color:#e9e9e8;--bs-btn-focus-shadow-rgb:233, 233, 232;--bs-btn-active-color:#000;--bs-btn-active-bg:#e9e9e8;--bs-btn-active-border-color:#e9e9e8;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#e9e9e8;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#e9e9e8;--bs-gradient:none}.btn-outline-dark{--bs-btn-color:#212529;--bs-btn-border-color:#212529;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#212529;--bs-btn-hover-border-color:#212529;--bs-btn-focus-shadow-rgb:33, 37, 41;--bs-btn-active-color:#fff;--bs-btn-active-bg:#212529;--bs-btn-active-border-color:#212529;--bs-btn-active-shadow:inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color:#212529;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#212529;--bs-gradient:none}.btn-link{--bs-btn-font-weight:400;--bs-btn-color:var(--bs-link-color);--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-link-hover-color);--bs-btn-hover-border-color:transparent;--bs-btn-active-color:var(--bs-link-hover-color);--bs-btn-active-border-color:transparent;--bs-btn-disabled-color:#6c757d;--bs-btn-disabled-border-color:transparent;--bs-btn-box-shadow:0 0 0 #000;--bs-btn-focus-shadow-rgb:217, 217, 217;text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-group-lg>.btn,.btn-lg{--bs-btn-padding-y:0.5rem;--bs-btn-padding-x:1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius:var(--bs-border-radius-lg)}.btn-group-sm>.btn,.btn-sm{--bs-btn-padding-y:0.25rem;--bs-btn-padding-x:0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius:var(--bs-border-radius-sm)}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media(prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropdown-center,.dropend,.dropstart,.dropup,.dropup-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex:1000;--bs-dropdown-min-width:10rem;--bs-dropdown-padding-x:0;--bs-dropdown-padding-y:0.5rem;--bs-dropdown-spacer:0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color:var(--bs-body-color);--bs-dropdown-bg:var(--bs-body-bg);--bs-dropdown-border-color:rgba(255, 255, 255, 0.2);--bs-dropdown-border-radius:var(--bs-border-radius);--bs-dropdown-border-width:var(--bs-border-width);--bs-dropdown-inner-border-radius:calc(var(--bs-border-radius) - var(--bs-border-width));--bs-dropdown-divider-bg:rgba(255, 255, 255, 0.2);--bs-dropdown-divider-margin-y:1rem;--bs-dropdown-box-shadow:var(--bs-box-shadow);--bs-dropdown-link-color:var(--bs-body-color);--bs-dropdown-link-hover-color:#fff;--bs-dropdown-link-hover-bg:rgba(255, 255, 255, 0.4);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#e83283;--bs-dropdown-link-disabled-color:var(--bs-tertiary-color);--bs-dropdown-item-padding-x:2rem;--bs-dropdown-item-padding-y:0.5rem;--bs-dropdown-header-color:#6c757d;--bs-dropdown-header-padding-x:2rem;--bs-dropdown-header-padding-y:0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y)var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width)solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y)0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y)var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;white-space:nowrap;background-color:initial;border:0;border-radius:var(--bs-dropdown-item-border-radius,0)}.dropdown-item:focus,.dropdown-item:hover{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:initial}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y)var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y)var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color:#dee2e6;--bs-dropdown-bg:#343a40;--bs-dropdown-border-color:rgba(255, 255, 255, 0.2);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color:#dee2e6;--bs-dropdown-link-hover-color:#fff;--bs-dropdown-divider-bg:rgba(255, 255, 255, 0.2);--bs-dropdown-link-hover-bg:rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color:#fff;--bs-dropdown-link-active-bg:#e83283;--bs-dropdown-link-disabled-color:#adb5bd;--bs-dropdown-header-color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:var(--bs-border-radius)}.btn-group>.btn-group:not(:first-child),.btn-group>:not(.btn-check:first-child)+.btn{margin-left:calc(-1 * var(--bs-border-width))}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:1.125rem;padding-left:1.125rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:calc(-1 * var(--bs-border-width))}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:nth-child(n+3),.btn-group-vertical>:not(.btn-check)+.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x:1rem;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-link-color);--bs-nav-link-hover-color:var(--bs-link-hover-color);--bs-nav-link-disabled-color:rgba(255, 255, 255, 0.7);display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y)var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;background:0 0;border:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(232,50,131,.25)}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width:0;--bs-nav-tabs-border-color:var(--bs-border-color);--bs-nav-tabs-border-radius:0;--bs-nav-tabs-link-hover-border-color:var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);--bs-nav-tabs-link-active-color:#343a40;--bs-nav-tabs-link-active-bg:#fff;--bs-nav-tabs-link-active-border-color:#dee2e6 #dee2e6 #fff;border-bottom:var(--bs-nav-tabs-border-width)solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1 * var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width)solid transparent;border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1 * var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius:50rem;--bs-nav-pills-link-active-color:#fff;--bs-nav-pills-link-active-bg:#e83283}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap:1rem;--bs-nav-underline-border-width:0.125rem;--bs-nav-underline-link-active-color:#fff;gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width)solid transparent}.nav-underline .nav-link:focus,.nav-underline .nav-link:hover{border-bottom-color:initial}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:initial}.nav-fill .nav-item,.nav-fill>.nav-link{flex:auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-grow:1;flex-basis:0;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x:0;--bs-navbar-padding-y:1rem;--bs-navbar-color:rgba(var(--bs-emphasis-color-rgb), 0.65);--bs-navbar-hover-color:rgba(var(--bs-emphasis-color-rgb), 0.8);--bs-navbar-disabled-color:rgba(var(--bs-emphasis-color-rgb), 0.3);--bs-navbar-active-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-padding-y:0.3125rem;--bs-navbar-brand-margin-end:1rem;--bs-navbar-brand-font-size:1.25rem;--bs-navbar-brand-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-brand-hover-color:rgba(var(--bs-emphasis-color-rgb), 1);--bs-navbar-nav-link-padding-x:0.5rem;--bs-navbar-toggler-padding-y:0.25rem;--bs-navbar-toggler-padding-x:0.75rem;--bs-navbar-toggler-font-size:1.25rem;--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color:rgba(var(--bs-emphasis-color-rgb), 0.15);--bs-navbar-toggler-border-radius:var(--bs-border-radius);--bs-navbar-toggler-focus-width:0.25rem;--bs-navbar-toggler-transition:box-shadow 0.15s ease-in-out;position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:var(--bs-navbar-padding-y)var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color:var(--bs-navbar-color);--bs-nav-link-hover-color:var(--bs-navbar-hover-color);--bs-nav-link-disabled-color:var(--bs-navbar-disabled-color);display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:focus,.navbar-text a:hover{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-grow:1;flex-basis:100%;align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y)var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:initial;border:var(--bs-border-width)solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition)}@media(prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:50%;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media(min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:initial!important;border:0!important;transform:none!important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media(min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:initial!important;border:0!important;transform:none!important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media(min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:initial!important;border:0!important;transform:none!important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media(min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:initial!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media(min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:initial!important;border:0!important;transform:none!important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;width:auto!important;height:auto!important;visibility:visible!important;background-color:initial!important;border:0!important;transform:none!important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color:rgba(255, 255, 255, 0.55);--bs-navbar-hover-color:rgba(255, 255, 255, 0.75);--bs-navbar-disabled-color:rgba(255, 255, 255, 0.25);--bs-navbar-active-color:#fff;--bs-navbar-brand-color:#fff;--bs-navbar-brand-hover-color:#fff;--bs-navbar-toggler-border-color:rgba(255, 255, 255, 0.1);--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y:1.75rem;--bs-card-spacer-x:2rem;--bs-card-title-spacer-y:1rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width:var(--bs-border-width);--bs-card-border-color:rgba(255, 255, 255, 0.2);--bs-card-border-radius:var(--bs-border-radius);--bs-card-box-shadow: ;--bs-card-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-card-cap-padding-y:0.875rem;--bs-card-cap-padding-x:2rem;--bs-card-cap-bg:transparent;--bs-card-cap-color:#fff;--bs-card-height: ;--bs-card-color:#fff;--bs-card-bg:transparent;--bs-card-img-overlay-padding:2rem;--bs-card-group-margin:0.75rem;position:relative;display:flex;flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width)solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:auto;padding:var(--bs-card-spacer-y)var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-.5 * var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y)var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width)solid var(--bs-card-border-color)}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius)var(--bs-card-inner-border-radius)0 0}.card-footer{padding:var(--bs-card-cap-padding-y)var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width)solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius)var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-bottom:calc(-1 * var(--bs-card-cap-padding-y));margin-left:calc(-.5 * var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-.5 * var(--bs-card-cap-padding-x));margin-left:calc(-.5 * var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media(min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child)>.card-header,.card-group>.card:not(:last-child)>.card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child)>.card-footer,.card-group>.card:not(:last-child)>.card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child)>.card-header,.card-group>.card:not(:first-child)>.card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child)>.card-footer,.card-group>.card:not(:first-child)>.card-img-bottom{border-bottom-left-radius:0}}.accordion{--bs-accordion-color:var(--bs-body-color);--bs-accordion-bg:var(--bs-body-bg);--bs-accordion-transition:color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease;--bs-accordion-border-color:var(--bs-border-color);--bs-accordion-border-width:var(--bs-border-width);--bs-accordion-border-radius:var(--bs-border-radius);--bs-accordion-inner-border-radius:calc(var(--bs-border-radius) - (var(--bs-border-width)));--bs-accordion-btn-padding-x:1.25rem;--bs-accordion-btn-padding-y:1rem;--bs-accordion-btn-color:var(--bs-body-color);--bs-accordion-btn-bg:rgba(255, 255, 255, 0.4);--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width:1.25rem;--bs-accordion-btn-icon-transform:rotate(-180deg);--bs-accordion-btn-icon-transition:transform 0.2s ease-in-out;--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%235d1434' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='m2 5 6 6 6-6'/%3e%3c/svg%3e");--bs-accordion-btn-focus-box-shadow:0 0 0 0.25rem rgba(232, 50, 131, 0.25);--bs-accordion-body-padding-x:1.25rem;--bs-accordion-body-padding-y:1rem;--bs-accordion-active-color:#fff;--bs-accordion-active-bg:#e83283}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y)var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;border-radius:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media(prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1 * var(--bs-accordion-border-width))0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media(prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width)solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type>.accordion-header .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type>.accordion-header .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type>.accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y)var(--bs-accordion-body-padding-x)}.accordion-flush>.accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush>.accordion-item:first-child{border-top:0}.accordion-flush>.accordion-item:last-child{border-bottom:0}.accordion-flush>.accordion-item>.accordion-collapse,.accordion-flush>.accordion-item>.accordion-header .accordion-button,.accordion-flush>.accordion-item>.accordion-header .accordion-button.collapsed{border-radius:0}[data-bs-theme=dark] .accordion-button::after{--bs-accordion-btn-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f184b5'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f184b5'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x:0;--bs-breadcrumb-padding-y:0;--bs-breadcrumb-margin-bottom:1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color:#fff;--bs-breadcrumb-item-padding-x:0.5rem;--bs-breadcrumb-item-active-color:#fff;display:flex;flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y)var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider,"/")}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x:0.75rem;--bs-pagination-padding-y:0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color:var(--bs-link-color);--bs-pagination-bg:rgba(255, 255, 255, 0.3);--bs-pagination-border-width:0;--bs-pagination-border-color:var(--bs-border-color);--bs-pagination-border-radius:var(--bs-border-radius);--bs-pagination-hover-color:#fff;--bs-pagination-hover-bg:rgba(255, 255, 255, 0.4);--bs-pagination-hover-border-color:var(--bs-border-color);--bs-pagination-focus-color:#fff;--bs-pagination-focus-bg:rgba(255, 255, 255, 0.4);--bs-pagination-focus-box-shadow:none;--bs-pagination-active-color:#fff;--bs-pagination-active-bg:#e83283;--bs-pagination-active-border-color:#e83283;--bs-pagination-disabled-color:rgba(255, 255, 255, 0.7);--bs-pagination-disabled-bg:rgba(255, 255, 255, 0.3);--bs-pagination-disabled-border-color:var(--bs-border-color);display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y)var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width)solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.active>.page-link,.page-link.active{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.disabled>.page-link,.page-link.disabled{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(-1 * 0)}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x:1.5rem;--bs-pagination-padding-y:0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius:var(--bs-border-radius-lg)}.pagination-sm{--bs-pagination-padding-x:0.5rem;--bs-pagination-padding-y:0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius:var(--bs-border-radius-sm)}.badge{--bs-badge-padding-x:0.65em;--bs-badge-padding-y:0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight:700;--bs-badge-color:#fff;--bs-badge-border-radius:var(--bs-border-radius);display:inline-block;padding:var(--bs-badge-padding-y)var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius)}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg:transparent;--bs-alert-padding-x:2rem;--bs-alert-padding-y:2rem;--bs-alert-margin-bottom:1rem;--bs-alert-color:inherit;--bs-alert-border-color:transparent;--bs-alert-border:var(--bs-border-width) solid var(--bs-alert-border-color);--bs-alert-border-radius:var(--bs-border-radius);--bs-alert-link-color:inherit;position:relative;padding:var(--bs-alert-padding-y)var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:6rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:2.5rem 2rem}.alert-primary{--bs-alert-color:var(--bs-primary-text-emphasis);--bs-alert-bg:var(--bs-primary-bg-subtle);--bs-alert-border-color:var(--bs-primary-border-subtle);--bs-alert-link-color:var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color:var(--bs-secondary-text-emphasis);--bs-alert-bg:var(--bs-secondary-bg-subtle);--bs-alert-border-color:var(--bs-secondary-border-subtle);--bs-alert-link-color:var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color:var(--bs-success-text-emphasis);--bs-alert-bg:var(--bs-success-bg-subtle);--bs-alert-border-color:var(--bs-success-border-subtle);--bs-alert-link-color:var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color:var(--bs-info-text-emphasis);--bs-alert-bg:var(--bs-info-bg-subtle);--bs-alert-border-color:var(--bs-info-border-subtle);--bs-alert-link-color:var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color:var(--bs-warning-text-emphasis);--bs-alert-bg:var(--bs-warning-bg-subtle);--bs-alert-border-color:var(--bs-warning-border-subtle);--bs-alert-link-color:var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color:var(--bs-danger-text-emphasis);--bs-alert-bg:var(--bs-danger-bg-subtle);--bs-alert-border-color:var(--bs-danger-border-subtle);--bs-alert-link-color:var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color:var(--bs-light-text-emphasis);--bs-alert-bg:var(--bs-light-bg-subtle);--bs-alert-border-color:var(--bs-light-border-subtle);--bs-alert-link-color:var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color:var(--bs-dark-text-emphasis);--bs-alert-bg:var(--bs-dark-bg-subtle);--bs-alert-border-color:var(--bs-dark-border-subtle);--bs-alert-link-color:var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:var(--bs-progress-height)}}.progress,.progress-stacked{--bs-progress-height:1rem;--bs-progress-font-size:0.75rem;--bs-progress-bg:rgba(0, 0, 0, 0.2);--bs-progress-border-radius:var(--bs-border-radius);--bs-progress-box-shadow:var(--bs-box-shadow-inset);--bs-progress-bar-color:#fff;--bs-progress-bar-bg:#e83283;--bs-progress-bar-transition:width 0.6s ease;display:flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius)}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media(prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:var(--bs-progress-height)var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color:#fff;--bs-list-group-bg:transparent;--bs-list-group-border-color:rgba(255, 255, 255, 0.2);--bs-list-group-border-width:0;--bs-list-group-border-radius:var(--bs-border-radius);--bs-list-group-item-padding-x:2rem;--bs-list-group-item-padding-y:1rem;--bs-list-group-action-color:#fff;--bs-list-group-action-hover-color:#fff;--bs-list-group-action-hover-bg:rgba(255, 255, 255, 0.4);--bs-list-group-action-active-color:var(--bs-body-color);--bs-list-group-action-active-bg:var(--bs-secondary-bg);--bs-list-group-disabled-color:rgba(255, 255, 255, 0.7);--bs-list-group-disabled-bg:transparent;--bs-list-group-active-color:#fff;--bs-list-group-active-bg:#e83283;--bs-list-group-active-border-color:#e83283;display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:var(--bs-list-group-border-radius)}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section,".")". ";counter-increment:section}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y)var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width)solid var(--bs-list-group-border-color)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1 * var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:not(.active):focus,.list-group-item-action:not(.active):hover{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:not(.active):active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media(min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1 * var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{--bs-list-group-color:var(--bs-primary-text-emphasis);--bs-list-group-bg:var(--bs-primary-bg-subtle);--bs-list-group-border-color:var(--bs-primary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-primary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-primary-border-subtle);--bs-list-group-active-color:var(--bs-primary-bg-subtle);--bs-list-group-active-bg:var(--bs-primary-text-emphasis);--bs-list-group-active-border-color:var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color:var(--bs-secondary-text-emphasis);--bs-list-group-bg:var(--bs-secondary-bg-subtle);--bs-list-group-border-color:var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-secondary-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-secondary-border-subtle);--bs-list-group-active-color:var(--bs-secondary-bg-subtle);--bs-list-group-active-bg:var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color:var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color:var(--bs-success-text-emphasis);--bs-list-group-bg:var(--bs-success-bg-subtle);--bs-list-group-border-color:var(--bs-success-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-success-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-success-border-subtle);--bs-list-group-active-color:var(--bs-success-bg-subtle);--bs-list-group-active-bg:var(--bs-success-text-emphasis);--bs-list-group-active-border-color:var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color:var(--bs-info-text-emphasis);--bs-list-group-bg:var(--bs-info-bg-subtle);--bs-list-group-border-color:var(--bs-info-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-info-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-info-border-subtle);--bs-list-group-active-color:var(--bs-info-bg-subtle);--bs-list-group-active-bg:var(--bs-info-text-emphasis);--bs-list-group-active-border-color:var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color:var(--bs-warning-text-emphasis);--bs-list-group-bg:var(--bs-warning-bg-subtle);--bs-list-group-border-color:var(--bs-warning-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-warning-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-warning-border-subtle);--bs-list-group-active-color:var(--bs-warning-bg-subtle);--bs-list-group-active-bg:var(--bs-warning-text-emphasis);--bs-list-group-active-border-color:var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color:var(--bs-danger-text-emphasis);--bs-list-group-bg:var(--bs-danger-bg-subtle);--bs-list-group-border-color:var(--bs-danger-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-danger-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-danger-border-subtle);--bs-list-group-active-color:var(--bs-danger-bg-subtle);--bs-list-group-active-bg:var(--bs-danger-text-emphasis);--bs-list-group-active-border-color:var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color:var(--bs-light-text-emphasis);--bs-list-group-bg:var(--bs-light-bg-subtle);--bs-list-group-border-color:var(--bs-light-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-light-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-light-border-subtle);--bs-list-group-active-color:var(--bs-light-bg-subtle);--bs-list-group-active-bg:var(--bs-light-text-emphasis);--bs-list-group-active-border-color:var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color:var(--bs-dark-text-emphasis);--bs-list-group-bg:var(--bs-dark-bg-subtle);--bs-list-group-border-color:var(--bs-dark-border-subtle);--bs-list-group-action-hover-color:var(--bs-emphasis-color);--bs-list-group-action-hover-bg:var(--bs-dark-border-subtle);--bs-list-group-action-active-color:var(--bs-emphasis-color);--bs-list-group-action-active-bg:var(--bs-dark-border-subtle);--bs-list-group-active-color:var(--bs-dark-bg-subtle);--bs-list-group-active-bg:var(--bs-dark-text-emphasis);--bs-list-group-active-border-color:var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color:#fff;--bs-btn-close-bg:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414'/%3e%3c/svg%3e");--bs-btn-close-opacity:0.5;--bs-btn-close-hover-opacity:0.75;--bs-btn-close-focus-shadow:0 0 0 0.25rem rgba(232, 50, 131, 0.25);--bs-btn-close-focus-opacity:1;--bs-btn-close-disabled-opacity:0.25;box-sizing:content-box;width:1em;height:1em;padding:.25em;color:var(--bs-btn-close-color);background:var(--bs-btn-close-bg)50%/1em no-repeat;filter:var(--bs-btn-close-filter);border:0;border-radius:.5rem;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}:root,[data-bs-theme=light]{--bs-btn-close-filter: }[data-bs-theme=dark]{--bs-btn-close-filter:invert(1) grayscale(100%) brightness(200%)}.toast{--bs-toast-zindex:1090;--bs-toast-padding-x:0.75rem;--bs-toast-padding-y:0.5rem;--bs-toast-spacing:1.5rem;--bs-toast-max-width:350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg:rgba(var(--bs-body-bg-rgb), 0.85);--bs-toast-border-width:var(--bs-border-width);--bs-toast-border-color:var(--bs-border-color-translucent);--bs-toast-border-radius:var(--bs-border-radius);--bs-toast-box-shadow:var(--bs-box-shadow);--bs-toast-header-color:#fff;--bs-toast-header-bg:transparent;--bs-toast-header-border-color:rgba(255, 255, 255, 0.2);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width)solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex:1090;position:absolute;z-index:var(--bs-toast-zindex);width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;align-items:center;padding:var(--bs-toast-padding-y)var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width)solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width))}.toast-header .btn-close{margin-right:calc(-.5 * var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex:1055;--bs-modal-width:500px;--bs-modal-padding:2rem;--bs-modal-margin:0.5rem;--bs-modal-color:var(--bs-body-color);--bs-modal-bg:var(--bs-body-bg);--bs-modal-border-color:var(--bs-border-color-translucent);--bs-modal-border-width:var(--bs-border-width);--bs-modal-border-radius:var(--bs-border-radius-lg);--bs-modal-box-shadow:var(--bs-box-shadow-sm);--bs-modal-inner-border-radius:calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));--bs-modal-header-padding-x:2rem;--bs-modal-header-padding-y:2rem;--bs-modal-header-padding:2rem 2rem;--bs-modal-header-border-color:var(--bs-border-color);--bs-modal-header-border-width:var(--bs-border-width);--bs-modal-title-line-height:1.5;--bs-modal-footer-gap:0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color:var(--bs-border-color);--bs-modal-footer-border-width:var(--bs-border-width);position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transform:translate(0,-50px);transition:transform .3s ease-out}@media(prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin) * 2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - var(--bs-modal-margin) * 2)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width)solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);outline:0}.modal-backdrop{--bs-backdrop-zindex:1050;--bs-backdrop-bg:#000;--bs-backdrop-opacity:0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;flex-shrink:0;align-items:center;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width)solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y) * .5)calc(var(--bs-modal-header-padding-x) * .5);margin-top:calc(-.5 * var(--bs-modal-header-padding-y));margin-right:calc(-.5 * var(--bs-modal-header-padding-x));margin-bottom:calc(-.5 * var(--bs-modal-header-padding-y));margin-left:auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;flex-shrink:0;flex-wrap:wrap;align-items:center;justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * .5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width)solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap) * .5)}@media(min-width:576px){.modal{--bs-modal-margin:1.75rem;--bs-modal-box-shadow:var(--bs-box-shadow)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width:300px}}@media(min-width:992px){.modal-lg,.modal-xl{--bs-modal-width:800px}}@media(min-width:1200px){.modal-xl{--bs-modal-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-footer,.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media(max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-footer,.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media(max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-footer,.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media(max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-footer,.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media(max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-footer,.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media(max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-footer,.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex:1080;--bs-tooltip-max-width:200px;--bs-tooltip-padding-x:1rem;--bs-tooltip-padding-y:0.5rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color:var(--bs-body-bg);--bs-tooltip-bg:var(--bs-emphasis-color);--bs-tooltip-border-radius:var(--bs-border-radius);--bs-tooltip-opacity:0.7;--bs-tooltip-arrow-width:0.8rem;--bs-tooltip-arrow-height:0.4rem;z-index:var(--bs-tooltip-zindex);display:block;margin:var(--bs-tooltip-margin);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height)calc(var(--bs-tooltip-arrow-width) * .5)0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5)var(--bs-tooltip-arrow-height)calc(var(--bs-tooltip-arrow-width) * .5)0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:calc(-1 * var(--bs-tooltip-arrow-height))}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width) * .5)var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:calc(-1 * var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width) * .5)0 calc(var(--bs-tooltip-arrow-width) * .5)var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y)var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex:1070;--bs-popover-max-width:276px;--bs-popover-font-size:0.875rem;--bs-popover-bg:var(--bs-body-bg);--bs-popover-border-width:var(--bs-border-width);--bs-popover-border-color:var(--bs-border-color-translucent);--bs-popover-border-radius:var(--bs-border-radius-lg);--bs-popover-inner-border-radius:calc(var(--bs-border-radius-lg) - var(--bs-border-width));--bs-popover-box-shadow:var(--bs-box-shadow);--bs-popover-header-padding-x:2rem;--bs-popover-header-padding-y:0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color:inherit;--bs-popover-header-bg:transparent;--bs-popover-body-padding-x:2rem;--bs-popover-body-padding-y:1rem;--bs-popover-body-color:#fff;--bs-popover-arrow-width:1rem;--bs-popover-arrow-height:0.5rem;--bs-popover-arrow-border:var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width)solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid;border-width:0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-top>.popover-arrow::before{border-width:var(--bs-popover-arrow-height)calc(var(--bs-popover-arrow-width) * .5)0}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-end>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5)var(--bs-popover-arrow-height)calc(var(--bs-popover-arrow-width) * .5)0}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::before{border-width:0 calc(var(--bs-popover-arrow-width) * .5)var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-.5 * var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width)solid var(--bs-popover-header-bg)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-start>.popover-arrow::before{border-width:calc(var(--bs-popover-arrow-width) * .5)0 calc(var(--bs-popover-arrow-width) * .5)var(--bs-popover-arrow-height)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y)var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width)solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y)var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;filter:var(--bs-carousel-control-icon-filter);border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:initial;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:var(--bs-carousel-indicator-active-bg);background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:var(--bs-carousel-caption-color);text-align:center}.carousel-dark{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}:root,[data-bs-theme=light]{--bs-carousel-indicator-active-bg:#fff;--bs-carousel-caption-color:#fff;--bs-carousel-control-icon-filter: }[data-bs-theme=dark]{--bs-carousel-indicator-active-bg:#000;--bs-carousel-caption-color:#000;--bs-carousel-control-icon-filter:invert(1) grayscale(100)}.spinner-border,.spinner-grow{display:inline-block;flex-shrink:0;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed)linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-border-width:0.25em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-border;border:var(--bs-spinner-border-width)solid;border-right-color:transparent}.spinner-border-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem;--bs-spinner-border-width:0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width:2rem;--bs-spinner-height:2rem;--bs-spinner-vertical-align:-0.125em;--bs-spinner-animation-speed:0.75s;--bs-spinner-animation-name:spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width:1rem;--bs-spinner-height:1rem}@media(prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed:1.5s}}.offcanvas,.offcanvas-lg,.offcanvas-md,.offcanvas-sm,.offcanvas-xl,.offcanvas-xxl{--bs-offcanvas-zindex:1045;--bs-offcanvas-width:400px;--bs-offcanvas-height:30vh;--bs-offcanvas-padding-x:2rem;--bs-offcanvas-padding-y:2rem;--bs-offcanvas-color:var(--bs-body-color);--bs-offcanvas-bg:var(--bs-body-bg);--bs-offcanvas-border-width:var(--bs-border-width);--bs-offcanvas-border-color:var(--bs-border-color-translucent);--bs-offcanvas-box-shadow:var(--bs-box-shadow-sm);--bs-offcanvas-transition:transform 0.3s ease-in-out;--bs-offcanvas-title-line-height:1.5}@media(max-width:575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width:575.98px) and (prefers-reduced-motion:reduce){.offcanvas-sm{transition:none}}@media(max-width:575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.show:not(.hiding),.offcanvas-sm.showing{transform:none}.offcanvas-sm.hiding,.offcanvas-sm.show,.offcanvas-sm.showing{visibility:visible}}@media(min-width:576px){.offcanvas-sm{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:initial!important}}@media(max-width:767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width:767.98px) and (prefers-reduced-motion:reduce){.offcanvas-md{transition:none}}@media(max-width:767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.show:not(.hiding),.offcanvas-md.showing{transform:none}.offcanvas-md.hiding,.offcanvas-md.show,.offcanvas-md.showing{visibility:visible}}@media(min-width:768px){.offcanvas-md{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:initial!important}}@media(max-width:991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width:991.98px) and (prefers-reduced-motion:reduce){.offcanvas-lg{transition:none}}@media(max-width:991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.show:not(.hiding),.offcanvas-lg.showing{transform:none}.offcanvas-lg.hiding,.offcanvas-lg.show,.offcanvas-lg.showing{visibility:visible}}@media(min-width:992px){.offcanvas-lg{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:initial!important}}@media(max-width:1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width:1199.98px) and (prefers-reduced-motion:reduce){.offcanvas-xl{transition:none}}@media(max-width:1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.show:not(.hiding),.offcanvas-xl.showing{transform:none}.offcanvas-xl.hiding,.offcanvas-xl.show,.offcanvas-xl.showing{visibility:visible}}@media(min-width:1200px){.offcanvas-xl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:initial!important}}@media(max-width:1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width:1399.98px) and (prefers-reduced-motion:reduce){.offcanvas-xxl{transition:none}}@media(max-width:1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.show:not(.hiding),.offcanvas-xxl.showing{transform:none}.offcanvas-xxl.hiding,.offcanvas-xxl.show,.offcanvas-xxl.showing{visibility:visible}}@media(min-width:1400px){.offcanvas-xxl{--bs-offcanvas-height:auto;--bs-offcanvas-border-width:0;background-color:initial!important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible;background-color:initial!important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media(prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width)solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.show:not(.hiding),.offcanvas.showing{transform:none}.offcanvas.hiding,.offcanvas.show,.offcanvas.showing{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;padding:var(--bs-offcanvas-padding-y)var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y) * .5)calc(var(--bs-offcanvas-padding-x) * .5);margin-top:calc(-.5 * var(--bs-offcanvas-padding-y));margin-right:calc(-.5 * var(--bs-offcanvas-padding-x));margin-bottom:calc(-.5 * var(--bs-offcanvas-padding-y));margin-left:auto}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;padding:var(--bs-offcanvas-padding-y)var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-primary{color:#fff!important;background-color:RGBA(var(--bs-primary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-secondary{color:#000!important;background-color:RGBA(var(--bs-secondary-rgb),var(--bs-bg-opacity,1))!important}.text-bg-success{color:#fff!important;background-color:RGBA(var(--bs-success-rgb),var(--bs-bg-opacity,1))!important}.text-bg-info{color:#fff!important;background-color:RGBA(var(--bs-info-rgb),var(--bs-bg-opacity,1))!important}.text-bg-warning{color:#fff!important;background-color:RGBA(var(--bs-warning-rgb),var(--bs-bg-opacity,1))!important}.text-bg-danger{color:#fff!important;background-color:RGBA(var(--bs-danger-rgb),var(--bs-bg-opacity,1))!important}.text-bg-light{color:#000!important;background-color:RGBA(var(--bs-light-rgb),var(--bs-bg-opacity,1))!important}.text-bg-dark{color:#fff!important;background-color:RGBA(var(--bs-dark-rgb),var(--bs-bg-opacity,1))!important}.link-primary{color:RGBA(var(--bs-primary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-primary-rgb),var(--bs-link-underline-opacity,1))!important}.link-primary:focus,.link-primary:hover{color:RGBA(186,40,105,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(186,40,105,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(186,40,105,var(--bs-link-underline-opacity,1))!important}.link-secondary{color:RGBA(var(--bs-secondary-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-secondary-rgb),var(--bs-link-underline-opacity,1))!important}.link-secondary:focus,.link-secondary:hover{color:RGBA(255,255,255,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(255,255,255,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(255,255,255,var(--bs-link-underline-opacity,1))!important}.link-success{color:RGBA(var(--bs-success-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-success-rgb),var(--bs-link-underline-opacity,1))!important}.link-success:focus,.link-success:hover{color:RGBA(52,172,134,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(52,172,134,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(52,172,134,var(--bs-link-underline-opacity,1))!important}.link-info{color:RGBA(var(--bs-info-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-info-rgb),var(--bs-link-underline-opacity,1))!important}.link-info:focus,.link-info:hover{color:RGBA(46,162,201,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(46,162,201,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(46,162,201,var(--bs-link-underline-opacity,1))!important}.link-warning{color:RGBA(var(--bs-warning-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-warning-rgb),var(--bs-link-underline-opacity,1))!important}.link-warning:focus,.link-warning:hover{color:RGBA(204,154,6,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(204,154,6,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(204,154,6,var(--bs-link-underline-opacity,1))!important}.link-danger{color:RGBA(var(--bs-danger-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-danger-rgb),var(--bs-link-underline-opacity,1))!important}.link-danger:focus,.link-danger:hover{color:RGBA(202,101,16,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(202,101,16,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(202,101,16,var(--bs-link-underline-opacity,1))!important}.link-light{color:RGBA(var(--bs-light-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-light-rgb),var(--bs-link-underline-opacity,1))!important}.link-light:focus,.link-light:hover{color:RGBA(237,237,237,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(237,237,237,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(237,237,237,var(--bs-link-underline-opacity,1))!important}.link-dark{color:RGBA(var(--bs-dark-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-dark-rgb),var(--bs-link-underline-opacity,1))!important}.link-dark:focus,.link-dark:hover{color:RGBA(26,30,33,var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(26,30,33,var(--bs-link-underline-opacity,1))!important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,1))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-body-emphasis:focus,.link-body-emphasis:hover{color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-opacity,.75))!important;-webkit-text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,.75))!important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb),var(--bs-link-underline-opacity,.75))!important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x,0)var(--bs-focus-ring-y,0)var(--bs-focus-ring-blur,0)var(--bs-focus-ring-width)var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,.5));text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,.5));text-underline-offset:.25em;-webkit-backface-visibility:hidden;backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:.2s ease-in-out transform}@media(prefers-reduced-motion:reduce){.icon-link>.bi{transition:none}}.icon-link-hover:focus-visible>.bi,.icon-link-hover:hover>.bi{transform:var(--bs-icon-link-transform,translate3d(.25em,0,0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}@media(min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media(min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media(min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media(min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}@media(min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:-webkit-sticky;position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption),.visually-hidden:not(caption){position:absolute!important}.visually-hidden *,.visually-hidden-focusable:not(:focus):not(:focus-within) *{overflow:hidden!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:var(--bs-border-width);min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.object-fit-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-none{-o-object-fit:none!important;object-fit:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.overflow-x-auto{overflow-x:auto!important}.overflow-x-hidden{overflow-x:hidden!important}.overflow-x-visible{overflow-x:visible!important}.overflow-x-scroll{overflow-x:scroll!important}.overflow-y-auto{overflow-y:auto!important}.overflow-y-hidden{overflow-y:hidden!important}.overflow-y-visible{overflow-y:visible!important}.overflow-y-scroll{overflow-y:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-inline-grid{display:inline-grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:var(--bs-box-shadow)!important}.shadow-sm{box-shadow:var(--bs-box-shadow-sm)!important}.shadow-lg{box-shadow:var(--bs-box-shadow-lg)!important}.shadow-none{box-shadow:none!important}.focus-ring-primary{--bs-focus-ring-color:rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color:rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color:rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color:rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color:rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color:rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color:rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color:rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:var(--bs-border-width)var(--bs-border-style)var(--bs-border-color)!important}.border-0{border:0!important}.border-top{border-top:var(--bs-border-width)var(--bs-border-style)var(--bs-border-color)!important}.border-top-0{border-top:0!important}.border-end{border-right:var(--bs-border-width)var(--bs-border-style)var(--bs-border-color)!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:var(--bs-border-width)var(--bs-border-style)var(--bs-border-color)!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:var(--bs-border-width)var(--bs-border-style)var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-primary{--bs-border-opacity:1;border-color:rgba(var(--bs-primary-rgb),var(--bs-border-opacity))!important}.border-secondary{--bs-border-opacity:1;border-color:rgba(var(--bs-secondary-rgb),var(--bs-border-opacity))!important}.border-success{--bs-border-opacity:1;border-color:rgba(var(--bs-success-rgb),var(--bs-border-opacity))!important}.border-info{--bs-border-opacity:1;border-color:rgba(var(--bs-info-rgb),var(--bs-border-opacity))!important}.border-warning{--bs-border-opacity:1;border-color:rgba(var(--bs-warning-rgb),var(--bs-border-opacity))!important}.border-danger{--bs-border-opacity:1;border-color:rgba(var(--bs-danger-rgb),var(--bs-border-opacity))!important}.border-light{--bs-border-opacity:1;border-color:rgba(var(--bs-light-rgb),var(--bs-border-opacity))!important}.border-dark{--bs-border-opacity:1;border-color:rgba(var(--bs-dark-rgb),var(--bs-border-opacity))!important}.border-black{--bs-border-opacity:1;border-color:rgba(var(--bs-black-rgb),var(--bs-border-opacity))!important}.border-white{--bs-border-opacity:1;border-color:rgba(var(--bs-white-rgb),var(--bs-border-opacity))!important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle)!important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle)!important}.border-success-subtle{border-color:var(--bs-success-border-subtle)!important}.border-info-subtle{border-color:var(--bs-info-border-subtle)!important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle)!important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle)!important}.border-light-subtle{border-color:var(--bs-light-border-subtle)!important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle)!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.border-opacity-10{--bs-border-opacity:0.1}.border-opacity-25{--bs-border-opacity:0.25}.border-opacity-50{--bs-border-opacity:0.5}.border-opacity-75{--bs-border-opacity:0.75}.border-opacity-100{--bs-border-opacity:1}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.5rem!important}.m-2{margin:1rem!important}.m-3{margin:2rem!important}.m-4{margin:3rem!important}.m-5{margin:6rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.5rem!important;margin-left:.5rem!important}.mx-2{margin-right:1rem!important;margin-left:1rem!important}.mx-3{margin-right:2rem!important;margin-left:2rem!important}.mx-4{margin-right:3rem!important;margin-left:3rem!important}.mx-5{margin-right:6rem!important;margin-left:6rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-2{margin-top:1rem!important;margin-bottom:1rem!important}.my-3{margin-top:2rem!important;margin-bottom:2rem!important}.my-4{margin-top:3rem!important;margin-bottom:3rem!important}.my-5{margin-top:6rem!important;margin-bottom:6rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.5rem!important}.mt-2{margin-top:1rem!important}.mt-3{margin-top:2rem!important}.mt-4{margin-top:3rem!important}.mt-5{margin-top:6rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.5rem!important}.me-2{margin-right:1rem!important}.me-3{margin-right:2rem!important}.me-4{margin-right:3rem!important}.me-5{margin-right:6rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.5rem!important}.mb-2{margin-bottom:1rem!important}.mb-3{margin-bottom:2rem!important}.mb-4{margin-bottom:3rem!important}.mb-5{margin-bottom:6rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.5rem!important}.ms-2{margin-left:1rem!important}.ms-3{margin-left:2rem!important}.ms-4{margin-left:3rem!important}.ms-5{margin-left:6rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.5rem!important}.p-2{padding:1rem!important}.p-3{padding:2rem!important}.p-4{padding:3rem!important}.p-5{padding:6rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.5rem!important;padding-left:.5rem!important}.px-2{padding-right:1rem!important;padding-left:1rem!important}.px-3{padding-right:2rem!important;padding-left:2rem!important}.px-4{padding-right:3rem!important;padding-left:3rem!important}.px-5{padding-right:6rem!important;padding-left:6rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-2{padding-top:1rem!important;padding-bottom:1rem!important}.py-3{padding-top:2rem!important;padding-bottom:2rem!important}.py-4{padding-top:3rem!important;padding-bottom:3rem!important}.py-5{padding-top:6rem!important;padding-bottom:6rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.5rem!important}.pt-2{padding-top:1rem!important}.pt-3{padding-top:2rem!important}.pt-4{padding-top:3rem!important}.pt-5{padding-top:6rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.5rem!important}.pe-2{padding-right:1rem!important}.pe-3{padding-right:2rem!important}.pe-4{padding-right:3rem!important}.pe-5{padding-right:6rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.5rem!important}.pb-2{padding-bottom:1rem!important}.pb-3{padding-bottom:2rem!important}.pb-4{padding-bottom:3rem!important}.pb-5{padding-bottom:6rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.5rem!important}.ps-2{padding-left:1rem!important}.ps-3{padding-left:2rem!important}.ps-4{padding-left:3rem!important}.ps-5{padding-left:6rem!important}.gap-0{gap:0!important}.gap-1{gap:.5rem!important}.gap-2{gap:1rem!important}.gap-3{gap:2rem!important}.gap-4{gap:3rem!important}.gap-5{gap:6rem!important}.row-gap-0{row-gap:0!important}.row-gap-1{row-gap:.5rem!important}.row-gap-2{row-gap:1rem!important}.row-gap-3{row-gap:2rem!important}.row-gap-4{row-gap:3rem!important}.row-gap-5{row-gap:6rem!important}.column-gap-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-1{-moz-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-2{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-3{-moz-column-gap:2rem!important;column-gap:2rem!important}.column-gap-4{-moz-column-gap:3rem!important;column-gap:3rem!important}.column-gap-5{-moz-column-gap:6rem!important;column-gap:6rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-lighter{font-weight:lighter!important}.fw-light{font-weight:300!important}.fw-normal{font-weight:400!important}.fw-medium{font-weight:500!important}.fw-semibold{font-weight:600!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-body-secondary{--bs-text-opacity:1;color:var(--bs-secondary-color)!important}.text-body-tertiary{--bs-text-opacity:1;color:var(--bs-tertiary-color)!important}.text-body-emphasis{--bs-text-opacity:1;color:var(--bs-emphasis-color)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis)!important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis)!important}.text-success-emphasis{color:var(--bs-success-text-emphasis)!important}.text-info-emphasis{color:var(--bs-info-text-emphasis)!important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis)!important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis)!important}.text-light-emphasis{color:var(--bs-light-text-emphasis)!important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis)!important}.link-opacity-10{--bs-link-opacity:0.1}.link-opacity-10-hover:hover{--bs-link-opacity:0.1}.link-opacity-25{--bs-link-opacity:0.25}.link-opacity-25-hover:hover{--bs-link-opacity:0.25}.link-opacity-50{--bs-link-opacity:0.5}.link-opacity-50-hover:hover{--bs-link-opacity:0.5}.link-opacity-75{--bs-link-opacity:0.75}.link-opacity-75-hover:hover{--bs-link-opacity:0.75}.link-opacity-100{--bs-link-opacity:1}.link-opacity-100-hover:hover{--bs-link-opacity:1}.link-offset-1{text-underline-offset:.125em!important}.link-offset-1-hover:hover{text-underline-offset:.125em!important}.link-offset-2{text-underline-offset:.25em!important}.link-offset-2-hover:hover{text-underline-offset:.25em!important}.link-offset-3{text-underline-offset:.375em!important}.link-offset-3-hover:hover{text-underline-offset:.375em!important}.link-underline-primary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-primary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-secondary{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-secondary-rgb),var(--bs-link-underline-opacity))!important}.link-underline-success{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-success-rgb),var(--bs-link-underline-opacity))!important}.link-underline-info{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-info-rgb),var(--bs-link-underline-opacity))!important}.link-underline-warning{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-warning-rgb),var(--bs-link-underline-opacity))!important}.link-underline-danger{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-danger-rgb),var(--bs-link-underline-opacity))!important}.link-underline-light{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-light-rgb),var(--bs-link-underline-opacity))!important}.link-underline-dark{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important;text-decoration-color:rgba(var(--bs-dark-rgb),var(--bs-link-underline-opacity))!important}.link-underline{--bs-link-underline-opacity:1;-webkit-text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important;text-decoration-color:rgba(var(--bs-link-color-rgb),var(--bs-link-underline-opacity,1))!important}.link-underline-opacity-0{--bs-link-underline-opacity:0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity:0}.link-underline-opacity-10{--bs-link-underline-opacity:0.1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity:0.1}.link-underline-opacity-25{--bs-link-underline-opacity:0.25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity:0.25}.link-underline-opacity-50{--bs-link-underline-opacity:0.5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity:0.5}.link-underline-opacity-75{--bs-link-underline-opacity:0.75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity:0.75}.link-underline-opacity-100{--bs-link-underline-opacity:1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:initial!important}.bg-body-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-bg-rgb),var(--bs-bg-opacity))!important}.bg-body-tertiary{--bs-bg-opacity:1;background-color:rgba(var(--bs-tertiary-bg-rgb),var(--bs-bg-opacity))!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle)!important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle)!important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle)!important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle)!important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle)!important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle)!important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle)!important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle)!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:var(--bs-border-radius)!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:var(--bs-border-radius-sm)!important}.rounded-2{border-radius:var(--bs-border-radius)!important}.rounded-3{border-radius:var(--bs-border-radius-lg)!important}.rounded-4{border-radius:var(--bs-border-radius-xl)!important}.rounded-5{border-radius:var(--bs-border-radius-xxl)!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:var(--bs-border-radius-pill)!important}.rounded-top{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-0{border-top-left-radius:0!important;border-top-right-radius:0!important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm)!important;border-top-right-radius:var(--bs-border-radius-sm)!important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius)!important;border-top-right-radius:var(--bs-border-radius)!important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg)!important;border-top-right-radius:var(--bs-border-radius-lg)!important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl)!important;border-top-right-radius:var(--bs-border-radius-xl)!important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl)!important;border-top-right-radius:var(--bs-border-radius-xxl)!important}.rounded-top-circle{border-top-left-radius:50%!important;border-top-right-radius:50%!important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill)!important;border-top-right-radius:var(--bs-border-radius-pill)!important}.rounded-end{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-0{border-top-right-radius:0!important;border-bottom-right-radius:0!important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm)!important;border-bottom-right-radius:var(--bs-border-radius-sm)!important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius)!important;border-bottom-right-radius:var(--bs-border-radius)!important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg)!important;border-bottom-right-radius:var(--bs-border-radius-lg)!important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl)!important;border-bottom-right-radius:var(--bs-border-radius-xl)!important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-right-radius:var(--bs-border-radius-xxl)!important}.rounded-end-circle{border-top-right-radius:50%!important;border-bottom-right-radius:50%!important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill)!important;border-bottom-right-radius:var(--bs-border-radius-pill)!important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-0{border-bottom-right-radius:0!important;border-bottom-left-radius:0!important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm)!important;border-bottom-left-radius:var(--bs-border-radius-sm)!important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius)!important;border-bottom-left-radius:var(--bs-border-radius)!important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg)!important;border-bottom-left-radius:var(--bs-border-radius-lg)!important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl)!important;border-bottom-left-radius:var(--bs-border-radius-xl)!important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl)!important;border-bottom-left-radius:var(--bs-border-radius-xxl)!important}.rounded-bottom-circle{border-bottom-right-radius:50%!important;border-bottom-left-radius:50%!important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill)!important;border-bottom-left-radius:var(--bs-border-radius-pill)!important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-0{border-bottom-left-radius:0!important;border-top-left-radius:0!important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm)!important;border-top-left-radius:var(--bs-border-radius-sm)!important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius)!important;border-top-left-radius:var(--bs-border-radius)!important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg)!important;border-top-left-radius:var(--bs-border-radius-lg)!important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl)!important;border-top-left-radius:var(--bs-border-radius-xl)!important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl)!important;border-top-left-radius:var(--bs-border-radius-xxl)!important}.rounded-start-circle{border-bottom-left-radius:50%!important;border-top-left-radius:50%!important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill)!important;border-top-left-radius:var(--bs-border-radius-pill)!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}.z-n1{z-index:-1!important}.z-0{z-index:0!important}.z-1{z-index:1!important}.z-2{z-index:2!important}.z-3{z-index:3!important}@media(min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.object-fit-sm-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-sm-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-sm-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-sm-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-sm-none{-o-object-fit:none!important;object-fit:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-inline-grid{display:inline-grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.5rem!important}.m-sm-2{margin:1rem!important}.m-sm-3{margin:2rem!important}.m-sm-4{margin:3rem!important}.m-sm-5{margin:6rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-2{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-3{margin-right:2rem!important;margin-left:2rem!important}.mx-sm-4{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-5{margin-right:6rem!important;margin-left:6rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-2{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-3{margin-top:2rem!important;margin-bottom:2rem!important}.my-sm-4{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-5{margin-top:6rem!important;margin-bottom:6rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.5rem!important}.mt-sm-2{margin-top:1rem!important}.mt-sm-3{margin-top:2rem!important}.mt-sm-4{margin-top:3rem!important}.mt-sm-5{margin-top:6rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.5rem!important}.me-sm-2{margin-right:1rem!important}.me-sm-3{margin-right:2rem!important}.me-sm-4{margin-right:3rem!important}.me-sm-5{margin-right:6rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.5rem!important}.mb-sm-2{margin-bottom:1rem!important}.mb-sm-3{margin-bottom:2rem!important}.mb-sm-4{margin-bottom:3rem!important}.mb-sm-5{margin-bottom:6rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.5rem!important}.ms-sm-2{margin-left:1rem!important}.ms-sm-3{margin-left:2rem!important}.ms-sm-4{margin-left:3rem!important}.ms-sm-5{margin-left:6rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.5rem!important}.p-sm-2{padding:1rem!important}.p-sm-3{padding:2rem!important}.p-sm-4{padding:3rem!important}.p-sm-5{padding:6rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-2{padding-right:1rem!important;padding-left:1rem!important}.px-sm-3{padding-right:2rem!important;padding-left:2rem!important}.px-sm-4{padding-right:3rem!important;padding-left:3rem!important}.px-sm-5{padding-right:6rem!important;padding-left:6rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-2{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-3{padding-top:2rem!important;padding-bottom:2rem!important}.py-sm-4{padding-top:3rem!important;padding-bottom:3rem!important}.py-sm-5{padding-top:6rem!important;padding-bottom:6rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.5rem!important}.pt-sm-2{padding-top:1rem!important}.pt-sm-3{padding-top:2rem!important}.pt-sm-4{padding-top:3rem!important}.pt-sm-5{padding-top:6rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.5rem!important}.pe-sm-2{padding-right:1rem!important}.pe-sm-3{padding-right:2rem!important}.pe-sm-4{padding-right:3rem!important}.pe-sm-5{padding-right:6rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.5rem!important}.pb-sm-2{padding-bottom:1rem!important}.pb-sm-3{padding-bottom:2rem!important}.pb-sm-4{padding-bottom:3rem!important}.pb-sm-5{padding-bottom:6rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.5rem!important}.ps-sm-2{padding-left:1rem!important}.ps-sm-3{padding-left:2rem!important}.ps-sm-4{padding-left:3rem!important}.ps-sm-5{padding-left:6rem!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.5rem!important}.gap-sm-2{gap:1rem!important}.gap-sm-3{gap:2rem!important}.gap-sm-4{gap:3rem!important}.gap-sm-5{gap:6rem!important}.row-gap-sm-0{row-gap:0!important}.row-gap-sm-1{row-gap:.5rem!important}.row-gap-sm-2{row-gap:1rem!important}.row-gap-sm-3{row-gap:2rem!important}.row-gap-sm-4{row-gap:3rem!important}.row-gap-sm-5{row-gap:6rem!important}.column-gap-sm-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-sm-1{-moz-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-sm-2{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-sm-3{-moz-column-gap:2rem!important;column-gap:2rem!important}.column-gap-sm-4{-moz-column-gap:3rem!important;column-gap:3rem!important}.column-gap-sm-5{-moz-column-gap:6rem!important;column-gap:6rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media(min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.object-fit-md-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-md-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-md-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-md-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-md-none{-o-object-fit:none!important;object-fit:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-inline-grid{display:inline-grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.5rem!important}.m-md-2{margin:1rem!important}.m-md-3{margin:2rem!important}.m-md-4{margin:3rem!important}.m-md-5{margin:6rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-2{margin-right:1rem!important;margin-left:1rem!important}.mx-md-3{margin-right:2rem!important;margin-left:2rem!important}.mx-md-4{margin-right:3rem!important;margin-left:3rem!important}.mx-md-5{margin-right:6rem!important;margin-left:6rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-2{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-3{margin-top:2rem!important;margin-bottom:2rem!important}.my-md-4{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-5{margin-top:6rem!important;margin-bottom:6rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.5rem!important}.mt-md-2{margin-top:1rem!important}.mt-md-3{margin-top:2rem!important}.mt-md-4{margin-top:3rem!important}.mt-md-5{margin-top:6rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.5rem!important}.me-md-2{margin-right:1rem!important}.me-md-3{margin-right:2rem!important}.me-md-4{margin-right:3rem!important}.me-md-5{margin-right:6rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.5rem!important}.mb-md-2{margin-bottom:1rem!important}.mb-md-3{margin-bottom:2rem!important}.mb-md-4{margin-bottom:3rem!important}.mb-md-5{margin-bottom:6rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.5rem!important}.ms-md-2{margin-left:1rem!important}.ms-md-3{margin-left:2rem!important}.ms-md-4{margin-left:3rem!important}.ms-md-5{margin-left:6rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.5rem!important}.p-md-2{padding:1rem!important}.p-md-3{padding:2rem!important}.p-md-4{padding:3rem!important}.p-md-5{padding:6rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-2{padding-right:1rem!important;padding-left:1rem!important}.px-md-3{padding-right:2rem!important;padding-left:2rem!important}.px-md-4{padding-right:3rem!important;padding-left:3rem!important}.px-md-5{padding-right:6rem!important;padding-left:6rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-2{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-3{padding-top:2rem!important;padding-bottom:2rem!important}.py-md-4{padding-top:3rem!important;padding-bottom:3rem!important}.py-md-5{padding-top:6rem!important;padding-bottom:6rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.5rem!important}.pt-md-2{padding-top:1rem!important}.pt-md-3{padding-top:2rem!important}.pt-md-4{padding-top:3rem!important}.pt-md-5{padding-top:6rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.5rem!important}.pe-md-2{padding-right:1rem!important}.pe-md-3{padding-right:2rem!important}.pe-md-4{padding-right:3rem!important}.pe-md-5{padding-right:6rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.5rem!important}.pb-md-2{padding-bottom:1rem!important}.pb-md-3{padding-bottom:2rem!important}.pb-md-4{padding-bottom:3rem!important}.pb-md-5{padding-bottom:6rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.5rem!important}.ps-md-2{padding-left:1rem!important}.ps-md-3{padding-left:2rem!important}.ps-md-4{padding-left:3rem!important}.ps-md-5{padding-left:6rem!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.5rem!important}.gap-md-2{gap:1rem!important}.gap-md-3{gap:2rem!important}.gap-md-4{gap:3rem!important}.gap-md-5{gap:6rem!important}.row-gap-md-0{row-gap:0!important}.row-gap-md-1{row-gap:.5rem!important}.row-gap-md-2{row-gap:1rem!important}.row-gap-md-3{row-gap:2rem!important}.row-gap-md-4{row-gap:3rem!important}.row-gap-md-5{row-gap:6rem!important}.column-gap-md-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-md-1{-moz-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-md-2{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-md-3{-moz-column-gap:2rem!important;column-gap:2rem!important}.column-gap-md-4{-moz-column-gap:3rem!important;column-gap:3rem!important}.column-gap-md-5{-moz-column-gap:6rem!important;column-gap:6rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media(min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.object-fit-lg-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-lg-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-lg-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-lg-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-lg-none{-o-object-fit:none!important;object-fit:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-inline-grid{display:inline-grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.5rem!important}.m-lg-2{margin:1rem!important}.m-lg-3{margin:2rem!important}.m-lg-4{margin:3rem!important}.m-lg-5{margin:6rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-2{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-3{margin-right:2rem!important;margin-left:2rem!important}.mx-lg-4{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-5{margin-right:6rem!important;margin-left:6rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-2{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-3{margin-top:2rem!important;margin-bottom:2rem!important}.my-lg-4{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-5{margin-top:6rem!important;margin-bottom:6rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.5rem!important}.mt-lg-2{margin-top:1rem!important}.mt-lg-3{margin-top:2rem!important}.mt-lg-4{margin-top:3rem!important}.mt-lg-5{margin-top:6rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.5rem!important}.me-lg-2{margin-right:1rem!important}.me-lg-3{margin-right:2rem!important}.me-lg-4{margin-right:3rem!important}.me-lg-5{margin-right:6rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.5rem!important}.mb-lg-2{margin-bottom:1rem!important}.mb-lg-3{margin-bottom:2rem!important}.mb-lg-4{margin-bottom:3rem!important}.mb-lg-5{margin-bottom:6rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.5rem!important}.ms-lg-2{margin-left:1rem!important}.ms-lg-3{margin-left:2rem!important}.ms-lg-4{margin-left:3rem!important}.ms-lg-5{margin-left:6rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.5rem!important}.p-lg-2{padding:1rem!important}.p-lg-3{padding:2rem!important}.p-lg-4{padding:3rem!important}.p-lg-5{padding:6rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-2{padding-right:1rem!important;padding-left:1rem!important}.px-lg-3{padding-right:2rem!important;padding-left:2rem!important}.px-lg-4{padding-right:3rem!important;padding-left:3rem!important}.px-lg-5{padding-right:6rem!important;padding-left:6rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-2{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-3{padding-top:2rem!important;padding-bottom:2rem!important}.py-lg-4{padding-top:3rem!important;padding-bottom:3rem!important}.py-lg-5{padding-top:6rem!important;padding-bottom:6rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.5rem!important}.pt-lg-2{padding-top:1rem!important}.pt-lg-3{padding-top:2rem!important}.pt-lg-4{padding-top:3rem!important}.pt-lg-5{padding-top:6rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.5rem!important}.pe-lg-2{padding-right:1rem!important}.pe-lg-3{padding-right:2rem!important}.pe-lg-4{padding-right:3rem!important}.pe-lg-5{padding-right:6rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.5rem!important}.pb-lg-2{padding-bottom:1rem!important}.pb-lg-3{padding-bottom:2rem!important}.pb-lg-4{padding-bottom:3rem!important}.pb-lg-5{padding-bottom:6rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.5rem!important}.ps-lg-2{padding-left:1rem!important}.ps-lg-3{padding-left:2rem!important}.ps-lg-4{padding-left:3rem!important}.ps-lg-5{padding-left:6rem!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.5rem!important}.gap-lg-2{gap:1rem!important}.gap-lg-3{gap:2rem!important}.gap-lg-4{gap:3rem!important}.gap-lg-5{gap:6rem!important}.row-gap-lg-0{row-gap:0!important}.row-gap-lg-1{row-gap:.5rem!important}.row-gap-lg-2{row-gap:1rem!important}.row-gap-lg-3{row-gap:2rem!important}.row-gap-lg-4{row-gap:3rem!important}.row-gap-lg-5{row-gap:6rem!important}.column-gap-lg-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-lg-1{-moz-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-lg-2{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-lg-3{-moz-column-gap:2rem!important;column-gap:2rem!important}.column-gap-lg-4{-moz-column-gap:3rem!important;column-gap:3rem!important}.column-gap-lg-5{-moz-column-gap:6rem!important;column-gap:6rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media(min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.object-fit-xl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xl-none{-o-object-fit:none!important;object-fit:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-inline-grid{display:inline-grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.5rem!important}.m-xl-2{margin:1rem!important}.m-xl-3{margin:2rem!important}.m-xl-4{margin:3rem!important}.m-xl-5{margin:6rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-2{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-3{margin-right:2rem!important;margin-left:2rem!important}.mx-xl-4{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-5{margin-right:6rem!important;margin-left:6rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-2{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-3{margin-top:2rem!important;margin-bottom:2rem!important}.my-xl-4{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-5{margin-top:6rem!important;margin-bottom:6rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.5rem!important}.mt-xl-2{margin-top:1rem!important}.mt-xl-3{margin-top:2rem!important}.mt-xl-4{margin-top:3rem!important}.mt-xl-5{margin-top:6rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.5rem!important}.me-xl-2{margin-right:1rem!important}.me-xl-3{margin-right:2rem!important}.me-xl-4{margin-right:3rem!important}.me-xl-5{margin-right:6rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.5rem!important}.mb-xl-2{margin-bottom:1rem!important}.mb-xl-3{margin-bottom:2rem!important}.mb-xl-4{margin-bottom:3rem!important}.mb-xl-5{margin-bottom:6rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.5rem!important}.ms-xl-2{margin-left:1rem!important}.ms-xl-3{margin-left:2rem!important}.ms-xl-4{margin-left:3rem!important}.ms-xl-5{margin-left:6rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.5rem!important}.p-xl-2{padding:1rem!important}.p-xl-3{padding:2rem!important}.p-xl-4{padding:3rem!important}.p-xl-5{padding:6rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-2{padding-right:1rem!important;padding-left:1rem!important}.px-xl-3{padding-right:2rem!important;padding-left:2rem!important}.px-xl-4{padding-right:3rem!important;padding-left:3rem!important}.px-xl-5{padding-right:6rem!important;padding-left:6rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-2{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-3{padding-top:2rem!important;padding-bottom:2rem!important}.py-xl-4{padding-top:3rem!important;padding-bottom:3rem!important}.py-xl-5{padding-top:6rem!important;padding-bottom:6rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.5rem!important}.pt-xl-2{padding-top:1rem!important}.pt-xl-3{padding-top:2rem!important}.pt-xl-4{padding-top:3rem!important}.pt-xl-5{padding-top:6rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.5rem!important}.pe-xl-2{padding-right:1rem!important}.pe-xl-3{padding-right:2rem!important}.pe-xl-4{padding-right:3rem!important}.pe-xl-5{padding-right:6rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.5rem!important}.pb-xl-2{padding-bottom:1rem!important}.pb-xl-3{padding-bottom:2rem!important}.pb-xl-4{padding-bottom:3rem!important}.pb-xl-5{padding-bottom:6rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.5rem!important}.ps-xl-2{padding-left:1rem!important}.ps-xl-3{padding-left:2rem!important}.ps-xl-4{padding-left:3rem!important}.ps-xl-5{padding-left:6rem!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.5rem!important}.gap-xl-2{gap:1rem!important}.gap-xl-3{gap:2rem!important}.gap-xl-4{gap:3rem!important}.gap-xl-5{gap:6rem!important}.row-gap-xl-0{row-gap:0!important}.row-gap-xl-1{row-gap:.5rem!important}.row-gap-xl-2{row-gap:1rem!important}.row-gap-xl-3{row-gap:2rem!important}.row-gap-xl-4{row-gap:3rem!important}.row-gap-xl-5{row-gap:6rem!important}.column-gap-xl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xl-1{-moz-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-xl-2{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xl-3{-moz-column-gap:2rem!important;column-gap:2rem!important}.column-gap-xl-4{-moz-column-gap:3rem!important;column-gap:3rem!important}.column-gap-xl-5{-moz-column-gap:6rem!important;column-gap:6rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media(min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.object-fit-xxl-contain{-o-object-fit:contain!important;object-fit:contain!important}.object-fit-xxl-cover{-o-object-fit:cover!important;object-fit:cover!important}.object-fit-xxl-fill{-o-object-fit:fill!important;object-fit:fill!important}.object-fit-xxl-scale{-o-object-fit:scale-down!important;object-fit:scale-down!important}.object-fit-xxl-none{-o-object-fit:none!important;object-fit:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-inline-grid{display:inline-grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.5rem!important}.m-xxl-2{margin:1rem!important}.m-xxl-3{margin:2rem!important}.m-xxl-4{margin:3rem!important}.m-xxl-5{margin:6rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-2{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-3{margin-right:2rem!important;margin-left:2rem!important}.mx-xxl-4{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-5{margin-right:6rem!important;margin-left:6rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-2{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-3{margin-top:2rem!important;margin-bottom:2rem!important}.my-xxl-4{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-5{margin-top:6rem!important;margin-bottom:6rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.5rem!important}.mt-xxl-2{margin-top:1rem!important}.mt-xxl-3{margin-top:2rem!important}.mt-xxl-4{margin-top:3rem!important}.mt-xxl-5{margin-top:6rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.5rem!important}.me-xxl-2{margin-right:1rem!important}.me-xxl-3{margin-right:2rem!important}.me-xxl-4{margin-right:3rem!important}.me-xxl-5{margin-right:6rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.5rem!important}.mb-xxl-2{margin-bottom:1rem!important}.mb-xxl-3{margin-bottom:2rem!important}.mb-xxl-4{margin-bottom:3rem!important}.mb-xxl-5{margin-bottom:6rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.5rem!important}.ms-xxl-2{margin-left:1rem!important}.ms-xxl-3{margin-left:2rem!important}.ms-xxl-4{margin-left:3rem!important}.ms-xxl-5{margin-left:6rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.5rem!important}.p-xxl-2{padding:1rem!important}.p-xxl-3{padding:2rem!important}.p-xxl-4{padding:3rem!important}.p-xxl-5{padding:6rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-2{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-3{padding-right:2rem!important;padding-left:2rem!important}.px-xxl-4{padding-right:3rem!important;padding-left:3rem!important}.px-xxl-5{padding-right:6rem!important;padding-left:6rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-2{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-3{padding-top:2rem!important;padding-bottom:2rem!important}.py-xxl-4{padding-top:3rem!important;padding-bottom:3rem!important}.py-xxl-5{padding-top:6rem!important;padding-bottom:6rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.5rem!important}.pt-xxl-2{padding-top:1rem!important}.pt-xxl-3{padding-top:2rem!important}.pt-xxl-4{padding-top:3rem!important}.pt-xxl-5{padding-top:6rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.5rem!important}.pe-xxl-2{padding-right:1rem!important}.pe-xxl-3{padding-right:2rem!important}.pe-xxl-4{padding-right:3rem!important}.pe-xxl-5{padding-right:6rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.5rem!important}.pb-xxl-2{padding-bottom:1rem!important}.pb-xxl-3{padding-bottom:2rem!important}.pb-xxl-4{padding-bottom:3rem!important}.pb-xxl-5{padding-bottom:6rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.5rem!important}.ps-xxl-2{padding-left:1rem!important}.ps-xxl-3{padding-left:2rem!important}.ps-xxl-4{padding-left:3rem!important}.ps-xxl-5{padding-left:6rem!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.5rem!important}.gap-xxl-2{gap:1rem!important}.gap-xxl-3{gap:2rem!important}.gap-xxl-4{gap:3rem!important}.gap-xxl-5{gap:6rem!important}.row-gap-xxl-0{row-gap:0!important}.row-gap-xxl-1{row-gap:.5rem!important}.row-gap-xxl-2{row-gap:1rem!important}.row-gap-xxl-3{row-gap:2rem!important}.row-gap-xxl-4{row-gap:3rem!important}.row-gap-xxl-5{row-gap:6rem!important}.column-gap-xxl-0{-moz-column-gap:0!important;column-gap:0!important}.column-gap-xxl-1{-moz-column-gap:.5rem!important;column-gap:.5rem!important}.column-gap-xxl-2{-moz-column-gap:1rem!important;column-gap:1rem!important}.column-gap-xxl-3{-moz-column-gap:2rem!important;column-gap:2rem!important}.column-gap-xxl-4{-moz-column-gap:3rem!important;column-gap:3rem!important}.column-gap-xxl-5{-moz-column-gap:6rem!important;column-gap:6rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media(min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-inline-grid{display:inline-grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}}body{background-image:linear-gradient(90deg,#21373f,#5e62b0,#700936)}[data-bs-theme=dark] body{color:#fff}.table-secondary{--bs-table-hover-color:$white}.btn-secondary{color:#fff;border:none}.btn-secondary:focus,.btn-secondary:hover{color:#fff}.btn-secondary.disabled{color:#fff}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:0}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:0}.bg-light .btn{background-color:rgba(0,0,0,.2)}.form-control::-webkit-file-upload-button{background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);border:none}.form-control::file-selector-button,.input-group-text{background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);border:none}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:rgba(255,255,255,.1)}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:rgba(255,255,255,.1)}.input-group-text{box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1)}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:0;border-left:none}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-right:none}.form-select:not([multiple]){transition:border-color .15s ease-in-out}.form-select:not([multiple]) option{color:#000}.bg-light .form-control{color:#343a40;border-color:rgba(0,0,0,.2)}.bg-light .form-control::-moz-placeholder{color:rgba(0,0,0,.2)}.bg-light .form-control::placeholder{color:rgba(0,0,0,.2)}.dropdown-menu{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.dropdown-menu a{color:#fff}.dropdown-menu .text-muted{color:rgba(255,255,255,.7)!important}.nav-tabs .nav-link{transition:none}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{color:#fff;isolation:auto}.nav-tabs .nav-link,.nav-tabs .nav-link.disabled{background-color:rgba(0,0,0,.2)}.nav-tabs .nav-item:first-child>.nav-link{border-radius:.5rem 0 0 .5rem}.nav-tabs .nav-item:last-child>.nav-link{border-radius:0 .5rem .5rem 0}.nav-tabs .nav-item .nav-link.active,.nav-tabs .nav-item.show .nav-link{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,#fff,rgba(255,255,255,.9) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);border-radius:.5rem;transform:scale(1.1)}.nav-tabs .nav-item .nav-link.active a,.nav-tabs .nav-item.show .nav-link a{color:#fff}.nav-tabs .nav-item .nav-link.active .text-muted,.nav-tabs .nav-item.show .nav-link .text-muted{color:rgba(255,255,255,.7)!important}.nav-tabs .nav-item .nav-link.active:focus,.nav-tabs .nav-item .nav-link.active:hover,.nav-tabs .nav-item.show .nav-link:focus,.nav-tabs .nav-item.show .nav-link:hover{color:#343a40}.nav-tabs .dropdown-menu{border-radius:.5rem}.nav-pills .nav-link{transition:none}.nav-pills .nav-link:focus,.nav-pills .nav-link:hover{color:#fff;isolation:auto}.alert{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);position:relative;overflow:hidden;color:#fff}.alert a{color:#fff}.alert .text-muted{color:rgba(255,255,255,.7)!important}.alert-primary::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#e83283}.alert-secondary::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:rgba(255,255,255,.4)}.alert-success::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#41d7a7}.alert-info::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#39cbfb}.alert-warning::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#ffc107}.alert-danger::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#fd7e14}.alert-light::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#e9e9e8}.alert-dark::after{position:absolute;top:0;left:0;width:.5rem;height:100%;content:"";background-color:#212529}.alert .alert-link{color:#fff}.badge.bg-secondary{--bs-bg-opacity:.4}.badge.bg-light{color:#343a40}.list-group{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.list-group a{color:#fff}.list-group .text-muted{color:rgba(255,255,255,.7)!important}.card{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);--bs-secondary-rgb:transparent}.card a{color:#fff}.card .text-muted{color:rgba(255,255,255,.7)!important}.card.bg-primary{background-image:none}.card.bg-success{background-image:none}.card.bg-info{background-image:none}.card.bg-warning{background-image:none}.card.bg-danger{background-image:none}.card.bg-light{background-image:none}.card.bg-dark{background-image:none}.card.bg-light .card-body,.card.bg-light .card-header{color:#343a40}.card.border-primary{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-primary a{color:#fff}.card.border-primary .text-muted{color:rgba(255,255,255,.7)!important}.card.border-primary .card-header{background-color:#e83283!important;border-bottom:none}.card.border-secondary{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-secondary a{color:#fff}.card.border-secondary .text-muted{color:rgba(255,255,255,.7)!important}.card.border-secondary .card-header{background-color:rgba(255,255,255,.4)!important;border-bottom:none}.card.border-success{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-success a{color:#fff}.card.border-success .text-muted{color:rgba(255,255,255,.7)!important}.card.border-success .card-header{background-color:#41d7a7!important;border-bottom:none}.card.border-info{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-info a{color:#fff}.card.border-info .text-muted{color:rgba(255,255,255,.7)!important}.card.border-info .card-header{background-color:#39cbfb!important;border-bottom:none}.card.border-warning{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-warning a{color:#fff}.card.border-warning .text-muted{color:rgba(255,255,255,.7)!important}.card.border-warning .card-header{background-color:#ffc107!important;border-bottom:none}.card.border-danger{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-danger a{color:#fff}.card.border-danger .text-muted{color:rgba(255,255,255,.7)!important}.card.border-danger .card-header{background-color:#fd7e14!important;border-bottom:none}.card.border-light{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-light a{color:#fff}.card.border-light .text-muted{color:rgba(255,255,255,.7)!important}.card.border-light .card-header{background-color:#e9e9e8!important;border-bottom:none;color:#343a40}.card.border-dark{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.card.border-dark a{color:#fff}.card.border-dark .text-muted{color:rgba(255,255,255,.7)!important}.card.border-dark .card-header{background-color:#212529!important;border-bottom:none}.card-header{font-weight:700}.accordion-item{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px);box-shadow:none}.accordion-item a{color:#fff}.accordion-item .text-muted{color:rgba(255,255,255,.7)!important}.toast{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.toast a{color:#fff}.toast .text-muted{color:rgba(255,255,255,.7)!important}.popover{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.popover a{color:#fff}.popover .text-muted{color:rgba(255,255,255,.7)!important}.popover-header{border-bottom-color:rgba(255,255,255,.2)}.tooltip{-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.modal-content{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.modal-content a{color:#fff}.modal-content .text-muted{color:rgba(255,255,255,.7)!important}.offcanvas{border:none;box-shadow:inset 1px 1px rgba(255,255,255,.2),inset -1px -1px rgba(255,255,255,.1),1px 3px 24px -1px rgba(0,0,0,.15);background-color:initial;background-image:linear-gradient(125deg,rgba(255,255,255,.3),rgba(255,255,255,.2) 70%);-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.offcanvas a{color:#fff}.offcanvas .text-muted{color:rgba(255,255,255,.7)!important}/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src/Yavsc.Server/Directory.Packages.props b/src/Yavsc.Server/Directory.Packages.props new file mode 100644 index 000000000..661f437f0 --- /dev/null +++ b/src/Yavsc.Server/Directory.Packages.props @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/Yavsc.Server/Helpers/EstimateSignatureFileHelper.cs b/src/Yavsc.Server/Helpers/EstimateSignatureFileHelper.cs new file mode 100644 index 000000000..29062bc3c --- /dev/null +++ b/src/Yavsc.Server/Helpers/EstimateSignatureFileHelper.cs @@ -0,0 +1,158 @@ +#nullable enable annotations + +using System.Security.Claims; +using System.Text; +using System.Text.Json; +using Microsoft.AspNetCore.Http; +using Yavsc.Models.Billing; +using Yavsc.Server.Models.FileSystem; +namespace Yavsc.Server.Helpers; + +/// +/// Filesystem counterpart of : writes +/// the wire-format JSON payload next to the user's other files, +/// under signatures/, and updates the user's disk quota. +/// +/// This is the JSON counterpart of the legacy +/// ReceiveProSignatureAsync method, which stored +/// sign-{billingCode}-{signType}-{estimateId}.png blobs. +/// We don't reuse that helper because (a) the wire format is no +/// longer a binary image, (b) there's no billingCode on +/// a freshly signed estimate in our model, and (c) the legacy +/// helper takes an whereas our pipeline +/// decodes a JSON body upstream of the controller and passes +/// in directly. +/// +public static class EstimateSignatureFileHelper +{ + /// + /// Sub-directory under the user's root where signature + /// payloads live. Kept short to leave room in PATH_MAX on + /// legacy filesystems; the rest of the filename is + /// sign-{type}-{estimateId}-{utcTicks}.json. + /// + public const string SignaturesSubdir = "signatures"; + + /// + /// Format string for signature file names. Public so the + /// migration and the admin tools can list by pattern. + /// + public static string FileNameFormat(SignatureType type, long estimateId, long utcTicks) + => $"sign-{type.ToString().ToLowerInvariant()}-{estimateId}-{utcTicks}.json"; + + /// + /// Persist a signature wire payload to disk. Returns the + /// file info (relative path under the user's root) suitable + /// for storing in ; the + /// caller is responsible for the database write. + /// + /// Signed-in user. Their + /// Identity.Name locates the disk root via + /// . + /// + /// Estimate this signature + /// attaches to. Used in the file name for human inspection + /// and to support multiple versions over time. + /// Provider or client signature. + /// Decoded wire payload (strokes + + /// coordinateMax + capturedAtUtc). Already validated + /// upstream. + /// Cancellation token forwarded to + /// the file write. + public static async Task ReceiveEstimateSignatureAsync( + this ClaimsPrincipal user, + long estimateId, + SignatureType type, + SignaturePadPayload payload, + SiteSettings siteSettings, + CancellationToken token = default) + { + if (user is null) throw new ArgumentNullException(nameof(user)); + if (payload is null) throw new ArgumentNullException(nameof(payload)); + if (estimateId <= 0) throw new ArgumentOutOfRangeException(nameof(estimateId)); + + // Ensure the user has a /signatures/ sub-directory we can + // write to. EnsureDestinationDirectory throws on invalid + // paths and creates the directory on the way; the + // SignaturesSubdir constant is a server-controlled value + // (not user-derived), so we skip the IsValidYavscPath + // check that ReceiveUserFile performs on user-supplied + // subpaths. + var root = user.EnsureDestinationDirectory(SignaturesSubdir, siteSettings); + + var fileName = FileNameFormat(type, estimateId, DateTime.UtcNow.Ticks); + var fullPath = Path.Combine(root, fileName); + + var envelope = new + { + format = "yavsc.signature/v1", + coordinateMax = payload.CoordinateMax, + capturedAtUtc = payload.CapturedAtUtc, + estimateId, + type = type.ToString(), + // Identity.Name is the username; we keep the wire + // payload keyed on the username rather than the + // numeric/guid Id so disk-side human inspection + // (e.g. cat sign-pro-1234-...json) is self-evident. + signerName = user.Identity?.Name, + strokes = payload.Strokes, + strokeCount = CountStrokes(payload.Strokes), + }; + + var json = JsonSerializer.Serialize(envelope, new JsonSerializerOptions { WriteIndented = true }); + await File.WriteAllTextAsync(fullPath, json, Encoding.UTF8, token).ConfigureAwait(false); + + // Quota update is the controller's responsibility: the + // helper has no DbContext access, and a ClaimsPrincipal + // is not an ApplicationUser. The controller looks up + // the user by Identity.Name and bumps DiskUsage after + // a successful database write. + + return new FileReceivedInfo(root, fileName); + } + + private static int CountStrokes(int[] strokes) + { + int n = 0; + for (int i = 0; i < strokes.Length;) + { + int k = strokes[i]; + if (k <= 0) break; + n++; + i += 1 + 2 * k; + } + return n; + } +} + +/// +/// Wire payload accepted by the signature endpoint and +/// persisted by . +/// Mirrors PostIt.Models.SignaturePadData's JSON shape +/// (without the disk-only envelope fields) so the two sides +/// stay trivially compatible. +/// +public class SignaturePadPayload +{ + /// + /// Normalised coordinate upper bound. Must be + /// PostIt.Models.SignaturePadData.CoordinateMax + /// (10_000) today; declared as a property so a future + /// resolution change can be replayed against the same + /// wire format. + /// + public int CoordinateMax { get; set; } = 10_000; + + /// + /// Client-reported capture time. The server may ignore + /// this for ordering (UTC now is the truth) but keeps it + /// for round-trip display. + /// + public DateTime CapturedAtUtc { get; set; } + + /// + /// Wire strokes. See + /// PostIt.Models.SignaturePadData for the format. + /// + public int[] Strokes { get; set; } = Array.Empty(); +} diff --git a/src/Yavsc.Server/Helpers/EstimateSignaturePersister.cs b/src/Yavsc.Server/Helpers/EstimateSignaturePersister.cs deleted file mode 100644 index be8848e11..000000000 --- a/src/Yavsc.Server/Helpers/EstimateSignaturePersister.cs +++ /dev/null @@ -1,111 +0,0 @@ -#nullable enable annotations - -using System.Security.Claims; -using Microsoft.EntityFrameworkCore; -using Yavsc.Models; -using Yavsc.Models.Billing; - -namespace Yavsc.Server.Helpers; - -/// -/// Shared signature-persistence logic for estimates, extracted from -/// BillingController.Sign. Stages the -/// row (find-or-add by (EstimateId, Type)) for the PostIt -/// wire-format payload — but does not call -/// SaveChanges. The caller owns the transaction so it can -/// combine the signature write with its own changes (e.g. setting a -/// validation date or a query status) in a single -/// SaveChanges. -/// -/// -/// The column is the source of -/// truth; there is no on-disk copy, so no disk-quota accounting is -/// involved. -/// -/// -public static class EstimateSignaturePersister -{ - /// - /// Stage a signature for signed by - /// as . Upserts - /// the row for the - /// (EstimateId, Type) pair, writing the wire payload - /// (Strokes, CoordinateMax, CapturedAtUtc) - /// and . Returns the staged - /// ; the caller persists with - /// SaveChanges. - /// - public static async Task StageAsync( - ApplicationDbContext db, - long estimateId, - SignatureType type, - string signerId, - SignaturePadPayload payload, - CancellationToken token = default) - { - if (db is null) throw new ArgumentNullException(nameof(db)); - if (payload is null) throw new ArgumentNullException(nameof(payload)); - if (estimateId <= 0) throw new ArgumentOutOfRangeException(nameof(estimateId)); - - // Find-or-add: the (EstimateId, Type) pair is unique, so a - // second submission for the same side of the estimate - // replaces the previous signature. EF translates this into a - // single UPDATE when the row exists and an INSERT otherwise; - // the unique index in ApplicationDbContext is the - // database-level guarantee that the contract holds if two - // requests race. - var signature = await db.Signatures - .FirstOrDefaultAsync(s => s.EstimateId == estimateId && s.Type == type, token) - .ConfigureAwait(false); - - if (signature is null) - { - signature = new Signature - { - EstimateId = estimateId, - SignerId = signerId, - Type = type, - }; - db.Signatures.Add(signature); - } - - signature.SignerId = signerId; - signature.CoordinateMax = payload.CoordinateMax; - signature.Strokes = payload.Strokes; - signature.CapturedAtUtc = payload.CapturedAtUtc; - - return signature; - } -} - -/// -/// Wire payload accepted by the signature path and staged by -/// . Mirrors -/// PostIt.Models.SignaturePadData's JSON shape (without the -/// disk-only envelope fields) so the two sides stay trivially -/// compatible. -/// -public class SignaturePadPayload -{ - /// - /// Normalised coordinate upper bound. Must be - /// PostIt.Models.SignaturePadData.CoordinateMax - /// (10_000) today; declared as a property so a future - /// resolution change can be replayed against the same - /// wire format. - /// - public int CoordinateMax { get; set; } = 10_000; - - /// - /// Client-reported capture time. The server may ignore - /// this for ordering (UTC now is the truth) but keeps it - /// for round-trip display. - /// - public DateTime CapturedAtUtc { get; set; } - - /// - /// Wire strokes. See - /// PostIt.Models.SignaturePadData for the format. - /// - public int[] Strokes { get; set; } = Array.Empty(); -} \ No newline at end of file diff --git a/src/Yavsc.Server/Helpers/FileSystemHelpers.cs b/src/Yavsc.Server/Helpers/FileSystemHelpers.cs index e0d5b8b9d..bc7b9c81d 100644 --- a/src/Yavsc.Server/Helpers/FileSystemHelpers.cs +++ b/src/Yavsc.Server/Helpers/FileSystemHelpers.cs @@ -1,17 +1,19 @@ #nullable enable annotations + + + using System.Security.Claims; using Microsoft.AspNetCore.Html; using Microsoft.Extensions.FileProviders; -using Microsoft.AspNetCore.Http; -using ImageMagick; using Yavsc.Models; using Yavsc.Models.Streaming; using Yavsc.ViewModels; +using Microsoft.AspNetCore.Http; using Yavsc.Exceptions; using Yavsc.Abstract.Helpers; +using ImageMagick; using Yavsc.Server.Models.FileSystem; using Yavsc.ViewModels.UserFiles; - namespace Yavsc.Server.Helpers { public static class FileSystemHelpers @@ -32,7 +34,7 @@ namespace Yavsc.Server.Helpers { var item = new FileReceivedInfo( settings.Bills, - AbstractFileSystemHelpers.SignFileNameFormat(signType, billingCode, estimateId)); + AbstractFileSystemHelpers.SignFileNameFormat("pro", billingCode, estimateId)); var fi = new FileInfo(item.FullName); if (fi.Exists) item.Overridden = true; @@ -44,7 +46,7 @@ namespace Yavsc.Server.Helpers this ClaimsPrincipal user, string subpath, SiteSettings siteSettings) { - var root = Path.Combine(siteSettings.Blog, user.GetUserName()); + var root = Path.Combine(siteSettings.Blog, user.Identity.Name); if (!string.IsNullOrWhiteSpace(subpath)) { if (!subpath.IsValidYavscPath()) @@ -70,12 +72,8 @@ namespace Yavsc.Server.Helpers var root = Path.Combine(siteSettings.Blog, user.UserName); var fi = new FileInfo(Path.Combine(root, fileName)); if (!fi.Exists) return; - // Capture the length BEFORE deleting: FileInfo.Length throws - // FileNotFoundException once the file is gone, which would - // surface as a 400 even though the delete already succeeded. - var length = fi.Length; fi.Delete(); - user.DiskUsage -= length; + user.DiskUsage -= fi.Length; } public static FsOperationInfo DeleteUserDirOrFile(this ApplicationUser user, string dirName, SiteSettings siteSettings) @@ -89,15 +87,8 @@ namespace Yavsc.Server.Helpers { var fi = new FileInfo(Path.Combine(root, dirName)); if (!fi.Exists) return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.NotFound, ErrorMessage = "non existent" }; - // Capture the length BEFORE deleting: FileInfo.Length - // throws FileNotFoundException once the file is gone, so - // reading it after fi.Delete() would throw — the caller's - // catch turns that into a 400 even though the file was - // already removed (the user sees "file does not exist" - // while the delete in fact succeeded). - var length = fi.Length; fi.Delete(); - user.DiskUsage -= length; + user.DiskUsage -= fi.Length; } else { diff --git a/src/Yavsc.Server/Helpers/ServiceExtensions.cs b/src/Yavsc.Server/Helpers/ServiceExtensions.cs index 397eada85..f20d50476 100644 --- a/src/Yavsc.Server/Helpers/ServiceExtensions.cs +++ b/src/Yavsc.Server/Helpers/ServiceExtensions.cs @@ -1,6 +1,5 @@ #nullable enable annotations -using IdentityModel; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Configuration; @@ -108,27 +107,12 @@ public static class ServiceExtensions ValidateAudience = true, ValidAudiences = audiences, RoleClaimType = Constants.RoleClaimType, - // IdentityServer's ProfileService emits the name claim as - // JwtClaimTypes.Name ("name", short form). MapInboundClaims - // (below) is disabled, so it stays "name" on the principal — - // NameClaimType must therefore be "name" too, or - // User.Identity.Name / GetUserName() resolve to null. The - // previous Constants.NameClaimType (the Microsoft long URI) - // never matched the remapped xmlsoap URI either, so the name - // was unreachable on every bearer host regardless of scope. - NameClaimType = JwtClaimTypes.Name, + NameClaimType = Constants.NameClaimType, }; - // Keep JWT claim names as-emitted ("sub", "name", …). The default - // inbound map rewrites "name" → xmlsoap name URI and "sub" → - // nameid URI, which neither Constants.NameClaimType (microsoft - // URI) nor GetUserName()'s FindFirstValue("name") could match. - // Roles are already emitted as Constants.RoleClaimType (a long - // URI), so disabling the map leaves IsInMsRole() working. - options.MapInboundClaims = false; + options.MapInboundClaims = true; options.ClaimsIssuer = authority; options.Audience = audiences[0]; - // Dev: every Yavsc resource service (Yavsc.Api, Yavsc.Blogs, // Yavsc.Org itself) validates JWTs against the OP that runs // on https://localhost:5001 with a self-signed dev cert. diff --git a/src/Yavsc.Server/Helpers/UserHelpers.cs b/src/Yavsc.Server/Helpers/UserHelpers.cs index 4511308be..f0a30995f 100644 --- a/src/Yavsc.Server/Helpers/UserHelpers.cs +++ b/src/Yavsc.Server/Helpers/UserHelpers.cs @@ -14,9 +14,9 @@ namespace Yavsc.Server.Helpers if (readerId == null) { var userPosts = dbContext.blogSpotPublications.Include( - b => b.Post - ).Where(x => x.Post.AuthorId == posterId) - .Select(x => x.Post).ToArray(); + b => b.BlogPost + ).Where(x => x.BlogPost.AuthorId == posterId) + .Select(x => x.BlogPost).ToArray(); return userPosts; } else diff --git a/src/Yavsc.Server/Models/ApplicationDbContext.cs b/src/Yavsc.Server/Models/ApplicationDbContext.cs index 85ed8f8d9..76933bcad 100644 --- a/src/Yavsc.Server/Models/ApplicationDbContext.cs +++ b/src/Yavsc.Server/Models/ApplicationDbContext.cs @@ -509,9 +509,5 @@ namespace Yavsc.Models public DbSet ModerationLogs { get; set; } public DbSet NominativeServiceCommands { get; set; } - - public DbSet EstimateAttachedFiles { get; set; } - - public DbSet QueryAttachedFiles { get; set; } } } diff --git a/src/Yavsc.Server/Models/Billing/AttachFileRequest.cs b/src/Yavsc.Server/Models/Billing/AttachFileRequest.cs deleted file mode 100644 index 204237bd4..000000000 --- a/src/Yavsc.Server/Models/Billing/AttachFileRequest.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Yavsc.Models.Billing -{ - /// - /// Corps de requête pour attacher un fichier de l'espace perso à un - /// devis (fournisseur) ou à une demande (client), par référence à son - /// identifiant . - /// - public class AttachFileRequest - { - /// Identifiant du fichier référencé. - public long FileId { get; set; } - } -} \ No newline at end of file diff --git a/src/Yavsc.Server/Models/Billing/Estimate.cs b/src/Yavsc.Server/Models/Billing/Estimate.cs index 8a26faab9..74680e7b7 100644 --- a/src/Yavsc.Server/Models/Billing/Estimate.cs +++ b/src/Yavsc.Server/Models/Billing/Estimate.cs @@ -8,7 +8,6 @@ namespace Yavsc.Models.Billing { using Models.Workflow; using Newtonsoft.Json; - using System.Linq; public partial class Estimate : IEstimate { @@ -79,39 +78,14 @@ namespace Yavsc.Models.Billing public DateTime ClientValidationDate { get; set; } /// - /// All signatures captured against this estimate, keyed by - /// . The persister upserts by - /// (EstimateId, Type), so there is at most one row per type. - /// This collection is the persisted shape and stays - /// [JsonIgnore]: the wire payload exposes signatures - /// only through the dedicated and - /// projections, since an - /// estimate has exactly these two signature slots and never - /// more. See . + /// All signatures captured against this estimate, in + /// capture order. Multiple versions per (Type, SignerId) + /// are allowed; the controller reads the most recent + /// when asked. See . /// [InverseProperty(nameof(Signature.Estimate))] - [JsonIgnore, System.Text.Json.Serialization.JsonIgnore] public virtual ICollection Signatures { get; set; } = new List(); - /// - /// The provider's signature on this estimate, or null when the - /// provider has not signed. Projected from - /// ; not mapped (the table is - /// ). - /// - [NotMapped] - public Signature? SignaturePro - => Signatures?.FirstOrDefault(s => s.Type == SignatureType.Pro); - - /// - /// The client's signature on this estimate, or null when the - /// client has not signed. Projected from - /// ; not mapped. - /// - [NotMapped] - public Signature? SignatureClient - => Signatures?.FirstOrDefault(s => s.Type == SignatureType.Client); - } } diff --git a/src/Yavsc.Server/Models/Billing/EstimateAttachedFile.cs b/src/Yavsc.Server/Models/Billing/EstimateAttachedFile.cs deleted file mode 100644 index c4b7ccf2b..000000000 --- a/src/Yavsc.Server/Models/Billing/EstimateAttachedFile.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore; -using Yavsc.Models.Blog; - -namespace Yavsc.Models.Billing -{ - /// - /// Pièce jointe d'un devis, attachée **par référence** à un fichier - /// de l'espace de stockage personnel du fournisseur - /// (). Le fichier reste dans l'espace perso - /// du fournisseur (pas de duplication) ; seul le lien est persisté. - /// Le téléchargement est servi par le host Blogs, qui autorise - /// l'appelant (fournisseur ou client du devis) via cette table. - /// - [PrimaryKey(nameof(FileId), nameof(EstimateId))] - public class EstimateAttachedFile - { - /// - /// Identifiant du fichier référencé (partie de la clé composite). - /// - public long FileId { get; set; } - - [ForeignKey("FileId")] - public virtual UploadedFile File { get; set; } - - /// - /// Identifiant du devis auquel le fichier est rattaché - /// (partie de la clé composite). - /// - public long EstimateId { get; set; } - - [ForeignKey("EstimateId")] - public virtual Estimate Estimate { get; set; } - } -} \ No newline at end of file diff --git a/src/Yavsc.Server/Models/Billing/QueryAttachedFile.cs b/src/Yavsc.Server/Models/Billing/QueryAttachedFile.cs deleted file mode 100644 index 853fe252f..000000000 --- a/src/Yavsc.Server/Models/Billing/QueryAttachedFile.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.ComponentModel.DataAnnotations.Schema; -using Microsoft.EntityFrameworkCore; -using Yavsc.Models.Blog; - -namespace Yavsc.Models.Billing -{ - /// - /// Pièce jointe d'une demande (NominativeServiceCommand), attachée - /// **par référence** à un fichier de l'espace de stockage personnel - /// du client (). Le fichier reste dans - /// l'espace perso du client (pas de duplication) ; seul le lien est - /// persisté. Le téléchargement est servi par le host Blogs, qui - /// autorise l'appelant (client ou fournisseur de la demande) via - /// cette table. - /// - [PrimaryKey(nameof(FileId), nameof(CommandId))] - public class QueryAttachedFile - { - /// - /// Identifiant du fichier référencé (partie de la clé composite). - /// - public long FileId { get; set; } - - [ForeignKey("FileId")] - public virtual UploadedFile File { get; set; } - - /// - /// Identifiant de la demande à laquelle le fichier est rattaché - /// (partie de la clé composite). - /// - public long CommandId { get; set; } - - [ForeignKey("CommandId")] - public virtual NominativeServiceCommand Command { get; set; } - } -} \ No newline at end of file diff --git a/src/Yavsc.Server/Models/Billing/Signature.cs b/src/Yavsc.Server/Models/Billing/Signature.cs index 7e568dbd1..10da97f1b 100644 --- a/src/Yavsc.Server/Models/Billing/Signature.cs +++ b/src/Yavsc.Server/Models/Billing/Signature.cs @@ -7,8 +7,8 @@ namespace Yavsc.Models.Billing; /// /// One captured signature, attached to a single /// . Multiple versions are allowed per -/// (EstimateId, Type, SignerId) tuple — the controller reads the -/// most recent when asked. The wire-format payload is the +/// (EstimateId, Type, SignerId) tuple — the controller reads +/// the most recent when asked. The wire-format payload is the /// same int[] shape PostIt produces (see /// PostIt.Models.SignaturePadData): a length-prefixed /// sequence of strokes, each stroke being @@ -16,12 +16,13 @@ namespace Yavsc.Models.Billing; /// CoordinateMax]. /// /// -/// The column is the source of truth: the -/// signature is captured and stored entirely in-app (PostIt) and -/// round-tripped as JSON, with no on-disk copy. A dedicated table -/// (instead of a JSON column on ) keeps the -/// row narrow for list views and preserves -/// superseded versions for audit. +/// Why a separate table (instead of a JSON column on +/// ): the jalon 1 spec calls for at least +/// two distinct signatures per estimate cycle (provider +/// validation + client agreement) and the audit value of +/// preserving superseded versions. A dedicated table also keeps +/// the row narrow, which matters for +/// list views. /// /// public class Signature @@ -31,7 +32,7 @@ public class Signature public long EstimateId { get; set; } - [ForeignKey(nameof(EstimateId)), JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ForeignKey(nameof(EstimateId)), JsonIgnore] public virtual Estimate Estimate { get; set; } /// @@ -48,7 +49,7 @@ public class Signature [Required] public string SignerId { get; set; } - [ForeignKey(nameof(SignerId)), JsonIgnore, System.Text.Json.Serialization.JsonIgnore] + [ForeignKey(nameof(SignerId)), JsonIgnore] public virtual ApplicationUser Signer { get; set; } public SignatureType Type { get; set; } @@ -74,4 +75,16 @@ public class Signature public int[] Strokes { get; set; } = Array.Empty(); public DateTime CapturedAtUtc { get; set; } -} \ No newline at end of file + + /// + /// Path to the JSON-serialised wire payload on disk, + /// relative to UserFilesDirName. The disk copy is the + /// source of truth for the wire bytes; the + /// column is a denormalised index for queries. They are + /// written together in the same transaction by the + /// controller; the migration should keep them in sync + /// through ApplicationDbContext.SaveChanges. + /// + [Required] + public string FilePath { get; set; } +} diff --git a/src/Yavsc.Abstract/Billing/SignatureType.cs b/src/Yavsc.Server/Models/Billing/SignatureType.cs similarity index 99% rename from src/Yavsc.Abstract/Billing/SignatureType.cs rename to src/Yavsc.Server/Models/Billing/SignatureType.cs index 5a27f5cdd..7c27924fd 100644 --- a/src/Yavsc.Abstract/Billing/SignatureType.cs +++ b/src/Yavsc.Server/Models/Billing/SignatureType.cs @@ -12,4 +12,4 @@ public enum SignatureType { Pro = 0, Client = 1, -} \ No newline at end of file +} diff --git a/src/Yavsc.Server/Models/Blog/BlogAttachedFile.cs b/src/Yavsc.Server/Models/Blog/BlogAttachedFile.cs index 4b024e637..a857665d6 100644 --- a/src/Yavsc.Server/Models/Blog/BlogAttachedFile.cs +++ b/src/Yavsc.Server/Models/Blog/BlogAttachedFile.cs @@ -1,4 +1,3 @@ -#nullable enable annotations using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; @@ -34,19 +33,6 @@ namespace Yavsc.Models.Blog /// /// public string ContentType { get; set; } - - /// - /// Propriétaire du fichier dans l'espace de stockage personnel. - /// Renseigné pour les fichiers téléversés via api/v1/fs - /// (référencés par / - /// ). Reste null pour les - /// pièces jointes de blog historiques (), - /// qui ne sont pas adressées par propriétaire. - /// - public string? OwnerId { get; set; } - - [ForeignKey("OwnerId")] - public virtual ApplicationUser? Owner { get; set; } } [PrimaryKey(nameof(FileId), nameof(PostId))] diff --git a/src/Yavsc.Server/Models/Blog/BlogPost.cs b/src/Yavsc.Server/Models/Blog/BlogPost.cs index e05bf5600..6dc8ff750 100644 --- a/src/Yavsc.Server/Models/Blog/BlogPost.cs +++ b/src/Yavsc.Server/Models/Blog/BlogPost.cs @@ -57,7 +57,7 @@ namespace Yavsc.Models.Blog get; set; } - [Display(Name = "Utilisateur ayant modifié le dernier")] + [Display(Name = "Utilisateur ayant modifé le dernier")] public string? UserModified { get; set; @@ -96,23 +96,14 @@ namespace Yavsc.Models.Blog [InverseProperty("Post")] public virtual List Comments { get; set; } - [InverseProperty("Post")] - public virtual BlogSpotPublication Publication { get; set; } - /// /// Whether this post is published. Not a column: the /// existence of a row in BlogSpotPublication /// is the source of truth. EF skips this property via - /// [NotMapped] so no migration is needed. - /// - /// This is a value BlogSpotService owns and - /// sets explicitly on every post it returns — a single - /// bulk lookup against blogSpotPublications for a - /// list, an AnyAsync for a single post — so the - /// flag is correct whether or not the EF query Included - /// the navigation. Clients - /// (PostIt) read it as-is off the wire and must not - /// recompute it. + /// [NotMapped] so no migration is needed. The + /// service hydrates it after each fetch (single bulk + /// lookup, not N+1) and it surfaces through the wire + /// as part of the JSON-serialised BlogPost. /// [NotMapped] public bool IsPublished { get; set; } @@ -156,7 +147,5 @@ namespace Yavsc.Models.Blog }).ToList() ?? new List(); } } - - } } diff --git a/src/Yavsc.Server/Models/Blog/BlogPostEditViewModel.cs b/src/Yavsc.Server/Models/Blog/BlogPostEditViewModel.cs index 5c3c1e225..152e2e9a6 100644 --- a/src/Yavsc.Server/Models/Blog/BlogPostEditViewModel.cs +++ b/src/Yavsc.Server/Models/Blog/BlogPostEditViewModel.cs @@ -6,7 +6,7 @@ namespace Yavsc.Models.Blog public BlogPostEditViewModel() { } - + public BlogPostEditViewModel(BlogPost post, bool publish) { Id = post.Id; diff --git a/src/Yavsc.Server/Models/Blog/BlogspotPublication.cs b/src/Yavsc.Server/Models/Blog/BlogspotPublication.cs index c724f4821..bcae52218 100644 --- a/src/Yavsc.Server/Models/Blog/BlogspotPublication.cs +++ b/src/Yavsc.Server/Models/Blog/BlogspotPublication.cs @@ -7,9 +7,9 @@ namespace Yavsc.Models public class BlogSpotPublication { [Key] - public long PostId { get; set; } - - [ForeignKey("PostId")] - public virtual BlogPost Post{ get; set; } + public long BlogpostId { get; set; } + + [ForeignKey("BlogpostId")] + public virtual BlogPost BlogPost{ get; set; } } } diff --git a/src/Yavsc.Server/Models/FileSystem/FileReceivedInfo.cs b/src/Yavsc.Server/Models/FileSystem/FileReceivedInfo.cs index 602cc4bf0..63627c116 100644 --- a/src/Yavsc.Server/Models/FileSystem/FileReceivedInfo.cs +++ b/src/Yavsc.Server/Models/FileSystem/FileReceivedInfo.cs @@ -43,29 +43,11 @@ namespace Yavsc.Server.Models.FileSystem } public string DestDir { get; set; } - + public string FileName { get; set; } public bool Overridden { get; set; } public bool QuotaOffense { get; set; } - - /// - /// Identifiant de la ligne - /// créée à l'enregistrement du fichier dans l'espace perso - /// (api/v1/fs). Nul si aucune métadonnée n'a été persistée. - /// - public long? FileId { get; set; } - - /// - /// Taille du fichier reçu (octets), pour le reflet côté métadonnées. - /// - public long Length { get; set; } - - /// - /// Type MIME du fichier reçu. - /// - public string ContentType { get; set; } - public string FullName { get => Path.Combine(DestDir, FileName); } } diff --git a/src/Yavsc.Server/Services/BlogSpotService.cs b/src/Yavsc.Server/Services/BlogSpotService.cs index 5d2ad5eaa..b4bf4c7ad 100644 --- a/src/Yavsc.Server/Services/BlogSpotService.cs +++ b/src/Yavsc.Server/Services/BlogSpotService.cs @@ -61,7 +61,7 @@ public class BlogSpotService blogFilesSubdir); var di = new DirectoryInfo(destDir); - if (!di.Exists) + if (!di.Exists) { logger.LogInformation("Creating directory for blog attachments: {destDir}", destDir); di.Create(); @@ -108,17 +108,10 @@ public class BlogSpotService public BlogPost Create(string userId, BlogPost post ) { // Sauvegarder le post d'abord pour obtenir son ID - // Le créateur vient de l'authentification, donc on ne le prend pas du post + // Le createur vient de l'authentification, donc on ne le prend pas du post post.AuthorId = userId; _context.BlogSpot.Add(post); _context.SaveChanges(userId); - // A freshly created post has no BlogSpotPublication row, - // so the server-side truth is "not published". The client - // may have sent isPublished:true in the POST body, but - // publication is toggled separately via SetPublishAsync; - // overwrite whatever was deserialized so the create - // response reflects the actual state. - post.IsPublished = false; return post; } @@ -133,7 +126,7 @@ public class BlogSpotService if (!auth.Succeeded) throw new AuthorizationFailureException(auth); - var pub = await _context.blogSpotPublications.AnyAsync(x => x.PostId == blog.Id); + var pub = await _context.blogSpotPublications.AnyAsync(x => x.BlogpostId == blog.Id); ScrubAclForViewer(blog, user); return new BlogPostEditViewModel(blog, pub); @@ -146,24 +139,21 @@ public class BlogSpotService .Include(p => p.Tags) .Include(p => p.Comments) .Include(p => p.ACL) - .Include(p => p.Publication) .SingleAsync(m => m.Id == blogPostId); if (blog == null) return null; + // Hydrate le flag [NotMapped] depuis la table de publication. + blog.IsPublished = await _context.blogSpotPublications + .AnyAsync(pub => pub.BlogpostId == blogPostId); + var auth = await _authorizationService.AuthorizeAsync(user, blog, new ReadPermission()); if (!auth.Succeeded) throw new AuthorizationFailureException(auth); ScrubAclForViewer(blog, user); - // IsPublished is a value the service serves to clients; - // set it from the loaded Publication navigation (Details - // Includes it above) rather than leaving it at the - // default false a deserialized entity would have. - blog.IsPublished = blog.Publication != null; - foreach (var c in blog.Comments) c.Author = _context.Users.First(u => u.Id == c.AuthorId); @@ -186,7 +176,7 @@ public class BlogSpotService _context.Update(blog); var publication = await _context.blogSpotPublications - .SingleOrDefaultAsync(p => p.PostId == blogEdit.Id); + .SingleOrDefaultAsync(p => p.BlogpostId == blogEdit.Id); if (publication != null) { @@ -195,7 +185,7 @@ public class BlogSpotService } else if (blogEdit.Publish) { - _context.blogSpotPublications.Add(new BlogSpotPublication { PostId = blogEdit.Id }); + _context.blogSpotPublications.Add(new BlogSpotPublication { BlogpostId = blogEdit.Id }); } _context.SaveChanges(user.GetUserId()); @@ -241,38 +231,43 @@ public class BlogSpotService .Include(p => p.ACL) .Include(p => p.Tags) .Include(p => p.Comments) - .Include(p => p.Publication) - .Where(p => p.Publication != null + .Where(p => p.ACL == null + || p.ACL.Count == 0 || p.AuthorId == viewerId || (userCircles != null && p.ACL.Any(a => userCircles.Contains(a.CircleId)))); } else { posts = _context.blogSpotPublications - .Include(p => p.Post) - .Include(b => b.Post.Author) - .Include(p => p.Post.ACL) - .Include(p => p.Post.Tags) - .Include(p => p.Post.Comments) - .Select(p => p.Post) + .Include(p => p.BlogPost) + .Include(b => b.BlogPost.Author) + .Include(p => p.BlogPost.ACL) + .Include(p => p.BlogPost.Tags) + .Include(p => p.BlogPost.Comments) + .Where(p => p.BlogPost.ACL == null || p.BlogPost.ACL.Count == 0) + .Select(p => p.BlogPost) .ToArray(); } - var materialized = posts.ToList(); + var materialised = posts.ToList(); - // IsPublished is a value the service owns: hydrate it - // from blogSpotPublications in one bulk lookup so it is - // correct whether or not the branch above Included the - // Publication navigation (the anonymous branch does - // not). Without this, a freshly deserialized list would - // report IsPublished == false for every post. - var blogPosts = materialized.OfType().ToList(); - await HydrateIsPublishedAsync(blogPosts); + var postIds = materialised.Select(p => p.Id).ToList(); + if (postIds.Count > 0) + { + var publishedIds = await _context.blogSpotPublications + .Where(pub => postIds.Contains(pub.BlogpostId)) + .Select(pub => pub.BlogpostId) + .ToListAsync(); - foreach (var post in blogPosts) + var publishedSet = publishedIds.ToHashSet(); + foreach (var post in materialised.OfType()) + post.IsPublished = publishedSet.Contains(post.Id); + } + + foreach (var post in materialised.OfType()) ScrubAclForViewer(post, user); - return materialized + return materialised .OrderByDescending(p => p.DateModified) .Skip(skip) .Take(take); @@ -313,17 +308,10 @@ public class BlogSpotService public async Task GetBlogPostAsync(long value) { - var blog = await _context.BlogSpot + return await _context.BlogSpot .Include(b => b.Author) .Include(b => b.ACL) .SingleOrDefaultAsync(x => x.Id == value); - if (blog != null) - // Serve the publication state from its source of truth - // so the delete response's isPublished is correct — the - // query above does not Include the Publication navigation. - blog.IsPublished = await _context.blogSpotPublications - .AnyAsync(p => p.PostId == blog.Id); - return blog; } public async Task SetPublishAsync(ClaimsPrincipal user, long postId, bool publish) @@ -335,11 +323,11 @@ public class BlogSpotService if (!auth.Succeeded) throw new AuthorizationFailureException(auth); - var existing = await _context.blogSpotPublications.SingleOrDefaultAsync(p => p.PostId == postId); + var existing = await _context.blogSpotPublications.SingleOrDefaultAsync(p => p.BlogpostId == postId); if (publish) { if (existing == null) - _context.blogSpotPublications.Add(new BlogSpotPublication { PostId = postId }); + _context.blogSpotPublications.Add(new BlogSpotPublication { BlogpostId = postId }); } else { @@ -357,29 +345,6 @@ public class BlogSpotService post.ACL = new List(); } - /// - /// Populate for each post in - /// from the blogSpotPublications - /// table — a single bulk lookup, not one query per post. The - /// IsPublished flag is a wire-only value the service owns: - /// clients (PostIt) read it as-is off the JSON response rather - /// than computing it themselves. Call this on every collection - /// the service returns to a caller, so the flag is correct - /// regardless of whether the EF query Included the - /// navigation. - /// - private async Task HydrateIsPublishedAsync(IReadOnlyList posts) - { - if (posts.Count == 0) return; - var ids = posts.Select(p => p.Id).ToArray(); - var publishedIds = await _context.blogSpotPublications - .Where(p => ids.Contains(p.PostId)) - .Select(p => p.PostId) - .ToHashSetAsync(); - foreach (var post in posts) - post.IsPublished = publishedIds.Contains(post.Id); - } - private static bool IsOwner(BlogPost post, ClaimsPrincipal? user) { if (user?.Identity?.IsAuthenticated != true) return false; diff --git a/src/Yavsc.Server/Services/MailSender.cs b/src/Yavsc.Server/Services/MailSender.cs index 9a6c60ed9..81518eddb 100644 --- a/src/Yavsc.Server/Services/MailSender.cs +++ b/src/Yavsc.Server/Services/MailSender.cs @@ -140,10 +140,6 @@ namespace Yavsc.Services catch (FormatException ex) { logger.LogError(ex, "Refusing to send email because the recipient or sender address is malformed. To={To}, From={From}", email, siteSettings.Owner.EMail); - logger.LogError(ex.Message); - if (ex.InnerException!=null) - logger.LogError(ex.InnerException.Message); - return string.Empty; } catch (SmtpCommandException ex) diff --git a/src/Yavsc.Server/Services/PermissionHandler.cs b/src/Yavsc.Server/Services/PermissionHandler.cs index 3958f7ff3..77ee9fd57 100644 --- a/src/Yavsc.Server/Services/PermissionHandler.cs +++ b/src/Yavsc.Server/Services/PermissionHandler.cs @@ -59,7 +59,7 @@ public class PermissionHandler : IAuthorizationHandler { return applicationDbContext.blogSpotPublications - .Any(p=>p.PostId == blogPost.Id); + .Any(p=>p.BlogpostId == blogPost.Id); } return false; } diff --git a/src/Yavsc.Server/Services/ProfileService.cs b/src/Yavsc.Server/Services/ProfileService.cs index 4c915f5c9..3b7b5121a 100644 --- a/src/Yavsc.Server/Services/ProfileService.cs +++ b/src/Yavsc.Server/Services/ProfileService.cs @@ -28,40 +28,35 @@ namespace Yavsc.Services var claims = new List { new Claim(JwtClaimTypes.Subject,user.Id.ToString()), }; + List claimAdds = new List(); foreach (var scope in context.RequestedResources.ParsedScopes) { if (context.Client.AllowedScopes.Contains(scope.ParsedName)) { claims.Add(new Claim(JwtClaimTypes.Scope, scope.ParsedName)); + claimAdds.Add(scope.ParsedName); } } - // name / email / roles are gated on the client being granted - // the "profile" identity scope. They MUST land in the *access - // token*: the Blogs/Api bearer resource hosts read the login - // via GetUserName() (FindFirstValue("name")) and roles via - // IsInMsRole() from the access token. But "profile" is an - // identity scope, which only appears in the id-token caller's - // ParsedScopes — never in the access-token caller's — so the - // previous gate (claimAdds.Contains("profile")) left name, - // email and roles out of every access token, and GetUserName() - // resolved to null on every bearer host (the MyFiles upload - // 500). Gate on the client's AllowedScopes instead, which is - // caller-independent, so both the access token and the id - // token carry these claims for any user grant whose client is - // allowed "profile". - if (context.Client.AllowedScopes.Contains(JwtClaimTypes.Profile)) + if (claimAdds.Contains(JwtClaimTypes.Profile)) { - // "name" carries the login (UserName), not the display - // FullName: GetUserName() / User.Identity.Name feed - // filesystem paths (user files root, avatar file name), - // and FullName can contain spaces. No bearer-host - // consumer displays this claim as a human name today. - claims.Add(new Claim(JwtClaimTypes.Name, user.UserName)); + claimAdds.Remove("profile"); + claimAdds.Add(JwtClaimTypes.Name); + claimAdds.Add(JwtClaimTypes.Email); + claimAdds.Add(Constants.RoleClaimType); + } + + if (claimAdds.Contains(JwtClaimTypes.Name)) + claims.Add(new Claim(JwtClaimTypes.Name, user.FullName)); + + if (claimAdds.Contains(JwtClaimTypes.Email)) claims.Add(new Claim(JwtClaimTypes.Email, user.Email)); + + if (claimAdds.Contains(Constants.RoleClaimType)) + { var roles = await this._userManager.GetRolesAsync(user); - if (roles.Count() > 0) + if (roles.Count()>0) { claims.AddRange(roles.Select(r => new Claim(Constants.RoleClaimType, r))); } diff --git a/src/Yavsc.Server/Yavsc.Server.csproj b/src/Yavsc.Server/Yavsc.Server.csproj index 61c426ec0..bb2022fc6 100644 --- a/src/Yavsc.Server/Yavsc.Server.csproj +++ b/src/Yavsc.Server/Yavsc.Server.csproj @@ -9,8 +9,8 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 diff --git a/src/test/Yavsc.Tests.Shared/BlogHelpers.cs b/src/Yavsc.Tests.Shared/BlogHelpers.cs similarity index 100% rename from src/test/Yavsc.Tests.Shared/BlogHelpers.cs rename to src/Yavsc.Tests.Shared/BlogHelpers.cs diff --git a/src/Yavsc.Tests.Shared/Directory.Packages.props b/src/Yavsc.Tests.Shared/Directory.Packages.props new file mode 100644 index 000000000..d2342f795 --- /dev/null +++ b/src/Yavsc.Tests.Shared/Directory.Packages.props @@ -0,0 +1,8 @@ + + + + diff --git a/src/test/Yavsc.Tests.Shared/IBackendFixture.cs b/src/Yavsc.Tests.Shared/IBackendFixture.cs similarity index 100% rename from src/test/Yavsc.Tests.Shared/IBackendFixture.cs rename to src/Yavsc.Tests.Shared/IBackendFixture.cs diff --git a/src/test/Yavsc.Tests.Shared/InMemoryDatabaseName.cs b/src/Yavsc.Tests.Shared/InMemoryDatabaseName.cs similarity index 100% rename from src/test/Yavsc.Tests.Shared/InMemoryDatabaseName.cs rename to src/Yavsc.Tests.Shared/InMemoryDatabaseName.cs diff --git a/src/test/Yavsc.Tests.Shared/README.md b/src/Yavsc.Tests.Shared/README.md similarity index 100% rename from src/test/Yavsc.Tests.Shared/README.md rename to src/Yavsc.Tests.Shared/README.md diff --git a/src/test/Yavsc.Tests.Shared/TestAuthPolicyProvider.cs b/src/Yavsc.Tests.Shared/TestAuthPolicyProvider.cs similarity index 100% rename from src/test/Yavsc.Tests.Shared/TestAuthPolicyProvider.cs rename to src/Yavsc.Tests.Shared/TestAuthPolicyProvider.cs diff --git a/src/test/Yavsc.Tests.Shared/TestTokenIssuer.cs b/src/Yavsc.Tests.Shared/TestTokenIssuer.cs similarity index 80% rename from src/test/Yavsc.Tests.Shared/TestTokenIssuer.cs rename to src/Yavsc.Tests.Shared/TestTokenIssuer.cs index b1e0fc005..18aa2e00c 100644 --- a/src/test/Yavsc.Tests.Shared/TestTokenIssuer.cs +++ b/src/Yavsc.Tests.Shared/TestTokenIssuer.cs @@ -63,32 +63,17 @@ public static class TestTokenIssuer /// Value of the scope claim. The /// production BlogScope policy requires /// RequireClaim("scope", "blogs"). - /// Value of the name claim. Defaults to - /// (the test convention where the login - /// equals the subject). Pass a distinct login to mirror production, - /// where sub is a GUID and name is the user's - /// UserName — the file-system list/upload paths key the - /// personal-storage tree off the login, not the subject. /// Optional additional claims /// (e.g. a role for an admin-bypass test). public static string Issue( string subject, string scope = "blogs", - string? name = null, IEnumerable? extraClaims = null) { var now = DateTime.UtcNow; var claims = new List { new("sub", subject), - // Mirror production: IdentityServer's ProfileService emits the - // "name" claim as the user's login (UserName) when the profile - // scope is granted, and the file-system helpers resolve the - // per-user storage root via GetUserName() (FindFirstValue("name")) - // — not via User.Identity.Name. A test token without "name" would - // make ReceiveUserFile / EnsureDestinationDirectory compute a - // null owner root and 500 on upload, so stamp it here. - new("name", name ?? subject), new("scope", scope), }; if (extraClaims is not null) claims.AddRange(extraClaims); diff --git a/src/test/Yavsc.Tests.Shared/WebHostFixture.cs b/src/Yavsc.Tests.Shared/WebHostFixture.cs similarity index 100% rename from src/test/Yavsc.Tests.Shared/WebHostFixture.cs rename to src/Yavsc.Tests.Shared/WebHostFixture.cs diff --git a/src/test/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj similarity index 78% rename from src/test/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj rename to src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj index b5819d758..dd93a8b34 100644 --- a/src/test/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj +++ b/src/Yavsc.Tests.Shared/Yavsc.Tests.Shared.csproj @@ -14,8 +14,8 @@ --> 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 @@ -24,6 +24,6 @@ - + \ No newline at end of file diff --git a/src/cli/Directory.Packages.props b/src/cli/Directory.Packages.props new file mode 100644 index 000000000..c1a40467d --- /dev/null +++ b/src/cli/Directory.Packages.props @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cli/cli.csproj b/src/cli/cli.csproj index 7b95a7e68..382dff670 100644 --- a/src/cli/cli.csproj +++ b/src/cli/cli.csproj @@ -7,8 +7,8 @@ true 1.1.0.0 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 + 1.1.0-feat-sign-estimate.1+306.Branch.feat-sign-estimate.Sha.e9a33695b38154859729b61d1280e596f1597acf + 1.1.0-feat-sign-estimate.1 diff --git a/src/test/Yavsc.Api.Tests/EstimateAttachmentsApiTests.cs b/src/test/Yavsc.Api.Tests/EstimateAttachmentsApiTests.cs deleted file mode 100644 index 226b8e72a..000000000 --- a/src/test/Yavsc.Api.Tests/EstimateAttachmentsApiTests.cs +++ /dev/null @@ -1,303 +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.Blog; -using Yavsc.Tests.Shared; - -namespace Yavsc.Api.Test; - -/// -/// Integration tests for the estimate attachment endpoints -/// (GET/POST/DELETE api/v1/estimate/{id}/attachments), which -/// attach a file from the provider's personal storage to an estimate -/// **by reference** (). -/// -/// Coverage mirrors the authorization contract documented on the -/// controller: only the provider (estimate.OwnerId) may attach or -/// detach, the file must belong to the caller, and listing is open to -/// both parties (provider/client) plus admin. A third party is refused. -/// The download of the bytes themselves is served by the Blogs host and -/// exercised in Yavsc.Blogs.Tests; these tests cover only the -/// metadata/relationship endpoints that live on the API host. -/// -[Collection("Yavsc Api")] -public sealed class EstimateAttachmentsApiTests : IClassFixture -{ - private readonly ApiWebServerFixture _fixture; - - public EstimateAttachmentsApiTests(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; - } - - /// - /// Reset the graph (seeding alice the provider and bob the client), - /// create an estimate owned by alice for bob, and seed two personal - /// rows: one owned by alice (the provider's - /// own file) and one owned by bob (a file that does NOT belong to the - /// provider, used to assert the ownership guard on attach). - /// - private (long estimateId, long aliceFileId, long bobFileId) Seed() - { - _fixture.ResetAndSeedActivityGraph(); - - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - - var estimate = new Estimate - { - ClientId = "bob", - OwnerId = "alice", - CommandType = BillingCodes.Rdv, - Title = "Devis prestation", - Description = "Devis de test", - AttachedFiles = new List(), - AttachedGraphics = new List(), - Bill = new List - { - new() { Name = "Prestation", Description = "Prestation de base", Count = 1, UnitaryCost = 120m, Currency = "EUR" }, - }, - }; - db.Estimates.Add(estimate); - db.SaveChanges(); - - var aliceFile = new UploadedFile - { - OwnerId = "alice", - Path = "provider-doc.pdf", - ContentType = "application/pdf", - Length = 2048, - }; - var bobFile = new UploadedFile - { - OwnerId = "bob", - Path = "client-note.txt", - ContentType = "text/plain", - Length = 64, - }; - db.UploadedFiles.AddRange(aliceFile, bobFile); - db.SaveChanges(); - - return (estimate.Id, aliceFile.Id, bobFile.Id); - } - - private int CountAttachments(long estimateId) - { - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - return db.EstimateAttachedFiles.Count(a => a.EstimateId == estimateId); - } - - [Fact] - public async Task Provider_attaching_own_file_creates_the_link() - { - var (estimateId, aliceFileId, _) = Seed(); - - using var http = NewClient("alice"); - var response = await http.PostAsJsonAsync( - $"/api/v1/estimate/{estimateId}/attachments", - new { fileId = aliceFileId }, - TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, - $"Expected 200, got {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - Assert.Equal(1, CountAttachments(estimateId)); - - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - var link = Assert.Single(db.EstimateAttachedFiles.Where(a => a.EstimateId == estimateId)); - Assert.Equal(aliceFileId, link.FileId); - } - - [Fact] - public async Task Attaching_the_same_file_twice_is_idempotent() - { - var (estimateId, aliceFileId, _) = Seed(); - - using var http = NewClient("alice"); - await http.PostAsJsonAsync( - $"/api/v1/estimate/{estimateId}/attachments", - new { fileId = aliceFileId }, - TestContext.Current.CancellationToken); - var second = await http.PostAsJsonAsync( - $"/api/v1/estimate/{estimateId}/attachments", - new { fileId = aliceFileId }, - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.OK, second.StatusCode); - Assert.Equal(1, CountAttachments(estimateId)); - } - - [Fact] - public async Task Non_provider_cannot_attach_to_an_estimate() - { - var (estimateId, aliceFileId, _) = Seed(); - - // bob is the client of the estimate, not the provider. - using var http = NewClient("bob"); - var response = await http.PostAsJsonAsync( - $"/api/v1/estimate/{estimateId}/attachments", - new { fileId = aliceFileId }, - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - Assert.Equal(0, CountAttachments(estimateId)); - } - - [Fact] - public async Task Provider_cannot_attach_a_file_they_do_not_own() - { - var (estimateId, _, bobFileId) = Seed(); - - // alice is the provider, but the file belongs to bob. - using var http = NewClient("alice"); - var response = await http.PostAsJsonAsync( - $"/api/v1/estimate/{estimateId}/attachments", - new { fileId = bobFileId }, - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - Assert.Equal(0, CountAttachments(estimateId)); - } - - [Fact] - public async Task Provider_detaches_a_file() - { - var (estimateId, aliceFileId, _) = Seed(); - - using var http = NewClient("alice"); - await http.PostAsJsonAsync( - $"/api/v1/estimate/{estimateId}/attachments", - new { fileId = aliceFileId }, - TestContext.Current.CancellationToken); - Assert.Equal(1, CountAttachments(estimateId)); - - var response = await http.DeleteAsync( - $"/api/v1/estimate/{estimateId}/attachments/{aliceFileId}", - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(0, CountAttachments(estimateId)); - } - - [Fact] - public async Task Non_provider_cannot_detach() - { - var (estimateId, aliceFileId, _) = Seed(); - - using var http = NewClient("alice"); - await http.PostAsJsonAsync( - $"/api/v1/estimate/{estimateId}/attachments", - new { fileId = aliceFileId }, - TestContext.Current.CancellationToken); - - using var bob = NewClient("bob"); - var response = await bob.DeleteAsync( - $"/api/v1/estimate/{estimateId}/attachments/{aliceFileId}", - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - Assert.Equal(1, CountAttachments(estimateId)); - } - - [Fact] - public async Task Provider_lists_attachments() - { - var (estimateId, aliceFileId, _) = Seed(); - - using var http = NewClient("alice"); - await http.PostAsJsonAsync( - $"/api/v1/estimate/{estimateId}/attachments", - new { fileId = aliceFileId }, - TestContext.Current.CancellationToken); - - var response = await http.GetAsync( - $"/api/v1/estimate/{estimateId}/attachments", - TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - Assert.True(response.StatusCode == HttpStatusCode.OK, - $"Expected 200, got {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - using var doc = JsonDocument.Parse(body); - var item = Assert.Single(doc.RootElement.EnumerateArray()); - Assert.Equal(aliceFileId, item.GetProperty("fileId").GetInt64()); - Assert.Equal("provider-doc.pdf", item.GetProperty("path").GetString()); - Assert.Equal("alice", item.GetProperty("ownerId").GetString()); - } - - [Fact] - public async Task Client_can_list_attachments_as_a_party() - { - var (estimateId, aliceFileId, _) = Seed(); - - using var alice = NewClient("alice"); - await alice.PostAsJsonAsync( - $"/api/v1/estimate/{estimateId}/attachments", - new { fileId = aliceFileId }, - TestContext.Current.CancellationToken); - - // bob is the client of the estimate: a party, may list. - using var bob = NewClient("bob"); - var response = await bob.GetAsync( - $"/api/v1/estimate/{estimateId}/attachments", - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task Third_party_cannot_list_attachments() - { - var (estimateId, aliceFileId, _) = Seed(); - - using var alice = NewClient("alice"); - await alice.PostAsJsonAsync( - $"/api/v1/estimate/{estimateId}/attachments", - new { fileId = aliceFileId }, - TestContext.Current.CancellationToken); - - // carol is neither the provider nor the client. - using var carol = NewClient("carol"); - var response = await carol.GetAsync( - $"/api/v1/estimate/{estimateId}/attachments", - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task Listing_an_unknown_estimate_returns_404() - { - _fixture.ResetAndSeedActivityGraph(); - using var http = NewClient("alice"); - - var response = await http.GetAsync( - "/api/v1/estimate/999999/attachments", - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } -} \ No newline at end of file diff --git a/src/test/Yavsc.Api.Tests/EstimatePdfGenerationTests.cs b/src/test/Yavsc.Api.Tests/EstimatePdfGenerationTests.cs deleted file mode 100644 index e0f156048..000000000 --- a/src/test/Yavsc.Api.Tests/EstimatePdfGenerationTests.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Reflection; -using Yavsc.Helpers; -using Yavsc.Models.Billing; -using Yavsc.ViewModels.Gen; - -namespace Yavsc.Api.Test; - -// Unit test of the estimate PDF generation pipeline (no HTTP fixture -// needed): it exercises TeXHelpers.SignatureToTikz — the vector drawing -// of the provider and client signatures — and TeXHelpers.GenerateEstimatePdf -// — the lualatex stdin compile that writes .pdf into DestDir. The -// TeX source mirrors what the Estimate_tex template emits for a devis -// signed by both parties: two side-by-side minipages, each with a -// tikzpicture bounded by \useasboundingbox and the strokes drawn by -// SignatureToTikz. -// -// The PDF test requires the system `lualatex` (and the TeX Live packages -// listed in the README runtime-deps section) on the host running the -// tests. On hosts without lualatex it skips cleanly rather than failing. -public sealed class EstimatePdfGenerationTests -{ - // A provider signature: a downstroke, a cross, and a tap (dot). - // PostIt wire format — [k, x0,y0, x1,y1, …] per stroke, coords in - // [0, 10000], y-down. - private static readonly int[] ProStrokes = - { - 4, 2000, 8000, 2000, 5000, 2000, 2000, 2100, 1900, - 3, 2000, 5000, 3500, 4500, 3500, 5100, - 1, 4000, 3000, - }; - - // A client signature: a wavy stroke and a shorter one. - private static readonly int[] CliStrokes = - { - 5, 1000, 7000, 1500, 4000, 2200, 3000, 3000, 2500, 4000, 1500, - 2, 3000, 6000, 4200, 5500, - }; - - [Fact] - public void SignatureToTikz_RendersBothSignaturesAsVectorDraws() - { - var pro = MakeSig(SignatureType.Pro, ProStrokes); - var cli = MakeSig(SignatureType.Client, CliStrokes); - - var proTex = pro.SignatureToTikz(5, 2.5).ToString(); - var cliTex = cli.SignatureToTikz(5, 2.5).ToString(); - - Assert.Contains("\\draw[line cap=round, line join=round, line width=1.5pt]", proTex); - Assert.Contains("\\fill", proTex); // the single-point tap → dot - Assert.Contains("(1.000,0.500)", proTex); // first point (2000,8000), y flipped - Assert.Contains("\\draw[line cap=round, line join=round, line width=1.5pt]", cliTex); - Assert.Contains("--", cliTex); - } - - [Fact] - public void SignatureToTikz_EmptySignatureIsEmpty() - { - var empty = MakeSig(SignatureType.Pro, Array.Empty()); - Assert.Equal("", empty.SignatureToTikz(5, 2.5).ToString()); - } - - [Fact] - public void GenerateEstimatePdf_WithBothSignatures_ProducesPdfInDestDir() - { - if (!IsLualatexAvailable()) - throw Xunit.Sdk.SkipException.ForSkip( - "lualatex is not installed on this host; skipping PDF generation test. " + - "Install the TeX Live runtime deps documented in the README."); - - var destDir = Path.Combine(Path.GetTempPath(), - "yavsc-estimate-pdf-" + Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(destDir); - - try - { - var pro = MakeSig(SignatureType.Pro, ProStrokes); - var cli = MakeSig(SignatureType.Client, CliStrokes); - var baseName = "estimate-test-both-signed"; - - var model = new PdfGenerationViewModel - { - TeXSource = BuildEstimateTeX(pro, cli), - BaseFileName = baseName, - DestDir = destDir, - }; - - var fo = model.GenerateEstimatePdf(); - - // The PDF must exist, at the expected path, and be non-empty. - var expectedPath = Path.Combine(destDir, baseName + ".pdf"); - Assert.True(fo is not null && fo.Exists, - "GenerateEstimatePdf returned a non-existent file. Error: " - + model.GenerationErrorMessage?.Value); - Assert.Equal(expectedPath, fo.FullName); - Assert.True(fo.Length > 0, "generated PDF is empty"); - - // Only the PDF should persist in DestDir — aux/log artifacts - // are cleaned up by GenerateEstimatePdf on success. - var leftovers = Directory.GetFiles(destDir) - .Where(p => !Path.GetFileName(p).Equals(baseName + ".pdf", StringComparison.Ordinal)) - .ToArray(); - Assert.Empty(leftovers); - } - finally - { - if (Directory.Exists(destDir)) - Directory.Delete(destDir, recursive: true); - } - } - - private static Signature MakeSig(SignatureType type, int[] strokes) - => new() - { - Type = type, - CoordinateMax = 10_000, - Strokes = strokes, - CapturedAtUtc = new DateTime(2026, 9, 22, 0, 0, 0, DateTimeKind.Utc), - }; - - // Builds the LaTeX source the Estimate_tex template would emit for a - // devis signed by both parties: same preamble (incl. tikz + bera) and - // the two-minipage signature foot, with the strokes drawn by - // SignatureToTikz. - private static string BuildEstimateTeX(Signature pro, Signature cli) - { - var fr = CultureInfo.CreateSpecificCulture("fr-FR"); - var date = new DateTime(2026, 9, 22).ToString("dddd dd MMMM yyyy", fr); - var proTikz = pro.SignatureToTikz(5, 2.5).ToString(); - var cliTikz = cli.SignatureToTikz(5, 2.5).ToString(); - - return $@"\documentclass[french,11pt]{{article}} -\usepackage{{eurosym}} -\usepackage{{babel}} -\usepackage[T1]{{fontenc}} -\usepackage[utf8]{{inputenc}} -\usepackage[a4paper]{{geometry}} -\usepackage{{units}} -\usepackage{{bera}} -\usepackage{{graphicx}} -\usepackage{{fancyhdr}} -\usepackage{{fp}} -\usepackage{{tikz}} -\geometry{{verbose,tmargin=4em,bmargin=8em,lmargin=6em,rmargin=6em}} -\setlength{{\parindent}}{{0pt}} -\begin{{document}} -Devis n°1 --- test signe par les deux parties - -\vspace{{2em}} -\noindent -\begin{{minipage}}[t]{{0.45\textwidth}} - \textbf{{Signature du fournisseur}}\\ -\small Valide le {date}\\ -\begin{{tikzpicture}} - \useasboundingbox (0,0) rectangle (5,2.5); - {proTikz} -\end{{tikzpicture}} -\end{{minipage}}\hfill -\begin{{minipage}}[t]{{0.45\textwidth}} - \textbf{{Signature du client}}\\ -\small Valide le {date}\\ -\begin{{tikzpicture}} - \useasboundingbox (0,0) rectangle (5,2.5); - {cliTikz} -\end{{tikzpicture}} -\end{{minipage}} -\end{{document}} -"; - } - - private static bool IsLualatexAvailable() - { - try - { - using var p = Process.Start(new ProcessStartInfo("lualatex", "--version") - { - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - }); - if (p is null) return false; - return p.WaitForExit(5000) && p.ExitCode == 0; - } - catch - { - return false; - } - } -} \ No newline at end of file diff --git a/src/test/Yavsc.Api.Tests/FrontOfficeApiControllerTests.cs b/src/test/Yavsc.Api.Tests/FrontOfficeApiControllerTests.cs deleted file mode 100644 index b2a8fdbee..000000000 --- a/src/test/Yavsc.Api.Tests/FrontOfficeApiControllerTests.cs +++ /dev/null @@ -1,342 +0,0 @@ -using System.Linq; -using System.Net; -using System.Net.Http.Headers; -using System.Net.Http.Json; -using System.Text.Json; -using Microsoft.Extensions.DependencyInjection; -using Yavsc.Api.Test.Fixtures; -using Yavsc.Helpers; -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 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; - } - - // Minimal valid PostIt wire-format signature: one stroke of one - // point (k=1, then 2*k coords). See PostIt.Models.SignaturePadData. - private static readonly int[] SampleStrokes = { 1, 5000, 5000 }; - - private static HttpContent NoSignature() => JsonBody(new { }); - - private static HttpContent WithSignature(SignatureType? type = null) => - type is null - ? JsonBody(new { strokes = SampleStrokes, coordinateMax = 10_000 }) - : JsonBody(new { strokes = SampleStrokes, coordinateMax = 10_000, signatureType = type }); - - private static JsonContent JsonBody(object value) => - JsonContent.Create(value, mediaType: new MediaTypeHeaderValue("application/json")); - - /// - /// Seeds an Rdv query and, when is - /// true, an estimate linked to it via CommandId. The provider is - /// (default alice) and the client is - /// (default bob) — pass the same value - /// for both to model a user who is both parties. Returns the query - /// id and (when seeded) the estimate id. - /// - private (long queryId, long estimateId) SeedQuery( - bool withEstimate, - string performer = "alice", - string client = "bob") - { - WorkflowHelpers.ConfigureBillingService(); - _fixture.ResetAndSeedActivityGraph(); - - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - - var location = db.Locations.Single(l => l.Address == "1 rue du Test"); - - var query = new RdvQuery - { - ActivityCode = "dev", - ClientId = client, - PerformerId = performer, - Consent = true, - UserCreated = performer, - UserModified = performer, - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - EventDate = DateTime.UtcNow.AddDays(1), - Location = location, - Reason = "Initial rendez-vous", - Status = Yavsc.QueryStatus.Inserted, - }; - db.RdvQueries.Add(query); - db.SaveChanges(); - - long estimateId = 0; - if (withEstimate) - { - var estimate = new Estimate - { - CommandId = query.Id, - ClientId = client, - OwnerId = performer, - CommandType = BillingCodes.Rdv, - Title = "Devis prestation", - Description = "Devis de test", - AttachedFiles = new List(), - AttachedGraphics = new List(), - Bill = new List - { - new() { Name = "Prestation", Description = "Prestation de base", Count = 1, UnitaryCost = 120m, Currency = "EUR" }, - }, - }; - db.Estimates.Add(estimate); - db.SaveChanges(); - estimateId = estimate.Id; - } - - return (query.Id, estimateId); - } - - [Fact] - public async Task Front_accept_query_updates_status_without_server_error() - { - var (queryId, _) = SeedQuery(withEstimate: false); - - using var http = NewClient(); - var response = await http.PostAsync( - $"/api/v1/front/query/accept?billingCode=Rdv&queryId={queryId}", - NoSignature(), - 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); - - // Default caller is "alice" (the provider) → ProAccepted. - Assert.Equal(QueryStatus.ProAccepted, updated.Status); - } - - [Fact] - public async Task Provider_accept_with_signature_sets_ProValidationDate_and_Pro_signature() - { - var (queryId, estimateId) = SeedQuery(withEstimate: true); - - using var http = NewClient(subject: "alice"); - var response = await http.PostAsync( - $"/api/v1/front/query/accept?billingCode=Rdv&queryId={queryId}", - WithSignature(), - 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(); - - Assert.Equal(QueryStatus.ProAccepted, assertDb.RdvQueries.Single(q => q.Id == queryId).Status); - - var estimate = assertDb.Estimates.Single(e => e.Id == estimateId); - Assert.True(estimate.ProviderValidationDate != default); - Assert.True(estimate.ClientValidationDate == default); - - var signature = assertDb.Signatures.Single(s => s.EstimateId == estimateId); - Assert.Equal(SignatureType.Pro, signature.Type); - Assert.Equal("alice", signature.SignerId); - } - - [Fact] - public async Task Client_accept_with_signature_sets_ClientValidationDate_and_Client_signature() - { - var (queryId, estimateId) = SeedQuery(withEstimate: true); - - using var http = NewClient(subject: "bob"); - var response = await http.PostAsync( - $"/api/v1/front/query/accept?billingCode=Rdv&queryId={queryId}", - WithSignature(), - 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(); - - Assert.Equal(QueryStatus.ClientAccepted, assertDb.RdvQueries.Single(q => q.Id == queryId).Status); - - var estimate = assertDb.Estimates.Single(e => e.Id == estimateId); - Assert.True(estimate.ClientValidationDate != default); - Assert.True(estimate.ProviderValidationDate == default); - - var signature = assertDb.Signatures.Single(s => s.EstimateId == estimateId); - Assert.Equal(SignatureType.Client, signature.Type); - Assert.Equal("bob", signature.SignerId); - } - - [Fact] - public async Task Reject_sets_status_Rejected() - { - var (queryId, _) = SeedQuery(withEstimate: false); - - using var http = NewClient(subject: "alice"); - var response = await http.PostAsync( - $"/api/v1/front/query/reject?billingCode=Rdv&queryId={queryId}", - NoSignature(), - 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(); - Assert.Equal(QueryStatus.Rejected, assertDb.RdvQueries.Single(q => q.Id == queryId).Status); - } - - [Fact] - public async Task Non_party_actor_is_forbidden() - { - var (queryId, _) = SeedQuery(withEstimate: false); - - using var http = NewClient(subject: "carol"); - var response = await http.PostAsync( - $"/api/v1/front/query/accept?billingCode=Rdv&queryId={queryId}", - NoSignature(), - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - [Fact] - public async Task Accept_with_signature_but_no_estimate_returns_400() - { - var (queryId, _) = SeedQuery(withEstimate: false); - - using var http = NewClient(subject: "alice"); - var response = await http.PostAsync( - $"/api/v1/front/query/accept?billingCode=Rdv&queryId={queryId}", - WithSignature(), - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - } - - [Fact] - public async Task Reject_with_signature_returns_400() - { - var (queryId, _) = SeedQuery(withEstimate: true); - - using var http = NewClient(subject: "alice"); - var response = await http.PostAsync( - $"/api/v1/front/query/reject?billingCode=Rdv&queryId={queryId}", - WithSignature(), - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - } - - [Fact] - public async Task Estimate_payload_carries_existing_signature_strokes() - { - var (queryId, estimateId) = SeedQuery(withEstimate: true); - - // Provider accepts with a signature. - using (var pro = NewClient(subject: "alice")) - { - var r = await pro.PostAsync( - $"/api/v1/front/query/accept?billingCode=Rdv&queryId={queryId}", - WithSignature(), - TestContext.Current.CancellationToken); - Assert.True(r.IsSuccessStatusCode, await r.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); - } - - // The estimate read endpoint carries the signature strokes on - // the estimate payload, so PostIt can repaint them on reopen. - using var reader = NewClient(subject: "alice"); - var response = await reader.GetAsync( - $"/api/v1/estimate/{estimateId}", - TestContext.Current.CancellationToken); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadFromJsonAsync(TestContext.Current.CancellationToken); - - // Signatures ride on the estimate payload as two dedicated - // slots, not a list: the provider signed here, so signaturePro - // is populated and signatureClient is absent/null. - var proSig = body.GetProperty("signaturePro"); - Assert.Equal(JsonValueKind.Object, proSig.ValueKind); - Assert.Equal(JsonValueKind.Array, proSig.GetProperty("strokes").ValueKind); - Assert.True(body.TryGetProperty("signatureClient", out var cli) - && cli.ValueKind == JsonValueKind.Null, - "signatureClient should be null when only the provider has signed"); - } - - // When one user is both the provider and the client of a query, the - // server cannot infer which side they are signing as from identity - // alone (it would always pick the provider). The caller declares the - // side (signatureType=1 here = Client); the server honors it because - // the caller is that party, and stores a Client signature. - [Fact] - public async Task Accept_declared_client_side_stores_client_signature_when_user_is_both_parties() - { - var (queryId, estimateId) = SeedQuery(withEstimate: true, performer: "alice", client: "alice"); - - using var http = NewClient(subject: "alice"); - var response = await http.PostAsync( - $"/api/v1/front/query/accept?billingCode=Rdv&queryId={queryId}", - WithSignature(SignatureType.Client), // signing as the client - 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(); - - Assert.Equal(QueryStatus.ClientAccepted, assertDb.RdvQueries.Single(q => q.Id == queryId).Status); - - var estimate = assertDb.Estimates.Single(e => e.Id == estimateId); - Assert.True(estimate.ClientValidationDate != default); - Assert.True(estimate.ProviderValidationDate == default); - - var signature = assertDb.Signatures.Single(s => s.EstimateId == estimateId); - Assert.Equal(SignatureType.Client, signature.Type); - Assert.Equal("alice", signature.SignerId); - } - - // A provider may not sign as the client (and vice versa): declaring - // the other party's side is refused even though the caller is a - // party to the query. Write is authorized only for the signature's - // author. - [Fact] - public async Task Accept_declaring_other_party_side_is_forbidden() - { - var (queryId, _) = SeedQuery(withEstimate: true); // provider alice, client bob - - using var http = NewClient(subject: "alice"); // alice is the provider - var response = await http.PostAsync( - $"/api/v1/front/query/accept?billingCode=Rdv&queryId={queryId}", - WithSignature(SignatureType.Client), // alice tries to sign as the client - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } -} \ No newline at end of file diff --git a/src/test/Yavsc.Api.Tests/FrontOfficeQueryAttachmentsApiTests.cs b/src/test/Yavsc.Api.Tests/FrontOfficeQueryAttachmentsApiTests.cs deleted file mode 100644 index d4438ef4e..000000000 --- a/src/test/Yavsc.Api.Tests/FrontOfficeQueryAttachmentsApiTests.cs +++ /dev/null @@ -1,256 +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.Helpers; -using Yavsc.Models; -using Yavsc.Models.Billing; -using Yavsc.Models.Blog; -using Yavsc.Models.Workflow; -using Yavsc.Tests.Shared; - -namespace Yavsc.Api.Test; - -/// -/// Integration tests for the query (demande) attachment endpoints -/// (GET/POST/DELETE api/v1/front/query/{queryId}/attachments), -/// which attach a file from the client's personal storage to their -/// NominativeServiceCommand **by reference** -/// (). -/// -/// Coverage mirrors the controller contract: only the client -/// (the demande's ClientId) may attach or detach, the file must -/// belong to the caller, and listing is open to both parties -/// (client/provider) plus admin. A third party is refused. -/// -[Collection("Yavsc Api")] -public sealed class FrontOfficeQueryAttachmentsApiTests : IClassFixture -{ - private readonly ApiWebServerFixture _fixture; - - public FrontOfficeQueryAttachmentsApiTests(ApiWebServerFixture fixture) - { - _fixture = fixture; - } - - private HttpClient NewClient(string subject = "bob", 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 an Rdv query from bob (client) to alice (provider) and two - /// personal rows: one owned by bob (the - /// client's own file) and one owned by alice (a file that does NOT - /// belong to the client, used to assert the ownership guard). - /// - private (long queryId, long bobFileId, long aliceFileId) Seed() - { - WorkflowHelpers.ConfigureBillingService(); - _fixture.ResetAndSeedActivityGraph(); - - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - var location = db.Locations.Single(l => l.Address == "1 rue du Test"); - - var query = new RdvQuery - { - ActivityCode = "dev", - ClientId = "bob", - PerformerId = "alice", - Consent = true, - UserCreated = "bob", - UserModified = "bob", - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - EventDate = DateTime.UtcNow.AddDays(1), - Location = location, - Reason = "Demande de devis", - Status = QueryStatus.InProgress, - Description = "Demande en attente", - }; - db.RdvQueries.Add(query); - db.SaveChanges(); - - var bobFile = new UploadedFile - { - OwnerId = "bob", - Path = "demande-photo.jpg", - ContentType = "image/jpeg", - Length = 4096, - }; - var aliceFile = new UploadedFile - { - OwnerId = "alice", - Path = "provider-spec.pdf", - ContentType = "application/pdf", - Length = 1024, - }; - db.UploadedFiles.AddRange(bobFile, aliceFile); - db.SaveChanges(); - - return (query.Id, bobFile.Id, aliceFile.Id); - } - - private int CountAttachments(long queryId) - { - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - return db.QueryAttachedFiles.Count(a => a.CommandId == queryId); - } - - private string AttachUrl(long queryId) => - $"/api/v1/front/query/{queryId}/attachments?billingCode=Rdv"; - - private string DetachUrl(long queryId, long fileId) => - $"/api/v1/front/query/{queryId}/attachments/{fileId}?billingCode=Rdv"; - - [Fact] - public async Task Client_attaching_own_file_creates_the_link() - { - var (queryId, bobFileId, _) = Seed(); - - using var http = NewClient("bob"); - var response = await http.PostAsJsonAsync( - AttachUrl(queryId), - new { fileId = bobFileId }, - TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, - $"Expected 200, got {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - Assert.Equal(1, CountAttachments(queryId)); - - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - var link = Assert.Single(db.QueryAttachedFiles.Where(a => a.CommandId == queryId)); - Assert.Equal(bobFileId, link.FileId); - } - - [Fact] - public async Task Provider_cannot_attach_to_a_query() - { - var (queryId, bobFileId, _) = Seed(); - - // alice is the provider of the query; only the client attaches. - using var http = NewClient("alice"); - var response = await http.PostAsJsonAsync( - AttachUrl(queryId), - new { fileId = bobFileId }, - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - Assert.Equal(0, CountAttachments(queryId)); - } - - [Fact] - public async Task Client_cannot_attach_a_file_they_do_not_own() - { - var (queryId, _, aliceFileId) = Seed(); - - // bob is the client, but the file belongs to alice. - using var http = NewClient("bob"); - var response = await http.PostAsJsonAsync( - AttachUrl(queryId), - new { fileId = aliceFileId }, - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - Assert.Equal(0, CountAttachments(queryId)); - } - - [Fact] - public async Task Client_detaches_a_file() - { - var (queryId, bobFileId, _) = Seed(); - - using var http = NewClient("bob"); - await http.PostAsJsonAsync( - AttachUrl(queryId), - new { fileId = bobFileId }, - TestContext.Current.CancellationToken); - Assert.Equal(1, CountAttachments(queryId)); - - var response = await http.DeleteAsync( - DetachUrl(queryId, bobFileId), - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(0, CountAttachments(queryId)); - } - - [Fact] - public async Task Provider_cannot_detach() - { - var (queryId, bobFileId, _) = Seed(); - - using var bob = NewClient("bob"); - await bob.PostAsJsonAsync( - AttachUrl(queryId), - new { fileId = bobFileId }, - TestContext.Current.CancellationToken); - - using var alice = NewClient("alice"); - var response = await alice.DeleteAsync( - DetachUrl(queryId, bobFileId), - TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - Assert.Equal(1, CountAttachments(queryId)); - } - - [Fact] - public async Task Provider_lists_attachments_as_a_party() - { - var (queryId, bobFileId, _) = Seed(); - - using var bob = NewClient("bob"); - await bob.PostAsJsonAsync( - AttachUrl(queryId), - new { fileId = bobFileId }, - TestContext.Current.CancellationToken); - - // alice is the provider: a party, may list. - using var alice = NewClient("alice"); - var response = await alice.GetAsync(AttachUrl(queryId), TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - Assert.True(response.StatusCode == HttpStatusCode.OK, - $"Expected 200, got {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - using var doc = JsonDocument.Parse(body); - var item = Assert.Single(doc.RootElement.EnumerateArray()); - Assert.Equal(bobFileId, item.GetProperty("fileId").GetInt64()); - Assert.Equal("bob", item.GetProperty("ownerId").GetString()); - } - - [Fact] - public async Task Third_party_cannot_list_attachments() - { - var (queryId, bobFileId, _) = Seed(); - - using var bob = NewClient("bob"); - await bob.PostAsJsonAsync( - AttachUrl(queryId), - new { fileId = bobFileId }, - TestContext.Current.CancellationToken); - - using var carol = NewClient("carol"); - var response = await carol.GetAsync(AttachUrl(queryId), TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } -} \ No newline at end of file diff --git a/src/test/Yavsc.Blogs.Tests/BlogSpotServiceIndexTests.cs b/src/test/Yavsc.Blogs.Tests/BlogSpotServiceIndexTests.cs deleted file mode 100644 index fa1b9c379..000000000 --- a/src/test/Yavsc.Blogs.Tests/BlogSpotServiceIndexTests.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System.Security.Claims; -using Microsoft.Extensions.DependencyInjection; -using Yavsc.Blogspot; -using Yavsc.Models; -using Yavsc.Models.Blog; -using Yavsc.Blogs.Tests.Fixtures; - -namespace Yavsc.Blogs.Tests; - -/// -/// Direct service-level tests for . -/// -/// The HTTP tests in all drive the -/// [Authorize("BlogScope")]-protected BlogApiController, -/// so the request is always authenticated by the time it reaches the -/// service. The MVC BlogspotController.Index action, by -/// contrast, is [AllowAnonymous] — that is the one caller that -/// actually exercises the unauthenticated branch of -/// , the branch that reads from -/// blogSpotPublications and projects to p.Post. -/// -/// These tests pin that behaviour at the service layer: a -/// BlogPost that has a BlogSpotPublication row is -/// listed for an anonymous viewer, and one without is not. Seeding -/// runs against the same shared SQLite store as the rest of the -/// Yavsc Blogs collection, so the real -/// (registered by -/// ) is resolved from DI and run -/// end-to-end — no mocking. -/// -[Collection("Yavsc Blogs")] -public sealed class BlogSpotServiceIndexTests : IClassFixture -{ - private readonly BlogsWebServerFixture _fixture; - - public BlogSpotServiceIndexTests(BlogsWebServerFixture fixture) - { - _fixture = fixture; - } - - /// An unauthenticated principal: a - /// with no authentication type has - /// == false, so - /// takes its anonymous - /// branch. This mirrors what the JwtBearer middleware hands the - /// MVC [AllowAnonymous] action when no bearer token is - /// presented. - private static ClaimsPrincipal Anonymous() - => new(new ClaimsIdentity()); - - /// Resolve the real from the - /// fixture's DI container and run against it, - /// disposing the request scope (and its scoped DbContext) afterwards. - /// The service itself is not ; the scope is, - /// so the scope is what we must dispose to avoid leaking a DbContext - /// against the shared SQLite connection across tests. - private async Task WithServiceAsync( - Func> body) - { - using var scope = _fixture.Services.CreateScope(); - var service = scope.ServiceProvider.GetRequiredService(); - return await body(service); - } - - private void Publish(long postId) - { - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - // The publication flag is the existence of a row in - // blogSpotPublications — same write SetPublishAsync performs - // in production. Inserting it directly is the most faithful - // way to seed "this post is published" without routing - // through the authorize-gated PUT/publish endpoint. - db.blogSpotPublications.Add(new BlogSpotPublication { PostId = postId }); - db.SaveChanges(); - } - - [Fact] - public async Task Index_lists_a_published_post_for_anonymous_access() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - var publishedId = _fixture.SeedBlogPost("alice", "Published for all"); - - Publish(publishedId); - - var posts = await WithServiceAsync(async svc => - { - var result = await svc.Index(Anonymous(), id: "", skip: 0, take: 25); - return result.OfType().ToArray(); - }); - - var listed = Assert.Single(posts); - Assert.Equal("Published for all", listed.Title); - // The service serves IsPublished from the - // blogSpotPublications table — the anonymous branch does - // not Include the Publication navigation, so this proves - // the value is hydrated by the service, not derived from - // the EF graph. - Assert.True(listed.IsPublished); - } - - [Fact] - public async Task Index_serves_IsPublished_false_for_a_draft_for_anonymous_access() - { - // Anonymous viewers never receive a draft (the branch - // queries blogSpotPublications), so this is checked through - // the authenticated branch: the service must still serve - // IsPublished == false for a post with no publication row, - // not the default of a freshly loaded entity. - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - var draftId = _fixture.SeedBlogPost("alice", "Draft, not published"); - - var viewer = new ClaimsPrincipal(new ClaimsIdentity( - [new Claim("sub", "alice")], authenticationType: "Bearer")); - - var posts = await WithServiceAsync(async svc => - { - var result = await svc.Index(viewer, id: "", skip: 0, take: 25); - return result.OfType().ToArray(); - }); - - var draft = Assert.Single(posts); - Assert.Equal(draftId, draft.Id); - Assert.False(draft.IsPublished); - } - - [Fact] - public async Task Index_does_not_list_an_unpublished_post_for_anonymous_access() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - var publishedId = _fixture.SeedBlogPost("alice", "Published for all"); - _fixture.SeedBlogPost("alice", "Draft, not published"); - - // Only the first post is published; the draft has no - // BlogSpotPublication row. - Publish(publishedId); - - var titles = await WithServiceAsync(async svc => - { - var result = await svc.Index(Anonymous(), id: "", skip: 0, take: 25); - return result.Select(p => p.Title).ToArray(); - }); - - Assert.Contains("Published for all", titles); - Assert.DoesNotContain("Draft, not published", titles); - } - - [Fact] - public async Task Index_returns_an_empty_list_for_anonymous_access_when_nothing_is_published() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - // A draft with no publication row: invisible to anonymous. - _fixture.SeedBlogPost("alice", "Draft, not published"); - - var result = await WithServiceAsync(svc => - svc.Index(Anonymous(), id: "", skip: 0, take: 25)); - - Assert.Empty(result); - } -} \ No newline at end of file diff --git a/src/test/Yavsc.Blogs.Tests/FileSystemApiTests.cs b/src/test/Yavsc.Blogs.Tests/FileSystemApiTests.cs deleted file mode 100644 index 0e36a6018..000000000 --- a/src/test/Yavsc.Blogs.Tests/FileSystemApiTests.cs +++ /dev/null @@ -1,397 +0,0 @@ -using System.Net; -using System.Net.Http.Headers; -using System.Text.Json; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Yavsc.Blogs.Tests.Fixtures; -using Yavsc.Models; -using Yavsc.Models.Billing; -using Yavsc.Models.Blog; -using Yavsc.Models.Relationship; -using Yavsc.Models.Workflow; -using Yavsc.Tests.Shared; - -namespace Yavsc.Blogs.Tests; - -/// -/// Integration tests for the personal-storage file endpoints -/// (api/v1/fs) served by FileSystemApiController on the -/// Blogs host. -/// -/// Two concerns: -/// -/// Upload records metadata: a multipart -/// POST api/v1/fs persists an row -/// owned by the caller and returns its fileId, so the client -/// can later attach that file to an estimate/query by -/// reference. -/// Download authorization: -/// GET api/v1/fs/file/{fileId} serves the bytes from the -/// owner's personal space. The owner may always read; a party -/// (client or provider) of an estimate/query the file is attached -/// to may read; a third party is refused. The bytes live on the -/// Blogs host's disk — this is the cross-container reason the -/// download endpoint lives here and not on the API host. -/// -/// -[Collection("Yavsc Blogs")] -public sealed class FileSystemApiTests : IClassFixture -{ - private readonly BlogsWebServerFixture _fixture; - - public FileSystemApiTests(BlogsWebServerFixture fixture) - { - _fixture = fixture; - } - - private HttpClient NewClient(string subject = "alice", string? name = null) - { - var handler = new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (_, _, _, _) => true - }; - var http = new HttpClient(handler) - { - BaseAddress = new Uri(_fixture.Addresses.First(a => a.StartsWith("https://"))) - }; - // Pass a distinct name to mirror production (sub = GUID, name = - // login); defaults to subject for the existing tests where the - // two are equal. - http.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("Bearer", TestTokenIssuer.Issue(subject, name: name)); - return http; - } - - private static string PhysicalFile(BlogsWebServerFixture fixture, string owner, string fileName) => - Path.Combine(fixture.BlogFilesRoot, owner, fileName); - - /// Write a physical file into the owner's personal space, - /// so GetFile can stream it back. Creates the directory if - /// needed. - private static void WritePhysicalFile(BlogsWebServerFixture fixture, string owner, string fileName, byte[] content) - { - var path = PhysicalFile(fixture, owner, fileName); - Directory.CreateDirectory(Path.GetDirectoryName(path)!); - File.WriteAllBytes(path, content); - } - - private long SeedUploadedFile(string ownerId, string path, string contentType, long length) - { - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - var file = new UploadedFile - { - OwnerId = ownerId, - Path = path, - ContentType = contentType, - Length = length, - }; - db.UploadedFiles.Add(file); - db.SaveChanges(); - return file.Id; - } - - [Fact] - public async Task Post_fs_creates_an_UploadedFile_row_owned_by_the_caller() - { - _fixture.ResetDatabase(); - // DiskQuota must be non-zero: ReceiveUserFile flags QuotaOffense - // once usage >= DiskQuota, and the controller skips the metadata - // row when QuotaOffense is set. A fresh user has quota 0. - _fixture.SeedUser("alice", u => u.DiskQuota = 10_000_000); - - var payload = System.Text.Encoding.UTF8.GetBytes("hello personal storage"); - - using var form = new MultipartFormDataContent(); - var fileContent = new ByteArrayContent(payload); - fileContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); - form.Add(fileContent, "file", "note.txt"); - - using var http = NewClient("alice"); - var response = await http.PostAsync("/api/v1/fs", form, TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - Assert.True(response.StatusCode == HttpStatusCode.OK, - $"Expected 200, got {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - using var doc = JsonDocument.Parse(body); - var item = Assert.Single(doc.RootElement.EnumerateArray()); - var fileId = item.GetProperty("fileId").GetInt64(); - Assert.True(fileId > 0, $"Expected a positive fileId, got {fileId}. Body: {body}"); - Assert.Equal("note.txt", item.GetProperty("fileName").GetString()); - Assert.False(item.GetProperty("quotaOffense").GetBoolean()); - - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - var row = Assert.Single(db.UploadedFiles.Where(u => u.OwnerId == "alice")); - Assert.Equal("note.txt", row.Path); - Assert.Equal("text/plain", row.ContentType); - Assert.Equal(payload.Length, row.Length); - - // The file physically landed under {BlogFilesRoot}/alice/note.txt. - Assert.True(File.Exists(PhysicalFile(_fixture, "alice", "note.txt")), - "Expected the uploaded file on disk under the owner's personal root"); - } - - /// - /// Regression for the "Mes fichiers" page listing nothing: the - /// personal-storage tree is keyed by the user's login - /// (UserName) — EnsureDestinationDirectory writes - /// under {Blog}/{name-claim}, the download endpoint and - /// avatar paths use user.UserName — but GET api/v1/fs - /// passed User.GetUserId() (the sub claim, a GUID in - /// production) to GetUserFiles, so the listing looked under - /// {Blog}/{sub-GUID}, an empty tree. This test decouples - /// sub from the login (mirroring production) and asserts the - /// listing returns the file stored under the login. - /// - [Fact] - public async Task Get_fs_lists_files_stored_under_the_user_login() - { - _fixture.ResetDatabase(); - // sub = "alice-sub" (GUID-like), login = "alice" — the shape - // ProfileService mints in production. DiskQuota non-zero so the - // upload persists the UploadedFile row (see Post_fs test). - _fixture.SeedUser("alice", u => - { - u.Id = "alice-sub"; - u.DiskQuota = 10_000_000; - }); - - var payload = System.Text.Encoding.UTF8.GetBytes("list me"); - - using var form = new MultipartFormDataContent(); - var fileContent = new ByteArrayContent(payload); - fileContent.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); - form.Add(fileContent, "file", "note.txt"); - - // Upload with a token whose sub is the GUID-like id and whose - // name is the login — EnsureDestinationDirectory keys off "name". - using (var http = NewClient("alice-sub", name: "alice")) - { - var post = await http.PostAsync("/api/v1/fs", form, TestContext.Current.CancellationToken); - var postBody = await post.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - Assert.True(post.StatusCode == HttpStatusCode.OK, - $"Upload failed: {(int)post.StatusCode} ({post.StatusCode}): {postBody}"); - } - - // The file landed under the login, not the subject. - Assert.True(File.Exists(PhysicalFile(_fixture, "alice", "note.txt")), - "Expected the uploaded file under {BlogFilesRoot}/alice/note.txt (the login)"); - Assert.False(Directory.Exists(Path.Combine(_fixture.BlogFilesRoot, "alice-sub")), - "The subject-named directory must NOT exist — storage is keyed by the login"); - - // GET /api/v1/fs must list under the login and find the file. - using var get = NewClient("alice-sub", name: "alice"); - var response = await get.GetAsync("/api/v1/fs", TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - Assert.True(response.StatusCode == HttpStatusCode.OK, - $"List failed: {(int)response.StatusCode} ({response.StatusCode}): {body}"); - - using var doc = JsonDocument.Parse(body); - Assert.True(doc.RootElement.TryGetProperty("files", out var filesEl), - $"Response has no 'files' property. Body: {body}"); - var names = filesEl.EnumerateArray().Select(f => f.GetProperty("name").GetString()).ToArray(); - Assert.Contains("note.txt", names); - - // The UploadedFile row (OwnerId = sub) is enriched onto the - // entry, so the client can attach the file by reference. - var noteEntry = filesEl.EnumerateArray() - .First(f => f.GetProperty("name").GetString() == "note.txt"); - Assert.True(noteEntry.TryGetProperty("id", out var idEl) && idEl.GetInt64() > 0, - "Expected the enriched UploadedFile id on the listed entry"); - } - - [Fact] - public async Task GetFile_owner_can_download_their_own_file() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - - var bytes = System.Text.Encoding.UTF8.GetBytes("owner payload"); - WritePhysicalFile(_fixture, "alice", "owner-note.txt", bytes); - var fileId = SeedUploadedFile("alice", "owner-note.txt", "text/plain", bytes.Length); - - using var http = NewClient("alice"); - var response = await http.GetAsync( - $"/api/v1/fs/file/{fileId}", TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var downloaded = await response.Content.ReadAsByteArrayAsync(TestContext.Current.CancellationToken); - Assert.Equal(bytes, downloaded); - } - - [Fact] - public async Task GetFile_refuses_a_third_party() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - _fixture.SeedUser("carol"); - - var bytes = System.Text.Encoding.UTF8.GetBytes("private to alice"); - WritePhysicalFile(_fixture, "alice", "secret.txt", bytes); - var fileId = SeedUploadedFile("alice", "secret.txt", "text/plain", bytes.Length); - - // carol is neither the owner nor a party to any estimate/query - // this file is attached to. - using var http = NewClient("carol"); - var response = await http.GetAsync( - $"/api/v1/fs/file/{fileId}", TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); - } - - /// - /// Seed the graph needed to prove a party (the client of an - /// estimate the file is attached to) can download a file they do - /// not own: alice owns the file and is the provider; bob is the - /// client; the file is attached to the estimate by reference. - /// - private long SeedPartyDownloadGraph(out long estimateId, out string fileName) - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - _fixture.SeedUser("bob"); - - fileName = "party-spec.pdf"; - var bytes = System.Text.Encoding.UTF8.GetBytes("party payload"); - - using var scope = _fixture.Services.CreateScope(); - var db = scope.ServiceProvider.GetRequiredService(); - - // Location + PerformerProfile(alice): Estimate.OwnerId is a FK - // to PerformerProfile.PerformerId, so the provider profile must - // exist before the estimate can be inserted. - var location = new Location { Address = "1 rue du Test", Latitude = 48.8, Longitude = 2.3 }; - db.Locations.Add(location); - db.SaveChanges(); - - db.Performers.Add(new PerformerProfile - { - PerformerId = "alice", - SIREN = "12345678901234", - OrganizationAddressId = location.Id, - ExerciseCountryCode = "fr", - AcceptNotifications = true, - AcceptPublicContact = true, - Active = true, - Rate = 5, - WebSite = "https://alice.dev", - }); - db.SaveChanges(); - - var estimate = new Estimate - { - OwnerId = "alice", - ClientId = "bob", - CommandType = "Rdv", - Title = "Devis party test", - Description = "Devis", - AttachedFiles = new List(), - AttachedGraphics = new List(), - Bill = new List - { - new() { Name = "Prestation", Description = "Prestation", Count = 1, UnitaryCost = 10m, Currency = "EUR" }, - }, - }; - db.Estimates.Add(estimate); - db.SaveChanges(); - estimateId = estimate.Id; - - var file = new UploadedFile - { - OwnerId = "alice", - Path = fileName, - ContentType = "application/pdf", - Length = bytes.Length, - }; - db.UploadedFiles.Add(file); - db.SaveChanges(); - - db.EstimateAttachedFiles.Add(new EstimateAttachedFile - { - FileId = file.Id, - EstimateId = estimate.Id, - }); - db.SaveChanges(); - - // The bytes must exist on disk for GetFile to stream them. - WritePhysicalFile(_fixture, "alice", fileName, bytes); - return file.Id; - } - - [Fact] - public async Task GetFile_party_client_of_an_attached_estimate_can_download() - { - var fileId = SeedPartyDownloadGraph(out _, out _); - - // bob is the client of the estimate the file is attached to: - // a party, authorized to read even though alice owns the file. - using var http = NewClient("bob"); - var response = await http.GetAsync( - $"/api/v1/fs/file/{fileId}", TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, - $"Expected 200, got {(int)response.StatusCode} ({response.StatusCode}): {body}"); - var downloaded = await response.Content.ReadAsByteArrayAsync(TestContext.Current.CancellationToken); - Assert.Equal(System.Text.Encoding.UTF8.GetBytes("party payload"), downloaded); - } - - [Fact] - public async Task GetFile_party_provider_of_an_attached_estimate_can_download() - { - var fileId = SeedPartyDownloadGraph(out _, out _); - - // alice is the owner too, so this also exercises the owner path; - // the provider of an attached estimate is authorized regardless. - using var http = NewClient("alice"); - var response = await http.GetAsync( - $"/api/v1/fs/file/{fileId}", TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task GetFile_unknown_id_returns_404() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - - using var http = NewClient("alice"); - var response = await http.GetAsync( - "/api/v1/fs/file/999999", TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - /// - /// Regression for the "delete returns 40x yet the file is gone" - /// report: DeleteUserDirOrFile read FileInfo.Length - /// after fi.Delete(), and FileInfo.Length - /// throws FileNotFoundException once the file no longer - /// exists. The controller's catch turned that into a 400 - /// even though the file had already been removed — the user saw - /// "le fichier n'existe pas sur le serveur" while the delete in - /// fact succeeded. This test asserts a clean 200 and that the - /// file is gone from disk. - /// - [Fact] - public async Task Delete_fs_removes_the_file_and_returns_200() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - - var bytes = System.Text.Encoding.UTF8.GetBytes("delete me"); - WritePhysicalFile(_fixture, "alice", "doomed.txt", bytes); - - using var http = NewClient("alice"); - var response = await http.DeleteAsync( - "/api/v1/fs/doomed.txt", TestContext.Current.CancellationToken); - var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); - - Assert.True(response.StatusCode == HttpStatusCode.OK, - $"Expected 200, got {(int)response.StatusCode} ({response.StatusCode}): {body}"); - Assert.False(File.Exists(PhysicalFile(_fixture, "alice", "doomed.txt")), - "The file must be gone from disk after a successful delete."); - } -} \ No newline at end of file diff --git a/src/test/Yavsc.Blogs.Tests/IBlogPostWireShapeTests.cs b/src/test/Yavsc.Blogs.Tests/IBlogPostWireShapeTests.cs deleted file mode 100644 index bb38725cd..000000000 --- a/src/test/Yavsc.Blogs.Tests/IBlogPostWireShapeTests.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Text.Json; -using Yavsc.Blogspot; -using Yavsc.Models.Blog; - -namespace Yavsc.Blogs.Tests; - -/// -/// Wire-shape tests pinning that -/// is carried on the JSON the blog API list endpoint emits. -/// -/// The blog API host (Yavsc.Blogs/Program.cs) serialises -/// with System.Text.Json (the .AddControllers() default — no -/// AddNewtonsoftJson), and BlogApiController.GetBlogspot -/// returns IEnumerable<IBlogPost>. System.Text.Json emits -/// only the members of the declared type, so a property that -/// exists solely on the concrete BlogPost entity is dropped from -/// the payload. IsPublished used to be one such property: it was -/// on BlogPost and BlogPostDto but not on -/// , so the list response omitted it and PostIt -/// read IsPublished == false for every post — including -/// published ones. -/// -/// These tests reproduce that serialisation path directly -/// (declared type IEnumerable<IBlogPost> through -/// ) and assert -/// isPublished survives the round trip. They live in -/// Yavsc.Blogs.Tests — the assembly that owns the blog API -/// contract — so a regression at the wire boundary is caught here, -/// where it manifested in production (PostIt showing a published -/// post's status as false). -/// -public sealed class IBlogPostWireShapeTests -{ - private static readonly JsonSerializerOptions WebDefaults - = new(JsonSerializerDefaults.Web); - - [Fact] - public void IsPublished_is_emitted_when_list_is_serialised_as_IEnumerable_IBlogPost() - { - var post = new BlogPost - { - Id = 13, - Title = "Published", - AuthorId = "alice", - Article = "x", - }; - post.IsPublished = true; - - // Declared type mirrors GetBlogspot's return: IEnumerable. - IEnumerable list = new List { post }; - - // Serialize with the DECLARED type, the way ASP.NET Core's - // SystemTextJsonOutputFormatter hands context.ObjectType (the - // action return type) to JsonSerializer.Serialize. - var json = JsonSerializer.Serialize(list, typeof(IEnumerable), - WebDefaults); - - using var doc = JsonDocument.Parse(json); - var element = doc.RootElement[0]; - // Web defaults use camelCase, so the property is "isPublished". - Assert.True(element.TryGetProperty("isPublished", out var isPublished), - $"isPublished was dropped from the list payload. JSON: {json}"); - Assert.True(isPublished.GetBoolean()); - } - - [Fact] - public void IsPublished_round_trips_through_BlogPostDto_on_the_client() - { - // The shape the client (PostIt) receives and deserialises with - // PropertyNameCaseInsensitive = true (cf. YavscApiClient). - var json = """ - [{ - "id": 13, - "title": "Published", - "article": "x", - "authorId": "alice", - "isPublished": true - }] - """; - - var posts = JsonSerializer.Deserialize>(json, - new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; - - var post = Assert.Single(posts); - Assert.True(post.IsPublished); - } -} \ No newline at end of file diff --git a/src/test/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs b/src/test/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs deleted file mode 100644 index 4a7600596..000000000 --- a/src/test/Yavsc.Org.Tests/Smoke/AccountSmokeTests.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System.Net; -using System.Net.Http; -using IdentityServer8.Stores; -using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.Extensions.DependencyInjection; -using Yavsc.Tests.Shared; -using Xunit; - -namespace Yavsc.Org.Tests.Smoke; - -/// -/// Smoke for the Account BC (login / registration / OIDC discovery). -/// Hits the in-memory test server through -/// : no sockets, no -/// self-signed certificates, no real DB (the -/// AddInMemoryCollection in WebServerFixture.SetupHost -/// points the EF context at the in-memory provider). -/// -/// GET /signin is the public login endpoint served by -/// Yavsc.Org/Controllers/Accounting/AccountController -/// (decorated [HttpGet(YavscConstants.SigninPath)] with -/// YavscConstants.SigninPath = "~/signin"). A 200 means the -/// entire pipeline (routing + Razor + IdentityServer + EF + DI) -/// is wired correctly end-to-end. -/// -public class AccountSmokeTests : IClassFixture -{ - private readonly TestWebApplicationFactory _factory; - - public AccountSmokeTests(TestWebApplicationFactory factory) - { - _factory = factory; - } - - - - [Fact] - public async Task ResourceStore_get_all_resources_does_not_throw() - { - using var scope = _factory.Services.CreateScope(); - var resourceStore = scope.ServiceProvider.GetRequiredService(); - - var exception = await Record.ExceptionAsync(resourceStore.GetAllResourcesAsync); - - Assert.Null(exception); - } - - // Regression: anonymous access to a restricted page used to redirect to - // the ASP.NET Identity default LoginPath "/Account/Login", which has no - // controller here. The application cookie must redirect to the real - // sign-in endpoint at /signin instead. - [Fact] - public async Task Anonymous_access_to_protected_page_redirects_to_signin() - { - var client = _factory.CreateClient(new WebApplicationFactoryClientOptions - { - AllowAutoRedirect = false, - }); - - // EstimateController is [Authorize]; GET /Estimate hits Index(). - var response = await client.GetAsync("/Estimate", TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); - var location = response.Headers.Location; - Assert.NotNull(location); - Assert.Equal("/signin", location.AbsolutePath); - Assert.Contains("ReturnUrl=%2FEstimate", location.Query); - } - - // Regression: the POST Delete action existed, but neither the GET - // Delete view nor a rendered confirmation page was reachable — - // Views/Account/Delete.cshtml was missing. An authenticated GET - // must now return the confirmation page (200). - [Fact] - public async Task Authenticated_get_account_delete_returns_confirmation_page() - { - var client = _factory.CreateClient(new WebApplicationFactoryClientOptions - { - AllowAutoRedirect = false, - }); - client.DefaultRequestHeaders.Add( - TestAuthPolicyProvider.HeaderName, TestAuthPolicyProvider.AdminRole); - - var response = await client.GetAsync("/Account/Delete", TestContext.Current.CancellationToken); - - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync( - TestContext.Current.CancellationToken); - Assert.Contains("Supprimer mon compte", body); - } -} diff --git a/src/test/Yavsc.Server.Tests/BlogSpotServiceDetailsTests.cs b/src/test/Yavsc.Server.Tests/BlogSpotServiceDetailsTests.cs deleted file mode 100644 index 713957804..000000000 --- a/src/test/Yavsc.Server.Tests/BlogSpotServiceDetailsTests.cs +++ /dev/null @@ -1,222 +0,0 @@ -using System.Security.Claims; -using Microsoft.EntityFrameworkCore; -using Yavsc.Models.Blog; -using Yavsc.Server.Exceptions; -using Yavsc.Server.Tests.Fixtures; - -namespace Yavsc.Server.Tests; - -/// -/// Service-level tests for . -/// -/// loads a -/// (with Author, Tags, Comments, ACL and -/// Publication navigations), runs a resource-based -/// ReadPermission authorization against it, scrubs the ACL for -/// non-owners, derives from the -/// loaded Publication navigation, and eagerly loads each -/// comment's Author. These tests pin each of those behaviours -/// at the service layer against the real -/// (the public/owner/ -/// sponsor/Administrator disjunction) and a real SQLite store — no -/// mocking. -/// -/// Seeding and the service call resolve through separate DI -/// scopes so the DbContext that writes the seed rows is disposed -/// before the one reads through, -/// mirroring the per-request DbContext lifetime in production. -/// -[Collection("Yavsc Server")] -public sealed class BlogSpotServiceDetailsTests : IClassFixture -{ - private readonly ServerServicesFixture _fixture; - - public BlogSpotServiceDetailsTests(ServerServicesFixture fixture) - { - _fixture = fixture; - } - - /// An authenticated principal whose sub claim is - /// . - /// reads sub first, which is what the JwtBearer middleware - /// in production preserves (MapInboundClaims = false), and what - /// compares against - /// . - private static ClaimsPrincipal Authenticated(string userId, - params Claim[] extra) - { - var claims = new List { new("sub", userId) }; - claims.AddRange(extra); - return new ClaimsPrincipal( - new ClaimsIdentity(claims, authenticationType: "Bearer")); - } - - /// An unauthenticated principal: a - /// with no authentication type has - /// == false. The - /// service still runs authorization — a published post's - /// ReadPermission succeeds purely on IsPublic, with no - /// identity required. - private static ClaimsPrincipal Anonymous() - => new(new ClaimsIdentity()); - - /// The Microsoft-format role claim - /// - /// reads when checking the "Administrator" bypass in - /// . - private static Claim AdminRole() - => new("http://schemas.microsoft.com/ws/2008/06/identity/claims/role", - "Administrator"); - - [Fact] - public async Task Details_returns_the_post_for_its_owner_and_keeps_the_acl() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - var circleId = _fixture.SeedCircle("alice", "alice-private"); - var postId = _fixture.SeedBlogPost("alice", "Owner reads own draft"); - _fixture.SeedAcl(postId, circleId); - - var blog = await _fixture.WithServiceAsync(svc => - svc.Details(Authenticated("alice"), postId)); - - Assert.Equal(postId, blog.Id); - Assert.Equal("Owner reads own draft", blog.Title); - // The owner is the one viewer ScrubAclForViewer keeps the ACL - // for — a non-owner would see an empty list. - Assert.Single(blog.ACL!); - // No BlogSpotPublication row was seeded, so IsPublished is - // derived from the (null) Publication navigation as false. - Assert.False(blog.IsPublished); - } - - [Fact] - public async Task Details_serves_IsPublished_true_when_a_publication_row_exists() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - var postId = _fixture.SeedBlogPost("alice", "Published post"); - _fixture.Publish(postId); - - var blog = await _fixture.WithServiceAsync(svc => - svc.Details(Authenticated("alice"), postId)); - - // IsPublished is a wire-only value the service derives from - // the loaded Publication navigation (Details Includes it). - Assert.True(blog.IsPublished); - } - - [Fact] - public async Task Details_lets_an_anonymous_viewer_read_a_published_post_and_scrubs_its_acl() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - var circleId = _fixture.SeedCircle("alice", "alice-private"); - var postId = _fixture.SeedBlogPost("alice", "Public post"); - // The ACL is real, but a non-owner viewer must not see it. - _fixture.SeedAcl(postId, circleId); - _fixture.Publish(postId); - - var blog = await _fixture.WithServiceAsync(svc => - svc.Details(Anonymous(), postId)); - - // ReadPermission succeeds purely on IsPublic (the publication - // row) — no identity needed. - Assert.Equal(postId, blog.Id); - Assert.True(blog.IsPublished); - // ScrubAclForViewer: the anonymous viewer is not the owner, so - // the ACL is replaced with an empty list rather than leaked. - Assert.Empty(blog.ACL!); - } - - [Fact] - public async Task Details_denies_a_draft_to_a_non_owner_who_is_not_a_sponsor() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - _fixture.SeedUser("bob"); - var postId = _fixture.SeedBlogPost("alice", "Alice's draft"); - - // No publication row (not public), bob is not the owner, bob - // is not a member of any circle alice owns, bob is not an - // Administrator — every branch of ReadPermission fails. - await Assert.ThrowsAsync(() => - _fixture.WithServiceAsync(svc => - svc.Details(Authenticated("bob"), postId))); - } - - [Fact] - public async Task Details_lets_a_sponsor_read_a_draft_and_scrubs_its_acl() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - _fixture.SeedUser("bob"); - // Sponsorship = bob is a member of a circle alice owns. - var circleId = _fixture.SeedCircle("alice", "alice-friends"); - _fixture.SeedCircleMember(circleId, "bob"); - var postId = _fixture.SeedBlogPost("alice", "Draft for friends"); - _fixture.SeedAcl(postId, circleId); - - var blog = await _fixture.WithServiceAsync(svc => - svc.Details(Authenticated("bob"), postId)); - - // ReadPermission succeeds on the IsSponsor branch. - Assert.Equal(postId, blog.Id); - Assert.False(blog.IsPublished); - // bob is not the owner, so the ACL is scrubbed even though he - // was granted read access via the circle. - Assert.Empty(blog.ACL!); - } - - [Fact] - public async Task Details_lets_an_administrator_read_a_draft() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - _fixture.SeedUser("root"); - var postId = _fixture.SeedBlogPost("alice", "Draft, admin view"); - - var blog = await _fixture.WithServiceAsync(svc => - svc.Details(Authenticated("root", AdminRole()), postId)); - - // The Administrator bypass in PermissionHandler short-circuits - // the public/owner/sponsor checks. - Assert.Equal(postId, blog.Id); - Assert.False(blog.IsPublished); - } - - [Fact] - public async Task Details_loads_each_comment_author() - { - _fixture.ResetDatabase(); - _fixture.SeedUser("alice"); - _fixture.SeedUser("bob"); - var postId = _fixture.SeedBlogPost("alice", "Post with comments"); - _fixture.SeedComment(postId, "bob", "Nice post!"); - - var blog = await _fixture.WithServiceAsync(svc => - svc.Details(Authenticated("alice"), postId)); - - var comment = Assert.Single(blog.Comments); - // Details explicitly loads c.Author from _context.Users for - // every comment; the navigation must not be left null. - Assert.NotNull(comment.Author); - Assert.Equal("bob", comment.Author.UserName); - } - - [Fact] - public async Task Details_throws_for_a_missing_post() - { - _fixture.ResetDatabase(); - - // The query uses SingleAsync, which throws - // InvalidOperationException when no row matches — the - // `if (blog == null) return null` guard below it is unreachable. - // Pin that current behaviour so a future switch to - // SingleOrDefaultAsync (which would make the guard live) is a - // deliberate, reviewed change rather than a silent one. - await Assert.ThrowsAsync(() => - _fixture.WithServiceAsync(svc => - svc.Details(Authenticated("alice"), 424242))); - } -} \ No newline at end of file diff --git a/src/test/Yavsc.Server.Tests/Fixtures/ServerServicesFixture.cs b/src/test/Yavsc.Server.Tests/Fixtures/ServerServicesFixture.cs deleted file mode 100644 index f57f65839..000000000 --- a/src/test/Yavsc.Server.Tests/Fixtures/ServerServicesFixture.cs +++ /dev/null @@ -1,452 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Npgsql; -using Yavsc.Models; -using Yavsc.Models.Access; -using Yavsc.Models.Blog; -using Yavsc.Models.Relationship; -using Yavsc.Services; - -namespace Yavsc.Server.Tests.Fixtures; - -/// -/// Service-level test host for and the -/// other Yavsc.Server services that take an -/// + . -/// -/// Unlike BlogsWebServerFixture (in Yavsc.Blogs.Tests), -/// this fixture does not spin up Kestrel, JWT bearer, or MVC -/// controllers. takes the -/// as a parameter, -/// so the caller controls identity directly — there is no HTTP/auth -/// middleware in the path under test. Building only the DI graph the -/// service actually resolves keeps these tests fast and isolates the -/// service layer from the blog API's transport concerns, which -/// Yavsc.Blogs.Tests already covers end-to-end. -/// -/// Database provider. Mirrors -/// ApiWebServerFixture: SQLite :memory: by default, or -/// real PostgreSQL when YAVSC_SERVER_TEST_DB_PROVIDER=npgsql is -/// set. Production runs on Npgsql, so the Npgsql path — which applies -/// the real Yavsc.Org migrations against a dedicated test -/// database — is the one that catches provider-specific bugs (FK -/// enforcement, type mappings, migration drift). SQLite stays the -/// default so the suite runs out-of-the-box with no external server. -/// -/// -/// SQLite: a named :memory: database -/// (Cache=Shared) backed by a single connection held open for the -/// fixture's lifetime, schema via EnsureCreated. Reset is -/// EnsureDeleted + EnsureCreated. -/// Npgsql: a dedicated database -/// yavscServerTestDb (created from the admin connection if it -/// does not exist), schema via Migrate using the -/// Yavsc.Org migrations assembly. Reset purges the blog-graph -/// tables in FK order — children before parents — so no DELETE -/// violates a constraint, instead of dropping the database (which -/// would force a full re-migration per test). -/// The real and the -/// real : Details calls -/// IAuthorizationService.AuthorizeAsync(user, blog, new -/// ReadPermission()), and answers -/// that requirement with the public/owner/sponsor/Administrator -/// disjunction that the tests below pin. No mocking. -/// A trivial -/// stub: Details never touches the file system, but the DI -/// container needs an instance to construct the service. -/// -/// -/// Marked so a single -/// fixture instance (one store, one DI container) is shared across -/// every [Collection("Yavsc Server")] test class. Each test -/// resets the store via . -/// -[CollectionDefinition("Yavsc Server")] -public sealed class ServerServicesFixture : IDisposable -{ - private const string DbProviderEnvVar = "YAVSC_SERVER_TEST_DB_PROVIDER"; - private const string NpgsqlAdminConnectionEnvVar = "YAVSC_SERVER_TEST_NPGSQL_ADMIN_CONNECTION"; - private const string DedicatedNpgsqlDatabaseName = "yavscServerTestDb"; - - private static readonly object _npgsqlLock = new(); - private static string? _sharedNpgsqlConnectionString; - - private readonly SqliteConnection? _sqliteConnection; - private readonly ServiceProvider _services; - - public IServiceProvider Services => _services; - - public ServerServicesFixture() - { - var services = new ServiceCollection(); - - if (UseNpgsqlProvider()) - { - var npgsqlConnectionString = EnsureNpgsqlDatabaseCreated(); - services.AddDbContext(opt => - opt.UseNpgsql(npgsqlConnectionString, - x => x.MigrationsAssembly("Yavsc.Org"))); - } - else - { - // Mode=Memory + Cache=Shared: a named in-memory database - // every connection string referencing "YavscServerTests" - // resolves to the same backing store, as long as at least - // one connection stays open against it. _sqliteConnection - // is held for the fixture's lifetime (disposed in Dispose), - // so the store survives across scoped DbContexts per test. - _sqliteConnection = new SqliteConnection( - "Data Source=YavscServerTests;Mode=Memory;Cache=Shared"); - _sqliteConnection.Open(); - - services.AddDbContext(opt => - // UseSqlite(DbConnection) keeps _sqliteConnection 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 - // store. - opt.UseSqlite(_sqliteConnection)); - } - - // BlogSpotService.AttachFiles writes under siteSettings.Blog. - // Details itself doesn't read it, but the service constructor - // throws if SiteSettings.Blog is null, so configure a temp - // value to keep construction alive. - services.Configure(s => s.Blog = "server-tests-files-root"); - - // Trivial file-system auth: Details never calls into it, but - // the DI container needs an instance to construct the service. - services.AddSingleton( - new NoopFileSystemAuthManager()); - - // The real BlogSpotService — same shape the production host - // builds (ApplicationDbContext, IAuthorizationService, - // IFileSystemAuthManager, IOptions, - // ILoggerFactory). - services.AddScoped(); - - // The real PermissionHandler: Details authorizes with - // ReadPermission, and PermissionHandler resolves it via the - // IsPublic / IsOwner / IsSponsor / Administrator disjunction. - services.AddScoped(); - - // Default authorization services register - // IAuthorizationService (DefaultAuthorizationService), which - // collects the IAuthorizationHandler above and runs it against - // the requirement passed to AuthorizeAsync. - services.AddAuthorization(); - services.AddLogging(); - - _services = services.BuildServiceProvider(); - - // Create the schema once. Npgsql uses Migrate (the Yavsc.Org - // migrations) so the test schema matches production; SQLite uses - // EnsureCreated (idempotent, model-derived). ResetDatabase - // re-creates / purges per test. - using (var scope = _services.CreateScope()) - { - var db = scope.ServiceProvider - .GetRequiredService(); - if (UseNpgsqlProvider()) - db.Database.Migrate(); - else - db.Database.EnsureCreated(); - } - } - - /// True when YAVSC_SERVER_TEST_DB_PROVIDER=npgsql - /// is set — the fixture then runs against a real PostgreSQL server - /// instead of the SQLite in-memory default. - public static bool UseNpgsqlProvider() - => string.Equals( - Environment.GetEnvironmentVariable(DbProviderEnvVar), - "npgsql", - StringComparison.OrdinalIgnoreCase); - - /// Reset the store to a known empty state for the next - /// test. SQLite: EnsureDeleted + EnsureCreated (the - /// named in-memory store survives because _sqliteConnection is held - /// open). Npgsql: purge the blog-graph tables in FK order — - /// children before parents — so no DELETE trips a foreign key, - /// avoiding a full re-migration per test. - public void ResetDatabase() - { - using var scope = _services.CreateScope(); - var db = scope.ServiceProvider - .GetRequiredService(); - - if (UseNpgsqlProvider()) - { - // Children before parents. BlogAttachedFile, Comment and - // BlogSpotPublication reference BlogSpot; CircleAuthorization - // ToBlogPost references BlogSpot + Circle; BlogTag references - // BlogSpot + Tag; BlogSpot references ApplicationUser; - // CircleMember references Circle + ApplicationUser. - db.BlogAttachedFiles.RemoveRange(db.BlogAttachedFiles); - db.Comment.RemoveRange(db.Comment); - db.blogSpotPublications.RemoveRange(db.blogSpotPublications); - db.CircleAuthorizationToBlogPost - .RemoveRange(db.CircleAuthorizationToBlogPost); - db.BlogTag.RemoveRange(db.BlogTag); - db.UploadedFiles.RemoveRange(db.UploadedFiles); - db.BlogSpot.RemoveRange(db.BlogSpot); - db.CircleMembers.RemoveRange(db.CircleMembers); - db.Circle.RemoveRange(db.Circle); - db.Users.RemoveRange(db.Users); - db.SaveChanges(); - return; - } - - db.Database.EnsureDeleted(); - db.Database.EnsureCreated(); - } - - /// Resolve a fresh service scope and the - /// from it, run - /// , and dispose the scope (and its - /// DbContext). Use this for seeding writes that should not share - /// a DbContext with the service call under test. - public T WithDb(Func body) - { - using var scope = _services.CreateScope(); - var db = scope.ServiceProvider - .GetRequiredService(); - return body(db); - } - - public void SeedUser(string userName, - Action? configure = null) - { - WithDb(db => - { - if (db.Users.Any(u => u.Id == userName)) return false; - var user = new ApplicationUser - { - Id = userName, - UserName = userName, - Email = $"{userName}@example.test", - }; - configure?.Invoke(user); - db.Users.Add(user); - db.SaveChanges(); - return true; - }); - } - - public long SeedBlogPost(string authorId, string title, - Action? configure = null) - { - return WithDb(db => - { - var post = new BlogPost - { - AuthorId = authorId, - Title = title, - Article = "Test article body.", - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - }; - configure?.Invoke(post); - db.BlogSpot.Add(post); - db.SaveChanges(); - return post.Id; - }); - } - - /// Insert a row for - /// — the existence of that row is what - /// makes a post "public" (readable by anyone) in - /// . This is the same write - /// SetPublishAsync performs in production. - public void Publish(long postId) - { - WithDb(db => - { - db.blogSpotPublications.Add( - new BlogSpotPublication { PostId = postId }); - db.SaveChanges(); - return true; - }); - } - - /// Seed a circle owned by and - /// return its server-assigned id. Sponsorship in - /// is "the viewer is a member of a - /// circle the post's author owns", so a circle + a - /// entry is what grants a non-owner - /// read access to a draft. - public long SeedCircle(string ownerId, string name) - { - return WithDb(db => - { - var circle = new Circle - { - OwnerId = ownerId, - Name = name, - Public = false, - }; - db.Circle.Add(circle); - db.SaveChanges(); - return circle.Id; - }); - } - - public void SeedCircleMember(long circleId, string memberId) - { - WithDb(db => - { - db.CircleMembers.Add( - new CircleMember { CircleId = circleId, MemberId = memberId }); - db.SaveChanges(); - return true; - }); - } - - /// Attach a ACL - /// entry to for . - /// The owner always sees the ACL; non-owners have it scrubbed by - /// 's ScrubAclForViewer. - public void SeedAcl(long postId, long circleId) - { - WithDb(db => - { - db.CircleAuthorizationToBlogPost.Add( - new CircleAuthorizationToBlogPost - { - BlogPostId = postId, - CircleId = circleId, - }); - db.SaveChanges(); - return true; - }); - } - - /// Seed a comment on authored by - /// . - /// loads each comment's Author navigation from - /// _context.Users; seeding the user first keeps the FK - /// valid (SQLite and PostgreSQL both enforce it). - public void SeedComment(long postId, string authorId, string article) - { - WithDb(db => - { - db.Comment.Add(new Comment - { - ReceiverId = postId, - AuthorId = authorId, - Article = article, - Visible = true, - UserModified = authorId, - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - }); - db.SaveChanges(); - return true; - }); - } - - /// Resolve the real from the - /// fixture's DI container, run against it, - /// and dispose the request scope (and its scoped DbContext). The - /// service is scoped, so it must be resolved from a scope — and the - /// scope is what we dispose to avoid leaking a DbContext against - /// the shared connection across tests. - public async Task WithServiceAsync( - Func> body) - { - using var scope = _services.CreateScope(); - var service = scope.ServiceProvider - .GetRequiredService(); - return await body(service); - } - - 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() - { - // The admin connection string (host + credentials) is supplied - // via environment, never embedded in source — committing a - // real dev password would leak it into git history. The Npgsql - // path is opt-in, so requiring the env var is fine: a missing - // value produces a clear failure rather than a silent fallback - // onto someone's local dev database. - var configured = Environment.GetEnvironmentVariable(NpgsqlAdminConnectionEnvVar); - if (string.IsNullOrWhiteSpace(configured)) - throw new InvalidOperationException( - $"{NpgsqlAdminConnectionEnvVar} is not set. The Npgsql test " - + "provider requires an admin PostgreSQL connection string " - + "in that environment variable (e.g. " - + "'Server=localhost;Port=5432;Database=postgres;" - + "Username=yavscdev;Password=...;Include Error Detail=true')."); - - var builder = new NpgsqlConnectionStringBuilder(configured) - { - Pooling = false, - IncludeErrorDetail = true - }; - - // The admin connection targets the maintenance database - // (postgres) so we can CREATE/DROP the dedicated test database; - // whatever database the env var names is overridden here. - builder.Database = "postgres"; - - return builder.ToString(); - } - - public void Dispose() - { - _services.Dispose(); - _sqliteConnection?.Dispose(); - } - - 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) { } - } -} \ No newline at end of file diff --git a/src/test/Yavsc.Server.Tests/Yavsc.Server.Tests.csproj b/src/test/Yavsc.Server.Tests/Yavsc.Server.Tests.csproj deleted file mode 100644 index bb62ead21..000000000 --- a/src/test/Yavsc.Server.Tests/Yavsc.Server.Tests.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - net10.0 - enable - enable - false - Yavsc.Server.Tests - true - 1.1.0.0 - 1.1.0.0 - 1.1.0-estimate.1+364.Branch.feature-estimate.Sha.3d6ab6e72f9ba4ccfa250a1cce154b550281405e - 1.1.0-estimate.1 - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/yavsc.sln b/yavsc.sln index e6caef782..6085a85fd 100644 --- a/yavsc.sln +++ b/yavsc.sln @@ -15,6 +15,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Api", "src\Yavsc.Api\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cli", "src\cli\cli.csproj", "{27DA6CEC-4266-4F78-BB32-8388F9A401D0}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{76A7FBA6-B7EC-4864-85C0-1F470794BA0F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Org.Tests", "src\Yavsc.Org.Tests\Yavsc.Org.Tests.csproj", "{CDCF676A-2037-472E-8C8B-67C5E4FDFEA1}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Blogs", "src\Yavsc.Blogs\Yavsc.Blogs.csproj", "{5D9E161F-3CB3-4854-9D07-3DF6882EA66C}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PostIt", "PostIt", "{E13D107F-4053-D0DE-6394-453609595BFE}" @@ -27,21 +31,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostIt.Browser", "src\PostI EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostIt.Desktop", "src\PostIt\PostIt.Desktop\PostIt.Desktop.csproj", "{EFE24256-9335-44C5-8B77-E180C2DB3C0B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Blogs.Tests", "src\Yavsc.Blogs.Tests\Yavsc.Blogs.Tests.csproj", "{0E471075-DABF-40E9-98B7-1630BEF19145}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Tests.Shared", "src\Yavsc.Tests.Shared\Yavsc.Tests.Shared.csproj", "{34D1F73D-BF74-47CC-9358-9F4F221C75D7}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Api.Client", "src\Yavsc.Api.Client\Yavsc.Api.Client.csproj", "{59AF5DEA-D349-495A-BC44-FC7BD4E55099}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PostIt.Tests", "src\PostIt\PostIt.Tests\PostIt.Tests.csproj", "{021E6F5A-B81D-42A7-9918-DFE39B424FC2}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6EA238F3-C35C-B265-FF02-A4695B9C69A9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Api.Tests", "src\test\Yavsc.Api.Tests\Yavsc.Api.Tests.csproj", "{70141DC1-39E5-4350-B995-9625347D29AA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Tests.Shared", "src\test\Yavsc.Tests.Shared\Yavsc.Tests.Shared.csproj", "{1A563263-B5E7-4B44-9AE9-AD357C2FA1A4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Server.Tests", "src\test\Yavsc.Server.Tests\Yavsc.Server.Tests.csproj", "{8788BE1C-A4D5-4641-A426-31F4D8D1BBFF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Org.Tests", "src\test\Yavsc.Org.Tests\Yavsc.Org.Tests.csproj", "{7CC76BA8-D4C3-46DA-96A2-F028B6B0058B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Blogs.Tests", "src\test\Yavsc.Blogs.Tests\Yavsc.Blogs.Tests.csproj", "{22ECDDEE-EFD7-4501-9905-BF4E27C605C3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yavsc.Api.Test", "src\Yavsc.Api.Test\Yavsc.Api.Test.csproj", "{49A42F0E-6E18-4617-9716-3E728966D96F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -113,6 +111,18 @@ Global {27DA6CEC-4266-4F78-BB32-8388F9A401D0}.Release|x64.Build.0 = Release|Any CPU {27DA6CEC-4266-4F78-BB32-8388F9A401D0}.Release|x86.ActiveCfg = Release|Any CPU {27DA6CEC-4266-4F78-BB32-8388F9A401D0}.Release|x86.Build.0 = Release|Any CPU + {CDCF676A-2037-472E-8C8B-67C5E4FDFEA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDCF676A-2037-472E-8C8B-67C5E4FDFEA1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDCF676A-2037-472E-8C8B-67C5E4FDFEA1}.Debug|x64.ActiveCfg = Debug|Any CPU + {CDCF676A-2037-472E-8C8B-67C5E4FDFEA1}.Debug|x64.Build.0 = Debug|Any CPU + {CDCF676A-2037-472E-8C8B-67C5E4FDFEA1}.Debug|x86.ActiveCfg = Debug|Any CPU + {CDCF676A-2037-472E-8C8B-67C5E4FDFEA1}.Debug|x86.Build.0 = Debug|Any CPU + {CDCF676A-2037-472E-8C8B-67C5E4FDFEA1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDCF676A-2037-472E-8C8B-67C5E4FDFEA1}.Release|Any CPU.Build.0 = Release|Any CPU + {CDCF676A-2037-472E-8C8B-67C5E4FDFEA1}.Release|x64.ActiveCfg = Release|Any CPU + {CDCF676A-2037-472E-8C8B-67C5E4FDFEA1}.Release|x64.Build.0 = Release|Any CPU + {CDCF676A-2037-472E-8C8B-67C5E4FDFEA1}.Release|x86.ActiveCfg = Release|Any CPU + {CDCF676A-2037-472E-8C8B-67C5E4FDFEA1}.Release|x86.Build.0 = Release|Any CPU {5D9E161F-3CB3-4854-9D07-3DF6882EA66C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5D9E161F-3CB3-4854-9D07-3DF6882EA66C}.Debug|Any CPU.Build.0 = Debug|Any CPU {5D9E161F-3CB3-4854-9D07-3DF6882EA66C}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -173,6 +183,30 @@ Global {EFE24256-9335-44C5-8B77-E180C2DB3C0B}.Release|x64.Build.0 = Release|Any CPU {EFE24256-9335-44C5-8B77-E180C2DB3C0B}.Release|x86.ActiveCfg = Release|Any CPU {EFE24256-9335-44C5-8B77-E180C2DB3C0B}.Release|x86.Build.0 = Release|Any CPU + {0E471075-DABF-40E9-98B7-1630BEF19145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E471075-DABF-40E9-98B7-1630BEF19145}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E471075-DABF-40E9-98B7-1630BEF19145}.Debug|x64.ActiveCfg = Debug|Any CPU + {0E471075-DABF-40E9-98B7-1630BEF19145}.Debug|x64.Build.0 = Debug|Any CPU + {0E471075-DABF-40E9-98B7-1630BEF19145}.Debug|x86.ActiveCfg = Debug|Any CPU + {0E471075-DABF-40E9-98B7-1630BEF19145}.Debug|x86.Build.0 = Debug|Any CPU + {0E471075-DABF-40E9-98B7-1630BEF19145}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E471075-DABF-40E9-98B7-1630BEF19145}.Release|Any CPU.Build.0 = Release|Any CPU + {0E471075-DABF-40E9-98B7-1630BEF19145}.Release|x64.ActiveCfg = Release|Any CPU + {0E471075-DABF-40E9-98B7-1630BEF19145}.Release|x64.Build.0 = Release|Any CPU + {0E471075-DABF-40E9-98B7-1630BEF19145}.Release|x86.ActiveCfg = Release|Any CPU + {0E471075-DABF-40E9-98B7-1630BEF19145}.Release|x86.Build.0 = Release|Any CPU + {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Debug|x64.ActiveCfg = Debug|Any CPU + {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Debug|x64.Build.0 = Debug|Any CPU + {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Debug|x86.ActiveCfg = Debug|Any CPU + {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Debug|x86.Build.0 = Debug|Any CPU + {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|Any CPU.Build.0 = Release|Any CPU + {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|x64.ActiveCfg = Release|Any CPU + {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|x64.Build.0 = Release|Any CPU + {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|x86.ActiveCfg = Release|Any CPU + {34D1F73D-BF74-47CC-9358-9F4F221C75D7}.Release|x86.Build.0 = Release|Any CPU {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Debug|Any CPU.Build.0 = Debug|Any CPU {59AF5DEA-D349-495A-BC44-FC7BD4E55099}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -197,66 +231,18 @@ Global {021E6F5A-B81D-42A7-9918-DFE39B424FC2}.Release|x64.Build.0 = Release|Any CPU {021E6F5A-B81D-42A7-9918-DFE39B424FC2}.Release|x86.ActiveCfg = Release|Any CPU {021E6F5A-B81D-42A7-9918-DFE39B424FC2}.Release|x86.Build.0 = Release|Any CPU - {70141DC1-39E5-4350-B995-9625347D29AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {70141DC1-39E5-4350-B995-9625347D29AA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {70141DC1-39E5-4350-B995-9625347D29AA}.Debug|x64.ActiveCfg = Debug|Any CPU - {70141DC1-39E5-4350-B995-9625347D29AA}.Debug|x64.Build.0 = Debug|Any CPU - {70141DC1-39E5-4350-B995-9625347D29AA}.Debug|x86.ActiveCfg = Debug|Any CPU - {70141DC1-39E5-4350-B995-9625347D29AA}.Debug|x86.Build.0 = Debug|Any CPU - {70141DC1-39E5-4350-B995-9625347D29AA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {70141DC1-39E5-4350-B995-9625347D29AA}.Release|Any CPU.Build.0 = Release|Any CPU - {70141DC1-39E5-4350-B995-9625347D29AA}.Release|x64.ActiveCfg = Release|Any CPU - {70141DC1-39E5-4350-B995-9625347D29AA}.Release|x64.Build.0 = Release|Any CPU - {70141DC1-39E5-4350-B995-9625347D29AA}.Release|x86.ActiveCfg = Release|Any CPU - {70141DC1-39E5-4350-B995-9625347D29AA}.Release|x86.Build.0 = Release|Any CPU - {1A563263-B5E7-4B44-9AE9-AD357C2FA1A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1A563263-B5E7-4B44-9AE9-AD357C2FA1A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1A563263-B5E7-4B44-9AE9-AD357C2FA1A4}.Debug|x64.ActiveCfg = Debug|Any CPU - {1A563263-B5E7-4B44-9AE9-AD357C2FA1A4}.Debug|x64.Build.0 = Debug|Any CPU - {1A563263-B5E7-4B44-9AE9-AD357C2FA1A4}.Debug|x86.ActiveCfg = Debug|Any CPU - {1A563263-B5E7-4B44-9AE9-AD357C2FA1A4}.Debug|x86.Build.0 = Debug|Any CPU - {1A563263-B5E7-4B44-9AE9-AD357C2FA1A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1A563263-B5E7-4B44-9AE9-AD357C2FA1A4}.Release|Any CPU.Build.0 = Release|Any CPU - {1A563263-B5E7-4B44-9AE9-AD357C2FA1A4}.Release|x64.ActiveCfg = Release|Any CPU - {1A563263-B5E7-4B44-9AE9-AD357C2FA1A4}.Release|x64.Build.0 = Release|Any CPU - {1A563263-B5E7-4B44-9AE9-AD357C2FA1A4}.Release|x86.ActiveCfg = Release|Any CPU - {1A563263-B5E7-4B44-9AE9-AD357C2FA1A4}.Release|x86.Build.0 = Release|Any CPU - {8788BE1C-A4D5-4641-A426-31F4D8D1BBFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8788BE1C-A4D5-4641-A426-31F4D8D1BBFF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8788BE1C-A4D5-4641-A426-31F4D8D1BBFF}.Debug|x64.ActiveCfg = Debug|Any CPU - {8788BE1C-A4D5-4641-A426-31F4D8D1BBFF}.Debug|x64.Build.0 = Debug|Any CPU - {8788BE1C-A4D5-4641-A426-31F4D8D1BBFF}.Debug|x86.ActiveCfg = Debug|Any CPU - {8788BE1C-A4D5-4641-A426-31F4D8D1BBFF}.Debug|x86.Build.0 = Debug|Any CPU - {8788BE1C-A4D5-4641-A426-31F4D8D1BBFF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8788BE1C-A4D5-4641-A426-31F4D8D1BBFF}.Release|Any CPU.Build.0 = Release|Any CPU - {8788BE1C-A4D5-4641-A426-31F4D8D1BBFF}.Release|x64.ActiveCfg = Release|Any CPU - {8788BE1C-A4D5-4641-A426-31F4D8D1BBFF}.Release|x64.Build.0 = Release|Any CPU - {8788BE1C-A4D5-4641-A426-31F4D8D1BBFF}.Release|x86.ActiveCfg = Release|Any CPU - {8788BE1C-A4D5-4641-A426-31F4D8D1BBFF}.Release|x86.Build.0 = Release|Any CPU - {7CC76BA8-D4C3-46DA-96A2-F028B6B0058B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7CC76BA8-D4C3-46DA-96A2-F028B6B0058B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7CC76BA8-D4C3-46DA-96A2-F028B6B0058B}.Debug|x64.ActiveCfg = Debug|Any CPU - {7CC76BA8-D4C3-46DA-96A2-F028B6B0058B}.Debug|x64.Build.0 = Debug|Any CPU - {7CC76BA8-D4C3-46DA-96A2-F028B6B0058B}.Debug|x86.ActiveCfg = Debug|Any CPU - {7CC76BA8-D4C3-46DA-96A2-F028B6B0058B}.Debug|x86.Build.0 = Debug|Any CPU - {7CC76BA8-D4C3-46DA-96A2-F028B6B0058B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7CC76BA8-D4C3-46DA-96A2-F028B6B0058B}.Release|Any CPU.Build.0 = Release|Any CPU - {7CC76BA8-D4C3-46DA-96A2-F028B6B0058B}.Release|x64.ActiveCfg = Release|Any CPU - {7CC76BA8-D4C3-46DA-96A2-F028B6B0058B}.Release|x64.Build.0 = Release|Any CPU - {7CC76BA8-D4C3-46DA-96A2-F028B6B0058B}.Release|x86.ActiveCfg = Release|Any CPU - {7CC76BA8-D4C3-46DA-96A2-F028B6B0058B}.Release|x86.Build.0 = Release|Any CPU - {22ECDDEE-EFD7-4501-9905-BF4E27C605C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {22ECDDEE-EFD7-4501-9905-BF4E27C605C3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {22ECDDEE-EFD7-4501-9905-BF4E27C605C3}.Debug|x64.ActiveCfg = Debug|Any CPU - {22ECDDEE-EFD7-4501-9905-BF4E27C605C3}.Debug|x64.Build.0 = Debug|Any CPU - {22ECDDEE-EFD7-4501-9905-BF4E27C605C3}.Debug|x86.ActiveCfg = Debug|Any CPU - {22ECDDEE-EFD7-4501-9905-BF4E27C605C3}.Debug|x86.Build.0 = Debug|Any CPU - {22ECDDEE-EFD7-4501-9905-BF4E27C605C3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {22ECDDEE-EFD7-4501-9905-BF4E27C605C3}.Release|Any CPU.Build.0 = Release|Any CPU - {22ECDDEE-EFD7-4501-9905-BF4E27C605C3}.Release|x64.ActiveCfg = Release|Any CPU - {22ECDDEE-EFD7-4501-9905-BF4E27C605C3}.Release|x64.Build.0 = Release|Any CPU - {22ECDDEE-EFD7-4501-9905-BF4E27C605C3}.Release|x86.ActiveCfg = Release|Any CPU - {22ECDDEE-EFD7-4501-9905-BF4E27C605C3}.Release|x86.Build.0 = Release|Any CPU + {49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|x64.ActiveCfg = Debug|Any CPU + {49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|x64.Build.0 = Debug|Any CPU + {49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|x86.ActiveCfg = Debug|Any CPU + {49A42F0E-6E18-4617-9716-3E728966D96F}.Debug|x86.Build.0 = Debug|Any CPU + {49A42F0E-6E18-4617-9716-3E728966D96F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {49A42F0E-6E18-4617-9716-3E728966D96F}.Release|Any CPU.Build.0 = Release|Any CPU + {49A42F0E-6E18-4617-9716-3E728966D96F}.Release|x64.ActiveCfg = Release|Any CPU + {49A42F0E-6E18-4617-9716-3E728966D96F}.Release|x64.Build.0 = Release|Any CPU + {49A42F0E-6E18-4617-9716-3E728966D96F}.Release|x86.ActiveCfg = Release|Any CPU + {49A42F0E-6E18-4617-9716-3E728966D96F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -267,19 +253,17 @@ Global {88142EC2-FEB2-4D69-AD3A-74F3364360F0} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2} {95DAD2B5-4324-4E89-A337-5703E45CFBE8} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2} {27DA6CEC-4266-4F78-BB32-8388F9A401D0} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2} + {CDCF676A-2037-472E-8C8B-67C5E4FDFEA1} = {76A7FBA6-B7EC-4864-85C0-1F470794BA0F} {5D9E161F-3CB3-4854-9D07-3DF6882EA66C} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2} {E13D107F-4053-D0DE-6394-453609595BFE} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2} {38B554FD-2AAF-4278-B87B-729FA4DC0111} = {E13D107F-4053-D0DE-6394-453609595BFE} {4C092CF8-524A-494D-AAFC-69383DFBA31D} = {E13D107F-4053-D0DE-6394-453609595BFE} {AF96C1C4-D128-4CD7-A8BB-D194E6D270F0} = {E13D107F-4053-D0DE-6394-453609595BFE} {EFE24256-9335-44C5-8B77-E180C2DB3C0B} = {E13D107F-4053-D0DE-6394-453609595BFE} + {0E471075-DABF-40E9-98B7-1630BEF19145} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2} + {34D1F73D-BF74-47CC-9358-9F4F221C75D7} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2} {59AF5DEA-D349-495A-BC44-FC7BD4E55099} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2} {021E6F5A-B81D-42A7-9918-DFE39B424FC2} = {E13D107F-4053-D0DE-6394-453609595BFE} - {6EA238F3-C35C-B265-FF02-A4695B9C69A9} = {CDB1BDB5-53F9-4B43-864F-60F2E74F44E2} - {70141DC1-39E5-4350-B995-9625347D29AA} = {6EA238F3-C35C-B265-FF02-A4695B9C69A9} - {1A563263-B5E7-4B44-9AE9-AD357C2FA1A4} = {6EA238F3-C35C-B265-FF02-A4695B9C69A9} - {8788BE1C-A4D5-4641-A426-31F4D8D1BBFF} = {6EA238F3-C35C-B265-FF02-A4695B9C69A9} - {7CC76BA8-D4C3-46DA-96A2-F028B6B0058B} = {6EA238F3-C35C-B265-FF02-A4695B9C69A9} - {22ECDDEE-EFD7-4501-9905-BF4E27C605C3} = {6EA238F3-C35C-B265-FF02-A4695B9C69A9} + {49A42F0E-6E18-4617-9716-3E728966D96F} = {76A7FBA6-B7EC-4864-85C0-1F470794BA0F} EndGlobalSection EndGlobal