├── .ci ├── _settings │ ├── base.partial.yml │ ├── base.yml │ ├── ci.partial.yml │ ├── ci.yml │ ├── pr.partial.yml │ └── pr.yml ├── _stages │ ├── _default.yml │ ├── common.env │ ├── dev.env │ ├── dev.yml │ ├── prod.env │ └── prod.yml ├── azure-deploy-kubernetes.yml ├── azure-deploy.yml ├── azure-pipelines-pr-tests.yml ├── azure-pipelines-template.yml ├── azure-pipelines.yml ├── steps │ ├── azure-pipelines-build.yml │ ├── azure-pipelines-docker.yml │ ├── azure-pipelines-tests.yml │ ├── build-import-meta-env.yml │ ├── cache-node-modules.yml │ └── docker-add-tag.yml └── tests │ ├── e2e-branch │ ├── branch.env │ ├── docker-compose.yml │ └── nginx.conf │ ├── e2e-docker.yml │ ├── e2e-stage.yml │ ├── storybook-tests.yml │ └── wait-for-http.sh ├── .config └── dotnet-tools.json ├── .editorconfig ├── .gitignore ├── .husky ├── pre-commit └── task-runner.json ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml ├── prettier.xml ├── templateApp.iml ├── vcs.xml └── watcherTasks.xml ├── .prettierignore ├── .prettierrc.json ├── .template.json ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .yarn └── releases │ └── yarn-3.5.1.cjs ├── .yarnrc.yml ├── LICENSE ├── Main.DotSettings ├── README.md ├── docker-compose.yaml ├── docs ├── Authentication.md ├── Auto-tests.md ├── CHANGELOG.md ├── CI-&-CD.md ├── Client-autogeneration.md ├── Deploy-Azure-WebApp.md ├── Deploy-docker-compose.md ├── Development-Howto.md ├── Email-sending.md ├── GitLab-setup-instruction.md ├── How-to-analyze-&-reduce-bundle-size.md ├── Logging.md ├── Pages-with-Tables-and-Filters.md ├── Postgres-Enums.md ├── Setting-up-development-environment.md ├── UI-tests.md ├── details │ ├── DateTime-handling.md │ ├── Feature-Flags.md │ ├── Filter-Sorting.md │ ├── MultiTenancy-EFIntegration.md │ ├── PullFromTemplate.md │ ├── REST.md │ ├── UIKit.md │ ├── Webhooks.md │ └── filter-sorting-azure-work-items.png └── images │ ├── ci-cd-approval.png │ ├── ci-cd.png │ ├── gitlab-access-token.png │ ├── mcc.png │ ├── sentry.png │ ├── statoscope-1.png │ ├── statoscope-2.png │ └── uitests │ ├── green-icon.png │ ├── main-example.png │ ├── refresh.png │ ├── stop.png │ └── trace-viewer.png ├── e2e ├── .env ├── .env.local_sample ├── .eslintrc.js ├── .gitignore ├── README.md ├── infrastructure │ ├── LocatorWithRootBase.ts │ ├── axios-retry-interceptor.ts │ ├── cache.ts │ ├── cleanup-local-storage.ts │ ├── fixtures.ts │ ├── helpers.ts │ ├── initialize-backend.ts │ └── types.ts ├── package.json ├── page-objects │ ├── LoginPageObject.ts │ ├── MainPageObject.ts │ ├── components │ │ ├── DialogPageObject.ts │ │ ├── DropDownPageObject.ts │ │ ├── FieldPageObject.ts │ │ ├── PopupPageObject.ts │ │ └── popup-helper.ts │ └── products │ │ ├── AddProductPageObject.ts │ │ └── ProductListPageObject.ts ├── playwright.config.ts ├── src │ └── api │ │ ├── api-helper.ts │ │ ├── index.ts │ │ ├── query-client.ts │ │ ├── query-client.types.ts │ │ └── query-client │ │ ├── ProductClient.ts │ │ ├── SignUrlClient.ts │ │ ├── TestApiClient.ts │ │ ├── TestDataClient.ts │ │ ├── VersionClient.ts │ │ └── helpers.ts ├── storybook │ ├── componentList.json │ ├── get-all-stories.test.ts │ ├── storybook-fixture.ts │ ├── storybook-smart.test.ts │ └── storybook-smart.test.ts-snapshots │ │ ├── Alert-default-1-chromium-linux.png │ │ ├── Alert-default-2-chromium-linux.png │ │ ├── Alert-two-modals-1-chromium-linux.png │ │ ├── Alert-two-modals-1-chromium-win32.png │ │ ├── Alert-two-modals-2-chromium-linux.png │ │ ├── Alert-two-modals-2-chromium-win32.png │ │ ├── Alert-with-all-params-1-chromium-linux.png │ │ ├── Alert-with-all-params-1-chromium-win32.png │ │ ├── Alert-with-all-params-2-chromium-linux.png │ │ ├── Alert-with-all-params-2-chromium-win32.png │ │ ├── Button-danger-1-chromium-linux.png │ │ ├── Button-danger-1-chromium-win32.png │ │ ├── Button-danger-2-chromium-linux.png │ │ ├── Button-danger-2-chromium-win32.png │ │ ├── Button-default-1-chromium-linux.png │ │ ├── Button-default-1-chromium-win32.png │ │ ├── Button-default-2-chromium-linux.png │ │ ├── Button-default-2-chromium-win32.png │ │ ├── Button-primary-1-chromium-linux.png │ │ ├── Button-primary-1-chromium-win32.png │ │ ├── Button-primary-2-chromium-linux.png │ │ ├── Button-primary-2-chromium-win32.png │ │ ├── Button-secondary-1-chromium-linux.png │ │ ├── Button-secondary-1-chromium-win32.png │ │ ├── Button-secondary-2-chromium-linux.png │ │ ├── Button-secondary-2-chromium-win32.png │ │ ├── ComboBox-default-1-chromium-linux.png │ │ ├── ComboBox-with-search-1-chromium-linux.png │ │ ├── Confirm-default-1-chromium-linux.png │ │ ├── Confirm-default-1-chromium-win32.png │ │ ├── Confirm-default-2-chromium-linux.png │ │ ├── Confirm-default-2-chromium-win32.png │ │ ├── Confirm-with-all-params-1-chromium-linux.png │ │ ├── Confirm-with-all-params-1-chromium-win32.png │ │ ├── Confirm-with-all-params-2-chromium-linux.png │ │ ├── Confirm-with-all-params-2-chromium-win32.png │ │ ├── DatePicker-default-1-chromium-linux.png │ │ ├── DatePicker-default-1-chromium-win32.png │ │ ├── DatePicker-default-1-http-host-docker-internal-6006-chromium-linux.png │ │ ├── DatePicker-default-2-chromium-linux.png │ │ ├── DatePicker-default-2-http-host-docker-internal-6006-chromium-linux.png │ │ ├── DatePicker-with-time-1-chromium-linux.png │ │ ├── DatePicker-with-time-1-chromium-win32.png │ │ ├── DropDown-cancel-any-selected-value-automatically-1-chromium-linux.png │ │ ├── DropDown-cancel-any-selected-value-automatically-1-chromium-win32.png │ │ ├── DropDown-cancel-any-selected-value-automatically-in-500-ms-1-chromium-linux.png │ │ ├── DropDown-cancel-any-selected-value-automatically-in-500-ms-1-chromium-win32.png │ │ ├── DropDown-cancel-if-asd-is-selected-1-chromium-linux.png │ │ ├── DropDown-cancel-if-asd-is-selected-1-chromium-win32.png │ │ ├── DropDown-cancel-if-asd-is-selected-accept-the-second-time-1-chromium-linux.png │ │ ├── DropDown-cancel-if-asd-is-selected-accept-the-second-time-1-chromium-win32.png │ │ ├── DropDown-default-1-chromium-linux.png │ │ ├── DropDown-default-1-chromium-win32.png │ │ ├── DropDown-default-1-http-host-docker-internal-6006-chromium-linux.png │ │ ├── DropDown-default-2-chromium-linux.png │ │ ├── DropDown-default-2-http-host-docker-internal-6006-chromium-linux.png │ │ ├── Field-default-1-chromium-linux.png │ │ ├── Field-default-1-chromium-win32.png │ │ ├── Field-default-1-http-host-docker-internal-6006-chromium-linux.png │ │ ├── Field-default-2-chromium-linux.png │ │ ├── Field-default-2-http-host-docker-internal-6006-chromium-linux.png │ │ ├── Field-with-hint-1-chromium-linux.png │ │ ├── Field-with-hint-1-chromium-win32.png │ │ ├── Field-with-link-props-1-chromium-linux.png │ │ ├── Field-with-link-props-1-chromium-win32.png │ │ ├── Input-default-1-chromium-linux.png │ │ ├── Input-default-1-chromium-win32.png │ │ ├── Input-default-2-chromium-linux.png │ │ ├── Input-helper-text-1-chromium-linux.png │ │ ├── Input-helper-text-1-chromium-win32.png │ │ ├── Input-password-1-chromium-linux.png │ │ ├── Input-password-1-chromium-win32.png │ │ ├── Input-with-error-1-chromium-linux.png │ │ ├── Input-with-error-1-chromium-win32.png │ │ ├── Prompt-default-1-chromium-linux.png │ │ ├── Prompt-default-1-chromium-win32.png │ │ ├── Prompt-default-2-chromium-linux.png │ │ ├── Prompt-default-2-chromium-win32.png │ │ ├── Prompt-with-all-params-1-chromium-linux.png │ │ ├── Prompt-with-all-params-1-chromium-win32.png │ │ ├── Prompt-with-all-params-2-chromium-linux.png │ │ ├── Prompt-with-all-params-2-chromium-win32.png │ │ ├── TimePicker-default-1-chromium-linux.png │ │ ├── TimePicker-default-1-chromium-win32.png │ │ ├── TimePicker-default-2-chromium-linux.png │ │ ├── TimePicker-interval-10-minutes-1-chromium-linux.png │ │ ├── TimePicker-interval-10-minutes-1-chromium-win32.png │ │ ├── TimePicker-min-time-1-chromium-linux.png │ │ ├── TimePicker-min-time-1-chromium-win32.png │ │ ├── TimePicker-time-set-1-chromium-linux.png │ │ ├── TimePicker-time-set-1-chromium-win32.png │ │ ├── TimePicker-time-set-to-non-present-value-1-chromium-linux.png │ │ └── TimePicker-time-set-to-non-present-value-1-chromium-win32.png ├── tests │ ├── login.spec.ts │ └── products.spec.ts └── tsconfig.json ├── frontend ├── .env ├── .env.development ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .idea │ ├── prettier.xml │ └── vcs.xml ├── .storybook │ ├── main.ts │ ├── preview-head.html │ ├── preview.ts │ └── wrapper.tsx ├── README.md ├── index.html ├── package.json ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── dictionaries │ │ ├── translation.de.json │ │ └── translation.en.json │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── manifest.json │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── mstile-70x70.png │ ├── robots.txt │ ├── safari-pinned-tab.svg │ └── site.webmanifest ├── src │ ├── App.tsx │ ├── application │ │ ├── constants │ │ │ ├── env-variables.ts │ │ │ └── links.ts │ │ ├── localization │ │ │ ├── LanguageProvider.tsx │ │ │ ├── i18next.d.ts │ │ │ ├── locales.ts │ │ │ ├── localization.ts │ │ │ └── useScopedTranslation.ts │ │ └── redux-store │ │ │ ├── index.ts │ │ │ ├── persistence │ │ │ ├── migrations.ts │ │ │ └── persist-config.ts │ │ │ ├── root-reducer.ts │ │ │ ├── root-store.ts │ │ │ ├── theme │ │ │ ├── theme-selectors.ts │ │ │ ├── theme-slice.ts │ │ │ └── theme-types.ts │ │ │ └── types.ts │ ├── assets │ │ ├── animations │ │ │ └── 9629-loading.json │ │ ├── auth │ │ │ ├── eye-hide.svg │ │ │ └── eye-show.svg │ │ ├── icons │ │ │ ├── arrow-down.svg │ │ │ ├── close-button.svg │ │ │ ├── dots.svg │ │ │ ├── i.svg │ │ │ ├── left.svg │ │ │ ├── right.svg │ │ │ └── search.svg │ │ └── img │ │ │ └── identity-221-Azure-Active-Directory.svg │ ├── components │ │ ├── animations │ │ │ └── Lottie.tsx │ │ ├── sign-url │ │ │ └── SignUrlImage.tsx │ │ └── uikit │ │ │ ├── CheckBox.module.scss │ │ │ ├── CheckBox.tsx │ │ │ ├── Field.module.scss │ │ │ ├── Field.tsx │ │ │ ├── FormError.tsx │ │ │ ├── IconButton.module.scss │ │ │ ├── IconButton.tsx │ │ │ ├── RadioButton.module.scss │ │ │ ├── RadioButton.tsx │ │ │ ├── RadioButtonGroup.module.scss │ │ │ ├── RadioButtonGroup.tsx │ │ │ ├── buttons │ │ │ ├── AppLink.module.scss │ │ │ ├── AppLink.tsx │ │ │ ├── Button.module.scss │ │ │ └── Button.tsx │ │ │ ├── inputs │ │ │ ├── Input.module.scss │ │ │ ├── Input.tsx │ │ │ ├── TextArea.module.scss │ │ │ ├── TextArea.tsx │ │ │ ├── date-time │ │ │ │ ├── DatePicker.tsx │ │ │ │ ├── HookFormDatePicker.tsx │ │ │ │ ├── HookFormTimePicker.tsx │ │ │ │ ├── TimePicker.module.scss │ │ │ │ └── TimePicker.tsx │ │ │ ├── dropdown │ │ │ │ ├── ComboBoxInput.tsx │ │ │ │ ├── DropDownInput.tsx │ │ │ │ ├── HookFormComboBoxInput.tsx │ │ │ │ ├── HookFormDropDownInput.tsx │ │ │ │ ├── HookFormMultiSelectDropDownInput.tsx │ │ │ │ ├── MultiSelectDropDownInput.tsx │ │ │ │ ├── SearchInput.tsx │ │ │ │ ├── StyledAutocomplete.module.scss │ │ │ │ ├── StyledAutocomplete.tsx │ │ │ │ ├── VirtualizedListboxAdapter.tsx │ │ │ │ └── types.ts │ │ │ └── hook-form.ts │ │ │ ├── menu │ │ │ ├── AppMenu.module.scss │ │ │ ├── AppMenu.tsx │ │ │ ├── AppPopper.module.scss │ │ │ ├── AppPopper.tsx │ │ │ ├── AppTooltip.module.scss │ │ │ ├── AppTooltip.tsx │ │ │ ├── DotMenu.module.scss │ │ │ ├── DotMenu.tsx │ │ │ ├── MenuDirection.ts │ │ │ ├── anchorTransformOrigin.ts │ │ │ └── useTransitionClass.ts │ │ │ ├── modal │ │ │ ├── CustomModal.module.scss │ │ │ ├── CustomModal.tsx │ │ │ ├── Modal.module.scss │ │ │ ├── useModal.tsx │ │ │ └── useModal.types.ts │ │ │ ├── pagination │ │ │ ├── AppPagination.module.scss │ │ │ └── AppPagination.tsx │ │ │ ├── suspense │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── Loading.module.scss │ │ │ └── Loading.tsx │ │ │ ├── table │ │ │ ├── AppTable.module.scss │ │ │ ├── AppTable.tsx │ │ │ └── updateSortByInUrl.tsx │ │ │ └── type-utils.ts │ ├── helpers │ │ ├── MiniProfiler.tsx │ │ ├── assert-never.ts │ │ ├── auth │ │ │ ├── auth-client.ts │ │ │ ├── auth-data.ts │ │ │ ├── auth-interceptor.ts │ │ │ └── openid │ │ │ │ ├── OpenIdCallback.tsx │ │ │ │ ├── openid-manager.ts │ │ │ │ └── openid-settings.ts │ │ ├── date-helpers.ts │ │ ├── empty-array.ts │ │ ├── error-helpers.ts │ │ ├── form │ │ │ ├── react-hook-form-helper.ts │ │ │ ├── useAdvancedForm.ts │ │ │ └── useErrorHandler.ts │ │ ├── interceptors │ │ │ ├── blob-error-interceptor.ts │ │ │ ├── inject-language-interceptor.ts │ │ │ └── inject-session-interceptor.ts │ │ ├── pagination-helper.ts │ │ ├── query-params.ts │ │ ├── retry-helper.tsx │ │ ├── router │ │ │ ├── useBlockNavigation.ts │ │ │ └── useBlocker.ts │ │ └── useTriggerOnClickOutsideElement.ts │ ├── index.scss │ ├── index.tsx │ ├── logo.svg │ ├── pages │ │ ├── ReactRouterErrorBoundary.tsx │ │ ├── authorized │ │ │ ├── RootPage.module.scss │ │ │ ├── RootPage.tsx │ │ │ ├── products │ │ │ │ ├── ProductListPage.module.scss │ │ │ │ ├── ProductListPage.tsx │ │ │ │ ├── create │ │ │ │ │ └── CreateProductPage.tsx │ │ │ │ ├── details │ │ │ │ │ └── ProductDetailsPage.tsx │ │ │ │ └── edit │ │ │ │ │ └── EditProductPage.tsx │ │ │ └── uikit │ │ │ │ ├── UiKitPage.module.scss │ │ │ │ ├── UiKitPage.tsx │ │ │ │ └── components │ │ │ │ └── PopperExample.tsx │ │ ├── router.tsx │ │ └── unauthorized │ │ │ ├── LoginErrorPage.module.scss │ │ │ ├── LoginErrorPage.tsx │ │ │ └── LoginPage.tsx │ ├── razor.scss │ ├── reportWebVitals.ts │ ├── services │ │ └── api │ │ │ ├── api-client.ts │ │ │ ├── api-client.types.ts │ │ │ ├── api-client │ │ │ ├── ProductClient.ts │ │ │ ├── ProductQuery.ts │ │ │ ├── SignUrlClient.ts │ │ │ ├── SignUrlQuery.ts │ │ │ ├── TestApiClient.ts │ │ │ ├── TestApiQuery.ts │ │ │ ├── TestDataClient.ts │ │ │ ├── TestDataQuery.ts │ │ │ ├── VersionClient.ts │ │ │ ├── VersionQuery.ts │ │ │ └── helpers.ts │ │ │ └── index.ts │ ├── setupTests.ts │ ├── stories │ │ ├── Button.stories.tsx │ │ ├── ComboBox.stories.tsx │ │ ├── DatePicker.stories.tsx │ │ ├── DropDown.stories.tsx │ │ ├── Field.stories.tsx │ │ ├── Input.stories.tsx │ │ ├── Modals │ │ │ ├── Alert.stories.tsx │ │ │ ├── Confirm.stories.tsx │ │ │ └── Prompt.stories.tsx │ │ ├── TimePicker.stories.tsx │ │ └── utils.ts │ └── styles │ │ ├── base.scss │ │ ├── variables.scss │ │ └── z-index.scss ├── tsconfig.json ├── tsconfig.node.json ├── types │ ├── declarations.d.ts │ ├── env.d.ts │ └── jlottie.d.ts └── vite.config.ts ├── k8s ├── README.md ├── app.yaml ├── dashboard │ ├── ingress.yaml │ ├── setup-dashboard.sh │ └── users.yaml ├── init-namespace.sh ├── letsencrypt.yaml ├── postgres.yaml ├── reinit-namespace.sh └── setup.sh ├── nginx ├── conf.d │ ├── compression.conf │ └── proxy.conf ├── logs │ └── placeholder.txt └── vhost.d │ └── default_location ├── package.json ├── patches ├── 2023-05-25-01-vite-autoprefixer.patch ├── 2023-05-28-01-Minor_backend_adjustments.patch ├── 2023-05-29-01-allowImportingTsExtensions.patch ├── 2023-06-01-02-ErrorBoundary-refetch-error-queries.patch ├── 2023-06-03-01-useModal-cancelButton-color.patch ├── 2023-06-03-02-useModal-showMultipleButtonsAdjustments.patch ├── 2023-06-06-01-adjust-error-boundary-for-react-router-data-api.patch ├── 2023-06-09-01-ErrorBoundary-versionQuery.patch ├── 2023-06-09-03-add-testid-to-dotmenu.patch ├── 2023-06-09-04-useAdvancedForm-use-superjson.patch ├── 2023-06-10-01-useEventCallback-in-useErrorHandler.patch ├── 2023-06-10-02-useEventCallback-in-AppMenu.patch ├── 2023-06-21-01-e2e-cacheDataBeforeAll.patch ├── 2023-06-21-02-fix-useModal-for-prompt-when-enter-is-pressed.patch ├── 2023-06-22-01-update-i18next.patch ├── 2023-06-22-02-settingsjson-use-local-typescript.patch └── 2023-07-05-01-useModal-focusing.patch ├── scripts ├── .env_template ├── GenerateCertificate.ps1 ├── GenerateDotEnv.ps1 ├── deploy │ ├── adjust-env-vars.sh │ ├── set-up-stage.sh │ └── wait-for-http.sh ├── integresql │ ├── docker-compose.yml │ └── run-compose.bat ├── mssql │ └── start_mssql.ps1 ├── opensearch │ ├── .env │ ├── Readme.MD │ ├── apply-internal-users.sh │ ├── docker-compose.yml │ ├── generate-certs.sh │ ├── generate-password.sh │ └── internal-users.yml ├── postgresql │ └── start_postgres.ps1 ├── pull-template-changes.ts ├── pull-template-post-processor.ts ├── rename-script.ts ├── tsconfig.json └── updates │ ├── update-helper.ts │ └── update-version.ts ├── tsconfig.json ├── types ├── node-replace.d.ts └── renamer.d.ts ├── webapi ├── .csharpierrc ├── .gitattributes ├── .gitignore ├── .idea │ ├── .gitignore │ ├── .idea.MccSoft.TemplateApp │ │ └── .idea │ │ │ ├── .gitignore │ │ │ ├── .name │ │ │ ├── CSharpierPlugin.xml │ │ │ ├── encodings.xml │ │ │ ├── indexLayout.xml │ │ │ ├── vcs.xml │ │ │ └── watcherTasks.xml │ ├── modules.xml │ ├── vcs.xml │ └── webapi.iml ├── .vscode │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── Directory.Build.props ├── Docs │ └── database.dgml ├── Lib │ ├── DomainHelpers │ │ └── MccSoft.DomainHelpers │ │ │ ├── DomainEvents │ │ │ ├── Events │ │ │ │ └── LogDomainEvent.cs │ │ │ ├── IDomainEvent.cs │ │ │ ├── IDomainEventEntity.cs │ │ │ ├── IDomainEventWithIntegerId.cs │ │ │ └── IDomainEventWithStringId.cs │ │ │ ├── EntityExtensions.cs │ │ │ ├── MccSoft.DomainHelpers.csproj │ │ │ └── Specification.cs │ ├── Health │ │ ├── MccSoft.Health │ │ │ ├── HealthCheckSettings.cs │ │ │ ├── HealthCheckWithTimeOut.cs │ │ │ ├── HealthExtensions.cs │ │ │ ├── MccSoft.Health.csproj │ │ │ └── MetricsRegistry.cs │ │ └── README.md │ ├── HttpClientExtension │ │ └── MccSoft.HttpClientExtension │ │ │ ├── AuthorizationHandler.cs │ │ │ ├── ClaimExtensions.cs │ │ │ ├── FailedRequestException.cs │ │ │ ├── HttpClientExtensions.cs │ │ │ ├── ITokenHandler.cs │ │ │ ├── MccSoft.HttpClientExtension.csproj │ │ │ └── ResponseExtension.cs │ ├── Logging │ │ ├── MccSoft.Logging.Tests │ │ │ ├── FieldTests.cs │ │ │ ├── MccSoft.Logging.Tests.csproj │ │ │ └── Usings.cs │ │ └── MccSoft.Logging │ │ │ ├── Disposable.cs │ │ │ ├── Elastic │ │ │ ├── ElasticLoggerExtensions.cs │ │ │ └── ElasticLoggingOptions.cs │ │ │ ├── ElasticFieldAttribute.cs │ │ │ ├── Field.cs │ │ │ ├── Field.partial.cs │ │ │ ├── FieldType.cs │ │ │ ├── INoSentryException.cs │ │ │ ├── LogAttribute.cs │ │ │ ├── LogAttributePostProcess.partial.cs │ │ │ ├── LoggerConfigurationExtensions.cs │ │ │ ├── LoggerExtensions.cs │ │ │ ├── LogglyOptions.cs │ │ │ ├── MccSoft.Logging.csproj │ │ │ ├── OperationContext.cs │ │ │ ├── OperationContextEnricher.cs │ │ │ ├── PrefixRenameFormatter.cs │ │ │ ├── SerilogRequestLoggingOptions.cs │ │ │ ├── SessionIdHeader.cs │ │ │ └── TopLevelActivityScope.cs │ ├── LowLevelPrimitives │ │ └── MccSoft.LowLevelPrimitives │ │ │ ├── CustomTenantIdAccessor.cs │ │ │ ├── DateTimeHelper.cs │ │ │ ├── Disposable.cs │ │ │ ├── ErrorTypes.cs │ │ │ ├── Exceptions │ │ │ ├── AccessDeniedException.cs │ │ │ ├── ConflictException.cs │ │ │ ├── IWebApiException.cs │ │ │ ├── InvalidRequestContentTypeException.cs │ │ │ ├── PersistenceAccessDeniedException.cs │ │ │ ├── PersistenceResourceNotFoundException.cs │ │ │ ├── ValidationException.cs │ │ │ └── WebApiBaseException.cs │ │ │ ├── Extensions │ │ │ ├── DateOnlyExtensions.cs │ │ │ ├── DateTimeExtensions.cs │ │ │ ├── LinqExtensions.cs │ │ │ ├── RandomExtensions.cs │ │ │ └── StringExtensions.cs │ │ │ ├── IUserAccessor.cs │ │ │ ├── IUserAccessor.partial.cs │ │ │ ├── MccSoft.LowLevelPrimitives.csproj │ │ │ └── Serialization │ │ │ ├── JsonExtensions.cs │ │ │ ├── JsonNetDateTimeUtcConverter.cs │ │ │ └── JsonObject │ │ │ └── JsonObjectConverter.cs │ ├── Mailing │ │ └── MccSoft.Mailing │ │ │ ├── MailingExtensions.cs │ │ │ ├── MccSoft.Mailing.csproj │ │ │ ├── Models │ │ │ ├── EmailModelBase.cs │ │ │ ├── MailSenderOptions.cs │ │ │ └── MailSettings.cs │ │ │ └── Services │ │ │ ├── EmailTemplateType.cs │ │ │ ├── IMailSender.cs │ │ │ ├── IRazorRenderService.cs │ │ │ ├── MailSender.cs │ │ │ └── RazorRenderService.cs │ ├── NpgSql │ │ └── MccSoft.NpgSql │ │ │ ├── Extensions.cs │ │ │ ├── MccSoft.NpgSql.csproj │ │ │ └── NpgsqlDbContextOptionsBuilderExtensions.cs │ ├── PersistenceHelpers │ │ └── MccSoft.PersistenceHelpers │ │ │ ├── DbContextQueryFilterExtensions.cs │ │ │ ├── DbRetryHelper.cs │ │ │ ├── DomainEvents │ │ │ ├── DbContextInterceptorHelpers.cs │ │ │ ├── DomainEventsExtensions.cs │ │ │ ├── DomainEventsSaveChangesInterceptor.cs │ │ │ └── DomainEventsTransactionInterceptor.cs │ │ │ ├── EfLogHelper.cs │ │ │ ├── Extensions.cs │ │ │ ├── MccSoft.PersistenceHelpers.csproj │ │ │ ├── ModelBuilderExtensions.cs │ │ │ ├── PostProcessEntitiesOnSaveInterceptor.cs │ │ │ ├── QueryableExtensions.cs │ │ │ └── TransactionLogger.cs │ ├── Testing │ │ └── MccSoft.Testing │ │ │ ├── AspNet │ │ │ └── DummyStringLocalizer.cs │ │ │ ├── AssertEx.cs │ │ │ ├── Database │ │ │ └── RegistrationExtensions.cs │ │ │ ├── HangfireMock.cs │ │ │ ├── Infrastructure │ │ │ └── MotherFactory.cs │ │ │ ├── MccSoft.Testing.csproj │ │ │ ├── RandomEx.cs │ │ │ ├── TaskUtils.cs │ │ │ ├── TestBase.cs │ │ │ └── TypeDescription.cs │ ├── WebApi │ │ ├── MccSoft.WebApi.Tests │ │ │ ├── HangfireStubTestService.cs │ │ │ ├── HangfireStubTests.cs │ │ │ ├── Infrastructure │ │ │ │ ├── Entity1.cs │ │ │ │ └── MyDbContext.cs │ │ │ ├── MccSoft.WebApi.Tests.csproj │ │ │ ├── Patching │ │ │ │ ├── PartialUpdateHelperTests.cs │ │ │ │ └── PatchingTests.cs │ │ │ ├── Usings.cs │ │ │ └── WebApiTestBase.cs │ │ └── MccSoft.WebApi │ │ │ ├── Domain │ │ │ └── Helpers │ │ │ │ ├── AnyOfAttribute.cs │ │ │ │ ├── OptionalEmailAttribute.cs │ │ │ │ ├── RequiredOrMissingAttribute.cs │ │ │ │ └── TrimAttribute.cs │ │ │ ├── Helpers │ │ │ ├── HttpContextExtensions.cs │ │ │ └── StreamExtensions.cs │ │ │ ├── Jobs │ │ │ ├── HangFireJobSettings.cs │ │ │ └── JobBase.cs │ │ │ ├── Localization │ │ │ └── CustomCookieRequestCultureProvider.cs │ │ │ ├── MccSoft.WebApi.csproj │ │ │ ├── Middleware │ │ │ └── ErrorHandlerMiddleware.cs │ │ │ ├── Pagination │ │ │ ├── PagedRequestDto.cs │ │ │ ├── PagedResult.cs │ │ │ ├── PagingExtensions.cs │ │ │ ├── PagingExtensions.partial.cs │ │ │ └── SortOrder.cs │ │ │ ├── Patching │ │ │ ├── Models │ │ │ │ ├── DoNotPatchAttribute.cs │ │ │ │ ├── IPatchRequest.cs │ │ │ │ ├── NotRequiredAttribute.cs │ │ │ │ └── PatchRequest.cs │ │ │ ├── PartialUpdateHelper.cs │ │ │ ├── PatchHelper.cs │ │ │ ├── PatchRequestSystemTextJsonConverter.cs │ │ │ └── RequireValueTypesSchemaProcessor.cs │ │ │ ├── Sentry │ │ │ ├── SentryEventProcessor.cs │ │ │ ├── SentryInitialization.cs │ │ │ └── SentryLoggerExtension.cs │ │ │ ├── Serialization │ │ │ ├── DefaultJsonSerializer.cs │ │ │ ├── FromQueryJson │ │ │ │ ├── FromJsonQueryAttribute.cs │ │ │ │ ├── JsonQueryBinder.cs │ │ │ │ ├── JsonQueryNSwagOperationProcessor.cs │ │ │ │ └── JsonQuerySwaggerGenExtensions.cs │ │ │ ├── ModelBinding │ │ │ │ ├── UtcDateTimeModelBinderProvider.cs │ │ │ │ └── UtcEverywhereExtensions.cs │ │ │ ├── SystemTextJsonSerializerSetup.cs │ │ │ └── SystemTextJsonSerializerSetup.partial.cs │ │ │ ├── SignedUrl │ │ │ ├── README.md │ │ │ ├── SignUrlController.cs │ │ │ ├── SignUrlHelper.cs │ │ │ ├── SignUrlOptions.cs │ │ │ ├── SignUrlRegistrationExtensions.cs │ │ │ ├── SignUrlTestController.cs │ │ │ └── ValidateSignedUrlAttribute.cs │ │ │ ├── Swagger │ │ │ ├── JsonObjectTypeMapper.cs │ │ │ └── ValidationProblemDetailsSchemaProcessor.cs │ │ │ ├── Throttler │ │ │ ├── ThrottleAttribute.cs │ │ │ ├── ThrottleConfigOptions.cs │ │ │ ├── ThrottleGroup.cs │ │ │ └── Throttler.cs │ │ │ └── WebApiRegistrationExtensions.cs │ └── WebHooks │ │ └── MccSoft.WebHooks │ │ ├── Domain │ │ ├── IWebHookSubscription.cs │ │ ├── WebHook.cs │ │ └── WebHookSubscription.cs │ │ ├── Interceptors │ │ ├── IWebHookInterceptors.cs │ │ └── WebHookInterceptors.cs │ │ ├── Manager │ │ ├── IWebHookManager.cs │ │ └── WebHookManager.cs │ │ ├── MccSoft.WebHooks.csproj │ │ ├── Processing │ │ ├── CustomRetryAttribute.cs │ │ ├── WebHookDeliveryJobFailureFilter.cs │ │ └── WebHookProcessor.cs │ │ ├── Publisher │ │ ├── IWebHookEventPublisher.cs │ │ └── WebHookEventPublisher.cs │ │ ├── Readme.md │ │ ├── ResiliencePipelineOptions.cs │ │ └── WebHookRegistration.cs ├── MccSoft.TemplateApp.sln ├── MccSoft.TemplateApp.sln.DotSettings ├── UpdateNugetPackages.ps1 ├── cobertura │ └── .placeholder ├── coverage.bat ├── src │ ├── MccSoft.TemplateApp.App │ │ ├── .gitignore │ │ ├── Areas │ │ │ └── Identity │ │ │ │ ├── IdentityHostingStartup.cs │ │ │ │ └── Pages │ │ │ │ ├── Account │ │ │ │ ├── Login.cshtml │ │ │ │ ├── Login.cshtml.cs │ │ │ │ ├── LoginWith2fa.cshtml │ │ │ │ ├── LoginWith2fa.cshtml.cs │ │ │ │ ├── LoginWithRecoveryCode.cshtml │ │ │ │ ├── LoginWithRecoveryCode.cshtml.cs │ │ │ │ ├── Logout.cshtml │ │ │ │ ├── Logout.cshtml.cs │ │ │ │ └── _ViewImports.cshtml │ │ │ │ ├── Error.cshtml │ │ │ │ ├── Error.cshtml.cs │ │ │ │ ├── _ViewImports.cshtml │ │ │ │ └── _ViewStart.cshtml │ │ ├── Controllers │ │ │ ├── OpenIdAuthorizationController.cs │ │ │ ├── SignUrlController.cs │ │ │ ├── TestDataController.cs │ │ │ └── VersionController.cs │ │ ├── Dictionaries │ │ │ ├── de │ │ │ │ ├── Frontend.json │ │ │ │ ├── IdentityErrors.json │ │ │ │ ├── ModelBindingErrors.json │ │ │ │ └── ValidationErrors.json │ │ │ ├── en │ │ │ │ ├── Frontend.json │ │ │ │ ├── IdentityErrors.json │ │ │ │ ├── ModelBindingErrors.json │ │ │ │ └── ValidationErrors.json │ │ │ └── fr │ │ │ │ ├── IdentityErrors.json │ │ │ │ ├── ModelBindingErrors.json │ │ │ │ └── ValidationErrors.json │ │ ├── Dockerfile │ │ ├── DomainEventHandlers │ │ │ └── LogDomainEventHandler.cs │ │ ├── Features │ │ │ ├── Files │ │ │ │ ├── DbFileExtensions.cs │ │ │ │ ├── Dto │ │ │ │ │ └── FileInfoDto.cs │ │ │ │ ├── FileService.cs │ │ │ │ └── FilesController.cs │ │ │ ├── Products │ │ │ │ ├── Dto │ │ │ │ │ ├── CreateProductDto.cs │ │ │ │ │ ├── PatchProductDto.cs │ │ │ │ │ ├── ProductDto.cs │ │ │ │ │ ├── ProductListItemDto.cs │ │ │ │ │ └── SearchProductDto.cs │ │ │ │ ├── ProductController.cs │ │ │ │ ├── ProductExtensions.cs │ │ │ │ └── ProductService.cs │ │ │ ├── TestApi │ │ │ │ ├── Dto │ │ │ │ │ ├── CreateTestTenantDto.cs │ │ │ │ │ └── CreateTestTenantResponseDto.cs │ │ │ │ ├── TestApiController.cs │ │ │ │ └── TestApiService.cs │ │ │ └── Webhooks │ │ │ │ ├── Dto │ │ │ │ ├── CreateWebHookDto.cs │ │ │ │ ├── UpdateWebHookDto.cs │ │ │ │ └── WebHookSubscriptionDto.cs │ │ │ │ ├── WebHookController.cs │ │ │ │ ├── WebHookEventType.cs │ │ │ │ └── WebHookExtensions.cs │ │ ├── Jobs │ │ │ └── ProductDataLoggerJob.cs │ │ ├── MccSoft.TemplateApp.App.csproj │ │ ├── MccSoft.TemplateApp.App.csproj.DotSettings │ │ ├── Middleware │ │ │ ├── RethrowErrorsFromPersistenceMiddleware.cs │ │ │ ├── RethrowErrorsFromPersistenceMiddlewareExtensions.cs │ │ │ ├── SignOutLockedUserMiddleware.cs │ │ │ └── SignOutLockedUserMiddlewareExtensions.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ ├── AssemblyInfo.cs │ │ │ ├── PublishProfiles │ │ │ │ └── Debug.pubxml │ │ │ └── launchSettings.json │ │ ├── Services │ │ │ └── Authentication │ │ │ │ ├── IdentityLocalizationHelper.cs │ │ │ │ ├── Seed │ │ │ │ ├── DefaultUserOptions.cs │ │ │ │ └── DefaultUserSeeder.cs │ │ │ │ └── UserAccessor.cs │ │ ├── Settings │ │ │ ├── AuditSettings.cs │ │ │ ├── ProductDataLoggerJobSettings.cs │ │ │ └── SwaggerOptions.cs │ │ ├── Setup │ │ │ ├── SetupAspNet.cs │ │ │ ├── SetupAspNet.partial.cs │ │ │ ├── SetupAudit.cs │ │ │ ├── SetupAudit.partial.cs │ │ │ ├── SetupAuth.cs │ │ │ ├── SetupAuth.partial.cs │ │ │ ├── SetupDatabase.cs │ │ │ ├── SetupDatabase.partial.cs │ │ │ ├── SetupHangfire.cs │ │ │ ├── SetupHangfire.partial.cs │ │ │ ├── SetupLocalization.cs │ │ │ ├── SetupLocalization.partial.cs │ │ │ ├── SetupMiniProfiler.cs │ │ │ ├── SetupStaticFiles.cs │ │ │ ├── SetupSwagger.cs │ │ │ ├── SetupSwagger.partial.cs │ │ │ ├── SetupWebhooks.cs │ │ │ └── SetupWebhooks.partial.cs │ │ ├── SetupServices.cs │ │ ├── Utils │ │ │ ├── AuthorizationUtils.cs │ │ │ ├── DateTimeProvider.cs │ │ │ └── Localization │ │ │ │ ├── CustomModelBindingLocalization.cs │ │ │ │ ├── LocalizableIdentityErrorDescriber.cs │ │ │ │ └── MetadataTranslationProvider.cs │ │ ├── Views │ │ │ ├── Emails │ │ │ │ ├── Example │ │ │ │ │ ├── ExampleEmailModel.cs │ │ │ │ │ ├── ExampleEmailModel_content.cshtml │ │ │ │ │ └── ExampleEmailModel_subject.cshtml │ │ │ │ └── Images │ │ │ │ │ └── logo.png │ │ │ ├── Shared │ │ │ │ ├── Constants.cs │ │ │ │ ├── GeneralEmailTemplate.cshtml │ │ │ │ ├── _Layout.cshtml │ │ │ │ └── _ValidationScriptsPartial.cshtml │ │ │ ├── _ViewImports.cshtml │ │ │ └── _ViewStart.cshtml │ │ ├── appsettings.Development.json │ │ ├── appsettings.Docker.json │ │ ├── appsettings.Test.json │ │ ├── appsettings.json │ │ └── wwwroot │ │ │ ├── css │ │ │ └── identity.css │ │ │ └── resources │ │ │ ├── login-background.jpg │ │ │ ├── password-eye-hidden.svg │ │ │ └── password-eye.svg │ ├── MccSoft.TemplateApp.Common │ │ └── MccSoft.TemplateApp.Common.csproj │ ├── MccSoft.TemplateApp.Domain │ │ ├── AssemblyInfo.cs │ │ ├── Audit │ │ │ └── AuditLog.cs │ │ ├── BaseEntity.cs │ │ ├── BaseEntity.partial.cs │ │ ├── BaseTenantEntity.cs │ │ ├── DbFile.cs │ │ ├── ITenantEntity.cs │ │ ├── MccSoft.TemplateApp.Domain.csproj │ │ ├── Product.cs │ │ ├── ProductType.cs │ │ ├── Tenant.cs │ │ ├── User.cs │ │ └── WebHook │ │ │ └── TemplateWebHookSubscription.cs │ ├── MccSoft.TemplateApp.Http │ │ ├── AuthenticationClient.cs │ │ ├── BaseClient.cs │ │ ├── GeneratedClient.cs │ │ ├── GeneratedClientOverrides.cs │ │ ├── IBaseClient.cs │ │ ├── MccSoft.TemplateApp.Http.csproj │ │ ├── SignInException.cs │ │ └── template │ │ │ ├── Client.Class.Annotations.liquid │ │ │ ├── Client.Class.Body.liquid │ │ │ ├── Client.Class.GetOperations.Documentation.liquid │ │ │ ├── Client.Class.GetOperations.Dto.liquid │ │ │ ├── Client.Class.GetOperations.Methods.liquid │ │ │ ├── Client.Interface.Body.liquid │ │ │ └── Client.Interface.GetOperations.Methods.liquid │ └── MccSoft.TemplateApp.Persistence │ │ ├── MccSoft.TemplateApp.Persistence.csproj │ │ ├── Migrations │ │ ├── 20220622143918_Initial.Designer.cs │ │ ├── 20220622143918_Initial.cs │ │ ├── 20220727182715_SetUpOwnedEntities.Designer.cs │ │ ├── 20220727182715_SetUpOwnedEntities.cs │ │ ├── 20221002173903_NullabilityInDomain.Designer.cs │ │ ├── 20221002173903_NullabilityInDomain.cs │ │ ├── 20221113171741_WebHooks_Initial.Designer.cs │ │ ├── 20221113171741_WebHooks_Initial.cs │ │ ├── 20221202151814_Tenant_Initial.Designer.cs │ │ ├── 20221202151814_Tenant_Initial.cs │ │ ├── 20231221012606_OpenIdDict_Update_5.Designer.cs │ │ ├── 20231221012606_OpenIdDict_Update_5.cs │ │ ├── 20240823105139_DbFile_Initial.Designer.cs │ │ ├── 20240823105139_DbFile_Initial.cs │ │ ├── 20250506131423_EF9.Designer.cs │ │ ├── 20250506131423_EF9.cs │ │ ├── 20250514161402_IntroducedWebhooks.Designer.cs │ │ ├── 20250514161402_IntroducedWebhooks.cs │ │ └── TemplateAppDbContextModelSnapshot.cs │ │ ├── PostgresDesignTimeDbContextFactory.cs │ │ └── TemplateAppDbContext.cs └── tests │ ├── MccSoft.TemplateApp.App.Tests │ ├── AppServiceTestBase.cs │ ├── AssemblyInfo.cs │ ├── MccSoft.TemplateApp.App.Tests.csproj │ ├── ProductServiceTests.cs │ ├── SignUrlHelperTests.cs │ ├── TestApiServiceTests.cs │ └── Usings.cs │ ├── MccSoft.TemplateApp.ComponentTests │ ├── BasicApiTests.cs │ ├── ComponentTestBase.cs │ ├── Helpers │ │ ├── AuthHelper.cs │ │ └── IdentityExtensions.cs │ ├── Infrastructure │ │ ├── Auth.cs │ │ ├── ComponentTestFixture.cs │ │ └── ComponentTestFixture.partial.cs │ ├── MccSoft.TemplateApp.ComponentTests.csproj │ ├── ProductControllerTests.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── Usings.cs │ ├── MccSoft.TemplateApp.Domain.Tests │ ├── MccSoft.TemplateApp.Domain.Tests.csproj │ └── Usings.cs │ └── MccSoft.TemplateApp.TestUtils │ ├── ExceptionHelper.cs │ ├── Factories │ └── ProductFactory.cs │ └── MccSoft.TemplateApp.TestUtils.csproj └── yarn.lock /.ci/_settings/base.partial.yml: -------------------------------------------------------------------------------- 1 | # ------------------------------------ 2 | # This is the base file that is used in all pipelines 3 | # Here you can override values defined in 'base.yml'. 4 | # ------------------------------------ 5 | variables: 6 | DUMMY: 1 7 | 8 | # # General 9 | # DISABLE_BUILD: false 10 | # DISABLE_TESTS: false 11 | # # ----------- 12 | 13 | # # Storybook 14 | # ENABLE_STORYBOOK: true 15 | # RUN_STORYBOOK_TESTS_AFTER_BUILD: false 16 | # RUN_STORYBOOK_TESTS_PARALLEL_BUILD: true 17 | # # ----------- 18 | 19 | # # e2e 20 | # ENABLE_E2E_DOCKER: true 21 | # ENABLE_E2E_STAGE: false 22 | # DEPLOY_IF_E2E_FAILS: false 23 | # e2e_test_shards: 2 24 | # sb_test_shards: 2 25 | # # ----------- 26 | 27 | # # Deployment 28 | # DOCKER_TAG: latest 29 | # ENABLE_DEPLOY: true 30 | # TAG_SOURCES_DEV: dev 31 | # TAG_SOURCES_PROD: prod 32 | # DEPLOY_IF_STORYBOOK_FAILS: false 33 | # # ----------- 34 | -------------------------------------------------------------------------------- /.ci/_settings/ci.partial.yml: -------------------------------------------------------------------------------- 1 | # ------------------------------------ 2 | # This file configures parameters for CI builds on the main branch. 3 | # Here you can override values defined in 'ci.yml' or 'base.yml'. 4 | # ------------------------------------ 5 | variables: 6 | DUMMY: 1 7 | -------------------------------------------------------------------------------- /.ci/_settings/ci.yml: -------------------------------------------------------------------------------- 1 | # ------------------------------------ 2 | # This file configures parameters for CI builds on the main branch. 3 | # It overrides parameters defined in ./base.yml 4 | # 5 | # This is the default file that will be overwritten during update. 6 | # Please, DO NOT CHANGE it 7 | # Change 'ci.partial.yml' instead 8 | # ------------------------------------ 9 | variables: 10 | DUMMY: 1 11 | -------------------------------------------------------------------------------- /.ci/_settings/pr.partial.yml: -------------------------------------------------------------------------------- 1 | # ------------------------------------ 2 | # This file configures parameters for Pull Request checks 3 | # Here you can override values defined in 'pr.yml' or 'base.yml' 4 | # ------------------------------------ 5 | variables: 6 | DUMMY: 1 7 | -------------------------------------------------------------------------------- /.ci/_settings/pr.yml: -------------------------------------------------------------------------------- 1 | # ------------------------------------ 2 | # This file configures parameters for Pull Request checks. 3 | # It overrides parameters defined in ./base.yml 4 | # 5 | # This is the default file that will be overwritten during update. 6 | # Please, DO NOT CHANGE it 7 | # Change 'pr.partial.yml' instead 8 | # ------------------------------------ 9 | variables: 10 | # General 11 | DOCKER_TAG: 12 | DISABLE_BUILD: true 13 | # ----------- 14 | 15 | # Deployment 16 | ENABLE_DEPLOY: false 17 | # ----------- 18 | -------------------------------------------------------------------------------- /.ci/_stages/_default.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | TMP: 1 3 | -------------------------------------------------------------------------------- /.ci/_stages/common.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/.ci/_stages/common.env -------------------------------------------------------------------------------- /.ci/_stages/dev.env: -------------------------------------------------------------------------------- 1 | VIRTUAL_HOST=template.mcc-soft.de 2 | General__SiteUrl=https://template.mcc-soft.de 3 | TestApiEnabled=true 4 | Serilog__Loggly__Server= 5 | -------------------------------------------------------------------------------- /.ci/_stages/dev.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | KUBECONFIG: 'k3s-dev.yaml' 3 | -------------------------------------------------------------------------------- /.ci/_stages/prod.env: -------------------------------------------------------------------------------- 1 | VIRTUAL_HOST=template.mcc-soft.de 2 | General__SiteUrl=https://template.mcc-soft.de 3 | TestApiEnabled=false -------------------------------------------------------------------------------- /.ci/_stages/prod.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | KUBECONFIG: 'k3s-prod.yaml' 3 | -------------------------------------------------------------------------------- /.ci/azure-pipelines-pr-tests.yml: -------------------------------------------------------------------------------- 1 | # This is a build for checking Pull Requests. 2 | # It should be triggered by Azure DevOps automatically when Pull Request is created 3 | # (you need to configure triggering in Azure DevOps by yourself). 4 | # Parameters for this build are defined in 5 | # - ./_settings/base.yml 6 | # - ./_settings/base.partial.yml -- change here 7 | # - ./_settings/pr.yml 8 | # - ./_settings/pr.partial.yml --or change here 9 | trigger: none 10 | parameters: 11 | - name: sb_test_shards 12 | type: number 13 | default: 0 14 | - name: e2e_test_shards 15 | type: number 16 | default: 0 17 | extends: 18 | template: azure-pipelines-template.yml 19 | parameters: 20 | name: pr 21 | sb_test_shards: ${{ parameters.sb_test_shards }} 22 | e2e_test_shards: ${{ parameters.e2e_test_shards }} 23 | -------------------------------------------------------------------------------- /.ci/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # This is a CI build that is meant to be run on every commit in your main branch. 2 | # Parameters for this build are defined in 3 | # - ./_settings/base.yml 4 | # - ./_settings/base.partial.yml -- change here 5 | # - ./_settings/ci.yml 6 | # - ./_settings/ci.partial.yml --or change here 7 | parameters: 8 | - name: sb_test_shards 9 | type: number 10 | default: 0 11 | - name: e2e_test_shards 12 | type: number 13 | default: 0 14 | 15 | trigger: 16 | branches: 17 | include: 18 | - master 19 | - main 20 | - feature/ci-* 21 | paths: 22 | exclude: 23 | - docs/* 24 | 25 | extends: 26 | template: azure-pipelines-template.yml 27 | parameters: 28 | name: ci 29 | sb_test_shards: ${{ parameters.sb_test_shards }} 30 | e2e_test_shards: ${{ parameters.e2e_test_shards }} 31 | -------------------------------------------------------------------------------- /.ci/steps/azure-pipelines-docker.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - publish: './publish' 3 | artifact: 'publish' 4 | 5 | - publish: './docker-compose.yaml' 6 | artifact: 'docker-compose' 7 | 8 | - publish: './nginx' 9 | artifact: 'nginx' 10 | 11 | - publish: './scripts/deploy' 12 | artifact: 'scripts' 13 | 14 | - script: | 15 | docker login $(DOCKER_REGISTRY) -u $(DOCKER_USER) -p $(DOCKER_TOKEN) 16 | displayName: 'Authenticate at Container Registry' 17 | 18 | - task: Docker@2 19 | condition: succeeded() 20 | inputs: 21 | repository: $(DOCKER_REGISTRY) 22 | command: 'buildAndPush' 23 | Dockerfile: 'publish/Dockerfile' 24 | buildContext: '$(Build.SourcesDirectory)/publish' 25 | ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/master') }}: 26 | tags: | 27 | $(BUILD_NUMBER) 28 | latest 29 | ${{ else }}: 30 | tags: | 31 | $(BUILD_NUMBER) 32 | displayName: 'build and push docker image' 33 | -------------------------------------------------------------------------------- /.ci/steps/build-import-meta-env.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: Cache@2 3 | inputs: 4 | key: 'import-meta-env|./yarn.lock' 5 | path: $(Build.SourcesDirectory)/import-meta 6 | cacheHitVar: IMPORT_META_ENV_CACHE 7 | displayName: Cache import-meta-env 8 | continueOnError: true 9 | 10 | - script: | 11 | npx pkg ./node_modules/@import-meta-env/cli/bin/import-meta-env.js -t node16-linux -o ./import-meta/import-meta-env 12 | condition: ne(variables.IMPORT_META_ENV_CACHE, 'true') 13 | displayName: 'package import-meta-env/cli' 14 | 15 | - script: | 16 | ./import-meta/import-meta-env --version 17 | cp ./import-meta/import-meta-env ./publish/ 18 | displayName: 'copy import-meta-env' 19 | -------------------------------------------------------------------------------- /.ci/steps/docker-add-tag.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: name 3 | type: string 4 | default: latest 5 | 6 | steps: 7 | - checkout: none 8 | - script: | 9 | docker login $(DOCKER_REGISTRY) -u $(DOCKER_USER) -p $(DOCKER_TOKEN) 10 | displayName: 'Authenticate at Container Registry' 11 | 12 | - script: | 13 | docker buildx imagetools create $(DOCKER_REGISTRY):$(BUILD_NUMBER) --tag $(DOCKER_REGISTRY):${{ parameters.name }} 14 | displayName: 'Add "${{ parameters.name }}" tag' 15 | -------------------------------------------------------------------------------- /.ci/tests/e2e-branch/branch.env: -------------------------------------------------------------------------------- 1 | ConnectionStrings__DefaultConnection=Server=postgres;Database=postgres;Port=5432;User Id=postgres;Password=q7feYuiaaT2C;Include Error Detail=True 2 | Https__ListenOnPortNumber=0 3 | TestApiEnabled=true 4 | -------------------------------------------------------------------------------- /.ci/tests/e2e-branch/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.0' 2 | services: 3 | nginx: 4 | image: nginx:latest 5 | volumes: 6 | - ./nginx.conf:/etc/nginx/nginx.conf:ro 7 | - ./ssl.crt:/etc/nginx/ssl.crt:ro 8 | - ./ssl.key:/etc/nginx/ssl.key:ro 9 | ports: 10 | - 443:443 11 | 12 | postgres: 13 | image: postgres:latest 14 | restart: always 15 | environment: 16 | POSTGRES_PASSWORD: q7feYuiaaT2C 17 | POSTGRES_DB: postgres 18 | expose: 19 | - 5432 20 | 21 | backend: 22 | image: ${IMAGE} 23 | env_file: 24 | - branch.env 25 | volumes: 26 | - ./files:/files:rw 27 | expose: 28 | - 5000 29 | -------------------------------------------------------------------------------- /.ci/tests/e2e-branch/nginx.conf: -------------------------------------------------------------------------------- 1 | events { 2 | worker_connections 4096; ## Default: 1024 3 | } 4 | 5 | http { 6 | server { 7 | listen 443 ssl; 8 | server_name localhost; 9 | ssl_certificate /etc/nginx/ssl.crt; 10 | ssl_certificate_key /etc/nginx/ssl.key; 11 | location / { 12 | proxy_pass http://backend:5000; 13 | proxy_http_version 1.1; 14 | proxy_set_header Upgrade $http_upgrade; 15 | proxy_set_header Connection $http_connection; 16 | proxy_set_header Host $host; 17 | proxy_cache_bypass $http_upgrade; 18 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 19 | proxy_set_header X-Forwarded-Proto $scheme; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | ## husky task runner examples ------------------- 5 | ## Note : for local installation use 'dotnet' prefix. e.g. 'dotnet husky' 6 | 7 | ## run all tasks 8 | #husky run 9 | 10 | ### run all tasks with group: 'group-name' 11 | #husky run --group group-name 12 | 13 | ## run task with name: 'task-name' 14 | #husky run --name task-name 15 | 16 | ## pass hook arguments to task 17 | #husky run --args "$1" "$2" 18 | 19 | ## or put your custom commands ------------------- 20 | #echo 'Husky.Net is awesome!' 21 | 22 | dotnet husky run 23 | -------------------------------------------------------------------------------- /.husky/task-runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "name": "Run csharpier", 5 | "command": "dotnet", 6 | "args": [ 7 | "csharpier", 8 | "${staged}" 9 | ], 10 | "include": [ 11 | "**/*.cs" 12 | ] 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/prettier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/templateApp.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | .idea 4 | publish 5 | webapi 6 | nginx 7 | frontend/src/services/api/api-client.ts 8 | .yarn 9 | scripts/.env_template 10 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 80, 6 | "tabWidth": 2, 7 | "proseWrap": "never", 8 | "endOfLine": "auto" 9 | } 10 | -------------------------------------------------------------------------------- /.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.6.0", 3 | "lastPatch": "" 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "csharpier.csharpier-vscode", 4 | "formulahendry.dotnet-test-explorer", 5 | "formulahendry.dotnet", 6 | "ms-dotnettools.csharp", 7 | "dbaeumer.vscode-eslint", 8 | "orta.vscode-jest", 9 | "ms-playwright.playwright", 10 | "esbenp.prettier-vscode", 11 | "mrmlnc.vscode-scss" 12 | ] 13 | } -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | changesetBaseRefs: 2 | - master 3 | - origin/master 4 | - upstream/master 5 | - main 6 | - origin/main 7 | - upstream/main 8 | - develop 9 | - origin/develop 10 | - upstream/develop 11 | 12 | nodeLinker: node-modules 13 | 14 | yarnPath: .yarn/releases/yarn-3.5.1.cjs 15 | -------------------------------------------------------------------------------- /Main.DotSettings: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/Deploy-Azure-WebApp.md: -------------------------------------------------------------------------------- 1 | ## Deploy to Azure WebApp 2 | 3 | Instructions will follow. 4 | -------------------------------------------------------------------------------- /docs/GitLab-setup-instruction.md: -------------------------------------------------------------------------------- 1 | # How to set up and use GitLab free Container Registry for build pipelines 2 | 3 | 1. Register a user in GitLab (using project email) 4 | - even if you plan to use Groups you have to create a dedicated account for this project for creating AccessTokens. Gitlab doesn't allow to create tokens scoped to a single project (it creates tokens per user). So, you have to create dedicated user for a project. 5 | 6 | 1. Create a personal access token with read/write access to container registry 7 | https://gitlab.com/-/profile/personal_access_tokens 8 | 9 |  10 | 1. Add secret variable `DOCKER_TOKEN` to a pipeline containing created token 11 | 1. Adjust `DOCKER_REGISTRY` and `DOCKER_USER` variables in pipeline. 12 | -------------------------------------------------------------------------------- /docs/details/filter-sorting-azure-work-items.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/docs/details/filter-sorting-azure-work-items.png -------------------------------------------------------------------------------- /docs/images/ci-cd-approval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/docs/images/ci-cd-approval.png -------------------------------------------------------------------------------- /docs/images/ci-cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/docs/images/ci-cd.png -------------------------------------------------------------------------------- /docs/images/gitlab-access-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/docs/images/gitlab-access-token.png -------------------------------------------------------------------------------- /docs/images/mcc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/docs/images/mcc.png -------------------------------------------------------------------------------- /docs/images/sentry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/docs/images/sentry.png -------------------------------------------------------------------------------- /docs/images/statoscope-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/docs/images/statoscope-1.png -------------------------------------------------------------------------------- /docs/images/statoscope-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/docs/images/statoscope-2.png -------------------------------------------------------------------------------- /docs/images/uitests/green-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/docs/images/uitests/green-icon.png -------------------------------------------------------------------------------- /docs/images/uitests/main-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/docs/images/uitests/main-example.png -------------------------------------------------------------------------------- /docs/images/uitests/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/docs/images/uitests/refresh.png -------------------------------------------------------------------------------- /docs/images/uitests/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/docs/images/uitests/stop.png -------------------------------------------------------------------------------- /docs/images/uitests/trace-viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/docs/images/uitests/trace-viewer.png -------------------------------------------------------------------------------- /e2e/.env: -------------------------------------------------------------------------------- 1 | # You could copy this file to .env.local and override values there. 2 | # Just don't commit .env.local (btw, it's in gitignore by default) 3 | #PROXY_HOST=localhost 4 | #PROXY_PORT=8888 5 | BASE_URL=https://template.mcc-soft.de 6 | STORYBOOK_URL=http://localhost:6006 -------------------------------------------------------------------------------- /e2e/.env.local_sample: -------------------------------------------------------------------------------- 1 | # You could copy this file to .env.local and override values there. 2 | # Just don't commit .env.local (btw, it's in gitignore by default) 3 | #PROXY_HOST=127.0.0.1 4 | #PROXY_PORT=8888 5 | #BASE_URL=https://localhost:5003 6 | #STORYBOOK_URL=http://localhost:6006 -------------------------------------------------------------------------------- /e2e/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /test-results/ 3 | /playwright-report/ 4 | /playwright/.cache/ 5 | -------------------------------------------------------------------------------- /e2e/infrastructure/axios-retry-interceptor.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; 2 | 3 | const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); 4 | const maxRetry = 20; 5 | export function addRetryInterceptor(axiosInstance: AxiosInstance) { 6 | axiosInstance.interceptors.response.use(undefined, async (error) => { 7 | if (error.config && error.response && error.response.status === 504) { 8 | await sleep(1000); 9 | const retriesLeft = error.config.maxRetry ?? maxRetry; 10 | if (retriesLeft) { 11 | error.config.maxRetry = retriesLeft - 1; 12 | return await axios.request(error.config); 13 | } 14 | } 15 | 16 | return await Promise.reject(error); 17 | }); 18 | } 19 | -------------------------------------------------------------------------------- /e2e/page-objects/LoginPageObject.ts: -------------------------------------------------------------------------------- 1 | import { Page } from '@playwright/test'; 2 | import { PageObjectBase } from 'infrastructure/LocatorWithRootBase'; 3 | 4 | export class LoginPageObject extends PageObjectBase { 5 | constructor(page: Page) { 6 | super(page.locator('.login-container')); 7 | } 8 | 9 | loginField = () => this.findField('Login'); 10 | passwordField = () => this.findField('Password'); 11 | loginButton = () => 12 | this.region.getByRole('button').filter({ hasText: 'Login' }); 13 | 14 | async fill(data: { login: string; password: string }) { 15 | await this.loginField().fillTextField(data.login); 16 | await this.passwordField().fillTextField(data.password); 17 | } 18 | 19 | async logIn(login: string, password: string) { 20 | await this.fill({ login, password }); 21 | await this.loginButton().click(); 22 | await this.waitForLoadings(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /e2e/page-objects/MainPageObject.ts: -------------------------------------------------------------------------------- 1 | import { PageObjectBase } from 'infrastructure/LocatorWithRootBase'; 2 | import { LocatorOrPageObject } from 'infrastructure/types'; 3 | import { ProductListPageObject } from './products/ProductListPageObject'; 4 | 5 | export class MainPageObject extends PageObjectBase { 6 | constructor(locator: LocatorOrPageObject) { 7 | super(locator.locator('[data-test-id="main-page-container"]')); 8 | } 9 | 10 | productList = () => new ProductListPageObject(this.page); 11 | 12 | logOutButton = () => 13 | this.region.getByRole('button').filter({ hasText: 'Log Out' }); 14 | 15 | async logOut() { 16 | await this.logOutButton().click(); 17 | await this.waitForLoadings(); 18 | await this.ensureHidden(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /e2e/src/api/index.ts: -------------------------------------------------------------------------------- 1 | import * as QueryFactory from './query-client'; 2 | 3 | export { QueryFactory }; 4 | -------------------------------------------------------------------------------- /e2e/storybook/storybook-fixture.ts: -------------------------------------------------------------------------------- 1 | import { Page, test, expect } from '@playwright/test'; 2 | 3 | const baseURL = process.env.STORYBOOK_URL ?? ''; 4 | 5 | export const storybookTest = test.extend({}); 6 | storybookTest.use({ baseURL }); 7 | 8 | export async function openStory(page: Page, storyName: string) { 9 | const iFrameUrl = '/iframe.html?id='; 10 | await page.goto(baseURL + iFrameUrl + storyName); 11 | 12 | await expect 13 | .poll(() => page.locator('#storybook-root').innerHTML()) 14 | .not.toBe(''); 15 | } 16 | -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Alert-default-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Alert-default-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Alert-default-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Alert-default-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Alert-two-modals-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Alert-two-modals-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Alert-two-modals-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Alert-two-modals-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Alert-two-modals-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Alert-two-modals-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Alert-two-modals-2-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Alert-two-modals-2-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Alert-with-all-params-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Alert-with-all-params-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Alert-with-all-params-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Alert-with-all-params-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Alert-with-all-params-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Alert-with-all-params-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Alert-with-all-params-2-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Alert-with-all-params-2-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-danger-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-danger-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-danger-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-danger-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-danger-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-danger-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-danger-2-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-danger-2-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-default-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-default-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-default-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-default-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-default-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-default-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-default-2-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-default-2-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-primary-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-primary-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-primary-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-primary-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-primary-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-primary-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-primary-2-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-primary-2-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-secondary-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-secondary-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-secondary-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-secondary-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-secondary-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-secondary-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Button-secondary-2-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Button-secondary-2-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/ComboBox-default-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/ComboBox-default-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/ComboBox-with-search-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/ComboBox-with-search-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-default-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-default-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-default-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-default-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-default-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-default-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-default-2-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-default-2-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-with-all-params-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-with-all-params-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-with-all-params-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-with-all-params-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-with-all-params-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-with-all-params-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-with-all-params-2-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Confirm-with-all-params-2-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-default-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-default-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-default-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-default-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-default-1-http-host-docker-internal-6006-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-default-1-http-host-docker-internal-6006-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-default-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-default-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-default-2-http-host-docker-internal-6006-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-default-2-http-host-docker-internal-6006-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-with-time-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-with-time-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-with-time-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DatePicker-with-time-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-any-selected-value-automatically-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-any-selected-value-automatically-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-any-selected-value-automatically-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-any-selected-value-automatically-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-any-selected-value-automatically-in-500-ms-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-any-selected-value-automatically-in-500-ms-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-any-selected-value-automatically-in-500-ms-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-any-selected-value-automatically-in-500-ms-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-if-asd-is-selected-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-if-asd-is-selected-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-if-asd-is-selected-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-if-asd-is-selected-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-if-asd-is-selected-accept-the-second-time-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-if-asd-is-selected-accept-the-second-time-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-if-asd-is-selected-accept-the-second-time-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-cancel-if-asd-is-selected-accept-the-second-time-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-default-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-default-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-default-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-default-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-default-1-http-host-docker-internal-6006-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-default-1-http-host-docker-internal-6006-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-default-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-default-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-default-2-http-host-docker-internal-6006-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/DropDown-default-2-http-host-docker-internal-6006-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Field-default-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Field-default-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Field-default-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Field-default-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Field-default-1-http-host-docker-internal-6006-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Field-default-1-http-host-docker-internal-6006-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Field-default-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Field-default-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Field-default-2-http-host-docker-internal-6006-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Field-default-2-http-host-docker-internal-6006-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Field-with-hint-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Field-with-hint-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Field-with-hint-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Field-with-hint-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Field-with-link-props-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Field-with-link-props-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Field-with-link-props-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Field-with-link-props-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Input-default-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Input-default-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Input-default-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Input-default-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Input-default-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Input-default-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Input-helper-text-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Input-helper-text-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Input-helper-text-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Input-helper-text-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Input-password-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Input-password-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Input-password-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Input-password-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Input-with-error-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Input-with-error-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Input-with-error-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Input-with-error-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-default-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-default-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-default-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-default-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-default-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-default-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-default-2-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-default-2-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-with-all-params-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-with-all-params-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-with-all-params-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-with-all-params-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-with-all-params-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-with-all-params-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-with-all-params-2-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/Prompt-with-all-params-2-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-default-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-default-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-default-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-default-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-default-2-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-default-2-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-interval-10-minutes-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-interval-10-minutes-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-interval-10-minutes-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-interval-10-minutes-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-min-time-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-min-time-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-min-time-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-min-time-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-time-set-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-time-set-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-time-set-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-time-set-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-time-set-to-non-present-value-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-time-set-to-non-present-value-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-time-set-to-non-present-value-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/e2e/storybook/storybook-smart.test.ts-snapshots/TimePicker-time-set-to-non-present-value-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | 4 | "compilerOptions": { 5 | "baseUrl": "./" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /frontend/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_SENTRY_DSN= 2 | REACT_APP_MINI_PROFILER_ENABLED=false 3 | REACT_APP_VERSION= 4 | TST=asd -------------------------------------------------------------------------------- /frontend/.env.development: -------------------------------------------------------------------------------- 1 | REACT_APP_MINI_PROFILER_ENABLED=true 2 | REACT_APP_VERSION=0.0.1 -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | node_modules 4 | 5 | package.json 6 | **/package.json 7 | **/build/** 8 | .idea 9 | .vscode 10 | vite.config.ts 11 | scripts/**/* 12 | -------------------------------------------------------------------------------- /frontend/.idea/prettier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/react-vite'; 2 | 3 | const config: StorybookConfig = { 4 | "stories": [ 5 | "../src/**/*.mdx", 6 | "../src/**/*.stories.@(js|jsx|ts|tsx)" 7 | ], 8 | 9 | "addons": [ 10 | "@storybook/addon-links", 11 | "@storybook/addon-essentials", 12 | "@storybook/addon-interactions" 13 | ], 14 | "framework": { 15 | "name": "@storybook/react-vite", 16 | "options": {} 17 | }, 18 | features: { 19 | storyStoreV7: true, 20 | previewCsfV3: true, 21 | }, 22 | "docs": { 23 | "autodocs": "tag" 24 | } 25 | }; 26 | export default config; -------------------------------------------------------------------------------- /frontend/.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import { wrapInModal, wrapInRouter } from './wrapper'; 2 | 3 | export const defaultExclude = [ 4 | 'className', 5 | 'onClick', 6 | 'style', 7 | 'testId', 8 | 'id', 9 | 'error', 10 | ]; 11 | 12 | export const parameters = { 13 | actions: { argTypesRegex: '^on[A-Z].*' }, 14 | controls: { 15 | matchers: { 16 | color: /(background|color)$/i, 17 | date: /Date$/, 18 | }, 19 | exclude: defaultExclude, 20 | }, 21 | }; 22 | 23 | export const decorators = [wrapInRouter, wrapInModal]; 24 | -------------------------------------------------------------------------------- /frontend/.storybook/wrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter } from 'react-router-dom'; 3 | import { ModalProvider } from '../src/components/uikit/modal/useModal'; 4 | export const wrapInRouter = (Story: any) => { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | }; 11 | export const wrapInModal = (Story: any) => { 12 | return ( 13 | 14 | 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/frontend/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/frontend/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/frontend/public/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/public/dictionaries/translation.de.json: -------------------------------------------------------------------------------- 1 | { 2 | "Common_App": "TemplateApp Admin", 3 | "Page": { 4 | "Login": { 5 | "login_field": "Login", 6 | "password_field": "Password", 7 | "login_button": "Login" 8 | }, 9 | "Products": { 10 | "Create": { 11 | "create_button": "Hinzufugen", 12 | "title": "Titel", 13 | "product_type": "Typ", 14 | "lastStockUpdatedAt": "Last Stock Update" 15 | }, 16 | "list": { 17 | "column_title": "Titel" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/frontend/public/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/frontend/public/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Template App", 3 | "name": "Template App web application", 4 | "icons": [ 5 | { 6 | "src": "android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "android-chrome-256x256.png", 12 | "sizes": "256x256", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": ".", 17 | "display": "standalone", 18 | "theme_color": "#000000", 19 | "background_color": "#ffffff" 20 | } 21 | -------------------------------------------------------------------------------- /frontend/public/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/frontend/public/mstile-144x144.png -------------------------------------------------------------------------------- /frontend/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/frontend/public/mstile-150x150.png -------------------------------------------------------------------------------- /frontend/public/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/frontend/public/mstile-310x150.png -------------------------------------------------------------------------------- /frontend/public/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/frontend/public/mstile-310x310.png -------------------------------------------------------------------------------- /frontend/public/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/frontend/public/mstile-70x70.png -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/application/constants/links.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createRoute, 3 | RequiredNumberParam, 4 | StringParam, 5 | } from 'react-router-url-params'; 6 | 7 | export const Links = { 8 | Unauthorized: { 9 | Login: createRoute('/login'), 10 | }, 11 | Authorized: { 12 | Dashboard: createRoute('/'), 13 | Products: createRoute('/products'), 14 | ProductDetails: createRoute('/products/:id', { 15 | id: RequiredNumberParam, 16 | }), 17 | CreateProduct: createRoute('/products/create'), 18 | EditProduct: createRoute('/products/:id/edit', { 19 | id: RequiredNumberParam, 20 | }), 21 | UiKit: createRoute('/uikit'), 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /frontend/src/application/localization/LanguageProvider.tsx: -------------------------------------------------------------------------------- 1 | import Logger from 'js-logger'; 2 | import React, { useEffect, useState } from 'react'; 3 | import { initializeLocalization } from './localization'; 4 | 5 | export const LanguageProvider: React.FC = (props) => { 6 | const [isLoading, setLoading] = useState(true); 7 | 8 | useEffect(() => { 9 | const bootstrapAsync = async () => { 10 | await initializeLocalization(); 11 | setLoading(false); 12 | }; 13 | 14 | bootstrapAsync().catch((e) => Logger.error(e)); 15 | }, []); 16 | 17 | return isLoading ? null : (props.children as any); 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/application/localization/i18next.d.ts: -------------------------------------------------------------------------------- 1 | import 'i18next'; 2 | import { defaultNS } from './locales'; 3 | import ns1 from '../../../public/dictionaries/translation.en.json'; 4 | 5 | declare module 'i18next' { 6 | // and extend them! 7 | interface CustomTypeOptions { 8 | // custom namespace type if you changed it 9 | defaultNS: typeof defaultNS; 10 | // custom resources type 11 | resources: { 12 | translation: typeof ns1; 13 | }; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/application/localization/locales.ts: -------------------------------------------------------------------------------- 1 | import { de, enGB } from 'date-fns/locale'; 2 | 3 | export enum Language { 4 | 'en' = 'en', 5 | 'de' = 'de', 6 | } 7 | export type Namespace = 'translation'; 8 | 9 | export const dateLocales: { 10 | [key in Language | 'default']: typeof enGB; 11 | } = { 12 | en: enGB, 13 | de: de, 14 | default: enGB, 15 | }; 16 | 17 | export const fallbackLng: Language = Language.en; 18 | export const defaultNS: Namespace = 'translation'; 19 | 20 | export const languages: Language[] = Object.values(Language); 21 | export const namespaces = [defaultNS]; 22 | -------------------------------------------------------------------------------- /frontend/src/application/localization/useScopedTranslation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FallbackNs, 3 | useTranslation, 4 | UseTranslationOptions, 5 | UseTranslationResponse, 6 | } from 'react-i18next'; 7 | import { FlatNamespace, KeyPrefix } from 'i18next'; 8 | import { $Tuple } from 'react-i18next/helpers'; 9 | /* 10 | Allows to specify the initial path of all translations returned by the function. 11 | E.g. useScopedTranslation('Page.Login'). All translations will start with 'Page.Login.*' 12 | */ 13 | export function useScopedTranslation< 14 | Ns extends FlatNamespace | $Tuple | undefined = undefined, 15 | TKPrefix extends KeyPrefix> = undefined, 16 | >( 17 | path: TKPrefix, 18 | ns?: Ns, 19 | options?: UseTranslationOptions, 20 | ): UseTranslationResponse, TKPrefix> { 21 | const i18n = useTranslation(ns, { ...options, keyPrefix: path }); 22 | return i18n; 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/application/redux-store/index.ts: -------------------------------------------------------------------------------- 1 | import { store, persistor } from './root-store'; 2 | export const RootStore = { 3 | store, 4 | persistor, 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/application/redux-store/persistence/migrations.ts: -------------------------------------------------------------------------------- 1 | import { MigrationManifest, PersistedState } from 'redux-persist/es/types'; 2 | 3 | export const migrations: MigrationManifest = { 4 | // @ts-ignore 5 | '0': (state: PersistedState) => { 6 | return { 7 | ...state, 8 | auth: undefined, 9 | }; 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /frontend/src/application/redux-store/persistence/persist-config.ts: -------------------------------------------------------------------------------- 1 | import { PersistConfig } from 'redux-persist/es/types'; 2 | import { createMigrate } from 'redux-persist'; 3 | import { migrations } from './migrations'; 4 | import storage from 'redux-persist/lib/storage'; 5 | import { GlobalState } from '../types'; 6 | 7 | export const persistConfig: PersistConfig = { 8 | key: 'root', 9 | version: 0, 10 | storage: storage, 11 | whitelist: ['theme'] as (keyof GlobalState)[], 12 | migrate: createMigrate(migrations, { debug: false }), 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/src/application/redux-store/root-reducer.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { GlobalState } from './types'; 3 | import { themeSlice } from 'application/redux-store/theme/theme-slice'; 4 | import { CombinedState, createAction, Reducer } from '@reduxjs/toolkit'; 5 | 6 | const combinedReducer: Reducer> = combineReducers({ 7 | theme: themeSlice.reducer, 8 | }); 9 | 10 | export const logoutAction = createAction('logout'); 11 | 12 | export const rootReducer: Reducer> = ( 13 | state, 14 | action, 15 | ) => { 16 | if (action.type === logoutAction.type) { 17 | state = undefined; 18 | } 19 | 20 | return combinedReducer(state, action); 21 | }; 22 | -------------------------------------------------------------------------------- /frontend/src/application/redux-store/theme/theme-selectors.ts: -------------------------------------------------------------------------------- 1 | import { GlobalState } from '../types'; 2 | 3 | export const getThemeSelector = (state: GlobalState) => { 4 | return state.theme; 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/application/redux-store/theme/theme-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | import { ThemeState } from './theme-types'; 3 | 4 | export const themeSlice = createSlice({ 5 | name: 'theme', 6 | initialState: { 7 | theme: 'light', 8 | } as ThemeState, 9 | reducers: { 10 | setThemeAction: ( 11 | state, 12 | action: PayloadAction<{ theme: 'light' | 'dark' }>, 13 | ) => { 14 | return { 15 | theme: action.payload.theme, 16 | }; 17 | }, 18 | }, 19 | }); 20 | 21 | export const ThemeActions = themeSlice.actions; 22 | -------------------------------------------------------------------------------- /frontend/src/application/redux-store/theme/theme-types.ts: -------------------------------------------------------------------------------- 1 | export type ThemeState = { 2 | theme: 'dark' | 'light'; 3 | }; 4 | -------------------------------------------------------------------------------- /frontend/src/application/redux-store/types.ts: -------------------------------------------------------------------------------- 1 | import { ThemeState } from './theme/theme-types'; 2 | 3 | export type GlobalState = { 4 | theme: ThemeState; 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/close-button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/dots.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/i.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/components/uikit/CheckBox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import styles from './CheckBox.module.scss'; 4 | 5 | type Props = React.InputHTMLAttributes & { 6 | title?: string; 7 | testId?: string; 8 | }; 9 | 10 | export const CheckBox = React.forwardRef( 11 | function CheckBox(props, ref) { 12 | const { title, testId, ...rest } = props; 13 | const id = 14 | props.id ?? props.name != undefined 15 | ? `uni-check-box-${props.name}` 16 | : undefined; 17 | return ( 18 | 19 | 27 | {title} 28 | 29 | ); 30 | }, 31 | ); 32 | -------------------------------------------------------------------------------- /frontend/src/components/uikit/FormError.tsx: -------------------------------------------------------------------------------- 1 | import React, { PropsWithChildren } from 'react'; 2 | import styles from './Field.module.scss'; 3 | 4 | export const FormError: React.FC> = (props) => { 5 | if (!props.children) return null; 6 | return {props.children}; 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/components/uikit/IconButton.module.scss: -------------------------------------------------------------------------------- 1 | .icon-button { 2 | cursor: pointer; 3 | opacity: 0.7; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | } 8 | 9 | .icon-button:hover { 10 | opacity: 1; 11 | } 12 | 13 | .icon-button[data-disabled='true'] { 14 | opacity: 0.5; 15 | pointer-events: none !important; 16 | } 17 | 18 | .image { 19 | max-width: 16px; 20 | max-height: 16px; 21 | width: 100%; 22 | height: 100%; 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/components/uikit/RadioButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import styles from './RadioButton.module.scss'; 3 | 4 | type Props = React.InputHTMLAttributes & { 5 | title?: string; 6 | value?: string | number; 7 | }; 8 | 9 | export const RadioButton = React.forwardRef( 10 | function RadioButton(props, ref) { 11 | const { title, ...rest } = props; 12 | const id = 13 | props.id ?? props.name != undefined 14 | ? `uni-radio-${props.name}-${props.value}` 15 | : undefined; 16 | return ( 17 | 18 | 25 | 26 | {title} 27 | 28 | 29 | ); 30 | }, 31 | ); 32 | -------------------------------------------------------------------------------- /frontend/src/components/uikit/RadioButtonGroup.module.scss: -------------------------------------------------------------------------------- 1 | @import 'src/styles/variables'; 2 | @import 'src/styles/base'; 3 | 4 | .root-container { 5 | display: flex; 6 | flex-direction: column; 7 | align-items: stretch; 8 | } 9 | 10 | .radio-button { 11 | padding: 3px 0; 12 | } 13 | 14 | .field-container { 15 | display: flex; 16 | flex-direction: row; 17 | align-items: center; 18 | } 19 | 20 | .errorText { 21 | @extend .inputErrorText; 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/components/uikit/inputs/TextArea.module.scss: -------------------------------------------------------------------------------- 1 | @import 'src/styles/variables'; 2 | @import 'src/styles/base'; 3 | 4 | .area { 5 | @extend .base-input; 6 | height: 121px; 7 | resize: none; 8 | } 9 | 10 | .helperText { 11 | @extend .inputErrorText; 12 | color: rgba($normal, 0.5); 13 | } 14 | 15 | .helperText[data-error='true'] { 16 | color: rgba($danger, 0.5); 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/components/uikit/inputs/date-time/TimePicker.module.scss: -------------------------------------------------------------------------------- 1 | @import 'src/styles/variables'; 2 | 3 | .container { 4 | padding: 0 !important; 5 | height: 40px; 6 | width: 120px; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/components/uikit/menu/AppMenu.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/variables'; 2 | .menuItem { 3 | padding: 8px 12px !important; 4 | line-height: 16px !important; 5 | font-size: 14px !important; 6 | background: white !important; 7 | } 8 | 9 | .menuItem:hover { 10 | background: $primary-background !important; 11 | } 12 | 13 | .list { 14 | border-radius: 2px; 15 | background: white !important; 16 | padding-top: 0px !important; 17 | padding-bottom: 0px !important; 18 | } 19 | 20 | .paper { 21 | box-shadow: 0px 4px 15px rgba(0, 0, 0, 0.08) !important; 22 | } 23 | 24 | .deleteItem { 25 | @extend .menuItem; 26 | color: $danger !important; 27 | span:hover { 28 | background-color: $danger-background !important; 29 | } 30 | } 31 | .deleteItem:hover { 32 | background-color: $danger-background !important; 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/components/uikit/menu/AppTooltip.module.scss: -------------------------------------------------------------------------------- 1 | @import 'src/styles/variables'; 2 | .popper { 3 | padding: 5px; 4 | margin-top: 5px; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/components/uikit/menu/DotMenu.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/variables'; 2 | .dotMenu { 3 | cursor: pointer; 4 | top: 18px; 5 | right: 16px; 6 | } 7 | 8 | .dotMenuImage:hover path { 9 | fill: $primary-light; 10 | } 11 | 12 | .dotMenuImage:active path { 13 | fill: $primary; 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/components/uikit/modal/Modal.module.scss: -------------------------------------------------------------------------------- 1 | @import 'src/styles/variables'; 2 | 3 | .header { 4 | padding-left: 24px; 5 | padding-right: 24px; 6 | height: 78px; 7 | display: flex; 8 | justify-content: space-between; 9 | align-items: center; 10 | border-bottom: 1px $separator solid; 11 | } 12 | 13 | .title { 14 | font-weight: normal; 15 | font-family: $font-family; 16 | font-size: 24px; 17 | line-height: 28px; 18 | } 19 | 20 | .body { 21 | display: flex; 22 | flex-direction: column; 23 | padding: 16px 24px 16px 24px; 24 | line-height: 17px; 25 | justify-content: flex-start; 26 | gap: 16px; 27 | } 28 | 29 | .closeButton { 30 | cursor: pointer; 31 | } 32 | 33 | .closeButton:hover path { 34 | fill: $close-button-hover; 35 | } 36 | 37 | .closeButton:active path { 38 | fill: $close-button-active; 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/components/uikit/table/AppTable.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/variables'; 2 | .sortable-column-header { 3 | cursor: pointer; 4 | user-select: none; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/components/uikit/type-utils.ts: -------------------------------------------------------------------------------- 1 | export type PickFieldsWithType = Pick< 2 | Base, 3 | { 4 | [Key in keyof Base]: Base[Key] extends Type ? Key : never; 5 | }[keyof Base] 6 | >; 7 | 8 | export function createId() { 9 | return Math.random().toString(36).substr(2, 9); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/helpers/assert-never.ts: -------------------------------------------------------------------------------- 1 | export class NeverError extends Error { 2 | // Typescript will give a compile-time error if we try to call this constructor 3 | constructor(value: never) { 4 | super(`Unreachable statement: ${value}`); 5 | } 6 | } 7 | 8 | export function assertNeverDontThrow(x: never): never { 9 | return x; 10 | } 11 | export function assertNever(x: never): never { 12 | throw new NeverError(x); 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/helpers/auth/auth-data.ts: -------------------------------------------------------------------------------- 1 | import { jwtDecode } from 'jwt-decode'; 2 | 3 | export type FetchLoginResponse = { 4 | access_token: string; 5 | refresh_token: string; 6 | expires_in: number; 7 | }; 8 | 9 | export type AuthData = { 10 | access_token: string; 11 | refresh_token: string; 12 | claims: UserClaims; 13 | }; 14 | 15 | export type UserClaims = { 16 | id: string; 17 | name: string; 18 | }; 19 | 20 | export function decodeClaimsFromToken(token: string): UserClaims { 21 | const claims = jwtDecode(token); 22 | return claims; 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/helpers/auth/openid/openid-settings.ts: -------------------------------------------------------------------------------- 1 | export const signInCallbackPath = '/auth/openid-callback'; 2 | export const signOutCallbackPath = '/auth/signout-callback'; 3 | export const scopes = 'offline_access'; 4 | export const backendUri = `${window.location.protocol}//${ 5 | window.location.hostname 6 | }${window.location.port ? `:${window.location.port}` : ''}`; 7 | export const signInRedirectUri = `${backendUri}${signInCallbackPath}`; 8 | export const signInRedirectUriPopup = `${backendUri}${signInCallbackPath}?popup=1`; 9 | export const signOutRedirectUri = `${backendUri}${signOutCallbackPath}`; 10 | export const signOutRedirectUriPopup = `${backendUri}${signOutCallbackPath}?popup=1`; 11 | export const clientId = 'web_client'; 12 | -------------------------------------------------------------------------------- /frontend/src/helpers/date-helpers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | addSeconds, 3 | differenceInHours, 4 | format, 5 | setDefaultOptions, 6 | startOfToday, 7 | } from 'date-fns'; 8 | 9 | export function convertDateToString( 10 | date: Date | null | undefined, 11 | withTime?: boolean, 12 | ) { 13 | if (date === undefined) return undefined; 14 | 15 | if (!date) return ''; 16 | 17 | return format(date, withTime === true ? "yyyy-MM-dd'T'HH:mm" : 'yyyy-MM-dd'); 18 | } 19 | 20 | export const convertToShortDate = (date: Date) => { 21 | return format(date, 'do MMM'); 22 | }; 23 | 24 | export function formatDurationInSecondsAsHHMM( 25 | durationInSeconds: number, 26 | ): string { 27 | const startDate = startOfToday(); 28 | const endDate = addSeconds(startOfToday(), durationInSeconds); 29 | return `${differenceInHours(startDate, endDate)}:${format(endDate, 'mm')}`; 30 | } 31 | 32 | export function setLocale(locale: Locale) { 33 | setDefaultOptions({ locale: locale }); 34 | } 35 | -------------------------------------------------------------------------------- /frontend/src/helpers/empty-array.ts: -------------------------------------------------------------------------------- 1 | const emptyArraySingleton: unknown[] = []; 2 | export function emptyArray(): T[] { 3 | return emptyArraySingleton as T[]; 4 | } 5 | -------------------------------------------------------------------------------- /frontend/src/helpers/interceptors/inject-language-interceptor.ts: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { InternalAxiosRequestConfig } from 'axios'; 3 | 4 | export async function injectLanguageInterceptor( 5 | config: InternalAxiosRequestConfig, 6 | ) { 7 | const language = i18n.language; 8 | config.headers['Accept-Language'] = language; 9 | return config; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/helpers/interceptors/inject-session-interceptor.ts: -------------------------------------------------------------------------------- 1 | import { buildVersion } from 'application/constants/env-variables'; 2 | import { InternalAxiosRequestConfig } from 'axios'; 3 | import { createId } from 'components/uikit/type-utils'; 4 | 5 | // it's constant while the app is running, and different after app restarts 6 | export const sessionId = `${createId()}-${createId()}-${createId()}`; 7 | 8 | /** 9 | * Intercepts all HTTP calls via axios and adds a session-id header. 10 | */ 11 | export const sessionAxiosInterceptor = (config: InternalAxiosRequestConfig) => { 12 | config.headers['ClientSession'] = sessionId; 13 | config.headers['ClientVersion'] = buildVersion; 14 | config.headers['ClientPlatform'] = 'web'; 15 | return config; 16 | }; 17 | -------------------------------------------------------------------------------- /frontend/src/helpers/query-params.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NumberParam, 3 | NumericArrayParam, 4 | QueryParamConfig, 5 | } from 'react-router-url-params'; 6 | 7 | export function createEnumArrayParam(): QueryParamConfig< 8 | T[] | null | undefined, 9 | T[] | null | undefined 10 | > { 11 | return NumericArrayParam as any; 12 | } 13 | export function createEnumParam(): QueryParamConfig< 14 | T | null | undefined, 15 | T | null | undefined 16 | > { 17 | return NumberParam as any; 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/index.scss: -------------------------------------------------------------------------------- 1 | //noinspection CssInvalidAtRule 2 | @import-normalize; 3 | 4 | body { 5 | margin: 0; 6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 7 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 8 | 'Montserrat', sans-serif; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | background-color: #e5e5e5; 12 | } 13 | html { 14 | line-height: unset; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/pages/ReactRouterErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import { useRouteError } from 'react-router'; 2 | 3 | /* 4 | * This is an ErrorBoundary that should be provided to `createBrowserRouter`. 5 | * It just rethrows an error so that it will be cought by `QuerySuspenseErrorWrapper`. 6 | * We could include all `QuerySuspenseErrorWrapper` error-handling logic into `ReactRouterErrorBoundary`, 7 | * but we'd still need to wrap all routes in and . 8 | */ 9 | export const ReactRouterErrorBoundary = () => { 10 | const error = useRouteError() as Error; 11 | throw error; 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/src/pages/authorized/RootPage.module.scss: -------------------------------------------------------------------------------- 1 | @import 'src/styles/variables'; 2 | @import 'src/styles/base'; 3 | .bottom-navigation { 4 | display: flex; 5 | justify-content: space-between; 6 | } 7 | .container { 8 | flex: 1; 9 | display: flex; 10 | flex-direction: column; 11 | } 12 | .content { 13 | flex: 1; 14 | } 15 | .language-switcher { 16 | width: 80px; 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/pages/authorized/products/ProductListPage.module.scss: -------------------------------------------------------------------------------- 1 | @import 'src/styles/variables'; 2 | @import 'src/styles/base'; 3 | .navigation { 4 | display: flex; 5 | justify-content: space-between; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/pages/authorized/products/details/ProductDetailsPage.tsx: -------------------------------------------------------------------------------- 1 | import { Links } from 'application/constants/links'; 2 | import { AppLink } from 'components/uikit/buttons/AppLink'; 3 | import { Loading } from 'components/uikit/suspense/Loading'; 4 | import React from 'react'; 5 | import { QueryFactory } from 'services/api'; 6 | 7 | export const ProductDetailsPage: React.FC = (props) => { 8 | const { id: productId } = Links.Authorized.ProductDetails.useParams(); 9 | const productQuery = QueryFactory.ProductQuery.useGetQuery(productId); 10 | 11 | return ( 12 | <> 13 | Back 14 | 15 | Product: {productQuery.data?.title} 16 | 17 | Edit 18 | 19 | 20 | > 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/src/pages/authorized/uikit/UiKitPage.module.scss: -------------------------------------------------------------------------------- 1 | @import 'src/styles/variables'; 2 | @import 'src/styles/base'; 3 | .main { 4 | max-width: 300px; 5 | padding: 20px; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/pages/unauthorized/LoginErrorPage.module.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/base'; 2 | .container { 3 | flex: 1; 4 | display: flex; 5 | align-items: center; 6 | align-self: center; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/pages/unauthorized/LoginErrorPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './LoginErrorPage.module.scss'; 3 | 4 | export const LoginErrorPage: React.FC<{ 5 | error: string; 6 | error_description: string; 7 | }> = (props) => { 8 | return ( 9 | 10 | 11 | {`ERROR: ${props.error}.`} 12 | {props.error_description} 13 | 14 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /frontend/src/pages/unauthorized/LoginPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { redirectToLoginPage } from 'helpers/auth/openid/openid-manager'; 3 | import Logger from 'js-logger'; 4 | 5 | export const LoginPage: React.FC = () => { 6 | useEffect(() => { 7 | redirectToLoginPage().catch((e) => Logger.error(e)); 8 | }, []); 9 | return null; 10 | }; 11 | -------------------------------------------------------------------------------- /frontend/src/razor.scss: -------------------------------------------------------------------------------- 1 | @import './styles/base'; 2 | @import './index'; 3 | @import './components/uikit/Field.module'; 4 | @import './components/uikit/inputs/Input.module'; 5 | @import './components/uikit/buttons/Button.module'; 6 | 7 | /*SVG load from resources as image and cannot be updated 8 | Original color should be #a5aaa5 9 | https://stackoverflow.com/a/53336754/291695*/ 10 | .passwordEyeImage:hover { 11 | filter: brightness(88%) contrast(88%); 12 | } 13 | 14 | .passwordEye { 15 | position: absolute; 16 | right: 7px; 17 | top: 7px; 18 | cursor: pointer; 19 | } 20 | 21 | input.input-validation-error { 22 | border-bottom: 1px solid $danger; 23 | } 24 | 25 | input.input-validation-error:focus { 26 | border-color: $danger; 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import { ReportHandler } from 'web-vitals'; 4 | 5 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 6 | if (onPerfEntry && onPerfEntry instanceof Function) { 7 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 8 | getCLS(onPerfEntry); 9 | getFID(onPerfEntry); 10 | getFCP(onPerfEntry); 11 | getLCP(onPerfEntry); 12 | getTTFB(onPerfEntry); 13 | }); 14 | } 15 | }; 16 | 17 | export default reportWebVitals; 18 | -------------------------------------------------------------------------------- /frontend/src/services/api/index.ts: -------------------------------------------------------------------------------- 1 | import * as QueryFactory from './api-client'; 2 | export { QueryFactory }; 3 | -------------------------------------------------------------------------------- /frontend/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | 7 | if (import.meta.env.DEV) { 8 | // set loooong timeout in debug (so that it doesn't timeout with breakpoints set) 9 | jest.setTimeout(10000000); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/stories/DatePicker.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StoryFn, Meta } from '@storybook/react'; 3 | import { defaultExclude } from '../../.storybook/preview'; 4 | import { DatePicker } from 'components/uikit/inputs/date-time/DatePicker'; 5 | const StoryComponent = DatePicker; 6 | 7 | export default { 8 | title: 'Example/DatePicker', 9 | component: StoryComponent, 10 | args: {} as Partial>, 11 | parameters: { 12 | controls: { 13 | exclude: [ 14 | ...defaultExclude, 15 | 'containerRef', 16 | 'endAdornmentClassname', 17 | 'onEnterPressed', 18 | 'onChange', 19 | ] as (keyof React.ComponentProps)[], 20 | }, 21 | }, 22 | } as Meta; 23 | 24 | export const Default = {}; 25 | 26 | export const WithTime = { 27 | args: { 28 | withTime: true, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /frontend/src/stories/utils.ts: -------------------------------------------------------------------------------- 1 | export function sleep(ms: number) { 2 | return new Promise((resolve) => setTimeout(resolve, ms)); 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/styles/z-index.scss: -------------------------------------------------------------------------------- 1 | $loadingZIndex: 90; 2 | $dropDownOptionListZIndex: 250; 3 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "typeRoots": ["types"], 5 | "baseUrl": "./src" 6 | }, 7 | "include": ["src", "types"], 8 | "references": [{ "path": "./tsconfig.node.json" }] 9 | } 10 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /frontend/types/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.scss'; 3 | declare module '*.module.scss'; 4 | declare module '*.svg' { 5 | const ReactComponent: React.FC< 6 | React.SVGProps & { 7 | testid?: string; 8 | title?: string; 9 | tst?: string; 10 | } 11 | >; 12 | const content: string; 13 | 14 | export { ReactComponent }; 15 | export default content; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/types/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly REACT_APP_VERSION: string; 5 | readonly REACT_APP_SENTRY_DSN: string; 6 | readonly REACT_APP_MINI_PROFILER_ENABLED: string; 7 | readonly DEV: string; 8 | readonly TST: string; 9 | // more env variables... variable types MUST be string 10 | } 11 | 12 | interface ImportMeta { 13 | readonly env: ImportMetaEnv; 14 | } 15 | 16 | declare const build: { 17 | readonly REACT_APP_VERSION: string; 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/types/jlottie.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@lottiefiles/jlottie' { 2 | export type JLottieOptions = { 3 | loop?: boolean; 4 | autoplay: boolean; 5 | animationData: any; 6 | debug?: boolean; 7 | container: HTMLElement; 8 | rendererSettings?: { 9 | preserveAspectRatio?: string; 10 | }; 11 | }; 12 | 13 | export function loadAnimation(options: JLottieOptions): { 14 | pause(); 15 | play(); 16 | destroy(); 17 | stop(); 18 | }; 19 | export function destroy(element: HTMLElement); 20 | export function stop(element: HTMLElement); 21 | export function play(element: HTMLElement); 22 | } 23 | -------------------------------------------------------------------------------- /k8s/dashboard/users.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: admin-user 5 | namespace: kubernetes-dashboard 6 | --- 7 | apiVersion: rbac.authorization.k8s.io/v1 8 | kind: ClusterRoleBinding 9 | metadata: 10 | name: admin-user 11 | roleRef: 12 | apiGroup: rbac.authorization.k8s.io 13 | kind: ClusterRole 14 | name: cluster-admin 15 | subjects: 16 | - kind: ServiceAccount 17 | name: admin-user 18 | namespace: kubernetes-dashboard 19 | --- 20 | apiVersion: v1 21 | kind: Secret 22 | type: kubernetes.io/service-account-token 23 | metadata: 24 | name: admin-user-secret 25 | namespace: kubernetes-dashboard 26 | annotations: 27 | kubernetes.io/service-account.name: 'admin-user' 28 | -------------------------------------------------------------------------------- /k8s/init-namespace.sh: -------------------------------------------------------------------------------- 1 | [ ! -f ".env" ] && (echo ".env file is not found" && exit 1) 2 | export $(cat ./.env | xargs) 3 | [ -z "$NAMESPACE" ] && (echo 'NAMESPACE env. variable is not defined (needed to configure DNS name)' && exit 1) 4 | 5 | # This file sets up k3s on fresh VPS: 6 | # 1. Create namespace 7 | kubectl create namespace $NAMESPACE || echo "$NAMESPACE exists" 8 | 9 | # 2. Create secret config maps (`$NAMESPACE-configmap-secret`) 10 | kubectl -n $NAMESPACE create configmap $NAMESPACE-configmap-secret --from-env-file=.env || (kubectl -n $NAMESPACE create configmap $NAMESPACE-configmap-secret --from-env-file=.env -o yaml --dry-run=client | kubectl replace -f -) 11 | -------------------------------------------------------------------------------- /k8s/letsencrypt.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: letsencrypt 5 | spec: 6 | acme: 7 | email: ${EMAIL} 8 | server: https://acme-v02.api.letsencrypt.org/directory 9 | privateKeySecretRef: 10 | name: letsencrypt 11 | solvers: 12 | # An empty 'selector' means that this solver matches all domains 13 | - selector: {} 14 | http01: 15 | ingress: {} 16 | -------------------------------------------------------------------------------- /k8s/reinit-namespace.sh: -------------------------------------------------------------------------------- 1 | [ ! -f ".env" ] && (echo ".env file is not found" && exit 1) 2 | export $(cat ./.env | xargs) 3 | [ -z "$NAMESPACE" ] && (echo 'NAMESPACE env. variable is not defined (needed to configure DNS name)' && exit 1) 4 | 5 | # This file sets up k3s on fresh VPS: 6 | # 1. Delete namespace 7 | # 2. Run script that initializes namespace from scratch 8 | 9 | kubectl delete namespace $NAMESPACE 10 | 11 | curl -sfL https://raw.githubusercontent.com/mccsoft/backend-frontend-template/master/k8s/init-namespace.sh | sh -s - 12 | -------------------------------------------------------------------------------- /nginx/conf.d/proxy.conf: -------------------------------------------------------------------------------- 1 | client_max_body_size 100m; 2 | -------------------------------------------------------------------------------- /nginx/logs/placeholder.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/nginx/logs/placeholder.txt -------------------------------------------------------------------------------- /nginx/vhost.d/default_location: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/nginx/vhost.d/default_location -------------------------------------------------------------------------------- /patches/2023-05-25-01-vite-autoprefixer.patch: -------------------------------------------------------------------------------- 1 | Index: frontend/vite.config.ts 2 | IDEA additional info: 3 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 4 | <+>UTF-8 5 | =================================================================== 6 | diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts 7 | --- a/frontend/vite.config.ts (revision ef06db92ff199be94f13cf67e7f20c5f02380fc6) 8 | +++ b/frontend/vite.config.ts (revision c9372b49deb3a7b98bff032fc3225ed8af809913) 9 | @@ -1,4 +1,5 @@ 10 | import { defineConfig, loadEnv } from 'vite'; 11 | +import autoprefixer from 'autoprefixer'; 12 | import tsconfigPaths from 'vite-tsconfig-paths'; 13 | import react from '@vitejs/plugin-react'; 14 | import reactSwc from '@vitejs/plugin-react-swc'; 15 | @@ -75,6 +76,9 @@ 16 | modules: { 17 | localsConvention: 'camelCase', 18 | }, 19 | + postcss: { 20 | + plugins: [autoprefixer], 21 | + }, 22 | }, 23 | build: { 24 | outDir: 'build', 25 | -------------------------------------------------------------------------------- /patches/2023-05-29-01-allowImportingTsExtensions.patch: -------------------------------------------------------------------------------- 1 | Subject: [PATCH] allowImportingTsExtensions 2 | --- 3 | Index: tsconfig.json 4 | IDEA additional info: 5 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 6 | <+>UTF-8 7 | =================================================================== 8 | diff --git a/tsconfig.json b/tsconfig.json 9 | --- a/tsconfig.json (revision 12b17622545cd607b5d0ae3cefd4960ff9f4d972) 10 | +++ b/tsconfig.json (date 1685370467183) 11 | @@ -17,6 +17,7 @@ 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | + "allowImportingTsExtensions": true, 16 | "jsx": "react-jsx", 17 | "noEmit": true 18 | } 19 | -------------------------------------------------------------------------------- /patches/2023-06-09-04-useAdvancedForm-use-superjson.patch: -------------------------------------------------------------------------------- 1 | Index: frontend/src/helpers/form/react-hook-form-helper.ts 2 | IDEA additional info: 3 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 4 | <+>UTF-8 5 | =================================================================== 6 | diff --git a/frontend/src/helpers/form/react-hook-form-helper.ts b/frontend/src/helpers/form/react-hook-form-helper.ts 7 | --- a/frontend/src/helpers/form/react-hook-form-helper.ts (revision c4f135936ea6157a73e00ef78ad3d1b2b4f16402) 8 | +++ b/frontend/src/helpers/form/react-hook-form-helper.ts (revision 751e17f20b3a0e4742c007961b0d947428f926c5) 9 | @@ -27,7 +27,7 @@ 10 | keepDirtyValues: true, 11 | }); 12 | } 13 | - }, [defaultValues]); 14 | + }, [superjson.stringify(defaultValues)]); 15 | } 16 | 17 | export function requiredRule() { 18 | -------------------------------------------------------------------------------- /patches/2023-06-21-02-fix-useModal-for-prompt-when-enter-is-pressed.patch: -------------------------------------------------------------------------------- 1 | Index: frontend/src/components/uikit/modal/useModal.tsx 2 | IDEA additional info: 3 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 4 | <+>UTF-8 5 | =================================================================== 6 | diff --git a/frontend/src/components/uikit/modal/useModal.tsx b/frontend/src/components/uikit/modal/useModal.tsx 7 | --- a/frontend/src/components/uikit/modal/useModal.tsx (revision b0835be135ba5da91ac6d6802da1e01833492e09) 8 | +++ b/frontend/src/components/uikit/modal/useModal.tsx (revision 6e5e1e177daa026788223a2d6a597f5dfc7d91d8) 9 | @@ -257,6 +257,7 @@ 10 | errorText={options.fieldError} 11 | onKeyDown={(event) => { 12 | if (event.key === 'Enter') { 13 | + commonClose(); 14 | options.resolve(fieldValue); 15 | setFieldValue(''); 16 | } 17 | -------------------------------------------------------------------------------- /patches/2023-06-22-02-settingsjson-use-local-typescript.patch: -------------------------------------------------------------------------------- 1 | Index: .vscode/settings.json 2 | IDEA additional info: 3 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 4 | <+>UTF-8 5 | =================================================================== 6 | diff --git a/.vscode/settings.json b/.vscode/settings.json 7 | --- a/.vscode/settings.json (revision 91c11b427140ed8cbed3215a959630a45c51e4a2) 8 | +++ b/.vscode/settings.json (revision b6a1cb43bce59be4b664f04f2d0eabe4a9d167de) 9 | @@ -25,5 +25,6 @@ 10 | }, 11 | "git.showPushSuccessNotification": true, 12 | "csharp.format.enable": true, 13 | - "files.autoSave": "onFocusChange" 14 | + "files.autoSave": "onFocusChange", 15 | + "typescript.tsdk": "node_modules\\typescript\\lib" 16 | } 17 | -------------------------------------------------------------------------------- /scripts/.env_template: -------------------------------------------------------------------------------- 1 | EMAIL=templateapp@mcc-soft.de 2 | NAMESPACE=templateapp 3 | Sentry__Dsn=https://b00b94bddfeb4097a5078a6dc2bf5f03@o920098.ingest.sentry.io/5864957 4 | Serilog__Remote__Server=logs-01.loggly.com 5 | Serilog__Remote__Port=443 6 | Serilog__Remote__Token=b2a89f6a-666d-4825-977c-79499bc958c5 7 | Serilog__Remote__InstanceName=dev 8 | Serilog__Elastic__Url= 9 | Serilog__Elastic__User= 10 | Serilog__Elastic__Password= 11 | TestApiEnabled=false 12 | POSTGRES_DATABASE=template_app 13 | POSTGRES_USER=postgres 14 | -------------------------------------------------------------------------------- /scripts/deploy/set-up-stage.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # You could put here any set up code (e.g. creating directories, setting permissions, etc.) 4 | # 5 | mkdir ./deafult_file_storage -p 6 | chmod 777 ./deafult_file_storage 7 | -------------------------------------------------------------------------------- /scripts/integresql/run-compose.bat: -------------------------------------------------------------------------------- 1 | docker compose up -d 2 | -------------------------------------------------------------------------------- /scripts/mssql/start_mssql.ps1: -------------------------------------------------------------------------------- 1 | docker volume create --name mssql-volume 2 | docker container rm mssql_db 3 | docker run --restart=always -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=yourStrong(!)Password" -p 1433:1433 --name mssql_db --hostname mssql_db -d mcr.microsoft.com/mssql/server:2022-latest -------------------------------------------------------------------------------- /scripts/opensearch/.env: -------------------------------------------------------------------------------- 1 | VIRTUAL_HOST=logs.mcc-soft.de 2 | -------------------------------------------------------------------------------- /scripts/opensearch/apply-internal-users.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker exec -it opensearch-node1 sh -c "cd /usr/share/opensearch/plugins/opensearch-security/tools/ && \ 4 | 5 | sh securityadmin.sh \ 6 | -f /usr/share/opensearch/config/opensearch-security/internal_users.yml \ 7 | -t internalusers \ 8 | -icl -nhnv \ 9 | -cacert /usr/share/opensearch/config/root-ca.pem \ 10 | -cert /usr/share/opensearch/config/kirk.pem \ 11 | -key /usr/share/opensearch/config/kirk-key.pem \ 12 | " 13 | 14 | -------------------------------------------------------------------------------- /scripts/opensearch/generate-password.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker exec -it opensearch-node1 sh -c "cd /usr/share/opensearch/plugins/opensearch-security/tools/ && \ 4 | ./hash.sh 5 | " 6 | -------------------------------------------------------------------------------- /scripts/opensearch/internal-users.yml: -------------------------------------------------------------------------------- 1 | _meta: 2 | type: 'internalusers' 3 | config_version: 2 4 | 5 | admin: 6 | hash: '$2y$12$4GwbIIr4s.CaZCESVt2EDefGfb.96H5UR9GmY0XckDXEXXub8vMy2' 7 | reserved: true 8 | backend_roles: 9 | - 'admin' 10 | description: 'Admin user' 11 | -------------------------------------------------------------------------------- /scripts/postgresql/start_postgres.ps1: -------------------------------------------------------------------------------- 1 | docker volume create --name postgres-volume 2 | docker container rm postgres_db 3 | docker run --restart=always -p 5432:5432 --name postgres_db -e POSTGRES_PASSWORD=postgres -v postgres-volume:/var/lib/postgresql/data -d postgres -------------------------------------------------------------------------------- /scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "paths": { 5 | "node-replace": ["../types/node-replace.d.ts"], 6 | "renamer": ["../types/renamer.d.ts"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "allowImportingTsExtensions": true, 17 | "jsx": "react-jsx", 18 | "noEmit": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /types/node-replace.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'node-replace' { 2 | export default function replace(options: { 3 | regex: string | RegExp; 4 | replacement: string; 5 | paths: string[]; 6 | recursive?: boolean; 7 | silent?: boolean; 8 | exclude?: string | string[]; 9 | }) {} 10 | } 11 | -------------------------------------------------------------------------------- /types/renamer.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'renamer' { 2 | export default class Renamer { 3 | public rename(options: { 4 | files: string | string[]; 5 | find: string | RegExp; 6 | replace: strint; 7 | dryRun?: boolean; 8 | }) {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /webapi/.csharpierrc: -------------------------------------------------------------------------------- 1 | printWidth: 100 2 | -------------------------------------------------------------------------------- /webapi/.gitignore: -------------------------------------------------------------------------------- 1 | /src/MccSoft.Unicorn.App/tempkey.rsa 2 | /.idea/.idea.MccSoft.Unicorn.Main/.idea/workspace.xml 3 | /.idea/.idea.MccSoft.Unicorn.Main/riderModule.iml 4 | /src/MccSoft.Unicorn.App/Statistics/*.repx 5 | cobertura/report 6 | cobertura/*.xml 7 | -------------------------------------------------------------------------------- /webapi/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /webapi/.idea/.idea.MccSoft.TemplateApp/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /contentModel.xml 6 | /modules.xml 7 | /projectSettingsUpdater.xml 8 | /.idea.MccSoft.TemplateApp.iml 9 | # Datasource local storage ignored files 10 | /dataSources/ 11 | /dataSources.local.xml 12 | # Editor-based HTTP Client requests 13 | /httpRequests/ 14 | -------------------------------------------------------------------------------- /webapi/.idea/.idea.MccSoft.TemplateApp/.idea/.name: -------------------------------------------------------------------------------- 1 | MccSoft.TemplateApp -------------------------------------------------------------------------------- /webapi/.idea/.idea.MccSoft.TemplateApp/.idea/CSharpierPlugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /webapi/.idea/.idea.MccSoft.TemplateApp/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /webapi/.idea/.idea.MccSoft.TemplateApp/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ../.ci 6 | ../.config 7 | ../docs 8 | ../frontend 9 | ../k8s 10 | ../scripts 11 | ../scripts/opensearch 12 | .config 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /webapi/.idea/.idea.MccSoft.TemplateApp/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /webapi/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /webapi/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /webapi/.idea/webapi.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /webapi/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "csharpier.csharpier-vscode", 4 | "formulahendry.dotnet-test-explorer", 5 | "formulahendry.dotnet", 6 | "ms-dotnettools.csharp", 7 | "dbaeumer.vscode-eslint", 8 | "orta.vscode-jest", 9 | "ms-playwright.playwright", 10 | "esbenp.prettier-vscode", 11 | "mrmlnc.vscode-scss" 12 | ] 13 | } -------------------------------------------------------------------------------- /webapi/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "eslint.alwaysShowStatus": true, 4 | "eslint.debug": true, 5 | "eslint.format.enable": true, 6 | "eslint.packageManager": "yarn", 7 | "editor.formatOnSave": true, 8 | "editor.defaultFormatter": "esbenp.prettier-vscode", 9 | 10 | "[csharp]": { 11 | "editor.formatOnType": true, 12 | "editor.formatOnSave": true, 13 | "editor.defaultFormatter": "csharpier.csharpier-vscode", 14 | }, 15 | 16 | "npm-scripts.showStartNotification": false, 17 | "dotnet-test-explorer.testProjectPath": "tests/**", 18 | "git.pullTags": false, 19 | "git.rebaseWhenSync": true, 20 | "git.autoStash": true, 21 | "git.branchPrefix": "feature/", 22 | "git.fetchOnPull": true, 23 | "git.showActionButton": { 24 | // "sync": false 25 | }, 26 | "git.showPushSuccessNotification": true, 27 | "csharp.format.enable": true, 28 | "files.autoSave": "onFocusChange" 29 | } 30 | -------------------------------------------------------------------------------- /webapi/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | VpRoslynDateTimeNowAnalayzer;ENUM0001;CS8509 4 | 1591;8618;CLASS0001;ASYNC0001;ASYNC0004;VpRoslynConfigureAwaitAnalayzer;RETURN0001;EF1001;1570;1998 5 | NN7SCLUR85N6WKBL8JYK774XWZ-ZQQQQQQQ6QZWTCCZL5AR27W3VJRL5 6 | 7 | 8 | -------------------------------------------------------------------------------- /webapi/Lib/DomainHelpers/MccSoft.DomainHelpers/DomainEvents/IDomainEvent.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace MccSoft.DomainHelpers.DomainEvents; 4 | 5 | public interface IDomainEvent : INotification { } 6 | -------------------------------------------------------------------------------- /webapi/Lib/DomainHelpers/MccSoft.DomainHelpers/DomainEvents/IDomainEventEntity.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MccSoft.DomainHelpers.DomainEvents; 4 | 5 | public interface IDomainEventEntity 6 | { 7 | IReadOnlyList DomainEvents { get; } 8 | void AddEvent(IDomainEvent domainEvent, bool removeEventsOfSameType = false); 9 | void ClearDomainEvents(); 10 | } 11 | -------------------------------------------------------------------------------- /webapi/Lib/DomainHelpers/MccSoft.DomainHelpers/DomainEvents/IDomainEventWithIntegerId.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.DomainHelpers.DomainEvents; 2 | 3 | public interface IDomainEventWithIntegerId : IDomainEvent 4 | { 5 | /// 6 | /// Id will be assigned by EntityFramework when entity is saved 7 | /// 8 | public int Id { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /webapi/Lib/DomainHelpers/MccSoft.DomainHelpers/DomainEvents/IDomainEventWithStringId.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.DomainHelpers.DomainEvents; 2 | 3 | public interface IDomainEventWithStringId : IDomainEvent 4 | { 5 | /// 6 | /// Id will be assigned by EntityFramework when entity is saved 7 | /// 8 | public string Id { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /webapi/Lib/DomainHelpers/MccSoft.DomainHelpers/EntityExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace MccSoft.DomainHelpers; 7 | 8 | public static class EfCoreExtensions 9 | { 10 | // https://learn.microsoft.com/en-us/ef/core/miscellaneous/nullable-reference-types#required-navigation-properties 11 | [StackTraceHidden] 12 | public static T ThrowIfNotIncluded( 13 | [NotNull] this T? backingField, 14 | [CallerMemberName] string navigationPropName = "" 15 | ) 16 | where T : class 17 | { 18 | return backingField 19 | ?? throw new NullReferenceException($"{navigationPropName} was not included"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /webapi/Lib/DomainHelpers/MccSoft.DomainHelpers/MccSoft.DomainHelpers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 6 | 1591 7 | Contains classes to help with different domain manipulations and checks. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /webapi/Lib/Health/MccSoft.Health/HealthCheckSettings.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.Health 2 | { 3 | /// 4 | /// Provides configuration for HealthChecks 5 | /// 6 | public class HealthCheckSetting 7 | { 8 | public int? TimeoutInSeconds { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /webapi/Lib/HttpClientExtension/MccSoft.HttpClientExtension/ITokenHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace MccSoft.HttpClientExtension; 4 | 5 | public interface ITokenHandler 6 | { 7 | Task GetAccessToken(); 8 | Task RefreshToken(); 9 | } 10 | -------------------------------------------------------------------------------- /webapi/Lib/HttpClientExtension/MccSoft.HttpClientExtension/MccSoft.HttpClientExtension.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 6 | 1591 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /webapi/Lib/Logging/MccSoft.Logging.Tests/FieldTests.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.Logging.Tests; 2 | 3 | public class FieldTests 4 | { 5 | [Fact] 6 | public void ToStringConversion() 7 | { 8 | void Method(string s) 9 | { 10 | s.Should().Be("f_Method"); 11 | } 12 | $"{Field.Method}".Should().Be("{f_Method}"); 13 | ((string)Field.Method).Should().Be("f_Method"); 14 | Method(Field.Method); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /webapi/Lib/Logging/MccSoft.Logging.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; 2 | global using FluentAssertions; 3 | -------------------------------------------------------------------------------- /webapi/Lib/Logging/MccSoft.Logging/Disposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MccSoft.Logging; 4 | 5 | internal class Disposable : IDisposable 6 | { 7 | private readonly Action _action; 8 | 9 | public Disposable(Action action) 10 | { 11 | _action = action; 12 | } 13 | 14 | public void Dispose() 15 | { 16 | _action(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /webapi/Lib/Logging/MccSoft.Logging/Elastic/ElasticLoggingOptions.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.Logging; 2 | 3 | public class ElasticLoggingOptions 4 | { 5 | public string Url { get; set; } 6 | public string User { get; set; } 7 | public string Password { get; set; } 8 | 9 | /// 10 | /// Optional parameter to override the name of the index in OpenSearch. 11 | /// Defaults to the . 12 | /// 13 | public string IndexName { get; set; } 14 | 15 | public bool EnableFileBuffer { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /webapi/Lib/Logging/MccSoft.Logging/ElasticFieldAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MccSoft.Logging; 4 | 5 | /// 6 | /// Specifies explicit type the field should have in Elasticsearch. 7 | /// 8 | [AttributeUsage(AttributeTargets.Field)] 9 | public class ElasticFieldAttribute : Attribute 10 | { 11 | public ElasticFieldAttribute(FieldType type) 12 | { 13 | Type = type; 14 | } 15 | 16 | /// 17 | /// The type with which the field is stored. 18 | /// 19 | public FieldType Type { get; private set; } 20 | } 21 | -------------------------------------------------------------------------------- /webapi/Lib/Logging/MccSoft.Logging/Field.partial.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.Logging; 2 | 3 | /// 4 | /// Represents commonly used logging fields, 5 | /// to avoid different names for the same thing. 6 | /// Put project-specific fields here. 7 | /// 8 | public partial class Field 9 | { 10 | public static Field ProductId = new(nameof(ProductId)); 11 | } 12 | -------------------------------------------------------------------------------- /webapi/Lib/Logging/MccSoft.Logging/INoSentryException.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.Logging; 2 | 3 | /// 4 | /// Exceptions with this interface will not be logged to Sentry 5 | /// 6 | public interface INoSentryException { } 7 | -------------------------------------------------------------------------------- /webapi/Lib/Logging/MccSoft.Logging/LogAttributePostProcess.partial.cs: -------------------------------------------------------------------------------- 1 | public static class LogAttributePostProcess 2 | { 3 | public static string PostProcessParameterName(string parameterName) 4 | { 5 | return parameterName; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /webapi/Lib/Logging/MccSoft.Logging/LogglyOptions.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.Logging; 2 | 3 | public class LogglyOptions 4 | { 5 | /// 6 | /// Loggly token 7 | /// 8 | public string Token { get; set; } 9 | 10 | /// 11 | /// Loggly server (usually logs-01.loggly.com) 12 | /// 13 | public string Server { get; set; } 14 | 15 | /// 16 | /// Loggly port (usually 443) 17 | /// 18 | public int Port { get; set; } 19 | 20 | /// 21 | /// Instance Name (usually something like 'dev/prod') that will be shown in logs as `InstanceName` field 22 | /// 23 | public string InstanceName { get; set; } 24 | } 25 | -------------------------------------------------------------------------------- /webapi/Lib/Logging/MccSoft.Logging/MccSoft.Logging.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | latest 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /webapi/Lib/Logging/MccSoft.Logging/OperationContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MccSoft.Logging; 4 | 5 | /// 6 | /// Context to contain parameters related to operation. 7 | /// 8 | public class OperationContext : Dictionary { } 9 | -------------------------------------------------------------------------------- /webapi/Lib/Logging/MccSoft.Logging/SerilogRequestLoggingOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MccSoft.Logging; 4 | 5 | public sealed class SerilogRequestLoggingOptions 6 | { 7 | public static string Position => "Serilog:RequestLogging"; 8 | 9 | /// 10 | /// Requests whose path starts from one of this values 11 | /// will be excluded from logs 12 | /// 13 | public List ExcludePaths { get; set; } = new(); 14 | } 15 | -------------------------------------------------------------------------------- /webapi/Lib/Logging/MccSoft.Logging/SessionIdHeader.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.Logging; 2 | 3 | public static class LoggingHeaders 4 | { 5 | /// 6 | /// The name of the session-id header for propagation 7 | /// via . 8 | /// 9 | public const string SessionId = nameof(SessionId); 10 | } 11 | -------------------------------------------------------------------------------- /webapi/Lib/LowLevelPrimitives/MccSoft.LowLevelPrimitives/DateTimeHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace MccSoft.LowLevelPrimitives; 5 | 6 | /// 7 | /// Helper to be able to override DateTime.UtcNow in tests, but avoid using/injecting IDateTimeProvider everywhere 8 | /// (especially in Domain entities) 9 | /// 10 | public static class DateTimeHelper 11 | { 12 | private static readonly AsyncLocal _customDateTime = new(); 13 | public static DateTime UtcNow => _customDateTime.Value ?? DateTime.UtcNow; 14 | 15 | public static Disposable SetCustomDateTime(DateTime dateTime) 16 | { 17 | var oldValue = _customDateTime.Value; 18 | _customDateTime.Value = dateTime; 19 | return new Disposable(() => 20 | { 21 | _customDateTime.Value = oldValue; 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /webapi/Lib/LowLevelPrimitives/MccSoft.LowLevelPrimitives/Disposable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MccSoft.LowLevelPrimitives; 4 | 5 | public class Disposable : IDisposable 6 | { 7 | private readonly Action _action; 8 | 9 | public Disposable(Action action) 10 | { 11 | _action = action; 12 | } 13 | 14 | public void Dispose() 15 | { 16 | _action(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /webapi/Lib/LowLevelPrimitives/MccSoft.LowLevelPrimitives/Exceptions/ConflictException.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace MccSoft.LowLevelPrimitives.Exceptions; 4 | 5 | public class ConflictException : WebApiBaseException 6 | { 7 | public ConflictException(string message) 8 | : base( 9 | ErrorTypes.Conflict, 10 | "Resource conflict occurred.", 11 | StatusCodes.Status409Conflict, 12 | message 13 | ) { } 14 | } 15 | -------------------------------------------------------------------------------- /webapi/Lib/LowLevelPrimitives/MccSoft.LowLevelPrimitives/Exceptions/InvalidRequestContentTypeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace MccSoft.LowLevelPrimitives.Exceptions; 6 | 7 | /// 8 | /// Thrown when the device sends a invalid Http Content-Type 9 | /// 10 | public class InvalidRequestContentTypeException : Exception, IWebApiException 11 | { 12 | public InvalidRequestContentTypeException(string message) : base(message) { } 13 | 14 | public IActionResult Result 15 | { 16 | get 17 | { 18 | var cr = new ContentResult 19 | { 20 | Content = Message, 21 | StatusCode = (int?)HttpStatusCode.BadRequest 22 | }; 23 | return cr; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /webapi/Lib/LowLevelPrimitives/MccSoft.LowLevelPrimitives/Exceptions/PersistenceAccessDeniedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MccSoft.LowLevelPrimitives.Exceptions; 4 | 5 | /// 6 | /// This exception could be thrown from Domain entities, and will result in HTTP 403 7 | /// 8 | public class PersistenceAccessDeniedException : Exception 9 | { 10 | public PersistenceAccessDeniedException(string message) : base(message) { } 11 | } 12 | -------------------------------------------------------------------------------- /webapi/Lib/LowLevelPrimitives/MccSoft.LowLevelPrimitives/Exceptions/PersistenceResourceNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace MccSoft.LowLevelPrimitives.Exceptions; 4 | 5 | /// 6 | /// Thrown when the resource requested by the client is not found and the server should 7 | /// reply with 404. 8 | /// 9 | public class PersistenceResourceNotFoundException : WebApiBaseException 10 | { 11 | public PersistenceResourceNotFoundException(string message) 12 | : base( 13 | ErrorTypes.NotFound, 14 | "The requested resource was not found.", 15 | StatusCodes.Status404NotFound, 16 | message 17 | ) { } 18 | } 19 | -------------------------------------------------------------------------------- /webapi/Lib/LowLevelPrimitives/MccSoft.LowLevelPrimitives/Extensions/DateTimeExtensions.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System; 3 | 4 | namespace MccSoft.LowLevelPrimitives.Extensions; 5 | 6 | public static class DateTimeExtensions 7 | { 8 | public static DateTime AsUtc(this DateTime dateTime) => 9 | DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); 10 | } 11 | -------------------------------------------------------------------------------- /webapi/Lib/LowLevelPrimitives/MccSoft.LowLevelPrimitives/Extensions/LinqExtensions.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace MccSoft.LowLevelPrimitives.Extensions; 7 | 8 | public static class LinqExtensions 9 | { 10 | public static void ForEach(this IEnumerable enumerable, Action action) 11 | { 12 | foreach (var item in enumerable) 13 | action(item); 14 | } 15 | 16 | public static IEnumerable Shuffle(this IEnumerable enumerable) 17 | { 18 | return enumerable.OrderBy(_ => Random.Shared.Next()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /webapi/Lib/LowLevelPrimitives/MccSoft.LowLevelPrimitives/Extensions/RandomExtensions.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System; 3 | 4 | namespace MccSoft.LowLevelPrimitives.Extensions; 5 | 6 | public static class RandomExtensions 7 | { 8 | private const string _alphabet = "0123456789ABCDEF"; 9 | 10 | public static string GetString(this Random random, int length = 10, string? alphabet = null) 11 | { 12 | alphabet ??= _alphabet; 13 | return string.Create( 14 | length, 15 | null as object, 16 | (span, _) => 17 | { 18 | for (var i = 0; i < span.Length; i++) 19 | span[i] = alphabet[random.Next(alphabet.Length)]; 20 | } 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /webapi/Lib/LowLevelPrimitives/MccSoft.LowLevelPrimitives/IUserAccessor.partial.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.LowLevelPrimitives; 2 | 3 | public partial interface IUserAccessor { } 4 | -------------------------------------------------------------------------------- /webapi/Lib/Mailing/MccSoft.Mailing/Models/EmailModelBase.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.Mailing.Models; 2 | 3 | public class EmailModelBase 4 | { 5 | // You don't need to fill the fields of base EmailModel manually. 6 | // They will be filled by EmailSender automatically. 7 | // Just use them in your template and don't worry :) 8 | 9 | public string SiteRootUrl { get; set; } 10 | public string RecipientEmail { get; set; } 11 | public string Subject { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /webapi/Lib/Mailing/MccSoft.Mailing/Models/MailSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MccSoft.Mailing.Models; 4 | 5 | public class MailSettings 6 | { 7 | public Func EmailViewPathProvider { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /webapi/Lib/Mailing/MccSoft.Mailing/Services/EmailTemplateType.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.Mailing; 2 | 3 | public enum EmailTemplateType 4 | { 5 | Subject, 6 | Content, 7 | } 8 | -------------------------------------------------------------------------------- /webapi/Lib/Mailing/MccSoft.Mailing/Services/IMailSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using MccSoft.Mailing.Models; 5 | 6 | namespace MccSoft.Mailing; 7 | 8 | public interface IMailSender 9 | { 10 | Task Send( 11 | string recipient, 12 | string subject, 13 | string text, 14 | string from = null, 15 | List attachments = null 16 | ); 17 | 18 | Task Send(string recipient, T model, List attachments = null) 19 | where T : EmailModelBase; 20 | } 21 | -------------------------------------------------------------------------------- /webapi/Lib/Mailing/MccSoft.Mailing/Services/IRazorRenderService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace MccSoft.Mailing; 4 | 5 | public interface IRazorRenderService 6 | { 7 | Task RenderToStringAsync(string viewName, T model); 8 | } 9 | -------------------------------------------------------------------------------- /webapi/Lib/NpgSql/MccSoft.NpgSql/MccSoft.NpgSql.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 6 | 1591 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /webapi/Lib/PersistenceHelpers/MccSoft.PersistenceHelpers/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace MccSoft.PersistenceHelpers; 5 | 6 | public static class Extensions 7 | { 8 | public static void RegisterRetryHelper( 9 | this IServiceCollection services, 10 | Action? setupAction = null 11 | ) 12 | { 13 | services.Configure(setupAction ?? (_ => { })); 14 | services.AddScoped(typeof(TransactionLogger<>)).AddScoped(typeof(DbRetryHelper<,>)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /webapi/Lib/Testing/MccSoft.Testing/AspNet/DummyStringLocalizer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.Extensions.Localization; 3 | 4 | namespace MccSoft.Testing.AspNet; 5 | 6 | public class DummyStringLocalizer : DummyStringLocalizer { } 7 | 8 | public class DummyStringLocalizer : IStringLocalizer 9 | { 10 | public IEnumerable GetAllStrings(bool includeParentCultures) 11 | { 12 | return new List(); 13 | } 14 | 15 | public LocalizedString this[string name] => new LocalizedString(name, name); 16 | 17 | public LocalizedString this[string name, params object[] arguments] => 18 | new LocalizedString(name, string.Format(name, arguments)); 19 | } 20 | -------------------------------------------------------------------------------- /webapi/Lib/Testing/MccSoft.Testing/Infrastructure/MotherFactory.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.Testing.Infrastructure; 2 | 3 | /// 4 | /// The empty class serving as the common extension point for real factories. 5 | /// To create a factory for a class, write an extension method for . 6 | /// 7 | public abstract class MotherFactory { } 8 | -------------------------------------------------------------------------------- /webapi/Lib/Testing/MccSoft.Testing/TypeDescription.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.Testing; 2 | 3 | /// 4 | /// Contains the summary XML-documentation of a type. 5 | /// 6 | public class TypeDescription 7 | { 8 | public TypeDescription(string type, string description) 9 | { 10 | Type = type; 11 | Description = description; 12 | } 13 | 14 | /// 15 | /// Name of the Type 16 | /// 17 | public string Type { get; } 18 | 19 | /// 20 | /// The description of the type 21 | /// 22 | public string Description { get; } 23 | } 24 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi.Tests/HangfireStubTestService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | public class HangfireStubTestService 4 | { 5 | private readonly MyDbContext _dbContext; 6 | 7 | public HangfireStubTestService(MyDbContext dbContext) 8 | { 9 | _dbContext = dbContext; 10 | } 11 | 12 | public async Task AddEntity(string title) 13 | { 14 | _dbContext.Entity1.Add(new Entity1() 15 | { 16 | Title = title, 17 | }); 18 | await _dbContext.SaveChangesAsync(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi.Tests/Infrastructure/Entity1.cs: -------------------------------------------------------------------------------- 1 | public class Entity1 2 | { 3 | public int Id { get; set; } 4 | public string Title { get; set; } 5 | } 6 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi.Tests/Infrastructure/MyDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | public class MyDbContext : DbContext 4 | { 5 | public MyDbContext(DbContextOptions options) : base(options) 6 | { 7 | } 8 | 9 | public DbSet Entity1 { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi.Tests/MccSoft.WebApi.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; 2 | global using FluentAssertions; 3 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Domain/Helpers/AnyOfAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Linq; 3 | 4 | namespace MccSoft.WebApi.Domain.Helpers; 5 | 6 | public class AnyOfThoseAttribute : ValidationAttribute 7 | { 8 | public object[] PossibleValues { get; set; } 9 | 10 | protected override ValidationResult IsValid(object value, ValidationContext validationContext) 11 | { 12 | if (PossibleValues.Any(x => x.Equals(value))) 13 | return ValidationResult.Success; 14 | else 15 | return new ValidationResult(FormatErrorMessage(validationContext.MemberName)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Domain/Helpers/OptionalEmailAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace MccSoft.WebApi.Domain.Helpers; 4 | 5 | public class OptionalEmailAddressAttribute : ValidationAttribute 6 | { 7 | private EmailAddressAttribute _emailAddressAttribute; 8 | 9 | public OptionalEmailAddressAttribute() 10 | { 11 | _emailAddressAttribute = new EmailAddressAttribute(); 12 | } 13 | 14 | public override bool IsValid(object value) 15 | { 16 | if (value != null && value.Equals(string.Empty)) 17 | return true; 18 | 19 | return _emailAddressAttribute.IsValid(value); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Helpers/StreamExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | 4 | namespace MccSoft.WebApi.Helpers; 5 | 6 | public static class StreamExtensions 7 | { 8 | public static async Task ToArray(this Stream stream) 9 | { 10 | long? position = null; 11 | if (stream.CanSeek) 12 | { 13 | position = stream.Position; 14 | stream.Seek(0, SeekOrigin.Begin); 15 | } 16 | 17 | await using var memoryStream = new MemoryStream(); 18 | await stream.CopyToAsync(memoryStream); 19 | 20 | if (position != null) 21 | { 22 | stream.Seek(position.Value, SeekOrigin.Begin); 23 | } 24 | 25 | return memoryStream.ToArray(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Jobs/HangFireJobSettings.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.WebApi.Jobs; 2 | 3 | /// 4 | /// Represents startup options for HangFire job. 5 | /// 6 | public abstract class HangFireJobSettings 7 | { 8 | /// 9 | /// Should be a cron compatible expression that is passed to hangfire scheduler. 10 | /// 11 | public string CronExpression { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Jobs/JobBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using MccSoft.Logging; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace MccSoft.WebApi.Jobs; 7 | 8 | /// 9 | /// The base class for recurring jobs. 10 | /// 11 | public abstract class JobBase 12 | { 13 | private readonly ILogger _logger; 14 | 15 | protected JobBase(ILogger logger) 16 | { 17 | _logger = logger; 18 | } 19 | 20 | /// 21 | /// Runs the job. 22 | /// 23 | public async Task Execute() 24 | { 25 | using IDisposable _ = _logger.BeginTopLevelActivity(GetType().Name, sessionId: null); 26 | await ExecuteCore(); 27 | } 28 | 29 | /// 30 | /// The main work of the job is done in this method. 31 | /// 32 | protected abstract Task ExecuteCore(); 33 | } 34 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Pagination/PagedRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.WebApi.Pagination; 2 | 3 | /// 4 | /// Paged Request DTO. 5 | /// 6 | public class PagedRequestDto 7 | { 8 | /// 9 | /// Offset of list. 10 | /// 11 | public int? Offset { get; set; } 12 | 13 | /// 14 | /// Number of requested records. 15 | /// 16 | public int? Limit { get; set; } 17 | 18 | /// 19 | /// Field name for sorting in DB. 20 | /// 21 | public string? SortBy { get; set; } 22 | 23 | /// 24 | /// Sort direction. Ascending or Descending. 25 | /// 26 | public SortOrder SortOrder { get; set; } 27 | } 28 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Pagination/PagedResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Linq; 4 | 5 | namespace MccSoft.WebApi.Pagination; 6 | 7 | public class PagedResult 8 | { 9 | public PagedResult(IEnumerable data, int totalCount) 10 | { 11 | Data = data.ToList(); 12 | TotalCount = totalCount; 13 | } 14 | 15 | [Required] 16 | public IList Data { get; set; } 17 | 18 | public int TotalCount { get; set; } 19 | } 20 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Pagination/PagingExtensions.partial.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.WebApi.Pagination; 2 | 3 | public static partial class PagingExtensions 4 | { 5 | private const int DefaultLimit = 2000; 6 | } 7 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Pagination/SortOrder.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.WebApi.Pagination; 2 | 3 | public enum SortOrder 4 | { 5 | Asc, 6 | Desc 7 | } 8 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Patching/Models/DoNotPatchAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MccSoft.WebApi.Patching.Models; 4 | 5 | /// 6 | /// Decorate properties that you don't want to be automatically copied to destination class during patching 7 | /// 8 | public class DoNotPatchAttribute : Attribute { } 9 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Patching/Models/IPatchRequest.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.WebApi.Patching.Models; 2 | 3 | public interface IPatchRequest 4 | { 5 | /// 6 | /// Returns true if property was present in http request; false otherwise 7 | /// 8 | bool IsFieldPresent(string propertyName); 9 | 10 | void SetHasProperty(string propertyName); 11 | } 12 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Patching/Models/NotRequiredAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MccSoft.WebApi.Patching.Models; 4 | 5 | /// 6 | /// Could be placed on properties to make them non-required 7 | /// 8 | public class NotRequiredAttribute : Attribute { } 9 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Sentry/SentryLoggerExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace MccSoft.WebApi.Sentry; 4 | 5 | public static class SentryLoggerExtension 6 | { 7 | public const string TestSentryMessagePrefix = "Test error to check Sentry integration from "; 8 | 9 | /// 10 | /// After service starts we need some test error event to be sent in Sentry. 11 | /// This event will ensure that Sentry reporting works for this service. 12 | /// 13 | public static void LogSentryTestError(this ILogger logger, string serviceName) 14 | { 15 | logger.LogError($"{TestSentryMessagePrefix}{serviceName}."); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Serialization/FromQueryJson/FromJsonQueryAttribute.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace MccSoft.WebApi.Serialization.FromQueryJson; 4 | 5 | /// 6 | /// Allows to use JSON-serialized DTO in Query parameters of HTTP requests. 7 | /// 8 | public class FromJsonQueryAttribute : ModelBinderAttribute 9 | { 10 | public FromJsonQueryAttribute() 11 | { 12 | BinderType = typeof(JsonQueryBinder); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Serialization/FromQueryJson/JsonQuerySwaggerGenExtensions.cs: -------------------------------------------------------------------------------- 1 | using NSwag.Generation.AspNetCore; 2 | 3 | namespace MccSoft.WebApi.Serialization.FromQueryJson; 4 | 5 | public static class JsonQuerySwaggerGenExtensions 6 | { 7 | /// 8 | /// Adds support for to NSwag. 9 | /// Integrate this by calling : 10 | /// builder.Services.AddOpenApiDocument(options => options.AddJsonQuerySupport()); 11 | /// OpenApi spec: https://swagger.io/docs/specification/describing-parameters/#schema-vs-content 12 | /// 13 | public static AspNetCoreOpenApiDocumentGeneratorSettings AddJsonQuerySupport( 14 | this AspNetCoreOpenApiDocumentGeneratorSettings options 15 | ) 16 | { 17 | options.OperationProcessors.Add(new JsonQueryNSwagOperationProcessor()); 18 | return options; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Serialization/SystemTextJsonSerializerSetup.partial.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using MccSoft.LowLevelPrimitives.Serialization.JsonObject; 3 | 4 | namespace MccSoft.WebApi.Serialization; 5 | 6 | public partial class SystemTextJsonSerializerSetup 7 | { 8 | static partial void CustomizeSettings(JsonSerializerOptions options) 9 | { 10 | options.Converters.Add(new JsonObjectConverter()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/SignedUrl/SignUrlOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MccSoft.WebApi.SignedUrl; 4 | 5 | public class SignUrlOptions 6 | { 7 | /// 8 | /// Secret to sign Url tokens 9 | /// 10 | public string Secret { get; set; } 11 | 12 | /// 13 | /// Interval during which the generated token will be valid 14 | /// 15 | public TimeSpan TokenDuration { get; set; } = TimeSpan.FromMinutes(20); 16 | } 17 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Swagger/JsonObjectTypeMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Nodes; 3 | using NJsonSchema; 4 | using NJsonSchema.Generation.TypeMappers; 5 | 6 | namespace MccSoft.WebApi.Swagger; 7 | 8 | /// 9 | /// Maps to `{ [key: string]: any; }` 10 | /// 11 | public class JsonObjectTypeMapper : ITypeMapper 12 | { 13 | public void GenerateSchema(JsonSchema schema, TypeMapperContext context) 14 | { 15 | schema.Type = JsonObjectType.Object; 16 | schema.AdditionalPropertiesSchema = JsonSchema.CreateAnySchema(); 17 | } 18 | 19 | public Type MappedType => typeof(JsonObject); 20 | public bool UseReference => false; 21 | } 22 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Throttler/ThrottleConfigOptions.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.WebApi.Throttler; 2 | 3 | /// 4 | /// Options for Throttler. 5 | /// 6 | public class ThrottleConfigOptions 7 | { 8 | /// 9 | /// Number of requests allowed during . 10 | /// If there will be more than during seconds 11 | /// requests will start to fail with HTTP 429. 12 | /// 13 | public int RequestLimit { get; set; } 14 | 15 | /// 16 | /// Time limit after which the request quotas are reset 17 | /// (i.e. it's allowed to make another number of requests). 18 | /// 19 | public int TimeoutInSeconds { get; set; } 20 | } 21 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/Throttler/ThrottleGroup.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.WebApi.Throttler; 2 | 3 | /// 4 | /// Request will be defined by such unique entity, called ThrottleGroup. 5 | /// 6 | public static class ThrottleGroup 7 | { 8 | /// 9 | /// Identity field of authorized user. 10 | /// 11 | public const string Identity = nameof(Identity); 12 | 13 | /// 14 | /// IP address of client. 15 | /// 16 | public const string IpAddress = nameof(IpAddress); 17 | } 18 | -------------------------------------------------------------------------------- /webapi/Lib/WebApi/MccSoft.WebApi/WebApiRegistrationExtensions.cs: -------------------------------------------------------------------------------- 1 | using MccSoft.WebApi.Middleware; 2 | using Microsoft.AspNetCore.Builder; 3 | 4 | namespace MccSoft.WebApi; 5 | 6 | public static class WebApiRegistrationExtensions 7 | { 8 | /// 9 | /// Adds a middleware to handle exceptions, including custom ones. 10 | /// 11 | /// The instance. 12 | public static IApplicationBuilder UseErrorHandling(this IApplicationBuilder app) 13 | { 14 | return app.UseMiddleware(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /webapi/Lib/WebHooks/MccSoft.WebHooks/Interceptors/WebHookInterceptors.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MccSoft.WebHooks.Domain; 3 | using MccSoft.WebHooks.Interceptors; 4 | 5 | public class WebHookInterceptors : IWebHookInterceptors 6 | where TSub : WebHookSubscription 7 | { 8 | public Action? AfterAllAttemptsFailed { get; set; } 9 | 10 | public Action>? ExecutionSucceeded { get; set; } 11 | 12 | public Action>? BeforeExecution { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /webapi/Lib/WebHooks/MccSoft.WebHooks/Publisher/IWebHookEventPublisher.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace MccSoft.WebHooks.Publisher; 5 | 6 | public interface IWebHookEventPublisher 7 | { 8 | /// 9 | /// Publishes a WebHook event by serializing the payload, storing it, and enqueuing delivery jobs. 10 | /// 11 | /// The type of event payload. 12 | /// The type of the event being published. 13 | /// The payload of the event. 14 | /// Token to cancel the asynchronous operation. 15 | /// A task that represents the asynchronous publish operation. 16 | Task PublishEvent(string eventType, T data, CancellationToken cancellationToken = default); 17 | }; 18 | -------------------------------------------------------------------------------- /webapi/cobertura/.placeholder: -------------------------------------------------------------------------------- 1 | This is a folder to place raw coverage files (cobertura.xml) to generate beautiful reports (by running coverage.bat from the folder above) -------------------------------------------------------------------------------- /webapi/coverage.bat: -------------------------------------------------------------------------------- 1 | rem This is a .bat file to generate code coverage reports. 2 | rem Download coverage.cobertura.xml files from build Artifacts (backend_coverage folder) and put it to `./cobertura` folder. 3 | rem Then run coverage.bat and check `./cobertura/report` folder to see the coverage. 4 | rem You could apply filters to see the coverage on separate files/classes. 5 | 6 | dotnet reportgenerator -reports:cobertura/* -targetdir:cobertura/report -sourcedirs:./ -assemblyfilters:+MccSoft.TemplateApp.App;+MccSoft.TemplateApp.Common;+MccSoft.TemplateApp.Domain 7 | open cobertura/report/index.html 8 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | /files/ 3 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Areas/Identity/IdentityHostingStartup.cs: -------------------------------------------------------------------------------- 1 | using MccSoft.TemplateApp.App.Areas.Identity; 2 | 3 | [assembly: HostingStartup(typeof(IdentityHostingStartup))] 4 | 5 | namespace MccSoft.TemplateApp.App.Areas.Identity; 6 | 7 | public class IdentityHostingStartup : IHostingStartup 8 | { 9 | public void Configure(IWebHostBuilder builder) 10 | { 11 | builder.ConfigureServices((context, services) => { }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Areas/Identity/Pages/Account/Logout.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LogoutModel 3 | @{ 4 | ViewData["Title"] = "Log out"; 5 | } 6 | 7 | 8 | 9 | 10 | @ViewData["Title"] 11 | @{ 12 | if (User.Identity.IsAuthenticated) 13 | { 14 | 15 | Click here to Logout 16 | 17 | } 18 | else 19 | { 20 | You have successfully logged out of the application. 21 | } 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Areas/Identity/Pages/Account/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using MccSoft.TemplateApp.App.Areas.Identity.Pages.Account -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Areas/Identity/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 | Error. 8 | An error occurred while processing your request. 9 | 10 | @if (Model.ShowRequestId) 11 | { 12 | 13 | Request ID: @Model.RequestId 14 | 15 | } 16 | 17 | Development Mode 18 | 19 | Swapping to Development environment will display more detailed information about the error that occurred. 20 | 21 | 22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 23 | 24 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Areas/Identity/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using MccSoft.TemplateApp.App.Areas.Identity 3 | @using MccSoft.TemplateApp.App.Areas.Identity.Pages 4 | 5 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 6 | @using MccSoft.TemplateApp.Domain 7 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Areas/Identity/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | 2 | @{ 3 | Layout = "/Views/Shared/_Layout.cshtml"; 4 | } 5 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Dictionaries/de/Frontend.json: -------------------------------------------------------------------------------- 1 | { 2 | "Common_App": "TemplateApp Admin", 3 | "Page": { 4 | "Login": { 5 | "login_field": "Login", 6 | "password_field": "Password", 7 | "login_button": "Login" 8 | }, 9 | "Products": { 10 | "Create": { 11 | "create_button": "Hinzufugen", 12 | "title": "Titel", 13 | "product_type": "Typ", 14 | "lastStockUpdatedAt": "Last Stock Update" 15 | }, 16 | "list": { 17 | "column_title": "Titel" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Dictionaries/de/ModelBindingErrors.json: -------------------------------------------------------------------------------- 1 | { 2 | "AttemptedValueIsInvalid": "DE:The value '{{value}}' is not valid for {{property}}", 3 | "MissingBindRequiredValue": "DE:A value for the '{{parameterName}}' parameter or property was not provided.", 4 | "MissingKeyOrValue": "DE:A value is required.", 5 | "MissingRequestBodyRequired": "DE:A non-empty request body is required.", 6 | "NonPropertyAttemptedValueIsInvalid": "DE:The value '{{value}}' is not valid.", 7 | "NonPropertyUnknownValueIsInvalid": "DE:The supplied value is invalid.", 8 | "NonPropertyValueMustBeANumber": "DE:The field must be a number.", 9 | "UnknownValueIsInvalid": "DE:The supplied value is invalid for {{propertyName}}.", 10 | "ValueIsInvalid": "DE:The value '{{value}}' is invalid.", 11 | "ValueMustBeANumber": "DE:The field {{propertyName}} must be a number.", 12 | "ValueMustNotBeNull": "DE:The value '{{value}}' is invalid." 13 | } -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Dictionaries/en/ModelBindingErrors.json: -------------------------------------------------------------------------------- 1 | { 2 | "AttemptedValueIsInvalid": "The value '{{value}}' is not valid for {{property}}", 3 | "MissingBindRequiredValue": "A value for the '{{parameterName}}' parameter or property was not provided.", 4 | "MissingKeyOrValue": "A value is required.", 5 | "MissingRequestBodyRequired": "A non-empty request body is required.", 6 | "NonPropertyAttemptedValueIsInvalid": "The value '{{value}}' is not valid.", 7 | "NonPropertyUnknownValueIsInvalid": "The supplied value is invalid.", 8 | "NonPropertyValueMustBeANumber": "The field must be a number.", 9 | "UnknownValueIsInvalid": "The supplied value is invalid for {{propertyName}}.", 10 | "ValueIsInvalid": "The value '{{value}}' is invalid.", 11 | "ValueMustBeANumber": "The field {{propertyName}} must be a number.", 12 | "ValueMustNotBeNull": "The value '{{value}}' is invalid." 13 | } -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Dictionaries/en/ValidationErrors.json: -------------------------------------------------------------------------------- 1 | { 2 | "Compare": "'{0}' and '{1}' do not match", 3 | "CreditCard": "Field must contain valid card number", 4 | "CustomValidation": "Field is invalid", 5 | "EmailAddress": "Field must contain valid e-mail address", 6 | "FileExtensions": "Field can contain only files with the following extensions: {1}", 7 | "MaxLength": "Field can have maximum of {1} characters", 8 | "MinLength": "Field must have minimum of {1} character", 9 | "Phone": "Field must contain a valid phone number", 10 | "Range": "Field must contain value from {1} to {2}", 11 | "RegularExpression": "Field must contain value conforming to regular expression '{1}'", 12 | "Required": "Required", 13 | "StringLength": "Field must be from {2} to {1} characters long", 14 | "Url": "Field must contain valid HTTP, HTTPS or FTP address" 15 | } 16 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Dictionaries/fr/ModelBindingErrors.json: -------------------------------------------------------------------------------- 1 | { 2 | "AttemptedValueIsInvalid": "FR:The value '{{value}}' is not valid for {{property}}", 3 | "MissingBindRequiredValue": "FR:A value for the '{{parameterName}}' parameter or property was not provided.", 4 | "MissingKeyOrValue": "FR:A value is required.", 5 | "MissingRequestBodyRequired": "FR:A non-empty request body is required.", 6 | "NonPropertyAttemptedValueIsInvalid": "FR:The value '{{value}}' is not valid.", 7 | "NonPropertyUnknownValueIsInvalid": "FR:The supplied value is invalid.", 8 | "NonPropertyValueMustBeANumber": "FR:The field must be a number.", 9 | "UnknownValueIsInvalid": "FR:The supplied value is invalid for {{propertyName}}.", 10 | "ValueIsInvalid": "FR:The value '{{value}}' is invalid.", 11 | "ValueMustBeANumber": "FR:The field {{propertyName}} must be a number.", 12 | "ValueMustNotBeNull": "FR:The value '{{value}}' is invalid." 13 | } -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Dictionaries/fr/ValidationErrors.json: -------------------------------------------------------------------------------- 1 | { 2 | "CreditCard": "Le champ {0} doit contenir un numéro de carte valide.", 3 | "CustomValidation": "Le champ {0} est invalide.", 4 | "EmailAddress": "Le champ {0} doit contenir une adresse courriel valide.", 5 | "FileExtensions": "Le champ {0} peut seulement contenir des fichiers avec les extensions suivantes : {1}", 6 | "MaxLength": "Le champ {0} ne peut dépasser {1} caractère(s).", 7 | "MinLength": "Le champ {0} doit contenir au moins {1} caractère(s).", 8 | "Phone": "Le champ {0} doit contenir un numéro de téléphone valide.", 9 | "Range": "Le champ {0} doit contenir une valeur entre {1} et {2}.", 10 | "RegularExpression": "Le champ {0} doit respecter l'expression régulière '{1}'.", 11 | "Required": "Le champ {0} est obligatoire.", 12 | "StringLength": "Le champ {0} doit être long de {2} à {1} caractère(s).", 13 | "Url": "Le champ {0} doit contenir une adresse HTTP, HTTPS ou FTP valide." 14 | } -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:9.0-noble 2 | USER root 3 | 4 | # we need gettext-base for envsubst to work 5 | RUN \ 6 | apt-get update \ 7 | && apt-get -y install gettext-base \ 8 | && mkdir /files \ 9 | && chmod 777 /files 10 | 11 | ENV ASPNETCORE_URLS="http://*:5000" 12 | COPY . /webapi 13 | 14 | RUN useradd dotnetuser --create-home --uid 4000 && \ 15 | chown dotnetuser /webapi && \ 16 | chown -R dotnetuser /webapi/wwwroot && \ 17 | chmod +x /webapi/import-meta-env 18 | 19 | WORKDIR /webapi 20 | USER dotnetuser 21 | ENTRYPOINT ./import-meta-env -x ./wwwroot/.env -e ./wwwroot/.env -p ./wwwroot/* && dotnet MccSoft.TemplateApp.App.dll 22 | EXPOSE 5000 23 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/DomainEventHandlers/LogDomainEventHandler.cs: -------------------------------------------------------------------------------- 1 | using MccSoft.DomainHelpers.DomainEvents.Events; 2 | using MediatR; 3 | 4 | namespace MccSoft.TemplateApp.App.DomainEventHandlers; 5 | 6 | /// 7 | /// Handles and logs its content to standard logger. 8 | /// 9 | public class LogDomainEventHandler : INotificationHandler 10 | { 11 | private readonly ILogger _logger; 12 | 13 | public LogDomainEventHandler(ILogger logger) 14 | { 15 | _logger = logger; 16 | } 17 | 18 | public Task Handle(LogDomainEvent notification, CancellationToken cancellationToken) 19 | { 20 | _logger.Log(notification.Level, notification.Message, notification.Parameters); 21 | return Task.CompletedTask; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Features/Files/DbFileExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using MccSoft.TemplateApp.App.Features.Files.Dto; 3 | using MccSoft.TemplateApp.Domain; 4 | using NeinLinq; 5 | 6 | namespace MccSoft.TemplateApp.App.Features.Files; 7 | 8 | public static class DbFileExtensions 9 | { 10 | [InjectLambda] 11 | public static FileInfoDto ToFileInfoDto(this DbFile dbFile) 12 | { 13 | return ToFileInfoDtoExpressionCompiled.Value(dbFile); 14 | } 15 | 16 | private static readonly Lazy> ToFileInfoDtoExpressionCompiled = 17 | new(() => ToFileInfoDto().Compile()); 18 | 19 | public static Expression> ToFileInfoDto() => 20 | dbFile => 21 | new FileInfoDto 22 | { 23 | Id = dbFile.Id.ToString(), 24 | FileName = dbFile.FileName, 25 | Size = dbFile.Size, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Features/Files/Dto/FileInfoDto.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.App.Features.Files.Dto; 2 | 3 | public class FileInfoDto 4 | { 5 | public string Id { get; set; } 6 | public string FileName { get; set; } 7 | public long Size { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Features/Products/Dto/CreateProductDto.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using MccSoft.TemplateApp.Domain; 3 | using MccSoft.WebApi.Domain.Helpers; 4 | 5 | namespace MccSoft.TemplateApp.App.Features.Products.Dto; 6 | 7 | public class CreateProductDto 8 | { 9 | [Required] 10 | [Trim] 11 | [MinLength(3)] 12 | public string Title { get; set; } 13 | 14 | public ProductType ProductType { get; set; } 15 | 16 | public DateOnly LastStockUpdatedAt { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Features/Products/Dto/PatchProductDto.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using MccSoft.TemplateApp.Domain; 3 | using MccSoft.WebApi.Patching.Models; 4 | 5 | namespace MccSoft.TemplateApp.App.Features.Products.Dto; 6 | 7 | public class PatchProductDto : PatchRequest 8 | { 9 | [MinLength(3)] 10 | public string Title { get; set; } 11 | public ProductType ProductType { get; set; } 12 | 13 | public DateOnly LastStockUpdatedAt { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Features/Products/Dto/ProductDto.cs: -------------------------------------------------------------------------------- 1 | using MccSoft.TemplateApp.Domain; 2 | 3 | namespace MccSoft.TemplateApp.App.Features.Products.Dto; 4 | 5 | public class ProductDto 6 | { 7 | public int Id { get; set; } 8 | public string Title { get; set; } 9 | public ProductType ProductType { get; set; } 10 | 11 | public DateOnly LastStockUpdatedAt { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Features/Products/Dto/ProductListItemDto.cs: -------------------------------------------------------------------------------- 1 | using MccSoft.TemplateApp.Domain; 2 | 3 | namespace MccSoft.TemplateApp.App.Features.Products.Dto; 4 | 5 | public class ProductListItemDto 6 | { 7 | public int Id { get; set; } 8 | public string Title { get; set; } 9 | public ProductType ProductType { get; set; } 10 | 11 | public DateOnly LastStockUpdatedAt { get; set; } 12 | 13 | public ProductListItemDto() { } 14 | } 15 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Features/Products/Dto/SearchProductDto.cs: -------------------------------------------------------------------------------- 1 | using MccSoft.TemplateApp.Domain; 2 | using MccSoft.WebApi.Pagination; 3 | 4 | namespace MccSoft.TemplateApp.App.Features.Products.Dto; 5 | 6 | public class SearchProductDto : PagedRequestDto 7 | { 8 | public string? Search { get; set; } 9 | public ProductType? ProductType { get; set; } 10 | 11 | public DateOnly? LastStockUpdatedAt { get; set; } 12 | 13 | public override string ToString() 14 | { 15 | return $"{nameof(Search)}: {Search}, {nameof(ProductType)}: {ProductType}, {nameof(LastStockUpdatedAt)}: {LastStockUpdatedAt}"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Features/TestApi/Dto/CreateTestTenantDto.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.App.Features.TestApi; 2 | 3 | public record CreateTestTenantDto 4 | { 5 | public string UserEmail { get; set; } 6 | public string UserPassword { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Features/TestApi/Dto/CreateTestTenantResponseDto.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.App.Features.TestApi; 2 | 3 | public class CreateTestTenantResponseDto 4 | { 5 | public CreateTestTenantResponseDto(int tenantId) 6 | { 7 | TenantId = tenantId; 8 | } 9 | 10 | public int TenantId { get; set; } 11 | } 12 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Features/Webhooks/Dto/CreateWebHookDto.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.App.Features.Webhooks.Dto; 2 | 3 | /// 4 | /// Webhook subscription DTO 5 | /// 6 | /// Human-readable name of integration 7 | /// URL for integration 8 | public record CreateWebHookDto( 9 | string Name, 10 | string Url, 11 | WebHookEventType EventType, 12 | string? method = null, 13 | Dictionary? Headers = null 14 | ); 15 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Features/Webhooks/Dto/WebHookSubscriptionDto.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.App.Features.Webhooks.Dto; 2 | 3 | /// 4 | /// Webhook subscription DTO 5 | /// 6 | /// Subscription Id 7 | /// Human-readable name of integration 8 | /// URL for integration 9 | public record WebhookSubscriptionDto( 10 | Guid Id, 11 | string Name, 12 | string Url, 13 | WebHookEventType eventType, 14 | string? method = null, 15 | Dictionary? headers = null 16 | ); 17 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Features/Webhooks/WebHookEventType.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.App.Features.Webhooks; 2 | 3 | public enum WebHookEventType 4 | { 5 | NewProduct = 1, 6 | ProductDeleted = 2, 7 | } 8 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/MccSoft.TemplateApp.App.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Middleware/RethrowErrorsFromPersistenceMiddlewareExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.App.Middleware; 2 | 3 | public static class RethrowErrorsFromPersistenceMiddlewareExtensions 4 | { 5 | /// 6 | /// Adds a middleware to handle exceptions, including custom ones. 7 | /// 8 | /// The instance. 9 | public static IApplicationBuilder UseRethrowErrorsFromPersistence(this IApplicationBuilder app) 10 | { 11 | return app.UseMiddleware(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Middleware/SignOutLockedUserMiddlewareExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.App.Middleware; 2 | 3 | public static class SignOutLockedUserMiddlewareExtensions 4 | { 5 | public static IApplicationBuilder UseSignOutLockedUser(this IApplicationBuilder builder) 6 | { 7 | return builder.UseMiddleware(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | [assembly: InternalsVisibleTo("MccSoft.TemplateApp.App.Tests")] 5 | [assembly: InternalsVisibleTo("MccSoft.TemplateApp.TestUtils")] 6 | [assembly: InternalsVisibleTo("MccSoft.TemplateApp.ComponentTests")] 7 | [assembly: ApiController] 8 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "MccSoft.TemplateApp.App": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "launchUrl": "https://localhost:5001", 7 | "environmentVariables": { 8 | "ASPNETCORE_URLS": "https://*:5001", 9 | "ASPNETCORE_ENVIRONMENT": "Development", 10 | "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy" 11 | } 12 | }, 13 | "BackendOnly": { 14 | "commandName": "Project", 15 | "launchBrowser": true, 16 | "launchUrl": "https://localhost:5001/swagger", 17 | "environmentVariables": { 18 | "ASPNETCORE_URLS": "https://*:5001", 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Services/Authentication/Seed/DefaultUserOptions.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.App.Services.Authentication; 2 | 3 | public class DefaultUserOptions 4 | { 5 | public string UserName { get; set; } 6 | public string Password { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Settings/AuditSettings.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.App.Settings; 2 | 3 | public class AuditSettings 4 | { 5 | public bool Enabled { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Settings/ProductDataLoggerJobSettings.cs: -------------------------------------------------------------------------------- 1 | using MccSoft.WebApi.Jobs; 2 | 3 | namespace MccSoft.TemplateApp.App.Settings; 4 | 5 | public class ProductDataLoggerJobSettings : HangFireJobSettings { } 6 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Setup/SetupAspNet.partial.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.App.Setup; 2 | 3 | /// 4 | /// This class will not be touched during pulling changes from Template. 5 | /// Consider putting your project-specific code here. 6 | /// 7 | public static partial class SetupAspNet 8 | { 9 | static partial void AddProjectSpecifics(WebApplicationBuilder builder) { } 10 | 11 | static partial void UseProjectSpecifics(WebApplication app) { } 12 | 13 | static partial void UseProjectSpecificEndpoints(WebApplication app) { } 14 | } 15 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Setup/SetupLocalization.partial.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.App.Setup; 2 | 3 | public static partial class SetupLocalization 4 | { 5 | static partial void AddProjectSpecifics(WebApplicationBuilder builder) { } 6 | 7 | static partial void UseProjectSpecifics(IApplicationBuilder app) { } 8 | } 9 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Setup/SetupSwagger.partial.cs: -------------------------------------------------------------------------------- 1 | using MccSoft.WebApi.Swagger; 2 | using NSwag.Generation.AspNetCore; 3 | 4 | namespace MccSoft.TemplateApp.App.Setup; 5 | 6 | public static partial class SetupSwagger 7 | { 8 | static partial void AddProjectSpecifics(WebApplicationBuilder builder) { } 9 | 10 | static partial void UseProjectSpecifics(IApplicationBuilder app) { } 11 | 12 | static partial void AdjustDefaultOpenApiDocument( 13 | AspNetCoreOpenApiDocumentGeneratorSettings options 14 | ) 15 | { 16 | options.SchemaSettings.TypeMappers.Add(new JsonObjectTypeMapper()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Views/Emails/Example/ExampleEmailModel.cs: -------------------------------------------------------------------------------- 1 | using MccSoft.Mailing.Models; 2 | 3 | namespace MccSoft.TemplateApp.App.Views.Emails.Example; 4 | 5 | public class ExampleEmailModel : EmailModelBase 6 | { 7 | public string Username { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Views/Emails/Example/ExampleEmailModel_content.cshtml: -------------------------------------------------------------------------------- 1 | @using MccSoft.TemplateApp.App.Views.Shared 2 | @model MccSoft.TemplateApp.App.Views.Emails.Example.ExampleEmailModel 3 | @{ 4 | Layout = "GeneralEmailTemplate"; 5 | } 6 | 7 | 8 | Greetings, @Model.Username! 9 | 10 | 11 | Welcome to our TemplateApp. 12 | 13 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Views/Emails/Example/ExampleEmailModel_subject.cshtml: -------------------------------------------------------------------------------- 1 | @model MccSoft.TemplateApp.App.Views.Emails.Example.ExampleEmailModel 2 | Welcome, @Model.Username! -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Views/Emails/Images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/webapi/src/MccSoft.TemplateApp.App/Views/Emails/Images/logo.png -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Views/Shared/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.App.Views.Shared; 2 | 3 | public class Constants 4 | { 5 | public const string DefaultParagraphStyle = 6 | "font-family: sans-serif; font-size: 17px; font-weight: normal; margin: 0; margin-bottom: 15px;"; 7 | } 8 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using MccSoft.TemplateApp.App.Areas.Identity 3 | 4 | 5 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 6 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/appsettings.Docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "Kestrel": { 3 | "EndPoints": { 4 | "Http": { 5 | "Url": "http://*:5000" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/appsettings.Test.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "System": "Warning", 7 | "Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware": "Warning", 8 | "Microsoft.AspNetCore.Cors": "Warning", 9 | "Microsoft.EntityFrameworkCore": "Information", 10 | "Microsoft.AspNetCore": "Warning" 11 | } 12 | }, 13 | "Documentation" : { 14 | "swaggerExportPath" : "../../../../../Docs/swagger.json", 15 | "databaseExportPath" : "../../../../../Docs/database.dgml" 16 | }, 17 | "Swagger" : { 18 | "Enabled" : true 19 | }, 20 | "Sentry" : { 21 | "Dsn" : "" 22 | }, 23 | "Audit" : { 24 | "Enabled" : false 25 | }, 26 | "Hangfire" : { 27 | "Disable" : true 28 | }, 29 | "General": { 30 | "SiteUrl": "https://localhost:5003" 31 | }, 32 | "TestApiEnabled": true 33 | } 34 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/wwwroot/resources/login-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mccsoft/backend-frontend-template/66da4884903ebb1fa01cc9118cf892684b423498/webapi/src/MccSoft.TemplateApp.App/wwwroot/resources/login-background.jpg -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.App/wwwroot/resources/password-eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Common/MccSoft.TemplateApp.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | latest 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Domain/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("MccSoft.TemplateApp.TestUtils")] 4 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Domain/BaseEntity.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using MccSoft.DomainHelpers.DomainEvents; 3 | 4 | namespace MccSoft.TemplateApp.Domain; 5 | 6 | public partial class BaseEntity : IDomainEventEntity 7 | { 8 | #region Domain Events 9 | 10 | private List? _domainEvents; 11 | public IReadOnlyList? DomainEvents => _domainEvents; 12 | 13 | public void AddEvent(IDomainEvent domainEvent, bool removeEventsOfSameType = false) 14 | { 15 | _domainEvents ??= new List(); 16 | if (removeEventsOfSameType) 17 | { 18 | _domainEvents.RemoveAll(x => x.GetType() == domainEvent.GetType()); 19 | } 20 | 21 | _domainEvents.Add(domainEvent); 22 | } 23 | 24 | public void ClearDomainEvents() 25 | { 26 | _domainEvents?.Clear(); 27 | } 28 | #endregion 29 | } 30 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Domain/BaseEntity.partial.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.Domain; 2 | 3 | public partial class BaseEntity { } 4 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Domain/BaseTenantEntity.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.Domain; 2 | 3 | public class BaseTenantEntity : BaseEntity, ITenantEntity 4 | { 5 | public int TenantId { get; private set; } 6 | public Tenant Tenant { get; } = null!; 7 | 8 | public void SetTenantIdUnsafe(int tenantId) 9 | { 10 | TenantId = tenantId; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Domain/ITenantEntity.cs: -------------------------------------------------------------------------------- 1 | public interface ITenantEntity 2 | { 3 | public int TenantId { get; } 4 | 5 | public Tenant Tenant { get; } 6 | 7 | /// 8 | /// Isn't meant to be called from Application code. 9 | /// Should be called by infrastructure (e.g. SaveChanges interceptor) 10 | /// 11 | void SetTenantIdUnsafe(int tenantId); 12 | } 13 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Domain/ProductType.cs: -------------------------------------------------------------------------------- 1 | namespace MccSoft.TemplateApp.Domain; 2 | 3 | public enum ProductType 4 | { 5 | Undefined, 6 | Auto, 7 | Electronic, 8 | Other, 9 | } 10 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Domain/Tenant.cs: -------------------------------------------------------------------------------- 1 | public class Tenant 2 | { 3 | public int Id { get; private set; } 4 | } 5 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Http/IBaseClient.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace MccSoft.TemplateApp.Http; 4 | 5 | public interface IBaseClient 6 | { 7 | public JsonSerializerOptions JsonSerializerSettings { get; } 8 | } 9 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Http/SignInException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MccSoft.TemplateApp.Http; 4 | 5 | public class SignInException : Exception 6 | { 7 | public SignInException(string stringContent) : base(stringContent) { } 8 | } 9 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Http/template/Client.Class.Annotations.liquid: -------------------------------------------------------------------------------- 1 | {% template Client.Class.GetOperations.Dto %} -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Http/template/Client.Class.Body.liquid: -------------------------------------------------------------------------------- 1 | {% template Client.Class.GetOperations.Methods %} -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Http/template/Client.Class.GetOperations.Documentation.liquid: -------------------------------------------------------------------------------- 1 | {%- if operation.HasSummary -%} 2 | /// {{ operation.Summary | csharpdocs }} 3 | {%- endif -%} 4 | {%- if operation.HasResultDescription -%} 5 | /// {{ operation.ResultDescription | csharpdocs }} 6 | {%- endif -%} 7 | /// A server side error occurred. 8 | {%- for exception in operation.ExceptionDescriptions -%} 9 | /// {{ exception.Description }} 10 | {%- endfor -%} 11 | {%- if operation.IsDeprecated -%} 12 | [System.Obsolete] 13 | {%- endif -%} 14 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Http/template/Client.Class.GetOperations.Dto.liquid: -------------------------------------------------------------------------------- 1 | {%- for operation in Operations -%} 2 | {%- if operation.HttpMethodUpper == 'Get' -%} 3 | {%- if operation.Parameters.size > 0 -%} 4 | {{operation.MethodAccessModifier}} partial class {{ Class }}{{ operation.ActualOperationName }}ParametersDto 5 | { 6 | {%- for parameter in operation.Parameters %} 7 | {%- if parameter.HasDescription -%} 8 | /// 9 | /// {{ parameter.Description | csharpdocs }} 10 | /// 11 | {%- endif -%} 12 | public {{ parameter.Type }} {{ parameter.VariableName }} { get; set; } 13 | {%- endfor -%} 14 | } 15 | 16 | {%- endif -%} 17 | {%- endif -%} 18 | {%- endfor %} 19 | -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Http/template/Client.Interface.Body.liquid: -------------------------------------------------------------------------------- 1 | {% template Client.Interface.GetOperations.Methods %} -------------------------------------------------------------------------------- /webapi/src/MccSoft.TemplateApp.Persistence/Migrations/20220727182715_SetUpOwnedEntities.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace MccSoft.TemplateApp.Persistence.Migrations 6 | { 7 | public partial class SetUpOwnedEntities : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.AddColumn( 12 | name: "OwnerId", 13 | table: "Products", 14 | type: "text", 15 | nullable: true); 16 | } 17 | 18 | protected override void Down(MigrationBuilder migrationBuilder) 19 | { 20 | migrationBuilder.DropColumn( 21 | name: "OwnerId", 22 | table: "Products"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /webapi/tests/MccSoft.TemplateApp.App.Tests/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: CollectionBehavior(MaxParallelThreads = 8)] 2 | -------------------------------------------------------------------------------- /webapi/tests/MccSoft.TemplateApp.App.Tests/SignUrlHelperTests.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using MccSoft.WebApi.SignedUrl; 3 | 4 | namespace MccSoft.TemplateApp.App.Tests; 5 | 6 | public class SignUrlHelperTests : AppServiceTestBase 7 | { 8 | public SignUrlHelperTests(ITestOutputHelper outputHelper) : base(outputHelper, null) 9 | { 10 | Sut = CreateService(x => x.AddSignUrl("fDmp1K2YveBbfDmpfDmp1K2YveBbfDmp")); 11 | } 12 | 13 | public SignUrlHelper Sut { get; set; } 14 | 15 | [Fact] 16 | public void GenerateSignedUrl() 17 | { 18 | _userAccessorMock.Setup(x => x.GetUserId()).Returns("5"); 19 | 20 | var signature = Sut.GenerateSignature(); 21 | var result = Sut.IsSignatureValid(signature, out var claims); 22 | result.Should().BeTrue(); 23 | claims.FindFirstValue("id").Should().Be("5"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /webapi/tests/MccSoft.TemplateApp.App.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; 2 | global using Xunit.Abstractions; 3 | global using FluentAssertions; 4 | -------------------------------------------------------------------------------- /webapi/tests/MccSoft.TemplateApp.ComponentTests/Helpers/IdentityExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Security.Claims; 3 | using IdentityModel; 4 | 5 | namespace MccSoft.TemplateApp.ComponentTests.Helpers; 6 | 7 | public static class IdentityExtensions 8 | { 9 | public static ClaimsIdentity SetUserIdClaim(this ClaimsIdentity identity, string value) 10 | { 11 | return identity.SetClaim(JwtClaimTypes.Id, value); 12 | } 13 | 14 | private static ClaimsIdentity SetClaim(this ClaimsIdentity identity, string type, string value) 15 | { 16 | var existingClaim = identity.Claims.FirstOrDefault(x => x.Type == type); 17 | if (existingClaim != null) 18 | { 19 | identity.RemoveClaim(existingClaim); 20 | } 21 | 22 | identity.AddClaim(new Claim(type, value)); 23 | 24 | return identity; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /webapi/tests/MccSoft.TemplateApp.ComponentTests/Infrastructure/ComponentTestFixture.partial.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | 3 | namespace MccSoft.TemplateApp.ComponentTests.Infrastructure; 4 | 5 | public partial class ComponentTestFixture 6 | { 7 | void ConfigureWebHostProjectSpecific(IWebHostBuilder builder) { } 8 | } 9 | -------------------------------------------------------------------------------- /webapi/tests/MccSoft.TemplateApp.ComponentTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | [assembly: CollectionBehavior(MaxParallelThreads = 8)] 2 | -------------------------------------------------------------------------------- /webapi/tests/MccSoft.TemplateApp.ComponentTests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; 2 | global using Xunit.Abstractions; 3 | global using FluentAssertions; 4 | -------------------------------------------------------------------------------- /webapi/tests/MccSoft.TemplateApp.Domain.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; 2 | global using Xunit.Abstractions; 3 | global using FluentAssertions; 4 | --------------------------------------------------------------------------------
You have successfully logged out of the application.
13 | Request ID: @Model.RequestId 14 |
@Model.RequestId
19 | Swapping to Development environment will display more detailed information about the error that occurred. 20 |
22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 23 |
8 | Greetings, @Model.Username! 9 |
11 | Welcome to our TemplateApp. 12 |