From 900bb5e845c3ad44defbd427cae3d44a4a43321f Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Sat, 25 Feb 2023 13:15:44 +0100 Subject: feat: Initial commit --- code/api/.build.yaml | 23 + code/api/.dockerignore | 10 + code/api/.version | 1 + code/api/.version-dev | 1 + code/api/CHANGELOG.md | 123 + code/api/Dockerfile | 16 + code/api/build_and_push.sh | 89 + code/api/cliff.toml | 62 + code/api/sql/quartz-create.sql | 156 + code/api/sql/quartz-drop.sql | 23 + code/api/src/Endpoints/EndpointBase.cs | 54 + .../Internal/Account/CreateAccountRoute.cs | 64 + .../Internal/Account/CreateInitialAccountRoute.cs | 32 + .../Internal/Account/DeleteAccountRoute.cs | 17 + .../Endpoints/Internal/Account/GetAccountRoute.cs | 26 + .../src/Endpoints/Internal/Account/LoginRoute.cs | 37 + .../src/Endpoints/Internal/Account/LogoutRoute.cs | 22 + .../Internal/Account/UpdateAccountRoute.cs | 59 + .../api/src/Endpoints/Internal/Account/_calls.http | 9 + .../api/src/Endpoints/Internal/INT_EndpointBase.cs | 9 + .../CreateResetRequestRoute.cs | 40 + .../FulfillResetRequestRoute.cs | 36 + .../IsResetRequestValidRoute.cs | 26 + .../Internal/PasswordResetRequests/_calls.http | 22 + .../src/Endpoints/Internal/Root/GetSessionRoute.cs | 64 + .../Internal/Root/IsAuthenticatedRoute.cs | 10 + .../Internal/Root/ReadConfigurationRoute.cs | 17 + .../Internal/Root/RefreshConfigurationRoute.cs | 15 + .../src/Endpoints/Internal/Root/ValidateRoute.cs | 39 + code/api/src/Endpoints/Internal/RouteBaseAsync.cs | 73 + code/api/src/Endpoints/Internal/RouteBaseSync.cs | 53 + code/api/src/Endpoints/V1/ApiSpecV1.cs | 18 + .../src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs | 58 + .../src/Endpoints/V1/ApiTokens/DeleteTokenRoute.cs | 31 + .../src/Endpoints/V1/ApiTokens/GetTokensRoute.cs | 36 + .../Endpoints/V1/Customers/CreateCustomerRoute.cs | 66 + .../Endpoints/V1/Projects/CreateProjectRoute.cs | 83 + .../src/Endpoints/V1/Projects/GetProjectsRoute.cs | 40 + code/api/src/Endpoints/V1/Projects/_calls.http | 12 + code/api/src/Endpoints/V1/RouteBaseAsync.cs | 73 + code/api/src/Endpoints/V1/RouteBaseSync.cs | 53 + code/api/src/Endpoints/V1/V1_EndpointBase.cs | 29 + code/api/src/IOL.GreatOffice.Api.csproj | 71 + code/api/src/Jobs/AccessTokenCleanupJob.cs | 20 + code/api/src/Jobs/JobRegister.cs | 23 + code/api/src/Jobs/VaultTokenRenewalJob.cs | 24 + .../20210517202115_InitialMigration.Designer.cs | 238 ++ .../Migrations/20210517202115_InitialMigration.cs | 162 + ...10522165932_RenameNoteToDescription.Designer.cs | 229 + .../20210522165932_RenameNoteToDescription.cs | 34 + .../20211002113037_V6Migration.Designer.cs | 233 ++ .../src/Migrations/20211002113037_V6Migration.cs | 130 + .../20220225143559_GithubUserMappings.Designer.cs | 270 ++ .../20220225143559_GithubUserMappings.cs | 43 + .../20220319135910_RenameCreated.Designer.cs | 270 ++ .../src/Migrations/20220319135910_RenameCreated.cs | 65 + .../20220319144958_ModifiedAt.Designer.cs | 290 ++ .../src/Migrations/20220319144958_ModifiedAt.cs | 66 + .../Migrations/20220319203018_UserBase.Designer.cs | 322 ++ code/api/src/Migrations/20220319203018_UserBase.cs | 140 + .../Migrations/20220320115601_Update1.Designer.cs | 342 ++ code/api/src/Migrations/20220320115601_Update1.cs | 139 + ...32220_UpdatedForgotPasswordRequests.Designer.cs | 344 ++ ...20220320132220_UpdatedForgotPasswordRequests.cs | 57 + .../20220529190359_ApiAccessTokens.Designer.cs | 401 ++ .../Migrations/20220529190359_ApiAccessTokens.cs | 48 + .../Migrations/20220530174741_Tenants.Designer.cs | 710 ++++ code/api/src/Migrations/20220530174741_Tenants.cs | 481 +++ .../20220530175322_RemoveUnusedNavs.Designer.cs | 686 +++ .../Migrations/20220530175322_RemoveUnusedNavs.cs | 78 + ...02214238_NullableOptionalBaseFields.Designer.cs | 656 +++ .../20220602214238_NullableOptionalBaseFields.cs | 649 +++ .../20220606232346_FleshOutNewModules.Designer.cs | 510 +++ .../20220606232346_FleshOutNewModules.cs | 630 +++ .../20220616170311_DataProtectionKeys.Designer.cs | 533 +++ .../20220616170311_DataProtectionKeys.cs | 33 + .../20220819203816_RemoveGithubUsers.Designer.cs | 496 +++ .../Migrations/20220819203816_RemoveGithubUsers.cs | 43 + ...030080515_InitialProjectAndCustomer.Designer.cs | 1074 +++++ .../20221030080515_InitialProjectAndCustomer.cs | 304 ++ .../20221030081459_DeletedAt.Designer.cs | 1126 +++++ .../api/src/Migrations/20221030081459_DeletedAt.cs | 146 + .../20221030084716_MinorChanges.Designer.cs | 1126 +++++ .../src/Migrations/20221030084716_MinorChanges.cs | 105 + .../20221030090557_MoreMinorChanges.Designer.cs | 1148 +++++ .../Migrations/20221030090557_MoreMinorChanges.cs | 78 + ...21031165813_TodoAndOwnerNavigations.Designer.cs | 1863 +++++++++ .../20221031165813_TodoAndOwnerNavigations.cs | 1092 +++++ .../20221114034213_RemoveTimeTracker.Designer.cs | 1589 +++++++ .../Migrations/20221114034213_RemoveTimeTracker.cs | 177 + ...035223_RenameForgotPasswordRequests.Designer.cs | 1589 +++++++ .../20221114035223_RenameForgotPasswordRequests.cs | 78 + .../20221209041908_TenantSlug.Designer.cs | 1593 +++++++ .../src/Migrations/20221209041908_TenantSlug.cs | 28 + ...20221209043806_ValidationEmailQueue.Designer.cs | 1618 ++++++++ .../20221209043806_ValidationEmailQueue.cs | 46 + .../20221214143556_AddUserDeletedBy.Designer.cs | 1622 ++++++++ .../Migrations/20221214143556_AddUserDeletedBy.cs | 29 + .../src/Migrations/AppDbContextModelSnapshot.cs | 1619 ++++++++ code/api/src/Models/Database/Api/ApiAccessToken.cs | 12 + code/api/src/Models/Database/Base.cs | 22 + code/api/src/Models/Database/BaseWithOwner.cs | 40 + code/api/src/Models/Database/Customer/Customer.cs | 29 + .../Models/Database/Customer/CustomerContact.cs | 12 + .../src/Models/Database/Customer/CustomerEvent.cs | 8 + .../src/Models/Database/Customer/CustomerGroup.cs | 7 + .../Database/Customer/CustomerGroupMembership.cs | 7 + .../Database/Internal/PasswordResetRequest.cs | 23 + code/api/src/Models/Database/Internal/Tenant.cs | 12 + code/api/src/Models/Database/Internal/User.cs | 44 + code/api/src/Models/Database/MainAppDatabase.cs | 107 + code/api/src/Models/Database/Project/Project.cs | 15 + .../src/Models/Database/Project/ProjectLabel.cs | 8 + .../src/Models/Database/Project/ProjectMember.cs | 8 + .../src/Models/Database/Queues/ValidationEmail.cs | 8 + code/api/src/Models/Database/Todo/Todo.cs | 17 + .../api/src/Models/Database/Todo/TodoCollection.cs | 11 + .../Database/Todo/TodoCollectionAccessControl.cs | 12 + code/api/src/Models/Database/Todo/TodoComment.cs | 8 + code/api/src/Models/Database/Todo/TodoLabel.cs | 8 + .../Enums/FulfillPasswordResetRequestResult.cs | 8 + .../src/Models/Enums/PasswordResetRequestStatus.cs | 6 + code/api/src/Models/Enums/ProjectRole.cs | 9 + code/api/src/Models/Enums/TodoClosingStatement.cs | 13 + code/api/src/Models/Enums/TodoVisibility.cs | 10 + code/api/src/Models/Misc/ApiSpecDocument.cs | 9 + code/api/src/Models/Misc/AppConfiguration.cs | 118 + code/api/src/Models/Misc/AppPath.cs | 23 + code/api/src/Models/Misc/KnownProblemModel.cs | 26 + code/api/src/Models/Misc/LoggedInUserModel.cs | 8 + code/api/src/Models/Misc/RequestTimeZoneInfo.cs | 8 + code/api/src/Models/Static/AppClaims.cs | 7 + code/api/src/Models/Static/AppConstants.cs | 12 + code/api/src/Models/Static/AppCookies.cs | 7 + code/api/src/Models/Static/AppDateTime.cs | 16 + .../src/Models/Static/AppEnvironmentVariables.cs | 21 + code/api/src/Models/Static/AppHeaders.cs | 7 + code/api/src/Models/Static/AppPaths.cs | 17 + code/api/src/Models/Static/JsonSettings.cs | 11 + code/api/src/Program.cs | 249 ++ code/api/src/Properties/launchSettings.json | 14 + code/api/src/Resources/SharedResources.cs | 4 + .../src/Resources/SharedResources.en.Designer.cs | 84 + code/api/src/Resources/SharedResources.en.resx | 45 + .../src/Resources/SharedResources.nb.Designer.cs | 120 + code/api/src/Resources/SharedResources.nb.resx | 101 + code/api/src/Resources/SharedResources.resx | 33 + code/api/src/Services/EmailValidationService.cs | 67 + code/api/src/Services/MailService.cs | 132 + code/api/src/Services/PasswordResetService.cs | 101 + code/api/src/Services/TenantService.cs | 61 + code/api/src/Services/UserService.cs | 54 + code/api/src/Services/VaultService.cs | 237 ++ .../src/Utilities/BasicAuthenticationAttribute.cs | 39 + .../src/Utilities/BasicAuthenticationHandler.cs | 79 + code/api/src/Utilities/ConfigurationExtensions.cs | 85 + code/api/src/Utilities/DateTimeExtensions.cs | 8 + .../api/src/Utilities/PaginationOperationFilter.cs | 39 + code/api/src/Utilities/QuartzJsonSerializer.cs | 16 + code/api/src/Utilities/QueryableExtensions.cs | 12 + code/api/src/Utilities/SwaggerDefaultValues.cs | 58 + .../src/Utilities/SwaggerGenOptionsExtensions.cs | 43 + code/api/src/wwwroot/version.txt | 1 + .../ApplicationTests/LoginPageTests.cs | 23 + .../Helpers/Element.cs | 6 + .../Helpers/WebServerFixture.cs | 48 + .../IOL.GreatOffice.IntegrationTests.csproj | 21 + code/app/.dockerignore | 7 + code/app/.env-example | 4 + code/app/.gitignore | 8 + code/app/.npmrc | 1 + code/app/.typesafe-i18n.json | 5 + code/app/.version | 1 + code/app/.version-dev | 1 + code/app/Dockerfile | 13 + code/app/build_and_push.sh | 89 + code/app/package.json | 50 + code/app/playwright.config.ts | 10 + code/app/pnpm-lock.yaml | 4389 ++++++++++++++++++++ code/app/postcss.config.cjs | 13 + code/app/src/actions/pwKey.ts | 7 + code/app/src/app.d.ts | 9 + code/app/src/app.html | 14 + code/app/src/app.pcss | 34 + code/app/src/components/alert.svelte | 268 ++ code/app/src/components/badge.svelte | 76 + code/app/src/components/button.svelte | 116 + code/app/src/components/checkbox.svelte | 29 + code/app/src/components/combobox.svelte | 451 ++ code/app/src/components/icons/adjustments.svelte | 14 + .../src/components/icons/bars-3-center-left.svelte | 15 + code/app/src/components/icons/calendar.svelte | 14 + code/app/src/components/icons/check-circle.svelte | 13 + code/app/src/components/icons/chevron-down.svelte | 7 + .../src/components/icons/chevron-up-down.svelte | 13 + code/app/src/components/icons/chevron-up.svelte | 7 + code/app/src/components/icons/database.svelte | 14 + .../src/components/icons/exclamation-circle.svelte | 13 + .../components/icons/exclamation-triangle.svelte | 13 + code/app/src/components/icons/folder-open.svelte | 14 + code/app/src/components/icons/funnel.svelte | 7 + code/app/src/components/icons/home.svelte | 14 + code/app/src/components/icons/index.ts | 47 + .../src/components/icons/information-circle.svelte | 13 + .../src/components/icons/magnifying-glass.svelte | 13 + code/app/src/components/icons/megaphone.svelte | 14 + code/app/src/components/icons/menu.svelte | 14 + code/app/src/components/icons/queue-list.svelte | 14 + code/app/src/components/icons/spinner.svelte | 20 + code/app/src/components/icons/x-circle.svelte | 13 + code/app/src/components/icons/x-mark.svelte | 11 + code/app/src/components/icons/x.svelte | 14 + code/app/src/components/index.ts | 25 + code/app/src/components/input.svelte | 112 + code/app/src/components/locale-switcher.svelte | 56 + code/app/src/components/notification.svelte | 119 + .../app/src/components/project-status-badge.svelte | 25 + code/app/src/components/switch.svelte | 125 + code/app/src/components/textarea.svelte | 81 + code/app/src/configuration/index.ts | 64 + code/app/src/global.d.ts | 11 + code/app/src/hooks.server.ts | 49 + code/app/src/i18n/en/app/index.ts | 7 + code/app/src/i18n/en/index.ts | 63 + code/app/src/i18n/formatters.ts | 13 + code/app/src/i18n/i18n-svelte.ts | 12 + code/app/src/i18n/i18n-types.ts | 461 ++ code/app/src/i18n/i18n-util.async.ts | 42 + code/app/src/i18n/i18n-util.sync.ts | 35 + code/app/src/i18n/i18n-util.ts | 45 + code/app/src/i18n/nb/app/index.ts | 7 + code/app/src/i18n/nb/index.ts | 51 + code/app/src/models/base/Customer.ts | 21 + code/app/src/models/base/CustomerContact.ts | 8 + code/app/src/models/base/CustomerEvent.ts | 6 + code/app/src/models/base/SessionData.ts | 5 + code/app/src/models/base/Tenant.ts | 8 + code/app/src/models/base/User.ts | 13 + code/app/src/models/base/UserRole.ts | 5 + code/app/src/models/internal/FormError.ts | 24 + code/app/src/models/internal/IForm.ts | 15 + code/app/src/models/internal/KnownProblem.ts | 10 + code/app/src/models/projects/Project.ts | 13 + code/app/src/models/projects/ProjectLabel.ts | 5 + code/app/src/models/projects/ProjectMember.ts | 10 + code/app/src/models/projects/ProjectMeta.ts | 7 + code/app/src/models/projects/ProjectRole.ts | 7 + code/app/src/models/projects/ProjectStatus.ts | 5 + code/app/src/models/work/WorkCategory.ts | 5 + code/app/src/models/work/WorkEntry.ts | 13 + code/app/src/models/work/WorkEntryQueryResponse.ts | 27 + code/app/src/models/work/WorkLabel.ts | 5 + code/app/src/models/work/WorkQuery.ts | 17 + code/app/src/routes/(api)/delete-cookie/+server.ts | 8 + code/app/src/routes/(main)/(app)/+layout.svelte | 379 ++ code/app/src/routes/(main)/(app)/home/+page.svelte | 1 + code/app/src/routes/(main)/(app)/org/+page.svelte | 4 + .../src/routes/(main)/(app)/profile/+page.svelte | 4 + .../src/routes/(main)/(app)/projects/+page.svelte | 118 + .../routes/(main)/(app)/projects/[id]/+page.svelte | 5 + .../(main)/(app)/projects/create/+page.svelte | 59 + .../src/routes/(main)/(app)/settings/+page.svelte | 205 + .../src/routes/(main)/(app)/tickets/+page.svelte | 4 + code/app/src/routes/(main)/(app)/todo/+page.svelte | 4 + code/app/src/routes/(main)/(app)/wiki/+page.svelte | 4 + code/app/src/routes/(main)/(public)/+layout.svelte | 18 + .../src/routes/(main)/(public)/portal/+page.svelte | 26 + .../app/src/routes/(main)/(public)/portal/+page.ts | 9 + .../(main)/(public)/reset-password/+page.svelte | 81 + .../routes/(main)/(public)/reset-password/+page.ts | 11 + .../(public)/reset-password/[id]/+page.server.ts | 11 + .../(public)/reset-password/[id]/+page.svelte | 82 + .../(main)/(public)/reset-password/[id]/+page.ts | 11 + .../routes/(main)/(public)/sign-in/+page.svelte | 155 + .../src/routes/(main)/(public)/sign-in/+page.ts | 11 + .../routes/(main)/(public)/sign-in/index.spec.js | 12 + .../src/routes/(main)/(public)/sign-in/index.ts | 20 + .../routes/(main)/(public)/sign-up/+page.svelte | 106 + .../src/routes/(main)/(public)/sign-up/+page.ts | 11 + code/app/src/routes/(main)/+layout.server.ts | 45 + code/app/src/routes/(main)/+layout.svelte | 31 + code/app/src/routes/(main)/+layout.ts | 10 + code/app/src/routes/(main)/+page.svelte | 1 + code/app/src/routes/book/+layout.svelte | 46 + code/app/src/routes/book/+layout.ts | 3 + code/app/src/routes/book/+page.svelte | 1 + code/app/src/routes/book/alerts/+page.svelte | 70 + code/app/src/routes/book/badges/+page.svelte | 19 + code/app/src/routes/book/buttons/+page.svelte | 23 + code/app/src/routes/book/inputs/+page.svelte | 75 + .../app/src/routes/book/notifications/+page.svelte | 50 + code/app/src/routes/book/toggles/+page.svelte | 27 + .../src/services/abstractions/IAccountService.ts | 54 + .../src/services/abstractions/IApiTokenService.ts | 34 + .../services/abstractions/IPasswordResetService.ts | 21 + .../src/services/abstractions/ISettingsService.ts | 3 + code/app/src/services/account-service.ts | 124 + code/app/src/services/api-tokens-service.ts | 22 + code/app/src/services/password-reset-service.ts | 48 + code/app/src/services/settings-service.ts | 10 + code/app/src/utilities/_fetch.ts | 94 + code/app/src/utilities/cache.ts | 38 + code/app/src/utilities/colors.ts | 47 + code/app/src/utilities/crypto-helpers.ts | 48 + code/app/src/utilities/dom-helpers.ts | 105 + code/app/src/utilities/global-state.ts | 22 + code/app/src/utilities/logger.ts | 118 + code/app/src/utilities/misc-helpers.ts | 77 + code/app/src/utilities/persistent-store.ts | 111 + code/app/src/utilities/storage-helpers.ts | 26 + code/app/src/utilities/testing-helpers.ts | 7 + code/app/src/utilities/validators.ts | 34 + code/app/static/favicon.ico | Bin 0 -> 1406 bytes code/app/static/version.txt | 1 + code/app/svelte.config.js | 27 + code/app/tailwind.config.cjs | 141 + code/app/tsconfig.json | 10 + code/app/vite.config.js | 16 + code/frontpage/.gitignore | 5 + code/frontpage/LICENSE | 22 + code/frontpage/archetypes/blog.md | 15 + code/frontpage/archetypes/default.md | 8 + code/frontpage/archetypes/docs.md | 15 + code/frontpage/archetypes/docs/_index.md | 10 + code/frontpage/archetypes/docs/lorem/_index.md | 10 + .../frontpage/archetypes/docs/lorem/ipsum/index.md | 16 + code/frontpage/assets/fonts/.gitkeep | 0 code/frontpage/assets/images/default-image.png | Bin 0 -> 4694 bytes code/frontpage/assets/js/alert-init.js | 5 + code/frontpage/assets/js/alert.js | 20 + code/frontpage/assets/js/app.js | 0 code/frontpage/assets/js/bootstrap.js | 2 + code/frontpage/assets/js/clipboard.js | 37 + code/frontpage/assets/js/darkmode-init.js | 21 + code/frontpage/assets/js/darkmode.js | 38 + code/frontpage/assets/js/highlight.js | 26 + code/frontpage/assets/js/index.js | 179 + code/frontpage/assets/js/instant.page.js | 1 + code/frontpage/assets/js/katex.js | 10 + code/frontpage/assets/js/lazysizes.js | 1 + code/frontpage/assets/js/mermaid.js | 11 + code/frontpage/assets/js/scroll-lock.js | 14 + code/frontpage/assets/js/to-top.js | 20 + code/frontpage/assets/js/vendor/.gitkeep | 0 code/frontpage/assets/scss/app.scss | 31 + code/frontpage/assets/scss/common/_dark.scss | 582 +++ code/frontpage/assets/scss/common/_global.scss | 288 ++ code/frontpage/assets/scss/common/_variables.scss | 190 + code/frontpage/assets/scss/components/_alerts.scss | 164 + .../frontpage/assets/scss/components/_buttons.scss | 255 ++ code/frontpage/assets/scss/components/_code.scss | 66 + .../assets/scss/components/_comments.scss | 30 + .../frontpage/assets/scss/components/_details.scss | 77 + code/frontpage/assets/scss/components/_forms.scss | 19 + code/frontpage/assets/scss/components/_images.scss | 62 + .../frontpage/assets/scss/components/_mermaid.scss | 8 + code/frontpage/assets/scss/components/_search.scss | 91 + code/frontpage/assets/scss/components/_syntax.scss | 62 + code/frontpage/assets/scss/components/_tables.scss | 5 + code/frontpage/assets/scss/layouts/_footer.scss | 20 + code/frontpage/assets/scss/layouts/_header.scss | 493 +++ code/frontpage/assets/scss/layouts/_pages.scss | 64 + code/frontpage/assets/scss/layouts/_posts.scss | 57 + code/frontpage/assets/scss/layouts/_sidebar.scss | 116 + code/frontpage/assets/scss/vendor/.gitkeep | 0 code/frontpage/babel.config.js | 17 + code/frontpage/config/_default/config.toml | 91 + code/frontpage/config/_default/languages.toml | 7 + code/frontpage/config/_default/markup.toml | 29 + code/frontpage/config/_default/menus/menus.en.toml | 24 + code/frontpage/config/_default/params.toml | 98 + code/frontpage/config/next/config.toml | 1 + code/frontpage/config/postcss.config.js | 44 + code/frontpage/config/production/config.toml | 2 + code/frontpage/content/en/_index.md | 9 + code/frontpage/content/en/blog/_index.md | 8 + .../content/en/blog/hi-greatoffice/index.md | 17 + code/frontpage/content/en/contact/index.md | 11 + code/frontpage/content/en/docs/index.md | 11 + code/frontpage/content/en/privacy/index.md | 33 + code/frontpage/content/en/terms/index.md | 18 + code/frontpage/data/docs-versions.yml | 5 + code/frontpage/i18n/de.yaml | 5 + code/frontpage/i18n/en.yaml | 17 + code/frontpage/i18n/nl.yaml | 17 + code/frontpage/layouts/404.html | 10 + .../layouts/_default/_markup/render-heading.html | 1 + code/frontpage/layouts/_default/baseof.html | 32 + code/frontpage/layouts/_default/index.js | 10 + code/frontpage/layouts/_default/index.json | 5 + code/frontpage/layouts/_default/list.html | 31 + .../frontpage/layouts/_default/section.sitemap.xml | 46 + code/frontpage/layouts/_default/single.html | 10 + code/frontpage/layouts/_default/terms.html | 20 + code/frontpage/layouts/_default/versions.html | 27 + code/frontpage/layouts/blog/single.html | 53 + code/frontpage/layouts/docs/list.html | 22 + code/frontpage/layouts/docs/single.html | 53 + code/frontpage/layouts/index.headers | 10 + code/frontpage/layouts/index.html | 68 + code/frontpage/layouts/index.redirects | 13 + .../layouts/partials/content/card-image.html | 17 + .../frontpage/layouts/partials/content/figure.html | 37 + code/frontpage/layouts/partials/content/image.html | 32 + code/frontpage/layouts/partials/footer/footer.html | 18 + .../layouts/partials/footer/script-footer.html | 119 + .../layouts/partials/head/custom-head.html | 1 + code/frontpage/layouts/partials/head/favicons.html | 9 + code/frontpage/layouts/partials/head/head.html | 11 + .../frontpage/layouts/partials/head/opengraph.html | 69 + .../layouts/partials/head/resource-hints.html | 4 + .../layouts/partials/head/script-header.html | 8 + code/frontpage/layouts/partials/head/seo.html | 48 + .../layouts/partials/head/structured-data.html | 210 + .../layouts/partials/head/stylesheet.html | 11 + .../layouts/partials/head/twitter_cards.html | 24 + code/frontpage/layouts/partials/header/alert.html | 10 + code/frontpage/layouts/partials/header/header.html | 254 ++ .../frontpage/layouts/partials/main/blog-meta.html | 2 + .../layouts/partials/main/breadcrumb.html | 4 + code/frontpage/layouts/partials/main/date.html | 6 + .../layouts/partials/main/docs-navigation.html | 24 + .../frontpage/layouts/partials/main/edit-page.html | 34 + .../layouts/partials/main/last-modified.html | 10 + .../partials/sidebar/auto-collapsible-menu.html | 57 + .../partials/sidebar/auto-default-menu.html | 37 + .../layouts/partials/sidebar/docs-menu.html | 9 + .../layouts/partials/sidebar/docs-toc.html | 26 + .../partials/sidebar/manual-collapsible-menu.html | 63 + .../partials/sidebar/manual-default-menu.html | 41 + code/frontpage/layouts/robots.txt | 7 + code/frontpage/layouts/rss.xml | 27 + code/frontpage/layouts/shortcodes/alert.html | 12 + code/frontpage/layouts/shortcodes/details.html | 4 + code/frontpage/layouts/shortcodes/email.html | 1 + code/frontpage/layouts/shortcodes/mermaid.html | 8 + code/frontpage/layouts/shortcodes/video.html | 11 + code/frontpage/layouts/sitemap.xml | 22 + code/frontpage/package.json | 74 + code/frontpage/pnpm-lock.yaml | 4292 +++++++++++++++++++ code/frontpage/static/android-chrome-192x192.png | Bin 0 -> 5765 bytes code/frontpage/static/android-chrome-512x512.png | Bin 0 -> 20786 bytes code/frontpage/static/apple-touch-icon.png | Bin 0 -> 5105 bytes code/frontpage/static/css/vendor/.gitkeep | 0 code/frontpage/static/doks.png | Bin 0 -> 8211 bytes code/frontpage/static/doks.svg | 1 + code/frontpage/static/favicon-16x16.png | Bin 0 -> 416 bytes code/frontpage/static/favicon-32x32.png | Bin 0 -> 773 bytes code/frontpage/static/favicon.ico | Bin 0 -> 15406 bytes code/frontpage/static/favicon.svg | 1 + code/frontpage/static/images/vendor/.gitkeep | 0 code/frontpage/static/js/vendor/.gitkeep | 0 code/frontpage/static/logo-doks.png | Bin 0 -> 20786 bytes code/frontpage/static/logo-doks.svg | 1 + code/frontpage/static/site.webmanifest | 1 + code/frontpage/theme.toml | 14 + 456 files changed, 52045 insertions(+) create mode 100644 code/api/.build.yaml create mode 100644 code/api/.dockerignore create mode 100644 code/api/.version create mode 100644 code/api/.version-dev create mode 100644 code/api/CHANGELOG.md create mode 100644 code/api/Dockerfile create mode 100755 code/api/build_and_push.sh create mode 100644 code/api/cliff.toml create mode 100644 code/api/sql/quartz-create.sql create mode 100644 code/api/sql/quartz-drop.sql create mode 100644 code/api/src/Endpoints/EndpointBase.cs create mode 100644 code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs create mode 100644 code/api/src/Endpoints/Internal/Account/CreateInitialAccountRoute.cs create mode 100644 code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs create mode 100644 code/api/src/Endpoints/Internal/Account/GetAccountRoute.cs create mode 100644 code/api/src/Endpoints/Internal/Account/LoginRoute.cs create mode 100644 code/api/src/Endpoints/Internal/Account/LogoutRoute.cs create mode 100644 code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs create mode 100644 code/api/src/Endpoints/Internal/Account/_calls.http create mode 100644 code/api/src/Endpoints/Internal/INT_EndpointBase.cs create mode 100644 code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs create mode 100644 code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs create mode 100644 code/api/src/Endpoints/Internal/PasswordResetRequests/IsResetRequestValidRoute.cs create mode 100644 code/api/src/Endpoints/Internal/PasswordResetRequests/_calls.http create mode 100644 code/api/src/Endpoints/Internal/Root/GetSessionRoute.cs create mode 100644 code/api/src/Endpoints/Internal/Root/IsAuthenticatedRoute.cs create mode 100644 code/api/src/Endpoints/Internal/Root/ReadConfigurationRoute.cs create mode 100644 code/api/src/Endpoints/Internal/Root/RefreshConfigurationRoute.cs create mode 100644 code/api/src/Endpoints/Internal/Root/ValidateRoute.cs create mode 100644 code/api/src/Endpoints/Internal/RouteBaseAsync.cs create mode 100644 code/api/src/Endpoints/Internal/RouteBaseSync.cs create mode 100644 code/api/src/Endpoints/V1/ApiSpecV1.cs create mode 100644 code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs create mode 100644 code/api/src/Endpoints/V1/ApiTokens/DeleteTokenRoute.cs create mode 100644 code/api/src/Endpoints/V1/ApiTokens/GetTokensRoute.cs create mode 100644 code/api/src/Endpoints/V1/Customers/CreateCustomerRoute.cs create mode 100644 code/api/src/Endpoints/V1/Projects/CreateProjectRoute.cs create mode 100644 code/api/src/Endpoints/V1/Projects/GetProjectsRoute.cs create mode 100644 code/api/src/Endpoints/V1/Projects/_calls.http create mode 100644 code/api/src/Endpoints/V1/RouteBaseAsync.cs create mode 100644 code/api/src/Endpoints/V1/RouteBaseSync.cs create mode 100644 code/api/src/Endpoints/V1/V1_EndpointBase.cs create mode 100644 code/api/src/IOL.GreatOffice.Api.csproj create mode 100644 code/api/src/Jobs/AccessTokenCleanupJob.cs create mode 100644 code/api/src/Jobs/JobRegister.cs create mode 100644 code/api/src/Jobs/VaultTokenRenewalJob.cs create mode 100644 code/api/src/Migrations/20210517202115_InitialMigration.Designer.cs create mode 100644 code/api/src/Migrations/20210517202115_InitialMigration.cs create mode 100644 code/api/src/Migrations/20210522165932_RenameNoteToDescription.Designer.cs create mode 100644 code/api/src/Migrations/20210522165932_RenameNoteToDescription.cs create mode 100644 code/api/src/Migrations/20211002113037_V6Migration.Designer.cs create mode 100644 code/api/src/Migrations/20211002113037_V6Migration.cs create mode 100644 code/api/src/Migrations/20220225143559_GithubUserMappings.Designer.cs create mode 100644 code/api/src/Migrations/20220225143559_GithubUserMappings.cs create mode 100644 code/api/src/Migrations/20220319135910_RenameCreated.Designer.cs create mode 100644 code/api/src/Migrations/20220319135910_RenameCreated.cs create mode 100644 code/api/src/Migrations/20220319144958_ModifiedAt.Designer.cs create mode 100644 code/api/src/Migrations/20220319144958_ModifiedAt.cs create mode 100644 code/api/src/Migrations/20220319203018_UserBase.Designer.cs create mode 100644 code/api/src/Migrations/20220319203018_UserBase.cs create mode 100644 code/api/src/Migrations/20220320115601_Update1.Designer.cs create mode 100644 code/api/src/Migrations/20220320115601_Update1.cs create mode 100644 code/api/src/Migrations/20220320132220_UpdatedForgotPasswordRequests.Designer.cs create mode 100644 code/api/src/Migrations/20220320132220_UpdatedForgotPasswordRequests.cs create mode 100644 code/api/src/Migrations/20220529190359_ApiAccessTokens.Designer.cs create mode 100644 code/api/src/Migrations/20220529190359_ApiAccessTokens.cs create mode 100644 code/api/src/Migrations/20220530174741_Tenants.Designer.cs create mode 100644 code/api/src/Migrations/20220530174741_Tenants.cs create mode 100644 code/api/src/Migrations/20220530175322_RemoveUnusedNavs.Designer.cs create mode 100644 code/api/src/Migrations/20220530175322_RemoveUnusedNavs.cs create mode 100644 code/api/src/Migrations/20220602214238_NullableOptionalBaseFields.Designer.cs create mode 100644 code/api/src/Migrations/20220602214238_NullableOptionalBaseFields.cs create mode 100644 code/api/src/Migrations/20220606232346_FleshOutNewModules.Designer.cs create mode 100644 code/api/src/Migrations/20220606232346_FleshOutNewModules.cs create mode 100644 code/api/src/Migrations/20220616170311_DataProtectionKeys.Designer.cs create mode 100644 code/api/src/Migrations/20220616170311_DataProtectionKeys.cs create mode 100644 code/api/src/Migrations/20220819203816_RemoveGithubUsers.Designer.cs create mode 100644 code/api/src/Migrations/20220819203816_RemoveGithubUsers.cs create mode 100644 code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.Designer.cs create mode 100644 code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.cs create mode 100644 code/api/src/Migrations/20221030081459_DeletedAt.Designer.cs create mode 100644 code/api/src/Migrations/20221030081459_DeletedAt.cs create mode 100644 code/api/src/Migrations/20221030084716_MinorChanges.Designer.cs create mode 100644 code/api/src/Migrations/20221030084716_MinorChanges.cs create mode 100644 code/api/src/Migrations/20221030090557_MoreMinorChanges.Designer.cs create mode 100644 code/api/src/Migrations/20221030090557_MoreMinorChanges.cs create mode 100644 code/api/src/Migrations/20221031165813_TodoAndOwnerNavigations.Designer.cs create mode 100644 code/api/src/Migrations/20221031165813_TodoAndOwnerNavigations.cs create mode 100644 code/api/src/Migrations/20221114034213_RemoveTimeTracker.Designer.cs create mode 100644 code/api/src/Migrations/20221114034213_RemoveTimeTracker.cs create mode 100644 code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.Designer.cs create mode 100644 code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.cs create mode 100644 code/api/src/Migrations/20221209041908_TenantSlug.Designer.cs create mode 100644 code/api/src/Migrations/20221209041908_TenantSlug.cs create mode 100644 code/api/src/Migrations/20221209043806_ValidationEmailQueue.Designer.cs create mode 100644 code/api/src/Migrations/20221209043806_ValidationEmailQueue.cs create mode 100644 code/api/src/Migrations/20221214143556_AddUserDeletedBy.Designer.cs create mode 100644 code/api/src/Migrations/20221214143556_AddUserDeletedBy.cs create mode 100644 code/api/src/Migrations/AppDbContextModelSnapshot.cs create mode 100644 code/api/src/Models/Database/Api/ApiAccessToken.cs create mode 100644 code/api/src/Models/Database/Base.cs create mode 100644 code/api/src/Models/Database/BaseWithOwner.cs create mode 100644 code/api/src/Models/Database/Customer/Customer.cs create mode 100644 code/api/src/Models/Database/Customer/CustomerContact.cs create mode 100644 code/api/src/Models/Database/Customer/CustomerEvent.cs create mode 100644 code/api/src/Models/Database/Customer/CustomerGroup.cs create mode 100644 code/api/src/Models/Database/Customer/CustomerGroupMembership.cs create mode 100644 code/api/src/Models/Database/Internal/PasswordResetRequest.cs create mode 100644 code/api/src/Models/Database/Internal/Tenant.cs create mode 100644 code/api/src/Models/Database/Internal/User.cs create mode 100644 code/api/src/Models/Database/MainAppDatabase.cs create mode 100644 code/api/src/Models/Database/Project/Project.cs create mode 100644 code/api/src/Models/Database/Project/ProjectLabel.cs create mode 100644 code/api/src/Models/Database/Project/ProjectMember.cs create mode 100644 code/api/src/Models/Database/Queues/ValidationEmail.cs create mode 100644 code/api/src/Models/Database/Todo/Todo.cs create mode 100644 code/api/src/Models/Database/Todo/TodoCollection.cs create mode 100644 code/api/src/Models/Database/Todo/TodoCollectionAccessControl.cs create mode 100644 code/api/src/Models/Database/Todo/TodoComment.cs create mode 100644 code/api/src/Models/Database/Todo/TodoLabel.cs create mode 100644 code/api/src/Models/Enums/FulfillPasswordResetRequestResult.cs create mode 100644 code/api/src/Models/Enums/PasswordResetRequestStatus.cs create mode 100644 code/api/src/Models/Enums/ProjectRole.cs create mode 100644 code/api/src/Models/Enums/TodoClosingStatement.cs create mode 100644 code/api/src/Models/Enums/TodoVisibility.cs create mode 100644 code/api/src/Models/Misc/ApiSpecDocument.cs create mode 100644 code/api/src/Models/Misc/AppConfiguration.cs create mode 100644 code/api/src/Models/Misc/AppPath.cs create mode 100644 code/api/src/Models/Misc/KnownProblemModel.cs create mode 100644 code/api/src/Models/Misc/LoggedInUserModel.cs create mode 100644 code/api/src/Models/Misc/RequestTimeZoneInfo.cs create mode 100644 code/api/src/Models/Static/AppClaims.cs create mode 100644 code/api/src/Models/Static/AppConstants.cs create mode 100644 code/api/src/Models/Static/AppCookies.cs create mode 100644 code/api/src/Models/Static/AppDateTime.cs create mode 100644 code/api/src/Models/Static/AppEnvironmentVariables.cs create mode 100644 code/api/src/Models/Static/AppHeaders.cs create mode 100644 code/api/src/Models/Static/AppPaths.cs create mode 100644 code/api/src/Models/Static/JsonSettings.cs create mode 100644 code/api/src/Program.cs create mode 100644 code/api/src/Properties/launchSettings.json create mode 100644 code/api/src/Resources/SharedResources.cs create mode 100644 code/api/src/Resources/SharedResources.en.Designer.cs create mode 100644 code/api/src/Resources/SharedResources.en.resx create mode 100644 code/api/src/Resources/SharedResources.nb.Designer.cs create mode 100644 code/api/src/Resources/SharedResources.nb.resx create mode 100644 code/api/src/Resources/SharedResources.resx create mode 100644 code/api/src/Services/EmailValidationService.cs create mode 100644 code/api/src/Services/MailService.cs create mode 100644 code/api/src/Services/PasswordResetService.cs create mode 100644 code/api/src/Services/TenantService.cs create mode 100644 code/api/src/Services/UserService.cs create mode 100644 code/api/src/Services/VaultService.cs create mode 100644 code/api/src/Utilities/BasicAuthenticationAttribute.cs create mode 100644 code/api/src/Utilities/BasicAuthenticationHandler.cs create mode 100644 code/api/src/Utilities/ConfigurationExtensions.cs create mode 100644 code/api/src/Utilities/DateTimeExtensions.cs create mode 100644 code/api/src/Utilities/PaginationOperationFilter.cs create mode 100644 code/api/src/Utilities/QuartzJsonSerializer.cs create mode 100644 code/api/src/Utilities/QueryableExtensions.cs create mode 100644 code/api/src/Utilities/SwaggerDefaultValues.cs create mode 100644 code/api/src/Utilities/SwaggerGenOptionsExtensions.cs create mode 100644 code/api/src/wwwroot/version.txt create mode 100644 code/api/tests/IOL.GreatOffice.IntegrationTests/ApplicationTests/LoginPageTests.cs create mode 100644 code/api/tests/IOL.GreatOffice.IntegrationTests/Helpers/Element.cs create mode 100644 code/api/tests/IOL.GreatOffice.IntegrationTests/Helpers/WebServerFixture.cs create mode 100644 code/api/tests/IOL.GreatOffice.IntegrationTests/IOL.GreatOffice.IntegrationTests.csproj create mode 100644 code/app/.dockerignore create mode 100644 code/app/.env-example create mode 100644 code/app/.gitignore create mode 100644 code/app/.npmrc create mode 100644 code/app/.typesafe-i18n.json create mode 100644 code/app/.version create mode 100644 code/app/.version-dev create mode 100644 code/app/Dockerfile create mode 100755 code/app/build_and_push.sh create mode 100644 code/app/package.json create mode 100644 code/app/playwright.config.ts create mode 100644 code/app/pnpm-lock.yaml create mode 100644 code/app/postcss.config.cjs create mode 100644 code/app/src/actions/pwKey.ts create mode 100644 code/app/src/app.d.ts create mode 100644 code/app/src/app.html create mode 100644 code/app/src/app.pcss create mode 100644 code/app/src/components/alert.svelte create mode 100644 code/app/src/components/badge.svelte create mode 100644 code/app/src/components/button.svelte create mode 100644 code/app/src/components/checkbox.svelte create mode 100644 code/app/src/components/combobox.svelte create mode 100644 code/app/src/components/icons/adjustments.svelte create mode 100644 code/app/src/components/icons/bars-3-center-left.svelte create mode 100644 code/app/src/components/icons/calendar.svelte create mode 100644 code/app/src/components/icons/check-circle.svelte create mode 100644 code/app/src/components/icons/chevron-down.svelte create mode 100644 code/app/src/components/icons/chevron-up-down.svelte create mode 100644 code/app/src/components/icons/chevron-up.svelte create mode 100644 code/app/src/components/icons/database.svelte create mode 100644 code/app/src/components/icons/exclamation-circle.svelte create mode 100644 code/app/src/components/icons/exclamation-triangle.svelte create mode 100644 code/app/src/components/icons/folder-open.svelte create mode 100644 code/app/src/components/icons/funnel.svelte create mode 100644 code/app/src/components/icons/home.svelte create mode 100644 code/app/src/components/icons/index.ts create mode 100644 code/app/src/components/icons/information-circle.svelte create mode 100644 code/app/src/components/icons/magnifying-glass.svelte create mode 100644 code/app/src/components/icons/megaphone.svelte create mode 100644 code/app/src/components/icons/menu.svelte create mode 100644 code/app/src/components/icons/queue-list.svelte create mode 100644 code/app/src/components/icons/spinner.svelte create mode 100644 code/app/src/components/icons/x-circle.svelte create mode 100644 code/app/src/components/icons/x-mark.svelte create mode 100644 code/app/src/components/icons/x.svelte create mode 100644 code/app/src/components/index.ts create mode 100644 code/app/src/components/input.svelte create mode 100644 code/app/src/components/locale-switcher.svelte create mode 100644 code/app/src/components/notification.svelte create mode 100644 code/app/src/components/project-status-badge.svelte create mode 100644 code/app/src/components/switch.svelte create mode 100644 code/app/src/components/textarea.svelte create mode 100644 code/app/src/configuration/index.ts create mode 100644 code/app/src/global.d.ts create mode 100644 code/app/src/hooks.server.ts create mode 100644 code/app/src/i18n/en/app/index.ts create mode 100644 code/app/src/i18n/en/index.ts create mode 100644 code/app/src/i18n/formatters.ts create mode 100644 code/app/src/i18n/i18n-svelte.ts create mode 100644 code/app/src/i18n/i18n-types.ts create mode 100644 code/app/src/i18n/i18n-util.async.ts create mode 100644 code/app/src/i18n/i18n-util.sync.ts create mode 100644 code/app/src/i18n/i18n-util.ts create mode 100644 code/app/src/i18n/nb/app/index.ts create mode 100644 code/app/src/i18n/nb/index.ts create mode 100644 code/app/src/models/base/Customer.ts create mode 100644 code/app/src/models/base/CustomerContact.ts create mode 100644 code/app/src/models/base/CustomerEvent.ts create mode 100644 code/app/src/models/base/SessionData.ts create mode 100644 code/app/src/models/base/Tenant.ts create mode 100644 code/app/src/models/base/User.ts create mode 100644 code/app/src/models/base/UserRole.ts create mode 100644 code/app/src/models/internal/FormError.ts create mode 100644 code/app/src/models/internal/IForm.ts create mode 100644 code/app/src/models/internal/KnownProblem.ts create mode 100644 code/app/src/models/projects/Project.ts create mode 100644 code/app/src/models/projects/ProjectLabel.ts create mode 100644 code/app/src/models/projects/ProjectMember.ts create mode 100644 code/app/src/models/projects/ProjectMeta.ts create mode 100644 code/app/src/models/projects/ProjectRole.ts create mode 100644 code/app/src/models/projects/ProjectStatus.ts create mode 100644 code/app/src/models/work/WorkCategory.ts create mode 100644 code/app/src/models/work/WorkEntry.ts create mode 100644 code/app/src/models/work/WorkEntryQueryResponse.ts create mode 100644 code/app/src/models/work/WorkLabel.ts create mode 100644 code/app/src/models/work/WorkQuery.ts create mode 100644 code/app/src/routes/(api)/delete-cookie/+server.ts create mode 100644 code/app/src/routes/(main)/(app)/+layout.svelte create mode 100644 code/app/src/routes/(main)/(app)/home/+page.svelte create mode 100644 code/app/src/routes/(main)/(app)/org/+page.svelte create mode 100644 code/app/src/routes/(main)/(app)/profile/+page.svelte create mode 100644 code/app/src/routes/(main)/(app)/projects/+page.svelte create mode 100644 code/app/src/routes/(main)/(app)/projects/[id]/+page.svelte create mode 100644 code/app/src/routes/(main)/(app)/projects/create/+page.svelte create mode 100644 code/app/src/routes/(main)/(app)/settings/+page.svelte create mode 100644 code/app/src/routes/(main)/(app)/tickets/+page.svelte create mode 100644 code/app/src/routes/(main)/(app)/todo/+page.svelte create mode 100644 code/app/src/routes/(main)/(app)/wiki/+page.svelte create mode 100644 code/app/src/routes/(main)/(public)/+layout.svelte create mode 100644 code/app/src/routes/(main)/(public)/portal/+page.svelte create mode 100644 code/app/src/routes/(main)/(public)/portal/+page.ts create mode 100644 code/app/src/routes/(main)/(public)/reset-password/+page.svelte create mode 100644 code/app/src/routes/(main)/(public)/reset-password/+page.ts create mode 100644 code/app/src/routes/(main)/(public)/reset-password/[id]/+page.server.ts create mode 100644 code/app/src/routes/(main)/(public)/reset-password/[id]/+page.svelte create mode 100644 code/app/src/routes/(main)/(public)/reset-password/[id]/+page.ts create mode 100644 code/app/src/routes/(main)/(public)/sign-in/+page.svelte create mode 100644 code/app/src/routes/(main)/(public)/sign-in/+page.ts create mode 100644 code/app/src/routes/(main)/(public)/sign-in/index.spec.js create mode 100644 code/app/src/routes/(main)/(public)/sign-in/index.ts create mode 100644 code/app/src/routes/(main)/(public)/sign-up/+page.svelte create mode 100644 code/app/src/routes/(main)/(public)/sign-up/+page.ts create mode 100644 code/app/src/routes/(main)/+layout.server.ts create mode 100644 code/app/src/routes/(main)/+layout.svelte create mode 100644 code/app/src/routes/(main)/+layout.ts create mode 100644 code/app/src/routes/(main)/+page.svelte create mode 100644 code/app/src/routes/book/+layout.svelte create mode 100644 code/app/src/routes/book/+layout.ts create mode 100644 code/app/src/routes/book/+page.svelte create mode 100644 code/app/src/routes/book/alerts/+page.svelte create mode 100644 code/app/src/routes/book/badges/+page.svelte create mode 100644 code/app/src/routes/book/buttons/+page.svelte create mode 100644 code/app/src/routes/book/inputs/+page.svelte create mode 100644 code/app/src/routes/book/notifications/+page.svelte create mode 100644 code/app/src/routes/book/toggles/+page.svelte create mode 100644 code/app/src/services/abstractions/IAccountService.ts create mode 100644 code/app/src/services/abstractions/IApiTokenService.ts create mode 100644 code/app/src/services/abstractions/IPasswordResetService.ts create mode 100644 code/app/src/services/abstractions/ISettingsService.ts create mode 100644 code/app/src/services/account-service.ts create mode 100644 code/app/src/services/api-tokens-service.ts create mode 100644 code/app/src/services/password-reset-service.ts create mode 100644 code/app/src/services/settings-service.ts create mode 100644 code/app/src/utilities/_fetch.ts create mode 100644 code/app/src/utilities/cache.ts create mode 100644 code/app/src/utilities/colors.ts create mode 100644 code/app/src/utilities/crypto-helpers.ts create mode 100644 code/app/src/utilities/dom-helpers.ts create mode 100644 code/app/src/utilities/global-state.ts create mode 100644 code/app/src/utilities/logger.ts create mode 100644 code/app/src/utilities/misc-helpers.ts create mode 100644 code/app/src/utilities/persistent-store.ts create mode 100644 code/app/src/utilities/storage-helpers.ts create mode 100644 code/app/src/utilities/testing-helpers.ts create mode 100644 code/app/src/utilities/validators.ts create mode 100644 code/app/static/favicon.ico create mode 100644 code/app/static/version.txt create mode 100644 code/app/svelte.config.js create mode 100644 code/app/tailwind.config.cjs create mode 100644 code/app/tsconfig.json create mode 100644 code/app/vite.config.js create mode 100644 code/frontpage/.gitignore create mode 100644 code/frontpage/LICENSE create mode 100644 code/frontpage/archetypes/blog.md create mode 100644 code/frontpage/archetypes/default.md create mode 100644 code/frontpage/archetypes/docs.md create mode 100644 code/frontpage/archetypes/docs/_index.md create mode 100644 code/frontpage/archetypes/docs/lorem/_index.md create mode 100644 code/frontpage/archetypes/docs/lorem/ipsum/index.md create mode 100644 code/frontpage/assets/fonts/.gitkeep create mode 100644 code/frontpage/assets/images/default-image.png create mode 100644 code/frontpage/assets/js/alert-init.js create mode 100644 code/frontpage/assets/js/alert.js create mode 100644 code/frontpage/assets/js/app.js create mode 100644 code/frontpage/assets/js/bootstrap.js create mode 100644 code/frontpage/assets/js/clipboard.js create mode 100644 code/frontpage/assets/js/darkmode-init.js create mode 100644 code/frontpage/assets/js/darkmode.js create mode 100644 code/frontpage/assets/js/highlight.js create mode 100644 code/frontpage/assets/js/index.js create mode 100644 code/frontpage/assets/js/instant.page.js create mode 100644 code/frontpage/assets/js/katex.js create mode 100644 code/frontpage/assets/js/lazysizes.js create mode 100644 code/frontpage/assets/js/mermaid.js create mode 100644 code/frontpage/assets/js/scroll-lock.js create mode 100644 code/frontpage/assets/js/to-top.js create mode 100644 code/frontpage/assets/js/vendor/.gitkeep create mode 100644 code/frontpage/assets/scss/app.scss create mode 100644 code/frontpage/assets/scss/common/_dark.scss create mode 100644 code/frontpage/assets/scss/common/_global.scss create mode 100644 code/frontpage/assets/scss/common/_variables.scss create mode 100644 code/frontpage/assets/scss/components/_alerts.scss create mode 100644 code/frontpage/assets/scss/components/_buttons.scss create mode 100644 code/frontpage/assets/scss/components/_code.scss create mode 100644 code/frontpage/assets/scss/components/_comments.scss create mode 100644 code/frontpage/assets/scss/components/_details.scss create mode 100644 code/frontpage/assets/scss/components/_forms.scss create mode 100644 code/frontpage/assets/scss/components/_images.scss create mode 100644 code/frontpage/assets/scss/components/_mermaid.scss create mode 100644 code/frontpage/assets/scss/components/_search.scss create mode 100644 code/frontpage/assets/scss/components/_syntax.scss create mode 100644 code/frontpage/assets/scss/components/_tables.scss create mode 100644 code/frontpage/assets/scss/layouts/_footer.scss create mode 100644 code/frontpage/assets/scss/layouts/_header.scss create mode 100644 code/frontpage/assets/scss/layouts/_pages.scss create mode 100644 code/frontpage/assets/scss/layouts/_posts.scss create mode 100644 code/frontpage/assets/scss/layouts/_sidebar.scss create mode 100644 code/frontpage/assets/scss/vendor/.gitkeep create mode 100644 code/frontpage/babel.config.js create mode 100644 code/frontpage/config/_default/config.toml create mode 100644 code/frontpage/config/_default/languages.toml create mode 100644 code/frontpage/config/_default/markup.toml create mode 100644 code/frontpage/config/_default/menus/menus.en.toml create mode 100644 code/frontpage/config/_default/params.toml create mode 100644 code/frontpage/config/next/config.toml create mode 100644 code/frontpage/config/postcss.config.js create mode 100644 code/frontpage/config/production/config.toml create mode 100644 code/frontpage/content/en/_index.md create mode 100644 code/frontpage/content/en/blog/_index.md create mode 100644 code/frontpage/content/en/blog/hi-greatoffice/index.md create mode 100644 code/frontpage/content/en/contact/index.md create mode 100644 code/frontpage/content/en/docs/index.md create mode 100644 code/frontpage/content/en/privacy/index.md create mode 100644 code/frontpage/content/en/terms/index.md create mode 100644 code/frontpage/data/docs-versions.yml create mode 100644 code/frontpage/i18n/de.yaml create mode 100644 code/frontpage/i18n/en.yaml create mode 100644 code/frontpage/i18n/nl.yaml create mode 100644 code/frontpage/layouts/404.html create mode 100644 code/frontpage/layouts/_default/_markup/render-heading.html create mode 100644 code/frontpage/layouts/_default/baseof.html create mode 100644 code/frontpage/layouts/_default/index.js create mode 100644 code/frontpage/layouts/_default/index.json create mode 100644 code/frontpage/layouts/_default/list.html create mode 100644 code/frontpage/layouts/_default/section.sitemap.xml create mode 100644 code/frontpage/layouts/_default/single.html create mode 100644 code/frontpage/layouts/_default/terms.html create mode 100644 code/frontpage/layouts/_default/versions.html create mode 100644 code/frontpage/layouts/blog/single.html create mode 100644 code/frontpage/layouts/docs/list.html create mode 100644 code/frontpage/layouts/docs/single.html create mode 100644 code/frontpage/layouts/index.headers create mode 100644 code/frontpage/layouts/index.html create mode 100644 code/frontpage/layouts/index.redirects create mode 100644 code/frontpage/layouts/partials/content/card-image.html create mode 100644 code/frontpage/layouts/partials/content/figure.html create mode 100644 code/frontpage/layouts/partials/content/image.html create mode 100644 code/frontpage/layouts/partials/footer/footer.html create mode 100644 code/frontpage/layouts/partials/footer/script-footer.html create mode 100644 code/frontpage/layouts/partials/head/custom-head.html create mode 100644 code/frontpage/layouts/partials/head/favicons.html create mode 100644 code/frontpage/layouts/partials/head/head.html create mode 100644 code/frontpage/layouts/partials/head/opengraph.html create mode 100644 code/frontpage/layouts/partials/head/resource-hints.html create mode 100644 code/frontpage/layouts/partials/head/script-header.html create mode 100644 code/frontpage/layouts/partials/head/seo.html create mode 100644 code/frontpage/layouts/partials/head/structured-data.html create mode 100644 code/frontpage/layouts/partials/head/stylesheet.html create mode 100644 code/frontpage/layouts/partials/head/twitter_cards.html create mode 100644 code/frontpage/layouts/partials/header/alert.html create mode 100644 code/frontpage/layouts/partials/header/header.html create mode 100644 code/frontpage/layouts/partials/main/blog-meta.html create mode 100644 code/frontpage/layouts/partials/main/breadcrumb.html create mode 100644 code/frontpage/layouts/partials/main/date.html create mode 100644 code/frontpage/layouts/partials/main/docs-navigation.html create mode 100644 code/frontpage/layouts/partials/main/edit-page.html create mode 100644 code/frontpage/layouts/partials/main/last-modified.html create mode 100644 code/frontpage/layouts/partials/sidebar/auto-collapsible-menu.html create mode 100644 code/frontpage/layouts/partials/sidebar/auto-default-menu.html create mode 100644 code/frontpage/layouts/partials/sidebar/docs-menu.html create mode 100644 code/frontpage/layouts/partials/sidebar/docs-toc.html create mode 100644 code/frontpage/layouts/partials/sidebar/manual-collapsible-menu.html create mode 100644 code/frontpage/layouts/partials/sidebar/manual-default-menu.html create mode 100644 code/frontpage/layouts/robots.txt create mode 100644 code/frontpage/layouts/rss.xml create mode 100644 code/frontpage/layouts/shortcodes/alert.html create mode 100644 code/frontpage/layouts/shortcodes/details.html create mode 100644 code/frontpage/layouts/shortcodes/email.html create mode 100644 code/frontpage/layouts/shortcodes/mermaid.html create mode 100644 code/frontpage/layouts/shortcodes/video.html create mode 100644 code/frontpage/layouts/sitemap.xml create mode 100644 code/frontpage/package.json create mode 100644 code/frontpage/pnpm-lock.yaml create mode 100644 code/frontpage/static/android-chrome-192x192.png create mode 100644 code/frontpage/static/android-chrome-512x512.png create mode 100644 code/frontpage/static/apple-touch-icon.png create mode 100644 code/frontpage/static/css/vendor/.gitkeep create mode 100644 code/frontpage/static/doks.png create mode 100644 code/frontpage/static/doks.svg create mode 100644 code/frontpage/static/favicon-16x16.png create mode 100644 code/frontpage/static/favicon-32x32.png create mode 100644 code/frontpage/static/favicon.ico create mode 100644 code/frontpage/static/favicon.svg create mode 100644 code/frontpage/static/images/vendor/.gitkeep create mode 100644 code/frontpage/static/js/vendor/.gitkeep create mode 100644 code/frontpage/static/logo-doks.png create mode 100644 code/frontpage/static/logo-doks.svg create mode 100644 code/frontpage/static/site.webmanifest create mode 100644 code/frontpage/theme.toml (limited to 'code') diff --git a/code/api/.build.yaml b/code/api/.build.yaml new file mode 100644 index 0000000..412cd17 --- /dev/null +++ b/code/api/.build.yaml @@ -0,0 +1,23 @@ +image: ubuntu/lts +packages: + - docker.io +secrets: + - ea28f7fe-b300-4b79-addf-d487ed6eb1ef + - b6c0403d-10a9-4238-89cc-5402dc0c9fe5 +sources: + - git@git.ivar.systems:greatoffice +tasks: + - setup: | + echo "export IMAGE_NAME=greatoffice/server + export HUB_NAME=dr.ivar.systems/greatoffice/server + export CURRENT_VERSION=$(cat ~/greatoffice/server/.version) + export CURRENT_VERSION_INT=${CURRENT_VERSION//[!0-9]/} + export NEW_VERSION=v$(CURRENT_VERSION_INT+1)-server" >> .buildenv + - build: | + sudo docker build -t $IMAGE_NAME:$NEW_VERSION ~/greatoffice/server + - publish: | + cat ~/.dockerpassword | sudo docker login dr.ivar.systems -u builder --password-stdin + sudo docker tag $IMAGE_NAME:$NEW_VERSION $HUB_NAME:$NEW_VERSION + sudo docker tag $IMAGE_NAME:$NEW_VERSION $HUB_NAME:latest + sudo docker push -a + complete-build() \ No newline at end of file diff --git a/code/api/.dockerignore b/code/api/.dockerignore new file mode 100644 index 0000000..2b24da3 --- /dev/null +++ b/code/api/.dockerignore @@ -0,0 +1,10 @@ +*/**/bin* +*/**/obj* +server-secrets.* +.git +*/**/node_modules/* +src/web-app +src/web-shared +src/tests +build_and_push.sh +cloc.sh diff --git a/code/api/.version b/code/api/.version new file mode 100644 index 0000000..f20f9eb --- /dev/null +++ b/code/api/.version @@ -0,0 +1 @@ +v22-server diff --git a/code/api/.version-dev b/code/api/.version-dev new file mode 100644 index 0000000..9cf68da --- /dev/null +++ b/code/api/.version-dev @@ -0,0 +1 @@ +v47-server-dev diff --git a/code/api/CHANGELOG.md b/code/api/CHANGELOG.md new file mode 100644 index 0000000..88710c3 --- /dev/null +++ b/code/api/CHANGELOG.md @@ -0,0 +1,123 @@ +# Changelog + +## [unreleased] + +### Miscellaneous Tasks + +- Bump version +- Update CHANGELOG.md for v41-server-dev + +### Refactor + +- Implement caching in VaultService and use VaultService instead of IOptions + +## [unreleased] + +### Miscellaneous Tasks + +- Bump version +- Update CHANGELOG.md for v40-server-dev + +### Refactor + +- Use Vault to get configuration + +## [unreleased] + +### Features + +- !WIP start implementation of svelte-query + +### Miscellaneous Tasks + +- Bump version +- Remove logging of quartz db host +- Update CHANGELOG.md for v6-projects-dev +- Bump version +- Bump version +- Bump version +- Update CHANGELOG.md for v39-server-dev + +### Refactor + +- Use Vault to get configuration +- Small changes on button style +- Add a small box-shadow + +## [unreleased] + +### Bug Fixes + +- !WIP flickering dropdown on multi dropdowns with new focus strategy +- Fix route matching when deciding which tab is open + +### Miscellaneous Tasks + +- Bump version +- Update CHANGELOG.md for v22-server +- Bump version +- Update CHANGELOG.md for v15-portal-dev +- Bump version +- Bump version +- Update CHANGELOG.md for v14-portal-dev +- Bump version +- Bump version +- Bump version +- Update CHANGELOG.md for v38-server-dev + +### Refactor + +- Dont loose focus on search input when navigating results +- Make optional base fields nullable +- Remove text +- Rename accounts to portal +- Rename accounts to portal +- Rename accounts to portal +- Rename noResultsText + +## [unreleased] + +### Bug Fixes + +- !WIP flickering dropdown on multi dropdowns with new focus strategy +- Fix route matching when deciding which tab is open + +### Miscellaneous Tasks + +- Bump version +- Update CHANGELOG.md for v15-portal-dev +- Bump version +- Bump version +- Update CHANGELOG.md for v14-portal-dev +- Bump version +- Bump version +- Bump version +- Update CHANGELOG.md for v37-server-dev +- Bump version +- Bump version +- Update CHANGELOG.md for v22-server + +### Refactor + +- Dont loose focus on search input when navigating results +- Make optional base fields nullable +- Remove text +- Rename accounts to portal +- Rename accounts to portal +- Rename accounts to portal +- Rename noResultsText + +## [unreleased] + +### Miscellaneous Tasks + +- Bump version +- Update CHANGELOG.md for v37-server-dev + +## [unreleased] + +### Miscellaneous Tasks + +- Bump version +- Update CHANGELOG.md for v21-server + diff --git a/code/api/Dockerfile b/code/api/Dockerfile new file mode 100644 index 0000000..5a5545f --- /dev/null +++ b/code/api/Dockerfile @@ -0,0 +1,16 @@ +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env +WORKDIR /source + +# Copy csproj and restore as distinct layers +COPY src/*.csproj ./ +RUN dotnet restore + +# Copy everything else and build +COPY src/ ./ +RUN dotnet publish -c Release -o out + +# Build runtime image +FROM mcr.microsoft.com/dotnet/aspnet:7.0 +WORKDIR /app +COPY --from=build-env /source/out . +ENTRYPOINT ["dotnet", "IOL.GreatOffice.Api.dll"] diff --git a/code/api/build_and_push.sh b/code/api/build_and_push.sh new file mode 100755 index 0000000..163d7f3 --- /dev/null +++ b/code/api/build_and_push.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +set -Eueo pipefail + +CURRENT_DEV_VERSION=$(cat .version-dev) +CURRENT_DEV_VERSION_INT=${CURRENT_DEV_VERSION//[!0-9]/} +CURRENT_VERSION=$(cat .version) +CURRENT_VERSION_INT=${CURRENT_VERSION//[!0-9]/} +if [ ${1-prod} == "dev" ]; then + NEW_VERSION="v$((CURRENT_DEV_VERSION_INT + 1))-dev" + OLD_VERSION=$CURRENT_DEV_VERSION +else + NEW_VERSION="v$((CURRENT_VERSION_INT + 1))" + OLD_VERSION=$CURRENT_VERSION +fi +IMAGE_NAME="greatoffice/server" +HUB_NAME="dr.ivar.systems/greatoffice/server" + +# Check for uncommited changes and optionally commit them +if [ "$(git status --untracked-files=no --porcelain)" ]; then + echo "Unclean git tree! press CTRL+C to exit or press ENTER to commit and push to the default branch" + read -n 1 + + read -p "Enter commit message: " COMMIT_MESSAGE + git add .. + git commit --quiet -m "$COMMIT_MESSAGE" +fi + +if [ ${1-prod} == "dev" ]; then + echo $NEW_VERSION >|.version-dev + git add .version-dev +else + echo $NEW_VERSION >|.version + git add .version +fi + +echo "Starting build of $HUB_NAME:$NEW_VERSION at $(date -u)..." +echo + +# Put version.txt inside of server +pushd src/wwwroot +echo "$NEW_VERSION" >version.txt +git add version.txt +popd + +git commit --quiet -m "chore(release): Bump version" + +read -p "Do you want to tag this build? (y/n) " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + read -p "Enter tag message (can be empty): " TAG_MESSAGE + git tag -am "$TAG_MESSAGE" $NEW_VERSION +fi + +read -p "Do you want to push the latest commits and tags to origin? (y/n) " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Pushing latest changes to remotes..." + echo + git push --quiet --follow-tags +fi + +# Build docker image +echo "Building docker image" +echo + +docker build -t $IMAGE_NAME:$NEW_VERSION . + +docker tag $IMAGE_NAME:$NEW_VERSION $HUB_NAME:$NEW_VERSION + +if [ ${1-prod} == "dev" ]; then + docker tag $IMAGE_NAME:$NEW_VERSION $HUB_NAME:latest-dev +fi +if [ ${1-prod} == "prod" ]; then + docker tag $IMAGE_NAME:$NEW_VERSION $HUB_NAME:latest +fi + +# Optionally push images to docker registry +echo "Press CTRL+C to exit or press ENTER to push docker image to registry" +read -n 1 +docker push $HUB_NAME:$NEW_VERSION + +if [ ${1-prod} == "dev" ]; then + docker push $HUB_NAME:latest-dev +fi + +if [ ${1-prod} == "prod" ]; then + docker push $HUB_NAME:latest +fi diff --git a/code/api/cliff.toml b/code/api/cliff.toml new file mode 100644 index 0000000..7299951 --- /dev/null +++ b/code/api/cliff.toml @@ -0,0 +1,62 @@ +# configuration file for git-cliff (0.1.0) + +[changelog] +# changelog header +header = """ +# Changelog\n +""" +# template for the changelog body +# https://tera.netlify.app/docs/#introduction +body = """ +{% if version %}\ + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else %}\ + ## [unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {% for commit in commits %} + - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# remove the leading and trailing whitespace from the template +trim = true +# changelog footer +footer = """ + +""" + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = true +# regex for preprocessing the commit messages +commit_preprocessors = [ + { pattern = "([ \\n])(([a-f0-9]{7})[a-f0-9]*)", replace = "${1}commit # [${3}](https://git.ivar.systems/greatoffice/commit/${2})" }, + { pattern = "https://git.ivar.systems/greatoffice/commit/([a-f0-9]{7})[a-f0-9]*", replace = "commit # [${1}](${0})" }, +] +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "Features" }, + { message = "^fix", group = "Bug Fixes" }, + { message = "^doc", group = "Documentation" }, + { message = "^perf", group = "Performance" }, + { message = "^refactor", group = "Refactor" }, + { message = "^style", group = "Styling" }, + { message = "^test", group = "Testing" }, + { message = "^chore", group = "Miscellaneous Tasks" }, +] +# filter out the commits that are not matched by commit parsers +filter_commits = true +# glob pattern for matching git tags +tag_pattern = "v.*" +# regex for skipping tags +skip_tags = "v0.1.0-beta.1" +# regex for ignoring tags +ignore_tags = "" +# sort the tags chronologically +date_order = true +# sort the commits inside sections by oldest/newest order +sort_commits = "newest" diff --git a/code/api/sql/quartz-create.sql b/code/api/sql/quartz-create.sql new file mode 100644 index 0000000..d0dc298 --- /dev/null +++ b/code/api/sql/quartz-create.sql @@ -0,0 +1,156 @@ +CREATE TABLE IF NOT EXISTS qrtz_job_details +( + sched_name TEXT NOT NULL, + job_name TEXT NOT NULL, + job_group TEXT NOT NULL, + description TEXT NULL, + job_class_name TEXT NOT NULL, + is_durable BOOL NOT NULL, + is_nonconcurrent BOOL NOT NULL, + is_update_data BOOL NOT NULL, + requests_recovery BOOL NOT NULL, + job_data BYTEA NULL, + PRIMARY KEY (sched_name, job_name, job_group) +); + +CREATE TABLE IF NOT EXISTS qrtz_triggers +( + sched_name TEXT NOT NULL, + trigger_name TEXT NOT NULL, + trigger_group TEXT NOT NULL, + job_name TEXT NOT NULL, + job_group TEXT NOT NULL, + description TEXT NULL, + next_fire_time BIGINT NULL, + prev_fire_time BIGINT NULL, + priority INTEGER NULL, + trigger_state TEXT NOT NULL, + trigger_type TEXT NOT NULL, + start_time BIGINT NOT NULL, + end_time BIGINT NULL, + calendar_name TEXT NULL, + misfire_instr SMALLINT NULL, + job_data BYTEA NULL, + PRIMARY KEY (sched_name, trigger_name, trigger_group), + FOREIGN KEY (sched_name, job_name, job_group) + REFERENCES qrtz_job_details (sched_name, job_name, job_group) +); + +CREATE TABLE IF NOT EXISTS qrtz_simple_triggers +( + sched_name TEXT NOT NULL, + trigger_name TEXT NOT NULL, + trigger_group TEXT NOT NULL, + repeat_count BIGINT NOT NULL, + repeat_interval BIGINT NOT NULL, + times_triggered BIGINT NOT NULL, + PRIMARY KEY (sched_name, trigger_name, trigger_group), + FOREIGN KEY (sched_name, trigger_name, trigger_group) + REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS QRTZ_SIMPROP_TRIGGERS +( + sched_name TEXT NOT NULL, + trigger_name TEXT NOT NULL, + trigger_group TEXT NOT NULL, + str_prop_1 TEXT NULL, + str_prop_2 TEXT NULL, + str_prop_3 TEXT NULL, + int_prop_1 INTEGER NULL, + int_prop_2 INTEGER NULL, + long_prop_1 BIGINT NULL, + long_prop_2 BIGINT NULL, + dec_prop_1 NUMERIC NULL, + dec_prop_2 NUMERIC NULL, + bool_prop_1 BOOL NULL, + bool_prop_2 BOOL NULL, + time_zone_id TEXT NULL, + PRIMARY KEY (sched_name, trigger_name, trigger_group), + FOREIGN KEY (sched_name, trigger_name, trigger_group) + REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS qrtz_cron_triggers +( + sched_name TEXT NOT NULL, + trigger_name TEXT NOT NULL, + trigger_group TEXT NOT NULL, + cron_expression TEXT NOT NULL, + time_zone_id TEXT, + PRIMARY KEY (sched_name, trigger_name, trigger_group), + FOREIGN KEY (sched_name, trigger_name, trigger_group) + REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS qrtz_blob_triggers +( + sched_name TEXT NOT NULL, + trigger_name TEXT NOT NULL, + trigger_group TEXT NOT NULL, + blob_data BYTEA NULL, + PRIMARY KEY (sched_name, trigger_name, trigger_group), + FOREIGN KEY (sched_name, trigger_name, trigger_group) + REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS qrtz_calendars +( + sched_name TEXT NOT NULL, + calendar_name TEXT NOT NULL, + calendar BYTEA NOT NULL, + PRIMARY KEY (sched_name, calendar_name) +); + +CREATE TABLE IF NOT EXISTS qrtz_paused_trigger_grps +( + sched_name TEXT NOT NULL, + trigger_group TEXT NOT NULL, + PRIMARY KEY (sched_name, trigger_group) +); + +CREATE TABLE IF NOT EXISTS qrtz_fired_triggers +( + sched_name TEXT NOT NULL, + entry_id TEXT NOT NULL, + trigger_name TEXT NOT NULL, + trigger_group TEXT NOT NULL, + instance_name TEXT NOT NULL, + fired_time BIGINT NOT NULL, + sched_time BIGINT NOT NULL, + priority INTEGER NOT NULL, + state TEXT NOT NULL, + job_name TEXT NULL, + job_group TEXT NULL, + is_nonconcurrent BOOL NOT NULL, + requests_recovery BOOL NULL, + PRIMARY KEY (sched_name, entry_id) +); + +CREATE TABLE IF NOT EXISTS qrtz_scheduler_state +( + sched_name TEXT NOT NULL, + instance_name TEXT NOT NULL, + last_checkin_time BIGINT NOT NULL, + checkin_interval BIGINT NOT NULL, + PRIMARY KEY (sched_name, instance_name) +); + +CREATE TABLE IF NOT EXISTS qrtz_locks +( + sched_name TEXT NOT NULL, + lock_name TEXT NOT NULL, + PRIMARY KEY (sched_name, lock_name) +); + +CREATE INDEX IF NOT EXISTS idx_qrtz_j_req_recovery on qrtz_job_details (requests_recovery); +CREATE INDEX IF NOT EXISTS idx_qrtz_t_next_fire_time on qrtz_triggers (next_fire_time); +CREATE INDEX IF NOT EXISTS idx_qrtz_t_state on qrtz_triggers (trigger_state); +CREATE INDEX IF NOT EXISTS idx_qrtz_t_nft_st on qrtz_triggers (next_fire_time, trigger_state); +CREATE INDEX IF NOT EXISTS idx_qrtz_ft_trig_name on qrtz_fired_triggers (trigger_name); +CREATE INDEX IF NOT EXISTS idx_qrtz_ft_trig_group on qrtz_fired_triggers (trigger_group); +CREATE INDEX IF NOT EXISTS idx_qrtz_ft_trig_nm_gp on qrtz_fired_triggers (sched_name, trigger_name, trigger_group); +CREATE INDEX IF NOT EXISTS idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers (instance_name); +CREATE INDEX IF NOT EXISTS idx_qrtz_ft_job_name on qrtz_fired_triggers (job_name); +CREATE INDEX IF NOT EXISTS idx_qrtz_ft_job_group on qrtz_fired_triggers (job_group); +CREATE INDEX IF NOT EXISTS idx_qrtz_ft_job_req_recovery on qrtz_fired_triggers (requests_recovery); diff --git a/code/api/sql/quartz-drop.sql b/code/api/sql/quartz-drop.sql new file mode 100644 index 0000000..87b0797 --- /dev/null +++ b/code/api/sql/quartz-drop.sql @@ -0,0 +1,23 @@ +DROP TABLE IF EXISTS qrtz_fired_triggers; +DROP TABLE IF EXISTS qrtz_paused_trigger_grps; +DROP TABLE IF EXISTS qrtz_scheduler_state; +DROP TABLE IF EXISTS qrtz_locks; +DROP TABLE IF EXISTS qrtz_simprop_triggers; +DROP TABLE IF EXISTS qrtz_simple_triggers; +DROP TABLE IF EXISTS qrtz_cron_triggers; +DROP TABLE IF EXISTS qrtz_blob_triggers; +DROP TABLE IF EXISTS qrtz_triggers; +DROP TABLE IF EXISTS qrtz_job_details; +DROP TABLE IF EXISTS qrtz_calendars; + +DROP INDEX IF EXISTS idx_qrtz_j_req_recovery; +DROP INDEX IF EXISTS idx_qrtz_t_next_fire_time; +DROP INDEX IF EXISTS idx_qrtz_t_state; +DROP INDEX IF EXISTS idx_qrtz_t_nft_st; +DROP INDEX IF EXISTS idx_qrtz_ft_trig_name; +DROP INDEX IF EXISTS idx_qrtz_ft_trig_group; +DROP INDEX IF EXISTS idx_qrtz_ft_trig_nm_gp; +DROP INDEX IF EXISTS idx_qrtz_ft_trig_inst_name; +DROP INDEX IF EXISTS idx_qrtz_ft_job_name; +DROP INDEX IF EXISTS idx_qrtz_ft_job_group; +DROP INDEX IF EXISTS idx_qrtz_ft_job_req_recovery; diff --git a/code/api/src/Endpoints/EndpointBase.cs b/code/api/src/Endpoints/EndpointBase.cs new file mode 100644 index 0000000..105fbdf --- /dev/null +++ b/code/api/src/Endpoints/EndpointBase.cs @@ -0,0 +1,54 @@ +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace IOL.GreatOffice.Api.Endpoints; + +[ApiController] +public class EndpointBase : ControllerBase +{ + /// + /// User data for the currently logged on user. + /// + protected LoggedInUserModel LoggedInUser => new() { + Username = User.FindFirstValue(AppClaims.NAME), + Id = User.FindFirstValue(AppClaims.USER_ID).AsGuid(), + }; + + [NonAction] + protected ActionResult KnownProblem(string title = default, string subtitle = default, Dictionary errors = default) { + HttpContext.Response.Headers.Add(AppHeaders.IS_KNOWN_PROBLEM, "1"); + return BadRequest(new KnownProblemModel { + Title = title, + Subtitle = subtitle, + Errors = errors, + TraceId = HttpContext.TraceIdentifier + }); + } + + [NonAction] + protected ActionResult KnownProblem(KnownProblemModel problem) { + HttpContext.Response.Headers.Add(AppHeaders.IS_KNOWN_PROBLEM, "1"); + problem.TraceId = HttpContext.TraceIdentifier; + return BadRequest(problem); + } + + [NonAction] + protected RequestTimeZoneInfo GetRequestTimeZone(ILogger logger = default) { + Request.Headers.TryGetValue(AppHeaders.BROWSER_TIME_ZONE, out var timeZoneHeader); + var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZoneHeader.ToString().HasValue() ? timeZoneHeader.ToString() : "UTC"); + var offset = tz.BaseUtcOffset.Hours; + + // This is fine as long as the client is not connecting from Australia: Lord Howe Island, + // according to https://en.wikipedia.org/wiki/Daylight_saving_time_by_country + if (tz.IsDaylightSavingTime(AppDateTime.UtcNow)) { + offset++; + } + + logger?.LogInformation("Request time zone (" + tz.Id + ") offset is: " + offset + " hours"); + + return new RequestTimeZoneInfo() { + TimeZoneInfo = tz, + Offset = offset, + LocalDateTime = TimeZoneInfo.ConvertTimeFromUtc(AppDateTime.UtcNow, tz) + }; + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs new file mode 100644 index 0000000..ee136a9 --- /dev/null +++ b/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs @@ -0,0 +1,64 @@ +using ILogger = Microsoft.Extensions.Logging.ILogger; + +namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; + +public class CreateAccountRoute : RouteBaseAsync.WithRequest.WithActionResult +{ + private readonly MainAppDatabase _database; + private readonly UserService _userService; + private readonly IStringLocalizer _localizer; + private readonly EmailValidationService _emailValidation; + private readonly ILogger _logger; + private readonly TenantService _tenantService; + + public CreateAccountRoute(UserService userService, MainAppDatabase database, IStringLocalizer localizer, EmailValidationService emailValidation, TenantService tenantService, ILogger logger) { + _userService = userService; + _database = database; + _localizer = localizer; + _emailValidation = emailValidation; + _tenantService = tenantService; + _logger = logger; + } + + public class Payload + { + public string Username { get; set; } + public string Password { get; set; } + } + + [AllowAnonymous] + [HttpPost("~/_/account/create")] + public override async Task HandleAsync(Payload request, CancellationToken cancellationToken = default) { + var problem = new KnownProblemModel(); + var username = request.Username.Trim(); + if (username.IsValidEmailAddress() == false) { + problem.AddError("username", _localizer["{username} does not look like a valid email", username]); + } else if (_database.Users.FirstOrDefault(c => c.Username == username) != default) { + problem.AddError("username", _localizer["There is already a user registered with username: {username}", username]); + } + + if (request.Password.Length < 6) { + problem.AddError("password", _localizer["The password requires 6 or more characters."]); + } + + if (problem.Errors.Any()) { + problem.Title = _localizer["Invalid form"]; + problem.Subtitle = _localizer["One or more fields is invalid"]; + return KnownProblem(problem); + } + + var user = new User(username); + var tenant = _tenantService.CreateTenant(user.DisplayName() + "'s tenant", user.Id, user.Username); + if (tenant == default) { + _logger.LogError("Not creating new user because the tenant could not be created"); + return KnownProblem(_localizer["Could not create your account, try again soon."]); + } + + user.HashAndSetPassword(request.Password); + _database.Users.Add(user); + await _database.SaveChangesAsync(cancellationToken); + await _userService.LogInUserAsync(HttpContext, user, false, cancellationToken); + await _emailValidation.SendValidationEmailAsync(user); + return Ok(); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/CreateInitialAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/CreateInitialAccountRoute.cs new file mode 100644 index 0000000..e1d13dd --- /dev/null +++ b/code/api/src/Endpoints/Internal/Account/CreateInitialAccountRoute.cs @@ -0,0 +1,32 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; + +public class CreateInitialAccountRoute : RouteBaseAsync.WithoutRequest.WithActionResult +{ + private readonly MainAppDatabase _database; + private readonly UserService _userService; + + public CreateInitialAccountRoute(MainAppDatabase database, UserService userService) { + _database = database; + _userService = userService; + } + + /// + /// Create an initial user account. + /// + /// + /// + [AllowAnonymous] + [HttpGet("~/_/account/create-initial")] + public override async Task HandleAsync(CancellationToken cancellationToken = default) { + if (_database.Users.Any()) { + return NotFound(); + } + + var user = new User("admin@ivarlovlie.no"); + user.HashAndSetPassword("ivar123"); + _database.Users.Add(user); + await _database.SaveChangesAsync(cancellationToken); + await _userService.LogInUserAsync(HttpContext, user, cancellationToken: cancellationToken); + return Redirect("/"); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs new file mode 100644 index 0000000..f487f74 --- /dev/null +++ b/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs @@ -0,0 +1,17 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; + +public class DeleteAccountRoute : RouteBaseAsync.WithoutRequest.WithActionResult +{ + private readonly UserService _userService; + + public DeleteAccountRoute(UserService userService) { + _userService = userService; + } + + [HttpDelete("~/_/account/delete")] + public override async Task HandleAsync(CancellationToken cancellationToken = default) { + await _userService.LogOutUser(HttpContext, cancellationToken); + await _userService.MarkUserAsDeleted(LoggedInUser.Id, LoggedInUser.Id); + return Ok(); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/GetAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/GetAccountRoute.cs new file mode 100644 index 0000000..121b40f --- /dev/null +++ b/code/api/src/Endpoints/Internal/Account/GetAccountRoute.cs @@ -0,0 +1,26 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; + +public class GetAccountRoute : RouteBaseAsync.WithoutRequest.WithActionResult +{ + private readonly MainAppDatabase _database; + + public GetAccountRoute(MainAppDatabase database) { + _database = database; + } + + [HttpGet("~/_/account")] + public override async Task> HandleAsync(CancellationToken cancellationToken = default) { + var user = _database.Users + .Select(x => new {x.Username, x.Id}) + .SingleOrDefault(c => c.Id == LoggedInUser.Id); + if (user != default) { + return Ok(new LoggedInUserModel { + Id = LoggedInUser.Id, + Username = LoggedInUser.Username + }); + } + + await HttpContext.SignOutAsync(); + return Unauthorized(); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/LoginRoute.cs b/code/api/src/Endpoints/Internal/Account/LoginRoute.cs new file mode 100644 index 0000000..703f324 --- /dev/null +++ b/code/api/src/Endpoints/Internal/Account/LoginRoute.cs @@ -0,0 +1,37 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; + +public class LoginRoute : RouteBaseAsync.WithRequest.WithActionResult +{ + private readonly MainAppDatabase _database; + private readonly UserService _userService; + private readonly IStringLocalizer _localizer; + + public LoginRoute(MainAppDatabase database, UserService userService, IStringLocalizer localizer) { + _database = database; + _userService = userService; + _localizer = localizer; + } + + public class Payload + { + public string Username { get; set; } + public string Password { get; set; } + public bool Persist { get; set; } + } + + [AllowAnonymous] + [HttpPost("~/_/account/login")] + public override async Task HandleAsync(Payload request, CancellationToken cancellationToken = default) { + var user = _database.Users.FirstOrDefault(u => u.Username == request.Username); + if (user == default || !user.VerifyPassword(request.Password)) { + return KnownProblem(_localizer["Invalid username or password"]); + } + + if (user.Deleted) { + return KnownProblem(_localizer["This user is deleted, please contact support@greatoffice.life if you think this is an error"]); + } + + await _userService.LogInUserAsync(HttpContext, user, request.Persist, cancellationToken); + return Ok(); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/LogoutRoute.cs b/code/api/src/Endpoints/Internal/Account/LogoutRoute.cs new file mode 100644 index 0000000..295d9f6 --- /dev/null +++ b/code/api/src/Endpoints/Internal/Account/LogoutRoute.cs @@ -0,0 +1,22 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; + +public class LogoutRoute : RouteBaseAsync.WithoutRequest.WithActionResult +{ + private readonly UserService _userService; + + public LogoutRoute(UserService userService) { + _userService = userService; + } + + /// + /// Logout a user. + /// + /// + /// + [AllowAnonymous] + [HttpGet("~/_/account/logout")] + public override async Task HandleAsync(CancellationToken cancellationToken = default) { + await _userService.LogOutUser(HttpContext, cancellationToken); + return Ok(); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs new file mode 100644 index 0000000..1081240 --- /dev/null +++ b/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs @@ -0,0 +1,59 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; + +public class UpdateAccountRoute : RouteBaseAsync.WithRequest.WithActionResult +{ + private readonly MainAppDatabase _database; + private readonly IStringLocalizer _localizer; + + public UpdateAccountRoute(MainAppDatabase database, IStringLocalizer localizer) { + _database = database; + _localizer = localizer; + } + + public class Payload + { + public string Username { get; set; } + + public string Password { get; set; } + } + + [HttpPost("~/_/account/update")] + public override async Task HandleAsync(Payload request, CancellationToken cancellationToken = default) { + var user = _database.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); + if (user == default) { + await HttpContext.SignOutAsync(); + return Unauthorized(); + } + + if (request.Password.IsNullOrWhiteSpace() && request.Username.IsNullOrWhiteSpace()) { + return KnownProblem(_localizer["Invalid request"], _localizer["No data was submitted"]); + } + + var problem = new KnownProblemModel(); + + if (request.Password.HasValue() && request.Password.Length < 6) { + problem.AddError("password", _localizer["The new password must contain at least 6 characters"]); + } + + if (request.Password.HasValue()) { + user.HashAndSetPassword(request.Password); + } + + if (request.Username.HasValue() && !request.Username.IsValidEmailAddress()) { + problem.AddError("username", _localizer["The new username does not look like a valid email address"]); + } + + if (problem.Errors.Any()) { + problem.Title = _localizer["Invalid form"]; + problem.Subtitle = _localizer["One or more validation errors occured"]; + return KnownProblem(problem); + } + + if (request.Username.HasValue()) { + user.Username = request.Username.Trim(); + } + + await _database.SaveChangesAsync(cancellationToken); + return Ok(); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/_calls.http b/code/api/src/Endpoints/Internal/Account/_calls.http new file mode 100644 index 0000000..78380f5 --- /dev/null +++ b/code/api/src/Endpoints/Internal/Account/_calls.http @@ -0,0 +1,9 @@ +### Login +POST http://localhost:5000/_/account/login +Content-Type: application/json + +{ + "username": "i@oiee.no", + "password": "ivar123", + "persist": false +} diff --git a/code/api/src/Endpoints/Internal/INT_EndpointBase.cs b/code/api/src/Endpoints/Internal/INT_EndpointBase.cs new file mode 100644 index 0000000..699a976 --- /dev/null +++ b/code/api/src/Endpoints/Internal/INT_EndpointBase.cs @@ -0,0 +1,9 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal; + +[Authorize] +[ApiExplorerSettings(IgnoreApi = true)] +[ApiVersionNeutral] +public class INT_EndpointBase : EndpointBase +{ + +} diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs new file mode 100644 index 0000000..c6ed417 --- /dev/null +++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs @@ -0,0 +1,40 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests; + +public class CreateResetRequestRoute : RouteBaseAsync.WithRequest.WithActionResult +{ + private readonly ILogger _logger; + private readonly PasswordResetService _passwordResetService; + private readonly MainAppDatabase _database; + private readonly IStringLocalizer _localizer; + + public CreateResetRequestRoute(ILogger logger, PasswordResetService passwordResetService, MainAppDatabase database, IStringLocalizer localizer) { + _logger = logger; + _passwordResetService = passwordResetService; + _database = database; + _localizer = localizer; + } + + public class Payload + { + public string Email { get; set; } + } + + [AllowAnonymous] + [HttpPost("~/_/password-reset-request/create")] + public override async Task HandleAsync(Payload payload, CancellationToken cancellationToken = default) { + if (payload.Email.IsNullOrWhiteSpace()) { + return KnownProblem(_localizer["Invalid form"], + _localizer["One or more fields is invalid"], + new() {{"email", new string[] {_localizer["Email is a required field"]}}} + ); + } + + var tz = GetRequestTimeZone(_logger); + _logger.LogInformation("Creating forgot password request with local date time: " + tz.LocalDateTime.ToString("u")); + var user = _database.Users.FirstOrDefault(c => c.Username.Equals(payload.Email)); + // Don't inform the caller that the user does not exist. + if (user == default) return Ok(); + await _passwordResetService.AddRequestAsync(user, tz.TimeZoneInfo, cancellationToken); + return Ok(); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs new file mode 100644 index 0000000..9cd92bb --- /dev/null +++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs @@ -0,0 +1,36 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests; + +public class FulfillResetRequestRoute : RouteBaseAsync.WithRequest.WithActionResult +{ + private readonly IStringLocalizer _localizer; + private readonly PasswordResetService _passwordResetService; + + public FulfillResetRequestRoute(PasswordResetService passwordResetService, IStringLocalizer localizer) { + _passwordResetService = passwordResetService; + _localizer = localizer; + } + + public class Payload + { + public Guid Id { get; set; } + public string NewPassword { get; set; } + } + + [AllowAnonymous] + [HttpPost("~/_/password-reset-request/fulfill")] + public override async Task HandleAsync(Payload request, CancellationToken cancellationToken = default) { + if (request.NewPassword.Length < 6) { + return KnownProblem(_localizer["Invalid form"], + _localizer["One or more fields is invalid"], + new Dictionary {{"newPassword", new string[] {_localizer["The new password needs to be atleast 6 characters"]}}} + ); + } + + return await _passwordResetService.FulfillRequestAsync(request.Id, request.NewPassword, cancellationToken) switch { + FulfillPasswordResetRequestResult.REQUEST_NOT_FOUND => NotFound(), + FulfillPasswordResetRequestResult.USER_NOT_FOUND => NotFound(), + FulfillPasswordResetRequestResult.FULFILLED => Ok(), + _ => StatusCode(500) + }; + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/IsResetRequestValidRoute.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/IsResetRequestValidRoute.cs new file mode 100644 index 0000000..a87c0a9 --- /dev/null +++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/IsResetRequestValidRoute.cs @@ -0,0 +1,26 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests; + +public class IsResetRequestValidRoute : RouteBaseAsync.WithRequest.WithActionResult +{ + private readonly PasswordResetService _passwordResetService; + + public IsResetRequestValidRoute(PasswordResetService passwordResetService) { + _passwordResetService = passwordResetService; + } + + public class ResponseModel + { + public ResponseModel(bool isValid) { + IsValid = isValid; + } + + public bool IsValid { get; } + } + + [AllowAnonymous] + [HttpGet("~/_/password-reset-request/is-valid")] + public override async Task> HandleAsync(Guid id, CancellationToken cancellationToken = default) { + var request = await _passwordResetService.GetRequestAsync(id, cancellationToken); + return Ok(request == default ? new ResponseModel(false) : new ResponseModel(!request.IsExpired)); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/_calls.http b/code/api/src/Endpoints/Internal/PasswordResetRequests/_calls.http new file mode 100644 index 0000000..cfd2d58 --- /dev/null +++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/_calls.http @@ -0,0 +1,22 @@ +### Create request +POST http://localhost:5000/_/password-reset-request/create +Accept: application/json +Content-Type: application/json + +{ + "email": "" +} + +### Fulfill request +POST http://localhost:5000/_/password-reset-request/fulfill +Accept: application/json +Content-Type: application/json + +{ + "id": "", + "newPassword": "" +} + +### Is request valid +GET http://localhost:5000/_/password-reset-request/is-valid?id= +Accept: application/json diff --git a/code/api/src/Endpoints/Internal/Root/GetSessionRoute.cs b/code/api/src/Endpoints/Internal/Root/GetSessionRoute.cs new file mode 100644 index 0000000..82bbb11 --- /dev/null +++ b/code/api/src/Endpoints/Internal/Root/GetSessionRoute.cs @@ -0,0 +1,64 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.Root; + +public class GetSessionRoute : RouteBaseSync.WithoutRequest.WithActionResult +{ + private readonly MainAppDatabase _database; + private readonly ILogger _logger; + + public GetSessionRoute(MainAppDatabase database, ILogger logger) { + _database = database; + _logger = logger; + } + + public class SessionResponse + { + public string Username { get; set; } + public string DisplayName { get; set; } + public Guid UserId { get; set; } + public SessionTenant CurrentTenant { get; set; } + public List AvailableTenants { get; set; } + + public class SessionTenant + { + public Guid Id { get; set; } + public string Name { get; set; } + } + } + + [Authorize] + [HttpGet("~/_/session-data")] + public override ActionResult Handle() { + var user = _database.Users.Include(c => c.Tenants) + .Select(c => new User() { + Id = c.Id, + Username = c.Username, + FirstName = c.FirstName, + LastName = c.LastName, + Tenants = c.Tenants + }).FirstOrDefault(c => c.Id == LoggedInUser.Id); + + if (user == default) { + return NotFound(); + } + + var currentTenant = user.Tenants.FirstOrDefault(c => c.Id == LoggedInUser.TenantId); + if (currentTenant == default) { + _logger.LogInformation("Could not find current tenant ({tenantId}) for user {userId}", LoggedInUser.TenantId, LoggedInUser.Id); + return NotFound(); + } + + return Ok(new SessionResponse() { + Username = user.Username, + DisplayName = user.DisplayName(), + UserId = user.Id, + CurrentTenant = new SessionResponse.SessionTenant() { + Id = currentTenant.Id, + Name = currentTenant.Name + }, + AvailableTenants = user.Tenants.Select(c => new SessionResponse.SessionTenant() { + Id = c.Id, + Name = c.Name + }).ToList() + }); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Root/IsAuthenticatedRoute.cs b/code/api/src/Endpoints/Internal/Root/IsAuthenticatedRoute.cs new file mode 100644 index 0000000..7bb0a86 --- /dev/null +++ b/code/api/src/Endpoints/Internal/Root/IsAuthenticatedRoute.cs @@ -0,0 +1,10 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.Root; + +public class IsAuthenticatedRoute : RouteBaseSync.WithoutRequest.WithActionResult +{ + [Authorize] + [HttpGet("~/_/is-authenticated")] + public override ActionResult Handle() { + return Ok(); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Root/ReadConfigurationRoute.cs b/code/api/src/Endpoints/Internal/Root/ReadConfigurationRoute.cs new file mode 100644 index 0000000..7270fd8 --- /dev/null +++ b/code/api/src/Endpoints/Internal/Root/ReadConfigurationRoute.cs @@ -0,0 +1,17 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.Root; + +public class ReadConfigurationRoute : RouteBaseSync.WithoutRequest.WithActionResult +{ + private readonly VaultService _vaultService; + + public ReadConfigurationRoute(VaultService vaultService) { + _vaultService = vaultService; + } + + [AllowAnonymous] + [HttpGet("~/_/configuration")] + public override ActionResult Handle() { + var config = _vaultService.GetCurrentAppConfiguration(); + return Content(JsonSerializer.Serialize(config.GetPublicVersion()), "application/json"); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Root/RefreshConfigurationRoute.cs b/code/api/src/Endpoints/Internal/Root/RefreshConfigurationRoute.cs new file mode 100644 index 0000000..fde4832 --- /dev/null +++ b/code/api/src/Endpoints/Internal/Root/RefreshConfigurationRoute.cs @@ -0,0 +1,15 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.Root; + +public class RefreshConfigurationRoute : RouteBaseSync.WithoutRequest.WithoutResult +{ + private readonly VaultService _vaultService; + + public RefreshConfigurationRoute(VaultService vaultService) { + _vaultService = vaultService; + } + + [HttpGet("~/_/refresh-configuration")] + public override void Handle() { + _vaultService.RefreshCurrentAppConfigurationAsync(); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Root/ValidateRoute.cs b/code/api/src/Endpoints/Internal/Root/ValidateRoute.cs new file mode 100644 index 0000000..8f0882d --- /dev/null +++ b/code/api/src/Endpoints/Internal/Root/ValidateRoute.cs @@ -0,0 +1,39 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal.Root; + +public class ValidateRoute : RouteBaseSync.WithRequest.WithActionResult +{ + private readonly EmailValidationService _emailValidation; + private readonly string CanonicalFrontendUrl; + private readonly ILogger _logger; + + public ValidateRoute(VaultService vaultService, EmailValidationService emailValidation, ILogger logger) { + _emailValidation = emailValidation; + _logger = logger; + var c = vaultService.GetCurrentAppConfiguration(); + CanonicalFrontendUrl = c.CANONICAL_FRONTEND_URL; + } + + public class QueryParams + { + [FromQuery] + public Guid Id { get; set; } + } + + [HttpGet("~/_/validate")] + public override ActionResult Handle([FromQuery] QueryParams request) { + var isFulfilled = _emailValidation.FulfillEmailValidationRequest(request.Id, LoggedInUser.Id); + if (!isFulfilled) { + _logger.LogError("Email validation fulfillment failed for request {requestId} and user {userId}", request.Id, LoggedInUser.Id); + return StatusCode(400, $""" + + +

The validation could not be completed

+

We are working on fixing this, in the meantime, have patience.

+Click here to go back to {CanonicalFrontendUrl} + +"""); + } + + return Redirect(CanonicalFrontendUrl + "/portal?msg=emailValidated"); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/RouteBaseAsync.cs b/code/api/src/Endpoints/Internal/RouteBaseAsync.cs new file mode 100644 index 0000000..a87facf --- /dev/null +++ b/code/api/src/Endpoints/Internal/RouteBaseAsync.cs @@ -0,0 +1,73 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal; + +/// +/// A base class for an endpoint that accepts parameters. +/// +public static class RouteBaseAsync +{ + public static class WithRequest + { + public abstract class WithResult : INT_EndpointBase + { + public abstract Task HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + } + + public abstract class WithoutResult : INT_EndpointBase + { + public abstract Task HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + } + + public abstract class WithActionResult : INT_EndpointBase + { + public abstract Task> HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + } + + public abstract class WithActionResult : INT_EndpointBase + { + public abstract Task HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + } + } + + public static class WithoutRequest + { + public abstract class WithResult : INT_EndpointBase + { + public abstract Task HandleAsync( + CancellationToken cancellationToken = default + ); + } + + public abstract class WithoutResult : INT_EndpointBase + { + public abstract Task HandleAsync( + CancellationToken cancellationToken = default + ); + } + + public abstract class WithActionResult : INT_EndpointBase + { + public abstract Task> HandleAsync( + CancellationToken cancellationToken = default + ); + } + + public abstract class WithActionResult : INT_EndpointBase + { + public abstract Task HandleAsync( + CancellationToken cancellationToken = default + ); + } + } +} diff --git a/code/api/src/Endpoints/Internal/RouteBaseSync.cs b/code/api/src/Endpoints/Internal/RouteBaseSync.cs new file mode 100644 index 0000000..9d9bd5a --- /dev/null +++ b/code/api/src/Endpoints/Internal/RouteBaseSync.cs @@ -0,0 +1,53 @@ +namespace IOL.GreatOffice.Api.Endpoints.Internal; + +/// +/// A base class for an endpoint that accepts parameters. +/// +public static class RouteBaseSync +{ + public static class WithRequest + { + public abstract class WithResult : INT_EndpointBase + { + public abstract TResponse Handle(TRequest request); + } + + public abstract class WithoutResult : INT_EndpointBase + { + public abstract void Handle(TRequest request); + } + + public abstract class WithActionResult : INT_EndpointBase + { + public abstract ActionResult Handle(TRequest request); + } + + public abstract class WithActionResult : INT_EndpointBase + { + public abstract ActionResult Handle(TRequest request); + } + } + + public static class WithoutRequest + { + public abstract class WithResult : INT_EndpointBase + { + public abstract TResponse Handle(); + } + + public abstract class WithoutResult : INT_EndpointBase + { + public abstract void Handle(); + } + + public abstract class WithActionResult : INT_EndpointBase + { + public abstract ActionResult Handle(); + } + + public abstract class WithActionResult : INT_EndpointBase + { + public abstract ActionResult Handle(); + } + } +} diff --git a/code/api/src/Endpoints/V1/ApiSpecV1.cs b/code/api/src/Endpoints/V1/ApiSpecV1.cs new file mode 100644 index 0000000..e4f9cc9 --- /dev/null +++ b/code/api/src/Endpoints/V1/ApiSpecV1.cs @@ -0,0 +1,18 @@ +namespace IOL.GreatOffice.Api.Endpoints.V1; + +public static class ApiSpecV1 +{ + private const int MAJOR = 1; + private const int MINOR = 0; + public const string VERSION_STRING = "1.0"; + + public static ApiSpecDocument Document => new() { + Version = new ApiVersion(MAJOR, MINOR), + VersionName = VERSION_STRING, + SwaggerPath = $"/swagger/{VERSION_STRING}/swagger.json", + OpenApiInfo = new OpenApiInfo { + Title = AppConstants.API_NAME, + Version = VERSION_STRING + } + }; +} diff --git a/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs b/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs new file mode 100644 index 0000000..163ddb6 --- /dev/null +++ b/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs @@ -0,0 +1,58 @@ +using System.Text; + +namespace IOL.GreatOffice.Api.Endpoints.V1.ApiTokens; + +public class CreateTokenRoute : RouteBaseSync.WithRequest.WithActionResult +{ + private readonly MainAppDatabase _database; + private readonly AppConfiguration _configuration; + private readonly ILogger _logger; + + public CreateTokenRoute(MainAppDatabase database, VaultService vaultService, ILogger logger) { + _database = database; + _configuration = vaultService.GetCurrentAppConfiguration(); + _logger = logger; + } + + public class Payload + { + public DateTime ExpiryDate { get; set; } + public bool AllowRead { get; set; } + public bool AllowCreate { get; set; } + public bool AllowUpdate { get; set; } + public bool AllowDelete { get; set; } + } + + /// + /// Create a new api token with the provided claims. + /// + /// The claims to set on the api token + /// + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [HttpPost("~/v{version:apiVersion}/api-tokens/create")] + public override ActionResult Handle(Payload request) { + var user = _database.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); + if (user == default) { + return NotFound(new KnownProblemModel("User does not exist")); + } + + var token_entropy = _configuration.APP_AES_KEY; + if (token_entropy.IsNullOrWhiteSpace()) { + _logger.LogWarning("No token entropy is available, Basic auth is disabled"); + return NotFound(); + } + + var accessToken = new ApiAccessToken() { + User = user, + ExpiryDate = request.ExpiryDate.ToUniversalTime(), + AllowCreate = request.AllowCreate, + AllowRead = request.AllowRead, + AllowDelete = request.AllowDelete, + AllowUpdate = request.AllowUpdate + }; + + _database.AccessTokens.Add(accessToken); + _database.SaveChanges(); + return Ok(Convert.ToBase64String(Encoding.UTF8.GetBytes(accessToken.Id.ToString().EncryptWithAes(token_entropy)))); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/V1/ApiTokens/DeleteTokenRoute.cs b/code/api/src/Endpoints/V1/ApiTokens/DeleteTokenRoute.cs new file mode 100644 index 0000000..ee19e40 --- /dev/null +++ b/code/api/src/Endpoints/V1/ApiTokens/DeleteTokenRoute.cs @@ -0,0 +1,31 @@ +namespace IOL.GreatOffice.Api.Endpoints.V1.ApiTokens; + +public class DeleteTokenRoute : RouteBaseSync.WithRequest.WithActionResult +{ + private readonly MainAppDatabase _database; + private readonly ILogger _logger; + + public DeleteTokenRoute(MainAppDatabase database, ILogger logger) { + _database = database; + _logger = logger; + } + + /// + /// Delete an api token, rendering it unusable + /// + /// Id of the token to delete + /// Nothing + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [HttpDelete("~/v{version:apiVersion}/api-tokens/delete")] + public override ActionResult Handle(Guid id) { + var token = _database.AccessTokens.SingleOrDefault(c => c.Id == id); + if (token == default) { + _logger.LogWarning("A deletion request of an already deleted (maybe) api token was received."); + return NotFound(); + } + + _database.AccessTokens.Remove(token); + _database.SaveChanges(); + return Ok(); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/V1/ApiTokens/GetTokensRoute.cs b/code/api/src/Endpoints/V1/ApiTokens/GetTokensRoute.cs new file mode 100644 index 0000000..ee46b34 --- /dev/null +++ b/code/api/src/Endpoints/V1/ApiTokens/GetTokensRoute.cs @@ -0,0 +1,36 @@ +namespace IOL.GreatOffice.Api.Endpoints.V1.ApiTokens; + +public class GetTokensRoute : RouteBaseSync.WithoutRequest.WithResult>> +{ + private readonly MainAppDatabase _database; + + public GetTokensRoute(MainAppDatabase database) { + _database = database; + } + + public class ResponseModel + { + public DateTime ExpiryDate { get; set; } + public bool AllowRead { get; set; } + public bool AllowCreate { get; set; } + public bool AllowUpdate { get; set; } + public bool AllowDelete { get; set; } + public bool HasExpired => ExpiryDate < AppDateTime.UtcNow; + } + + /// + /// Get all tokens, both active and inactive. + /// + /// A list of tokens + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [HttpGet("~/v{version:apiVersion}/api-tokens")] + public override ActionResult> Handle() { + return Ok(_database.AccessTokens.Where(c => c.User.Id == LoggedInUser.Id).Select(c => new ResponseModel() { + AllowCreate = c.AllowCreate, + AllowRead = c.AllowRead, + AllowDelete = c.AllowDelete, + AllowUpdate = c.AllowUpdate, + ExpiryDate = c.ExpiryDate + })); + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Customers/CreateCustomerRoute.cs b/code/api/src/Endpoints/V1/Customers/CreateCustomerRoute.cs new file mode 100644 index 0000000..e58aa37 --- /dev/null +++ b/code/api/src/Endpoints/V1/Customers/CreateCustomerRoute.cs @@ -0,0 +1,66 @@ +namespace IOL.GreatOffice.Api.Endpoints.V1.Customers; + +public class CreateCustomerRoute : RouteBaseAsync.WithRequest.WithActionResult +{ + private readonly MainAppDatabase _database; + private readonly ILogger _logger; + private readonly IStringLocalizer _localizer; + + public CreateCustomerRoute(MainAppDatabase database, ILogger logger, IStringLocalizer localizer) { + _database = database; + _logger = logger; + _localizer = localizer; + } + + [HttpPost("~/v{version:apiVersion}/customers/create")] + public override async Task HandleAsync(CreateCustomerPayload request, CancellationToken cancellationToken = default) { + var problem = new KnownProblemModel(); + if (request.Name.Trim().IsNullOrEmpty()) problem.AddError("name", _localizer["Name is a required field"]); + if (problem.Errors.Any()) { + problem.Title = _localizer["Invalid form"]; + problem.Subtitle = _localizer["One or more validation errors occured"]; + return KnownProblem(problem); + } + + var customer = new Customer(LoggedInUser) { + CustomerNumber = request.CustomerNumber, + Name = request.Name, + Description = request.Description, + Address1 = request.Address1, + Address2 = request.Address2, + Country = request.Country, + Currency = request.Currency, + Email = request.Email, + Phone = request.Phone, + PostalCity = request.PostalCity, + PostalCode = request.PostalCode, + VATNumber = request.VATNumber, + ORGNumber = request.ORGNumber, + DefaultReference = request.DefaultReference, + Website = request.Website + }; + customer.SetOwnerIds(default, LoggedInUser.TenantId); + _database.Customers.Add(customer); + await _database.SaveChangesAsync(cancellationToken); + return Ok(); + } +} + +public class CreateCustomerPayload +{ + public string CustomerNumber { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string Address1 { get; set; } + public string Address2 { get; set; } + public string PostalCode { get; set; } + public string PostalCity { get; set; } + public string Country { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public string VATNumber { get; set; } + public string ORGNumber { get; set; } + public string DefaultReference { get; set; } + public string Website { get; set; } + public string Currency { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Projects/CreateProjectRoute.cs b/code/api/src/Endpoints/V1/Projects/CreateProjectRoute.cs new file mode 100644 index 0000000..bd37faf --- /dev/null +++ b/code/api/src/Endpoints/V1/Projects/CreateProjectRoute.cs @@ -0,0 +1,83 @@ +namespace IOL.GreatOffice.Api.Endpoints.V1.Projects; + +public class CreateProjectRoute : RouteBaseAsync.WithRequest.WithActionResult +{ + private readonly MainAppDatabase _database; + private readonly IStringLocalizer _localizer; + + public CreateProjectRoute(MainAppDatabase database, IStringLocalizer localizer) { + _database = database; + _localizer = localizer; + } + + [HttpPost("~/v{version:apiVersion}/projects/create")] + public override async Task HandleAsync(CreateProjectPayload request, CancellationToken cancellationToken = default) { + var problem = new KnownProblemModel(); + + if (request.Name.IsNullOrEmpty()) { + problem.AddError("name", _localizer["Name is a required field"]); + } + + var project = new Project(LoggedInUser) { + Name = request.Name, + Description = request.Description, + Start = request.Start, + Stop = request.Stop, + }; + + project.SetOwnerIds(default, LoggedInUser.TenantId); + + foreach (var customerId in request.CustomerIds) { + var customer = _database.Customers.FirstOrDefault(c => c.Id == customerId); + if (customer == default) { + problem.AddError("customer_" + customerId, _localizer["Customer not found"]); + continue; + } + + project.Customers.Add(customer); + } + + foreach (var member in request.Members) { + var user = _database.Users.FirstOrDefault(c => c.Id == member.UserId); + if (user == default) { + problem.AddError("members_" + member.UserId, _localizer["User not found"]); + continue; + } + + project.Members.Add(new ProjectMember() { + Project = project, + User = user, + Role = member.Role + }); + } + + if (problem.Errors.Any()) { + problem.Title = _localizer["Invalid form"]; + problem.Subtitle = _localizer["One or more validation errors occured"]; + return KnownProblem(problem); + } + + _database.Projects.Add(project); + await _database.SaveChangesAsync(cancellationToken); + return Ok(); + } +} + +public class CreateProjectResponse +{ } + +public class CreateProjectPayload +{ + public string Name { get; set; } + public string Description { get; set; } + public DateTime Start { get; set; } + public DateTime Stop { get; set; } + public List CustomerIds { get; set; } + public List Members { get; set; } + + public class ProjectMember + { + public Guid UserId { get; set; } + public ProjectRole Role { get; set; } + } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Projects/GetProjectsRoute.cs b/code/api/src/Endpoints/V1/Projects/GetProjectsRoute.cs new file mode 100644 index 0000000..8fe70a6 --- /dev/null +++ b/code/api/src/Endpoints/V1/Projects/GetProjectsRoute.cs @@ -0,0 +1,40 @@ +using MR.AspNetCore.Pagination; + +namespace IOL.GreatOffice.Api.Endpoints.V1.Projects; + +public class GetProjectsRoute : RouteBaseAsync.WithRequest.WithActionResult> +{ + private readonly MainAppDatabase _database; + private readonly PaginationService _pagination; + + public GetProjectsRoute(MainAppDatabase database, PaginationService pagination) { + _database = database; + _pagination = pagination; + } + + [HttpGet("~/v{version:apiVersion}/projects")] + public override async Task>> HandleAsync([FromQuery] GetProjectsQueryParameters request, CancellationToken cancellationToken = default) { + var result = await _pagination.KeysetPaginateAsync( + _database.Projects.ForTenant(LoggedInUser).ConditionalWhere(() => request.NameQuery.HasValue(), p => p.Name.Contains(request.NameQuery)), + b => b.Descending(x => x.CreatedAt), + async id => await _database.Projects.FindAsync(id), + query => query.Select(p => new GetProjectsResponseDto() { + Id = p.Id, + Name = p.Name + }) + ); + return Ok(result); + } +} + +public class GetProjectsResponseDto +{ + public Guid Id { get; set; } + public string Name { get; set; } +} + +public class GetProjectsQueryParameters +{ + [FromQuery(Name = "name")] + public string NameQuery { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Projects/_calls.http b/code/api/src/Endpoints/V1/Projects/_calls.http new file mode 100644 index 0000000..af0eba6 --- /dev/null +++ b/code/api/src/Endpoints/V1/Projects/_calls.http @@ -0,0 +1,12 @@ +### Create Project +GET https://localhost:5001/v1/projects/create +Accept: application/json +Content-Type: application/json +Cookie: "" + +{ + "": "" +} + +### Get Projects +POST http://localhost diff --git a/code/api/src/Endpoints/V1/RouteBaseAsync.cs b/code/api/src/Endpoints/V1/RouteBaseAsync.cs new file mode 100644 index 0000000..33b6f5f --- /dev/null +++ b/code/api/src/Endpoints/V1/RouteBaseAsync.cs @@ -0,0 +1,73 @@ +namespace IOL.GreatOffice.Api.Endpoints.V1; + +/// +/// A base class for an endpoint that accepts parameters. +/// +public class RouteBaseAsync +{ + public class WithRequest + { + public abstract class WithResult : V1_EndpointBase + { + public abstract Task HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + } + + public abstract class WithoutResult : V1_EndpointBase + { + public abstract Task HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + } + + public abstract class WithActionResult : V1_EndpointBase + { + public abstract Task> HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + } + + public abstract class WithActionResult : V1_EndpointBase + { + public abstract Task HandleAsync( + TRequest request, + CancellationToken cancellationToken = default + ); + } + } + + public static class WithoutRequest + { + public abstract class WithResult : V1_EndpointBase + { + public abstract Task HandleAsync( + CancellationToken cancellationToken = default + ); + } + + public abstract class WithoutResult : V1_EndpointBase + { + public abstract Task HandleAsync( + CancellationToken cancellationToken = default + ); + } + + public abstract class WithActionResult : V1_EndpointBase + { + public abstract Task> HandleAsync( + CancellationToken cancellationToken = default + ); + } + + public abstract class WithActionResult : V1_EndpointBase + { + public abstract Task HandleAsync( + CancellationToken cancellationToken = default + ); + } + } +} diff --git a/code/api/src/Endpoints/V1/RouteBaseSync.cs b/code/api/src/Endpoints/V1/RouteBaseSync.cs new file mode 100644 index 0000000..6a86074 --- /dev/null +++ b/code/api/src/Endpoints/V1/RouteBaseSync.cs @@ -0,0 +1,53 @@ +namespace IOL.GreatOffice.Api.Endpoints.V1; + +/// +/// A base class for an endpoint that accepts parameters. +/// +public static class RouteBaseSync +{ + public static class WithRequest + { + public abstract class WithResult : V1_EndpointBase + { + public abstract TResponse Handle(TRequest request); + } + + public abstract class WithoutResult : V1_EndpointBase + { + public abstract void Handle(TRequest request); + } + + public abstract class WithActionResult : V1_EndpointBase + { + public abstract ActionResult Handle(TRequest request); + } + + public abstract class WithActionResult : V1_EndpointBase + { + public abstract ActionResult Handle(TRequest request); + } + } + + public static class WithoutRequest + { + public abstract class WithResult : V1_EndpointBase + { + public abstract TResponse Handle(); + } + + public abstract class WithoutResult : V1_EndpointBase + { + public abstract void Handle(); + } + + public abstract class WithActionResult : V1_EndpointBase + { + public abstract ActionResult Handle(); + } + + public abstract class WithActionResult : V1_EndpointBase + { + public abstract ActionResult Handle(); + } + } +} diff --git a/code/api/src/Endpoints/V1/V1_EndpointBase.cs b/code/api/src/Endpoints/V1/V1_EndpointBase.cs new file mode 100644 index 0000000..08ce4ab --- /dev/null +++ b/code/api/src/Endpoints/V1/V1_EndpointBase.cs @@ -0,0 +1,29 @@ +using System.Net.Http.Headers; + +namespace IOL.GreatOffice.Api.Endpoints.V1; + +[ApiVersion(ApiSpecV1.VERSION_STRING)] +[Authorize(AuthenticationSchemes = AuthSchemes)] +public class V1_EndpointBase : EndpointBase +{ + private const string AuthSchemes = CookieAuthenticationDefaults.AuthenticationScheme + "," + AppConstants.BASIC_AUTH_SCHEME; + + protected bool IsApiCall() { + if (!Request.Headers.ContainsKey("Authorization")) return false; + try { + var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]); + if (authHeader.Parameter == null) return false; + } catch { + return false; + } + + return true; + } + + protected bool HasApiPermission(string permission_key) { + var permission_claim = User.Claims.SingleOrDefault(c => c.Type == permission_key); + return permission_claim is { + Value: "True" + }; + } +} \ No newline at end of file diff --git a/code/api/src/IOL.GreatOffice.Api.csproj b/code/api/src/IOL.GreatOffice.Api.csproj new file mode 100644 index 0000000..15e5ebf --- /dev/null +++ b/code/api/src/IOL.GreatOffice.Api.csproj @@ -0,0 +1,71 @@ + + + + net7.0 + ed5ff3e5-46e2-4d7e-8272-7081f5abfee4 + true + true + disable + CS1591 + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + build_and_push.sh + + + Dockerfile + + + + + + ResXFileCodeGenerator + SharedResources.en.Designer.cs + + + ResXFileCodeGenerator + SharedResources.nb.Designer.cs + + + + + + True + True + SharedResource.en.resx + + + True + True + SharedResources.nb.resx + + + + diff --git a/code/api/src/Jobs/AccessTokenCleanupJob.cs b/code/api/src/Jobs/AccessTokenCleanupJob.cs new file mode 100644 index 0000000..20b450c --- /dev/null +++ b/code/api/src/Jobs/AccessTokenCleanupJob.cs @@ -0,0 +1,20 @@ +namespace IOL.GreatOffice.Api.Jobs; + +public class AccessTokenCleanupJob : IJob +{ + private readonly ILogger _logger; + private readonly MainAppDatabase _context; + + public AccessTokenCleanupJob(ILogger logger, MainAppDatabase context) { + _logger = logger; + _context = context; + } + + public Task Execute(IJobExecutionContext context) { + var staleTokens = _context.AccessTokens.Where(c => c.ExpiryDate < AppDateTime.UtcNow).ToList(); + if (staleTokens.IsNullOrEmpty()) return Task.CompletedTask; + _logger.LogInformation("Removing {0} stale tokens", staleTokens.Count()); + _context.AccessTokens.RemoveRange(staleTokens); + return Task.CompletedTask; + } +} diff --git a/code/api/src/Jobs/JobRegister.cs b/code/api/src/Jobs/JobRegister.cs new file mode 100644 index 0000000..1da7d5b --- /dev/null +++ b/code/api/src/Jobs/JobRegister.cs @@ -0,0 +1,23 @@ +namespace IOL.GreatOffice.Api.Jobs; + +public static class JobRegister +{ + private static readonly JobKey AccessTokenCleanupKey = new("AccessTokenCleanupKey"); + private static readonly JobKey VaultTokenRenewalKey = new("VaultTokenRenewalKey"); + + public static IServiceCollectionQuartzConfigurator RegisterJobs(this IServiceCollectionQuartzConfigurator configurator) { + configurator.AddJob(AccessTokenCleanupKey); + configurator.AddJob(VaultTokenRenewalKey); + configurator.AddTrigger(options => { + options.ForJob(AccessTokenCleanupKey) + .WithIdentity(AccessTokenCleanupKey.Name + "-trigger") + .WithCronSchedule("0 0 0/1 ? * * *"); + }); + configurator.AddTrigger(options => { + options.ForJob(VaultTokenRenewalKey) + .WithIdentity(VaultTokenRenewalKey.Name + "-trigger") + .WithCronSchedule("0 0 0/1 ? * * *"); + }); + return configurator; + } +} \ No newline at end of file diff --git a/code/api/src/Jobs/VaultTokenRenewalJob.cs b/code/api/src/Jobs/VaultTokenRenewalJob.cs new file mode 100644 index 0000000..1768629 --- /dev/null +++ b/code/api/src/Jobs/VaultTokenRenewalJob.cs @@ -0,0 +1,24 @@ +namespace IOL.GreatOffice.Api.Jobs; + +public class VaultTokenRenewalJob : IJob +{ + private readonly ILogger _logger; + private readonly VaultService _vaultService; + + public VaultTokenRenewalJob(ILogger logger, VaultService vaultService) { + _logger = logger; + _vaultService = vaultService; + } + + public async Task Execute(IJobExecutionContext context) { + _logger.LogInformation("Starting vault token renewal"); + var renew = await _vaultService.RenewTokenAsync(); + if (renew == default) { + _logger.LogCritical("Renewal did not succeed"); + return; + } + + var token = await _vaultService.LookupTokenAsync(); + _logger.LogInformation("Token was renewed, new expire time {expires}", token.Data.ExpireTime); + } +} \ No newline at end of file diff --git a/code/api/src/Migrations/20210517202115_InitialMigration.Designer.cs b/code/api/src/Migrations/20210517202115_InitialMigration.Designer.cs new file mode 100644 index 0000000..da3c3ec --- /dev/null +++ b/code/api/src/Migrations/20210517202115_InitialMigration.Designer.cs @@ -0,0 +1,238 @@ +// +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20210517202115_InitialMigration")] + partial class InitialMigration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.6") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.ToTable("time_labels"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users"); + + b.HasData( + new + { + Id = new Guid("784938f0-cc0e-46ec-afa6-fc60b47b28db"), + Created = new DateTime(2021, 5, 17, 20, 21, 14, 827, DateTimeKind.Utc).AddTicks(4868), + Password = "AAAAAAEAACcQAAAAEJdtrX3pEeIbcgY+BDAr56gvfbc420ag1TllA0cK6Q6Gw3+gGDIQtYIZnisW3dmqaQ==", + Username = "admin@ivarlovlie.no" + }); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.Property("EntriesId") + .HasColumnType("uuid") + .HasColumnName("entries_id"); + + b.Property("LabelsId") + .HasColumnType("uuid") + .HasColumnName("labels_id"); + + b.HasKey("EntriesId", "LabelsId") + .HasName("pk_time_entry_time_label"); + + b.HasIndex("LabelsId") + .HasDatabaseName("ix_time_entry_time_label_labels_id"); + + b.ToTable("time_entry_time_label"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeEntry", null) + .WithMany() + .HasForeignKey("EntriesId") + .HasConstraintName("fk_time_entry_time_label_time_entries_entries_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IOL.GreatOffice.Data.Database.TimeLabel", null) + .WithMany() + .HasForeignKey("LabelsId") + .HasConstraintName("fk_time_entry_time_label_time_labels_labels_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20210517202115_InitialMigration.cs b/code/api/src/Migrations/20210517202115_InitialMigration.cs new file mode 100644 index 0000000..8bfaf61 --- /dev/null +++ b/code/api/src/Migrations/20210517202115_InitialMigration.cs @@ -0,0 +1,162 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class InitialMigration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "time_categories", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + name = table.Column(type: "text", nullable: true), + color = table.Column(type: "text", nullable: true), + user_id = table.Column(type: "uuid", nullable: false), + created = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_time_categories", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "time_labels", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + name = table.Column(type: "text", nullable: true), + color = table.Column(type: "text", nullable: true), + user_id = table.Column(type: "uuid", nullable: false), + created = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_time_labels", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "users", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + username = table.Column(type: "text", nullable: true), + password = table.Column(type: "text", nullable: true), + created = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_users", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "time_entries", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + start = table.Column(type: "timestamp with time zone", nullable: false), + stop = table.Column(type: "timestamp with time zone", nullable: false), + note = table.Column(type: "text", nullable: true), + user_id = table.Column(type: "uuid", nullable: false), + category_id = table.Column(type: "uuid", nullable: true), + created = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_time_entries", x => x.id); + table.ForeignKey( + name: "fk_time_entries_time_categories_category_id", + column: x => x.category_id, + principalTable: "time_categories", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "forgot_password_requests", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + created = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_forgot_password_requests", x => x.id); + table.ForeignKey( + name: "fk_forgot_password_requests_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "time_entry_time_label", + columns: table => new + { + entries_id = table.Column(type: "uuid", nullable: false), + labels_id = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_time_entry_time_label", x => new { x.entries_id, x.labels_id }); + table.ForeignKey( + name: "fk_time_entry_time_label_time_entries_entries_id", + column: x => x.entries_id, + principalTable: "time_entries", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_time_entry_time_label_time_labels_labels_id", + column: x => x.labels_id, + principalTable: "time_labels", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.InsertData( + table: "users", + columns: new[] { "id", "created", "password", "username" }, + values: new object[] { new Guid("784938f0-cc0e-46ec-afa6-fc60b47b28db"), new DateTime(2021, 5, 17, 20, 21, 14, 827, DateTimeKind.Utc).AddTicks(4868), "AAAAAAEAACcQAAAAEJdtrX3pEeIbcgY+BDAr56gvfbc420ag1TllA0cK6Q6Gw3+gGDIQtYIZnisW3dmqaQ==", "admin@ivarlovlie.no" }); + + migrationBuilder.CreateIndex( + name: "ix_forgot_password_requests_user_id", + table: "forgot_password_requests", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_category_id", + table: "time_entries", + column: "category_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entry_time_label_labels_id", + table: "time_entry_time_label", + column: "labels_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "forgot_password_requests"); + + migrationBuilder.DropTable( + name: "time_entry_time_label"); + + migrationBuilder.DropTable( + name: "users"); + + migrationBuilder.DropTable( + name: "time_entries"); + + migrationBuilder.DropTable( + name: "time_labels"); + + migrationBuilder.DropTable( + name: "time_categories"); + } + } +} diff --git a/code/api/src/Migrations/20210522165932_RenameNoteToDescription.Designer.cs b/code/api/src/Migrations/20210522165932_RenameNoteToDescription.Designer.cs new file mode 100644 index 0000000..28408c7 --- /dev/null +++ b/code/api/src/Migrations/20210522165932_RenameNoteToDescription.Designer.cs @@ -0,0 +1,229 @@ +// +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20210522165932_RenameNoteToDescription")] + partial class RenameNoteToDescription + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.6") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp without time zone") + .HasColumnName("created"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("Created") + .HasColumnType("timestamp without time zone") + .HasColumnName("created"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("Created") + .HasColumnType("timestamp without time zone") + .HasColumnName("created"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Start") + .HasColumnType("timestamp without time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp without time zone") + .HasColumnName("stop"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("Created") + .HasColumnType("timestamp without time zone") + .HasColumnName("created"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.ToTable("time_labels"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp without time zone") + .HasColumnName("created"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users"); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.Property("EntriesId") + .HasColumnType("uuid") + .HasColumnName("entries_id"); + + b.Property("LabelsId") + .HasColumnType("uuid") + .HasColumnName("labels_id"); + + b.HasKey("EntriesId", "LabelsId") + .HasName("pk_time_entry_time_label"); + + b.HasIndex("LabelsId") + .HasDatabaseName("ix_time_entry_time_label_labels_id"); + + b.ToTable("time_entry_time_label"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeEntry", null) + .WithMany() + .HasForeignKey("EntriesId") + .HasConstraintName("fk_time_entry_time_label_time_entries_entries_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("IOL.GreatOffice.Data.Database.TimeLabel", null) + .WithMany() + .HasForeignKey("LabelsId") + .HasConstraintName("fk_time_entry_time_label_time_labels_labels_id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20210522165932_RenameNoteToDescription.cs b/code/api/src/Migrations/20210522165932_RenameNoteToDescription.cs new file mode 100644 index 0000000..e5bae54 --- /dev/null +++ b/code/api/src/Migrations/20210522165932_RenameNoteToDescription.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class RenameNoteToDescription : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "users", + keyColumn: "id", + keyValue: new Guid("784938f0-cc0e-46ec-afa6-fc60b47b28db")); + + migrationBuilder.RenameColumn( + name: "note", + table: "time_entries", + newName: "description"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "description", + table: "time_entries", + newName: "note"); + + migrationBuilder.InsertData( + table: "users", + columns: new[] { "id", "created", "password", "username" }, + values: new object[] { new Guid("784938f0-cc0e-46ec-afa6-fc60b47b28db"), new DateTime(2021, 5, 17, 20, 21, 14, 827, DateTimeKind.Utc).AddTicks(4868), "AAAAAAEAACcQAAAAEJdtrX3pEeIbcgY+BDAr56gvfbc420ag1TllA0cK6Q6Gw3+gGDIQtYIZnisW3dmqaQ==", "admin@ivarlovlie.no" }); + } + } +} diff --git a/code/api/src/Migrations/20211002113037_V6Migration.Designer.cs b/code/api/src/Migrations/20211002113037_V6Migration.Designer.cs new file mode 100644 index 0000000..50f461e --- /dev/null +++ b/code/api/src/Migrations/20211002113037_V6Migration.Designer.cs @@ -0,0 +1,233 @@ +// + + +#nullable disable + +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20211002113037_V6Migration")] + partial class V6Migration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.0-rc.1.21452.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.Property("EntriesId") + .HasColumnType("uuid") + .HasColumnName("entries_id"); + + b.Property("LabelsId") + .HasColumnType("uuid") + .HasColumnName("labels_id"); + + b.HasKey("EntriesId", "LabelsId") + .HasName("pk_time_entry_time_label"); + + b.HasIndex("LabelsId") + .HasDatabaseName("ix_time_entry_time_label_labels_id"); + + b.ToTable("time_entry_time_label", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeEntry", null) + .WithMany() + .HasForeignKey("EntriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_entries_entries_id"); + + b.HasOne("IOL.GreatOffice.Data.Database.TimeLabel", null) + .WithMany() + .HasForeignKey("LabelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_labels_labels_id"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20211002113037_V6Migration.cs b/code/api/src/Migrations/20211002113037_V6Migration.cs new file mode 100644 index 0000000..c7ac971 --- /dev/null +++ b/code/api/src/Migrations/20211002113037_V6Migration.cs @@ -0,0 +1,130 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class V6Migration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("SET TimeZone='UTC'"); + migrationBuilder.AlterColumn( + name: "created", + table: "users", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "created", + table: "time_labels", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "stop", + table: "time_entries", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "start", + table: "time_entries", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "created", + table: "time_entries", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "created", + table: "time_categories", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + + migrationBuilder.AlterColumn( + name: "created", + table: "forgot_password_requests", + type: "timestamp with time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp without time zone"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("SET TimeZone='UTC'"); + migrationBuilder.AlterColumn( + name: "created", + table: "users", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "created", + table: "time_labels", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "stop", + table: "time_entries", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "start", + table: "time_entries", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "created", + table: "time_entries", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "created", + table: "time_categories", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + + migrationBuilder.AlterColumn( + name: "created", + table: "forgot_password_requests", + type: "timestamp without time zone", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "timestamp with time zone"); + } + } +} diff --git a/code/api/src/Migrations/20220225143559_GithubUserMappings.Designer.cs b/code/api/src/Migrations/20220225143559_GithubUserMappings.Designer.cs new file mode 100644 index 0000000..6c59262 --- /dev/null +++ b/code/api/src/Migrations/20220225143559_GithubUserMappings.Designer.cs @@ -0,0 +1,270 @@ +// + + +#nullable disable + +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20220225143559_GithubUserMappings")] + partial class GithubUserMappings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.GithubUserMapping", b => + { + b.Property("GithubId") + .HasColumnType("text") + .HasColumnName("github_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("RefreshToken") + .HasColumnType("text") + .HasColumnName("refresh_token"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("GithubId") + .HasName("pk_github_user_mappings"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_github_user_mappings_user_id"); + + b.ToTable("github_user_mappings", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.Property("EntriesId") + .HasColumnType("uuid") + .HasColumnName("entries_id"); + + b.Property("LabelsId") + .HasColumnType("uuid") + .HasColumnName("labels_id"); + + b.HasKey("EntriesId", "LabelsId") + .HasName("pk_time_entry_time_label"); + + b.HasIndex("LabelsId") + .HasDatabaseName("ix_time_entry_time_label_labels_id"); + + b.ToTable("time_entry_time_label", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.GithubUserMapping", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_github_user_mappings_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeEntry", null) + .WithMany() + .HasForeignKey("EntriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_entries_entries_id"); + + b.HasOne("IOL.GreatOffice.Data.Database.TimeLabel", null) + .WithMany() + .HasForeignKey("LabelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_labels_labels_id"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20220225143559_GithubUserMappings.cs b/code/api/src/Migrations/20220225143559_GithubUserMappings.cs new file mode 100644 index 0000000..fc30c7a --- /dev/null +++ b/code/api/src/Migrations/20220225143559_GithubUserMappings.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class GithubUserMappings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "github_user_mappings", + columns: table => new + { + github_id = table.Column(type: "text", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + email = table.Column(type: "text", nullable: true), + refresh_token = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_github_user_mappings", x => x.github_id); + table.ForeignKey( + name: "fk_github_user_mappings_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateIndex( + name: "ix_github_user_mappings_user_id", + table: "github_user_mappings", + column: "user_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "github_user_mappings"); + } + } +} diff --git a/code/api/src/Migrations/20220319135910_RenameCreated.Designer.cs b/code/api/src/Migrations/20220319135910_RenameCreated.Designer.cs new file mode 100644 index 0000000..2f96cdc --- /dev/null +++ b/code/api/src/Migrations/20220319135910_RenameCreated.Designer.cs @@ -0,0 +1,270 @@ +// + + +#nullable disable + +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20220319135910_RenameCreated")] + partial class RenameCreated + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.GithubUserMapping", b => + { + b.Property("GithubId") + .HasColumnType("text") + .HasColumnName("github_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("RefreshToken") + .HasColumnType("text") + .HasColumnName("refresh_token"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("GithubId") + .HasName("pk_github_user_mappings"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_github_user_mappings_user_id"); + + b.ToTable("github_user_mappings", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.Property("EntriesId") + .HasColumnType("uuid") + .HasColumnName("entries_id"); + + b.Property("LabelsId") + .HasColumnType("uuid") + .HasColumnName("labels_id"); + + b.HasKey("EntriesId", "LabelsId") + .HasName("pk_time_entry_time_label"); + + b.HasIndex("LabelsId") + .HasDatabaseName("ix_time_entry_time_label_labels_id"); + + b.ToTable("time_entry_time_label", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.GithubUserMapping", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_github_user_mappings_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeEntry", null) + .WithMany() + .HasForeignKey("EntriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_entries_entries_id"); + + b.HasOne("IOL.GreatOffice.Data.Database.TimeLabel", null) + .WithMany() + .HasForeignKey("LabelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_labels_labels_id"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20220319135910_RenameCreated.cs b/code/api/src/Migrations/20220319135910_RenameCreated.cs new file mode 100644 index 0000000..6571e50 --- /dev/null +++ b/code/api/src/Migrations/20220319135910_RenameCreated.cs @@ -0,0 +1,65 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class RenameCreated : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "created", + table: "users", + newName: "created_at"); + + migrationBuilder.RenameColumn( + name: "created", + table: "time_labels", + newName: "created_at"); + + migrationBuilder.RenameColumn( + name: "created", + table: "time_entries", + newName: "created_at"); + + migrationBuilder.RenameColumn( + name: "created", + table: "time_categories", + newName: "created_at"); + + migrationBuilder.RenameColumn( + name: "created", + table: "forgot_password_requests", + newName: "created_at"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "created_at", + table: "users", + newName: "created"); + + migrationBuilder.RenameColumn( + name: "created_at", + table: "time_labels", + newName: "created"); + + migrationBuilder.RenameColumn( + name: "created_at", + table: "time_entries", + newName: "created"); + + migrationBuilder.RenameColumn( + name: "created_at", + table: "time_categories", + newName: "created"); + + migrationBuilder.RenameColumn( + name: "created_at", + table: "forgot_password_requests", + newName: "created"); + } + } +} diff --git a/code/api/src/Migrations/20220319144958_ModifiedAt.Designer.cs b/code/api/src/Migrations/20220319144958_ModifiedAt.Designer.cs new file mode 100644 index 0000000..349c4ad --- /dev/null +++ b/code/api/src/Migrations/20220319144958_ModifiedAt.Designer.cs @@ -0,0 +1,290 @@ +// + + +#nullable disable + +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20220319144958_ModifiedAt")] + partial class ModifiedAt + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.GithubUserMapping", b => + { + b.Property("GithubId") + .HasColumnType("text") + .HasColumnName("github_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("RefreshToken") + .HasColumnType("text") + .HasColumnName("refresh_token"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("GithubId") + .HasName("pk_github_user_mappings"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_github_user_mappings_user_id"); + + b.ToTable("github_user_mappings", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.Property("EntriesId") + .HasColumnType("uuid") + .HasColumnName("entries_id"); + + b.Property("LabelsId") + .HasColumnType("uuid") + .HasColumnName("labels_id"); + + b.HasKey("EntriesId", "LabelsId") + .HasName("pk_time_entry_time_label"); + + b.HasIndex("LabelsId") + .HasDatabaseName("ix_time_entry_time_label_labels_id"); + + b.ToTable("time_entry_time_label", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.GithubUserMapping", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_github_user_mappings_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeEntry", null) + .WithMany() + .HasForeignKey("EntriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_entries_entries_id"); + + b.HasOne("IOL.GreatOffice.Data.Database.TimeLabel", null) + .WithMany() + .HasForeignKey("LabelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_labels_labels_id"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20220319144958_ModifiedAt.cs b/code/api/src/Migrations/20220319144958_ModifiedAt.cs new file mode 100644 index 0000000..028473d --- /dev/null +++ b/code/api/src/Migrations/20220319144958_ModifiedAt.cs @@ -0,0 +1,66 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class ModifiedAt : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "modified_at", + table: "users", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "modified_at", + table: "time_labels", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "modified_at", + table: "time_entries", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "modified_at", + table: "time_categories", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "modified_at", + table: "forgot_password_requests", + type: "timestamp with time zone", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "modified_at", + table: "users"); + + migrationBuilder.DropColumn( + name: "modified_at", + table: "time_labels"); + + migrationBuilder.DropColumn( + name: "modified_at", + table: "time_entries"); + + migrationBuilder.DropColumn( + name: "modified_at", + table: "time_categories"); + + migrationBuilder.DropColumn( + name: "modified_at", + table: "forgot_password_requests"); + } + } +} diff --git a/code/api/src/Migrations/20220319203018_UserBase.Designer.cs b/code/api/src/Migrations/20220319203018_UserBase.Designer.cs new file mode 100644 index 0000000..c918d3d --- /dev/null +++ b/code/api/src/Migrations/20220319203018_UserBase.Designer.cs @@ -0,0 +1,322 @@ +// + + +#nullable disable + +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20220319203018_UserBase")] + partial class UserBase + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.GithubUserMapping", b => + { + b.Property("GithubId") + .HasColumnType("text") + .HasColumnName("github_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("RefreshToken") + .HasColumnType("text") + .HasColumnName("refresh_token"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("GithubId") + .HasName("pk_github_user_mappings"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_github_user_mappings_user_id"); + + b.ToTable("github_user_mappings", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_categories_user_id"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_entries_user_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_labels_user_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.Property("password") + .HasColumnType("text") + .HasColumnName("password"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.Property("EntriesId") + .HasColumnType("uuid") + .HasColumnName("entries_id"); + + b.Property("LabelsId") + .HasColumnType("uuid") + .HasColumnName("labels_id"); + + b.HasKey("EntriesId", "LabelsId") + .HasName("pk_time_entry_time_label"); + + b.HasIndex("LabelsId") + .HasDatabaseName("ix_time_entry_time_label_labels_id"); + + b.ToTable("time_entry_time_label", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.GithubUserMapping", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_github_user_mappings_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_time_categories_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_time_entries_users_user_id"); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_time_labels_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeEntry", null) + .WithMany() + .HasForeignKey("EntriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_entries_entries_id"); + + b.HasOne("IOL.GreatOffice.Data.Database.TimeLabel", null) + .WithMany() + .HasForeignKey("LabelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_labels_labels_id"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20220319203018_UserBase.cs b/code/api/src/Migrations/20220319203018_UserBase.cs new file mode 100644 index 0000000..14d3f4b --- /dev/null +++ b/code/api/src/Migrations/20220319203018_UserBase.cs @@ -0,0 +1,140 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class UserBase : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "modified_at", + table: "forgot_password_requests"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_labels", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_entries", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_categories", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_user_id", + table: "time_labels", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_user_id", + table: "time_entries", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_user_id", + table: "time_categories", + column: "user_id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_user_id", + table: "time_categories", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_user_id", + table: "time_entries", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_user_id", + table: "time_labels", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_user_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_user_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_user_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_labels_user_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_entries_user_id", + table: "time_entries"); + + migrationBuilder.DropIndex( + name: "ix_time_categories_user_id", + table: "time_categories"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_labels", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_entries", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_categories", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "modified_at", + table: "forgot_password_requests", + type: "timestamp with time zone", + nullable: true); + } + } +} diff --git a/code/api/src/Migrations/20220320115601_Update1.Designer.cs b/code/api/src/Migrations/20220320115601_Update1.Designer.cs new file mode 100644 index 0000000..3b6a63a --- /dev/null +++ b/code/api/src/Migrations/20220320115601_Update1.Designer.cs @@ -0,0 +1,342 @@ +// + + +#nullable disable + +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20220320115601_Update1")] + partial class Update1 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.GithubUserMapping", b => + { + b.Property("GithubId") + .HasColumnType("text") + .HasColumnName("github_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("RefreshToken") + .HasColumnType("text") + .HasColumnName("refresh_token"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("GithubId") + .HasName("pk_github_user_mappings"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_github_user_mappings_user_id"); + + b.ToTable("github_user_mappings", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_categories_user_id"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_entries_user_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_labels_user_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.Property("password") + .HasColumnType("text") + .HasColumnName("password"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.Property("EntriesId") + .HasColumnType("uuid") + .HasColumnName("entries_id"); + + b.Property("LabelsId") + .HasColumnType("uuid") + .HasColumnName("labels_id"); + + b.HasKey("EntriesId", "LabelsId") + .HasName("pk_time_entry_time_label"); + + b.HasIndex("LabelsId") + .HasDatabaseName("ix_time_entry_time_label_labels_id"); + + b.ToTable("time_entry_time_label", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.GithubUserMapping", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_github_user_mappings_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany("Categories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeCategory", "Category") + .WithMany("Entries") + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany("Entries") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_users_user_id"); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany("Labels") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeEntry", null) + .WithMany() + .HasForeignKey("EntriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_entries_entries_id"); + + b.HasOne("IOL.GreatOffice.Data.Database.TimeLabel", null) + .WithMany() + .HasForeignKey("LabelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_labels_labels_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.Navigation("Entries"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.User", b => + { + b.Navigation("Categories"); + + b.Navigation("Entries"); + + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20220320115601_Update1.cs b/code/api/src/Migrations/20220320115601_Update1.cs new file mode 100644 index 0000000..8b06fb7 --- /dev/null +++ b/code/api/src/Migrations/20220320115601_Update1.cs @@ -0,0 +1,139 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class Update1 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_user_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_user_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_user_id", + table: "time_labels"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_labels", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_entries", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_categories", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_user_id", + table: "time_categories", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_user_id", + table: "time_entries", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_user_id", + table: "time_labels", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_user_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_user_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_user_id", + table: "time_labels"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_labels", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_entries", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_categories", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_user_id", + table: "time_categories", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_user_id", + table: "time_entries", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_user_id", + table: "time_labels", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + } + } +} diff --git a/code/api/src/Migrations/20220320132220_UpdatedForgotPasswordRequests.Designer.cs b/code/api/src/Migrations/20220320132220_UpdatedForgotPasswordRequests.Designer.cs new file mode 100644 index 0000000..8dae7c8 --- /dev/null +++ b/code/api/src/Migrations/20220320132220_UpdatedForgotPasswordRequests.Designer.cs @@ -0,0 +1,344 @@ +// + + +#nullable disable + +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20220320132220_UpdatedForgotPasswordRequests")] + partial class UpdatedForgotPasswordRequests + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.GithubUserMapping", b => + { + b.Property("GithubId") + .HasColumnType("text") + .HasColumnName("github_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("RefreshToken") + .HasColumnType("text") + .HasColumnName("refresh_token"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("GithubId") + .HasName("pk_github_user_mappings"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_github_user_mappings_user_id"); + + b.ToTable("github_user_mappings", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_categories_user_id"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_entries_user_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_labels_user_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.Property("EntriesId") + .HasColumnType("uuid") + .HasColumnName("entries_id"); + + b.Property("LabelsId") + .HasColumnType("uuid") + .HasColumnName("labels_id"); + + b.HasKey("EntriesId", "LabelsId") + .HasName("pk_time_entry_time_label"); + + b.HasIndex("LabelsId") + .HasDatabaseName("ix_time_entry_time_label_labels_id"); + + b.ToTable("time_entry_time_label", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.GithubUserMapping", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_github_user_mappings_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany("Categories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeCategory", "Category") + .WithMany("Entries") + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany("Entries") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_users_user_id"); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.User", "User") + .WithMany("Labels") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Data.Database.TimeEntry", null) + .WithMany() + .HasForeignKey("EntriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_entries_entries_id"); + + b.HasOne("IOL.GreatOffice.Data.Database.TimeLabel", null) + .WithMany() + .HasForeignKey("LabelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_labels_labels_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.TimeCategory", b => + { + b.Navigation("Entries"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Data.Database.User", b => + { + b.Navigation("Categories"); + + b.Navigation("Entries"); + + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20220320132220_UpdatedForgotPasswordRequests.cs b/code/api/src/Migrations/20220320132220_UpdatedForgotPasswordRequests.cs new file mode 100644 index 0000000..df7a195 --- /dev/null +++ b/code/api/src/Migrations/20220320132220_UpdatedForgotPasswordRequests.cs @@ -0,0 +1,57 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class UpdatedForgotPasswordRequests : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_forgot_password_requests_users_user_id", + table: "forgot_password_requests"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "forgot_password_requests", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AddForeignKey( + name: "fk_forgot_password_requests_users_user_id", + table: "forgot_password_requests", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_forgot_password_requests_users_user_id", + table: "forgot_password_requests"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "forgot_password_requests", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddForeignKey( + name: "fk_forgot_password_requests_users_user_id", + table: "forgot_password_requests", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + } + } +} diff --git a/code/api/src/Migrations/20220529190359_ApiAccessTokens.Designer.cs b/code/api/src/Migrations/20220529190359_ApiAccessTokens.Designer.cs new file mode 100644 index 0000000..3dc01a7 --- /dev/null +++ b/code/api/src/Migrations/20220529190359_ApiAccessTokens.Designer.cs @@ -0,0 +1,401 @@ +// +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20220529190359_ApiAccessTokens")] + partial class ApiAccessTokens + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.Property("GithubId") + .HasColumnType("text") + .HasColumnName("github_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("RefreshToken") + .HasColumnType("text") + .HasColumnName("refresh_token"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("GithubId") + .HasName("pk_github_user_mappings"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_github_user_mappings_user_id"); + + b.ToTable("github_user_mappings", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_categories_user_id"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_entries_user_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_labels_user_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.Property("EntriesId") + .HasColumnType("uuid") + .HasColumnName("entries_id"); + + b.Property("LabelsId") + .HasColumnType("uuid") + .HasColumnName("labels_id"); + + b.HasKey("EntriesId", "LabelsId") + .HasName("pk_time_entry_time_label"); + + b.HasIndex("LabelsId") + .HasDatabaseName("ix_time_entry_time_label_labels_id"); + + b.ToTable("time_entry_time_label", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_github_user_mappings_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany("Categories") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany("Entries") + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany("Entries") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_users_user_id"); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany("Labels") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany() + .HasForeignKey("EntriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_entries_entries_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeLabel", null) + .WithMany() + .HasForeignKey("LabelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_labels_labels_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Navigation("Entries"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Navigation("Categories"); + + b.Navigation("Entries"); + + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20220529190359_ApiAccessTokens.cs b/code/api/src/Migrations/20220529190359_ApiAccessTokens.cs new file mode 100644 index 0000000..dc44bee --- /dev/null +++ b/code/api/src/Migrations/20220529190359_ApiAccessTokens.cs @@ -0,0 +1,48 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class ApiAccessTokens : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "api_access_tokens", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + expiry_date = table.Column(type: "timestamp with time zone", nullable: false), + allow_read = table.Column(type: "boolean", nullable: false), + allow_create = table.Column(type: "boolean", nullable: false), + allow_update = table.Column(type: "boolean", nullable: false), + allow_delete = table.Column(type: "boolean", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_api_access_tokens", x => x.id); + table.ForeignKey( + name: "fk_api_access_tokens_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateIndex( + name: "ix_api_access_tokens_user_id", + table: "api_access_tokens", + column: "user_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "api_access_tokens"); + } + } +} diff --git a/code/api/src/Migrations/20220530174741_Tenants.Designer.cs b/code/api/src/Migrations/20220530174741_Tenants.Designer.cs new file mode 100644 index 0000000..aadbec9 --- /dev/null +++ b/code/api/src/Migrations/20220530174741_Tenants.Designer.cs @@ -0,0 +1,710 @@ +// +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20220530174741_Tenants")] + partial class Tenants + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.Property("GithubId") + .HasColumnType("text") + .HasColumnName("github_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("RefreshToken") + .HasColumnType("text") + .HasColumnName("refresh_token"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("GithubId") + .HasName("pk_github_user_mappings"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_github_user_mappings_user_id"); + + b.ToTable("github_user_mappings", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TenantId1") + .HasColumnType("uuid") + .HasColumnName("tenant_id1"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.HasIndex("CreatedById") + .HasDatabaseName("ix_tenants_created_by_id"); + + b.HasIndex("DeletedById") + .HasDatabaseName("ix_tenants_deleted_by_id"); + + b.HasIndex("ModifiedById") + .HasDatabaseName("ix_tenants_modified_by_id"); + + b.HasIndex("TenantId1") + .HasDatabaseName("ix_tenants_tenant_id1"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tenants_user_id"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.HasIndex("CreatedById") + .HasDatabaseName("ix_time_categories_created_by_id"); + + b.HasIndex("DeletedById") + .HasDatabaseName("ix_time_categories_deleted_by_id"); + + b.HasIndex("ModifiedById") + .HasDatabaseName("ix_time_categories_modified_by_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_time_categories_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_categories_user_id"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.HasIndex("CreatedById") + .HasDatabaseName("ix_time_entries_created_by_id"); + + b.HasIndex("DeletedById") + .HasDatabaseName("ix_time_entries_deleted_by_id"); + + b.HasIndex("ModifiedById") + .HasDatabaseName("ix_time_entries_modified_by_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_time_entries_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_entries_user_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("CreatedById") + .HasDatabaseName("ix_time_labels_created_by_id"); + + b.HasIndex("DeletedById") + .HasDatabaseName("ix_time_labels_deleted_by_id"); + + b.HasIndex("ModifiedById") + .HasDatabaseName("ix_time_labels_modified_by_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_time_labels_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_labels_user_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.Property("EntriesId") + .HasColumnType("uuid") + .HasColumnName("entries_id"); + + b.Property("LabelsId") + .HasColumnType("uuid") + .HasColumnName("labels_id"); + + b.HasKey("EntriesId", "LabelsId") + .HasName("pk_time_entry_time_label"); + + b.HasIndex("LabelsId") + .HasDatabaseName("ix_time_entry_time_label_labels_id"); + + b.ToTable("time_entry_time_label", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_github_user_mappings_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenants_users_created_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenants_users_deleted_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenants_users_modified_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId1") + .HasConstraintName("fk_tenants_tenants_tenant_id1"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenants_users_user_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_users_created_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_users_deleted_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_users_modified_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_users_user_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany("Entries") + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_users_created_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_users_deleted_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_users_modified_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_users_user_id"); + + b.Navigation("Category"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_users_created_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_users_deleted_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_users_modified_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_users_user_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("TimeEntryTimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany() + .HasForeignKey("EntriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_entries_entries_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeLabel", null) + .WithMany() + .HasForeignKey("LabelsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entry_time_label_time_labels_labels_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Navigation("Entries"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20220530174741_Tenants.cs b/code/api/src/Migrations/20220530174741_Tenants.cs new file mode 100644 index 0000000..ea02ddd --- /dev/null +++ b/code/api/src/Migrations/20220530174741_Tenants.cs @@ -0,0 +1,481 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class Tenants : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "created_by_id", + table: "time_labels", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "deleted_by_id", + table: "time_labels", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "modified_by_id", + table: "time_labels", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "tenant_id", + table: "time_labels", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "created_by_id", + table: "time_entries", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "deleted_by_id", + table: "time_entries", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "modified_by_id", + table: "time_entries", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "tenant_id", + table: "time_entries", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "created_by_id", + table: "time_categories", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "deleted_by_id", + table: "time_categories", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "modified_by_id", + table: "time_categories", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "tenant_id", + table: "time_categories", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.CreateTable( + name: "tenants", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + name = table.Column(type: "text", nullable: true), + description = table.Column(type: "text", nullable: true), + contact_email = table.Column(type: "text", nullable: true), + master_user_id = table.Column(type: "uuid", nullable: false), + master_user_password = table.Column(type: "text", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true), + user_id = table.Column(type: "uuid", nullable: false), + tenant_id = table.Column(type: "uuid", nullable: false), + tenant_id1 = table.Column(type: "uuid", nullable: true), + modified_by_id = table.Column(type: "uuid", nullable: false), + created_by_id = table.Column(type: "uuid", nullable: false), + deleted_by_id = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_tenants", x => x.id); + table.ForeignKey( + name: "fk_tenants_tenants_tenant_id1", + column: x => x.tenant_id1, + principalTable: "tenants", + principalColumn: "id"); + table.ForeignKey( + name: "fk_tenants_users_created_by_id", + column: x => x.created_by_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_tenants_users_deleted_by_id", + column: x => x.deleted_by_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_tenants_users_modified_by_id", + column: x => x.modified_by_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_tenants_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_created_by_id", + table: "time_labels", + column: "created_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_deleted_by_id", + table: "time_labels", + column: "deleted_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_modified_by_id", + table: "time_labels", + column: "modified_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_tenant_id", + table: "time_labels", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_created_by_id", + table: "time_entries", + column: "created_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_deleted_by_id", + table: "time_entries", + column: "deleted_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_modified_by_id", + table: "time_entries", + column: "modified_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_tenant_id", + table: "time_entries", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_created_by_id", + table: "time_categories", + column: "created_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_deleted_by_id", + table: "time_categories", + column: "deleted_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_modified_by_id", + table: "time_categories", + column: "modified_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_tenant_id", + table: "time_categories", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_tenants_created_by_id", + table: "tenants", + column: "created_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_tenants_deleted_by_id", + table: "tenants", + column: "deleted_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_tenants_modified_by_id", + table: "tenants", + column: "modified_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_tenants_tenant_id1", + table: "tenants", + column: "tenant_id1"); + + migrationBuilder.CreateIndex( + name: "ix_tenants_user_id", + table: "tenants", + column: "user_id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_tenants_tenant_id", + table: "time_categories", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_created_by_id", + table: "time_categories", + column: "created_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_deleted_by_id", + table: "time_categories", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_modified_by_id", + table: "time_categories", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_tenants_tenant_id", + table: "time_entries", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_created_by_id", + table: "time_entries", + column: "created_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_deleted_by_id", + table: "time_entries", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_modified_by_id", + table: "time_entries", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_tenants_tenant_id", + table: "time_labels", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_created_by_id", + table: "time_labels", + column: "created_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_deleted_by_id", + table: "time_labels", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_modified_by_id", + table: "time_labels", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_time_categories_tenants_tenant_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_created_by_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_deleted_by_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_modified_by_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_tenants_tenant_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_created_by_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_deleted_by_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_modified_by_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_tenants_tenant_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_created_by_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_deleted_by_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_modified_by_id", + table: "time_labels"); + + migrationBuilder.DropTable( + name: "tenants"); + + migrationBuilder.DropIndex( + name: "ix_time_labels_created_by_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_labels_deleted_by_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_labels_modified_by_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_labels_tenant_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_entries_created_by_id", + table: "time_entries"); + + migrationBuilder.DropIndex( + name: "ix_time_entries_deleted_by_id", + table: "time_entries"); + + migrationBuilder.DropIndex( + name: "ix_time_entries_modified_by_id", + table: "time_entries"); + + migrationBuilder.DropIndex( + name: "ix_time_entries_tenant_id", + table: "time_entries"); + + migrationBuilder.DropIndex( + name: "ix_time_categories_created_by_id", + table: "time_categories"); + + migrationBuilder.DropIndex( + name: "ix_time_categories_deleted_by_id", + table: "time_categories"); + + migrationBuilder.DropIndex( + name: "ix_time_categories_modified_by_id", + table: "time_categories"); + + migrationBuilder.DropIndex( + name: "ix_time_categories_tenant_id", + table: "time_categories"); + + migrationBuilder.DropColumn( + name: "created_by_id", + table: "time_labels"); + + migrationBuilder.DropColumn( + name: "deleted_by_id", + table: "time_labels"); + + migrationBuilder.DropColumn( + name: "modified_by_id", + table: "time_labels"); + + migrationBuilder.DropColumn( + name: "tenant_id", + table: "time_labels"); + + migrationBuilder.DropColumn( + name: "created_by_id", + table: "time_entries"); + + migrationBuilder.DropColumn( + name: "deleted_by_id", + table: "time_entries"); + + migrationBuilder.DropColumn( + name: "modified_by_id", + table: "time_entries"); + + migrationBuilder.DropColumn( + name: "tenant_id", + table: "time_entries"); + + migrationBuilder.DropColumn( + name: "created_by_id", + table: "time_categories"); + + migrationBuilder.DropColumn( + name: "deleted_by_id", + table: "time_categories"); + + migrationBuilder.DropColumn( + name: "modified_by_id", + table: "time_categories"); + + migrationBuilder.DropColumn( + name: "tenant_id", + table: "time_categories"); + } + } +} diff --git a/code/api/src/Migrations/20220530175322_RemoveUnusedNavs.Designer.cs b/code/api/src/Migrations/20220530175322_RemoveUnusedNavs.Designer.cs new file mode 100644 index 0000000..f87309f --- /dev/null +++ b/code/api/src/Migrations/20220530175322_RemoveUnusedNavs.Designer.cs @@ -0,0 +1,686 @@ +// +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20220530175322_RemoveUnusedNavs")] + partial class RemoveUnusedNavs + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.Property("GithubId") + .HasColumnType("text") + .HasColumnName("github_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("RefreshToken") + .HasColumnType("text") + .HasColumnName("refresh_token"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("GithubId") + .HasName("pk_github_user_mappings"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_github_user_mappings_user_id"); + + b.ToTable("github_user_mappings", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TenantId1") + .HasColumnType("uuid") + .HasColumnName("tenant_id1"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.HasIndex("CreatedById") + .HasDatabaseName("ix_tenants_created_by_id"); + + b.HasIndex("DeletedById") + .HasDatabaseName("ix_tenants_deleted_by_id"); + + b.HasIndex("ModifiedById") + .HasDatabaseName("ix_tenants_modified_by_id"); + + b.HasIndex("TenantId1") + .HasDatabaseName("ix_tenants_tenant_id1"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tenants_user_id"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.HasIndex("CreatedById") + .HasDatabaseName("ix_time_categories_created_by_id"); + + b.HasIndex("DeletedById") + .HasDatabaseName("ix_time_categories_deleted_by_id"); + + b.HasIndex("ModifiedById") + .HasDatabaseName("ix_time_categories_modified_by_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_time_categories_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_categories_user_id"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.HasIndex("CreatedById") + .HasDatabaseName("ix_time_entries_created_by_id"); + + b.HasIndex("DeletedById") + .HasDatabaseName("ix_time_entries_deleted_by_id"); + + b.HasIndex("ModifiedById") + .HasDatabaseName("ix_time_entries_modified_by_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_time_entries_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_entries_user_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TimeEntryId") + .HasColumnType("uuid") + .HasColumnName("time_entry_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("CreatedById") + .HasDatabaseName("ix_time_labels_created_by_id"); + + b.HasIndex("DeletedById") + .HasDatabaseName("ix_time_labels_deleted_by_id"); + + b.HasIndex("ModifiedById") + .HasDatabaseName("ix_time_labels_modified_by_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_time_labels_tenant_id"); + + b.HasIndex("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_labels_user_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_github_user_mappings_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenants_users_created_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenants_users_deleted_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenants_users_modified_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId1") + .HasConstraintName("fk_tenants_tenants_tenant_id1"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenants_users_user_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_users_created_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_users_deleted_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_users_modified_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_users_user_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_users_created_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_users_deleted_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_users_modified_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_users_user_id"); + + b.Navigation("Category"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_users_created_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_users_deleted_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_users_modified_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_users_user_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20220530175322_RemoveUnusedNavs.cs b/code/api/src/Migrations/20220530175322_RemoveUnusedNavs.cs new file mode 100644 index 0000000..36b3cf1 --- /dev/null +++ b/code/api/src/Migrations/20220530175322_RemoveUnusedNavs.cs @@ -0,0 +1,78 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class RemoveUnusedNavs : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "time_entry_time_label"); + + migrationBuilder.AddColumn( + name: "time_entry_id", + table: "time_labels", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_time_entry_id", + table: "time_labels", + column: "time_entry_id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_time_entries_time_entry_id", + table: "time_labels", + column: "time_entry_id", + principalTable: "time_entries", + principalColumn: "id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_time_labels_time_entries_time_entry_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_labels_time_entry_id", + table: "time_labels"); + + migrationBuilder.DropColumn( + name: "time_entry_id", + table: "time_labels"); + + migrationBuilder.CreateTable( + name: "time_entry_time_label", + columns: table => new + { + entries_id = table.Column(type: "uuid", nullable: false), + labels_id = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_time_entry_time_label", x => new { x.entries_id, x.labels_id }); + table.ForeignKey( + name: "fk_time_entry_time_label_time_entries_entries_id", + column: x => x.entries_id, + principalTable: "time_entries", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_time_entry_time_label_time_labels_labels_id", + column: x => x.labels_id, + principalTable: "time_labels", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_time_entry_time_label_labels_id", + table: "time_entry_time_label", + column: "labels_id"); + } + } +} diff --git a/code/api/src/Migrations/20220602214238_NullableOptionalBaseFields.Designer.cs b/code/api/src/Migrations/20220602214238_NullableOptionalBaseFields.Designer.cs new file mode 100644 index 0000000..ddbe9d2 --- /dev/null +++ b/code/api/src/Migrations/20220602214238_NullableOptionalBaseFields.Designer.cs @@ -0,0 +1,656 @@ +// +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20220602214238_NullableOptionalBaseFields")] + partial class NullableOptionalBaseFields + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.Property("GithubId") + .HasColumnType("text") + .HasColumnName("github_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("RefreshToken") + .HasColumnType("text") + .HasColumnName("refresh_token"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("GithubId") + .HasName("pk_github_user_mappings"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_github_user_mappings_user_id"); + + b.ToTable("github_user_mappings", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TenantId1") + .HasColumnType("uuid") + .HasColumnName("tenant_id1"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.HasIndex("CreatedById") + .HasDatabaseName("ix_tenants_created_by_id"); + + b.HasIndex("DeletedById") + .HasDatabaseName("ix_tenants_deleted_by_id"); + + b.HasIndex("ModifiedById") + .HasDatabaseName("ix_tenants_modified_by_id"); + + b.HasIndex("TenantId1") + .HasDatabaseName("ix_tenants_tenant_id1"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tenants_user_id"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.HasIndex("CreatedById") + .HasDatabaseName("ix_time_categories_created_by_id"); + + b.HasIndex("DeletedById") + .HasDatabaseName("ix_time_categories_deleted_by_id"); + + b.HasIndex("ModifiedById") + .HasDatabaseName("ix_time_categories_modified_by_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_time_categories_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_categories_user_id"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.HasIndex("CreatedById") + .HasDatabaseName("ix_time_entries_created_by_id"); + + b.HasIndex("DeletedById") + .HasDatabaseName("ix_time_entries_deleted_by_id"); + + b.HasIndex("ModifiedById") + .HasDatabaseName("ix_time_entries_modified_by_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_time_entries_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_entries_user_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TimeEntryId") + .HasColumnType("uuid") + .HasColumnName("time_entry_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("CreatedById") + .HasDatabaseName("ix_time_labels_created_by_id"); + + b.HasIndex("DeletedById") + .HasDatabaseName("ix_time_labels_deleted_by_id"); + + b.HasIndex("ModifiedById") + .HasDatabaseName("ix_time_labels_modified_by_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_time_labels_tenant_id"); + + b.HasIndex("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_labels_user_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_github_user_mappings_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .HasConstraintName("fk_tenants_users_created_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById") + .HasConstraintName("fk_tenants_users_deleted_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById") + .HasConstraintName("fk_tenants_users_modified_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId1") + .HasConstraintName("fk_tenants_tenants_tenant_id1"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenants_users_user_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .HasConstraintName("fk_time_categories_users_created_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById") + .HasConstraintName("fk_time_categories_users_deleted_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById") + .HasConstraintName("fk_time_categories_users_modified_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_time_categories_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_categories_users_user_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .HasConstraintName("fk_time_entries_users_created_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById") + .HasConstraintName("fk_time_entries_users_deleted_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById") + .HasConstraintName("fk_time_entries_users_modified_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_time_entries_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_entries_users_user_id"); + + b.Navigation("Category"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .HasConstraintName("fk_time_labels_users_created_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "DeletedBy") + .WithMany() + .HasForeignKey("DeletedById") + .HasConstraintName("fk_time_labels_users_deleted_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ModifiedBy") + .WithMany() + .HasForeignKey("ModifiedById") + .HasConstraintName("fk_time_labels_users_modified_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "Tenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_time_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_time_labels_users_user_id"); + + b.Navigation("CreatedBy"); + + b.Navigation("DeletedBy"); + + b.Navigation("ModifiedBy"); + + b.Navigation("Tenant"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20220602214238_NullableOptionalBaseFields.cs b/code/api/src/Migrations/20220602214238_NullableOptionalBaseFields.cs new file mode 100644 index 0000000..eebab5c --- /dev/null +++ b/code/api/src/Migrations/20220602214238_NullableOptionalBaseFields.cs @@ -0,0 +1,649 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class NullableOptionalBaseFields : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_tenants_users_created_by_id", + table: "tenants"); + + migrationBuilder.DropForeignKey( + name: "fk_tenants_users_deleted_by_id", + table: "tenants"); + + migrationBuilder.DropForeignKey( + name: "fk_tenants_users_modified_by_id", + table: "tenants"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_tenants_tenant_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_created_by_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_deleted_by_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_modified_by_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_tenants_tenant_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_created_by_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_deleted_by_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_modified_by_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_tenants_tenant_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_created_by_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_deleted_by_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_modified_by_id", + table: "time_labels"); + + migrationBuilder.AlterColumn( + name: "tenant_id", + table: "time_labels", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "modified_by_id", + table: "time_labels", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "deleted_by_id", + table: "time_labels", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "created_by_id", + table: "time_labels", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "tenant_id", + table: "time_entries", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "modified_by_id", + table: "time_entries", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "deleted_by_id", + table: "time_entries", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "created_by_id", + table: "time_entries", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "tenant_id", + table: "time_categories", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "modified_by_id", + table: "time_categories", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "deleted_by_id", + table: "time_categories", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "created_by_id", + table: "time_categories", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "tenant_id", + table: "tenants", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "modified_by_id", + table: "tenants", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "deleted_by_id", + table: "tenants", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AlterColumn( + name: "created_by_id", + table: "tenants", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddForeignKey( + name: "fk_tenants_users_created_by_id", + table: "tenants", + column: "created_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_tenants_users_deleted_by_id", + table: "tenants", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_tenants_users_modified_by_id", + table: "tenants", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_tenants_tenant_id", + table: "time_categories", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_created_by_id", + table: "time_categories", + column: "created_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_deleted_by_id", + table: "time_categories", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_modified_by_id", + table: "time_categories", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_tenants_tenant_id", + table: "time_entries", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_created_by_id", + table: "time_entries", + column: "created_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_deleted_by_id", + table: "time_entries", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_modified_by_id", + table: "time_entries", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_tenants_tenant_id", + table: "time_labels", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_created_by_id", + table: "time_labels", + column: "created_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_deleted_by_id", + table: "time_labels", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_modified_by_id", + table: "time_labels", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_tenants_users_created_by_id", + table: "tenants"); + + migrationBuilder.DropForeignKey( + name: "fk_tenants_users_deleted_by_id", + table: "tenants"); + + migrationBuilder.DropForeignKey( + name: "fk_tenants_users_modified_by_id", + table: "tenants"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_tenants_tenant_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_created_by_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_deleted_by_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_modified_by_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_tenants_tenant_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_created_by_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_deleted_by_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_modified_by_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_tenants_tenant_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_created_by_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_deleted_by_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_modified_by_id", + table: "time_labels"); + + migrationBuilder.AlterColumn( + name: "tenant_id", + table: "time_labels", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "modified_by_id", + table: "time_labels", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "deleted_by_id", + table: "time_labels", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "created_by_id", + table: "time_labels", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "tenant_id", + table: "time_entries", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "modified_by_id", + table: "time_entries", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "deleted_by_id", + table: "time_entries", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "created_by_id", + table: "time_entries", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "tenant_id", + table: "time_categories", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "modified_by_id", + table: "time_categories", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "deleted_by_id", + table: "time_categories", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "created_by_id", + table: "time_categories", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "tenant_id", + table: "tenants", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "modified_by_id", + table: "tenants", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "deleted_by_id", + table: "tenants", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "created_by_id", + table: "tenants", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AddForeignKey( + name: "fk_tenants_users_created_by_id", + table: "tenants", + column: "created_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_tenants_users_deleted_by_id", + table: "tenants", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_tenants_users_modified_by_id", + table: "tenants", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_tenants_tenant_id", + table: "time_categories", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_created_by_id", + table: "time_categories", + column: "created_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_deleted_by_id", + table: "time_categories", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_modified_by_id", + table: "time_categories", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_tenants_tenant_id", + table: "time_entries", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_created_by_id", + table: "time_entries", + column: "created_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_deleted_by_id", + table: "time_entries", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_modified_by_id", + table: "time_entries", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_tenants_tenant_id", + table: "time_labels", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_created_by_id", + table: "time_labels", + column: "created_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_deleted_by_id", + table: "time_labels", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_modified_by_id", + table: "time_labels", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/code/api/src/Migrations/20220606232346_FleshOutNewModules.Designer.cs b/code/api/src/Migrations/20220606232346_FleshOutNewModules.Designer.cs new file mode 100644 index 0000000..5c4d3a3 --- /dev/null +++ b/code/api/src/Migrations/20220606232346_FleshOutNewModules.Designer.cs @@ -0,0 +1,510 @@ +// +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20220606232346_FleshOutNewModules")] + partial class FleshOutNewModules + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.Property("GithubId") + .HasColumnType("text") + .HasColumnName("github_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("RefreshToken") + .HasColumnType("text") + .HasColumnName("refresh_token"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("GithubId") + .HasName("pk_github_user_mappings"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_github_user_mappings_user_id"); + + b.ToTable("github_user_mappings", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TimeEntryId") + .HasColumnType("uuid") + .HasColumnName("time_entry_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.Property("TenantsId") + .HasColumnType("uuid") + .HasColumnName("tenants_id"); + + b.Property("UsersId") + .HasColumnType("uuid") + .HasColumnName("users_id"); + + b.HasKey("TenantsId", "UsersId") + .HasName("pk_tenant_user"); + + b.HasIndex("UsersId") + .HasDatabaseName("ix_tenant_user_users_id"); + + b.ToTable("tenant_user", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_github_user_mappings_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany() + .HasForeignKey("TenantsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_tenants_tenants_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_users_users_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20220606232346_FleshOutNewModules.cs b/code/api/src/Migrations/20220606232346_FleshOutNewModules.cs new file mode 100644 index 0000000..49a36b8 --- /dev/null +++ b/code/api/src/Migrations/20220606232346_FleshOutNewModules.cs @@ -0,0 +1,630 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class FleshOutNewModules : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_tenants_tenants_tenant_id1", + table: "tenants"); + + migrationBuilder.DropForeignKey( + name: "fk_tenants_users_created_by_id", + table: "tenants"); + + migrationBuilder.DropForeignKey( + name: "fk_tenants_users_deleted_by_id", + table: "tenants"); + + migrationBuilder.DropForeignKey( + name: "fk_tenants_users_modified_by_id", + table: "tenants"); + + migrationBuilder.DropForeignKey( + name: "fk_tenants_users_user_id", + table: "tenants"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_tenants_tenant_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_created_by_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_deleted_by_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_modified_by_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_user_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_tenants_tenant_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_created_by_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_deleted_by_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_modified_by_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_user_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_tenants_tenant_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_created_by_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_deleted_by_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_modified_by_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_user_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_labels_created_by_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_labels_deleted_by_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_labels_modified_by_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_labels_tenant_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_labels_user_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_entries_created_by_id", + table: "time_entries"); + + migrationBuilder.DropIndex( + name: "ix_time_entries_deleted_by_id", + table: "time_entries"); + + migrationBuilder.DropIndex( + name: "ix_time_entries_modified_by_id", + table: "time_entries"); + + migrationBuilder.DropIndex( + name: "ix_time_entries_tenant_id", + table: "time_entries"); + + migrationBuilder.DropIndex( + name: "ix_time_entries_user_id", + table: "time_entries"); + + migrationBuilder.DropIndex( + name: "ix_time_categories_created_by_id", + table: "time_categories"); + + migrationBuilder.DropIndex( + name: "ix_time_categories_deleted_by_id", + table: "time_categories"); + + migrationBuilder.DropIndex( + name: "ix_time_categories_modified_by_id", + table: "time_categories"); + + migrationBuilder.DropIndex( + name: "ix_time_categories_tenant_id", + table: "time_categories"); + + migrationBuilder.DropIndex( + name: "ix_time_categories_user_id", + table: "time_categories"); + + migrationBuilder.DropIndex( + name: "ix_tenants_created_by_id", + table: "tenants"); + + migrationBuilder.DropIndex( + name: "ix_tenants_deleted_by_id", + table: "tenants"); + + migrationBuilder.DropIndex( + name: "ix_tenants_modified_by_id", + table: "tenants"); + + migrationBuilder.DropIndex( + name: "ix_tenants_tenant_id1", + table: "tenants"); + + migrationBuilder.DropIndex( + name: "ix_tenants_user_id", + table: "tenants"); + + migrationBuilder.DropColumn( + name: "tenant_id1", + table: "tenants"); + + migrationBuilder.AddColumn( + name: "deleted", + table: "users", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "email", + table: "users", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "first_name", + table: "users", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "last_name", + table: "users", + type: "text", + nullable: true); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_labels", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddColumn( + name: "deleted", + table: "time_labels", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_entries", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddColumn( + name: "deleted", + table: "time_entries", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_categories", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddColumn( + name: "deleted", + table: "time_categories", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "tenants", + type: "uuid", + nullable: true, + oldClrType: typeof(Guid), + oldType: "uuid"); + + migrationBuilder.AddColumn( + name: "deleted", + table: "tenants", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.AddColumn( + name: "deleted", + table: "api_access_tokens", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateTable( + name: "tenant_user", + columns: table => new + { + tenants_id = table.Column(type: "uuid", nullable: false), + users_id = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_tenant_user", x => new { x.tenants_id, x.users_id }); + table.ForeignKey( + name: "fk_tenant_user_tenants_tenants_id", + column: x => x.tenants_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_tenant_user_users_users_id", + column: x => x.users_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_tenant_user_users_id", + table: "tenant_user", + column: "users_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "tenant_user"); + + migrationBuilder.DropColumn( + name: "deleted", + table: "users"); + + migrationBuilder.DropColumn( + name: "email", + table: "users"); + + migrationBuilder.DropColumn( + name: "first_name", + table: "users"); + + migrationBuilder.DropColumn( + name: "last_name", + table: "users"); + + migrationBuilder.DropColumn( + name: "deleted", + table: "time_labels"); + + migrationBuilder.DropColumn( + name: "deleted", + table: "time_entries"); + + migrationBuilder.DropColumn( + name: "deleted", + table: "time_categories"); + + migrationBuilder.DropColumn( + name: "deleted", + table: "tenants"); + + migrationBuilder.DropColumn( + name: "deleted", + table: "api_access_tokens"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_labels", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_entries", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "time_categories", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "tenants", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000"), + oldClrType: typeof(Guid), + oldType: "uuid", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "tenant_id1", + table: "tenants", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_created_by_id", + table: "time_labels", + column: "created_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_deleted_by_id", + table: "time_labels", + column: "deleted_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_modified_by_id", + table: "time_labels", + column: "modified_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_tenant_id", + table: "time_labels", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_user_id", + table: "time_labels", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_created_by_id", + table: "time_entries", + column: "created_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_deleted_by_id", + table: "time_entries", + column: "deleted_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_modified_by_id", + table: "time_entries", + column: "modified_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_tenant_id", + table: "time_entries", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_user_id", + table: "time_entries", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_created_by_id", + table: "time_categories", + column: "created_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_deleted_by_id", + table: "time_categories", + column: "deleted_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_modified_by_id", + table: "time_categories", + column: "modified_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_tenant_id", + table: "time_categories", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_user_id", + table: "time_categories", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_tenants_created_by_id", + table: "tenants", + column: "created_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_tenants_deleted_by_id", + table: "tenants", + column: "deleted_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_tenants_modified_by_id", + table: "tenants", + column: "modified_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_tenants_tenant_id1", + table: "tenants", + column: "tenant_id1"); + + migrationBuilder.CreateIndex( + name: "ix_tenants_user_id", + table: "tenants", + column: "user_id"); + + migrationBuilder.AddForeignKey( + name: "fk_tenants_tenants_tenant_id1", + table: "tenants", + column: "tenant_id1", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_tenants_users_created_by_id", + table: "tenants", + column: "created_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_tenants_users_deleted_by_id", + table: "tenants", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_tenants_users_modified_by_id", + table: "tenants", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_tenants_users_user_id", + table: "tenants", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_tenants_tenant_id", + table: "time_categories", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_created_by_id", + table: "time_categories", + column: "created_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_deleted_by_id", + table: "time_categories", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_modified_by_id", + table: "time_categories", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_user_id", + table: "time_categories", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_tenants_tenant_id", + table: "time_entries", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_created_by_id", + table: "time_entries", + column: "created_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_deleted_by_id", + table: "time_entries", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_modified_by_id", + table: "time_entries", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_user_id", + table: "time_entries", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_tenants_tenant_id", + table: "time_labels", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_created_by_id", + table: "time_labels", + column: "created_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_deleted_by_id", + table: "time_labels", + column: "deleted_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_modified_by_id", + table: "time_labels", + column: "modified_by_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_user_id", + table: "time_labels", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/code/api/src/Migrations/20220616170311_DataProtectionKeys.Designer.cs b/code/api/src/Migrations/20220616170311_DataProtectionKeys.Designer.cs new file mode 100644 index 0000000..b89e68d --- /dev/null +++ b/code/api/src/Migrations/20220616170311_DataProtectionKeys.Designer.cs @@ -0,0 +1,533 @@ +// +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20220616170311_DataProtectionKeys")] + partial class DataProtectionKeys + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.6") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.Property("GithubId") + .HasColumnType("text") + .HasColumnName("github_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("RefreshToken") + .HasColumnType("text") + .HasColumnName("refresh_token"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("GithubId") + .HasName("pk_github_user_mappings"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_github_user_mappings_user_id"); + + b.ToTable("github_user_mappings", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TimeEntryId") + .HasColumnType("uuid") + .HasColumnName("time_entry_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.Property("TenantsId") + .HasColumnType("uuid") + .HasColumnName("tenants_id"); + + b.Property("UsersId") + .HasColumnType("uuid") + .HasColumnName("users_id"); + + b.HasKey("TenantsId", "UsersId") + .HasName("pk_tenant_user"); + + b.HasIndex("UsersId") + .HasDatabaseName("ix_tenant_user_users_id"); + + b.ToTable("tenant_user", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.GithubUserMapping", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_github_user_mappings_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany() + .HasForeignKey("TenantsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_tenants_tenants_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_users_users_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20220616170311_DataProtectionKeys.cs b/code/api/src/Migrations/20220616170311_DataProtectionKeys.cs new file mode 100644 index 0000000..bc3c673 --- /dev/null +++ b/code/api/src/Migrations/20220616170311_DataProtectionKeys.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class DataProtectionKeys : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "data_protection_keys", + columns: table => new + { + id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + friendly_name = table.Column(type: "text", nullable: true), + xml = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_data_protection_keys", x => x.id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "data_protection_keys"); + } + } +} diff --git a/code/api/src/Migrations/20220819203816_RemoveGithubUsers.Designer.cs b/code/api/src/Migrations/20220819203816_RemoveGithubUsers.Designer.cs new file mode 100644 index 0000000..0ba742c --- /dev/null +++ b/code/api/src/Migrations/20220819203816_RemoveGithubUsers.Designer.cs @@ -0,0 +1,496 @@ +// +using System; +using IOL.GreatOffice.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20220819203816_RemoveGithubUsers")] + partial class RemoveGithubUsers + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TimeEntryId") + .HasColumnType("uuid") + .HasColumnName("time_entry_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.Property("TenantsId") + .HasColumnType("uuid") + .HasColumnName("tenants_id"); + + b.Property("UsersId") + .HasColumnType("uuid") + .HasColumnName("users_id"); + + b.HasKey("TenantsId", "UsersId") + .HasName("pk_tenant_user"); + + b.HasIndex("UsersId") + .HasDatabaseName("ix_tenant_user_users_id"); + + b.ToTable("tenant_user", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany() + .HasForeignKey("TenantsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_tenants_tenants_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_users_users_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20220819203816_RemoveGithubUsers.cs b/code/api/src/Migrations/20220819203816_RemoveGithubUsers.cs new file mode 100644 index 0000000..d301f67 --- /dev/null +++ b/code/api/src/Migrations/20220819203816_RemoveGithubUsers.cs @@ -0,0 +1,43 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class RemoveGithubUsers : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "github_user_mappings"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "github_user_mappings", + columns: table => new + { + github_id = table.Column(type: "text", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + email = table.Column(type: "text", nullable: true), + refresh_token = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_github_user_mappings", x => x.github_id); + table.ForeignKey( + name: "fk_github_user_mappings_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateIndex( + name: "ix_github_user_mappings_user_id", + table: "github_user_mappings", + column: "user_id"); + } + } +} diff --git a/code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.Designer.cs b/code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.Designer.cs new file mode 100644 index 0000000..284ea89 --- /dev/null +++ b/code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.Designer.cs @@ -0,0 +1,1074 @@ +// +using System; +using IOL.GreatOffice.Api.Data.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20221030080515_InitialProjectAndCustomer")] + partial class InitialProjectAndCustomer + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("GroupsId") + .HasColumnType("uuid") + .HasColumnName("groups_id"); + + b.HasKey("CustomersId", "GroupsId") + .HasName("pk_customer_customer_group"); + + b.HasIndex("GroupsId") + .HasDatabaseName("ix_customer_customer_group_groups_id"); + + b.ToTable("customer_customer_group", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Address1") + .HasColumnType("text") + .HasColumnName("address1"); + + b.Property("Address2") + .HasColumnType("text") + .HasColumnName("address2"); + + b.Property("Country") + .HasColumnType("text") + .HasColumnName("country"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Currency") + .HasColumnType("text") + .HasColumnName("currency"); + + b.Property("CustomerNumber") + .HasColumnType("text") + .HasColumnName("customer_number"); + + b.Property("DefaultReference") + .HasColumnType("text") + .HasColumnName("default_reference"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("PostalCity") + .HasColumnType("text") + .HasColumnName("postal_city"); + + b.Property("PostalCode") + .HasColumnType("text") + .HasColumnName("postal_code"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_customers_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customers_user_id"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("WorkTitle") + .HasColumnType("text") + .HasColumnName("work_title"); + + b.HasKey("Id") + .HasName("pk_customer_contacts"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_contacts_customer_id"); + + b.ToTable("customer_contacts", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_events"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_events_customer_id"); + + b.ToTable("customer_events", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_groups"); + + b.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_projects"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_project_labels"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_labels_project_id"); + + b.ToTable("project_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_project_member"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_member_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_member_user_id"); + + b.ToTable("project_member", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TimeEntryId") + .HasColumnType("uuid") + .HasColumnName("time_entry_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.Property("TenantsId") + .HasColumnType("uuid") + .HasColumnName("tenants_id"); + + b.Property("UsersId") + .HasColumnType("uuid") + .HasColumnName("users_id"); + + b.HasKey("TenantsId", "UsersId") + .HasName("pk_tenant_user"); + + b.HasIndex("UsersId") + .HasDatabaseName("ix_tenant_user_users_id"); + + b.ToTable("tenant_user", (string)null); + }); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.CustomerGroup", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customer_groups_groups_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", null) + .WithMany("Customers") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_customers_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Contacts") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_contacts_customers_customer_id"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Events") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_events_customers_customer_id"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Labels") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_labels_projects_project_id"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Members") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_member_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_member_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany() + .HasForeignKey("TenantsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_tenants_tenants_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_users_users_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Navigation("Contacts"); + + b.Navigation("Events"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Navigation("Customers"); + + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.cs b/code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.cs new file mode 100644 index 0000000..8a856cb --- /dev/null +++ b/code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.cs @@ -0,0 +1,304 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class InitialProjectAndCustomer : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "customer_groups", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + name = table.Column(type: "text", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted = table.Column(type: "boolean", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: true), + modified_by_id = table.Column(type: "uuid", nullable: true), + created_by_id = table.Column(type: "uuid", nullable: true), + deleted_by_id = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_customer_groups", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "projects", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + name = table.Column(type: "text", nullable: true), + description = table.Column(type: "text", nullable: true), + start = table.Column(type: "timestamp with time zone", nullable: true), + stop = table.Column(type: "timestamp with time zone", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted = table.Column(type: "boolean", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: true), + modified_by_id = table.Column(type: "uuid", nullable: true), + created_by_id = table.Column(type: "uuid", nullable: true), + deleted_by_id = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_projects", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "customers", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + customer_number = table.Column(type: "text", nullable: true), + name = table.Column(type: "text", nullable: true), + description = table.Column(type: "text", nullable: true), + address1 = table.Column(type: "text", nullable: true), + address2 = table.Column(type: "text", nullable: true), + postal_code = table.Column(type: "text", nullable: true), + postal_city = table.Column(type: "text", nullable: true), + country = table.Column(type: "text", nullable: true), + phone = table.Column(type: "text", nullable: true), + email = table.Column(type: "text", nullable: true), + vat_number = table.Column(type: "text", nullable: true), + org_number = table.Column(type: "text", nullable: true), + default_reference = table.Column(type: "text", nullable: true), + website = table.Column(type: "text", nullable: true), + currency = table.Column(type: "text", nullable: true), + project_id = table.Column(type: "uuid", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted = table.Column(type: "boolean", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: true), + modified_by_id = table.Column(type: "uuid", nullable: true), + created_by_id = table.Column(type: "uuid", nullable: true), + deleted_by_id = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_customers", x => x.id); + table.ForeignKey( + name: "fk_customers_projects_project_id", + column: x => x.project_id, + principalTable: "projects", + principalColumn: "id"); + table.ForeignKey( + name: "fk_customers_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "project_labels", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + value = table.Column(type: "text", nullable: true), + color = table.Column(type: "text", nullable: true), + project_id = table.Column(type: "uuid", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted = table.Column(type: "boolean", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: true), + modified_by_id = table.Column(type: "uuid", nullable: true), + created_by_id = table.Column(type: "uuid", nullable: true), + deleted_by_id = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_project_labels", x => x.id); + table.ForeignKey( + name: "fk_project_labels_projects_project_id", + column: x => x.project_id, + principalTable: "projects", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "project_member", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + project_id = table.Column(type: "uuid", nullable: true), + user_id = table.Column(type: "uuid", nullable: true), + role = table.Column(type: "integer", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_project_member", x => x.id); + table.ForeignKey( + name: "fk_project_member_projects_project_id", + column: x => x.project_id, + principalTable: "projects", + principalColumn: "id"); + table.ForeignKey( + name: "fk_project_member_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "customer_contacts", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + customer_id = table.Column(type: "uuid", nullable: true), + first_name = table.Column(type: "text", nullable: true), + last_name = table.Column(type: "text", nullable: true), + email = table.Column(type: "text", nullable: true), + phone = table.Column(type: "text", nullable: true), + work_title = table.Column(type: "text", nullable: true), + note = table.Column(type: "text", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted = table.Column(type: "boolean", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: true), + modified_by_id = table.Column(type: "uuid", nullable: true), + created_by_id = table.Column(type: "uuid", nullable: true), + deleted_by_id = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_customer_contacts", x => x.id); + table.ForeignKey( + name: "fk_customer_contacts_customers_customer_id", + column: x => x.customer_id, + principalTable: "customers", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "customer_customer_group", + columns: table => new + { + customers_id = table.Column(type: "uuid", nullable: false), + groups_id = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_customer_customer_group", x => new { x.customers_id, x.groups_id }); + table.ForeignKey( + name: "fk_customer_customer_group_customer_groups_groups_id", + column: x => x.groups_id, + principalTable: "customer_groups", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_customer_customer_group_customers_customers_id", + column: x => x.customers_id, + principalTable: "customers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "customer_events", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + customer_id = table.Column(type: "uuid", nullable: true), + title = table.Column(type: "text", nullable: true), + note = table.Column(type: "text", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted = table.Column(type: "boolean", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: true), + modified_by_id = table.Column(type: "uuid", nullable: true), + created_by_id = table.Column(type: "uuid", nullable: true), + deleted_by_id = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_customer_events", x => x.id); + table.ForeignKey( + name: "fk_customer_events_customers_customer_id", + column: x => x.customer_id, + principalTable: "customers", + principalColumn: "id"); + }); + + migrationBuilder.CreateIndex( + name: "ix_customer_contacts_customer_id", + table: "customer_contacts", + column: "customer_id"); + + migrationBuilder.CreateIndex( + name: "ix_customer_customer_group_groups_id", + table: "customer_customer_group", + column: "groups_id"); + + migrationBuilder.CreateIndex( + name: "ix_customer_events_customer_id", + table: "customer_events", + column: "customer_id"); + + migrationBuilder.CreateIndex( + name: "ix_customers_project_id", + table: "customers", + column: "project_id"); + + migrationBuilder.CreateIndex( + name: "ix_customers_user_id", + table: "customers", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_project_labels_project_id", + table: "project_labels", + column: "project_id"); + + migrationBuilder.CreateIndex( + name: "ix_project_member_project_id", + table: "project_member", + column: "project_id"); + + migrationBuilder.CreateIndex( + name: "ix_project_member_user_id", + table: "project_member", + column: "user_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "customer_contacts"); + + migrationBuilder.DropTable( + name: "customer_customer_group"); + + migrationBuilder.DropTable( + name: "customer_events"); + + migrationBuilder.DropTable( + name: "project_labels"); + + migrationBuilder.DropTable( + name: "project_member"); + + migrationBuilder.DropTable( + name: "customer_groups"); + + migrationBuilder.DropTable( + name: "customers"); + + migrationBuilder.DropTable( + name: "projects"); + } + } +} diff --git a/code/api/src/Migrations/20221030081459_DeletedAt.Designer.cs b/code/api/src/Migrations/20221030081459_DeletedAt.Designer.cs new file mode 100644 index 0000000..78f9454 --- /dev/null +++ b/code/api/src/Migrations/20221030081459_DeletedAt.Designer.cs @@ -0,0 +1,1126 @@ +// +using System; +using IOL.GreatOffice.Api.Data.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20221030081459_DeletedAt")] + partial class DeletedAt + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("GroupsId") + .HasColumnType("uuid") + .HasColumnName("groups_id"); + + b.HasKey("CustomersId", "GroupsId") + .HasName("pk_customer_customer_group"); + + b.HasIndex("GroupsId") + .HasDatabaseName("ix_customer_customer_group_groups_id"); + + b.ToTable("customer_customer_group", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Address1") + .HasColumnType("text") + .HasColumnName("address1"); + + b.Property("Address2") + .HasColumnType("text") + .HasColumnName("address2"); + + b.Property("Country") + .HasColumnType("text") + .HasColumnName("country"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Currency") + .HasColumnType("text") + .HasColumnName("currency"); + + b.Property("CustomerNumber") + .HasColumnType("text") + .HasColumnName("customer_number"); + + b.Property("DefaultReference") + .HasColumnType("text") + .HasColumnName("default_reference"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("PostalCity") + .HasColumnType("text") + .HasColumnName("postal_city"); + + b.Property("PostalCode") + .HasColumnType("text") + .HasColumnName("postal_code"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_customers_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customers_user_id"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("WorkTitle") + .HasColumnType("text") + .HasColumnName("work_title"); + + b.HasKey("Id") + .HasName("pk_customer_contacts"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_contacts_customer_id"); + + b.ToTable("customer_contacts", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_events"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_events_customer_id"); + + b.ToTable("customer_events", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_groups"); + + b.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_projects"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_project_labels"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_labels_project_id"); + + b.ToTable("project_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_project_member"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_member_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_member_user_id"); + + b.ToTable("project_member", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TimeEntryId") + .HasColumnType("uuid") + .HasColumnName("time_entry_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.Property("TenantsId") + .HasColumnType("uuid") + .HasColumnName("tenants_id"); + + b.Property("UsersId") + .HasColumnType("uuid") + .HasColumnName("users_id"); + + b.HasKey("TenantsId", "UsersId") + .HasName("pk_tenant_user"); + + b.HasIndex("UsersId") + .HasDatabaseName("ix_tenant_user_users_id"); + + b.ToTable("tenant_user", (string)null); + }); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.CustomerGroup", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customer_groups_groups_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", null) + .WithMany("Customers") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_customers_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Contacts") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_contacts_customers_customer_id"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Events") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_events_customers_customer_id"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Labels") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_labels_projects_project_id"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Members") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_member_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_member_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany() + .HasForeignKey("TenantsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_tenants_tenants_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_users_users_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Navigation("Contacts"); + + b.Navigation("Events"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Navigation("Customers"); + + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221030081459_DeletedAt.cs b/code/api/src/Migrations/20221030081459_DeletedAt.cs new file mode 100644 index 0000000..8132a6b --- /dev/null +++ b/code/api/src/Migrations/20221030081459_DeletedAt.cs @@ -0,0 +1,146 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class DeletedAt : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "deleted_at", + table: "users", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "deleted_at", + table: "time_labels", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "deleted_at", + table: "time_entries", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "deleted_at", + table: "time_categories", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "deleted_at", + table: "tenants", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "deleted_at", + table: "projects", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "deleted_at", + table: "project_member", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "deleted_at", + table: "project_labels", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "deleted_at", + table: "customers", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "deleted_at", + table: "customer_groups", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "deleted_at", + table: "customer_events", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "deleted_at", + table: "customer_contacts", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "deleted_at", + table: "api_access_tokens", + type: "timestamp with time zone", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "deleted_at", + table: "users"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "time_labels"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "time_entries"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "time_categories"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "tenants"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "projects"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "project_member"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "project_labels"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "customers"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "customer_groups"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "customer_events"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "customer_contacts"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "api_access_tokens"); + } + } +} diff --git a/code/api/src/Migrations/20221030084716_MinorChanges.Designer.cs b/code/api/src/Migrations/20221030084716_MinorChanges.Designer.cs new file mode 100644 index 0000000..1527d91 --- /dev/null +++ b/code/api/src/Migrations/20221030084716_MinorChanges.Designer.cs @@ -0,0 +1,1126 @@ +// +using System; +using IOL.GreatOffice.Api.Data.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20221030084716_MinorChanges")] + partial class MinorChanges + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("GroupsId") + .HasColumnType("uuid") + .HasColumnName("groups_id"); + + b.HasKey("CustomersId", "GroupsId") + .HasName("pk_customer_customer_group"); + + b.HasIndex("GroupsId") + .HasDatabaseName("ix_customer_customer_group_groups_id"); + + b.ToTable("customer_customer_group", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Address1") + .HasColumnType("text") + .HasColumnName("address1"); + + b.Property("Address2") + .HasColumnType("text") + .HasColumnName("address2"); + + b.Property("Country") + .HasColumnType("text") + .HasColumnName("country"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Currency") + .HasColumnType("text") + .HasColumnName("currency"); + + b.Property("CustomerNumber") + .HasColumnType("text") + .HasColumnName("customer_number"); + + b.Property("DefaultReference") + .HasColumnType("text") + .HasColumnName("default_reference"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("PostalCity") + .HasColumnType("text") + .HasColumnName("postal_city"); + + b.Property("PostalCode") + .HasColumnType("text") + .HasColumnName("postal_code"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_customers_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customers_user_id"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("WorkTitle") + .HasColumnType("text") + .HasColumnName("work_title"); + + b.HasKey("Id") + .HasName("pk_customer_contacts"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_contacts_customer_id"); + + b.ToTable("customer_contacts", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_events"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_events_customer_id"); + + b.ToTable("customer_events", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_groups"); + + b.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_projects"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_project_labels"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_labels_project_id"); + + b.ToTable("project_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_project_members"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_members_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_members_user_id"); + + b.ToTable("project_members", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TimeEntryId") + .HasColumnType("uuid") + .HasColumnName("time_entry_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.Property("TenantsId") + .HasColumnType("uuid") + .HasColumnName("tenants_id"); + + b.Property("UsersId") + .HasColumnType("uuid") + .HasColumnName("users_id"); + + b.HasKey("TenantsId", "UsersId") + .HasName("pk_tenant_user"); + + b.HasIndex("UsersId") + .HasDatabaseName("ix_tenant_user_users_id"); + + b.ToTable("tenant_user", (string)null); + }); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.CustomerGroup", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customer_groups_groups_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", null) + .WithMany("Customers") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_customers_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Contacts") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_contacts_customers_customer_id"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Events") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_events_customers_customer_id"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Labels") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_labels_projects_project_id"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Members") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_members_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_members_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany() + .HasForeignKey("TenantsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_tenants_tenants_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_users_users_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Navigation("Contacts"); + + b.Navigation("Events"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Navigation("Customers"); + + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221030084716_MinorChanges.cs b/code/api/src/Migrations/20221030084716_MinorChanges.cs new file mode 100644 index 0000000..e708caf --- /dev/null +++ b/code/api/src/Migrations/20221030084716_MinorChanges.cs @@ -0,0 +1,105 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class MinorChanges : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_project_member_projects_project_id", + table: "project_member"); + + migrationBuilder.DropForeignKey( + name: "fk_project_member_users_user_id", + table: "project_member"); + + migrationBuilder.DropPrimaryKey( + name: "pk_project_member", + table: "project_member"); + + migrationBuilder.RenameTable( + name: "project_member", + newName: "project_members"); + + migrationBuilder.RenameIndex( + name: "ix_project_member_user_id", + table: "project_members", + newName: "ix_project_members_user_id"); + + migrationBuilder.RenameIndex( + name: "ix_project_member_project_id", + table: "project_members", + newName: "ix_project_members_project_id"); + + migrationBuilder.AddPrimaryKey( + name: "pk_project_members", + table: "project_members", + column: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_project_members_projects_project_id", + table: "project_members", + column: "project_id", + principalTable: "projects", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_project_members_users_user_id", + table: "project_members", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_project_members_projects_project_id", + table: "project_members"); + + migrationBuilder.DropForeignKey( + name: "fk_project_members_users_user_id", + table: "project_members"); + + migrationBuilder.DropPrimaryKey( + name: "pk_project_members", + table: "project_members"); + + migrationBuilder.RenameTable( + name: "project_members", + newName: "project_member"); + + migrationBuilder.RenameIndex( + name: "ix_project_members_user_id", + table: "project_member", + newName: "ix_project_member_user_id"); + + migrationBuilder.RenameIndex( + name: "ix_project_members_project_id", + table: "project_member", + newName: "ix_project_member_project_id"); + + migrationBuilder.AddPrimaryKey( + name: "pk_project_member", + table: "project_member", + column: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_project_member_projects_project_id", + table: "project_member", + column: "project_id", + principalTable: "projects", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_project_member_users_user_id", + table: "project_member", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + } + } +} diff --git a/code/api/src/Migrations/20221030090557_MoreMinorChanges.Designer.cs b/code/api/src/Migrations/20221030090557_MoreMinorChanges.Designer.cs new file mode 100644 index 0000000..ff1dfb2 --- /dev/null +++ b/code/api/src/Migrations/20221030090557_MoreMinorChanges.Designer.cs @@ -0,0 +1,1148 @@ +// +using System; +using IOL.GreatOffice.Api.Data.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20221030090557_MoreMinorChanges")] + partial class MoreMinorChanges + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("GroupsId") + .HasColumnType("uuid") + .HasColumnName("groups_id"); + + b.HasKey("CustomersId", "GroupsId") + .HasName("pk_customer_customer_group"); + + b.HasIndex("GroupsId") + .HasDatabaseName("ix_customer_customer_group_groups_id"); + + b.ToTable("customer_customer_group", (string)null); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("ProjectsId") + .HasColumnType("uuid") + .HasColumnName("projects_id"); + + b.HasKey("CustomersId", "ProjectsId") + .HasName("pk_customer_project"); + + b.HasIndex("ProjectsId") + .HasDatabaseName("ix_customer_project_projects_id"); + + b.ToTable("customer_project", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Address1") + .HasColumnType("text") + .HasColumnName("address1"); + + b.Property("Address2") + .HasColumnType("text") + .HasColumnName("address2"); + + b.Property("Country") + .HasColumnType("text") + .HasColumnName("country"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Currency") + .HasColumnType("text") + .HasColumnName("currency"); + + b.Property("CustomerNumber") + .HasColumnType("text") + .HasColumnName("customer_number"); + + b.Property("DefaultReference") + .HasColumnType("text") + .HasColumnName("default_reference"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("PostalCity") + .HasColumnType("text") + .HasColumnName("postal_city"); + + b.Property("PostalCode") + .HasColumnType("text") + .HasColumnName("postal_code"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customers_user_id"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("WorkTitle") + .HasColumnType("text") + .HasColumnName("work_title"); + + b.HasKey("Id") + .HasName("pk_customer_contacts"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_contacts_customer_id"); + + b.ToTable("customer_contacts", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_events"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_events_customer_id"); + + b.ToTable("customer_events", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_groups"); + + b.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_projects"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_project_labels"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_labels_project_id"); + + b.ToTable("project_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_project_members"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_members_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_members_user_id"); + + b.ToTable("project_members", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TimeEntryId") + .HasColumnType("uuid") + .HasColumnName("time_entry_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.Property("TenantsId") + .HasColumnType("uuid") + .HasColumnName("tenants_id"); + + b.Property("UsersId") + .HasColumnType("uuid") + .HasColumnName("users_id"); + + b.HasKey("TenantsId", "UsersId") + .HasName("pk_tenant_user"); + + b.HasIndex("UsersId") + .HasDatabaseName("ix_tenant_user_users_id"); + + b.ToTable("tenant_user", (string)null); + }); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.CustomerGroup", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customer_groups_groups_id"); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_projects_projects_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Contacts") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_contacts_customers_customer_id"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Events") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_events_customers_customer_id"); + + b.Navigation("Customer"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Labels") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_labels_projects_project_id"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Members") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_members_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_members_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany() + .HasForeignKey("TenantsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_tenants_tenants_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_users_users_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Navigation("Contacts"); + + b.Navigation("Events"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221030090557_MoreMinorChanges.cs b/code/api/src/Migrations/20221030090557_MoreMinorChanges.cs new file mode 100644 index 0000000..5ebd664 --- /dev/null +++ b/code/api/src/Migrations/20221030090557_MoreMinorChanges.cs @@ -0,0 +1,78 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class MoreMinorChanges : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_customers_projects_project_id", + table: "customers"); + + migrationBuilder.DropIndex( + name: "ix_customers_project_id", + table: "customers"); + + migrationBuilder.DropColumn( + name: "project_id", + table: "customers"); + + migrationBuilder.CreateTable( + name: "customer_project", + columns: table => new + { + customers_id = table.Column(type: "uuid", nullable: false), + projects_id = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_customer_project", x => new { x.customers_id, x.projects_id }); + table.ForeignKey( + name: "fk_customer_project_customers_customers_id", + column: x => x.customers_id, + principalTable: "customers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_customer_project_projects_projects_id", + column: x => x.projects_id, + principalTable: "projects", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_customer_project_projects_id", + table: "customer_project", + column: "projects_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "customer_project"); + + migrationBuilder.AddColumn( + name: "project_id", + table: "customers", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "ix_customers_project_id", + table: "customers", + column: "project_id"); + + migrationBuilder.AddForeignKey( + name: "fk_customers_projects_project_id", + table: "customers", + column: "project_id", + principalTable: "projects", + principalColumn: "id"); + } + } +} diff --git a/code/api/src/Migrations/20221031165813_TodoAndOwnerNavigations.Designer.cs b/code/api/src/Migrations/20221031165813_TodoAndOwnerNavigations.Designer.cs new file mode 100644 index 0000000..1909d36 --- /dev/null +++ b/code/api/src/Migrations/20221031165813_TodoAndOwnerNavigations.Designer.cs @@ -0,0 +1,1863 @@ +// +using System; +using IOL.GreatOffice.Api.Data.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20221031165813_TodoAndOwnerNavigations")] + partial class TodoAndOwnerNavigations + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("GroupsId") + .HasColumnType("uuid") + .HasColumnName("groups_id"); + + b.HasKey("CustomersId", "GroupsId") + .HasName("pk_customer_customer_group"); + + b.HasIndex("GroupsId") + .HasDatabaseName("ix_customer_customer_group_groups_id"); + + b.ToTable("customer_customer_group", (string)null); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("ProjectsId") + .HasColumnType("uuid") + .HasColumnName("projects_id"); + + b.HasKey("CustomersId", "ProjectsId") + .HasName("pk_customer_project"); + + b.HasIndex("ProjectsId") + .HasDatabaseName("ix_customer_project_projects_id"); + + b.ToTable("customer_project", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Address1") + .HasColumnType("text") + .HasColumnName("address1"); + + b.Property("Address2") + .HasColumnType("text") + .HasColumnName("address2"); + + b.Property("Country") + .HasColumnType("text") + .HasColumnName("country"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Currency") + .HasColumnType("text") + .HasColumnName("currency"); + + b.Property("CustomerNumber") + .HasColumnType("text") + .HasColumnName("customer_number"); + + b.Property("DefaultReference") + .HasColumnType("text") + .HasColumnName("default_reference"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + b.Property("OwnerId") + .HasColumnType("uuid") + .HasColumnName("owner_id"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("PostalCity") + .HasColumnType("text") + .HasColumnName("postal_city"); + + b.Property("PostalCode") + .HasColumnType("text") + .HasColumnName("postal_code"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("OwnerId") + .HasDatabaseName("ix_customers_owner_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customers_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customers_user_id"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("WorkTitle") + .HasColumnType("text") + .HasColumnName("work_title"); + + b.HasKey("Id") + .HasName("pk_customer_contacts"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_contacts_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_contacts_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_contacts_user_id"); + + b.ToTable("customer_contacts", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_events"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_events_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_events_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_events_user_id"); + + b.ToTable("customer_events", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_groups"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_groups_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_groups_user_id"); + + b.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_projects"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_projects_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_projects_user_id"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_project_labels"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_labels_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_project_labels_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_labels_user_id"); + + b.ToTable("project_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_project_members"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_members_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_members_user_id"); + + b.ToTable("project_members", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OwningTenantId") + .HasColumnType("uuid") + .HasColumnName("owning_tenant_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.HasIndex("OwningTenantId") + .HasDatabaseName("ix_tenants_owning_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tenants_user_id"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_categories"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_time_categories_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_categories_user_id"); + + b.ToTable("time_categories", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CategoryId") + .HasColumnType("uuid") + .HasColumnName("category_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_entries"); + + b.HasIndex("CategoryId") + .HasDatabaseName("ix_time_entries_category_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_time_entries_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_entries_user_id"); + + b.ToTable("time_entries", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TimeEntryId") + .HasColumnType("uuid") + .HasColumnName("time_entry_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_time_labels"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_time_labels_tenant_id"); + + b.HasIndex("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_time_labels_user_id"); + + b.ToTable("time_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AssignedToId") + .HasColumnType("uuid") + .HasColumnName("assigned_to_id"); + + b.Property("ClosedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("closed_at"); + + b.Property("ClosedById") + .HasColumnType("uuid") + .HasColumnName("closed_by_id"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("PublicId") + .HasColumnType("text") + .HasColumnName("public_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todos"); + + b.HasIndex("AssignedToId") + .HasDatabaseName("ix_todos_assigned_to_id"); + + b.HasIndex("ClosedById") + .HasDatabaseName("ix_todos_closed_by_id"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todos_collection_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todos_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todos_user_id"); + + b.ToTable("todos", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Visibility") + .HasColumnType("integer") + .HasColumnName("visibility"); + + b.HasKey("Id") + .HasName("pk_todo_collections"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_todo_collections_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_collections_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collections_user_id"); + + b.ToTable("todo_collections", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CanBrowse") + .HasColumnType("boolean") + .HasColumnName("can_browse"); + + b.Property("CanComment") + .HasColumnType("boolean") + .HasColumnName("can_comment"); + + b.Property("CanEdit") + .HasColumnType("boolean") + .HasColumnName("can_edit"); + + b.Property("CanSubmit") + .HasColumnType("boolean") + .HasColumnName("can_submit"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_collection_access_controls"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todo_collection_access_controls_collection_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collection_access_controls_user_id"); + + b.ToTable("todo_collection_access_controls", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ClosingStatement") + .HasColumnType("integer") + .HasColumnName("closing_statement"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_todo_comments"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_comments_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_comments_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_comments_user_id"); + + b.ToTable("todo_comments", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_labels"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_labels_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_labels_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_labels_user_id"); + + b.ToTable("todo_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_users_tenant_id"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.CustomerGroup", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customer_groups_groups_id"); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_projects_projects_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .HasConstraintName("fk_customers_users_owner_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customers_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Contacts") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_contacts_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_contacts_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_contacts_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Events") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_events_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_events_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_events_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_groups_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_groups_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_projects_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_projects_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Labels") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_labels_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_project_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Members") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_members_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_members_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("OwningTenantId") + .HasConstraintName("fk_tenants_tenants_owning_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany("Tenants") + .HasForeignKey("UserId") + .HasConstraintName("fk_tenants_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_time_categories_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_time_categories_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_time_entries_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_time_entries_users_user_id"); + + b.Navigation("Category"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_time_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_time_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "AssignedTo") + .WithMany() + .HasForeignKey("AssignedToId") + .HasConstraintName("fk_todos_users_assigned_to_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ClosedBy") + .WithMany() + .HasForeignKey("ClosedById") + .HasConstraintName("fk_todos_users_closed_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany() + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_todos_todo_projects_collection_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todos_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todos_users_user_id"); + + b.Navigation("AssignedTo"); + + b.Navigation("ClosedBy"); + + b.Navigation("Collection"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .HasConstraintName("fk_todo_collections_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_collections_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collections_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany("AccessControls") + .HasForeignKey("CollectionId") + .HasConstraintName("fk_todo_collection_access_controls_todo_collections_collection"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collection_access_controls_users_user_id"); + + b.Navigation("Collection"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_comments_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Comments") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_comments_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_comments_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Labels") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_labels_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany("Users") + .HasForeignKey("TenantId") + .HasConstraintName("fk_users_tenants_tenant_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Navigation("Contacts"); + + b.Navigation("Events"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Navigation("Comments"); + + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Navigation("AccessControls"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Navigation("Tenants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221031165813_TodoAndOwnerNavigations.cs b/code/api/src/Migrations/20221031165813_TodoAndOwnerNavigations.cs new file mode 100644 index 0000000..334913e --- /dev/null +++ b/code/api/src/Migrations/20221031165813_TodoAndOwnerNavigations.cs @@ -0,0 +1,1092 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class TodoAndOwnerNavigations : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "tenant_user"); + + migrationBuilder.RenameColumn( + name: "modified_by_id", + table: "time_labels", + newName: "modified_by"); + + migrationBuilder.RenameColumn( + name: "deleted_by_id", + table: "time_labels", + newName: "deleted_by"); + + migrationBuilder.RenameColumn( + name: "created_by_id", + table: "time_labels", + newName: "created_by"); + + migrationBuilder.RenameColumn( + name: "modified_by_id", + table: "time_entries", + newName: "modified_by"); + + migrationBuilder.RenameColumn( + name: "deleted_by_id", + table: "time_entries", + newName: "deleted_by"); + + migrationBuilder.RenameColumn( + name: "created_by_id", + table: "time_entries", + newName: "created_by"); + + migrationBuilder.RenameColumn( + name: "modified_by_id", + table: "time_categories", + newName: "modified_by"); + + migrationBuilder.RenameColumn( + name: "deleted_by_id", + table: "time_categories", + newName: "deleted_by"); + + migrationBuilder.RenameColumn( + name: "created_by_id", + table: "time_categories", + newName: "created_by"); + + migrationBuilder.RenameColumn( + name: "modified_by_id", + table: "tenants", + newName: "owning_tenant_id"); + + migrationBuilder.RenameColumn( + name: "deleted_by_id", + table: "tenants", + newName: "modified_by"); + + migrationBuilder.RenameColumn( + name: "created_by_id", + table: "tenants", + newName: "deleted_by"); + + migrationBuilder.RenameColumn( + name: "modified_by_id", + table: "projects", + newName: "modified_by"); + + migrationBuilder.RenameColumn( + name: "deleted_by_id", + table: "projects", + newName: "deleted_by"); + + migrationBuilder.RenameColumn( + name: "created_by_id", + table: "projects", + newName: "created_by"); + + migrationBuilder.RenameColumn( + name: "modified_by_id", + table: "project_labels", + newName: "modified_by"); + + migrationBuilder.RenameColumn( + name: "deleted_by_id", + table: "project_labels", + newName: "deleted_by"); + + migrationBuilder.RenameColumn( + name: "created_by_id", + table: "project_labels", + newName: "created_by"); + + migrationBuilder.RenameColumn( + name: "modified_by_id", + table: "customers", + newName: "owner_id"); + + migrationBuilder.RenameColumn( + name: "deleted_by_id", + table: "customers", + newName: "modified_by"); + + migrationBuilder.RenameColumn( + name: "created_by_id", + table: "customers", + newName: "deleted_by"); + + migrationBuilder.RenameColumn( + name: "modified_by_id", + table: "customer_groups", + newName: "modified_by"); + + migrationBuilder.RenameColumn( + name: "deleted_by_id", + table: "customer_groups", + newName: "deleted_by"); + + migrationBuilder.RenameColumn( + name: "created_by_id", + table: "customer_groups", + newName: "created_by"); + + migrationBuilder.RenameColumn( + name: "modified_by_id", + table: "customer_events", + newName: "modified_by"); + + migrationBuilder.RenameColumn( + name: "deleted_by_id", + table: "customer_events", + newName: "deleted_by"); + + migrationBuilder.RenameColumn( + name: "created_by_id", + table: "customer_events", + newName: "created_by"); + + migrationBuilder.RenameColumn( + name: "modified_by_id", + table: "customer_contacts", + newName: "modified_by"); + + migrationBuilder.RenameColumn( + name: "deleted_by_id", + table: "customer_contacts", + newName: "deleted_by"); + + migrationBuilder.RenameColumn( + name: "created_by_id", + table: "customer_contacts", + newName: "created_by"); + + migrationBuilder.AddColumn( + name: "tenant_id", + table: "users", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "created_by", + table: "tenants", + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "created_by", + table: "customers", + type: "uuid", + nullable: true); + + migrationBuilder.CreateTable( + name: "todo_collections", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + name = table.Column(type: "text", nullable: true), + description = table.Column(type: "text", nullable: true), + visibility = table.Column(type: "integer", nullable: false), + project_id = table.Column(type: "uuid", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted = table.Column(type: "boolean", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: true), + modified_by = table.Column(type: "uuid", nullable: true), + created_by = table.Column(type: "uuid", nullable: true), + deleted_by = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_todo_collections", x => x.id); + table.ForeignKey( + name: "fk_todo_collections_projects_project_id", + column: x => x.project_id, + principalTable: "projects", + principalColumn: "id"); + table.ForeignKey( + name: "fk_todo_collections_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id"); + table.ForeignKey( + name: "fk_todo_collections_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "todo_collection_access_controls", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + collection_id = table.Column(type: "uuid", nullable: true), + user_id = table.Column(type: "uuid", nullable: true), + can_browse = table.Column(type: "boolean", nullable: false), + can_submit = table.Column(type: "boolean", nullable: false), + can_comment = table.Column(type: "boolean", nullable: false), + can_edit = table.Column(type: "boolean", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_todo_collection_access_controls", x => x.id); + table.ForeignKey( + name: "fk_todo_collection_access_controls_todo_collections_collection", + column: x => x.collection_id, + principalTable: "todo_collections", + principalColumn: "id"); + table.ForeignKey( + name: "fk_todo_collection_access_controls_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "todos", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + public_id = table.Column(type: "text", nullable: true), + assigned_to_id = table.Column(type: "uuid", nullable: true), + closed_by_id = table.Column(type: "uuid", nullable: true), + collection_id = table.Column(type: "uuid", nullable: false), + closed_at = table.Column(type: "timestamp with time zone", nullable: true), + title = table.Column(type: "text", nullable: true), + description = table.Column(type: "text", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted = table.Column(type: "boolean", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: true), + modified_by = table.Column(type: "uuid", nullable: true), + created_by = table.Column(type: "uuid", nullable: true), + deleted_by = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_todos", x => x.id); + table.ForeignKey( + name: "fk_todos_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id"); + table.ForeignKey( + name: "fk_todos_todo_projects_collection_id", + column: x => x.collection_id, + principalTable: "todo_collections", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_todos_users_assigned_to_id", + column: x => x.assigned_to_id, + principalTable: "users", + principalColumn: "id"); + table.ForeignKey( + name: "fk_todos_users_closed_by_id", + column: x => x.closed_by_id, + principalTable: "users", + principalColumn: "id"); + table.ForeignKey( + name: "fk_todos_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "todo_comments", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + value = table.Column(type: "text", nullable: true), + todo_id = table.Column(type: "uuid", nullable: true), + closing_statement = table.Column(type: "integer", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted = table.Column(type: "boolean", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: true), + modified_by = table.Column(type: "uuid", nullable: true), + created_by = table.Column(type: "uuid", nullable: true), + deleted_by = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_todo_comments", x => x.id); + table.ForeignKey( + name: "fk_todo_comments_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id"); + table.ForeignKey( + name: "fk_todo_comments_todos_todo_id", + column: x => x.todo_id, + principalTable: "todos", + principalColumn: "id"); + table.ForeignKey( + name: "fk_todo_comments_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "todo_labels", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + name = table.Column(type: "text", nullable: true), + color = table.Column(type: "text", nullable: true), + todo_id = table.Column(type: "uuid", nullable: true), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + modified_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted_at = table.Column(type: "timestamp with time zone", nullable: true), + deleted = table.Column(type: "boolean", nullable: false), + user_id = table.Column(type: "uuid", nullable: true), + tenant_id = table.Column(type: "uuid", nullable: true), + modified_by = table.Column(type: "uuid", nullable: true), + created_by = table.Column(type: "uuid", nullable: true), + deleted_by = table.Column(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_todo_labels", x => x.id); + table.ForeignKey( + name: "fk_todo_labels_tenants_tenant_id", + column: x => x.tenant_id, + principalTable: "tenants", + principalColumn: "id"); + table.ForeignKey( + name: "fk_todo_labels_todos_todo_id", + column: x => x.todo_id, + principalTable: "todos", + principalColumn: "id"); + table.ForeignKey( + name: "fk_todo_labels_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateIndex( + name: "ix_users_tenant_id", + table: "users", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_tenant_id", + table: "time_labels", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_user_id", + table: "time_labels", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_tenant_id", + table: "time_entries", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_user_id", + table: "time_entries", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_tenant_id", + table: "time_categories", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_user_id", + table: "time_categories", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_tenants_owning_tenant_id", + table: "tenants", + column: "owning_tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_tenants_user_id", + table: "tenants", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_projects_tenant_id", + table: "projects", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_projects_user_id", + table: "projects", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_project_labels_tenant_id", + table: "project_labels", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_project_labels_user_id", + table: "project_labels", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_customers_owner_id", + table: "customers", + column: "owner_id"); + + migrationBuilder.CreateIndex( + name: "ix_customers_tenant_id", + table: "customers", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_customer_groups_tenant_id", + table: "customer_groups", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_customer_groups_user_id", + table: "customer_groups", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_customer_events_tenant_id", + table: "customer_events", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_customer_events_user_id", + table: "customer_events", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_customer_contacts_tenant_id", + table: "customer_contacts", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_customer_contacts_user_id", + table: "customer_contacts", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_todo_collection_access_controls_collection_id", + table: "todo_collection_access_controls", + column: "collection_id"); + + migrationBuilder.CreateIndex( + name: "ix_todo_collection_access_controls_user_id", + table: "todo_collection_access_controls", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_todo_collections_project_id", + table: "todo_collections", + column: "project_id"); + + migrationBuilder.CreateIndex( + name: "ix_todo_collections_tenant_id", + table: "todo_collections", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_todo_collections_user_id", + table: "todo_collections", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_todo_comments_tenant_id", + table: "todo_comments", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_todo_comments_todo_id", + table: "todo_comments", + column: "todo_id"); + + migrationBuilder.CreateIndex( + name: "ix_todo_comments_user_id", + table: "todo_comments", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_todo_labels_tenant_id", + table: "todo_labels", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_todo_labels_todo_id", + table: "todo_labels", + column: "todo_id"); + + migrationBuilder.CreateIndex( + name: "ix_todo_labels_user_id", + table: "todo_labels", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_todos_assigned_to_id", + table: "todos", + column: "assigned_to_id"); + + migrationBuilder.CreateIndex( + name: "ix_todos_closed_by_id", + table: "todos", + column: "closed_by_id"); + + migrationBuilder.CreateIndex( + name: "ix_todos_collection_id", + table: "todos", + column: "collection_id"); + + migrationBuilder.CreateIndex( + name: "ix_todos_tenant_id", + table: "todos", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_todos_user_id", + table: "todos", + column: "user_id"); + + migrationBuilder.AddForeignKey( + name: "fk_customer_contacts_tenants_tenant_id", + table: "customer_contacts", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_customer_contacts_users_user_id", + table: "customer_contacts", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_customer_events_tenants_tenant_id", + table: "customer_events", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_customer_events_users_user_id", + table: "customer_events", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_customer_groups_tenants_tenant_id", + table: "customer_groups", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_customer_groups_users_user_id", + table: "customer_groups", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_customers_tenants_tenant_id", + table: "customers", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_customers_users_owner_id", + table: "customers", + column: "owner_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_project_labels_tenants_tenant_id", + table: "project_labels", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_project_labels_users_user_id", + table: "project_labels", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_projects_tenants_tenant_id", + table: "projects", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_projects_users_user_id", + table: "projects", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_tenants_tenants_owning_tenant_id", + table: "tenants", + column: "owning_tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_tenants_users_user_id", + table: "tenants", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_tenants_tenant_id", + table: "time_categories", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_categories_users_user_id", + table: "time_categories", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_tenants_tenant_id", + table: "time_entries", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_entries_users_user_id", + table: "time_entries", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_tenants_tenant_id", + table: "time_labels", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_time_labels_users_user_id", + table: "time_labels", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_users_tenants_tenant_id", + table: "users", + column: "tenant_id", + principalTable: "tenants", + principalColumn: "id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_customer_contacts_tenants_tenant_id", + table: "customer_contacts"); + + migrationBuilder.DropForeignKey( + name: "fk_customer_contacts_users_user_id", + table: "customer_contacts"); + + migrationBuilder.DropForeignKey( + name: "fk_customer_events_tenants_tenant_id", + table: "customer_events"); + + migrationBuilder.DropForeignKey( + name: "fk_customer_events_users_user_id", + table: "customer_events"); + + migrationBuilder.DropForeignKey( + name: "fk_customer_groups_tenants_tenant_id", + table: "customer_groups"); + + migrationBuilder.DropForeignKey( + name: "fk_customer_groups_users_user_id", + table: "customer_groups"); + + migrationBuilder.DropForeignKey( + name: "fk_customers_tenants_tenant_id", + table: "customers"); + + migrationBuilder.DropForeignKey( + name: "fk_customers_users_owner_id", + table: "customers"); + + migrationBuilder.DropForeignKey( + name: "fk_project_labels_tenants_tenant_id", + table: "project_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_project_labels_users_user_id", + table: "project_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_projects_tenants_tenant_id", + table: "projects"); + + migrationBuilder.DropForeignKey( + name: "fk_projects_users_user_id", + table: "projects"); + + migrationBuilder.DropForeignKey( + name: "fk_tenants_tenants_owning_tenant_id", + table: "tenants"); + + migrationBuilder.DropForeignKey( + name: "fk_tenants_users_user_id", + table: "tenants"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_tenants_tenant_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_categories_users_user_id", + table: "time_categories"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_tenants_tenant_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_entries_users_user_id", + table: "time_entries"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_tenants_tenant_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_time_labels_users_user_id", + table: "time_labels"); + + migrationBuilder.DropForeignKey( + name: "fk_users_tenants_tenant_id", + table: "users"); + + migrationBuilder.DropTable( + name: "todo_collection_access_controls"); + + migrationBuilder.DropTable( + name: "todo_comments"); + + migrationBuilder.DropTable( + name: "todo_labels"); + + migrationBuilder.DropTable( + name: "todos"); + + migrationBuilder.DropTable( + name: "todo_collections"); + + migrationBuilder.DropIndex( + name: "ix_users_tenant_id", + table: "users"); + + migrationBuilder.DropIndex( + name: "ix_time_labels_tenant_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_labels_user_id", + table: "time_labels"); + + migrationBuilder.DropIndex( + name: "ix_time_entries_tenant_id", + table: "time_entries"); + + migrationBuilder.DropIndex( + name: "ix_time_entries_user_id", + table: "time_entries"); + + migrationBuilder.DropIndex( + name: "ix_time_categories_tenant_id", + table: "time_categories"); + + migrationBuilder.DropIndex( + name: "ix_time_categories_user_id", + table: "time_categories"); + + migrationBuilder.DropIndex( + name: "ix_tenants_owning_tenant_id", + table: "tenants"); + + migrationBuilder.DropIndex( + name: "ix_tenants_user_id", + table: "tenants"); + + migrationBuilder.DropIndex( + name: "ix_projects_tenant_id", + table: "projects"); + + migrationBuilder.DropIndex( + name: "ix_projects_user_id", + table: "projects"); + + migrationBuilder.DropIndex( + name: "ix_project_labels_tenant_id", + table: "project_labels"); + + migrationBuilder.DropIndex( + name: "ix_project_labels_user_id", + table: "project_labels"); + + migrationBuilder.DropIndex( + name: "ix_customers_owner_id", + table: "customers"); + + migrationBuilder.DropIndex( + name: "ix_customers_tenant_id", + table: "customers"); + + migrationBuilder.DropIndex( + name: "ix_customer_groups_tenant_id", + table: "customer_groups"); + + migrationBuilder.DropIndex( + name: "ix_customer_groups_user_id", + table: "customer_groups"); + + migrationBuilder.DropIndex( + name: "ix_customer_events_tenant_id", + table: "customer_events"); + + migrationBuilder.DropIndex( + name: "ix_customer_events_user_id", + table: "customer_events"); + + migrationBuilder.DropIndex( + name: "ix_customer_contacts_tenant_id", + table: "customer_contacts"); + + migrationBuilder.DropIndex( + name: "ix_customer_contacts_user_id", + table: "customer_contacts"); + + migrationBuilder.DropColumn( + name: "tenant_id", + table: "users"); + + migrationBuilder.DropColumn( + name: "created_by", + table: "tenants"); + + migrationBuilder.DropColumn( + name: "created_by", + table: "customers"); + + migrationBuilder.RenameColumn( + name: "modified_by", + table: "time_labels", + newName: "modified_by_id"); + + migrationBuilder.RenameColumn( + name: "deleted_by", + table: "time_labels", + newName: "deleted_by_id"); + + migrationBuilder.RenameColumn( + name: "created_by", + table: "time_labels", + newName: "created_by_id"); + + migrationBuilder.RenameColumn( + name: "modified_by", + table: "time_entries", + newName: "modified_by_id"); + + migrationBuilder.RenameColumn( + name: "deleted_by", + table: "time_entries", + newName: "deleted_by_id"); + + migrationBuilder.RenameColumn( + name: "created_by", + table: "time_entries", + newName: "created_by_id"); + + migrationBuilder.RenameColumn( + name: "modified_by", + table: "time_categories", + newName: "modified_by_id"); + + migrationBuilder.RenameColumn( + name: "deleted_by", + table: "time_categories", + newName: "deleted_by_id"); + + migrationBuilder.RenameColumn( + name: "created_by", + table: "time_categories", + newName: "created_by_id"); + + migrationBuilder.RenameColumn( + name: "owning_tenant_id", + table: "tenants", + newName: "modified_by_id"); + + migrationBuilder.RenameColumn( + name: "modified_by", + table: "tenants", + newName: "deleted_by_id"); + + migrationBuilder.RenameColumn( + name: "deleted_by", + table: "tenants", + newName: "created_by_id"); + + migrationBuilder.RenameColumn( + name: "modified_by", + table: "projects", + newName: "modified_by_id"); + + migrationBuilder.RenameColumn( + name: "deleted_by", + table: "projects", + newName: "deleted_by_id"); + + migrationBuilder.RenameColumn( + name: "created_by", + table: "projects", + newName: "created_by_id"); + + migrationBuilder.RenameColumn( + name: "modified_by", + table: "project_labels", + newName: "modified_by_id"); + + migrationBuilder.RenameColumn( + name: "deleted_by", + table: "project_labels", + newName: "deleted_by_id"); + + migrationBuilder.RenameColumn( + name: "created_by", + table: "project_labels", + newName: "created_by_id"); + + migrationBuilder.RenameColumn( + name: "owner_id", + table: "customers", + newName: "modified_by_id"); + + migrationBuilder.RenameColumn( + name: "modified_by", + table: "customers", + newName: "deleted_by_id"); + + migrationBuilder.RenameColumn( + name: "deleted_by", + table: "customers", + newName: "created_by_id"); + + migrationBuilder.RenameColumn( + name: "modified_by", + table: "customer_groups", + newName: "modified_by_id"); + + migrationBuilder.RenameColumn( + name: "deleted_by", + table: "customer_groups", + newName: "deleted_by_id"); + + migrationBuilder.RenameColumn( + name: "created_by", + table: "customer_groups", + newName: "created_by_id"); + + migrationBuilder.RenameColumn( + name: "modified_by", + table: "customer_events", + newName: "modified_by_id"); + + migrationBuilder.RenameColumn( + name: "deleted_by", + table: "customer_events", + newName: "deleted_by_id"); + + migrationBuilder.RenameColumn( + name: "created_by", + table: "customer_events", + newName: "created_by_id"); + + migrationBuilder.RenameColumn( + name: "modified_by", + table: "customer_contacts", + newName: "modified_by_id"); + + migrationBuilder.RenameColumn( + name: "deleted_by", + table: "customer_contacts", + newName: "deleted_by_id"); + + migrationBuilder.RenameColumn( + name: "created_by", + table: "customer_contacts", + newName: "created_by_id"); + + migrationBuilder.CreateTable( + name: "tenant_user", + columns: table => new + { + tenants_id = table.Column(type: "uuid", nullable: false), + users_id = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_tenant_user", x => new { x.tenants_id, x.users_id }); + table.ForeignKey( + name: "fk_tenant_user_tenants_tenants_id", + column: x => x.tenants_id, + principalTable: "tenants", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_tenant_user_users_users_id", + column: x => x.users_id, + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_tenant_user_users_id", + table: "tenant_user", + column: "users_id"); + } + } +} diff --git a/code/api/src/Migrations/20221114034213_RemoveTimeTracker.Designer.cs b/code/api/src/Migrations/20221114034213_RemoveTimeTracker.Designer.cs new file mode 100644 index 0000000..c649575 --- /dev/null +++ b/code/api/src/Migrations/20221114034213_RemoveTimeTracker.Designer.cs @@ -0,0 +1,1589 @@ +// +using System; +using IOL.GreatOffice.Api.Data.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20221114034213_RemoveTimeTracker")] + partial class RemoveTimeTracker + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("GroupsId") + .HasColumnType("uuid") + .HasColumnName("groups_id"); + + b.HasKey("CustomersId", "GroupsId") + .HasName("pk_customer_customer_group"); + + b.HasIndex("GroupsId") + .HasDatabaseName("ix_customer_customer_group_groups_id"); + + b.ToTable("customer_customer_group", (string)null); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("ProjectsId") + .HasColumnType("uuid") + .HasColumnName("projects_id"); + + b.HasKey("CustomersId", "ProjectsId") + .HasName("pk_customer_project"); + + b.HasIndex("ProjectsId") + .HasDatabaseName("ix_customer_project_projects_id"); + + b.ToTable("customer_project", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Address1") + .HasColumnType("text") + .HasColumnName("address1"); + + b.Property("Address2") + .HasColumnType("text") + .HasColumnName("address2"); + + b.Property("Country") + .HasColumnType("text") + .HasColumnName("country"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Currency") + .HasColumnType("text") + .HasColumnName("currency"); + + b.Property("CustomerNumber") + .HasColumnType("text") + .HasColumnName("customer_number"); + + b.Property("DefaultReference") + .HasColumnType("text") + .HasColumnName("default_reference"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + b.Property("OwnerId") + .HasColumnType("uuid") + .HasColumnName("owner_id"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("PostalCity") + .HasColumnType("text") + .HasColumnName("postal_city"); + + b.Property("PostalCode") + .HasColumnType("text") + .HasColumnName("postal_code"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("OwnerId") + .HasDatabaseName("ix_customers_owner_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customers_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customers_user_id"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("WorkTitle") + .HasColumnType("text") + .HasColumnName("work_title"); + + b.HasKey("Id") + .HasName("pk_customer_contacts"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_contacts_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_contacts_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_contacts_user_id"); + + b.ToTable("customer_contacts", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_events"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_events_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_events_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_events_user_id"); + + b.ToTable("customer_events", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_groups"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_groups_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_groups_user_id"); + + b.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_projects"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_projects_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_projects_user_id"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_project_labels"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_labels_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_project_labels_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_labels_user_id"); + + b.ToTable("project_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_project_members"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_members_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_members_user_id"); + + b.ToTable("project_members", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OwningTenantId") + .HasColumnType("uuid") + .HasColumnName("owning_tenant_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.HasIndex("OwningTenantId") + .HasDatabaseName("ix_tenants_owning_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tenants_user_id"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AssignedToId") + .HasColumnType("uuid") + .HasColumnName("assigned_to_id"); + + b.Property("ClosedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("closed_at"); + + b.Property("ClosedById") + .HasColumnType("uuid") + .HasColumnName("closed_by_id"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("PublicId") + .HasColumnType("text") + .HasColumnName("public_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todos"); + + b.HasIndex("AssignedToId") + .HasDatabaseName("ix_todos_assigned_to_id"); + + b.HasIndex("ClosedById") + .HasDatabaseName("ix_todos_closed_by_id"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todos_collection_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todos_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todos_user_id"); + + b.ToTable("todos", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Visibility") + .HasColumnType("integer") + .HasColumnName("visibility"); + + b.HasKey("Id") + .HasName("pk_todo_collections"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_todo_collections_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_collections_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collections_user_id"); + + b.ToTable("todo_collections", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CanBrowse") + .HasColumnType("boolean") + .HasColumnName("can_browse"); + + b.Property("CanComment") + .HasColumnType("boolean") + .HasColumnName("can_comment"); + + b.Property("CanEdit") + .HasColumnType("boolean") + .HasColumnName("can_edit"); + + b.Property("CanSubmit") + .HasColumnType("boolean") + .HasColumnName("can_submit"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_collection_access_controls"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todo_collection_access_controls_collection_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collection_access_controls_user_id"); + + b.ToTable("todo_collection_access_controls", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ClosingStatement") + .HasColumnType("integer") + .HasColumnName("closing_statement"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_todo_comments"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_comments_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_comments_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_comments_user_id"); + + b.ToTable("todo_comments", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_labels"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_labels_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_labels_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_labels_user_id"); + + b.ToTable("todo_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_users_tenant_id"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.CustomerGroup", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customer_groups_groups_id"); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_projects_projects_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .HasConstraintName("fk_customers_users_owner_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customers_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Contacts") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_contacts_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_contacts_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_contacts_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Events") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_events_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_events_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_events_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_groups_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_groups_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_forgot_password_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_projects_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_projects_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Labels") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_labels_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_project_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Members") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_members_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_members_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("OwningTenantId") + .HasConstraintName("fk_tenants_tenants_owning_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany("Tenants") + .HasForeignKey("UserId") + .HasConstraintName("fk_tenants_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "AssignedTo") + .WithMany() + .HasForeignKey("AssignedToId") + .HasConstraintName("fk_todos_users_assigned_to_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ClosedBy") + .WithMany() + .HasForeignKey("ClosedById") + .HasConstraintName("fk_todos_users_closed_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany() + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_todos_todo_projects_collection_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todos_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todos_users_user_id"); + + b.Navigation("AssignedTo"); + + b.Navigation("ClosedBy"); + + b.Navigation("Collection"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .HasConstraintName("fk_todo_collections_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_collections_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collections_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany("AccessControls") + .HasForeignKey("CollectionId") + .HasConstraintName("fk_todo_collection_access_controls_todo_collections_collection"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collection_access_controls_users_user_id"); + + b.Navigation("Collection"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_comments_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Comments") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_comments_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_comments_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Labels") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_labels_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany("Users") + .HasForeignKey("TenantId") + .HasConstraintName("fk_users_tenants_tenant_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Navigation("Contacts"); + + b.Navigation("Events"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Navigation("Comments"); + + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Navigation("AccessControls"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Navigation("Tenants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221114034213_RemoveTimeTracker.cs b/code/api/src/Migrations/20221114034213_RemoveTimeTracker.cs new file mode 100644 index 0000000..70b2539 --- /dev/null +++ b/code/api/src/Migrations/20221114034213_RemoveTimeTracker.cs @@ -0,0 +1,177 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + /// + public partial class RemoveTimeTracker : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "time_labels"); + + migrationBuilder.DropTable( + name: "time_entries"); + + migrationBuilder.DropTable( + name: "time_categories"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "time_categories", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + tenantid = table.Column(name: "tenant_id", type: "uuid", nullable: true), + userid = table.Column(name: "user_id", type: "uuid", nullable: true), + color = table.Column(type: "text", nullable: true), + createdat = table.Column(name: "created_at", type: "timestamp with time zone", nullable: false), + createdby = table.Column(name: "created_by", type: "uuid", nullable: true), + deleted = table.Column(type: "boolean", nullable: false), + deletedat = table.Column(name: "deleted_at", type: "timestamp with time zone", nullable: true), + deletedby = table.Column(name: "deleted_by", type: "uuid", nullable: true), + modifiedat = table.Column(name: "modified_at", type: "timestamp with time zone", nullable: true), + modifiedby = table.Column(name: "modified_by", type: "uuid", nullable: true), + name = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_time_categories", x => x.id); + table.ForeignKey( + name: "fk_time_categories_tenants_tenant_id", + column: x => x.tenantid, + principalTable: "tenants", + principalColumn: "id"); + table.ForeignKey( + name: "fk_time_categories_users_user_id", + column: x => x.userid, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "time_entries", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + categoryid = table.Column(name: "category_id", type: "uuid", nullable: true), + tenantid = table.Column(name: "tenant_id", type: "uuid", nullable: true), + userid = table.Column(name: "user_id", type: "uuid", nullable: true), + createdat = table.Column(name: "created_at", type: "timestamp with time zone", nullable: false), + createdby = table.Column(name: "created_by", type: "uuid", nullable: true), + deleted = table.Column(type: "boolean", nullable: false), + deletedat = table.Column(name: "deleted_at", type: "timestamp with time zone", nullable: true), + deletedby = table.Column(name: "deleted_by", type: "uuid", nullable: true), + description = table.Column(type: "text", nullable: true), + modifiedat = table.Column(name: "modified_at", type: "timestamp with time zone", nullable: true), + modifiedby = table.Column(name: "modified_by", type: "uuid", nullable: true), + start = table.Column(type: "timestamp with time zone", nullable: false), + stop = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_time_entries", x => x.id); + table.ForeignKey( + name: "fk_time_entries_tenants_tenant_id", + column: x => x.tenantid, + principalTable: "tenants", + principalColumn: "id"); + table.ForeignKey( + name: "fk_time_entries_time_categories_category_id", + column: x => x.categoryid, + principalTable: "time_categories", + principalColumn: "id"); + table.ForeignKey( + name: "fk_time_entries_users_user_id", + column: x => x.userid, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "time_labels", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + tenantid = table.Column(name: "tenant_id", type: "uuid", nullable: true), + userid = table.Column(name: "user_id", type: "uuid", nullable: true), + color = table.Column(type: "text", nullable: true), + createdat = table.Column(name: "created_at", type: "timestamp with time zone", nullable: false), + createdby = table.Column(name: "created_by", type: "uuid", nullable: true), + deleted = table.Column(type: "boolean", nullable: false), + deletedat = table.Column(name: "deleted_at", type: "timestamp with time zone", nullable: true), + deletedby = table.Column(name: "deleted_by", type: "uuid", nullable: true), + modifiedat = table.Column(name: "modified_at", type: "timestamp with time zone", nullable: true), + modifiedby = table.Column(name: "modified_by", type: "uuid", nullable: true), + name = table.Column(type: "text", nullable: true), + timeentryid = table.Column(name: "time_entry_id", type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_time_labels", x => x.id); + table.ForeignKey( + name: "fk_time_labels_tenants_tenant_id", + column: x => x.tenantid, + principalTable: "tenants", + principalColumn: "id"); + table.ForeignKey( + name: "fk_time_labels_time_entries_time_entry_id", + column: x => x.timeentryid, + principalTable: "time_entries", + principalColumn: "id"); + table.ForeignKey( + name: "fk_time_labels_users_user_id", + column: x => x.userid, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_tenant_id", + table: "time_categories", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_categories_user_id", + table: "time_categories", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_category_id", + table: "time_entries", + column: "category_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_tenant_id", + table: "time_entries", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_entries_user_id", + table: "time_entries", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_tenant_id", + table: "time_labels", + column: "tenant_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_time_entry_id", + table: "time_labels", + column: "time_entry_id"); + + migrationBuilder.CreateIndex( + name: "ix_time_labels_user_id", + table: "time_labels", + column: "user_id"); + } + } +} diff --git a/code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.Designer.cs b/code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.Designer.cs new file mode 100644 index 0000000..feb9805 --- /dev/null +++ b/code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.Designer.cs @@ -0,0 +1,1589 @@ +// +using System; +using IOL.GreatOffice.Api.Data.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20221114035223_RenameForgotPasswordRequests")] + partial class RenameForgotPasswordRequests + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("GroupsId") + .HasColumnType("uuid") + .HasColumnName("groups_id"); + + b.HasKey("CustomersId", "GroupsId") + .HasName("pk_customer_customer_group"); + + b.HasIndex("GroupsId") + .HasDatabaseName("ix_customer_customer_group_groups_id"); + + b.ToTable("customer_customer_group", (string)null); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("ProjectsId") + .HasColumnType("uuid") + .HasColumnName("projects_id"); + + b.HasKey("CustomersId", "ProjectsId") + .HasName("pk_customer_project"); + + b.HasIndex("ProjectsId") + .HasDatabaseName("ix_customer_project_projects_id"); + + b.ToTable("customer_project", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Address1") + .HasColumnType("text") + .HasColumnName("address1"); + + b.Property("Address2") + .HasColumnType("text") + .HasColumnName("address2"); + + b.Property("Country") + .HasColumnType("text") + .HasColumnName("country"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Currency") + .HasColumnType("text") + .HasColumnName("currency"); + + b.Property("CustomerNumber") + .HasColumnType("text") + .HasColumnName("customer_number"); + + b.Property("DefaultReference") + .HasColumnType("text") + .HasColumnName("default_reference"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + b.Property("OwnerId") + .HasColumnType("uuid") + .HasColumnName("owner_id"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("PostalCity") + .HasColumnType("text") + .HasColumnName("postal_city"); + + b.Property("PostalCode") + .HasColumnType("text") + .HasColumnName("postal_code"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("OwnerId") + .HasDatabaseName("ix_customers_owner_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customers_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customers_user_id"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("WorkTitle") + .HasColumnType("text") + .HasColumnName("work_title"); + + b.HasKey("Id") + .HasName("pk_customer_contacts"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_contacts_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_contacts_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_contacts_user_id"); + + b.ToTable("customer_contacts", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_events"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_events_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_events_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_events_user_id"); + + b.ToTable("customer_events", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_groups"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_groups_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_groups_user_id"); + + b.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_password_reset_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_password_reset_requests_user_id"); + + b.ToTable("password_reset_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_projects"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_projects_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_projects_user_id"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_project_labels"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_labels_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_project_labels_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_labels_user_id"); + + b.ToTable("project_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_project_members"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_members_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_members_user_id"); + + b.ToTable("project_members", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OwningTenantId") + .HasColumnType("uuid") + .HasColumnName("owning_tenant_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.HasIndex("OwningTenantId") + .HasDatabaseName("ix_tenants_owning_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tenants_user_id"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AssignedToId") + .HasColumnType("uuid") + .HasColumnName("assigned_to_id"); + + b.Property("ClosedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("closed_at"); + + b.Property("ClosedById") + .HasColumnType("uuid") + .HasColumnName("closed_by_id"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("PublicId") + .HasColumnType("text") + .HasColumnName("public_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todos"); + + b.HasIndex("AssignedToId") + .HasDatabaseName("ix_todos_assigned_to_id"); + + b.HasIndex("ClosedById") + .HasDatabaseName("ix_todos_closed_by_id"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todos_collection_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todos_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todos_user_id"); + + b.ToTable("todos", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Visibility") + .HasColumnType("integer") + .HasColumnName("visibility"); + + b.HasKey("Id") + .HasName("pk_todo_collections"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_todo_collections_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_collections_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collections_user_id"); + + b.ToTable("todo_collections", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CanBrowse") + .HasColumnType("boolean") + .HasColumnName("can_browse"); + + b.Property("CanComment") + .HasColumnType("boolean") + .HasColumnName("can_comment"); + + b.Property("CanEdit") + .HasColumnType("boolean") + .HasColumnName("can_edit"); + + b.Property("CanSubmit") + .HasColumnType("boolean") + .HasColumnName("can_submit"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_collection_access_controls"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todo_collection_access_controls_collection_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collection_access_controls_user_id"); + + b.ToTable("todo_collection_access_controls", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ClosingStatement") + .HasColumnType("integer") + .HasColumnName("closing_statement"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_todo_comments"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_comments_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_comments_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_comments_user_id"); + + b.ToTable("todo_comments", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_labels"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_labels_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_labels_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_labels_user_id"); + + b.ToTable("todo_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_users_tenant_id"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.CustomerGroup", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customer_groups_groups_id"); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_projects_projects_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .HasConstraintName("fk_customers_users_owner_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customers_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Contacts") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_contacts_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_contacts_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_contacts_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Events") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_events_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_events_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_events_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_groups_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_groups_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_password_reset_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_projects_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_projects_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Labels") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_labels_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_project_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Members") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_members_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_members_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("OwningTenantId") + .HasConstraintName("fk_tenants_tenants_owning_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany("Tenants") + .HasForeignKey("UserId") + .HasConstraintName("fk_tenants_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "AssignedTo") + .WithMany() + .HasForeignKey("AssignedToId") + .HasConstraintName("fk_todos_users_assigned_to_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ClosedBy") + .WithMany() + .HasForeignKey("ClosedById") + .HasConstraintName("fk_todos_users_closed_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany() + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_todos_todo_projects_collection_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todos_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todos_users_user_id"); + + b.Navigation("AssignedTo"); + + b.Navigation("ClosedBy"); + + b.Navigation("Collection"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .HasConstraintName("fk_todo_collections_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_collections_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collections_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany("AccessControls") + .HasForeignKey("CollectionId") + .HasConstraintName("fk_todo_collection_access_controls_todo_collections_collection"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collection_access_controls_users_user_id"); + + b.Navigation("Collection"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_comments_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Comments") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_comments_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_comments_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Labels") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_labels_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany("Users") + .HasForeignKey("TenantId") + .HasConstraintName("fk_users_tenants_tenant_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Navigation("Contacts"); + + b.Navigation("Events"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Navigation("Comments"); + + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Navigation("AccessControls"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Navigation("Tenants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.cs b/code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.cs new file mode 100644 index 0000000..8b256e7 --- /dev/null +++ b/code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.cs @@ -0,0 +1,78 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + /// + public partial class RenameForgotPasswordRequests : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_forgot_password_requests_users_user_id", + table: "forgot_password_requests"); + + migrationBuilder.DropPrimaryKey( + name: "pk_forgot_password_requests", + table: "forgot_password_requests"); + + migrationBuilder.RenameTable( + name: "forgot_password_requests", + newName: "password_reset_requests"); + + migrationBuilder.RenameIndex( + name: "ix_forgot_password_requests_user_id", + table: "password_reset_requests", + newName: "ix_password_reset_requests_user_id"); + + migrationBuilder.AddPrimaryKey( + name: "pk_password_reset_requests", + table: "password_reset_requests", + column: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_password_reset_requests_users_user_id", + table: "password_reset_requests", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_password_reset_requests_users_user_id", + table: "password_reset_requests"); + + migrationBuilder.DropPrimaryKey( + name: "pk_password_reset_requests", + table: "password_reset_requests"); + + migrationBuilder.RenameTable( + name: "password_reset_requests", + newName: "forgot_password_requests"); + + migrationBuilder.RenameIndex( + name: "ix_password_reset_requests_user_id", + table: "forgot_password_requests", + newName: "ix_forgot_password_requests_user_id"); + + migrationBuilder.AddPrimaryKey( + name: "pk_forgot_password_requests", + table: "forgot_password_requests", + column: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_forgot_password_requests_users_user_id", + table: "forgot_password_requests", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/code/api/src/Migrations/20221209041908_TenantSlug.Designer.cs b/code/api/src/Migrations/20221209041908_TenantSlug.Designer.cs new file mode 100644 index 0000000..d23568d --- /dev/null +++ b/code/api/src/Migrations/20221209041908_TenantSlug.Designer.cs @@ -0,0 +1,1593 @@ +// +using System; +using IOL.GreatOffice.Api.Data.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20221209041908_TenantSlug")] + partial class TenantSlug + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("GroupsId") + .HasColumnType("uuid") + .HasColumnName("groups_id"); + + b.HasKey("CustomersId", "GroupsId") + .HasName("pk_customer_customer_group"); + + b.HasIndex("GroupsId") + .HasDatabaseName("ix_customer_customer_group_groups_id"); + + b.ToTable("customer_customer_group", (string)null); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("ProjectsId") + .HasColumnType("uuid") + .HasColumnName("projects_id"); + + b.HasKey("CustomersId", "ProjectsId") + .HasName("pk_customer_project"); + + b.HasIndex("ProjectsId") + .HasDatabaseName("ix_customer_project_projects_id"); + + b.ToTable("customer_project", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Address1") + .HasColumnType("text") + .HasColumnName("address1"); + + b.Property("Address2") + .HasColumnType("text") + .HasColumnName("address2"); + + b.Property("Country") + .HasColumnType("text") + .HasColumnName("country"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Currency") + .HasColumnType("text") + .HasColumnName("currency"); + + b.Property("CustomerNumber") + .HasColumnType("text") + .HasColumnName("customer_number"); + + b.Property("DefaultReference") + .HasColumnType("text") + .HasColumnName("default_reference"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + b.Property("OwnerId") + .HasColumnType("uuid") + .HasColumnName("owner_id"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("PostalCity") + .HasColumnType("text") + .HasColumnName("postal_city"); + + b.Property("PostalCode") + .HasColumnType("text") + .HasColumnName("postal_code"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("OwnerId") + .HasDatabaseName("ix_customers_owner_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customers_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customers_user_id"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("WorkTitle") + .HasColumnType("text") + .HasColumnName("work_title"); + + b.HasKey("Id") + .HasName("pk_customer_contacts"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_contacts_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_contacts_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_contacts_user_id"); + + b.ToTable("customer_contacts", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_events"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_events_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_events_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_events_user_id"); + + b.ToTable("customer_events", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_groups"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_groups_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_groups_user_id"); + + b.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_password_reset_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_password_reset_requests_user_id"); + + b.ToTable("password_reset_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_projects"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_projects_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_projects_user_id"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_project_labels"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_labels_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_project_labels_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_labels_user_id"); + + b.ToTable("project_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_project_members"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_members_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_members_user_id"); + + b.ToTable("project_members", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OwningTenantId") + .HasColumnType("uuid") + .HasColumnName("owning_tenant_id"); + + b.Property("Slug") + .HasColumnType("text") + .HasColumnName("slug"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.HasIndex("OwningTenantId") + .HasDatabaseName("ix_tenants_owning_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tenants_user_id"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AssignedToId") + .HasColumnType("uuid") + .HasColumnName("assigned_to_id"); + + b.Property("ClosedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("closed_at"); + + b.Property("ClosedById") + .HasColumnType("uuid") + .HasColumnName("closed_by_id"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("PublicId") + .HasColumnType("text") + .HasColumnName("public_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todos"); + + b.HasIndex("AssignedToId") + .HasDatabaseName("ix_todos_assigned_to_id"); + + b.HasIndex("ClosedById") + .HasDatabaseName("ix_todos_closed_by_id"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todos_collection_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todos_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todos_user_id"); + + b.ToTable("todos", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Visibility") + .HasColumnType("integer") + .HasColumnName("visibility"); + + b.HasKey("Id") + .HasName("pk_todo_collections"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_todo_collections_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_collections_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collections_user_id"); + + b.ToTable("todo_collections", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CanBrowse") + .HasColumnType("boolean") + .HasColumnName("can_browse"); + + b.Property("CanComment") + .HasColumnType("boolean") + .HasColumnName("can_comment"); + + b.Property("CanEdit") + .HasColumnType("boolean") + .HasColumnName("can_edit"); + + b.Property("CanSubmit") + .HasColumnType("boolean") + .HasColumnName("can_submit"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_collection_access_controls"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todo_collection_access_controls_collection_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collection_access_controls_user_id"); + + b.ToTable("todo_collection_access_controls", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ClosingStatement") + .HasColumnType("integer") + .HasColumnName("closing_statement"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_todo_comments"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_comments_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_comments_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_comments_user_id"); + + b.ToTable("todo_comments", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_labels"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_labels_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_labels_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_labels_user_id"); + + b.ToTable("todo_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_users_tenant_id"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.CustomerGroup", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customer_groups_groups_id"); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_projects_projects_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .HasConstraintName("fk_customers_users_owner_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customers_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Contacts") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_contacts_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_contacts_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_contacts_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Events") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_events_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_events_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_events_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_groups_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_groups_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_password_reset_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_projects_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_projects_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Labels") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_labels_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_project_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Members") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_members_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_members_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("OwningTenantId") + .HasConstraintName("fk_tenants_tenants_owning_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany("Tenants") + .HasForeignKey("UserId") + .HasConstraintName("fk_tenants_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "AssignedTo") + .WithMany() + .HasForeignKey("AssignedToId") + .HasConstraintName("fk_todos_users_assigned_to_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ClosedBy") + .WithMany() + .HasForeignKey("ClosedById") + .HasConstraintName("fk_todos_users_closed_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany() + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_todos_todo_projects_collection_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todos_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todos_users_user_id"); + + b.Navigation("AssignedTo"); + + b.Navigation("ClosedBy"); + + b.Navigation("Collection"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .HasConstraintName("fk_todo_collections_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_collections_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collections_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany("AccessControls") + .HasForeignKey("CollectionId") + .HasConstraintName("fk_todo_collection_access_controls_todo_collections_collection"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collection_access_controls_users_user_id"); + + b.Navigation("Collection"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_comments_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Comments") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_comments_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_comments_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Labels") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_labels_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany("Users") + .HasForeignKey("TenantId") + .HasConstraintName("fk_users_tenants_tenant_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Navigation("Contacts"); + + b.Navigation("Events"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Navigation("Comments"); + + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Navigation("AccessControls"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Navigation("Tenants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221209041908_TenantSlug.cs b/code/api/src/Migrations/20221209041908_TenantSlug.cs new file mode 100644 index 0000000..9dc8be8 --- /dev/null +++ b/code/api/src/Migrations/20221209041908_TenantSlug.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + /// + public partial class TenantSlug : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "slug", + table: "tenants", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "slug", + table: "tenants"); + } + } +} diff --git a/code/api/src/Migrations/20221209043806_ValidationEmailQueue.Designer.cs b/code/api/src/Migrations/20221209043806_ValidationEmailQueue.Designer.cs new file mode 100644 index 0000000..aa2d7f4 --- /dev/null +++ b/code/api/src/Migrations/20221209043806_ValidationEmailQueue.Designer.cs @@ -0,0 +1,1618 @@ +// +using System; +using IOL.GreatOffice.Api.Data.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20221209043806_ValidationEmailQueue")] + partial class ValidationEmailQueue + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("GroupsId") + .HasColumnType("uuid") + .HasColumnName("groups_id"); + + b.HasKey("CustomersId", "GroupsId") + .HasName("pk_customer_customer_group"); + + b.HasIndex("GroupsId") + .HasDatabaseName("ix_customer_customer_group_groups_id"); + + b.ToTable("customer_customer_group", (string)null); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("ProjectsId") + .HasColumnType("uuid") + .HasColumnName("projects_id"); + + b.HasKey("CustomersId", "ProjectsId") + .HasName("pk_customer_project"); + + b.HasIndex("ProjectsId") + .HasDatabaseName("ix_customer_project_projects_id"); + + b.ToTable("customer_project", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Address1") + .HasColumnType("text") + .HasColumnName("address1"); + + b.Property("Address2") + .HasColumnType("text") + .HasColumnName("address2"); + + b.Property("Country") + .HasColumnType("text") + .HasColumnName("country"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Currency") + .HasColumnType("text") + .HasColumnName("currency"); + + b.Property("CustomerNumber") + .HasColumnType("text") + .HasColumnName("customer_number"); + + b.Property("DefaultReference") + .HasColumnType("text") + .HasColumnName("default_reference"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + b.Property("OwnerId") + .HasColumnType("uuid") + .HasColumnName("owner_id"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("PostalCity") + .HasColumnType("text") + .HasColumnName("postal_city"); + + b.Property("PostalCode") + .HasColumnType("text") + .HasColumnName("postal_code"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("OwnerId") + .HasDatabaseName("ix_customers_owner_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customers_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customers_user_id"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("WorkTitle") + .HasColumnType("text") + .HasColumnName("work_title"); + + b.HasKey("Id") + .HasName("pk_customer_contacts"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_contacts_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_contacts_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_contacts_user_id"); + + b.ToTable("customer_contacts", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_events"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_events_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_events_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_events_user_id"); + + b.ToTable("customer_events", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_groups"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_groups_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_groups_user_id"); + + b.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_password_reset_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_password_reset_requests_user_id"); + + b.ToTable("password_reset_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_projects"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_projects_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_projects_user_id"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_project_labels"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_labels_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_project_labels_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_labels_user_id"); + + b.ToTable("project_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_project_members"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_members_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_members_user_id"); + + b.ToTable("project_members", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Queues.ValidationEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("EmailSentAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("email_sent_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_validation_emails"); + + b.ToTable("validation_emails", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OwningTenantId") + .HasColumnType("uuid") + .HasColumnName("owning_tenant_id"); + + b.Property("Slug") + .HasColumnType("text") + .HasColumnName("slug"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.HasIndex("OwningTenantId") + .HasDatabaseName("ix_tenants_owning_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tenants_user_id"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AssignedToId") + .HasColumnType("uuid") + .HasColumnName("assigned_to_id"); + + b.Property("ClosedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("closed_at"); + + b.Property("ClosedById") + .HasColumnType("uuid") + .HasColumnName("closed_by_id"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("PublicId") + .HasColumnType("text") + .HasColumnName("public_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todos"); + + b.HasIndex("AssignedToId") + .HasDatabaseName("ix_todos_assigned_to_id"); + + b.HasIndex("ClosedById") + .HasDatabaseName("ix_todos_closed_by_id"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todos_collection_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todos_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todos_user_id"); + + b.ToTable("todos", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Visibility") + .HasColumnType("integer") + .HasColumnName("visibility"); + + b.HasKey("Id") + .HasName("pk_todo_collections"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_todo_collections_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_collections_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collections_user_id"); + + b.ToTable("todo_collections", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CanBrowse") + .HasColumnType("boolean") + .HasColumnName("can_browse"); + + b.Property("CanComment") + .HasColumnType("boolean") + .HasColumnName("can_comment"); + + b.Property("CanEdit") + .HasColumnType("boolean") + .HasColumnName("can_edit"); + + b.Property("CanSubmit") + .HasColumnType("boolean") + .HasColumnName("can_submit"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_collection_access_controls"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todo_collection_access_controls_collection_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collection_access_controls_user_id"); + + b.ToTable("todo_collection_access_controls", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ClosingStatement") + .HasColumnType("integer") + .HasColumnName("closing_statement"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_todo_comments"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_comments_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_comments_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_comments_user_id"); + + b.ToTable("todo_comments", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_labels"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_labels_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_labels_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_labels_user_id"); + + b.ToTable("todo_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("EmailLastValidated") + .HasColumnType("timestamp with time zone") + .HasColumnName("email_last_validated"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_users_tenant_id"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.CustomerGroup", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customer_groups_groups_id"); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_projects_projects_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .HasConstraintName("fk_customers_users_owner_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customers_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Contacts") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_contacts_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_contacts_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_contacts_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Events") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_events_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_events_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_events_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_groups_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_groups_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_password_reset_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_projects_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_projects_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Labels") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_labels_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_project_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Members") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_members_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_members_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("OwningTenantId") + .HasConstraintName("fk_tenants_tenants_owning_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany("Tenants") + .HasForeignKey("UserId") + .HasConstraintName("fk_tenants_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "AssignedTo") + .WithMany() + .HasForeignKey("AssignedToId") + .HasConstraintName("fk_todos_users_assigned_to_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ClosedBy") + .WithMany() + .HasForeignKey("ClosedById") + .HasConstraintName("fk_todos_users_closed_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany() + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_todos_todo_projects_collection_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todos_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todos_users_user_id"); + + b.Navigation("AssignedTo"); + + b.Navigation("ClosedBy"); + + b.Navigation("Collection"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .HasConstraintName("fk_todo_collections_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_collections_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collections_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany("AccessControls") + .HasForeignKey("CollectionId") + .HasConstraintName("fk_todo_collection_access_controls_todo_collections_collection"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collection_access_controls_users_user_id"); + + b.Navigation("Collection"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_comments_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Comments") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_comments_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_comments_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Labels") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_labels_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany("Users") + .HasForeignKey("TenantId") + .HasConstraintName("fk_users_tenants_tenant_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Navigation("Contacts"); + + b.Navigation("Events"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Navigation("Comments"); + + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Navigation("AccessControls"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Navigation("Tenants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221209043806_ValidationEmailQueue.cs b/code/api/src/Migrations/20221209043806_ValidationEmailQueue.cs new file mode 100644 index 0000000..3599a37 --- /dev/null +++ b/code/api/src/Migrations/20221209043806_ValidationEmailQueue.cs @@ -0,0 +1,46 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + /// + public partial class ValidationEmailQueue : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "email_last_validated", + table: "users", + type: "timestamp with time zone", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.CreateTable( + name: "validation_emails", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + emailsentat = table.Column(name: "email_sent_at", type: "timestamp with time zone", nullable: false), + userid = table.Column(name: "user_id", type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_validation_emails", x => x.id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "validation_emails"); + + migrationBuilder.DropColumn( + name: "email_last_validated", + table: "users"); + } + } +} diff --git a/code/api/src/Migrations/20221214143556_AddUserDeletedBy.Designer.cs b/code/api/src/Migrations/20221214143556_AddUserDeletedBy.Designer.cs new file mode 100644 index 0000000..de4debc --- /dev/null +++ b/code/api/src/Migrations/20221214143556_AddUserDeletedBy.Designer.cs @@ -0,0 +1,1622 @@ +// +using System; +using IOL.GreatOffice.Api.Data.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20221214143556_AddUserDeletedBy")] + partial class AddUserDeletedBy + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("GroupsId") + .HasColumnType("uuid") + .HasColumnName("groups_id"); + + b.HasKey("CustomersId", "GroupsId") + .HasName("pk_customer_customer_group"); + + b.HasIndex("GroupsId") + .HasDatabaseName("ix_customer_customer_group_groups_id"); + + b.ToTable("customer_customer_group", (string)null); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("ProjectsId") + .HasColumnType("uuid") + .HasColumnName("projects_id"); + + b.HasKey("CustomersId", "ProjectsId") + .HasName("pk_customer_project"); + + b.HasIndex("ProjectsId") + .HasDatabaseName("ix_customer_project_projects_id"); + + b.ToTable("customer_project", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Address1") + .HasColumnType("text") + .HasColumnName("address1"); + + b.Property("Address2") + .HasColumnType("text") + .HasColumnName("address2"); + + b.Property("Country") + .HasColumnType("text") + .HasColumnName("country"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Currency") + .HasColumnType("text") + .HasColumnName("currency"); + + b.Property("CustomerNumber") + .HasColumnType("text") + .HasColumnName("customer_number"); + + b.Property("DefaultReference") + .HasColumnType("text") + .HasColumnName("default_reference"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + b.Property("OwnerId") + .HasColumnType("uuid") + .HasColumnName("owner_id"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("PostalCity") + .HasColumnType("text") + .HasColumnName("postal_city"); + + b.Property("PostalCode") + .HasColumnType("text") + .HasColumnName("postal_code"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("OwnerId") + .HasDatabaseName("ix_customers_owner_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customers_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customers_user_id"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("WorkTitle") + .HasColumnType("text") + .HasColumnName("work_title"); + + b.HasKey("Id") + .HasName("pk_customer_contacts"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_contacts_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_contacts_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_contacts_user_id"); + + b.ToTable("customer_contacts", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_events"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_events_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_events_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_events_user_id"); + + b.ToTable("customer_events", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_groups"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_groups_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_groups_user_id"); + + b.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_password_reset_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_password_reset_requests_user_id"); + + b.ToTable("password_reset_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_projects"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_projects_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_projects_user_id"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_project_labels"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_labels_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_project_labels_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_labels_user_id"); + + b.ToTable("project_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_project_members"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_members_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_members_user_id"); + + b.ToTable("project_members", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OwningTenantId") + .HasColumnType("uuid") + .HasColumnName("owning_tenant_id"); + + b.Property("Slug") + .HasColumnType("text") + .HasColumnName("slug"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.HasIndex("OwningTenantId") + .HasDatabaseName("ix_tenants_owning_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tenants_user_id"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AssignedToId") + .HasColumnType("uuid") + .HasColumnName("assigned_to_id"); + + b.Property("ClosedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("closed_at"); + + b.Property("ClosedById") + .HasColumnType("uuid") + .HasColumnName("closed_by_id"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("PublicId") + .HasColumnType("text") + .HasColumnName("public_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todos"); + + b.HasIndex("AssignedToId") + .HasDatabaseName("ix_todos_assigned_to_id"); + + b.HasIndex("ClosedById") + .HasDatabaseName("ix_todos_closed_by_id"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todos_collection_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todos_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todos_user_id"); + + b.ToTable("todos", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Visibility") + .HasColumnType("integer") + .HasColumnName("visibility"); + + b.HasKey("Id") + .HasName("pk_todo_collections"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_todo_collections_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_collections_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collections_user_id"); + + b.ToTable("todo_collections", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CanBrowse") + .HasColumnType("boolean") + .HasColumnName("can_browse"); + + b.Property("CanComment") + .HasColumnType("boolean") + .HasColumnName("can_comment"); + + b.Property("CanEdit") + .HasColumnType("boolean") + .HasColumnName("can_edit"); + + b.Property("CanSubmit") + .HasColumnType("boolean") + .HasColumnName("can_submit"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_collection_access_controls"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todo_collection_access_controls_collection_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collection_access_controls_user_id"); + + b.ToTable("todo_collection_access_controls", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ClosingStatement") + .HasColumnType("integer") + .HasColumnName("closing_statement"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_todo_comments"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_comments_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_comments_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_comments_user_id"); + + b.ToTable("todo_comments", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_labels"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_labels_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_labels_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_labels_user_id"); + + b.ToTable("todo_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("EmailLastValidated") + .HasColumnType("timestamp with time zone") + .HasColumnName("email_last_validated"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_users_tenant_id"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ValidationEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("EmailSentAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("email_sent_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_validation_emails"); + + b.ToTable("validation_emails", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.CustomerGroup", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customer_groups_groups_id"); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_projects_projects_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .HasConstraintName("fk_customers_users_owner_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customers_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Contacts") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_contacts_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_contacts_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_contacts_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Events") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_events_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_events_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_events_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_groups_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_groups_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_password_reset_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_projects_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_projects_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Labels") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_labels_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_project_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Members") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_members_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_members_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("OwningTenantId") + .HasConstraintName("fk_tenants_tenants_owning_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany("Tenants") + .HasForeignKey("UserId") + .HasConstraintName("fk_tenants_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "AssignedTo") + .WithMany() + .HasForeignKey("AssignedToId") + .HasConstraintName("fk_todos_users_assigned_to_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ClosedBy") + .WithMany() + .HasForeignKey("ClosedById") + .HasConstraintName("fk_todos_users_closed_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany() + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_todos_todo_projects_collection_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todos_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todos_users_user_id"); + + b.Navigation("AssignedTo"); + + b.Navigation("ClosedBy"); + + b.Navigation("Collection"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .HasConstraintName("fk_todo_collections_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_collections_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collections_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany("AccessControls") + .HasForeignKey("CollectionId") + .HasConstraintName("fk_todo_collection_access_controls_todo_collections_collection"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collection_access_controls_users_user_id"); + + b.Navigation("Collection"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_comments_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Comments") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_comments_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_comments_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Labels") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_labels_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany("Users") + .HasForeignKey("TenantId") + .HasConstraintName("fk_users_tenants_tenant_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Navigation("Contacts"); + + b.Navigation("Events"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Navigation("Comments"); + + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Navigation("AccessControls"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Navigation("Tenants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221214143556_AddUserDeletedBy.cs b/code/api/src/Migrations/20221214143556_AddUserDeletedBy.cs new file mode 100644 index 0000000..e929b88 --- /dev/null +++ b/code/api/src/Migrations/20221214143556_AddUserDeletedBy.cs @@ -0,0 +1,29 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + /// + public partial class AddUserDeletedBy : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "deleted_by", + table: "users", + type: "uuid", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "deleted_by", + table: "users"); + } + } +} diff --git a/code/api/src/Migrations/AppDbContextModelSnapshot.cs b/code/api/src/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..e0a5cfb --- /dev/null +++ b/code/api/src/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,1619 @@ +// +using System; +using IOL.GreatOffice.Api.Data.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("GroupsId") + .HasColumnType("uuid") + .HasColumnName("groups_id"); + + b.HasKey("CustomersId", "GroupsId") + .HasName("pk_customer_customer_group"); + + b.HasIndex("GroupsId") + .HasDatabaseName("ix_customer_customer_group_groups_id"); + + b.ToTable("customer_customer_group", (string)null); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.Property("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property("ProjectsId") + .HasColumnType("uuid") + .HasColumnName("projects_id"); + + b.HasKey("CustomersId", "ProjectsId") + .HasName("pk_customer_project"); + + b.HasIndex("ProjectsId") + .HasDatabaseName("ix_customer_project_projects_id"); + + b.ToTable("customer_project", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Address1") + .HasColumnType("text") + .HasColumnName("address1"); + + b.Property("Address2") + .HasColumnType("text") + .HasColumnName("address2"); + + b.Property("Country") + .HasColumnType("text") + .HasColumnName("country"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Currency") + .HasColumnType("text") + .HasColumnName("currency"); + + b.Property("CustomerNumber") + .HasColumnType("text") + .HasColumnName("customer_number"); + + b.Property("DefaultReference") + .HasColumnType("text") + .HasColumnName("default_reference"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + b.Property("OwnerId") + .HasColumnType("uuid") + .HasColumnName("owner_id"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("PostalCity") + .HasColumnType("text") + .HasColumnName("postal_city"); + + b.Property("PostalCode") + .HasColumnType("text") + .HasColumnName("postal_code"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("OwnerId") + .HasDatabaseName("ix_customers_owner_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customers_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customers_user_id"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("WorkTitle") + .HasColumnType("text") + .HasColumnName("work_title"); + + b.HasKey("Id") + .HasName("pk_customer_contacts"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_contacts_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_contacts_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_contacts_user_id"); + + b.ToTable("customer_contacts", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_events"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_events_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_events_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_events_user_id"); + + b.ToTable("customer_events", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_groups"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_groups_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_groups_user_id"); + + b.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_password_reset_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_password_reset_requests_user_id"); + + b.ToTable("password_reset_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_projects"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_projects_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_projects_user_id"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_project_labels"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_labels_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_project_labels_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_labels_user_id"); + + b.ToTable("project_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_project_members"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_members_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_members_user_id"); + + b.ToTable("project_members", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("OwningTenantId") + .HasColumnType("uuid") + .HasColumnName("owning_tenant_id"); + + b.Property("Slug") + .HasColumnType("text") + .HasColumnName("slug"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.HasIndex("OwningTenantId") + .HasDatabaseName("ix_tenants_owning_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tenants_user_id"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("AssignedToId") + .HasColumnType("uuid") + .HasColumnName("assigned_to_id"); + + b.Property("ClosedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("closed_at"); + + b.Property("ClosedById") + .HasColumnType("uuid") + .HasColumnName("closed_by_id"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("PublicId") + .HasColumnType("text") + .HasColumnName("public_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todos"); + + b.HasIndex("AssignedToId") + .HasDatabaseName("ix_todos_assigned_to_id"); + + b.HasIndex("ClosedById") + .HasDatabaseName("ix_todos_closed_by_id"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todos_collection_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todos_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todos_user_id"); + + b.ToTable("todos", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Visibility") + .HasColumnType("integer") + .HasColumnName("visibility"); + + b.HasKey("Id") + .HasName("pk_todo_collections"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_todo_collections_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_collections_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collections_user_id"); + + b.ToTable("todo_collections", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CanBrowse") + .HasColumnType("boolean") + .HasColumnName("can_browse"); + + b.Property("CanComment") + .HasColumnType("boolean") + .HasColumnName("can_comment"); + + b.Property("CanEdit") + .HasColumnType("boolean") + .HasColumnName("can_edit"); + + b.Property("CanSubmit") + .HasColumnType("boolean") + .HasColumnName("can_submit"); + + b.Property("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_collection_access_controls"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todo_collection_access_controls_collection_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collection_access_controls_user_id"); + + b.ToTable("todo_collection_access_controls", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ClosingStatement") + .HasColumnType("integer") + .HasColumnName("closing_statement"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_todo_comments"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_comments_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_comments_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_comments_user_id"); + + b.ToTable("todo_comments", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_labels"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_labels_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_labels_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_labels_user_id"); + + b.ToTable("todo_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property("EmailLastValidated") + .HasColumnType("timestamp with time zone") + .HasColumnName("email_last_validated"); + + b.Property("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_users_tenant_id"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ValidationEmail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("EmailSentAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("email_sent_at"); + + b.Property("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_validation_emails"); + + b.ToTable("validation_emails", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.CustomerGroup", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customer_groups_groups_id"); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_projects_projects_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .HasConstraintName("fk_customers_users_owner_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customers_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Contacts") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_contacts_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_contacts_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_contacts_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Events") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_events_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_events_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_events_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_groups_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_groups_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_password_reset_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_projects_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_projects_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Labels") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_labels_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_project_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Members") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_members_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_members_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("OwningTenantId") + .HasConstraintName("fk_tenants_tenants_owning_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany("Tenants") + .HasForeignKey("UserId") + .HasConstraintName("fk_tenants_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "AssignedTo") + .WithMany() + .HasForeignKey("AssignedToId") + .HasConstraintName("fk_todos_users_assigned_to_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ClosedBy") + .WithMany() + .HasForeignKey("ClosedById") + .HasConstraintName("fk_todos_users_closed_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany() + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_todos_todo_projects_collection_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todos_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todos_users_user_id"); + + b.Navigation("AssignedTo"); + + b.Navigation("ClosedBy"); + + b.Navigation("Collection"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .HasConstraintName("fk_todo_collections_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_collections_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collections_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany("AccessControls") + .HasForeignKey("CollectionId") + .HasConstraintName("fk_todo_collection_access_controls_todo_collections_collection"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collection_access_controls_users_user_id"); + + b.Navigation("Collection"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_comments_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Comments") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_comments_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_comments_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Labels") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_labels_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany("Users") + .HasForeignKey("TenantId") + .HasConstraintName("fk_users_tenants_tenant_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Navigation("Contacts"); + + b.Navigation("Events"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Navigation("Comments"); + + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Navigation("AccessControls"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Navigation("Tenants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Models/Database/Api/ApiAccessToken.cs b/code/api/src/Models/Database/Api/ApiAccessToken.cs new file mode 100644 index 0000000..9359fc4 --- /dev/null +++ b/code/api/src/Models/Database/Api/ApiAccessToken.cs @@ -0,0 +1,12 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class ApiAccessToken : Base +{ + public User User { get; set; } + public DateTime ExpiryDate { get; set; } + public bool AllowRead { get; set; } + public bool AllowCreate { get; set; } + public bool AllowUpdate { get; set; } + public bool AllowDelete { get; set; } + public bool HasExpired => ExpiryDate < AppDateTime.UtcNow; +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Base.cs b/code/api/src/Models/Database/Base.cs new file mode 100644 index 0000000..0b16b12 --- /dev/null +++ b/code/api/src/Models/Database/Base.cs @@ -0,0 +1,22 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public abstract class Base +{ + protected Base() { + Id = Guid.NewGuid(); + CreatedAt = AppDateTime.UtcNow; + } + + public Guid Id { get; init; } + public DateTime CreatedAt { get; init; } + public DateTime? ModifiedAt { get; private set; } + public DateTime? DeletedAt { get; private set; } + public bool Deleted { get; private set; } + + public void SetModified() => ModifiedAt = AppDateTime.UtcNow; + + public void SetDeleted() { + Deleted = true; + DeletedAt = AppDateTime.UtcNow; + } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/BaseWithOwner.cs b/code/api/src/Models/Database/BaseWithOwner.cs new file mode 100644 index 0000000..7e9f6c1 --- /dev/null +++ b/code/api/src/Models/Database/BaseWithOwner.cs @@ -0,0 +1,40 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +/// +/// Base class for all entities with ownership. +/// +public abstract class BaseWithOwner : Base +{ + protected BaseWithOwner() { } + + protected BaseWithOwner(Guid createdBy) { + CreatedBy = createdBy; + } + + protected BaseWithOwner(LoggedInUserModel loggedInUser) { + CreatedBy = loggedInUser.Id; + } + + public Guid? UserId { get; private set; } + public Guid? TenantId { get; private set; } + public Guid? ModifiedBy { get; private set; } + public Guid? CreatedBy { get; private set; } + public Guid? DeletedBy { get; private set; } + public User OwningUser { get; set; } + public Tenant OwningTenant { get; set; } + + public void SetDeleted(Guid userId) { + DeletedBy = userId; + base.SetDeleted(); + } + + public void SetModified(Guid userId) { + ModifiedBy = userId; + base.SetModified(); + } + + public void SetOwnerIds(Guid userId = default, Guid tenantId = default) { + if (tenantId != default) TenantId = tenantId; + if (userId != default) UserId = userId; + } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Customer/Customer.cs b/code/api/src/Models/Database/Customer/Customer.cs new file mode 100644 index 0000000..8e153c6 --- /dev/null +++ b/code/api/src/Models/Database/Customer/Customer.cs @@ -0,0 +1,29 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class Customer : BaseWithOwner +{ + public Customer() { } + public Customer(LoggedInUserModel loggedInUserModel) : base(loggedInUserModel) { } + + public string CustomerNumber { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string Address1 { get; set; } + public string Address2 { get; set; } + public string PostalCode { get; set; } + public string PostalCity { get; set; } + public string Country { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public string VATNumber { get; set; } + public string ORGNumber { get; set; } + public string DefaultReference { get; set; } + public string Website { get; set; } + public string Currency { get; set; } + public Guid? OwnerId { get; set; } + public User Owner { get; set; } + public ICollection Groups { get; set; } + public ICollection Contacts { get; set; } + public ICollection Events { get; set; } + public ICollection Projects { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Customer/CustomerContact.cs b/code/api/src/Models/Database/Customer/CustomerContact.cs new file mode 100644 index 0000000..f5a951d --- /dev/null +++ b/code/api/src/Models/Database/Customer/CustomerContact.cs @@ -0,0 +1,12 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class CustomerContact : BaseWithOwner +{ + public Customer Customer { get; set; } + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + public string Phone { get; set; } + public string WorkTitle { get; set; } + public string Note { get; set; } +} diff --git a/code/api/src/Models/Database/Customer/CustomerEvent.cs b/code/api/src/Models/Database/Customer/CustomerEvent.cs new file mode 100644 index 0000000..a87da4c --- /dev/null +++ b/code/api/src/Models/Database/Customer/CustomerEvent.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class CustomerEvent : BaseWithOwner +{ + public Customer Customer { get; set; } + public string Title { get; set; } + public string Note { get; set; } +} diff --git a/code/api/src/Models/Database/Customer/CustomerGroup.cs b/code/api/src/Models/Database/Customer/CustomerGroup.cs new file mode 100644 index 0000000..9438f3c --- /dev/null +++ b/code/api/src/Models/Database/Customer/CustomerGroup.cs @@ -0,0 +1,7 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class CustomerGroup : BaseWithOwner +{ + public string Name { get; set; } + public ICollection Customers { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Customer/CustomerGroupMembership.cs b/code/api/src/Models/Database/Customer/CustomerGroupMembership.cs new file mode 100644 index 0000000..ec0d4af --- /dev/null +++ b/code/api/src/Models/Database/Customer/CustomerGroupMembership.cs @@ -0,0 +1,7 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class CustomerGroupMembership : Base +{ + public Customer Customer { get; set; } + public CustomerGroup Group { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Internal/PasswordResetRequest.cs b/code/api/src/Models/Database/Internal/PasswordResetRequest.cs new file mode 100644 index 0000000..ee73fd2 --- /dev/null +++ b/code/api/src/Models/Database/Internal/PasswordResetRequest.cs @@ -0,0 +1,23 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class PasswordResetRequest +{ + public PasswordResetRequest() { } + + public PasswordResetRequest(User user) { + CreatedAt = AppDateTime.UtcNow; + Id = Guid.NewGuid(); + User = user; + } + + public Guid Id { get; set; } + public Guid UserId { get; set; } + public User User { get; set; } + public DateTime CreatedAt { get; set; } + + [NotMapped] + public DateTime ExpirationDate => CreatedAt.AddMinutes(15); + + [NotMapped] + public bool IsExpired => DateTime.Compare(ExpirationDate, AppDateTime.UtcNow) < 0; +} diff --git a/code/api/src/Models/Database/Internal/Tenant.cs b/code/api/src/Models/Database/Internal/Tenant.cs new file mode 100644 index 0000000..471164d --- /dev/null +++ b/code/api/src/Models/Database/Internal/Tenant.cs @@ -0,0 +1,12 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class Tenant : BaseWithOwner +{ + public string Slug { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string ContactEmail { get; set; } + public Guid MasterUserId { get; set; } + public string MasterUserPassword { get; set; } + public ICollection Users { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Internal/User.cs b/code/api/src/Models/Database/Internal/User.cs new file mode 100644 index 0000000..f4d08ff --- /dev/null +++ b/code/api/src/Models/Database/Internal/User.cs @@ -0,0 +1,44 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class User : Base +{ + public User() { } + + public User(string username) { + Username = username; + } + + public string FirstName { get; set; } + public string LastName { get; set; } + public string Email { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public DateTime EmailLastValidated { get; set; } + public ICollection Tenants { get; set; } + public Guid? DeletedBy { get; set; } + + public string DisplayName(bool isForGreeting = false) { + if (!isForGreeting && FirstName.HasValue() && LastName.HasValue()) return FirstName + " " + LastName; + return FirstName.HasValue() ? FirstName : Username ?? Email; + } + + public void HashAndSetPassword(string password) { + Password = PasswordHelper.HashPassword(password); + } + + public bool VerifyPassword(string password) { + return PasswordHelper.Verify(password, Password); + } + + public void SetDeleted(Guid userId) { + base.SetDeleted(); + DeletedBy = userId; + } + + public IEnumerable DefaultClaims() { + return new Claim[] { + new(AppClaims.USER_ID, Id.ToString()), + new(AppClaims.NAME, Username), + }; + } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/MainAppDatabase.cs b/code/api/src/Models/Database/MainAppDatabase.cs new file mode 100644 index 0000000..2b42fe0 --- /dev/null +++ b/code/api/src/Models/Database/MainAppDatabase.cs @@ -0,0 +1,107 @@ +using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; + +namespace IOL.GreatOffice.Api.Data.Database; + +public class MainAppDatabase : DbContext, IDataProtectionKeyContext +{ + public MainAppDatabase(DbContextOptions options) : base(options) { } + public DbSet Users { get; set; } + public DbSet PasswordResetRequests { get; set; } + public DbSet AccessTokens { get; set; } + public DbSet Tenants { get; set; } + public DbSet DataProtectionKeys { get; set; } + public DbSet Projects { get; set; } + public DbSet ProjectLabels { get; set; } + public DbSet Customers { get; set; } + public DbSet CustomersContacts { get; set; } + public DbSet CustomerEvents { get; set; } + public DbSet CustomerGroups { get; set; } + public DbSet TodoLabels { get; set; } + public DbSet TodoProjectAccessControls { get; set; } + public DbSet TodoProjects { get; set; } + public DbSet TodoComments { get; set; } + public DbSet Todos { get; set; } + public DbSet ValidationEmails { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) { + modelBuilder.Entity(e => { + e.HasMany(n => n.Tenants); + e.ToTable("users"); + }); + modelBuilder.Entity(e => { + e.HasOne(c => c.User); + e.ToTable("password_reset_requests"); + }); + modelBuilder.Entity(e => { + e.HasOne(n => n.User); + e.ToTable("api_access_tokens"); + }); + modelBuilder.Entity(e => { + e.HasMany(n => n.Users); + e.ToTable("tenants"); + }); + modelBuilder.Entity(e => { + e.HasMany(n => n.Members); + e.HasMany(n => n.Customers); + e.ToTable("projects"); + }); + modelBuilder.Entity(e => { + e.HasOne(n => n.Project); + e.HasOne(n => n.User); + e.ToTable("project_members"); + }); + modelBuilder.Entity(e => { + e.HasOne(n => n.Project); + e.ToTable("project_labels"); + }); + modelBuilder.Entity(e => { + e.HasOne(n => n.Owner); + e.HasMany(n => n.Events); + e.HasMany(n => n.Contacts); + e.HasMany(n => n.Groups); + e.HasMany(n => n.Projects); + e.ToTable("customers"); + }); + modelBuilder.Entity(e => { + e.HasOne(n => n.Customer); + e.ToTable("customer_contacts"); + }); + modelBuilder.Entity(e => { + e.HasOne(n => n.Customer); + e.ToTable("customer_events"); + }); + modelBuilder.Entity(e => { + e.HasMany(n => n.Customers); + e.ToTable("customer_groups"); + }); + modelBuilder.Entity(e => { + e.HasOne(n => n.Collection); + e.HasOne(n => n.AssignedTo); + e.HasOne(n => n.ClosedBy); + e.HasMany(n => n.Labels); + e.HasMany(n => n.Comments); + e.ToTable("todos"); + }); + modelBuilder.Entity(e => { + e.HasOne(n => n.Project); + e.HasMany(n => n.AccessControls); + e.ToTable("todo_collections"); + }); + modelBuilder.Entity(e => { + e.HasOne(n => n.Todo); + e.ToTable("todo_comments"); + }); + modelBuilder.Entity(e => { + e.HasOne(n => n.Todo); + e.ToTable("todo_labels"); + }); + modelBuilder.Entity(e => { + e.HasOne(n => n.User); + e.HasOne(n => n.Collection); + e.ToTable("todo_collection_access_controls"); + }); + modelBuilder.Entity(e => { e.ToTable("validation_emails"); }); + + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Project/Project.cs b/code/api/src/Models/Database/Project/Project.cs new file mode 100644 index 0000000..de9e2cb --- /dev/null +++ b/code/api/src/Models/Database/Project/Project.cs @@ -0,0 +1,15 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class Project : BaseWithOwner +{ + public Project() { } + + public Project(LoggedInUserModel loggedInUserModel) : base(loggedInUserModel) { } + public string Name { get; set; } + public string Description { get; set; } + public DateTime? Start { get; set; } + public DateTime? Stop { get; set; } + public ICollection Customers { get; set; } + public ICollection Members { get; set; } + public ICollection Labels { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Project/ProjectLabel.cs b/code/api/src/Models/Database/Project/ProjectLabel.cs new file mode 100644 index 0000000..0e1dc5d --- /dev/null +++ b/code/api/src/Models/Database/Project/ProjectLabel.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class ProjectLabel : BaseWithOwner +{ + public string Value { get; set; } + public string Color { get; set; } + public Project Project { get; set; } +} diff --git a/code/api/src/Models/Database/Project/ProjectMember.cs b/code/api/src/Models/Database/Project/ProjectMember.cs new file mode 100644 index 0000000..a5e0682 --- /dev/null +++ b/code/api/src/Models/Database/Project/ProjectMember.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class ProjectMember : Base +{ + public Project Project { get; set; } + public User User { get; set; } + public ProjectRole Role { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Queues/ValidationEmail.cs b/code/api/src/Models/Database/Queues/ValidationEmail.cs new file mode 100644 index 0000000..0457768 --- /dev/null +++ b/code/api/src/Models/Database/Queues/ValidationEmail.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class ValidationEmail +{ + public Guid Id { get; set; } + public DateTime EmailSentAt { get; set; } + public Guid UserId { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Todo/Todo.cs b/code/api/src/Models/Database/Todo/Todo.cs new file mode 100644 index 0000000..2d7f109 --- /dev/null +++ b/code/api/src/Models/Database/Todo/Todo.cs @@ -0,0 +1,17 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class Todo : BaseWithOwner +{ + public string PublicId { get; set; } + public Guid? AssignedToId { get; set; } + public Guid? ClosedById { get; set; } + public Guid CollectionId { get; set; } + public DateTime? ClosedAt { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public ICollection Labels { get; set; } + public ICollection Comments { get; set; } + public User AssignedTo { get; set; } + public User ClosedBy { get; set; } + public TodoCollection Collection { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Todo/TodoCollection.cs b/code/api/src/Models/Database/Todo/TodoCollection.cs new file mode 100644 index 0000000..470e5e7 --- /dev/null +++ b/code/api/src/Models/Database/Todo/TodoCollection.cs @@ -0,0 +1,11 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class TodoCollection : BaseWithOwner +{ + public string Name { get; set; } + public string Description { get; set; } + public TodoVisibility Visibility { get; set; } + public Guid? ProjectId { get; set; } + public Project Project { get; set; } + public ICollection AccessControls { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Todo/TodoCollectionAccessControl.cs b/code/api/src/Models/Database/Todo/TodoCollectionAccessControl.cs new file mode 100644 index 0000000..1676c06 --- /dev/null +++ b/code/api/src/Models/Database/Todo/TodoCollectionAccessControl.cs @@ -0,0 +1,12 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class TodoCollectionAccessControl : Base +{ + public TodoCollection Collection { get; set; } + public User User { get; set; } + public Guid? UserId { get; set; } + public bool CanBrowse { get; set; } + public bool CanSubmit { get; set; } + public bool CanComment { get; set; } + public bool CanEdit { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Todo/TodoComment.cs b/code/api/src/Models/Database/Todo/TodoComment.cs new file mode 100644 index 0000000..32ac3a3 --- /dev/null +++ b/code/api/src/Models/Database/Todo/TodoComment.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class TodoComment : BaseWithOwner +{ + public string Value { get; set; } + public Todo Todo { get; set; } + public TodoClosingStatement? ClosingStatement { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Models/Database/Todo/TodoLabel.cs b/code/api/src/Models/Database/Todo/TodoLabel.cs new file mode 100644 index 0000000..7753ade --- /dev/null +++ b/code/api/src/Models/Database/Todo/TodoLabel.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class TodoLabel : BaseWithOwner +{ + public string Name { get; set; } + public string Color { get; set; } + public Todo Todo { get; set; } +} diff --git a/code/api/src/Models/Enums/FulfillPasswordResetRequestResult.cs b/code/api/src/Models/Enums/FulfillPasswordResetRequestResult.cs new file mode 100644 index 0000000..2a84c48 --- /dev/null +++ b/code/api/src/Models/Enums/FulfillPasswordResetRequestResult.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Data.Enums; + +public enum FulfillPasswordResetRequestResult +{ + REQUEST_NOT_FOUND, + USER_NOT_FOUND, + FULFILLED +} \ No newline at end of file diff --git a/code/api/src/Models/Enums/PasswordResetRequestStatus.cs b/code/api/src/Models/Enums/PasswordResetRequestStatus.cs new file mode 100644 index 0000000..5629e6f --- /dev/null +++ b/code/api/src/Models/Enums/PasswordResetRequestStatus.cs @@ -0,0 +1,6 @@ +namespace IOL.GreatOffice.Api.Data.Enums; + +public enum PasswordResetRequestStatus +{ + +} \ No newline at end of file diff --git a/code/api/src/Models/Enums/ProjectRole.cs b/code/api/src/Models/Enums/ProjectRole.cs new file mode 100644 index 0000000..c4a3f29 --- /dev/null +++ b/code/api/src/Models/Enums/ProjectRole.cs @@ -0,0 +1,9 @@ +namespace IOL.GreatOffice.Api.Data.Enums; + +public enum ProjectRole +{ + EXTERNAL, + RESOURCE, + LEADER, + OWNER +} \ No newline at end of file diff --git a/code/api/src/Models/Enums/TodoClosingStatement.cs b/code/api/src/Models/Enums/TodoClosingStatement.cs new file mode 100644 index 0000000..d838031 --- /dev/null +++ b/code/api/src/Models/Enums/TodoClosingStatement.cs @@ -0,0 +1,13 @@ +namespace IOL.GreatOffice.Api.Data.Enums; + +public enum TodoClosingStatement +{ + REPORTED, + RESOLVED, + FIXED, + IMPLEMENTED, + WONT_FIX, + BY_DESIGN, + INVALID, + DUPLICATE +} \ No newline at end of file diff --git a/code/api/src/Models/Enums/TodoVisibility.cs b/code/api/src/Models/Enums/TodoVisibility.cs new file mode 100644 index 0000000..2c8fa83 --- /dev/null +++ b/code/api/src/Models/Enums/TodoVisibility.cs @@ -0,0 +1,10 @@ + +namespace IOL.GreatOffice.Api.Data.Enums; + +public enum TodoVisibility +{ + PRIVATE = 0, + UNLISTED = 1, + INTERNAL = 2, + PUBLIC = 3, +} \ No newline at end of file diff --git a/code/api/src/Models/Misc/ApiSpecDocument.cs b/code/api/src/Models/Misc/ApiSpecDocument.cs new file mode 100644 index 0000000..1c7d936 --- /dev/null +++ b/code/api/src/Models/Misc/ApiSpecDocument.cs @@ -0,0 +1,9 @@ +namespace IOL.GreatOffice.Api.Data.Models; + +public class ApiSpecDocument +{ + public string VersionName { get; set; } + public string SwaggerPath { get; set; } + public ApiVersion Version { get; set; } + public OpenApiInfo OpenApiInfo { get; set; } +} diff --git a/code/api/src/Models/Misc/AppConfiguration.cs b/code/api/src/Models/Misc/AppConfiguration.cs new file mode 100644 index 0000000..31e5726 --- /dev/null +++ b/code/api/src/Models/Misc/AppConfiguration.cs @@ -0,0 +1,118 @@ +using System.Security.Cryptography.X509Certificates; + +namespace IOL.GreatOffice.Api.Data.Models; + +public class AppConfiguration +{ + /// + /// An reachable ip address or url that points to a postgres database. + /// + public string DB_HOST { get; set; } + + /// + /// The port number to use with DB_HOST to point to the postgres database. + /// + public string DB_PORT { get; set; } + + /// + /// The database user to authenticate against postgres with. + /// + public string DB_USER { get; set; } + + /// + /// The password for the database user to authenticate against postgres with. + /// + public string DB_PASSWORD { get; set; } + + /// + /// The name of the main app database. + /// + public string DB_NAME { get; set; } + + /// + /// An reachable ip address or url that points to a postgres(quartz) database. + /// + public string QUARTZ_DB_HOST { get; set; } + + /// + /// The port number to use with QUARTZ_DB_HOST to point to the postgres(quartz) database. + /// + public string QUARTZ_DB_PORT { get; set; } + + /// + /// The database user to authenticate against postgres(quartz) with. + /// + public string QUARTZ_DB_USER { get; set; } + + /// + /// The password for the database user to authenticate against postgres(quartz) with. + /// + public string QUARTZ_DB_PASSWORD { get; set; } + + /// + /// The name of the quartz database. + /// + public string QUARTZ_DB_NAME { get; set; } + + /// + /// API key to use when pushing logs to SEQ + /// + public string SEQ_API_KEY { get; set; } + + /// + /// Url pointing to the seq instance that processes server logs + /// + public string SEQ_API_URL { get; set; } + + /// + /// A token used when sending email via Postmakr + /// + public string POSTMARK_TOKEN { get; set; } + + /// + /// The address to send emails from, needs to be setup as a sender in postmark + /// + public string EMAIL_FROM_ADDRESS { get; set; } + + /// + /// The absolute url to the frontend app + /// + public string CANONICAL_FRONTEND_URL { get; set; } + + /// + /// The absolute url to the backend api + /// + public string CANONICAL_BACKEND_URL { get; set; } + + /// + /// A random string used to encrypt/decrypt for general purposes + /// + public string APP_AES_KEY { get; set; } + + /// + /// A base64 string containing a passwordless pfx cert + /// + public string APP_CERT { get; set; } + + public X509Certificate2 CERT1() => new(Convert.FromBase64String(APP_CERT)); + + public object GetPublicVersion() { + return new { + DB_HOST, + DB_PORT, + DB_USER, + DB_PASSWORD = DB_PASSWORD.Obfuscate() ?? "", + QUARTZ_DB_HOST, + QUARTZ_DB_PORT, + QUARTZ_DB_USER, + QUARTZ_DB_PASSWORD = QUARTZ_DB_PASSWORD.Obfuscate() ?? "", + SEQ_API_KEY = SEQ_API_KEY.Obfuscate() ?? "", + SEQ_API_URL, + POSTMARK_TOKEN = POSTMARK_TOKEN.Obfuscate() ?? "", + EMAIL_FROM_ADDRESS, + APP_AES_KEY = APP_AES_KEY.Obfuscate() ?? "", + CERT1 = CERT1().PublicKey.Oid.FriendlyName, + CANONICAL_FRONTEND_URL + }; + } +} \ No newline at end of file diff --git a/code/api/src/Models/Misc/AppPath.cs b/code/api/src/Models/Misc/AppPath.cs new file mode 100644 index 0000000..e47e48c --- /dev/null +++ b/code/api/src/Models/Misc/AppPath.cs @@ -0,0 +1,23 @@ +namespace IOL.GreatOffice.Api.Data.Models; + +public sealed record AppPath +{ + public string HostPath { get; init; } + public string WebPath { get; init; } + + public string GetHostPathForFilename(string filename, string fallback = "") { + if (filename.IsNullOrWhiteSpace()) { + return fallback; + } + + return Path.Combine(HostPath, filename); + } + + public string GetWebPathForFilename(string filename, string fallback = "") { + if (filename.IsNullOrWhiteSpace()) { + return fallback; + } + + return Path.Combine(WebPath, filename); + } +} diff --git a/code/api/src/Models/Misc/KnownProblemModel.cs b/code/api/src/Models/Misc/KnownProblemModel.cs new file mode 100644 index 0000000..9acc85c --- /dev/null +++ b/code/api/src/Models/Misc/KnownProblemModel.cs @@ -0,0 +1,26 @@ +namespace IOL.GreatOffice.Api.Data.Models; + +public class KnownProblemModel +{ + public KnownProblemModel(string title = default, string subtitle = default, Dictionary errors = default) { + Title = title; + Subtitle = subtitle; + Errors = errors ?? new(); + } + + public string Title { get; set; } + public string Subtitle { get; set; } + public Dictionary Errors { get; set; } + public string TraceId { get; set; } + + public void AddError(string field, string errorText) { + if (!Errors.ContainsKey(field)) { + Errors.Add(field, new[] {errorText}); + } else { + var currentErrors = Errors[field]; + var newErrors = currentErrors.Concat(new[] {errorText}); + Errors.Remove(field); + Errors.Add(field, newErrors.ToArray()); + } + } +} \ No newline at end of file diff --git a/code/api/src/Models/Misc/LoggedInUserModel.cs b/code/api/src/Models/Misc/LoggedInUserModel.cs new file mode 100644 index 0000000..541d4a5 --- /dev/null +++ b/code/api/src/Models/Misc/LoggedInUserModel.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Data.Models; + +public class LoggedInUserModel +{ + public Guid Id { get; set; } + public string Username { get; set; } + public Guid TenantId { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Models/Misc/RequestTimeZoneInfo.cs b/code/api/src/Models/Misc/RequestTimeZoneInfo.cs new file mode 100644 index 0000000..4c5aa13 --- /dev/null +++ b/code/api/src/Models/Misc/RequestTimeZoneInfo.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Data.Models; + +public class RequestTimeZoneInfo +{ + public TimeZoneInfo TimeZoneInfo { get; set; } + public int Offset { get; set; } + public DateTime LocalDateTime { get; set; } +} \ No newline at end of file diff --git a/code/api/src/Models/Static/AppClaims.cs b/code/api/src/Models/Static/AppClaims.cs new file mode 100644 index 0000000..6569700 --- /dev/null +++ b/code/api/src/Models/Static/AppClaims.cs @@ -0,0 +1,7 @@ +namespace IOL.GreatOffice.Api.Data.Static; + +public static class AppClaims +{ + public const string USER_ID = "user_id"; + public const string NAME = "name"; +} diff --git a/code/api/src/Models/Static/AppConstants.cs b/code/api/src/Models/Static/AppConstants.cs new file mode 100644 index 0000000..d0a888b --- /dev/null +++ b/code/api/src/Models/Static/AppConstants.cs @@ -0,0 +1,12 @@ +namespace IOL.GreatOffice.Api.Data.Static; + +public static class AppConstants +{ + public const string API_NAME = "Greatoffice API"; + public const string BASIC_AUTH_SCHEME = "BasicAuthenticationScheme"; + public const string TOKEN_ALLOW_READ = "TOKEN_ALLOW_READ"; + public const string TOKEN_ALLOW_CREATE = "TOKEN_ALLOW_CREATE"; + public const string TOKEN_ALLOW_UPDATE = "TOKEN_ALLOW_UPDATE"; + public const string TOKEN_ALLOW_DELETE = "TOKEN_ALLOW_DELETE"; + public const string VAULT_CACHE_KEY = "VAULT_CACHE_KEY"; +} diff --git a/code/api/src/Models/Static/AppCookies.cs b/code/api/src/Models/Static/AppCookies.cs new file mode 100644 index 0000000..e307b83 --- /dev/null +++ b/code/api/src/Models/Static/AppCookies.cs @@ -0,0 +1,7 @@ +namespace IOL.GreatOffice.Api.Data.Static; + +public static class AppCookies +{ + public const string Locale = "go_locale"; + public const string Session = "go_session"; +} \ No newline at end of file diff --git a/code/api/src/Models/Static/AppDateTime.cs b/code/api/src/Models/Static/AppDateTime.cs new file mode 100644 index 0000000..880d2a8 --- /dev/null +++ b/code/api/src/Models/Static/AppDateTime.cs @@ -0,0 +1,16 @@ +namespace IOL.GreatOffice.Api.Data.Static; + +public static class AppDateTime +{ + private static DateTime? dateTime; + + public static DateTime UtcNow => dateTime ?? DateTime.UtcNow; + + public static void Set(DateTime setDateTime) { + dateTime = setDateTime; + } + + public static void Reset() { + dateTime = null; + } +} diff --git a/code/api/src/Models/Static/AppEnvironmentVariables.cs b/code/api/src/Models/Static/AppEnvironmentVariables.cs new file mode 100644 index 0000000..c3f821d --- /dev/null +++ b/code/api/src/Models/Static/AppEnvironmentVariables.cs @@ -0,0 +1,21 @@ +namespace IOL.GreatOffice.Api.Data.Static; + +public static class AppEnvironmentVariables +{ + /// + /// An access token that can be used to access the Hashicorp Vault instance that is available at VAULT_URL + /// + public const string VAULT_TOKEN = "VAULT_TOKEN"; + /// + /// An url pointing to the Hashicorp Vault instance the app should use + /// + public const string VAULT_URL = "VAULT_URL"; + /// + /// The duration of which to keep a local cached version of the configuration + /// + public const string VAULT_CACHE_TTL = "VAULT_CACHE_TTL"; + /// + /// The vault key name for the main configuration json object, described by + /// + public const string MAIN_CONFIG_SHEET = "MAIN_CONFIG_SHEET"; +} diff --git a/code/api/src/Models/Static/AppHeaders.cs b/code/api/src/Models/Static/AppHeaders.cs new file mode 100644 index 0000000..d534aba --- /dev/null +++ b/code/api/src/Models/Static/AppHeaders.cs @@ -0,0 +1,7 @@ +namespace IOL.GreatOffice.Api.Data.Static; + +public static class AppHeaders +{ + public const string BROWSER_TIME_ZONE = "X-TimeZone"; + public const string IS_KNOWN_PROBLEM = "X-IsKnownProblem"; +} diff --git a/code/api/src/Models/Static/AppPaths.cs b/code/api/src/Models/Static/AppPaths.cs new file mode 100644 index 0000000..a24f5af --- /dev/null +++ b/code/api/src/Models/Static/AppPaths.cs @@ -0,0 +1,17 @@ + +namespace IOL.GreatOffice.Api.Data.Static; + +public static class AppPaths +{ + public static AppPath AppData => new() { + HostPath = Path.Combine(Directory.GetCurrentDirectory(), "AppData") + }; + + public static AppPath DataProtectionKeys => new() { + HostPath = Path.Combine(Directory.GetCurrentDirectory(), "AppData", "dp-keys") + }; + + public static AppPath Frontend => new() { + HostPath = Path.Combine(Directory.GetCurrentDirectory(), "Frontend") + }; +} diff --git a/code/api/src/Models/Static/JsonSettings.cs b/code/api/src/Models/Static/JsonSettings.cs new file mode 100644 index 0000000..a163c11 --- /dev/null +++ b/code/api/src/Models/Static/JsonSettings.cs @@ -0,0 +1,11 @@ +namespace IOL.GreatOffice.Api.Data.Static; + +public static class JsonSettings +{ + public static Action Default { get; } = options => { + options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; + options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; + options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString; + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + }; +} diff --git a/code/api/src/Program.cs b/code/api/src/Program.cs new file mode 100644 index 0000000..3da1111 --- /dev/null +++ b/code/api/src/Program.cs @@ -0,0 +1,249 @@ +global using System; +global using System.Linq; +global using System.IO; +global using System.Threading; +global using System.Threading.Tasks; +global using System.Collections.Generic; +global using System.ComponentModel.DataAnnotations.Schema; +global using System.Security.Claims; +global using System.Text.Json; +global using System.Text.Json.Serialization; +global using IOL.GreatOffice.Api.Data.Database; +global using IOL.GreatOffice.Api.Data.Enums; +global using IOL.GreatOffice.Api.Data.Models; +global using IOL.GreatOffice.Api.Data.Static; +global using IOL.GreatOffice.Api.Services; +global using IOL.GreatOffice.Api.Utilities; +global using IOL.Helpers; +global using Microsoft.OpenApi.Models; +global using Microsoft.AspNetCore.Authentication.Cookies; +global using Microsoft.AspNetCore.Builder; +global using Microsoft.AspNetCore.DataProtection; +global using Microsoft.AspNetCore.Authorization; +global using Microsoft.AspNetCore.Http; +global using Microsoft.AspNetCore.Authentication; +global using Microsoft.AspNetCore.Mvc; +global using Microsoft.EntityFrameworkCore; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.Localization; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Hosting; +global using Microsoft.Extensions.Logging; +global using Serilog; +global using Quartz; +global using IOL.GreatOffice.Api.Resources; +using IOL.GreatOffice.Api.Endpoints.V1; +using IOL.GreatOffice.Api.Jobs; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.Localization; +using Microsoft.AspNetCore.Mvc.Versioning; +using Serilog.Events; + +namespace IOL.GreatOffice.Api; + +public static class Program +{ + public static WebApplicationBuilder CreateAppBuilder(string[] args) { + var builder = WebApplication.CreateBuilder(args); + builder.Services.AddLogging(); + builder.Services.AddHttpClient(); + builder.Services.AddMemoryCache(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddSingleton(); + builder.Services.AddHttpClient(); + builder.Services.AddHttpClient(); + var vaultService = builder.Services.BuildServiceProvider().GetRequiredService(); + var configuration = vaultService.GetCurrentAppConfiguration(); + var logger = new LoggerConfiguration() + .MinimumLevel.Information() + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) + .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Information) + .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) + .WriteTo.Console(); + + if (!builder.Environment.IsDevelopment() && configuration.SEQ_API_KEY.HasValue() && configuration.SEQ_API_URL.HasValue()) { + logger.WriteTo.Seq(configuration.SEQ_API_URL, apiKey: configuration.SEQ_API_KEY); + } + + Log.Logger = logger.CreateLogger(); + Log.Information("Starting web host, " + + JsonSerializer.Serialize(configuration.GetPublicVersion(), + new JsonSerializerOptions() { + WriteIndented = true + })); + + builder.Host.UseSerilog(Log.Logger); + + if (builder.Environment.IsDevelopment()) { + builder.Services.AddCors(); + } + + if (builder.Environment.IsProduction()) { + builder.Services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedProto; }); + } + + builder.Services.AddLocalization(); + builder.Services.AddRequestLocalization(options => { + var supportedCultures = new[] {"en", "nb"}; + options.SetDefaultCulture(supportedCultures[0]) + .AddSupportedCultures(supportedCultures) + .AddSupportedUICultures(supportedCultures); + options.ApplyCurrentCultureToResponseHeaders = true; + }); + + builder.Services.Configure(options => { + options.AddInitialRequestCultureProvider(new CustomRequestCultureProvider(async context => + // Get culture from specific cookie + await Task.FromResult(new ProviderCultureResult(context.Request.Cookies[AppCookies.Locale] ?? "en"))) + ); + }); + + builder.Services + .AddDataProtection() + .ProtectKeysWithCertificate(configuration.CERT1()) + .PersistKeysToDbContext(); + + builder.Services.Configure(JsonSettings.Default); + builder.Services.AddQuartz(options => { + options.UsePersistentStore(o => { + o.UsePostgres(builder.Configuration.GetQuartzDatabaseConnectionString(vaultService.GetCurrentAppConfiguration)); + o.UseSerializer(); + }); + options.UseMicrosoftDependencyInjectionJobFactory(); + options.RegisterJobs(); + }); + + builder.Services.AddQuartzHostedService(options => { options.WaitForJobsToComplete = true; }); + builder.Services.AddAuthentication(options => { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; + }) + .AddCookie(options => { + options.Cookie.Name = AppCookies.Session; + options.Cookie.Domain = builder.Environment.IsDevelopment() ? "localhost" : ".greatoffice.app"; + options.Cookie.HttpOnly = true; + options.Cookie.IsEssential = true; + options.SlidingExpiration = true; + options.Events.OnRedirectToAccessDenied = + options.Events.OnRedirectToLogin = c => { + c.Response.StatusCode = StatusCodes.Status401Unauthorized; + return Task.FromResult(null); + }; + }) + .AddScheme(AppConstants.BASIC_AUTH_SCHEME, default); + + builder.Services.AddDbContext(options => { + options.UseNpgsql(builder.Configuration.GetAppDatabaseConnectionString(vaultService.GetCurrentAppConfiguration), + npgsqlDbContextOptionsBuilder => { + npgsqlDbContextOptionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + npgsqlDbContextOptionsBuilder.EnableRetryOnFailure(5, TimeSpan.FromSeconds(10), default); + }) + .UseSnakeCaseNamingConvention(); + if (builder.Environment.IsDevelopment()) { + options.EnableSensitiveDataLogging(); + } + }); + + builder.Services.AddApiVersioning(options => { + options.ApiVersionReader = new UrlSegmentApiVersionReader(); + options.ReportApiVersions = true; + options.AssumeDefaultVersionWhenUnspecified = false; + }); + builder.Services.AddVersionedApiExplorer(options => { options.SubstituteApiVersionInUrl = true; }); + builder.Services.AddSwaggerGen(options => { + options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "IOL.GreatOffice.Api.xml")); + options.UseApiEndpoints(); + options.OperationFilter(); + options.OperationFilter(); + options.SwaggerDoc(ApiSpecV1.Document.VersionName, ApiSpecV1.Document.OpenApiInfo); + options.AddSecurityDefinition("Basic", + new OpenApiSecurityScheme { + Name = "Authorization", + Type = SecuritySchemeType.ApiKey, + Scheme = "Basic", + BearerFormat = "Basic", + In = ParameterLocation.Header, + Description = + "Enter your token in the text input below.\r\n\r\nExample: \"Basic 12345abcdef\"", + }); + + options.AddSecurityRequirement(new OpenApiSecurityRequirement { + { + new OpenApiSecurityScheme { + Reference = new OpenApiReference { + Type = ReferenceType.SecurityScheme, + Id = "Basic" + } + }, + Array.Empty() + } + }); + }); + + builder.Services.AddPagination(options => { + options.DefaultSize = 50; + options.MaxSize = 100; + options.CanChangeSizeFromQuery = true; + }); + + builder.Services + .AddControllers() + .AddDataAnnotationsLocalization() + .AddJsonOptions(JsonSettings.Default); + + return builder; + } + + public static WebApplication CreateWebApplication(WebApplicationBuilder builder) { + var app = builder.Build(); + if (app.Environment.IsDevelopment()) { + app.UseDeveloperExceptionPage(); + app.UseCors(cors => { + cors.AllowAnyMethod(); + cors.AllowAnyHeader(); + cors.SetIsOriginAllowed(_ => true); + cors.AllowCredentials(); + cors.WithExposedHeaders(AppHeaders.IS_KNOWN_PROBLEM); + }); + } + + if (app.Environment.IsProduction()) { + app.UseForwardedHeaders(); + } + + app.UseDefaultFiles() + .UseStaticFiles() + .UseRequestLocalization() + .UseRouting() + .UseSerilogRequestLogging() + .UseStatusCodePages() + .UseAuthentication() + .UseAuthorization() + .UseSwagger() + .UseSwaggerUI(options => { + options.SwaggerEndpoint(ApiSpecV1.Document.SwaggerPath, ApiSpecV1.Document.VersionName); + options.DocumentTitle = AppConstants.API_NAME; + }) + .UseEndpoints(endpoints => { endpoints.MapControllers(); }); + return app; + } + + public static int Main(string[] args) { + try { + CreateWebApplication(CreateAppBuilder(args)).Run(); + return 0; + } catch (Exception ex) { + Log.Fatal(ex, "Unhandled exception"); + return 1; + } + finally { + Log.Information("Shut down complete, flushing logs..."); + Log.CloseAndFlush(); + } + } +} \ No newline at end of file diff --git a/code/api/src/Properties/launchSettings.json b/code/api/src/Properties/launchSettings.json new file mode 100644 index 0000000..ebd333b --- /dev/null +++ b/code/api/src/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IOL.GreatOffice": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": false, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/code/api/src/Resources/SharedResources.cs b/code/api/src/Resources/SharedResources.cs new file mode 100644 index 0000000..f98895b --- /dev/null +++ b/code/api/src/Resources/SharedResources.cs @@ -0,0 +1,4 @@ +namespace IOL.GreatOffice.Api.Resources; + +public class SharedResources +{ } \ No newline at end of file diff --git a/code/api/src/Resources/SharedResources.en.Designer.cs b/code/api/src/Resources/SharedResources.en.Designer.cs new file mode 100644 index 0000000..02ada48 --- /dev/null +++ b/code/api/src/Resources/SharedResources.en.Designer.cs @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace IOL.GreatOffice.Api.Resources { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SharedResources_en { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SharedResources_en() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("IOL.GreatOffice.Api.Resources.SharedResources_en", typeof(SharedResources_en).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string Name_is_a_required_field { + get { + return ResourceManager.GetString("Name is a required field", resourceCulture); + } + } + + internal static string Start_is_a_required_field { + get { + return ResourceManager.GetString("Start is a required field", resourceCulture); + } + } + + internal static string Invalid_form { + get { + return ResourceManager.GetString("Invalid form", resourceCulture); + } + } + + internal static string One_or_more_validation_errors_occured { + get { + return ResourceManager.GetString("One or more validation errors occured", resourceCulture); + } + } + + internal static string One_or_more_fields_is_invalid { + get { + return ResourceManager.GetString("One or more fields is invalid", resourceCulture); + } + } + + internal static string Invalid_username_or_password { + get { + return ResourceManager.GetString("Invalid username or password", resourceCulture); + } + } + } +} diff --git a/code/api/src/Resources/SharedResources.en.resx b/code/api/src/Resources/SharedResources.en.resx new file mode 100644 index 0000000..87e46e0 --- /dev/null +++ b/code/api/src/Resources/SharedResources.en.resx @@ -0,0 +1,45 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name is a required field + Name is a required field + + + Start is a required field + Start is a required field + + + Invalid form + Invalid form + + + One or more validation errors occured + One or more validation errors occured + + + One or more fields is invalid + One or more fields is invalid + + + Invalid username or password + Invalid username or password + + \ No newline at end of file diff --git a/code/api/src/Resources/SharedResources.nb.Designer.cs b/code/api/src/Resources/SharedResources.nb.Designer.cs new file mode 100644 index 0000000..d6095f3 --- /dev/null +++ b/code/api/src/Resources/SharedResources.nb.Designer.cs @@ -0,0 +1,120 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace IOL.GreatOffice.Api.Resources { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SharedResources_nb { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SharedResources_nb() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("IOL.GreatOffice.Api.Resources.SharedResources_nb", typeof(SharedResources_nb).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string Name_is_a_required_field { + get { + return ResourceManager.GetString("Name is a required field", resourceCulture); + } + } + + internal static string Start_is_a_required_field { + get { + return ResourceManager.GetString("Start is a required field", resourceCulture); + } + } + + internal static string Invalid_form { + get { + return ResourceManager.GetString("Invalid form", resourceCulture); + } + } + + internal static string One_or_more_fields_is_invalid { + get { + return ResourceManager.GetString("One or more fields is invalid", resourceCulture); + } + } + + internal static string Invalid_username_or_password { + get { + return ResourceManager.GetString("Invalid username or password", resourceCulture); + } + } + + internal static string One_or_more_validation_errors_occured { + get { + return ResourceManager.GetString("One or more validation errors occured", resourceCulture); + } + } + + internal static string Greatoffice_Email_Validation { + get { + return ResourceManager.GetString("Greatoffice Email Validation", resourceCulture); + } + } + + internal static string Reset_password___Greatoffice { + get { + return ResourceManager.GetString("Reset password - Greatoffice", resourceCulture); + } + } + + internal static string The_password_requires_6_or_more_characters_ { + get { + return ResourceManager.GetString("The password requires 6 or more characters.", resourceCulture); + } + } + + internal static string There_is_already_a_user_registered_with_username___username_ { + get { + return ResourceManager.GetString("There is already a user registered with username: {username}", resourceCulture); + } + } + + internal static string _username__does_not_look_like_a_valid_email { + get { + return ResourceManager.GetString("{username} does not look like a valid email", resourceCulture); + } + } + + internal static string Could_not_create_your_account__try_again_soon_ { + get { + return ResourceManager.GetString("Could not create your account, try again soon.", resourceCulture); + } + } + } +} diff --git a/code/api/src/Resources/SharedResources.nb.resx b/code/api/src/Resources/SharedResources.nb.resx new file mode 100644 index 0000000..b3513c9 --- /dev/null +++ b/code/api/src/Resources/SharedResources.nb.resx @@ -0,0 +1,101 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + Navn er et påkrevd felt + Name is a required field + + + Start er et påkrevd felt + Start is a required field + + + Ugyldig skjema + Invalid form + + + One or more fields is invalid + En eller flere felt er ugyldige + + + Ugyldig brukernavn eller passord + Invalid username or password + + + En eller flere valideringsfeil har oppstått + + + Hei, {0}. + +Bekreft din e-postadresse ved å åpne denne lenken i en nettleser {1} + Hello, {0}. + + Validate your email address by opening this link in a browser {1} + + + Greatoffice e-postaddresse bekreftelse + Greatoffice Email Validation + + + Hei {0}, + +Åpne denne lenken i en nettleser for å sette nytt passord. + +{1}/reset-password/{2} + +Lenken utgår {3}. +Hvis du ikke vet hva dette gjelder, kan du se bort i fra mailen. + Hi {0}, + + Go to the following link to set a new password. + + {1}/reset-password/{2} + + The link expires at {3}. + If you did not request a password reset, no action is required. + + + Sett nytt passord - Greatoffice + Reset password - Greatoffice + + + Passordet må være 6 eller flere tegn. + The password requires 6 or more characters. + + + Det finnes allerede en bruker med brukernavnet: {username} + There is already a user registered with username: {username} + + + {username} ser ikke ut som en gyldig e-postadresse + {username} does not look like a valid email + + + Kunne ikke lage din konto, prøv igjen snart. + Could not create your account, try again soon. + + \ No newline at end of file diff --git a/code/api/src/Resources/SharedResources.resx b/code/api/src/Resources/SharedResources.resx new file mode 100644 index 0000000..833428b --- /dev/null +++ b/code/api/src/Resources/SharedResources.resx @@ -0,0 +1,33 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Name is a required field + Name is a required field + + + Start is a required field + Start is a required field + + + Invalid form + Invalid form + + + One or more fields is invalid + One or more fields is invalid + + + + + \ No newline at end of file diff --git a/code/api/src/Services/EmailValidationService.cs b/code/api/src/Services/EmailValidationService.cs new file mode 100644 index 0000000..5552c4f --- /dev/null +++ b/code/api/src/Services/EmailValidationService.cs @@ -0,0 +1,67 @@ +namespace IOL.GreatOffice.Api.Services; + +public class EmailValidationService +{ + private readonly IStringLocalizer _localizer; + private readonly MainAppDatabase _database; + private readonly MailService _mailService; + private readonly ILogger _logger; + private readonly string EmailValidationUrl; + + public EmailValidationService(IStringLocalizer localizer, MainAppDatabase database, MailService mailService, ILogger logger, VaultService vaultService) { + _localizer = localizer; + _database = database; + _mailService = mailService; + _logger = logger; + var configuration = vaultService.GetCurrentAppConfiguration(); + EmailValidationUrl = configuration.CANONICAL_BACKEND_URL + "/_/validate"; + } + + public bool FulfillEmailValidationRequest(Guid id, Guid userId) { + var item = _database.ValidationEmails.FirstOrDefault(c => c.Id == id); + if (item == default) { + _logger.LogDebug("Did not find email validation request with id: {requestId}", id); + return false; + } + + if (item.UserId != userId) { + _logger.LogInformation("An unknown user tried to validate the email validation request {requestId}", id); + return false; + } + + var user = _database.Users.FirstOrDefault(c => c.Id == item.UserId); + if (user == default) { + _database.ValidationEmails.Remove(item); + _database.SaveChanges(); + _logger.LogInformation("Deleting request {requestId} because user does not exist anymore", id); + return false; + } + + user.EmailLastValidated = AppDateTime.UtcNow; + _database.ValidationEmails.Remove(item); + _database.Users.Update(user); + _database.SaveChanges(); + _logger.LogInformation("Successfully validated the email for user {userId}", user.Id); + return true; + } + + public async Task SendValidationEmailAsync(User user) { + var queueItem = new ValidationEmail() { + UserId = user.Id, + Id = Guid.NewGuid() + }; + var email = new MailService.PostmarkEmail() { + To = user.Username, + Subject = _localizer["Greatoffice Email Validation"], + TextBody = _localizer[""" +Hello {0}, + +Validate your email address by opening this link in a browser {1} +""", user.DisplayName(true), EmailValidationUrl + "?id=" + queueItem.Id] + }; + queueItem.EmailSentAt = AppDateTime.UtcNow; + _database.ValidationEmails.Add(queueItem); + await _database.SaveChangesAsync(); + Task.Run(async () => await _mailService.SendMailAsync(email)); + } +} \ No newline at end of file diff --git a/code/api/src/Services/MailService.cs b/code/api/src/Services/MailService.cs new file mode 100644 index 0000000..a6f7db4 --- /dev/null +++ b/code/api/src/Services/MailService.cs @@ -0,0 +1,132 @@ +namespace IOL.GreatOffice.Api.Services; + +public class MailService +{ + private readonly ILogger _logger; + private static string _fromEmail; + private readonly HttpClient _httpClient; + + public MailService(VaultService vaultService, ILogger logger, HttpClient httpClient) { + var configuration = vaultService.GetCurrentAppConfiguration(); + _fromEmail = configuration.EMAIL_FROM_ADDRESS; + _logger = logger; + httpClient.DefaultRequestHeaders.Add("X-Postmark-Server-Token", configuration.POSTMARK_TOKEN); + _httpClient = httpClient; + } + + /// + /// Send an email. + /// + /// + /// + public async Task SendMailAsync(PostmarkEmail message) { + try { + if (message.MessageStream.IsNullOrWhiteSpace()) { + message.MessageStream = "outbound"; + } + + if (message.From.IsNullOrWhiteSpace() && _fromEmail.HasValue()) { + message.From = _fromEmail; + } else { + throw new ApplicationException("Not one from-email is available"); + } + + if (message.To.IsNullOrWhiteSpace()) { + throw new ArgumentNullException(nameof(message.To), "A recipient should be specified."); + } + + if (!message.To.IsValidEmailAddress()) { + throw new ArgumentException(nameof(message.To), "To is not a valid email address"); + } + + if (message.HtmlBody.IsNullOrWhiteSpace() && message.TextBody.IsNullOrWhiteSpace()) { + throw new ArgumentNullException(nameof(message), "Both HtmlBody and TextBody is empty, nothing to send"); + } +#if DEBUG + _logger.LogInformation("Sending email: {0}", JsonSerializer.Serialize(message, new JsonSerializerOptions() {WriteIndented = true})); +#endif + var response = await _httpClient.PostAsJsonAsync("https://api.postmarkapp.com/email", message); + _logger.LogInformation("Postmark returned with message: {0}", (await response.Content.ReadFromJsonAsync()).Message); + } catch (Exception e) { + _logger.LogError(e, "A silent exception occured while trying to send an email"); + } + } + + private class PostmarkSendResponse + { + /// + /// The Message ID returned from Postmark. + /// + public Guid MessageID { get; set; } + + /// + /// The message from the API. + /// In the event of an error, this message may contain helpful text. + /// + public string Message { get; set; } + + /// + /// The time the request was received by Postmark. + /// + public DateTime SubmittedAt { get; set; } + + /// + /// The recipient of the submitted request. + /// + public string To { get; set; } + + /// + /// The error code returned from Postmark. + /// This does not map to HTTP status codes. + /// + public int ErrorCode { get; set; } + } + + public class PostmarkEmail + { + /// + /// The message subject line. + /// + public string Subject { get; set; } + + /// + /// The message body, if the message contains + /// + public string HtmlBody { get; set; } + + /// + /// The message body, if the message is plain text. + /// + public string TextBody { get; set; } + + /// + /// The message stream used to send this message. + /// + public string MessageStream { get; set; } + + /// + /// The sender's email address. + /// + public string From { get; set; } + + /// + /// Any recipients. Separate multiple recipients with a comma. + /// + public string To { get; set; } + + /// + /// Any CC recipients. Separate multiple recipients with a comma. + /// + public string Cc { get; set; } + + /// + /// Any BCC recipients. Separate multiple recipients with a comma. + /// + public string Bcc { get; set; } + + /// + /// The email address to reply to. This is optional. + /// + public string ReplyTo { get; set; } + } +} \ No newline at end of file diff --git a/code/api/src/Services/PasswordResetService.cs b/code/api/src/Services/PasswordResetService.cs new file mode 100644 index 0000000..4c00b81 --- /dev/null +++ b/code/api/src/Services/PasswordResetService.cs @@ -0,0 +1,101 @@ +namespace IOL.GreatOffice.Api.Services; + +public class PasswordResetService +{ + private readonly MainAppDatabase _database; + private readonly MailService _mailService; + private readonly AppConfiguration _configuration; + private readonly ILogger _logger; + private readonly IStringLocalizer _localizer; + + public PasswordResetService( + MainAppDatabase database, + VaultService vaultService, + ILogger logger, + MailService mailService, IStringLocalizer localizer) { + _database = database; + _configuration = vaultService.GetCurrentAppConfiguration(); + _logger = logger; + _mailService = mailService; + _localizer = localizer; + } + + public async Task GetRequestAsync(Guid id, CancellationToken cancellationToken = default) { + var request = await _database.PasswordResetRequests + .Include(c => c.User) + .SingleOrDefaultAsync(c => c.Id == id, cancellationToken); + if (request == default) { + return default; + } + + _logger.LogInformation($"Found password reset request for user: {request.User.Username}, expires at {request.ExpirationDate} (in {request.ExpirationDate.Subtract(AppDateTime.UtcNow).Minutes} minutes)."); + return request; + } + + public async Task FulfillRequestAsync(Guid id, string newPassword, CancellationToken cancellationToken = default) { + var request = await GetRequestAsync(id, cancellationToken); + if (request == default) return FulfillPasswordResetRequestResult.REQUEST_NOT_FOUND; + var user = _database.Users.FirstOrDefault(c => c.Id == request.User.Id); + if (user == default) return FulfillPasswordResetRequestResult.USER_NOT_FOUND; + user.HashAndSetPassword(newPassword); + _database.Users.Update(user); + await _database.SaveChangesAsync(cancellationToken); + _logger.LogInformation($"Fullfilled password reset request for user: {request.User.Username}"); + await DeleteRequestsForUserAsync(user.Id, cancellationToken); + return FulfillPasswordResetRequestResult.FULFILLED; + } + + public async Task AddRequestAsync(User user, TimeZoneInfo requestTz, CancellationToken cancellationToken = default) { + await DeleteRequestsForUserAsync(user.Id, cancellationToken); + var request = new PasswordResetRequest(user); + _database.PasswordResetRequests.Add(request); + await _database.SaveChangesAsync(cancellationToken); + var zonedExpirationDate = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(request.ExpirationDate, requestTz.Id); + var message = new MailService.PostmarkEmail() { + To = request.User.Username, + Subject = _localizer["Reset password - Greatoffice"], + TextBody = _localizer[""" +Hi {0}, + +Go to the following link to set a new password. + +{1}/reset-password/{2} + +The link expires at {3}. +If you did not request a password reset, no action is required. +""", user.DisplayName(true), _configuration.CANONICAL_FRONTEND_URL, request.Id, zonedExpirationDate.ToString("yyyy-MM-dd hh:mm")] + }; + +#pragma warning disable 4014 + Task.Run(() => { +#pragma warning restore 4014 + _mailService.SendMailAsync(message); + _logger.LogInformation($"Added password reset request for user: {request.User.Username}, expires in {request.ExpirationDate.Subtract(AppDateTime.UtcNow)}."); + }, + cancellationToken); + } + + public async Task DeleteRequestsForUserAsync(Guid userId, CancellationToken cancellationToken = default) { + var requestsToRemove = _database.PasswordResetRequests.Where(c => c.UserId == userId).ToList(); + if (!requestsToRemove.Any()) return; + _database.PasswordResetRequests.RemoveRange(requestsToRemove); + await _database.SaveChangesAsync(cancellationToken); + _logger.LogInformation($"Deleted {requestsToRemove.Count} password reset requests for user: {userId}."); + } + + public async Task DeleteStaleRequestsAsync(CancellationToken cancellationToken = default) { + var deleteCount = 0; + foreach (var request in _database.PasswordResetRequests.Where(c => c.IsExpired)) { + if (!request.IsExpired) { + continue; + } + + _database.PasswordResetRequests.Remove(request); + deleteCount++; + _logger.LogInformation($"Marking password reset request with id: {request.Id} for deletion, expiration date was {request.ExpirationDate}."); + } + + await _database.SaveChangesAsync(cancellationToken); + _logger.LogInformation($"Deleted {deleteCount} stale password reset requests."); + } +} \ No newline at end of file diff --git a/code/api/src/Services/TenantService.cs b/code/api/src/Services/TenantService.cs new file mode 100644 index 0000000..0de6f53 --- /dev/null +++ b/code/api/src/Services/TenantService.cs @@ -0,0 +1,61 @@ +namespace IOL.GreatOffice.Api.Services; + +public class TenantService +{ + private readonly MainAppDatabase _database; + private readonly ILogger _logger; + + public TenantService(MainAppDatabase database, ILogger logger) { + _database = database; + _logger = logger; + } + + public Tenant CreateTenant(string name, Guid masterUserId, string email) { + var tenant = new Tenant() { + Name = name, + Slug = Slug.Generate(true, name), + MasterUserId = masterUserId, + ContactEmail = email + }; + + var masterUserExists = _database.Users.Any(c => c.Id == tenant.MasterUserId); + if (!masterUserExists) { + _logger.LogError("Tried to create a new tenant with a non-existent master user: {masterUserId}", masterUserId); + return default; + } + + if (!email.IsValidEmailAddress()) { + _logger.LogError("Tried to create a new tenant with an invalid email {contactEmail}", email); + return default; + } + + _database.Tenants.Add(tenant); + _database.SaveChanges(); + return tenant; + } + + public void AddUserToTenant(Guid userId, Guid tenantId) { + var tenant = _database.Tenants.FirstOrDefault(c => c.Id == tenantId); + if (tenant == default) { + _logger.LogError("Tried adding user {userId} to tenant {tenantId} but the tenant was not found", userId, tenantId); + return; + } + + var user = _database.Users.FirstOrDefault(c => c.Id == userId); + if (user == default) { + _logger.LogError("Tried adding user {userId} to tenant {tenantId} but the user was not found", userId, tenantId); + return; + } + + if (tenant.Users.Any(c => c.Id == user.Id)) { + _logger.LogDebug("User {userId} is already a part of tenant {tenantId}", userId, tenantId); + return; + } + + tenant.Users.Add(user); + tenant.SetModified(); + _database.Tenants.Update(tenant); + _database.SaveChanges(); + _logger.LogInformation("Added user {userId} to tenant {tenantId}", userId, tenantId); + } +} \ No newline at end of file diff --git a/code/api/src/Services/UserService.cs b/code/api/src/Services/UserService.cs new file mode 100644 index 0000000..4df8ded --- /dev/null +++ b/code/api/src/Services/UserService.cs @@ -0,0 +1,54 @@ +namespace IOL.GreatOffice.Api.Services; + +public class UserService +{ + private readonly PasswordResetService _passwordResetService; + private readonly ILogger _logger; + private readonly MainAppDatabase _database; + + public UserService(PasswordResetService passwordResetService, ILogger logger, MainAppDatabase database) { + _passwordResetService = passwordResetService; + _logger = logger; + _database = database; + } + + public async Task LogInUserAsync(HttpContext httpContext, User user, bool persist = false, CancellationToken cancellationToken = default) { + var identity = new ClaimsIdentity(user.DefaultClaims(), CookieAuthenticationDefaults.AuthenticationScheme); + var principal = new ClaimsPrincipal(identity); + var authenticationProperties = new AuthenticationProperties { + AllowRefresh = true, + IssuedUtc = DateTimeOffset.UtcNow, + }; + + if (persist) { + authenticationProperties.ExpiresUtc = DateTimeOffset.UtcNow.AddMonths(6); + authenticationProperties.IsPersistent = true; + } + + await httpContext.SignInAsync(principal, authenticationProperties); + await _passwordResetService.DeleteRequestsForUserAsync(user.Id, cancellationToken); + _logger.LogInformation("Logged in user {userId}", user.Id); + } + + public async Task LogOutUser(HttpContext httpContext, CancellationToken cancellationToken = default) { + await httpContext.SignOutAsync(); + _logger.LogInformation("Logged out user {userId}", httpContext.User.FindFirst(AppClaims.USER_ID)); + } + + public async Task MarkUserAsDeleted(Guid userId, Guid actorId) { + var user = _database.Users.FirstOrDefault(c => c.Id == userId); + if (user == default) { + _logger.LogInformation("Tried to delete unknown user {userId}", userId); + return; + } + + if (user.Username is "demo@greatoffice.app" or "tester@greatoffice.app") { + _logger.LogInformation("Not deleting user {userId}, because it's username is {username}", user.Id, user.Username); + return; + } + + await _passwordResetService.DeleteRequestsForUserAsync(user.Id); + user.SetDeleted(actorId); + await _database.SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/code/api/src/Services/VaultService.cs b/code/api/src/Services/VaultService.cs new file mode 100644 index 0000000..cd2eecf --- /dev/null +++ b/code/api/src/Services/VaultService.cs @@ -0,0 +1,237 @@ +using Microsoft.Extensions.Caching.Memory; + +namespace IOL.GreatOffice.Api.Services; + +public class VaultService +{ + private readonly HttpClient _client; + private readonly IMemoryCache _cache; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + private int CACHE_TTL { get; set; } + + public VaultService(HttpClient client, IConfiguration configuration, IMemoryCache cache, ILogger logger) { + var token = configuration.GetValue(AppEnvironmentVariables.VAULT_TOKEN); + var vaultUrl = configuration.GetValue(AppEnvironmentVariables.VAULT_URL); + CACHE_TTL = configuration.GetValue(AppEnvironmentVariables.VAULT_CACHE_TTL, 60 * 60 * 12); + if (token.IsNullOrWhiteSpace()) throw new ApplicationException("VAULT_TOKEN is empty"); + if (vaultUrl.IsNullOrWhiteSpace()) throw new ApplicationException("VAULT_URL is empty"); + client.DefaultRequestHeaders.Add("X-Vault-Token", token); + client.BaseAddress = new Uri(vaultUrl); + _client = client; + _cache = cache; + _configuration = configuration; + _logger = logger; + } + + public async Task GetSecretJsonAsync(string path) { + var result = await _cache.GetOrCreate(AppConstants.VAULT_CACHE_KEY, + async cacheEntry => { + cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(CACHE_TTL); + var getSecretResponse = await _client.GetFromJsonAsync>("/v1/kv/data/" + path); + if (getSecretResponse == null) { + return default; + } + + Log.Debug("Setting new vault cache, " + new { + PATH = path, + CACHE_TTL, + Data = JsonSerializer.Serialize(getSecretResponse.Data.Data) + }); + return getSecretResponse.Data.Data ?? default; + }); + return result; + } + + public Task RefreshAsync(string path) { + _cache.Remove(AppConstants.VAULT_CACHE_KEY); + CACHE_TTL = _configuration.GetValue(AppEnvironmentVariables.VAULT_CACHE_TTL, 60 * 60 * 12); + return GetSecretJsonAsync(path); + } + + public async Task RenewTokenAsync() { + var response = await _client.PostAsJsonAsync("v1/auth/token/renew-self", new {increment = "2h"}); + if (response.IsSuccessStatusCode) { + return await response.Content.ReadFromJsonAsync(); + } + + return default; + } + + public async Task LookupTokenAsync() { + var response = await _client.GetAsync("v1/auth/token/lookup-self"); + if (response.IsSuccessStatusCode) { + return await response.Content.ReadFromJsonAsync(); + } + + return default; + } + + public AppConfiguration GetCurrentAppConfiguration() { +#if DEBUG + var isInFlightMode = true; + if (isInFlightMode) { + return new AppConfiguration() { + EMAIL_FROM_ADDRESS = "heydev@greatoffice.life", + DB_HOST = "localhost", + DB_PORT = "5432", + DB_NAME = "greatoffice_ivar_dev", + DB_PASSWORD = "ivar123", + DB_USER = "postgres", + CANONICAL_FRONTEND_URL = "http://localhost:5173", + CANONICAL_BACKEND_URL = "http://localhost:5000", + POSTMARK_TOKEN = "b530c311-45c7-43e5-aa48-f2c992886e51", + QUARTZ_DB_HOST = "localhost", + QUARTZ_DB_PORT = "5432", + QUARTZ_DB_NAME = "greatoffice_quartz_ivar_dev", + QUARTZ_DB_PASSWORD = "ivar123", + QUARTZ_DB_USER = "postgres", + APP_CERT = "MIII2QIBAzCCCJ8GCSqGSIb3DQEHAaCCCJAEggiMMIIIiDCCAz8GCSqGSIb3DQEHBqCCAzAwggMsAgEAMIIDJQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI1JebRQOOJekCAggAgIIC+FTMxILwgOWxknEWvucjaHTtf/KUcSPwc/zWg0RoFzu03o1vZkStztz92L2J+nnNrQti7WEx0C/h5ug67qCQAdzkjNHZE9wn1pI2EqaCwKYwZnOTmyJDLV86xQlH9QYTs4/L1F2qTwpKdBoB2lNyTswYgZ8WNYY65fbFKUUVIaHkReGnma/ERm8F38Ymp7cudqQ4G6sh6E+JFSo2IfcTFb260fWO/iMDU3GryNsaUl4amT4aQfsSrNtf6ODy8Ivh7tJeLbR6bqzMtsKPzavT5ZbA6AP2GYAQSVejNP79lqmtoOs8+dz7HaJdxORBESYi02z+j2rb4ssI+ZPx+YtGvgpr79gVf3qM1/VX4ROzLDUUJGWS2RnRDBBY/d0uMqftndUHSPMFuhfvgBUXdzLqhoEqYNMTP3BpOyBQ7f26soLKrc3zup2WIn8WSnQFLi2D8cWPVPS9iAb0EgQ5cBjuaLz2aX1WVMCSzya7a6Z93rLxUf9s3+PEy75ibhoh/cJvAlCMTfiVAhJOaIroR1K4MKkO23ylyLHv49/2GYIaZ8n0WRO57fM5jDUhOfti+ZzPM6hrSJkRSla+pr8DFlpqOqObksGwxGGTqq6ZvWon19kXesFl5n640uJBu7Viq8IdxGAbX/aRkZNlvja7sOgfiNz3Hxomz7DWwgWLKaNKlFSqFMzsTUye+mUByC1AfEn14/SYyyxRTB99PmItxWFyjo9nOsxH5riz7tPTPxUXzhVb4eDt7PjY+ZsEKTC3a/LFqf3k5MWk+qc4p0Kx1sGaGEAPCCE04mZ7NOdqk6dhoP46FNUEh8CmxDDVaMSdThrvyzv9KrclwQnRMJz7BJWVXUemyQl3aModepXIhvLv90nH1qzYlFDQ0H6rxzCB4f1l//GoWPyYFBxGh6UrkunXWx2fopR87zi2OF3azxqscF/qLVU4FHKzhMrec7eE0/dk3d+0If/AxQ4p7Cso92i/5n0Bsg5DWc4EIWBuldodsjVxxq7dKxinKJkwggVBBgkqhkiG9w0BBwGgggUyBIIFLjCCBSowggUmBgsqhkiG9w0BDAoBAqCCBO4wggTqMBwGCiqGSIb3DQEMAQMwDgQIb6GEBS5DxrkCAggABIIEyHcCXqJMUG8t0PhvDwf0dHZo6SiA2WsLz1hM+KgNBrE8YwuXEZTGYzfHy85cEWNB2WLV5kxgu/vtifCnnlS1bvc2kMKT3dIFER/7hOqRh8pNvzMoeNc4zNkiEB1ZXxlctUKDsQozbLUhnRATwNyeaMkt3B0KQuRaMxGuA9riRISnmGd1K5GTm3VZ0I7e6vDqXCllLzfOQ+aoz8WIOFJ1aoN2E5+bDTtcr18xYJMd+kNOMjMcbg5f9kmNZAk1MBRuiEWtUjMhRySYWk1Km/y5WHRNRShHTae/E4ifmpLuUKsfOjX7T/4RDWg8rYCnxUpLfCln+omQ9t0gFSN+Et7Dw+cyW48Kkrw6StnRz/AeLxo3SU/PAXVazmAa6ZkuNe+uasvTniYM+enw4hgcXPzTu90R40fTGHO1Sp8EV3IbvrtwFu9kjCxtgleLQ139HTtpWXjVZ0o1ikmn2uN4f73gxKIKxmSX4xZZN6IDOze3OOY1aalUIzkbwFAYCV74zEL05dJzo3GOOJfdQsp2GNJPfkcAcuMPMvi+mieBk6XjKDCj95b41hSWDqfuMUgPh3xm3/felVD1HRNO9NF0RscosP02NLi44TcNz4LX9j/E9PHpNFF+W4ba1w7v7h4P5/leQFRP7+H90fPHA2M8UOHZ4AwmwdA5sHYXBoXkVS3snbVzhzkvW5GblFn0l1AFj8AO0HLCwGSumZ1uUEvEA021hmluHbs62iIiOYJbacbcT/TUpO5/2tFMPKr042LmpQFDIuEfrurLTC3r1iXuS6fkWbf2IxdjTrtL61AtPqtFagKSGsyHViO7nPu6yhbhTbmQJ4G0t++b6h18RPS+3muwrnSxgAz6OmbBWybNKOlAyTjd4JO3hfCaQ+K/mO2Q9TnSUOTgeobXXZsOEdltPXFJExQ7+cqkr4gKdPeoTZEcv8jRoS+NHasZIvMPGrwYnvOuSJ09qAwtIcvhGaPkEmTZ6b3wQl0mnTMPCQHXGTXztucB0O33kbn8sClfs6P6dg0GdR6ZnNFacwIpe8T8PmLg5q8bu5FL1eNo4+ijzC64lrZkKeRKKT1vBtZfcGQTvE8TTdQQS5MkKcptfL/3HVE9VopNZlzryJGYj89GMeQ1PfABi1Ovs5gjfro+0xBbtVuAWbP8dM5ugozO1//vjTMZYwXml4nIFkHuGe7R4ZpKRIVjVy7RScelCuQ0yNMGIzx/5Dz3FQXWq1Jii669Oxs/R7iupwo+f6O9XmCJAGXIw5a11Yw9cULptVNc9rPHrauAOeNpE77aSQRKEOJZADvdLB8cXjpXFf2mvzFib69Cuks8QxktAK7Yk3fke1CJpoIb75d8iHkY21epOtqavTppezEd+0uq5RJThH+/nMyZVhRI3tSJ0kVDc1HVX2bTquWcXtniuZNOWYklLxKPfQNho8n0pHRk22UmB8DOxMjnAyt3s7xBNpujU+I7D30lK9N3PH4U+Oyc9pIWc2T7pFILvvToxoE3l2flg6eHnBd6a7ENDVbz1ELwwmt36QQAVQytEngTBYkorbJcQC6e2r/RqoqpP2N4dB7+2ZDMVw97VBraMl7ELaYdf9SOdzuis2engAojSiUUO/gdKGaJGnnldOSi5rvnxs+iMjElMCMGCSqGSIb3DQEJFTEWBBRSLC58imQohokANg6rVjq9KE/MxjAxMCEwCQYFKw4DAhoFAAQUILUGtKvqxRY/ywrrlxKrsuLiNLwECCWv9bVh/bZZAgIIAA==" + }; + } +#endif + + return GetSecretJsonAsync( + _configuration.GetValue(AppEnvironmentVariables.MAIN_CONFIG_SHEET) + ).Result; + } + + public async Task RefreshCurrentAppConfigurationAsync() { + await RefreshAsync(_configuration.GetValue(AppEnvironmentVariables.MAIN_CONFIG_SHEET)); + } + + public class RenewTokenResponse + { + public Guid RequestId { get; set; } + public string LeaseId { get; set; } + public bool Renewable { get; set; } + public long LeaseDuration { get; set; } + public object Data { get; set; } + public object WrapInfo { get; set; } + public List Warnings { get; set; } + public Auth Auth { get; set; } + } + + public class Auth + { + public string ClientToken { get; set; } + public string Accessor { get; set; } + public List Policies { get; set; } + public List TokenPolicies { get; set; } + public object Metadata { get; set; } + public long LeaseDuration { get; set; } + public bool Renewable { get; set; } + public string EntityId { get; set; } + public string TokenType { get; set; } + public bool Orphan { get; set; } + public object MfaRequirement { get; set; } + public long NumUses { get; set; } + } + + public class GetSecretResponse + { + public VaultSecret Data { get; set; } + } + + public class VaultSecret + { + public T Data { get; set; } + public VaultSecretMetadata Metadata { get; set; } + } + + public class VaultSecretMetadata + { + public DateTimeOffset CreatedTime { get; set; } + public object CustomMetadata { get; set; } + public string DeletionTime { get; set; } + public bool Destroyed { get; set; } + public long Version { get; set; } + } + + public class TokenLookupResponse + { + [JsonPropertyName("request_id")] + public Guid RequestId { get; set; } + + [JsonPropertyName("lease_id")] + public string LeaseId { get; set; } + + [JsonPropertyName("renewable")] + public bool Renewable { get; set; } + + [JsonPropertyName("lease_duration")] + public long LeaseDuration { get; set; } + + [JsonPropertyName("data")] + public TokenLookupResponseData Data { get; set; } + + [JsonPropertyName("wrap_info")] + public object WrapInfo { get; set; } + + [JsonPropertyName("warnings")] + public object Warnings { get; set; } + + [JsonPropertyName("auth")] + public object Auth { get; set; } + } + + public class TokenLookupResponseData + { + [JsonPropertyName("accessor")] + public string Accessor { get; set; } + + [JsonPropertyName("creation_time")] + public long CreationTime { get; set; } + + [JsonPropertyName("creation_ttl")] + public long CreationTtl { get; set; } + + [JsonPropertyName("display_name")] + public string DisplayName { get; set; } + + [JsonPropertyName("entity_id")] + public string EntityId { get; set; } + + [JsonPropertyName("expire_time")] + public DateTimeOffset ExpireTime { get; set; } + + [JsonPropertyName("explicit_max_ttl")] + public long ExplicitMaxTtl { get; set; } + + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("issue_time")] + public DateTimeOffset IssueTime { get; set; } + + [JsonPropertyName("last_renewal")] + public DateTimeOffset LastRenewal { get; set; } + + [JsonPropertyName("last_renewal_time")] + public long LastRenewalTime { get; set; } + + [JsonPropertyName("meta")] + public object Meta { get; set; } + + [JsonPropertyName("num_uses")] + public long NumUses { get; set; } + + [JsonPropertyName("orphan")] + public bool Orphan { get; set; } + + [JsonPropertyName("path")] + public string Path { get; set; } + + [JsonPropertyName("policies")] + public List Policies { get; set; } + + [JsonPropertyName("renewable")] + public bool Renewable { get; set; } + + [JsonPropertyName("ttl")] + public long Ttl { get; set; } + + [JsonPropertyName("type")] + public string Type { get; set; } + } +} \ No newline at end of file diff --git a/code/api/src/Utilities/BasicAuthenticationAttribute.cs b/code/api/src/Utilities/BasicAuthenticationAttribute.cs new file mode 100644 index 0000000..0bfd007 --- /dev/null +++ b/code/api/src/Utilities/BasicAuthenticationAttribute.cs @@ -0,0 +1,39 @@ +using System.Net.Http.Headers; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace IOL.GreatOffice.Api.Utilities; + +public class BasicAuthenticationAttribute : TypeFilterAttribute +{ + public BasicAuthenticationAttribute(string claimPermission) : base(typeof(BasicAuthenticationFilter)) { + Arguments = new object[] { + new Claim(claimPermission, "True") + }; + } +} + +public class BasicAuthenticationFilter : IAuthorizationFilter +{ + private readonly Claim _claim; + + public BasicAuthenticationFilter(Claim claim) { + _claim = claim; + } + + public void OnAuthorization(AuthorizationFilterContext context) { + if (!context.HttpContext.Request.Headers.ContainsKey("Authorization")) return; + try { + var authHeader = AuthenticationHeaderValue.Parse(context.HttpContext.Request.Headers["Authorization"]); + if (authHeader.Parameter is null) { + context.Result = new ForbidResult(AppConstants.BASIC_AUTH_SCHEME); + } + + var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value); + if (!hasClaim) { + context.Result = new ForbidResult(AppConstants.BASIC_AUTH_SCHEME); + } + } catch { + // ignore + } + } +} diff --git a/code/api/src/Utilities/BasicAuthenticationHandler.cs b/code/api/src/Utilities/BasicAuthenticationHandler.cs new file mode 100644 index 0000000..b0a2d1a --- /dev/null +++ b/code/api/src/Utilities/BasicAuthenticationHandler.cs @@ -0,0 +1,79 @@ +using System.Net.Http.Headers; +using System.Text; +using System.Text.Encodings.Web; +using Microsoft.Extensions.Options; + +namespace IOL.GreatOffice.Api.Utilities; + +public class BasicAuthenticationHandler : AuthenticationHandler +{ + private readonly MainAppDatabase _context; + private readonly AppConfiguration _configuration; + private readonly ILogger _logger; + + public BasicAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock, + MainAppDatabase context, + VaultService vaultService + ) : + base(options, logger, encoder, clock) { + _context = context; + _configuration = vaultService.GetCurrentAppConfiguration(); + _logger = logger.CreateLogger(); + } + + protected override Task HandleAuthenticateAsync() { + var endpoint = Context.GetEndpoint(); + if (endpoint?.Metadata.GetMetadata() != null) + return Task.FromResult(AuthenticateResult.NoResult()); + + if (!Request.Headers.ContainsKey("Authorization")) + return Task.FromResult(AuthenticateResult.Fail("Missing Authorization Header")); + + try { + var tokenEntropy = _configuration.APP_AES_KEY; + if (tokenEntropy.IsNullOrWhiteSpace()) { + _logger.LogWarning("No token entropy is available in env:TOKEN_ENTROPY, Basic auth is disabled"); + return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header")); + } + + var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]); + if (authHeader.Parameter == null) return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header")); + var credentialBytes = Convert.FromBase64String(authHeader.Parameter); + var decryptedString = Encoding.UTF8.GetString(credentialBytes).DecryptWithAes(tokenEntropy); + var tokenIsGuid = Guid.TryParse(decryptedString, out var tokenId); + + if (!tokenIsGuid) { + return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header")); + } + + var token = _context.AccessTokens.Include(c => c.User).SingleOrDefault(c => c.Id == tokenId); + if (token == default) { + return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header: Not Found")); + } + + if (token.HasExpired) { + return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header: Expired")); + } + + var permissions = new List() { + new(AppConstants.TOKEN_ALLOW_READ, token.AllowRead.ToString()), + new(AppConstants.TOKEN_ALLOW_UPDATE, token.AllowUpdate.ToString()), + new(AppConstants.TOKEN_ALLOW_CREATE, token.AllowCreate.ToString()), + new(AppConstants.TOKEN_ALLOW_DELETE, token.AllowDelete.ToString()), + }; + var claims = token.User.DefaultClaims().Concat(permissions); + var identity = new ClaimsIdentity(claims, AppConstants.BASIC_AUTH_SCHEME); + var principal = new ClaimsPrincipal(identity); + var ticket = new AuthenticationTicket(principal, AppConstants.BASIC_AUTH_SCHEME); + + return Task.FromResult(AuthenticateResult.Success(ticket)); + } catch (Exception e) { + _logger.LogError(e, $"An exception occured when challenging {AppConstants.BASIC_AUTH_SCHEME}"); + return Task.FromResult(AuthenticateResult.Fail("Invalid Authorization Header")); + } + } +} diff --git a/code/api/src/Utilities/ConfigurationExtensions.cs b/code/api/src/Utilities/ConfigurationExtensions.cs new file mode 100644 index 0000000..c95e293 --- /dev/null +++ b/code/api/src/Utilities/ConfigurationExtensions.cs @@ -0,0 +1,85 @@ +namespace IOL.GreatOffice.Api.Utilities; + +public static class ConfigurationExtensions +{ + public static string GetAppDatabaseConnectionString(this IConfiguration config, AppConfiguration configuration) { + var host = configuration.DB_HOST; + var port = configuration.DB_PORT; + var database = configuration.DB_NAME; + var user = configuration.DB_USER; + var password = configuration.DB_PASSWORD; + + string result; + if (config.GetValue("ASPNETCORE_ENVIRONMENT") == "Development") { + result = $"Server={host};Port={port};Database={database};User Id={user};Password={password};Include Error Detail=true"; + } else { + result = $"Server={host};Port={port};Database={database};User Id={user};Password={password}"; + } + + Log.Debug("Using app database connection string: " + result); + return result; + } + + public static string GetAppDatabaseConnectionString(this IConfiguration config, Func configuration) { + var _configuration = configuration(); + var host = _configuration.DB_HOST; + var port = _configuration.DB_PORT; + var database = _configuration.DB_NAME; + var user = _configuration.DB_USER; + var password = _configuration.DB_PASSWORD; + + string result; + if (config.GetValue("ASPNETCORE_ENVIRONMENT") == "Development") { + result = $"Server={host};Port={port};Database={database};User Id={user};Password={password};Include Error Detail=true"; + } else { + result = $"Server={host};Port={port};Database={database};User Id={user};Password={password}"; + } + + Log.Debug("Using app database connection string: " + result); + return result; + } + + public static string GetQuartzDatabaseConnectionString(this IConfiguration config, AppConfiguration configuration) { + var host = configuration.QUARTZ_DB_HOST; + var port = configuration.QUARTZ_DB_PORT; + var database = configuration.QUARTZ_DB_NAME; + var user = configuration.QUARTZ_DB_USER; + var password = configuration.QUARTZ_DB_PASSWORD; + + string result; + if (config.GetValue("ASPNETCORE_ENVIRONMENT") == "Development") { + result = $"Server={host};Port={port};Database={database};User Id={user};Password={password};Include Error Detail=true"; + } else { + result = $"Server={host};Port={port};Database={database};User Id={user};Password={password}"; + } + + Log.Debug("Using quartz database connection string: " + result); + return result; + } + + public static string GetQuartzDatabaseConnectionString(this IConfiguration config, Func configuration) { + var _configuration = configuration(); + var host = _configuration.QUARTZ_DB_HOST; + var port = _configuration.QUARTZ_DB_PORT; + var database = _configuration.QUARTZ_DB_NAME; + var user = _configuration.QUARTZ_DB_USER; + var password = _configuration.QUARTZ_DB_PASSWORD; + + string result; + if (config.GetValue("ASPNETCORE_ENVIRONMENT") == "Development") { + result = $"Server={host};Port={port};Database={database};User Id={user};Password={password};Include Error Detail=true"; + } else { + result = $"Server={host};Port={port};Database={database};User Id={user};Password={password}"; + } + + Log.Debug("Using quartz database connection string: " + result); + return result; + } + + public static string GetVersion(this IConfiguration configuration) { + var versionFilePath = Path.Combine(AppPaths.AppData.HostPath, "version.txt"); + if (!File.Exists(versionFilePath)) return "unknown-" + configuration.GetValue("ASPNETCORE_ENVIRONMENT"); + var versionText = File.ReadAllText(versionFilePath); + return versionText + "-" + configuration.GetValue("ASPNETCORE_ENVIRONMENT"); + } +} diff --git a/code/api/src/Utilities/DateTimeExtensions.cs b/code/api/src/Utilities/DateTimeExtensions.cs new file mode 100644 index 0000000..d25e9a8 --- /dev/null +++ b/code/api/src/Utilities/DateTimeExtensions.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Utilities; + +public static class DateTimeExtensions +{ + public static bool IsNullOrEmpty(this DateTime dateTime) { + return (dateTime == default); + } +} \ No newline at end of file diff --git a/code/api/src/Utilities/PaginationOperationFilter.cs b/code/api/src/Utilities/PaginationOperationFilter.cs new file mode 100644 index 0000000..ad02a3d --- /dev/null +++ b/code/api/src/Utilities/PaginationOperationFilter.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Options; +using MR.AspNetCore.Pagination; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace IOL.GreatOffice.Api.Utilities; + +public class PaginationOperationFilter : IOperationFilter +{ + private readonly PaginationOptions _paginationOptions; + + public PaginationOperationFilter( + IOptions paginationOptions) { + _paginationOptions = paginationOptions.Value; + } + + public void Apply(OpenApiOperation operation, OperationFilterContext context) { + var boolSchema = context.SchemaGenerator.GenerateSchema(typeof(bool), context.SchemaRepository); + var intSchema = context.SchemaGenerator.GenerateSchema(typeof(int), context.SchemaRepository); + + if (PaginationActionDetector.IsKeysetPaginationResultAction(context.MethodInfo, out _)) { + CreateParameter(_paginationOptions.FirstQueryParameterName, "true if you want the first page", boolSchema); + CreateParameter(_paginationOptions.BeforeQueryParameterName, "Id of the reference entity you want results before"); + CreateParameter(_paginationOptions.AfterQueryParameterName, "Id of the reference entity you want results after"); + CreateParameter(_paginationOptions.LastQueryParameterName, "true if you want the last page", boolSchema); + } else if (PaginationActionDetector.IsOffsetPaginationResultAction(context.MethodInfo, out _)) { + CreateParameter(_paginationOptions.PageQueryParameterName, "The page", intSchema); + } + + void CreateParameter(string name, string description, OpenApiSchema schema = null) { + operation.Parameters.Add(new OpenApiParameter { + Required = false, + In = ParameterLocation.Query, + Name = name, + Description = description, + Schema = schema, + }); + } + } +} \ No newline at end of file diff --git a/code/api/src/Utilities/QuartzJsonSerializer.cs b/code/api/src/Utilities/QuartzJsonSerializer.cs new file mode 100644 index 0000000..164a189 --- /dev/null +++ b/code/api/src/Utilities/QuartzJsonSerializer.cs @@ -0,0 +1,16 @@ +using Quartz.Spi; + +namespace IOL.GreatOffice.Api.Utilities; + +public class QuartzJsonSerializer : IObjectSerializer +{ + public void Initialize() { } + + public byte[] Serialize(T obj) where T : class { + return JsonSerializer.SerializeToUtf8Bytes(obj); + } + + public T DeSerialize(byte[] data) where T : class { + return JsonSerializer.Deserialize(data); + } +} diff --git a/code/api/src/Utilities/QueryableExtensions.cs b/code/api/src/Utilities/QueryableExtensions.cs new file mode 100644 index 0000000..bf2bf3b --- /dev/null +++ b/code/api/src/Utilities/QueryableExtensions.cs @@ -0,0 +1,12 @@ +namespace IOL.GreatOffice.Api.Utilities; + +public static class QueryableExtensions +{ + public static IQueryable ForTenant(this IQueryable queryable, LoggedInUserModel loggedInUserModel) where T : BaseWithOwner { + return queryable.Where(c => c.TenantId == loggedInUserModel.TenantId); + } + + public static IQueryable ForUser(this IQueryable queryable, LoggedInUserModel loggedInUserModel) where T : BaseWithOwner { + return queryable.Where(c => c.UserId == loggedInUserModel.Id); + } +} \ No newline at end of file diff --git a/code/api/src/Utilities/SwaggerDefaultValues.cs b/code/api/src/Utilities/SwaggerDefaultValues.cs new file mode 100644 index 0000000..5e73fa8 --- /dev/null +++ b/code/api/src/Utilities/SwaggerDefaultValues.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace IOL.GreatOffice.Api.Utilities; + +/// +/// Represents the Swagger/Swashbuckle operation filter used to document the implicit API version parameter. +/// +/// This is only required due to bugs in the . +/// Once they are fixed and published, this class can be removed. +public class SwaggerDefaultValues : IOperationFilter +{ + /// + /// Applies the filter to the specified operation using the given context. + /// + /// The operation to apply the filter to. + /// The current operation filter context. + public void Apply(OpenApiOperation operation, OperationFilterContext context) { + var apiDescription = context.ApiDescription; + + operation.Deprecated |= apiDescription.IsDeprecated(); + + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1752#issue-663991077 + foreach (var responseType in context.ApiDescription.SupportedResponseTypes) { + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/b7cf75e7905050305b115dd96640ddd6e74c7ac9/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L383-L387 + var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString(); + var response = operation.Responses[responseKey]; + + foreach (var contentType in response.Content.Keys) { + if (responseType.ApiResponseFormats.All(x => x.MediaType != contentType)) { + response.Content.Remove(contentType); + } + } + } + + if (operation.Parameters == null) { + return; + } + + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412 + // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413 + foreach (var parameter in operation.Parameters) { + var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name); + + if (parameter.Description == null) { + parameter.Description = description.ModelMetadata.Description; + } + + if (parameter.Schema.Default == null && description.DefaultValue != null) { + // REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330 + var json = JsonSerializer.Serialize(description.DefaultValue, description.ModelMetadata.ModelType); + parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json); + } + + parameter.Required |= description.IsRequired; + } + } +} diff --git a/code/api/src/Utilities/SwaggerGenOptionsExtensions.cs b/code/api/src/Utilities/SwaggerGenOptionsExtensions.cs new file mode 100644 index 0000000..a3d9036 --- /dev/null +++ b/code/api/src/Utilities/SwaggerGenOptionsExtensions.cs @@ -0,0 +1,43 @@ +#nullable enable +using IOL.GreatOffice.Api.Endpoints; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Controllers; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace IOL.GreatOffice.Api.Utilities; + +public static class SwaggerGenOptionsExtensions +{ + /// + /// Updates Swagger document to support ApiEndpoints.

+ /// For controllers inherited from :
+ /// - Replaces action Tag with [namespace]
+ ///
+ public static void UseApiEndpoints(this SwaggerGenOptions options) { + options.TagActionsBy(EndpointNamespaceOrDefault); + } + + private static IList EndpointNamespaceOrDefault(ApiDescription api) { + if (api.ActionDescriptor is not ControllerActionDescriptor actionDescriptor) { + throw new InvalidOperationException($"Unable to determine tag for endpoint: {api.ActionDescriptor.DisplayName}"); + } + + if (actionDescriptor.ControllerTypeInfo.GetBaseTypesAndThis().Any(t => t == typeof(EndpointBase))) { + return new[] { + actionDescriptor.ControllerTypeInfo.Namespace?.Split('.').Last() + }; + } + + return new[] { + actionDescriptor.ControllerName + }; + } + + private static IEnumerable GetBaseTypesAndThis(this Type type) { + var current = type; + while (current != null) { + yield return current; + current = current.BaseType; + } + } +} \ No newline at end of file diff --git a/code/api/src/wwwroot/version.txt b/code/api/src/wwwroot/version.txt new file mode 100644 index 0000000..9cf68da --- /dev/null +++ b/code/api/src/wwwroot/version.txt @@ -0,0 +1 @@ +v47-server-dev diff --git a/code/api/tests/IOL.GreatOffice.IntegrationTests/ApplicationTests/LoginPageTests.cs b/code/api/tests/IOL.GreatOffice.IntegrationTests/ApplicationTests/LoginPageTests.cs new file mode 100644 index 0000000..10525fd --- /dev/null +++ b/code/api/tests/IOL.GreatOffice.IntegrationTests/ApplicationTests/LoginPageTests.cs @@ -0,0 +1,23 @@ +using IOL.GreatOffice.IntegrationTests.Helpers; +using Xunit; + +namespace IOL.GreatOffice.IntegrationTests.ApplicationTests; + +public class LoginPageTests : IClassFixture +{ + private readonly WebServerFixture _fixture; + + public LoginPageTests(WebServerFixture fixture) { + _fixture = fixture; + } + + [Fact] + public async Task LoginPageTestsRenders() { + var page = await _fixture.Browser.NewPageAsync(); + await page.GotoAsync(_fixture.BaseUrl); + + var actual = await page.TextContentAsync(Element.ByName("Page Title")); + + Assert.Equal("Welcome", actual); + } +} diff --git a/code/api/tests/IOL.GreatOffice.IntegrationTests/Helpers/Element.cs b/code/api/tests/IOL.GreatOffice.IntegrationTests/Helpers/Element.cs new file mode 100644 index 0000000..da83cc3 --- /dev/null +++ b/code/api/tests/IOL.GreatOffice.IntegrationTests/Helpers/Element.cs @@ -0,0 +1,6 @@ +namespace IOL.GreatOffice.IntegrationTests.Helpers; + +public static class Element +{ + public static string ByName(string name) => $"[pw-name='{name}']"; +} diff --git a/code/api/tests/IOL.GreatOffice.IntegrationTests/Helpers/WebServerFixture.cs b/code/api/tests/IOL.GreatOffice.IntegrationTests/Helpers/WebServerFixture.cs new file mode 100644 index 0000000..080fa9f --- /dev/null +++ b/code/api/tests/IOL.GreatOffice.IntegrationTests/Helpers/WebServerFixture.cs @@ -0,0 +1,48 @@ +using System.Net; +using System.Net.Sockets; +using Microsoft.AspNetCore.Builder; +using Microsoft.Playwright; +using Xunit; +using Program = IOL.GreatOffice.Api.Program; + +namespace IOL.GreatOffice.IntegrationTests.Helpers; + +// ReSharper disable once ClassNeverInstantiated.Global +public class WebServerFixture : IAsyncLifetime, IDisposable +{ + private readonly WebApplication Host; + private IPlaywright Playwright { get; set; } + public IBrowser Browser { get; private set; } + public string BaseUrl { get; } = $"https://localhost:{GetRandomUnusedPort()}"; + + public WebServerFixture() { + Host = Program.CreateWebApplication(Program.CreateAppBuilder(default)); + } + + public async Task InitializeAsync() { + Playwright = await Microsoft.Playwright.Playwright.CreateAsync(); + Browser = await Playwright.Chromium.LaunchAsync(); + await Host.StartAsync(); + } + + public async Task DisposeAsync() { + await Host.StopAsync(); + await Host.DisposeAsync(); + Playwright?.Dispose(); + } + + public void Dispose() { + Host.StopAsync(); + Host.DisposeAsync(); + Playwright?.Dispose(); + GC.SuppressFinalize(this); + } + + private static int GetRandomUnusedPort() { + var listener = new TcpListener(IPAddress.Any, 0); + listener.Start(); + var port = ((IPEndPoint)listener.LocalEndpoint).Port; + listener.Stop(); + return port; + } +} diff --git a/code/api/tests/IOL.GreatOffice.IntegrationTests/IOL.GreatOffice.IntegrationTests.csproj b/code/api/tests/IOL.GreatOffice.IntegrationTests/IOL.GreatOffice.IntegrationTests.csproj new file mode 100644 index 0000000..0376a10 --- /dev/null +++ b/code/api/tests/IOL.GreatOffice.IntegrationTests/IOL.GreatOffice.IntegrationTests.csproj @@ -0,0 +1,21 @@ + + + + Exe + true + net6.0 + enable + disable + + + + + + + + + + + + + diff --git a/code/app/.dockerignore b/code/app/.dockerignore new file mode 100644 index 0000000..00774fa --- /dev/null +++ b/code/app/.dockerignore @@ -0,0 +1,7 @@ +.env +.env-example +.svelte-kit +.git +static +node_modules +build \ No newline at end of file diff --git a/code/app/.env-example b/code/app/.env-example new file mode 100644 index 0000000..270860f --- /dev/null +++ b/code/app/.env-example @@ -0,0 +1,4 @@ +VITE_LOG_LEVEL=DEBUG +VITE_TESTING=true +VITE_TEST_USERNAME="ms@test.tld" +VITE_TEST_PASSWORD="test123" \ No newline at end of file diff --git a/code/app/.gitignore b/code/app/.gitignore new file mode 100644 index 0000000..f4401a3 --- /dev/null +++ b/code/app/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example diff --git a/code/app/.npmrc b/code/app/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/code/app/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/code/app/.typesafe-i18n.json b/code/app/.typesafe-i18n.json new file mode 100644 index 0000000..38fa5ca --- /dev/null +++ b/code/app/.typesafe-i18n.json @@ -0,0 +1,5 @@ +{ + "adapter": "svelte", + "$schema": "https://unpkg.com/typesafe-i18n@5.24.0/schema/typesafe-i18n.json", + "outputPath": "src/i18n" +} \ No newline at end of file diff --git a/code/app/.version b/code/app/.version new file mode 100644 index 0000000..626799f --- /dev/null +++ b/code/app/.version @@ -0,0 +1 @@ +v1 diff --git a/code/app/.version-dev b/code/app/.version-dev new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/code/app/.version-dev @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/code/app/Dockerfile b/code/app/Dockerfile new file mode 100644 index 0000000..e381c8d --- /dev/null +++ b/code/app/Dockerfile @@ -0,0 +1,13 @@ +FROM registry.hub.docker.com/library/node:lts-buster-slim AS builder +WORKDIR . +COPY package.json . +RUN npm i +COPY . . +RUN npm run build +FROM registry.hub.docker.com/library/node:lts-buster-slim +USER node:node +WORKDIR . +COPY --from=builder --chown=node:node build build +COPY --from=builder --chown=node:node node_modules node_modules +COPY --chown=node:node package.json . +CMD ["node","build"] \ No newline at end of file diff --git a/code/app/build_and_push.sh b/code/app/build_and_push.sh new file mode 100755 index 0000000..6143419 --- /dev/null +++ b/code/app/build_and_push.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +set -Eueo pipefail + +CURRENT_DEV_VERSION=$(cat .version-dev) +CURRENT_DEV_VERSION_INT=${CURRENT_DEV_VERSION//[!0-9]/} +CURRENT_VERSION=$(cat .version) +CURRENT_VERSION_INT=${CURRENT_VERSION//[!0-9]/} +if [ ${1-prod} == "dev" ]; then + NEW_VERSION="v$((CURRENT_DEV_VERSION_INT + 1))-dev" + OLD_VERSION=$CURRENT_DEV_VERSION +else + NEW_VERSION="v$((CURRENT_VERSION_INT + 1))" + OLD_VERSION=$CURRENT_VERSION +fi +IMAGE_NAME="greatoffice/app" +HUB_NAME="dr.ivar.systems/greatoffice/app" + +# Check for uncommited changes and optionally commit them +if [ "$(git status --untracked-files=no --porcelain)" ]; then + echo "Unclean git tree! press CTRL+C to exit or press ENTER to commit and push to the default branch" + read -n 1 + + read -p "Enter commit message: " COMMIT_MESSAGE + git add .. + git commit --quiet -m "$COMMIT_MESSAGE" +fi + +if [ ${1-prod} == "dev" ]; then + echo $NEW_VERSION >|.version-dev + git add .version-dev +else + echo $NEW_VERSION >|.version + git add .version +fi + +echo "Starting build of $HUB_NAME:$NEW_VERSION at $(date -u)..." +echo + +# Put version.txt inside of server +pushd static +echo "$NEW_VERSION" >version.txt +git add version.txt +popd + +git commit --quiet -m "chore(release): Bump version" + +read -p "Do you want to tag this build? (y/n) " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + read -p "Enter tag message (can be empty): " TAG_MESSAGE + git tag -am "$TAG_MESSAGE" $NEW_VERSION +fi + +read -p "Do you want to push the latest commits and tags to origin? (y/n) " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Pushing latest changes to remotes..." + echo + git push --quiet --follow-tags +fi + +# Build docker image +echo "Building docker image" +echo + +docker build -t $IMAGE_NAME:$NEW_VERSION . + +docker tag $IMAGE_NAME:$NEW_VERSION $HUB_NAME:$NEW_VERSION + +if [ ${1-prod} == "dev" ]; then + docker tag $IMAGE_NAME:$NEW_VERSION $HUB_NAME:latest-dev +fi +if [ ${1-prod} == "prod" ]; then + docker tag $IMAGE_NAME:$NEW_VERSION $HUB_NAME:latest +fi + +# Optionally push images to docker registry +echo "Press CTRL+C to exit or press ENTER to push docker image to registry" +read -n 1 +docker push $HUB_NAME:$NEW_VERSION + +if [ ${1-prod} == "dev" ]; then + docker push $HUB_NAME:latest-dev +fi + +if [ ${1-prod} == "prod" ]; then + docker push $HUB_NAME:latest +fi diff --git a/code/app/package.json b/code/app/package.json new file mode 100644 index 0000000..a868844 --- /dev/null +++ b/code/app/package.json @@ -0,0 +1,50 @@ +{ + "name": "greatoffice-kit", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "npm-run-all --parallel vite typesafe-i18n", + "typesafe-i18n": "typesafe-i18n", + "vite": "vite", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "test": "playwright test" + }, + "devDependencies": { + "@faker-js/faker": "^7.6.0", + "@playwright/test": "^1.30.0", + "@sveltejs/adapter-node": "1.1.7", + "@sveltejs/kit": "1.5.0", + "@tailwindcss/forms": "^0.5.3", + "@types/js-cookie": "^3.0.2", + "@vite-pwa/sveltekit": "^0.1.3", + "autoprefixer": "^10.4.13", + "npm-run-all": "^4.1.5", + "pino-pretty": "^9.1.1", + "postcss": "^8.4.21", + "postcss-load-config": "^4.0.1", + "svelte": "^3.55.1", + "svelte-check": "^3.0.3", + "svelte-preprocess": "^5.0.1", + "tailwindcss": "^3.2.6", + "tslib": "^2.5.0", + "typesafe-i18n": "^5.24.0", + "typescript": "^4.9.5", + "vite": "^4.1.1", + "vite-plugin-pwa": "^0.14.3" + }, + "dependencies": { + "@developermuch/dev-svelte-headlessui": "0.0.1", + "@rgossiaux/svelte-headlessui": "^1.0.2", + "fuzzysort": "^2.0.4", + "js-cookie": "^3.0.1", + "pino": "^8.9.0", + "pino-seq": "^0.9.0", + "svelte-headless-table": "^0.17.2", + "temporal-polyfill": "^0.0.8", + "turbo-query": "^1.9.0" + } +} \ No newline at end of file diff --git a/code/app/playwright.config.ts b/code/app/playwright.config.ts new file mode 100644 index 0000000..22c46d9 --- /dev/null +++ b/code/app/playwright.config.ts @@ -0,0 +1,10 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + webServer: { + command: 'pnpm run build && pnpm run preview', + port: 4173 + } +}; + +export default config; diff --git a/code/app/pnpm-lock.yaml b/code/app/pnpm-lock.yaml new file mode 100644 index 0000000..04e088e --- /dev/null +++ b/code/app/pnpm-lock.yaml @@ -0,0 +1,4389 @@ +lockfileVersion: 5.4 + +specifiers: + '@developermuch/dev-svelte-headlessui': 0.0.1 + '@faker-js/faker': ^7.6.0 + '@playwright/test': ^1.30.0 + '@rgossiaux/svelte-headlessui': ^1.0.2 + '@sveltejs/adapter-node': 1.1.7 + '@sveltejs/kit': 1.5.0 + '@tailwindcss/forms': ^0.5.3 + '@types/js-cookie': ^3.0.2 + '@vite-pwa/sveltekit': ^0.1.3 + autoprefixer: ^10.4.13 + fuzzysort: ^2.0.4 + js-cookie: ^3.0.1 + npm-run-all: ^4.1.5 + pino: ^8.9.0 + pino-pretty: ^9.1.1 + pino-seq: ^0.9.0 + postcss: ^8.4.21 + postcss-load-config: ^4.0.1 + svelte: ^3.55.1 + svelte-check: ^3.0.3 + svelte-headless-table: ^0.17.2 + svelte-preprocess: ^5.0.1 + tailwindcss: ^3.2.6 + temporal-polyfill: ^0.0.8 + tslib: ^2.5.0 + turbo-query: ^1.9.0 + typesafe-i18n: ^5.24.0 + typescript: ^4.9.5 + vite: ^4.1.1 + vite-plugin-pwa: ^0.14.3 + +dependencies: + '@developermuch/dev-svelte-headlessui': 0.0.1_svelte@3.55.1 + '@rgossiaux/svelte-headlessui': 1.0.2_svelte@3.55.1 + fuzzysort: 2.0.4 + js-cookie: 3.0.1 + pino: 8.9.0 + pino-seq: 0.9.0 + svelte-headless-table: 0.17.2_svelte@3.55.1 + temporal-polyfill: 0.0.8 + turbo-query: 1.9.0 + +devDependencies: + '@faker-js/faker': 7.6.0 + '@playwright/test': 1.30.0 + '@sveltejs/adapter-node': 1.1.7_@sveltejs+kit@1.5.0 + '@sveltejs/kit': 1.5.0_svelte@3.55.1+vite@4.1.1 + '@tailwindcss/forms': 0.5.3_tailwindcss@3.2.6 + '@types/js-cookie': 3.0.2 + '@vite-pwa/sveltekit': 0.1.3_kkbswr7m3b4a2hnxfqban7fxma + autoprefixer: 10.4.13_postcss@8.4.21 + npm-run-all: 4.1.5 + pino-pretty: 9.1.1 + postcss: 8.4.21 + postcss-load-config: 4.0.1_postcss@8.4.21 + svelte: 3.55.1 + svelte-check: 3.0.3_gqx7lw3sljhsd4bstor5m2aa2u + svelte-preprocess: 5.0.1_b25a55hc532q56kmuqlrolam2i + tailwindcss: 3.2.6_postcss@8.4.21 + tslib: 2.5.0 + typesafe-i18n: 5.24.0_typescript@4.9.5 + typescript: 4.9.5 + vite: 4.1.1 + vite-plugin-pwa: 0.14.3_vite@4.1.1 + +packages: + + /@ampproject/remapping/2.2.0: + resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.1.1 + '@jridgewell/trace-mapping': 0.3.17 + dev: true + + /@apideck/better-ajv-errors/0.3.6_ajv@8.12.0: + resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==} + engines: {node: '>=10'} + peerDependencies: + ajv: '>=8' + dependencies: + ajv: 8.12.0 + json-schema: 0.4.0 + jsonpointer: 5.0.1 + leven: 3.1.0 + dev: true + + /@babel/code-frame/7.18.6: + resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.18.6 + dev: true + + /@babel/compat-data/7.20.14: + resolution: {integrity: sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/core/7.20.12: + resolution: {integrity: sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.0 + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.20.14 + '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 + '@babel/helper-module-transforms': 7.20.11 + '@babel/helpers': 7.20.13 + '@babel/parser': 7.20.15 + '@babel/template': 7.20.7 + '@babel/traverse': 7.20.13 + '@babel/types': 7.20.7 + convert-source-map: 1.9.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/generator/7.20.14: + resolution: {integrity: sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.7 + '@jridgewell/gen-mapping': 0.3.2 + jsesc: 2.5.2 + dev: true + + /@babel/helper-annotate-as-pure/7.18.6: + resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.7 + dev: true + + /@babel/helper-builder-binary-assignment-operator-visitor/7.18.9: + resolution: {integrity: sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-explode-assignable-expression': 7.18.6 + '@babel/types': 7.20.7 + dev: true + + /@babel/helper-compilation-targets/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/compat-data': 7.20.14 + '@babel/core': 7.20.12 + '@babel/helper-validator-option': 7.18.6 + browserslist: 4.21.5 + lru-cache: 5.1.1 + semver: 6.3.0 + dev: true + + /@babel/helper-create-class-features-plugin/7.20.12_@babel+core@7.20.12: + resolution: {integrity: sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-member-expression-to-functions': 7.20.7 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/helper-replace-supers': 7.20.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + '@babel/helper-split-export-declaration': 7.18.6 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-create-regexp-features-plugin/7.20.5_@babel+core@7.20.12: + resolution: {integrity: sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-annotate-as-pure': 7.18.6 + regexpu-core: 5.3.0 + dev: true + + /@babel/helper-define-polyfill-provider/0.3.3_@babel+core@7.20.12: + resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==} + peerDependencies: + '@babel/core': ^7.4.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + debug: 4.3.4 + lodash.debounce: 4.0.8 + resolve: 1.22.1 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-environment-visitor/7.18.9: + resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-explode-assignable-expression/7.18.6: + resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.7 + dev: true + + /@babel/helper-function-name/7.19.0: + resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.20.7 + '@babel/types': 7.20.7 + dev: true + + /@babel/helper-hoist-variables/7.18.6: + resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.7 + dev: true + + /@babel/helper-member-expression-to-functions/7.20.7: + resolution: {integrity: sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.7 + dev: true + + /@babel/helper-module-imports/7.18.6: + resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.7 + dev: true + + /@babel/helper-module-transforms/7.20.11: + resolution: {integrity: sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-simple-access': 7.20.2 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/helper-validator-identifier': 7.19.1 + '@babel/template': 7.20.7 + '@babel/traverse': 7.20.13 + '@babel/types': 7.20.7 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-optimise-call-expression/7.18.6: + resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.7 + dev: true + + /@babel/helper-plugin-utils/7.20.2: + resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-remap-async-to-generator/7.18.9_@babel+core@7.20.12: + resolution: {integrity: sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-wrap-function': 7.20.5 + '@babel/types': 7.20.7 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-replace-supers/7.20.7: + resolution: {integrity: sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-member-expression-to-functions': 7.20.7 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/template': 7.20.7 + '@babel/traverse': 7.20.13 + '@babel/types': 7.20.7 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helper-simple-access/7.20.2: + resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.7 + dev: true + + /@babel/helper-skip-transparent-expression-wrappers/7.20.0: + resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.7 + dev: true + + /@babel/helper-split-export-declaration/7.18.6: + resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.20.7 + dev: true + + /@babel/helper-string-parser/7.19.4: + resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-identifier/7.19.1: + resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-option/7.18.6: + resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-wrap-function/7.20.5: + resolution: {integrity: sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-function-name': 7.19.0 + '@babel/template': 7.20.7 + '@babel/traverse': 7.20.13 + '@babel/types': 7.20.7 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/helpers/7.20.13: + resolution: {integrity: sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.20.7 + '@babel/traverse': 7.20.13 + '@babel/types': 7.20.7 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/highlight/7.18.6: + resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.19.1 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: true + + /@babel/parser/7.20.15: + resolution: {integrity: sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.20.7 + dev: true + + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + '@babel/plugin-proposal-optional-chaining': 7.20.7_@babel+core@7.20.12 + dev: true + + /@babel/plugin-proposal-async-generator-functions/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-remap-async-to-generator': 7.18.9_@babel+core@7.20.12 + '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.20.12 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-class-properties/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-create-class-features-plugin': 7.20.12_@babel+core@7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-class-static-block/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-create-class-features-plugin': 7.20.12_@babel+core@7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.20.12 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-dynamic-import/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.20.12 + dev: true + + /@babel/plugin-proposal-export-namespace-from/7.18.9_@babel+core@7.20.12: + resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.20.12 + dev: true + + /@babel/plugin-proposal-json-strings/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.20.12 + dev: true + + /@babel/plugin-proposal-logical-assignment-operators/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.20.12 + dev: true + + /@babel/plugin-proposal-nullish-coalescing-operator/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.20.12 + dev: true + + /@babel/plugin-proposal-numeric-separator/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.20.12 + dev: true + + /@babel/plugin-proposal-object-rest-spread/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.20.14 + '@babel/core': 7.20.12 + '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.20.12 + '@babel/plugin-transform-parameters': 7.20.7_@babel+core@7.20.12 + dev: true + + /@babel/plugin-proposal-optional-catch-binding/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.20.12 + dev: true + + /@babel/plugin-proposal-optional-chaining/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.20.12 + dev: true + + /@babel/plugin-proposal-private-methods/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-create-class-features-plugin': 7.20.12_@babel+core@7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-private-property-in-object/7.20.5_@babel+core@7.20.12: + resolution: {integrity: sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.20.12_@babel+core@7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.20.12 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-proposal-unicode-property-regex/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} + engines: {node: '>=4'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-create-regexp-features-plugin': 7.20.5_@babel+core@7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.20.12: + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.20.12: + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-class-static-block/7.14.5_@babel+core@7.20.12: + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-dynamic-import/7.8.3_@babel+core@7.20.12: + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-export-namespace-from/7.8.3_@babel+core@7.20.12: + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-import-assertions/7.20.0_@babel+core@7.20.12: + resolution: {integrity: sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.20.12: + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.20.12: + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.20.12: + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.20.12: + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.20.12: + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.20.12: + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.20.12: + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.20.12: + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-syntax-top-level-await/7.14.5_@babel+core@7.20.12: + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-arrow-functions/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-async-to-generator/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-remap-async-to-generator': 7.18.9_@babel+core@7.20.12 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-block-scoped-functions/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-block-scoping/7.20.15_@babel+core@7.20.12: + resolution: {integrity: sha512-Vv4DMZ6MiNOhu/LdaZsT/bsLRxgL94d269Mv4R/9sp6+Mp++X/JqypZYypJXLlM4mlL352/Egzbzr98iABH1CA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-classes/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-replace-supers': 7.20.7 + '@babel/helper-split-export-declaration': 7.18.6 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-computed-properties/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/template': 7.20.7 + dev: true + + /@babel/plugin-transform-destructuring/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-dotall-regex/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-create-regexp-features-plugin': 7.20.5_@babel+core@7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-duplicate-keys/7.18.9_@babel+core@7.20.12: + resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-exponentiation-operator/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-for-of/7.18.8_@babel+core@7.20.12: + resolution: {integrity: sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-function-name/7.18.9_@babel+core@7.20.12: + resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-literals/7.18.9_@babel+core@7.20.12: + resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-member-expression-literals/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-modules-amd/7.20.11_@babel+core@7.20.12: + resolution: {integrity: sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-module-transforms': 7.20.11 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-modules-commonjs/7.20.11_@babel+core@7.20.12: + resolution: {integrity: sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-module-transforms': 7.20.11 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-simple-access': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-modules-systemjs/7.20.11_@babel+core@7.20.12: + resolution: {integrity: sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-module-transforms': 7.20.11 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-validator-identifier': 7.19.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-modules-umd/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-module-transforms': 7.20.11 + '@babel/helper-plugin-utils': 7.20.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-named-capturing-groups-regex/7.20.5_@babel+core@7.20.12: + resolution: {integrity: sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-create-regexp-features-plugin': 7.20.5_@babel+core@7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-new-target/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-object-super/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-replace-supers': 7.20.7 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/plugin-transform-parameters/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-property-literals/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-regenerator/7.20.5_@babel+core@7.20.12: + resolution: {integrity: sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + regenerator-transform: 0.15.1 + dev: true + + /@babel/plugin-transform-reserved-words/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-shorthand-properties/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-spread/7.20.7_@babel+core@7.20.12: + resolution: {integrity: sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + dev: true + + /@babel/plugin-transform-sticky-regex/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-template-literals/7.18.9_@babel+core@7.20.12: + resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-typeof-symbol/7.18.9_@babel+core@7.20.12: + resolution: {integrity: sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-unicode-escapes/7.18.10_@babel+core@7.20.12: + resolution: {integrity: sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/plugin-transform-unicode-regex/7.18.6_@babel+core@7.20.12: + resolution: {integrity: sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-create-regexp-features-plugin': 7.20.5_@babel+core@7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + dev: true + + /@babel/preset-env/7.20.2_@babel+core@7.20.12: + resolution: {integrity: sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.20.14 + '@babel/core': 7.20.12 + '@babel/helper-compilation-targets': 7.20.7_@babel+core@7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-validator-option': 7.18.6 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.20.7_@babel+core@7.20.12 + '@babel/plugin-proposal-async-generator-functions': 7.20.7_@babel+core@7.20.12 + '@babel/plugin-proposal-class-properties': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-proposal-class-static-block': 7.20.7_@babel+core@7.20.12 + '@babel/plugin-proposal-dynamic-import': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-proposal-export-namespace-from': 7.18.9_@babel+core@7.20.12 + '@babel/plugin-proposal-json-strings': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-proposal-logical-assignment-operators': 7.20.7_@babel+core@7.20.12 + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-proposal-numeric-separator': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-proposal-object-rest-spread': 7.20.7_@babel+core@7.20.12 + '@babel/plugin-proposal-optional-catch-binding': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-proposal-optional-chaining': 7.20.7_@babel+core@7.20.12 + '@babel/plugin-proposal-private-methods': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-proposal-private-property-in-object': 7.20.5_@babel+core@7.20.12 + '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-syntax-async-generators': 7.8.4_@babel+core@7.20.12 + '@babel/plugin-syntax-class-properties': 7.12.13_@babel+core@7.20.12 + '@babel/plugin-syntax-class-static-block': 7.14.5_@babel+core@7.20.12 + '@babel/plugin-syntax-dynamic-import': 7.8.3_@babel+core@7.20.12 + '@babel/plugin-syntax-export-namespace-from': 7.8.3_@babel+core@7.20.12 + '@babel/plugin-syntax-import-assertions': 7.20.0_@babel+core@7.20.12 + '@babel/plugin-syntax-json-strings': 7.8.3_@babel+core@7.20.12 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4_@babel+core@7.20.12 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3_@babel+core@7.20.12 + '@babel/plugin-syntax-numeric-separator': 7.10.4_@babel+core@7.20.12 + '@babel/plugin-syntax-object-rest-spread': 7.8.3_@babel+core@7.20.12 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3_@babel+core@7.20.12 + '@babel/plugin-syntax-optional-chaining': 7.8.3_@babel+core@7.20.12 + '@babel/plugin-syntax-private-property-in-object': 7.14.5_@babel+core@7.20.12 + '@babel/plugin-syntax-top-level-await': 7.14.5_@babel+core@7.20.12 + '@babel/plugin-transform-arrow-functions': 7.20.7_@babel+core@7.20.12 + '@babel/plugin-transform-async-to-generator': 7.20.7_@babel+core@7.20.12 + '@babel/plugin-transform-block-scoped-functions': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-transform-block-scoping': 7.20.15_@babel+core@7.20.12 + '@babel/plugin-transform-classes': 7.20.7_@babel+core@7.20.12 + '@babel/plugin-transform-computed-properties': 7.20.7_@babel+core@7.20.12 + '@babel/plugin-transform-destructuring': 7.20.7_@babel+core@7.20.12 + '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-transform-duplicate-keys': 7.18.9_@babel+core@7.20.12 + '@babel/plugin-transform-exponentiation-operator': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-transform-for-of': 7.18.8_@babel+core@7.20.12 + '@babel/plugin-transform-function-name': 7.18.9_@babel+core@7.20.12 + '@babel/plugin-transform-literals': 7.18.9_@babel+core@7.20.12 + '@babel/plugin-transform-member-expression-literals': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-transform-modules-amd': 7.20.11_@babel+core@7.20.12 + '@babel/plugin-transform-modules-commonjs': 7.20.11_@babel+core@7.20.12 + '@babel/plugin-transform-modules-systemjs': 7.20.11_@babel+core@7.20.12 + '@babel/plugin-transform-modules-umd': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-transform-named-capturing-groups-regex': 7.20.5_@babel+core@7.20.12 + '@babel/plugin-transform-new-target': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-transform-object-super': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-transform-parameters': 7.20.7_@babel+core@7.20.12 + '@babel/plugin-transform-property-literals': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-transform-regenerator': 7.20.5_@babel+core@7.20.12 + '@babel/plugin-transform-reserved-words': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-transform-shorthand-properties': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-transform-spread': 7.20.7_@babel+core@7.20.12 + '@babel/plugin-transform-sticky-regex': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-transform-template-literals': 7.18.9_@babel+core@7.20.12 + '@babel/plugin-transform-typeof-symbol': 7.18.9_@babel+core@7.20.12 + '@babel/plugin-transform-unicode-escapes': 7.18.10_@babel+core@7.20.12 + '@babel/plugin-transform-unicode-regex': 7.18.6_@babel+core@7.20.12 + '@babel/preset-modules': 0.1.5_@babel+core@7.20.12 + '@babel/types': 7.20.7 + babel-plugin-polyfill-corejs2: 0.3.3_@babel+core@7.20.12 + babel-plugin-polyfill-corejs3: 0.6.0_@babel+core@7.20.12 + babel-plugin-polyfill-regenerator: 0.4.1_@babel+core@7.20.12 + core-js-compat: 3.27.2 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/preset-modules/0.1.5_@babel+core@7.20.12: + resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-proposal-unicode-property-regex': 7.18.6_@babel+core@7.20.12 + '@babel/plugin-transform-dotall-regex': 7.18.6_@babel+core@7.20.12 + '@babel/types': 7.20.7 + esutils: 2.0.3 + dev: true + + /@babel/regjsgen/0.8.0: + resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} + dev: true + + /@babel/runtime/7.20.13: + resolution: {integrity: sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 + dev: true + + /@babel/template/7.20.7: + resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/parser': 7.20.15 + '@babel/types': 7.20.7 + dev: true + + /@babel/traverse/7.20.13: + resolution: {integrity: sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.20.14 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/parser': 7.20.15 + '@babel/types': 7.20.7 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/types/7.20.7: + resolution: {integrity: sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.19.4 + '@babel/helper-validator-identifier': 7.19.1 + to-fast-properties: 2.0.0 + dev: true + + /@developermuch/dev-svelte-headlessui/0.0.1_svelte@3.55.1: + resolution: {integrity: sha512-tfBlHliv75oQFRrC430nIsw+A8+iFmr5c2g0A+VTlVD3960nEL9jOE0LDHYKq6VhX5LnOLTFIZwVKC1DxFo0QA==} + peerDependencies: + svelte: ^3.44.0 + dependencies: + svelte: 3.55.1 + dev: false + + /@esbuild/android-arm/0.16.17: + resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64/0.16.17: + resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64/0.16.17: + resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64/0.16.17: + resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64/0.16.17: + resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64/0.16.17: + resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64/0.16.17: + resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm/0.16.17: + resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64/0.16.17: + resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32/0.16.17: + resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64/0.16.17: + resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el/0.16.17: + resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64/0.16.17: + resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64/0.16.17: + resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x/0.16.17: + resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64/0.16.17: + resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64/0.16.17: + resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64/0.16.17: + resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64/0.16.17: + resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64/0.16.17: + resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32/0.16.17: + resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64/0.16.17: + resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@faker-js/faker/7.6.0: + resolution: {integrity: sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==} + engines: {node: '>=14.0.0', npm: '>=6.0.0'} + dev: true + + /@jridgewell/gen-mapping/0.1.1: + resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + + /@jridgewell/gen-mapping/0.3.2: + resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/trace-mapping': 0.3.17 + dev: true + + /@jridgewell/resolve-uri/3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/set-array/1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/source-map/0.3.2: + resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==} + dependencies: + '@jridgewell/gen-mapping': 0.3.2 + '@jridgewell/trace-mapping': 0.3.17 + dev: true + + /@jridgewell/sourcemap-codec/1.4.14: + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + dev: true + + /@jridgewell/trace-mapping/0.3.17: + resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + + /@nodelib/fs.scandir/2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat/2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk/1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@playwright/test/1.30.0: + resolution: {integrity: sha512-SVxkQw1xvn/Wk/EvBnqWIq6NLo1AppwbYOjNLmyU0R1RoQ3rLEBtmjTnElcnz8VEtn11fptj1ECxK0tgURhajw==} + engines: {node: '>=14'} + hasBin: true + dependencies: + '@types/node': 18.13.0 + playwright-core: 1.30.0 + dev: true + + /@polka/url/1.0.0-next.21: + resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} + dev: true + + /@rgossiaux/svelte-headlessui/1.0.2_svelte@3.55.1: + resolution: {integrity: sha512-sauopYTSivhzXe1kAvgawkhyYJcQlK8Li3p0d2OtcCIVprOzdbard5lbqWB4xHDv83zAobt2mR08oizO2poHLQ==} + peerDependencies: + svelte: ^3.44.0 + dependencies: + svelte: 3.55.1 + dev: false + + /@rollup/plugin-babel/5.3.1_3dsfpkpoyvuuxyfgdbpn4j4uzm: + resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} + engines: {node: '>= 10.0.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@types/babel__core': ^7.1.9 + rollup: ^1.20.0||^2.0.0 + peerDependenciesMeta: + '@types/babel__core': + optional: true + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-module-imports': 7.18.6 + '@rollup/pluginutils': 3.1.0_rollup@2.79.1 + rollup: 2.79.1 + dev: true + + /@rollup/plugin-commonjs/24.0.1_rollup@3.14.0: + resolution: {integrity: sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.68.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.0.2_rollup@3.14.0 + commondir: 1.0.1 + estree-walker: 2.0.2 + glob: 8.1.0 + is-reference: 1.2.1 + magic-string: 0.27.0 + rollup: 3.14.0 + dev: true + + /@rollup/plugin-json/6.0.0_rollup@3.14.0: + resolution: {integrity: sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.0.2_rollup@3.14.0 + rollup: 3.14.0 + dev: true + + /@rollup/plugin-node-resolve/11.2.1_rollup@2.79.1: + resolution: {integrity: sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==} + engines: {node: '>= 10.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.79.1 + '@types/resolve': 1.17.1 + builtin-modules: 3.3.0 + deepmerge: 4.3.0 + is-module: 1.0.0 + resolve: 1.22.1 + rollup: 2.79.1 + dev: true + + /@rollup/plugin-node-resolve/15.0.1_rollup@3.14.0: + resolution: {integrity: sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.0.2_rollup@3.14.0 + '@types/resolve': 1.20.2 + deepmerge: 4.3.0 + is-builtin-module: 3.2.1 + is-module: 1.0.0 + resolve: 1.22.1 + rollup: 3.14.0 + dev: true + + /@rollup/plugin-replace/2.4.2_rollup@2.79.1: + resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==} + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + dependencies: + '@rollup/pluginutils': 3.1.0_rollup@2.79.1 + magic-string: 0.25.9 + rollup: 2.79.1 + dev: true + + /@rollup/plugin-replace/5.0.2_rollup@3.14.0: + resolution: {integrity: sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.0.2_rollup@3.14.0 + magic-string: 0.27.0 + rollup: 3.14.0 + dev: true + + /@rollup/pluginutils/3.1.0_rollup@2.79.1: + resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} + engines: {node: '>= 8.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + dependencies: + '@types/estree': 0.0.39 + estree-walker: 1.0.1 + picomatch: 2.3.1 + rollup: 2.79.1 + dev: true + + /@rollup/pluginutils/5.0.2_rollup@3.14.0: + resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@types/estree': 1.0.0 + estree-walker: 2.0.2 + picomatch: 2.3.1 + rollup: 3.14.0 + dev: true + + /@surma/rollup-plugin-off-main-thread/2.2.3: + resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==} + dependencies: + ejs: 3.1.8 + json5: 2.2.3 + magic-string: 0.25.9 + string.prototype.matchall: 4.0.8 + dev: true + + /@sveltejs/adapter-node/1.1.7_@sveltejs+kit@1.5.0: + resolution: {integrity: sha512-N93uYDzH8+RlneYdfgS33nudVk9e8JhYy9vPOsJKeMCkWPb5ejJ0D+LUAEuyA1BOyA7ZkaVTupLStAG4nuhT2A==} + peerDependencies: + '@sveltejs/kit': ^1.0.0 + dependencies: + '@rollup/plugin-commonjs': 24.0.1_rollup@3.14.0 + '@rollup/plugin-json': 6.0.0_rollup@3.14.0 + '@rollup/plugin-node-resolve': 15.0.1_rollup@3.14.0 + '@sveltejs/kit': 1.5.0_svelte@3.55.1+vite@4.1.1 + rollup: 3.14.0 + dev: true + + /@sveltejs/kit/1.5.0_svelte@3.55.1+vite@4.1.1: + resolution: {integrity: sha512-AkWgCO9i2djZjTqCgIQJ5XfnSzRINowh2w2Gk9wDRuTwxKizSuYe3jNvds/HCDDGHo8XE5E0yWNC9j2XxbrX+g==} + engines: {node: ^16.14 || >=18} + hasBin: true + requiresBuild: true + peerDependencies: + svelte: ^3.54.0 + vite: ^4.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte': 2.0.2_svelte@3.55.1+vite@4.1.1 + '@types/cookie': 0.5.1 + cookie: 0.5.0 + devalue: 4.2.3 + esm-env: 1.0.0 + kleur: 4.1.5 + magic-string: 0.27.0 + mime: 3.0.0 + sade: 1.8.1 + set-cookie-parser: 2.5.1 + sirv: 2.0.2 + svelte: 3.55.1 + tiny-glob: 0.2.9 + undici: 5.18.0 + vite: 4.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@sveltejs/vite-plugin-svelte/2.0.2_svelte@3.55.1+vite@4.1.1: + resolution: {integrity: sha512-xCEan0/NNpQuL0l5aS42FjwQ6wwskdxC3pW1OeFtEKNZwRg7Evro9lac9HesGP6TdFsTv2xMes5ASQVKbCacxg==} + engines: {node: ^14.18.0 || >= 16} + peerDependencies: + svelte: ^3.54.0 + vite: ^4.0.0 + dependencies: + debug: 4.3.4 + deepmerge: 4.3.0 + kleur: 4.1.5 + magic-string: 0.27.0 + svelte: 3.55.1 + svelte-hmr: 0.15.1_svelte@3.55.1 + vite: 4.1.1 + vitefu: 0.2.4_vite@4.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@tailwindcss/forms/0.5.3_tailwindcss@3.2.6: + resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==} + peerDependencies: + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 3.2.6_postcss@8.4.21 + dev: true + + /@types/cookie/0.5.1: + resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==} + dev: true + + /@types/estree/0.0.39: + resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} + dev: true + + /@types/estree/1.0.0: + resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} + dev: true + + /@types/events/3.0.0: + resolution: {integrity: sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==} + dev: false + + /@types/js-cookie/3.0.2: + resolution: {integrity: sha512-6+0ekgfusHftJNYpihfkMu8BWdeHs9EOJuGcSofErjstGPfPGEu9yTu4t460lTzzAMl2cM5zngQJqPMHbbnvYA==} + dev: true + + /@types/node/18.13.0: + resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==} + + /@types/pino/5.20.0: + resolution: {integrity: sha512-gz3Ahvx1UDEveXViOQtYqnUkjSVQFdoJqpZTW/63spEHwOGRJRJIi3JMJSClp5Sk1x1ljn9tHWjGczmP6s/rvg==} + dependencies: + '@types/events': 3.0.0 + '@types/node': 18.13.0 + dev: false + + /@types/pug/2.0.6: + resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==} + dev: true + + /@types/resolve/1.17.1: + resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} + dependencies: + '@types/node': 18.13.0 + dev: true + + /@types/resolve/1.20.2: + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + dev: true + + /@types/sass/1.43.1: + resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==} + dependencies: + '@types/node': 18.13.0 + dev: true + + /@types/trusted-types/2.0.2: + resolution: {integrity: sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==} + dev: true + + /@vite-pwa/sveltekit/0.1.3_kkbswr7m3b4a2hnxfqban7fxma: + resolution: {integrity: sha512-wOo8riFow/eT+5UWyOcfO3ol72+2bT2fHDwd+sryqqriCWbRxOSUBKivVLlqzyna0QA0sNkM1jDGjJShpwq7aQ==} + engines: {node: '>=16.14'} + peerDependencies: + '@sveltejs/kit': ^1.0.0 + vite-plugin-pwa: ^0.14.0 + dependencies: + '@sveltejs/kit': 1.5.0_svelte@3.55.1+vite@4.1.1 + vite-plugin-pwa: 0.14.3_vite@4.1.1 + dev: true + + /abort-controller/3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + + /acorn-node/1.8.2: + resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==} + dependencies: + acorn: 7.4.1 + acorn-walk: 7.2.0 + xtend: 4.0.2 + dev: true + + /acorn-walk/7.2.0: + resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn/7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /acorn/8.8.2: + resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ajv/8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + dev: true + + /ansi-styles/3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + + /ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /anymatch/3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /arg/5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + dev: true + + /async/3.2.4: + resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + dev: true + + /at-least-node/1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + dev: true + + /atomic-sleep/1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + /autoprefixer/10.4.13_postcss@8.4.21: + resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.21.5 + caniuse-lite: 1.0.30001451 + fraction.js: 4.2.0 + normalize-range: 0.1.2 + picocolors: 1.0.0 + postcss: 8.4.21 + postcss-value-parser: 4.2.0 + dev: true + + /available-typed-arrays/1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: true + + /babel-plugin-polyfill-corejs2/0.3.3_@babel+core@7.20.12: + resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.20.14 + '@babel/core': 7.20.12 + '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.20.12 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-polyfill-corejs3/0.6.0_@babel+core@7.20.12: + resolution: {integrity: sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.20.12 + core-js-compat: 3.27.2 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-polyfill-regenerator/0.4.1_@babel+core@7.20.12: + resolution: {integrity: sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.20.12 + '@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.20.12 + transitivePeerDependencies: + - supports-color + dev: true + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /base64-js/1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + /binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: true + + /brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /browserslist/4.21.5: + resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001451 + electron-to-chromium: 1.4.289 + node-releases: 2.0.10 + update-browserslist-db: 1.0.10_browserslist@4.21.5 + dev: true + + /buffer-crc32/0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: true + + /buffer-from/1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + + /buffer/6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + /builtin-modules/3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + dev: true + + /busboy/1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + dependencies: + streamsearch: 1.1.0 + dev: true + + /call-bind/1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.2.0 + dev: true + + /callsites/3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /camelcase-css/2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + dev: true + + /caniuse-lite/1.0.30001451: + resolution: {integrity: sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w==} + dev: true + + /chalk/2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: true + + /chalk/4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /color-convert/1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: true + + /color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name/1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: true + + /color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /colorette/2.0.19: + resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} + dev: true + + /commander/2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + /common-tags/1.8.2: + resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} + engines: {node: '>=4.0.0'} + dev: true + + /commondir/1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + dev: true + + /concat-map/0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true + + /convert-source-map/1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + dev: true + + /cookie/0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: true + + /core-js-compat/3.27.2: + resolution: {integrity: sha512-welaYuF7ZtbYKGrIy7y3eb40d37rG1FvzEOfe7hSLd2iD6duMDqUhRfSvCGyC46HhR6Y8JXXdZ2lnRUMkPBpvg==} + dependencies: + browserslist: 4.21.5 + dev: true + + /cross-spawn/6.0.5: + resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} + engines: {node: '>=4.8'} + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.1 + shebang-command: 1.2.0 + which: 1.3.1 + dev: true + + /crypto-random-string/2.0.0: + resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} + engines: {node: '>=8'} + dev: true + + /cssesc/3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /dateformat/4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + dev: true + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /deepmerge/4.3.0: + resolution: {integrity: sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==} + engines: {node: '>=0.10.0'} + dev: true + + /define-properties/1.1.4: + resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + dev: true + + /defined/1.0.1: + resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} + dev: true + + /detect-indent/6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + dev: true + + /detective/5.2.1: + resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} + engines: {node: '>=0.8.0'} + hasBin: true + dependencies: + acorn-node: 1.8.2 + defined: 1.0.1 + minimist: 1.2.7 + dev: true + + /devalue/4.2.3: + resolution: {integrity: sha512-JG6Q248aN0pgFL57e3zqTVeFraBe+5W2ugvv1mLXsJP6YYIYJhRZhAl7QP8haJrqob6X10F9NEkuCvNILZTPeQ==} + dev: true + + /didyoumean/1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dev: true + + /dlv/1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dev: true + + /ejs/3.1.8: + resolution: {integrity: sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==} + engines: {node: '>=0.10.0'} + hasBin: true + dependencies: + jake: 10.8.5 + dev: true + + /electron-to-chromium/1.4.289: + resolution: {integrity: sha512-relLdMfPBxqGCxy7Gyfm1HcbRPcFUJdlgnCPVgQ23sr1TvUrRJz0/QPoGP0+x41wOVSTN/Wi3w6YDgHiHJGOzg==} + dev: true + + /end-of-stream/1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + dev: true + + /error-ex/1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + dev: true + + /es-abstract/1.21.1: + resolution: {integrity: sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + es-set-tostringtag: 2.0.1 + es-to-primitive: 1.2.1 + function-bind: 1.1.1 + function.prototype.name: 1.1.5 + get-intrinsic: 1.2.0 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.0.1 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.4 + is-array-buffer: 3.0.1 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.10 + is-weakref: 1.0.2 + object-inspect: 1.12.3 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.4.3 + safe-regex-test: 1.0.0 + string.prototype.trimend: 1.0.6 + string.prototype.trimstart: 1.0.6 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.9 + dev: true + + /es-set-tostringtag/2.0.1: + resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.0 + has: 1.0.3 + has-tostringtag: 1.0.0 + dev: true + + /es-to-primitive/1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + + /es6-promise/3.3.1: + resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} + dev: true + + /esbuild/0.16.17: + resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.16.17 + '@esbuild/android-arm64': 0.16.17 + '@esbuild/android-x64': 0.16.17 + '@esbuild/darwin-arm64': 0.16.17 + '@esbuild/darwin-x64': 0.16.17 + '@esbuild/freebsd-arm64': 0.16.17 + '@esbuild/freebsd-x64': 0.16.17 + '@esbuild/linux-arm': 0.16.17 + '@esbuild/linux-arm64': 0.16.17 + '@esbuild/linux-ia32': 0.16.17 + '@esbuild/linux-loong64': 0.16.17 + '@esbuild/linux-mips64el': 0.16.17 + '@esbuild/linux-ppc64': 0.16.17 + '@esbuild/linux-riscv64': 0.16.17 + '@esbuild/linux-s390x': 0.16.17 + '@esbuild/linux-x64': 0.16.17 + '@esbuild/netbsd-x64': 0.16.17 + '@esbuild/openbsd-x64': 0.16.17 + '@esbuild/sunos-x64': 0.16.17 + '@esbuild/win32-arm64': 0.16.17 + '@esbuild/win32-ia32': 0.16.17 + '@esbuild/win32-x64': 0.16.17 + dev: true + + /escalade/3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + + /escape-string-regexp/1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true + + /esm-env/1.0.0: + resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==} + dev: true + + /estree-walker/1.0.1: + resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} + dev: true + + /estree-walker/2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: true + + /esutils/2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /event-target-shim/5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + /events/3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + /fast-copy/3.0.0: + resolution: {integrity: sha512-4HzS+9pQ5Yxtv13Lhs1Z1unMXamBdn5nA4bEi1abYpDNSpSp7ODYQ1KPMF6nTatfEzgH6/zPvXKU1zvHiUjWlA==} + dev: true + + /fast-deep-equal/3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true + + /fast-glob/3.2.12: + resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fast-json-stable-stringify/2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fast-redact/3.1.2: + resolution: {integrity: sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==} + engines: {node: '>=6'} + dev: false + + /fast-safe-stringify/2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + dev: true + + /fastq/1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + dependencies: + reusify: 1.0.4 + dev: true + + /filelist/1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + dependencies: + minimatch: 5.1.6 + dev: true + + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /for-each/0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.7 + dev: true + + /fraction.js/4.2.0: + resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} + dev: true + + /fs-extra/9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.10 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: true + + /fs.realpath/1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true + + /fsevents/2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: true + + /function.prototype.name/1.1.5: + resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + functions-have-names: 1.2.3 + dev: true + + /functions-have-names/1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + dev: true + + /fuzzysort/2.0.4: + resolution: {integrity: sha512-Api1mJL+Ad7W7vnDZnWq5pGaXJjyencT+iKGia2PlHUcSsSzWwIQ3S1isiMpwpavjYtGd2FzhUIhnnhOULZgDw==} + dev: false + + /gensync/1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + dev: true + + /get-intrinsic/1.2.0: + resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + dev: true + + /get-own-enumerable-property-symbols/3.0.2: + resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + dev: true + + /get-symbol-description/1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + dev: true + + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob/7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + dev: true + + /glob/8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: true + + /globals/11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + dev: true + + /globalthis/1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.1.4 + dev: true + + /globalyzer/0.1.0: + resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} + dev: true + + /globrex/0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + dev: true + + /gopd/1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.0 + dev: true + + /graceful-fs/4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + dev: true + + /has-bigints/1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + dev: true + + /has-flag/3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: true + + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /has-property-descriptors/1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.2.0 + dev: true + + /has-proto/1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + dev: true + + /has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: true + + /has-tostringtag/1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: true + + /help-me/4.2.0: + resolution: {integrity: sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==} + dependencies: + glob: 8.1.0 + readable-stream: 3.6.0 + dev: true + + /hosted-git-info/2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: true + + /idb/7.1.1: + resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} + dev: true + + /ieee754/1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + /import-fresh/3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /inflight/1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + dev: true + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /internal-slot/1.0.4: + resolution: {integrity: sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.0 + has: 1.0.3 + side-channel: 1.0.4 + dev: true + + /is-array-buffer/3.0.1: + resolution: {integrity: sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + is-typed-array: 1.1.10 + dev: true + + /is-arrayish/0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true + + /is-bigint/1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 + dev: true + + /is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-boolean-object/1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-builtin-module/3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} + engines: {node: '>=6'} + dependencies: + builtin-modules: 3.3.0 + dev: true + + /is-callable/1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true + + /is-core-module/2.11.0: + resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} + dependencies: + has: 1.0.3 + dev: true + + /is-date-object/1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-module/1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + dev: true + + /is-negative-zero/2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + dev: true + + /is-number-object/1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /is-obj/1.0.1: + resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} + engines: {node: '>=0.10.0'} + dev: true + + /is-reference/1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + dependencies: + '@types/estree': 1.0.0 + dev: true + + /is-regex/1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-regexp/1.0.0: + resolution: {integrity: sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==} + engines: {node: '>=0.10.0'} + dev: true + + /is-shared-array-buffer/1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-stream/2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: true + + /is-string/1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-symbol/1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /is-typed-array/1.1.10: + resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + dev: true + + /is-weakref/1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.2 + dev: true + + /isexe/2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + dev: true + + /jake/10.8.5: + resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + async: 3.2.4 + chalk: 4.1.2 + filelist: 1.0.4 + minimatch: 3.1.2 + dev: true + + /jest-worker/26.6.2: + resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} + engines: {node: '>= 10.13.0'} + dependencies: + '@types/node': 18.13.0 + merge-stream: 2.0.0 + supports-color: 7.2.0 + dev: true + + /joycon/3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + dev: true + + /js-cookie/3.0.1: + resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==} + engines: {node: '>=12'} + dev: false + + /js-tokens/4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true + + /jsesc/0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true + dev: true + + /jsesc/2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /json-parse-better-errors/1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + dev: true + + /json-schema-traverse/1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + dev: true + + /json-schema/0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + dev: true + + /json5/2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + dev: true + + /jsonfile/6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.10 + dev: true + + /jsonpointer/5.0.1: + resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} + engines: {node: '>=0.10.0'} + dev: true + + /kleur/4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + dev: true + + /leven/3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + dev: true + + /lilconfig/2.0.6: + resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} + engines: {node: '>=10'} + dev: true + + /load-json-file/4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + dependencies: + graceful-fs: 4.2.10 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + dev: true + + /lodash.debounce/4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + dev: true + + /lodash.sortby/4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + dev: true + + /lodash/4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: true + + /lru-cache/5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + dependencies: + yallist: 3.1.1 + dev: true + + /magic-string/0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + dependencies: + sourcemap-codec: 1.4.8 + dev: true + + /magic-string/0.27.0: + resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.14 + dev: true + + /memorystream/0.3.1: + resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} + engines: {node: '>= 0.10.0'} + dev: true + + /merge-stream/2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + + /merge2/1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /micromatch/4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /mime/3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + dev: true + + /min-indent/1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + + /mini-svg-data-uri/1.4.4: + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} + hasBin: true + dev: true + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimatch/5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimist/1.2.7: + resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + dev: true + + /mkdirp/0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.7 + dev: true + + /mri/1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + dev: true + + /mrmime/1.0.1: + resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} + engines: {node: '>=10'} + dev: true + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /nice-try/1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + dev: true + + /node-releases/2.0.10: + resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} + dev: true + + /normalize-package-data/2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.1 + semver: 5.7.1 + validate-npm-package-license: 3.0.4 + dev: true + + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /normalize-range/0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + dev: true + + /npm-run-all/4.1.5: + resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==} + engines: {node: '>= 4'} + hasBin: true + dependencies: + ansi-styles: 3.2.1 + chalk: 2.4.2 + cross-spawn: 6.0.5 + memorystream: 0.3.1 + minimatch: 3.1.2 + pidtree: 0.3.1 + read-pkg: 3.0.0 + shell-quote: 1.8.0 + string.prototype.padend: 3.1.4 + dev: true + + /object-hash/3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + dev: true + + /object-inspect/1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + dev: true + + /object-keys/1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object.assign/4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /on-exit-leak-free/2.1.0: + resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} + + /once/1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 + dev: true + + /parent-module/1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /parse-json/4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + dependencies: + error-ex: 1.3.2 + json-parse-better-errors: 1.0.2 + dev: true + + /path-is-absolute/1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true + + /path-key/2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + dev: true + + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + + /path-type/3.0.0: + resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} + engines: {node: '>=4'} + dependencies: + pify: 3.0.0 + dev: true + + /picocolors/1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true + + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pidtree/0.3.1: + resolution: {integrity: sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==} + engines: {node: '>=0.10'} + hasBin: true + dev: true + + /pify/2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + dev: true + + /pify/3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + dev: true + + /pino-abstract-transport/1.0.0: + resolution: {integrity: sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==} + dependencies: + readable-stream: 4.3.0 + split2: 4.1.0 + + /pino-pretty/9.1.1: + resolution: {integrity: sha512-iJrnjgR4FWQIXZkUF48oNgoRI9BpyMhaEmihonHeCnZ6F50ZHAS4YGfGBT/ZVNsPmd+hzkIPGzjKdY08+/yAXw==} + hasBin: true + dependencies: + colorette: 2.0.19 + dateformat: 4.6.3 + fast-copy: 3.0.0 + fast-safe-stringify: 2.1.1 + help-me: 4.2.0 + joycon: 3.1.1 + minimist: 1.2.7 + on-exit-leak-free: 2.1.0 + pino-abstract-transport: 1.0.0 + pump: 3.0.0 + readable-stream: 4.3.0 + secure-json-parse: 2.7.0 + sonic-boom: 3.2.1 + strip-json-comments: 3.1.1 + dev: true + + /pino-seq/0.9.0: + resolution: {integrity: sha512-XbvixSHuoC6IXQ6rbdD2YFZDaPFTY505uEzWX4rNpiZQwecbwHzSyBxTdvgwLBR4WXOzfKWixKn/vXbhclIijw==} + hasBin: true + dependencies: + '@types/pino': 5.20.0 + commander: 2.20.3 + seq-logging: 1.1.2 + split2: 3.2.2 + dev: false + + /pino-std-serializers/6.1.0: + resolution: {integrity: sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==} + dev: false + + /pino/8.9.0: + resolution: {integrity: sha512-/x9qSrFW4wh+7OL5bLIbfl06aK/8yCSIldhD3VmVGiVYWSdFFpXvJh/4xWKENs+DhG1VkJnnpWxMF6fZ2zGXeg==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.1.2 + on-exit-leak-free: 2.1.0 + pino-abstract-transport: 1.0.0 + pino-std-serializers: 6.1.0 + process-warning: 2.1.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.4.2 + sonic-boom: 3.2.1 + thread-stream: 2.3.0 + dev: false + + /playwright-core/1.30.0: + resolution: {integrity: sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /postcss-import/14.1.0_postcss@8.4.21: + resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} + engines: {node: '>=10.0.0'} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.21 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.1 + dev: true + + /postcss-js/4.0.1_postcss@8.4.21: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.21 + dev: true + + /postcss-load-config/3.1.4_postcss@8.4.21: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.0.6 + postcss: 8.4.21 + yaml: 1.10.2 + dev: true + + /postcss-load-config/4.0.1_postcss@8.4.21: + resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 2.0.6 + postcss: 8.4.21 + yaml: 2.2.1 + dev: true + + /postcss-nested/6.0.0_postcss@8.4.21: + resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.21 + postcss-selector-parser: 6.0.11 + dev: true + + /postcss-selector-parser/6.0.11: + resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: true + + /postcss-value-parser/4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + dev: true + + /postcss/8.4.21: + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /pretty-bytes/5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} + dev: true + + /pretty-bytes/6.1.0: + resolution: {integrity: sha512-Rk753HI8f4uivXi4ZCIYdhmG1V+WKzvRMg/X+M42a6t7D07RcmopXJMDNk6N++7Bl75URRGsb40ruvg7Hcp2wQ==} + engines: {node: ^14.13.1 || >=16.0.0} + dev: true + + /process-warning/2.1.0: + resolution: {integrity: sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg==} + dev: false + + /process/0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + /pump/3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: true + + /punycode/2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} + engines: {node: '>=6'} + dev: true + + /queue-microtask/1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /quick-format-unescaped/4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + dev: false + + /quick-lru/5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + dev: true + + /randombytes/2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /read-cache/1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 + dev: true + + /read-pkg/3.0.0: + resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} + engines: {node: '>=4'} + dependencies: + load-json-file: 4.0.0 + normalize-package-data: 2.5.0 + path-type: 3.0.0 + dev: true + + /readable-stream/3.6.0: + resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + /readable-stream/4.3.0: + resolution: {integrity: sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + + /readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /real-require/0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + dev: false + + /regenerate-unicode-properties/10.1.0: + resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==} + engines: {node: '>=4'} + dependencies: + regenerate: 1.4.2 + dev: true + + /regenerate/1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + dev: true + + /regenerator-runtime/0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + dev: true + + /regenerator-transform/0.15.1: + resolution: {integrity: sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==} + dependencies: + '@babel/runtime': 7.20.13 + dev: true + + /regexp.prototype.flags/1.4.3: + resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + functions-have-names: 1.2.3 + dev: true + + /regexpu-core/5.3.0: + resolution: {integrity: sha512-ZdhUQlng0RoscyW7jADnUZ25F5eVtHdMyXSb2PiwafvteRAOJUjFoUPEYZSIfP99fBIs3maLIRfpEddT78wAAQ==} + engines: {node: '>=4'} + dependencies: + '@babel/regjsgen': 0.8.0 + regenerate: 1.4.2 + regenerate-unicode-properties: 10.1.0 + regjsparser: 0.9.1 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.1.0 + dev: true + + /regjsparser/0.9.1: + resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} + hasBin: true + dependencies: + jsesc: 0.5.0 + dev: true + + /require-from-string/2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + dev: true + + /resolve-from/4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /resolve/1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + hasBin: true + dependencies: + is-core-module: 2.11.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + + /reusify/1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true + + /rimraf/2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + + /rollup-plugin-terser/7.0.2_rollup@2.79.1: + resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} + deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser + peerDependencies: + rollup: ^2.0.0 + dependencies: + '@babel/code-frame': 7.18.6 + jest-worker: 26.6.2 + rollup: 2.79.1 + serialize-javascript: 4.0.0 + terser: 5.16.3 + dev: true + + /rollup/2.79.1: + resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} + engines: {node: '>=10.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /rollup/3.14.0: + resolution: {integrity: sha512-o23sdgCLcLSe3zIplT9nQ1+r97okuaiR+vmAPZPTDYB7/f3tgWIYNyiQveMsZwshBT0is4eGax/HH83Q7CG+/Q==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /run-parallel/1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /sade/1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + dependencies: + mri: 1.2.0 + dev: true + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + /safe-regex-test/1.0.0: + resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + is-regex: 1.1.4 + dev: true + + /safe-stable-stringify/2.4.2: + resolution: {integrity: sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==} + engines: {node: '>=10'} + dev: false + + /sander/0.5.1: + resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} + dependencies: + es6-promise: 3.3.1 + graceful-fs: 4.2.10 + mkdirp: 0.5.6 + rimraf: 2.7.1 + dev: true + + /secure-json-parse/2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + dev: true + + /semver/5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + dev: true + + /semver/6.3.0: + resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + hasBin: true + dev: true + + /seq-logging/1.1.2: + resolution: {integrity: sha512-9n7bCIHiMdBene104oSEa2917OcNBw+uee2v+we4AQxmjqt/aeQkWy1296IvGsogbj5fK6wuDNhVhm/DYmauVA==} + dev: false + + /serialize-javascript/4.0.0: + resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==} + dependencies: + randombytes: 2.1.0 + dev: true + + /set-cookie-parser/2.5.1: + resolution: {integrity: sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==} + dev: true + + /shebang-command/1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + dependencies: + shebang-regex: 1.0.0 + dev: true + + /shebang-regex/1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + dev: true + + /shell-quote/1.8.0: + resolution: {integrity: sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==} + dev: true + + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.0 + object-inspect: 1.12.3 + dev: true + + /sirv/2.0.2: + resolution: {integrity: sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==} + engines: {node: '>= 10'} + dependencies: + '@polka/url': 1.0.0-next.21 + mrmime: 1.0.1 + totalist: 3.0.0 + dev: true + + /sonic-boom/3.2.1: + resolution: {integrity: sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A==} + dependencies: + atomic-sleep: 1.0.0 + + /sorcery/0.11.0: + resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==} + hasBin: true + dependencies: + '@jridgewell/sourcemap-codec': 1.4.14 + buffer-crc32: 0.2.13 + minimist: 1.2.7 + sander: 0.5.1 + dev: true + + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + + /source-map-support/0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map/0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + + /source-map/0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + dependencies: + whatwg-url: 7.1.0 + dev: true + + /sourcemap-codec/1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + dev: true + + /spdx-correct/3.1.1: + resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-exceptions/2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse/3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-license-ids/3.0.12: + resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} + dev: true + + /split2/3.2.2: + resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + dependencies: + readable-stream: 3.6.0 + dev: false + + /split2/4.1.0: + resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==} + engines: {node: '>= 10.x'} + + /streamsearch/1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + dev: true + + /string.prototype.matchall/4.0.8: + resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + get-intrinsic: 1.2.0 + has-symbols: 1.0.3 + internal-slot: 1.0.4 + regexp.prototype.flags: 1.4.3 + side-channel: 1.0.4 + dev: true + + /string.prototype.padend/3.1.4: + resolution: {integrity: sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + dev: true + + /string.prototype.trimend/1.0.6: + resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + dev: true + + /string.prototype.trimstart/1.0.6: + resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.21.1 + dev: true + + /string_decoder/1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + + /stringify-object/3.3.0: + resolution: {integrity: sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==} + engines: {node: '>=4'} + dependencies: + get-own-enumerable-property-symbols: 3.0.2 + is-obj: 1.0.1 + is-regexp: 1.0.0 + dev: true + + /strip-bom/3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true + + /strip-comments/2.0.1: + resolution: {integrity: sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==} + engines: {node: '>=10'} + dev: true + + /strip-indent/3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + + /strip-json-comments/3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /supports-color/5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + + /supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + + /svelte-check/3.0.3_gqx7lw3sljhsd4bstor5m2aa2u: + resolution: {integrity: sha512-ByBFXo3bfHRGIsYEasHkdMhLkNleVfszX/Ns1oip58tPJlKdo5Ssr8kgVIuo5oq00hss8AIcdesuy0Xt0BcTvg==} + hasBin: true + peerDependencies: + svelte: ^3.55.0 + dependencies: + '@jridgewell/trace-mapping': 0.3.17 + chokidar: 3.5.3 + fast-glob: 3.2.12 + import-fresh: 3.3.0 + picocolors: 1.0.0 + sade: 1.8.1 + svelte: 3.55.1 + svelte-preprocess: 5.0.1_b25a55hc532q56kmuqlrolam2i + typescript: 4.9.5 + transitivePeerDependencies: + - '@babel/core' + - coffeescript + - less + - postcss + - postcss-load-config + - pug + - sass + - stylus + - sugarss + dev: true + + /svelte-headless-table/0.17.2_svelte@3.55.1: + resolution: {integrity: sha512-XYG3PC6HzotIhzeqb+M+HGwtkfIhuRJr2+BgRYYD4xTANEUtlWg6M8tIgQp7fqJf7VKOdhmXQbyvjWKE/VGG3w==} + dependencies: + svelte-keyed: 1.1.6_svelte@3.55.1 + svelte-render: 1.6.0 + svelte-subscribe: 1.0.5 + transitivePeerDependencies: + - svelte + dev: false + + /svelte-hmr/0.15.1_svelte@3.55.1: + resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==} + engines: {node: ^12.20 || ^14.13.1 || >= 16} + peerDependencies: + svelte: '>=3.19.0' + dependencies: + svelte: 3.55.1 + dev: true + + /svelte-keyed/1.1.6_svelte@3.55.1: + resolution: {integrity: sha512-sd/7j3waSpOkeiQqATstlvNySbEpSs49aoaZ/nc09x/iEndnXQ/9Zy5PTD06+C7hlZk8KjZ4bhrktAQ791jWMQ==} + peerDependencies: + svelte: ^3.49.0 + dependencies: + svelte: 3.55.1 + dev: false + + /svelte-preprocess/5.0.1_b25a55hc532q56kmuqlrolam2i: + resolution: {integrity: sha512-0HXyhCoc9rsW4zGOgtInylC6qj259E1hpFnJMJWTf+aIfeqh4O/QHT31KT2hvPEqQfdjmqBR/kO2JDkkciBLrQ==} + engines: {node: '>= 14.10.0'} + requiresBuild: true + peerDependencies: + '@babel/core': ^7.10.2 + coffeescript: ^2.5.1 + less: ^3.11.3 || ^4.0.0 + postcss: ^7 || ^8 + postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0 + pug: ^3.0.0 + sass: ^1.26.8 + stylus: ^0.55.0 + sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0 + svelte: ^3.23.0 + typescript: ^3.9.5 || ^4.0.0 + peerDependenciesMeta: + '@babel/core': + optional: true + coffeescript: + optional: true + less: + optional: true + postcss: + optional: true + postcss-load-config: + optional: true + pug: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + typescript: + optional: true + dependencies: + '@types/pug': 2.0.6 + '@types/sass': 1.43.1 + detect-indent: 6.1.0 + magic-string: 0.27.0 + postcss: 8.4.21 + postcss-load-config: 4.0.1_postcss@8.4.21 + sorcery: 0.11.0 + strip-indent: 3.0.0 + svelte: 3.55.1 + typescript: 4.9.5 + dev: true + + /svelte-render/1.6.0: + resolution: {integrity: sha512-Y6Ea6U768MuhH0xR72pAPnXdSnJ+4yi24YyD/b9Q46yGalQFVxUyan496EELWtQEvgQE9Z2PsFAG8JjvrWcDAg==} + dependencies: + svelte-subscribe: 1.0.5 + dev: false + + /svelte-subscribe/1.0.5: + resolution: {integrity: sha512-p+vRSBVzR9BQC72mjd2eqCv8zx5euLZQJF7QqAw5d41aKzQVOq90y71/NXch+nDNMjWbRo0CX+brcWYgPryJlw==} + dev: false + + /svelte/3.55.1: + resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==} + engines: {node: '>= 8'} + + /tailwindcss/3.2.6_postcss@8.4.21: + resolution: {integrity: sha512-BfgQWZrtqowOQMC2bwaSNe7xcIjdDEgixWGYOd6AL0CbKHJlvhfdbINeAW76l1sO+1ov/MJ93ODJ9yluRituIw==} + engines: {node: '>=12.13.0'} + hasBin: true + peerDependencies: + postcss: ^8.0.9 + dependencies: + arg: 5.0.2 + chokidar: 3.5.3 + color-name: 1.1.4 + detective: 5.2.1 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.2.12 + glob-parent: 6.0.2 + is-glob: 4.0.3 + lilconfig: 2.0.6 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.21 + postcss-import: 14.1.0_postcss@8.4.21 + postcss-js: 4.0.1_postcss@8.4.21 + postcss-load-config: 3.1.4_postcss@8.4.21 + postcss-nested: 6.0.0_postcss@8.4.21 + postcss-selector-parser: 6.0.11 + postcss-value-parser: 4.2.0 + quick-lru: 5.1.1 + resolve: 1.22.1 + transitivePeerDependencies: + - ts-node + dev: true + + /temp-dir/2.0.0: + resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + engines: {node: '>=8'} + dev: true + + /temporal-polyfill/0.0.8: + resolution: {integrity: sha512-IuA8GhS1PRC04H/zVNAIxJvCZQum6V5HjqFj7gz1a3SMUf/Kf1xIXILNYtxrWYnGqIU/RrDRxlCKCm/vmqnBvw==} + dependencies: + temporal-spec: 0.0.3 + dev: false + + /temporal-spec/0.0.3: + resolution: {integrity: sha512-gJu7QRqn5c2vTSkYWGC4qz1i+FZ9C+Cz16UIBMRcjgXOsHfXeSIgaWUKeq/2rz1iNfFxvmF/ywqbfC6ggTpjkA==} + dev: false + + /tempy/0.6.0: + resolution: {integrity: sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==} + engines: {node: '>=10'} + dependencies: + is-stream: 2.0.1 + temp-dir: 2.0.0 + type-fest: 0.16.0 + unique-string: 2.0.0 + dev: true + + /terser/5.16.3: + resolution: {integrity: sha512-v8wWLaS/xt3nE9dgKEWhNUFP6q4kngO5B8eYFUuebsu7Dw/UNAnpUod6UHo04jSSkv8TzKHjZDSd7EXdDQAl8Q==} + engines: {node: '>=10'} + hasBin: true + dependencies: + '@jridgewell/source-map': 0.3.2 + acorn: 8.8.2 + commander: 2.20.3 + source-map-support: 0.5.21 + dev: true + + /thread-stream/2.3.0: + resolution: {integrity: sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==} + dependencies: + real-require: 0.2.0 + dev: false + + /tiny-glob/0.2.9: + resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} + dependencies: + globalyzer: 0.1.0 + globrex: 0.1.2 + dev: true + + /to-fast-properties/2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: true + + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /totalist/3.0.0: + resolution: {integrity: sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==} + engines: {node: '>=6'} + dev: true + + /tr46/1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + dependencies: + punycode: 2.3.0 + dev: true + + /tslib/2.5.0: + resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} + dev: true + + /turbo-query/1.9.0: + resolution: {integrity: sha512-14E/AJkYEexNBP6PiT4lJCPZloCLVg5HIPE1OrmcIAQmrwgq0QdJL3eIjYebCQ5uvTgIyg+jp/J7/o6l+3GTrQ==} + dev: false + + /type-fest/0.16.0: + resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} + engines: {node: '>=10'} + dev: true + + /typed-array-length/1.0.4: + resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + dependencies: + call-bind: 1.0.2 + for-each: 0.3.3 + is-typed-array: 1.1.10 + dev: true + + /typesafe-i18n/5.24.0_typescript@4.9.5: + resolution: {integrity: sha512-GGIV+x+Azs+uVe1940ZX3MtIKSN0eXrO/x1Z7d0B/FO610evlmTEBIIYIHFWvjhZJslty11INedwRkZKSDVwTQ==} + hasBin: true + peerDependencies: + typescript: '>=3.5.1' + dependencies: + typescript: 4.9.5 + dev: true + + /typescript/4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /unbox-primitive/1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.2 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: true + + /undici/5.18.0: + resolution: {integrity: sha512-1iVwbhonhFytNdg0P4PqyIAXbdlVZVebtPDvuM36m66mRw4OGrCm2MYynJv/UENFLdP13J1nPVQzVE2zTs1OeA==} + engines: {node: '>=12.18'} + dependencies: + busboy: 1.6.0 + dev: true + + /unicode-canonical-property-names-ecmascript/2.0.0: + resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} + engines: {node: '>=4'} + dev: true + + /unicode-match-property-ecmascript/2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.0 + unicode-property-aliases-ecmascript: 2.1.0 + dev: true + + /unicode-match-property-value-ecmascript/2.1.0: + resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} + engines: {node: '>=4'} + dev: true + + /unicode-property-aliases-ecmascript/2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + dev: true + + /unique-string/2.0.0: + resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} + engines: {node: '>=8'} + dependencies: + crypto-random-string: 2.0.0 + dev: true + + /universalify/2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + engines: {node: '>= 10.0.0'} + dev: true + + /upath/1.2.0: + resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} + engines: {node: '>=4'} + dev: true + + /update-browserslist-db/1.0.10_browserslist@4.21.5: + resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.21.5 + escalade: 3.1.1 + picocolors: 1.0.0 + dev: true + + /uri-js/4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.0 + dev: true + + /util-deprecate/1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + /validate-npm-package-license/3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.1.1 + spdx-expression-parse: 3.0.1 + dev: true + + /vite-plugin-pwa/0.14.3_vite@4.1.1: + resolution: {integrity: sha512-o/CEzdHXamdSV4WJ6hp1EQNe+yVvoFf9b5q1nMhOSqKxaW7BaDqAHAwnq8jt21wakDmcaipnuF3/j78AzZJ6wg==} + peerDependencies: + vite: ^3.1.0 || ^4.0.0 + dependencies: + '@rollup/plugin-replace': 5.0.2_rollup@3.14.0 + debug: 4.3.4 + fast-glob: 3.2.12 + pretty-bytes: 6.1.0 + rollup: 3.14.0 + vite: 4.1.1 + workbox-build: 6.5.4 + workbox-window: 6.5.4 + transitivePeerDependencies: + - '@types/babel__core' + - supports-color + dev: true + + /vite/4.1.1: + resolution: {integrity: sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.16.17 + postcss: 8.4.21 + resolve: 1.22.1 + rollup: 3.14.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /vitefu/0.2.4_vite@4.1.1: + resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 + peerDependenciesMeta: + vite: + optional: true + dependencies: + vite: 4.1.1 + dev: true + + /webidl-conversions/4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + dev: true + + /whatwg-url/7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + dev: true + + /which-boxed-primitive/1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + + /which-typed-array/1.1.9: + resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + is-typed-array: 1.1.10 + dev: true + + /which/1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /workbox-background-sync/6.5.4: + resolution: {integrity: sha512-0r4INQZMyPky/lj4Ou98qxcThrETucOde+7mRGJl13MPJugQNKeZQOdIJe/1AchOP23cTqHcN/YVpD6r8E6I8g==} + dependencies: + idb: 7.1.1 + workbox-core: 6.5.4 + dev: true + + /workbox-broadcast-update/6.5.4: + resolution: {integrity: sha512-I/lBERoH1u3zyBosnpPEtcAVe5lwykx9Yg1k6f8/BGEPGaMMgZrwVrqL1uA9QZ1NGGFoyE6t9i7lBjOlDhFEEw==} + dependencies: + workbox-core: 6.5.4 + dev: true + + /workbox-build/6.5.4: + resolution: {integrity: sha512-kgRevLXEYvUW9WS4XoziYqZ8Q9j/2ziJYEtTrjdz5/L/cTUa2XfyMP2i7c3p34lgqJ03+mTiz13SdFef2POwbA==} + engines: {node: '>=10.0.0'} + dependencies: + '@apideck/better-ajv-errors': 0.3.6_ajv@8.12.0 + '@babel/core': 7.20.12 + '@babel/preset-env': 7.20.2_@babel+core@7.20.12 + '@babel/runtime': 7.20.13 + '@rollup/plugin-babel': 5.3.1_3dsfpkpoyvuuxyfgdbpn4j4uzm + '@rollup/plugin-node-resolve': 11.2.1_rollup@2.79.1 + '@rollup/plugin-replace': 2.4.2_rollup@2.79.1 + '@surma/rollup-plugin-off-main-thread': 2.2.3 + ajv: 8.12.0 + common-tags: 1.8.2 + fast-json-stable-stringify: 2.1.0 + fs-extra: 9.1.0 + glob: 7.2.3 + lodash: 4.17.21 + pretty-bytes: 5.6.0 + rollup: 2.79.1 + rollup-plugin-terser: 7.0.2_rollup@2.79.1 + source-map: 0.8.0-beta.0 + stringify-object: 3.3.0 + strip-comments: 2.0.1 + tempy: 0.6.0 + upath: 1.2.0 + workbox-background-sync: 6.5.4 + workbox-broadcast-update: 6.5.4 + workbox-cacheable-response: 6.5.4 + workbox-core: 6.5.4 + workbox-expiration: 6.5.4 + workbox-google-analytics: 6.5.4 + workbox-navigation-preload: 6.5.4 + workbox-precaching: 6.5.4 + workbox-range-requests: 6.5.4 + workbox-recipes: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + workbox-streams: 6.5.4 + workbox-sw: 6.5.4 + workbox-window: 6.5.4 + transitivePeerDependencies: + - '@types/babel__core' + - supports-color + dev: true + + /workbox-cacheable-response/6.5.4: + resolution: {integrity: sha512-DCR9uD0Fqj8oB2TSWQEm1hbFs/85hXXoayVwFKLVuIuxwJaihBsLsp4y7J9bvZbqtPJ1KlCkmYVGQKrBU4KAug==} + dependencies: + workbox-core: 6.5.4 + dev: true + + /workbox-core/6.5.4: + resolution: {integrity: sha512-OXYb+m9wZm8GrORlV2vBbE5EC1FKu71GGp0H4rjmxmF4/HLbMCoTFws87M3dFwgpmg0v00K++PImpNQ6J5NQ6Q==} + dev: true + + /workbox-expiration/6.5.4: + resolution: {integrity: sha512-jUP5qPOpH1nXtjGGh1fRBa1wJL2QlIb5mGpct3NzepjGG2uFFBn4iiEBiI9GUmfAFR2ApuRhDydjcRmYXddiEQ==} + dependencies: + idb: 7.1.1 + workbox-core: 6.5.4 + dev: true + + /workbox-google-analytics/6.5.4: + resolution: {integrity: sha512-8AU1WuaXsD49249Wq0B2zn4a/vvFfHkpcFfqAFHNHwln3jK9QUYmzdkKXGIZl9wyKNP+RRX30vcgcyWMcZ9VAg==} + dependencies: + workbox-background-sync: 6.5.4 + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + dev: true + + /workbox-navigation-preload/6.5.4: + resolution: {integrity: sha512-IIwf80eO3cr8h6XSQJF+Hxj26rg2RPFVUmJLUlM0+A2GzB4HFbQyKkrgD5y2d84g2IbJzP4B4j5dPBRzamHrng==} + dependencies: + workbox-core: 6.5.4 + dev: true + + /workbox-precaching/6.5.4: + resolution: {integrity: sha512-hSMezMsW6btKnxHB4bFy2Qfwey/8SYdGWvVIKFaUm8vJ4E53JAY+U2JwLTRD8wbLWoP6OVUdFlXsTdKu9yoLTg==} + dependencies: + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + dev: true + + /workbox-range-requests/6.5.4: + resolution: {integrity: sha512-Je2qR1NXCFC8xVJ/Lux6saH6IrQGhMpDrPXWZWWS8n/RD+WZfKa6dSZwU+/QksfEadJEr/NfY+aP/CXFFK5JFg==} + dependencies: + workbox-core: 6.5.4 + dev: true + + /workbox-recipes/6.5.4: + resolution: {integrity: sha512-QZNO8Ez708NNwzLNEXTG4QYSKQ1ochzEtRLGaq+mr2PyoEIC1xFW7MrWxrONUxBFOByksds9Z4//lKAX8tHyUA==} + dependencies: + workbox-cacheable-response: 6.5.4 + workbox-core: 6.5.4 + workbox-expiration: 6.5.4 + workbox-precaching: 6.5.4 + workbox-routing: 6.5.4 + workbox-strategies: 6.5.4 + dev: true + + /workbox-routing/6.5.4: + resolution: {integrity: sha512-apQswLsbrrOsBUWtr9Lf80F+P1sHnQdYodRo32SjiByYi36IDyL2r7BH1lJtFX8fwNHDa1QOVY74WKLLS6o5Pg==} + dependencies: + workbox-core: 6.5.4 + dev: true + + /workbox-strategies/6.5.4: + resolution: {integrity: sha512-DEtsxhx0LIYWkJBTQolRxG4EI0setTJkqR4m7r4YpBdxtWJH1Mbg01Cj8ZjNOO8etqfA3IZaOPHUxCs8cBsKLw==} + dependencies: + workbox-core: 6.5.4 + dev: true + + /workbox-streams/6.5.4: + resolution: {integrity: sha512-FXKVh87d2RFXkliAIheBojBELIPnWbQdyDvsH3t74Cwhg0fDheL1T8BqSM86hZvC0ZESLsznSYWw+Va+KVbUzg==} + dependencies: + workbox-core: 6.5.4 + workbox-routing: 6.5.4 + dev: true + + /workbox-sw/6.5.4: + resolution: {integrity: sha512-vo2RQo7DILVRoH5LjGqw3nphavEjK4Qk+FenXeUsknKn14eCNedHOXWbmnvP4ipKhlE35pvJ4yl4YYf6YsJArA==} + dev: true + + /workbox-window/6.5.4: + resolution: {integrity: sha512-HnLZJDwYBE+hpG25AQBO8RUWBJRaCsI9ksQJEp3aCOFCaG5kqaToAYXFRAHxzRluM2cQbGzdQF5rjKPWPA1fug==} + dependencies: + '@types/trusted-types': 2.0.2 + workbox-core: 6.5.4 + dev: true + + /wrappy/1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true + + /xtend/4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: true + + /yallist/3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + dev: true + + /yaml/1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: true + + /yaml/2.2.1: + resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==} + engines: {node: '>= 14'} + dev: true diff --git a/code/app/postcss.config.cjs b/code/app/postcss.config.cjs new file mode 100644 index 0000000..a53e3b3 --- /dev/null +++ b/code/app/postcss.config.cjs @@ -0,0 +1,13 @@ +const tailwindcss = require("tailwindcss"); +const autoprefixer = require("autoprefixer"); +const nesting = require("tailwindcss/nesting"); + +const config = { + plugins: [ + nesting, + tailwindcss, + autoprefixer + ], +}; + +module.exports = config; diff --git a/code/app/src/actions/pwKey.ts b/code/app/src/actions/pwKey.ts new file mode 100644 index 0000000..cf85685 --- /dev/null +++ b/code/app/src/actions/pwKey.ts @@ -0,0 +1,7 @@ +import { is_testing } from "$configuration"; + +export default function pwKey(node: HTMLElement, value: string | undefined) { + if (!value) return; + if (!is_testing()) return; + node.setAttribute("pw-key", value); +} \ No newline at end of file diff --git a/code/app/src/app.d.ts b/code/app/src/app.d.ts new file mode 100644 index 0000000..31b276e --- /dev/null +++ b/code/app/src/app.d.ts @@ -0,0 +1,9 @@ +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +// and what to do when importing types +declare namespace App { + interface Locals { } + interface Platform { } + interface PrivateEnv { } + interface PublicEnv { } +} \ No newline at end of file diff --git a/code/app/src/app.html b/code/app/src/app.html new file mode 100644 index 0000000..308b223 --- /dev/null +++ b/code/app/src/app.html @@ -0,0 +1,14 @@ + + + + + + + %sveltekit.head% + + + +
%sveltekit.body%
+ + + \ No newline at end of file diff --git a/code/app/src/app.pcss b/code/app/src/app.pcss new file mode 100644 index 0000000..5450db4 --- /dev/null +++ b/code/app/src/app.pcss @@ -0,0 +1,34 @@ +/* Write your global styles here, in PostCSS syntax */ +@tailwind base; +@tailwind components; +@tailwind utilities; + +pre { + font-family: monospace !important; +} + +*:focus-visible { + outline: 1px auto; +} + +.c-disabled { + cursor: not-allowed !important; + filter: opacity(.45); + pointer-events: none !important; +} + +.c-disabled.loading { + cursor: wait !important; +} + +.link { + @apply text-blue-600 hover:text-blue-700 transition duration-300 ease-in-out mb-4 cursor-pointer; + + &.danger { + @apply text-red-600 hover:text-red-700; + } + + &.active { + @apply underline + } +} diff --git a/code/app/src/components/alert.svelte b/code/app/src/components/alert.svelte new file mode 100644 index 0000000..16d8340 --- /dev/null +++ b/code/app/src/components/alert.svelte @@ -0,0 +1,268 @@ + + +{#if visible} +
+
+
+ +
+
+ {#if !rightLinkText} + {#if title} +

+ {title} +

+ {/if} + {#if message} +
+

+ {@html message} +

+
+ {/if} + {#if listItems?.length ?? 0} +
    + {#each listItems as listItem} +
  • {listItem}
  • + {/each} +
+ {/if} + {:else} +
+
+ {#if title} +

+ {title} +

+ {/if} + {#if message} +
+

+ {@html message} +

+
+ {/if} + {#if listItems?.length ?? 0} +
    + {#each listItems as listItem} +
  • {listItem}
  • + {/each} +
+ {/if} +
+

+ rightLinkClicked()} + class="whitespace-nowrap font-medium text-{colorClassPart}-700 hover:text-{colorClassPart}-600" + > + {rightLinkText} + + +

+
+ {/if} + {#if actions?.length ?? 0} +
+
+ {#each actions as action} + {@const color = action?.color ?? colorClassPart} + + {/each} +
+
+ {/if} +
+ {#if closeable} +
+
+ +
+
+ {/if} +
+
+{/if} diff --git a/code/app/src/components/badge.svelte b/code/app/src/components/badge.svelte new file mode 100644 index 0000000..f967c2d --- /dev/null +++ b/code/app/src/components/badge.svelte @@ -0,0 +1,76 @@ + + + + {#if withDot} + + + + {/if} + {text} + {#if removable} + + {/if} + diff --git a/code/app/src/components/button.svelte b/code/app/src/components/button.svelte new file mode 100644 index 0000000..1d6ac4b --- /dev/null +++ b/code/app/src/components/button.svelte @@ -0,0 +1,116 @@ + + + + +{#if href} + + {#if loading} + + {/if} + {text} + +{:else} + +{/if} + + diff --git a/code/app/src/components/checkbox.svelte b/code/app/src/components/checkbox.svelte new file mode 100644 index 0000000..db72bee --- /dev/null +++ b/code/app/src/components/checkbox.svelte @@ -0,0 +1,29 @@ + + +
+ + +
diff --git a/code/app/src/components/combobox.svelte b/code/app/src/components/combobox.svelte new file mode 100644 index 0000000..396c18a --- /dev/null +++ b/code/app/src/components/combobox.svelte @@ -0,0 +1,451 @@ + + + + + + +
+ {#if label} + + {/if} +
+
+ {#if multiple === true && hasSelection} +
+ {#each options.filter((c) => c.selected === true) as option} + methods.deselect_entry(e.detail.id)} + text={option.name} + /> + {/each} +
+ {/if} +
+ search.do()} + on:click={search.on_input_click} + on:focus={search.on_input_focus} + on:blur={search.on_input_focusout} + autocomplete="off" + /> + {#if hasSelection} + + {:else} + + + + {/if} +
+
+ {#if errorText} +

+ {errorText} +

+ {/if} +
+
    + {#if searchResults.length > 0} + {#each searchResults.filter((c) => !c.selected) as result} +
  • + {@html highlight(result, '', "")} +
  • + {/each} + {:else if options.length > 0} + {#each options as option} + +
  • + {option.name} + {#if option.selected} + + + + {/if} +
  • + {/each} + {:else} + +

    {noResultsText}

    + {#if createable && !searchValue} +

    {$LL.combobox.createRecordHelpText()}

    + {/if} +
    + {/if} +
+ {#if showCreationHint} +
+
+ {/if} +
+
+
+ + diff --git a/code/app/src/components/icons/adjustments.svelte b/code/app/src/components/icons/adjustments.svelte new file mode 100644 index 0000000..83bda27 --- /dev/null +++ b/code/app/src/components/icons/adjustments.svelte @@ -0,0 +1,14 @@ + + + diff --git a/code/app/src/components/icons/bars-3-center-left.svelte b/code/app/src/components/icons/bars-3-center-left.svelte new file mode 100644 index 0000000..785ece3 --- /dev/null +++ b/code/app/src/components/icons/bars-3-center-left.svelte @@ -0,0 +1,15 @@ + diff --git a/code/app/src/components/icons/calendar.svelte b/code/app/src/components/icons/calendar.svelte new file mode 100644 index 0000000..e0053ee --- /dev/null +++ b/code/app/src/components/icons/calendar.svelte @@ -0,0 +1,14 @@ + + + diff --git a/code/app/src/components/icons/check-circle.svelte b/code/app/src/components/icons/check-circle.svelte new file mode 100644 index 0000000..e30778e --- /dev/null +++ b/code/app/src/components/icons/check-circle.svelte @@ -0,0 +1,13 @@ + diff --git a/code/app/src/components/icons/chevron-down.svelte b/code/app/src/components/icons/chevron-down.svelte new file mode 100644 index 0000000..5b29ece --- /dev/null +++ b/code/app/src/components/icons/chevron-down.svelte @@ -0,0 +1,7 @@ + + + diff --git a/code/app/src/components/icons/chevron-up-down.svelte b/code/app/src/components/icons/chevron-up-down.svelte new file mode 100644 index 0000000..c07aed5 --- /dev/null +++ b/code/app/src/components/icons/chevron-up-down.svelte @@ -0,0 +1,13 @@ + diff --git a/code/app/src/components/icons/chevron-up.svelte b/code/app/src/components/icons/chevron-up.svelte new file mode 100644 index 0000000..289e71d --- /dev/null +++ b/code/app/src/components/icons/chevron-up.svelte @@ -0,0 +1,7 @@ + + + diff --git a/code/app/src/components/icons/database.svelte b/code/app/src/components/icons/database.svelte new file mode 100644 index 0000000..6ffdadb --- /dev/null +++ b/code/app/src/components/icons/database.svelte @@ -0,0 +1,14 @@ + + + diff --git a/code/app/src/components/icons/exclamation-circle.svelte b/code/app/src/components/icons/exclamation-circle.svelte new file mode 100644 index 0000000..2ce79b1 --- /dev/null +++ b/code/app/src/components/icons/exclamation-circle.svelte @@ -0,0 +1,13 @@ + diff --git a/code/app/src/components/icons/exclamation-triangle.svelte b/code/app/src/components/icons/exclamation-triangle.svelte new file mode 100644 index 0000000..8d807db --- /dev/null +++ b/code/app/src/components/icons/exclamation-triangle.svelte @@ -0,0 +1,13 @@ + diff --git a/code/app/src/components/icons/folder-open.svelte b/code/app/src/components/icons/folder-open.svelte new file mode 100644 index 0000000..409c8e2 --- /dev/null +++ b/code/app/src/components/icons/folder-open.svelte @@ -0,0 +1,14 @@ + + + diff --git a/code/app/src/components/icons/funnel.svelte b/code/app/src/components/icons/funnel.svelte new file mode 100644 index 0000000..7e9daeb --- /dev/null +++ b/code/app/src/components/icons/funnel.svelte @@ -0,0 +1,7 @@ + + + diff --git a/code/app/src/components/icons/home.svelte b/code/app/src/components/icons/home.svelte new file mode 100644 index 0000000..ee8305d --- /dev/null +++ b/code/app/src/components/icons/home.svelte @@ -0,0 +1,14 @@ + + + diff --git a/code/app/src/components/icons/index.ts b/code/app/src/components/icons/index.ts new file mode 100644 index 0000000..eb5b439 --- /dev/null +++ b/code/app/src/components/icons/index.ts @@ -0,0 +1,47 @@ +import XIcon from "./x.svelte"; +import MenuIcon from "./menu.svelte"; +import AdjustmentsIcon from "./adjustments.svelte"; +import DatabaseIcon from "./database.svelte"; +import HomeIcon from "./home.svelte"; +import InformationCircleIcon from "./information-circle.svelte"; +import ExclamationTriangleIcon from "./exclamation-triangle.svelte"; +import XCircleIcon from "./x-circle.svelte"; +import CheckCircleIcon from "./check-circle.svelte"; +import XMarkIcon from "./x-mark.svelte"; +import SpinnerIcon from "./spinner.svelte"; +import ExclamationCircleIcon from "./exclamation-circle.svelte"; +import ChevronUpDownIcon from "./chevron-up-down.svelte"; +import MagnifyingGlassIcon from "./magnifying-glass.svelte"; +import Bars3CenterLeftIcon from "./bars-3-center-left.svelte"; +import CalendarIcon from "./calendar.svelte"; +import FolderOpenIcon from "./folder-open.svelte"; +import MegaphoneIcon from "./megaphone.svelte"; +import QueueListIcon from "./queue-list.svelte"; +import ChevronDownIcon from "./chevron-down.svelte"; +import ChevronUpIcon from "./chevron-up.svelte"; +import FunnelIcon from "./funnel.svelte"; + +export { + FunnelIcon, + ChevronDownIcon, + ChevronUpIcon, + QueueListIcon, + FolderOpenIcon, + MegaphoneIcon, + CalendarIcon, + Bars3CenterLeftIcon, + MagnifyingGlassIcon, + ChevronUpDownIcon, + XIcon, + MenuIcon, + HomeIcon, + DatabaseIcon, + AdjustmentsIcon, + InformationCircleIcon, + ExclamationTriangleIcon, + ExclamationCircleIcon, + XCircleIcon, + CheckCircleIcon, + XMarkIcon, + SpinnerIcon +} \ No newline at end of file diff --git a/code/app/src/components/icons/information-circle.svelte b/code/app/src/components/icons/information-circle.svelte new file mode 100644 index 0000000..68dbc60 --- /dev/null +++ b/code/app/src/components/icons/information-circle.svelte @@ -0,0 +1,13 @@ + diff --git a/code/app/src/components/icons/magnifying-glass.svelte b/code/app/src/components/icons/magnifying-glass.svelte new file mode 100644 index 0000000..f8fdb6e --- /dev/null +++ b/code/app/src/components/icons/magnifying-glass.svelte @@ -0,0 +1,13 @@ + diff --git a/code/app/src/components/icons/megaphone.svelte b/code/app/src/components/icons/megaphone.svelte new file mode 100644 index 0000000..7ada5f3 --- /dev/null +++ b/code/app/src/components/icons/megaphone.svelte @@ -0,0 +1,14 @@ + + + diff --git a/code/app/src/components/icons/menu.svelte b/code/app/src/components/icons/menu.svelte new file mode 100644 index 0000000..471d85f --- /dev/null +++ b/code/app/src/components/icons/menu.svelte @@ -0,0 +1,14 @@ + + + diff --git a/code/app/src/components/icons/queue-list.svelte b/code/app/src/components/icons/queue-list.svelte new file mode 100644 index 0000000..6148394 --- /dev/null +++ b/code/app/src/components/icons/queue-list.svelte @@ -0,0 +1,14 @@ + + + diff --git a/code/app/src/components/icons/spinner.svelte b/code/app/src/components/icons/spinner.svelte new file mode 100644 index 0000000..80cc57c --- /dev/null +++ b/code/app/src/components/icons/spinner.svelte @@ -0,0 +1,20 @@ + + + + diff --git a/code/app/src/components/icons/x-circle.svelte b/code/app/src/components/icons/x-circle.svelte new file mode 100644 index 0000000..3793b5a --- /dev/null +++ b/code/app/src/components/icons/x-circle.svelte @@ -0,0 +1,13 @@ + diff --git a/code/app/src/components/icons/x-mark.svelte b/code/app/src/components/icons/x-mark.svelte new file mode 100644 index 0000000..fd1c6a1 --- /dev/null +++ b/code/app/src/components/icons/x-mark.svelte @@ -0,0 +1,11 @@ + diff --git a/code/app/src/components/icons/x.svelte b/code/app/src/components/icons/x.svelte new file mode 100644 index 0000000..6125ab8 --- /dev/null +++ b/code/app/src/components/icons/x.svelte @@ -0,0 +1,14 @@ + + + diff --git a/code/app/src/components/index.ts b/code/app/src/components/index.ts new file mode 100644 index 0000000..494bc0c --- /dev/null +++ b/code/app/src/components/index.ts @@ -0,0 +1,25 @@ +import Alert from "./alert.svelte"; +import Button from "./button.svelte"; +import Checkbox from "./checkbox.svelte"; +import Input from "./input.svelte"; +import LocaleSwitcher from "./locale-switcher.svelte"; +import Switch from "./switch.svelte"; +import Badge from "./badge.svelte"; +import ProjectStatusBadge from "./project-status-badge.svelte"; +import TextArea from "./textarea.svelte"; +import Combobox from "./combobox.svelte"; +import Notification from "./notification.svelte"; + +export { + Badge, + Combobox, + TextArea, + ProjectStatusBadge, + Alert, + Button, + Checkbox, + Input, + LocaleSwitcher, + Switch, + Notification, +}; \ No newline at end of file diff --git a/code/app/src/components/input.svelte b/code/app/src/components/input.svelte new file mode 100644 index 0000000..f97c1f1 --- /dev/null +++ b/code/app/src/components/input.svelte @@ -0,0 +1,112 @@ + + +
+ {#if label && !cornerHint && !hideLabel} + + {:else if cornerHint && !hideLabel} +
+ {#if label} + + {/if} + + {cornerHint} + +
+ {/if} +
+ {#if icon} +
+ +
+ {:else if addon} +
+ {addon} +
+ {/if} + + {#if errorText} +
+ +
+ {/if} +
+ {#if helpText && !errorText} +

+ {helpText} +

+ {/if} + {#if errorText || errors?.length === 1} +

+ {errorText ?? errors[0]} +

+ {:else if errors && errors.length} +
    + {#each errors as error} +
  • {error}
  • + {/each} +
+ {/if} +
diff --git a/code/app/src/components/locale-switcher.svelte b/code/app/src/components/locale-switcher.svelte new file mode 100644 index 0000000..fc03f39 --- /dev/null +++ b/code/app/src/components/locale-switcher.svelte @@ -0,0 +1,56 @@ + + + diff --git a/code/app/src/components/notification.svelte b/code/app/src/components/notification.svelte new file mode 100644 index 0000000..d78b3d3 --- /dev/null +++ b/code/app/src/components/notification.svelte @@ -0,0 +1,119 @@ + + + + +
+
+ +
+
+
+ {#if icon} +
+ +
+ {/if} +
+

{title}

+ {#if subtitle} +

{subtitle}

+ {/if} +
+ {#if !nonClosable} +
+ +
+ {/if} +
+
+
+
+
+
diff --git a/code/app/src/components/project-status-badge.svelte b/code/app/src/components/project-status-badge.svelte new file mode 100644 index 0000000..3e93935 --- /dev/null +++ b/code/app/src/components/project-status-badge.svelte @@ -0,0 +1,25 @@ + + + diff --git a/code/app/src/components/switch.svelte b/code/app/src/components/switch.svelte new file mode 100644 index 0000000..1b67f80 --- /dev/null +++ b/code/app/src/components/switch.svelte @@ -0,0 +1,125 @@ + + + + +
+ {#if hasLabelOrDescription && !rightAlignedLabelDescription} + + {#if label} + {label} + {/if} + {#if description} + {description} + {/if} + + {/if} + {#if type === "short"} + + {:else if type === "icon"} + + {:else if type === "default"} + + {/if} + {#if hasLabelOrDescription && rightAlignedLabelDescription} + + {#if label} + {label} + {/if} + {#if description} + {description} + {/if} + + {/if} +
diff --git a/code/app/src/components/textarea.svelte b/code/app/src/components/textarea.svelte new file mode 100644 index 0000000..223a265 --- /dev/null +++ b/code/app/src/components/textarea.svelte @@ -0,0 +1,81 @@ + + +
+ {#if label} + + {/if} +
+