├── .dockerignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── backend ├── .eslintrc ├── .prettierrc ├── .vscode │ ├── launch.json │ └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── nest-cli.json ├── nodemon-debug.json ├── nodemon.json ├── ormconfig.ts ├── package-lock.json ├── package.json ├── src │ ├── app.module.ts │ ├── boilerplate.polyfill.ts │ ├── common │ │ ├── abstract.entity.ts │ │ ├── constants │ │ │ ├── order.ts │ │ │ └── role-type.ts │ │ └── dto │ │ │ ├── AbstractDto.ts │ │ │ ├── AbstractSearchDto.ts │ │ │ ├── PageMetaDto.ts │ │ │ └── PageOptionsDto.ts │ ├── decorators │ │ ├── auth-user.decorator.ts │ │ ├── roles.decorator.ts │ │ ├── swagger.schema.ts │ │ ├── transforms.decorator.ts │ │ └── validators.decorator.ts │ ├── exceptions │ │ ├── file-not-image.exception.ts │ │ └── user-not-found.exception.ts │ ├── filters │ │ ├── bad-request.filter.ts │ │ ├── constraint-errors.ts │ │ └── query-failed.filter.ts │ ├── guards │ │ ├── auth.guard.ts │ │ └── roles.guard.ts │ ├── i18n │ │ ├── en │ │ │ └── translations.json │ │ ├── et │ │ │ └── translations.json │ │ └── ru │ │ │ └── translations.json │ ├── interceptors │ │ └── auth-user-interceptor.service.ts │ ├── interfaces │ │ ├── IAwsConfig.ts │ │ └── IFile.ts │ ├── main.hmr.ts │ ├── main.ts │ ├── middlewares │ │ ├── context.middelware.ts │ │ └── index.ts │ ├── migrations │ │ └── 1554465583933-create_users_table.ts │ ├── modules │ │ └── users │ │ │ ├── application │ │ │ └── user │ │ │ │ └── create-user │ │ │ │ ├── create-user.command.ts │ │ │ │ └── create-user.handler.ts │ │ │ ├── domain │ │ │ ├── invitation │ │ │ │ └── invitation.aggregate.ts │ │ │ └── user │ │ │ │ ├── email-verification-token.vo.ts │ │ │ │ ├── events │ │ │ │ ├── email-verified.event.ts │ │ │ │ ├── index.ts │ │ │ │ ├── user-created.event.ts │ │ │ │ ├── user-deleted.event.ts │ │ │ │ └── user-logged-in.event.ts │ │ │ │ ├── index.ts │ │ │ │ ├── jwt.ts │ │ │ │ ├── user-email.vo.ts │ │ │ │ ├── user-id.entity.ts │ │ │ │ ├── user-password.vo.ts │ │ │ │ ├── user.aggregate.ts │ │ │ │ └── user.repository.ts │ │ │ ├── index.ts │ │ │ ├── infra │ │ │ ├── database │ │ │ │ ├── models │ │ │ │ │ ├── index.ts │ │ │ │ │ └── user.schema.ts │ │ │ │ └── repositories │ │ │ │ │ ├── index.ts │ │ │ │ │ └── user.repository.ts │ │ │ ├── http │ │ │ │ └── user │ │ │ │ │ ├── create-user.dto.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── user.controller.ts │ │ │ └── mappers │ │ │ │ └── user.mapper.ts │ │ │ └── users.module.ts │ ├── providers │ │ ├── context.service.ts │ │ └── utils.service.ts │ ├── shared │ │ ├── core │ │ │ ├── app-error.ts │ │ │ ├── guard.ts │ │ │ ├── index.ts │ │ │ ├── result.ts │ │ │ ├── use-case-error.ts │ │ │ ├── use-case.ts │ │ │ └── with-changes.ts │ │ ├── domain │ │ │ ├── aggregate-root.ts │ │ │ ├── business-rule.ts │ │ │ ├── domain-event.ts │ │ │ ├── domain-service.ts │ │ │ ├── entity.ts │ │ │ ├── identifier.ts │ │ │ ├── index.ts │ │ │ ├── unique-entity-id.ts │ │ │ ├── value-object.ts │ │ │ └── watched-list.ts │ │ └── infra │ │ │ ├── config-service.ts │ │ │ ├── database │ │ │ ├── entity-base.ts │ │ │ ├── index.ts │ │ │ └── snake-naming.ts │ │ │ ├── index.ts │ │ │ ├── mapper.ts │ │ │ └── redis │ │ │ └── abstract-redis-client.ts │ └── viveo-swagger.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json ├── tsconfig.eslint.json ├── tsconfig.json ├── webpack.config.js └── yarn.lock ├── cli ├── .eslintrc ├── .prettierrc ├── README.md ├── cli.ts ├── package.json ├── src │ ├── cli.ts │ ├── commands │ │ ├── MigrationCreate.ts │ │ ├── MigrationCustom.ts │ │ ├── MigrationsDown.ts │ │ ├── MigrationsList.ts │ │ ├── MigrationsPrune.ts │ │ ├── MigrationsSync.ts │ │ ├── MigrationsUp.ts │ │ └── hello.ts │ ├── nestjsBffAppContainer.ts │ └── types │ │ └── node.d.ts ├── tests │ ├── .eslintrc │ ├── commands │ │ ├── __snapshots__ │ │ │ └── hello.test.ts.snap │ │ └── hello.test.ts │ └── utils │ │ └── index.ts ├── tsconfig.json └── tslint.json ├── docker-compose.yml ├── docs ├── ATTRIBUTIONS.md ├── images │ ├── NestJS-BFF-ArchitectureOverview.png │ ├── dashboard.jpg │ └── login.jpg └── licences │ ├── MIT LICENCE from contributing-template │ ├── MIT LICENSE from angular-6-jwt-authentication-example │ ├── MIT LICENSE from cli-typescript-boilerplate │ ├── MIT LICENSE from core-ui │ └── MIT LICENSE from nestjs ├── frontend ├── .editorconfig ├── .gitignore ├── .vscode │ └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── README.md ├── angular.json ├── e2e │ ├── app.e2e-spec.ts │ ├── app.po.ts │ └── tsconfig.e2e.json ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── proxy.conf.json ├── src │ ├── app │ │ ├── _nav.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── app.routing.ts │ │ ├── containers │ │ │ ├── default-layout │ │ │ │ ├── default-layout.component.html │ │ │ │ ├── default-layout.component.ts │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── guards │ │ │ ├── auth.guard.ts │ │ │ └── index.ts │ │ ├── interceptors │ │ │ ├── error.interceptor.ts │ │ │ ├── index.ts │ │ │ └── jwt.interceptor.ts │ │ ├── services │ │ │ ├── authentication.service.ts │ │ │ └── index.ts │ │ └── views │ │ │ ├── base │ │ │ ├── base-routing.module.ts │ │ │ ├── base.module.ts │ │ │ ├── cards.component.html │ │ │ ├── cards.component.ts │ │ │ ├── carousels.component.html │ │ │ ├── carousels.component.ts │ │ │ ├── collapses.component.html │ │ │ ├── collapses.component.ts │ │ │ ├── forms.component.html │ │ │ ├── forms.component.ts │ │ │ ├── paginations.component.html │ │ │ ├── paginations.component.ts │ │ │ ├── popovers.component.html │ │ │ ├── popovers.component.ts │ │ │ ├── progress.component.html │ │ │ ├── progress.component.ts │ │ │ ├── switches.component.html │ │ │ ├── switches.component.ts │ │ │ ├── tables.component.html │ │ │ ├── tables.component.ts │ │ │ ├── tabs.component.html │ │ │ ├── tabs.component.ts │ │ │ ├── tooltips.component.html │ │ │ └── tooltips.component.ts │ │ │ ├── buttons │ │ │ ├── brand-buttons.component.html │ │ │ ├── brand-buttons.component.ts │ │ │ ├── buttons-routing.module.ts │ │ │ ├── buttons.component.html │ │ │ ├── buttons.component.ts │ │ │ ├── buttons.module.ts │ │ │ ├── dropdowns.component.css │ │ │ ├── dropdowns.component.html │ │ │ └── dropdowns.component.ts │ │ │ ├── chartjs │ │ │ ├── chartjs-routing.module.ts │ │ │ ├── chartjs.component.html │ │ │ ├── chartjs.component.ts │ │ │ └── chartjs.module.ts │ │ │ ├── dashboard │ │ │ ├── dashboard-routing.module.ts │ │ │ ├── dashboard.component.html │ │ │ ├── dashboard.component.ts │ │ │ └── dashboard.module.ts │ │ │ ├── error │ │ │ ├── 404.component.html │ │ │ ├── 404.component.ts │ │ │ ├── 500.component.html │ │ │ └── 500.component.ts │ │ │ ├── icons │ │ │ ├── coreui-icons.component.html │ │ │ ├── coreui-icons.component.ts │ │ │ ├── flags.component.html │ │ │ ├── flags.component.ts │ │ │ ├── font-awesome.component.html │ │ │ ├── font-awesome.component.ts │ │ │ ├── icons-routing.module.ts │ │ │ ├── icons.module.ts │ │ │ ├── simple-line-icons.component.html │ │ │ └── simple-line-icons.component.ts │ │ │ ├── login │ │ │ ├── login.component.html │ │ │ └── login.component.ts │ │ │ ├── notifications │ │ │ ├── alerts.component.html │ │ │ ├── alerts.component.ts │ │ │ ├── badges.component.html │ │ │ ├── badges.component.ts │ │ │ ├── modals.component.html │ │ │ ├── modals.component.ts │ │ │ ├── notifications-routing.module.ts │ │ │ └── notifications.module.ts │ │ │ ├── register │ │ │ ├── register.component.html │ │ │ └── register.component.ts │ │ │ ├── reminders │ │ │ ├── reminder-data.service.spec.ts │ │ │ ├── reminder-data.service.ts │ │ │ └── reminder.spec.ts │ │ │ ├── theme │ │ │ ├── colors.component.html │ │ │ ├── colors.component.ts │ │ │ ├── theme-routing.module.ts │ │ │ ├── theme.module.ts │ │ │ ├── typography.component.html │ │ │ └── typography.component.ts │ │ │ └── widgets │ │ │ ├── widgets-routing.module.ts │ │ │ ├── widgets.component.html │ │ │ ├── widgets.component.ts │ │ │ └── widgets.module.ts │ ├── assets │ │ ├── .gitkeep │ │ ├── favicon.ico │ │ └── img │ │ │ ├── avatars │ │ │ ├── 1.jpg │ │ │ ├── 2.jpg │ │ │ ├── 3.jpg │ │ │ ├── 4.jpg │ │ │ ├── 5.jpg │ │ │ ├── 6.jpg │ │ │ ├── 7.jpg │ │ │ └── 8.jpg │ │ │ └── brand │ │ │ ├── icon.svg │ │ │ └── logo.svg │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── scss │ │ ├── _custom.scss │ │ ├── _variables.scss │ │ ├── style.scss │ │ └── vendors │ │ │ ├── _variables.scss │ │ │ └── chart.js │ │ │ └── chart.scss │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── typings.d.ts ├── tsconfig.json └── tslint.json ├── global ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── package.json ├── src │ ├── application │ │ └── reminder-orchestration │ │ │ └── send-reminder-to-archive.command.ts │ └── domain │ │ ├── reminder-archive │ │ └── reminder-archive.entity.ts │ │ └── reminder │ │ └── reminder.entity.ts ├── tsconfig.json └── tslint.json ├── lerna.json ├── package.json ├── pkg-bff ├── .gitignore ├── backend │ ├── .gitignore │ ├── .npmignore │ ├── .vscode │ │ ├── launch.json │ │ └── settings.json │ ├── LICENSE │ ├── README.md │ ├── e2e │ │ ├── auth │ │ │ ├── auth-e2e.module.ts │ │ │ └── auth.e2e-spec.ts │ │ ├── core │ │ │ ├── global-setup-auth.ts │ │ │ ├── global-setup-base.ts │ │ │ ├── global-setup-db.ts │ │ │ ├── global-setup-hook.js │ │ │ ├── global-tear-down-base.ts │ │ │ ├── global-tear-down-hook.js │ │ │ ├── test-object-literals.constants.ts │ │ │ └── test-object.utils.ts │ │ ├── jest-e2e.json │ │ ├── trace │ │ │ └── trace.e2e-spec.ts │ │ └── tsconfig.json │ ├── jest.json │ ├── package.json │ ├── src │ │ ├── application │ │ │ ├── core │ │ │ │ └── core.module.ts │ │ │ ├── organization-orchestration │ │ │ │ ├── organization-orchestration.module.ts │ │ │ │ └── organization-orchestration.service.ts │ │ │ └── user-auth │ │ │ │ ├── user-auth.constants.ts │ │ │ │ ├── user-auth.module.ts │ │ │ │ └── user-auth.service.ts │ │ ├── config │ │ │ ├── keys │ │ │ │ ├── jwt.private-key.test.pem │ │ │ │ └── jwt.public-key.test.pem │ │ │ ├── nestjs-bff.config.ts │ │ │ ├── nestjs.config.env.interface.ts │ │ │ └── nestjs.config.test.ts │ │ ├── domain │ │ │ ├── _foo │ │ │ │ ├── foo.constants.ts │ │ │ │ ├── foo.module.ts │ │ │ │ ├── model │ │ │ │ │ ├── foo.entity.ts │ │ │ │ │ ├── foo.model.ts │ │ │ │ │ └── foo.schema.ts │ │ │ │ └── repo │ │ │ │ │ └── foo.repo.ts │ │ │ ├── access-permissions │ │ │ │ ├── access-permissions.constants.ts │ │ │ │ ├── access-permissions.module.ts │ │ │ │ ├── model │ │ │ │ │ ├── access-permissions.entity.ts │ │ │ │ │ ├── access-permissions.model.ts │ │ │ │ │ └── access-permissions.schema.ts │ │ │ │ └── repo │ │ │ │ │ ├── access-permissions-entity.authcheck.ts │ │ │ │ │ └── access-permissions.repo.ts │ │ │ ├── authentication │ │ │ │ ├── authentication.constants.ts │ │ │ │ ├── authentication.module.ts │ │ │ │ ├── model │ │ │ │ │ ├── authentication.entity.ts │ │ │ │ │ ├── authentication.model.ts │ │ │ │ │ └── authentication.schema.ts │ │ │ │ ├── repo │ │ │ │ │ └── authentication.repo.ts │ │ │ │ ├── social │ │ │ │ │ ├── facebook-authentication.service.ts │ │ │ │ │ ├── facebook-authenticationservice.ts │ │ │ │ │ ├── facebook-client.service.ts │ │ │ │ │ ├── facebook-profile..service.ts │ │ │ │ │ ├── facebook-profile.interface.ts │ │ │ │ │ └── i-oauth-access-token.ts │ │ │ │ ├── utils │ │ │ │ │ └── encryption.util.ts │ │ │ │ └── validators │ │ │ │ │ ├── depricated-authentication-create.validator.ts │ │ │ │ │ └── messages.constants.ts │ │ │ ├── core │ │ │ │ ├── core.constants.ts │ │ │ │ ├── core.module.ts │ │ │ │ ├── repo │ │ │ │ │ ├── base.repo.spec.ts │ │ │ │ │ └── base.repo.ts │ │ │ │ └── validators │ │ │ │ │ ├── class-validator.spec.ts │ │ │ │ │ └── class-validator.ts │ │ │ ├── organization │ │ │ │ ├── model │ │ │ │ │ ├── organization.model.ts │ │ │ │ │ └── organization.schema.ts │ │ │ │ ├── organization.constants.ts │ │ │ │ ├── organization.module.ts │ │ │ │ └── repo │ │ │ │ │ └── organization.repo.ts │ │ │ └── user │ │ │ │ ├── model │ │ │ │ ├── user.model.ts │ │ │ │ └── user.schema.ts │ │ │ │ ├── repo │ │ │ │ └── user.repo.ts │ │ │ │ ├── user.constants.ts │ │ │ │ └── user.module.ts │ │ ├── host │ │ │ └── http │ │ │ │ ├── application-service-host │ │ │ │ ├── auth │ │ │ │ │ ├── auth.controller.ts │ │ │ │ │ └── auth.module.ts │ │ │ │ ├── organization-orchestration │ │ │ │ │ ├── organization-orchestration.controller.ts │ │ │ │ │ └── organization-orchestration.module.ts │ │ │ │ └── web-app-health-check │ │ │ │ │ ├── web-app-health-check.controller.spec.ts │ │ │ │ │ ├── web-app-health-check.controller.ts │ │ │ │ │ ├── web-app-health-check.module.ts │ │ │ │ │ └── web-app-health-check.service.ts │ │ │ │ ├── core │ │ │ │ ├── controller │ │ │ │ │ └── domain-controller-base.ts │ │ │ │ ├── core.constants.ts │ │ │ │ ├── core.module.ts │ │ │ │ ├── decorators │ │ │ │ │ ├── authorization.decorator.ts │ │ │ │ │ └── roles.decorator.ts │ │ │ │ ├── exceptions │ │ │ │ │ ├── http-exception.filter.ts │ │ │ │ │ └── server.exception.ts │ │ │ │ ├── guards │ │ │ │ │ ├── authorization.guard.ts │ │ │ │ │ └── roles.guard.ts │ │ │ │ ├── interceptors │ │ │ │ │ ├── timeout.interceptor.ts │ │ │ │ │ └── transform.interceptor.ts │ │ │ │ ├── jwt │ │ │ │ │ ├── i-jwt-payload.ts │ │ │ │ │ ├── jwt-token.service.spec.ts │ │ │ │ │ └── jwt-token.service.ts │ │ │ │ ├── middleware │ │ │ │ │ └── attach-authentication.middleware.ts │ │ │ │ ├── types │ │ │ │ │ └── bff-request.contract.ts │ │ │ │ └── utils │ │ │ │ │ └── core.utils.ts │ │ │ │ ├── web-app-base.module.ts │ │ │ │ └── web-app-helper.ts │ │ ├── shared │ │ │ ├── app │ │ │ │ ├── app.shared.constants.ts │ │ │ │ └── app.shared.module.ts │ │ │ ├── authchecks │ │ │ │ ├── always-false.authcheck.ts │ │ │ │ ├── always-true.authcheck.ts │ │ │ │ ├── authcheck.contract.ts │ │ │ │ ├── authcheck.utils.ts │ │ │ │ ├── authorization-params.ts │ │ │ │ ├── crud-operations.enum.ts │ │ │ │ ├── org-access.authcheck.ts │ │ │ │ ├── org-roles.authcheck.ts │ │ │ │ ├── org.authcheck.spec.ts │ │ │ │ ├── role.authcheck.ts │ │ │ │ ├── scoped-access.authcheck.ts │ │ │ │ ├── scoped-data.ts │ │ │ │ ├── scoped-entity.authcheck.spec.ts │ │ │ │ ├── scoped-entity.authcheck.ts │ │ │ │ ├── user-access.authcheck.spec.ts │ │ │ │ └── user-access.authcheck.ts │ │ │ ├── caching │ │ │ │ ├── cache-store.shared.ts │ │ │ │ ├── caching.shared.constants.ts │ │ │ │ ├── caching.shared.module.ts │ │ │ │ ├── caching.utils.spec.ts │ │ │ │ └── caching.utils.ts │ │ │ ├── database │ │ │ │ └── mongo │ │ │ │ │ ├── mongo.shared.constants.ts │ │ │ │ │ └── mongo.shared.module.ts │ │ │ ├── exceptions │ │ │ │ ├── app.exception.ts │ │ │ │ ├── authorization-check.exception.ts │ │ │ │ ├── authorization.exception.ts │ │ │ │ └── validation.exception.ts │ │ │ ├── logging │ │ │ │ ├── console-logger.shared.service.ts │ │ │ │ ├── log-levels.const.ts │ │ │ │ ├── logger-winston.shared.service.ts │ │ │ │ ├── logger.middleware.ts │ │ │ │ ├── logger.shared.service.ts │ │ │ │ ├── logging.constants.ts │ │ │ │ ├── logging.interceptor.ts │ │ │ │ └── logging.shared.module.ts │ │ │ ├── migrations │ │ │ │ ├── interfaces │ │ │ │ │ └── migration.interface.ts │ │ │ │ ├── migrations.shared.constants.ts │ │ │ │ ├── migrations.shared.module.ts │ │ │ │ ├── migrations.shared.service.ts │ │ │ │ └── schemas │ │ │ │ │ └── migration.schema.ts │ │ │ ├── testing │ │ │ │ ├── jest.spec.ts │ │ │ │ └── test-literals.constants.ts │ │ │ └── utils │ │ │ │ ├── hash.utils.ts │ │ │ │ └── key.shared.utils.ts │ │ ├── tsconfig.json │ │ └── types │ │ │ ├── node.d.ts │ │ │ └── typings.d.ts │ ├── templates │ │ └── migrationTemplate.ts │ ├── tsconfig.json │ └── tslint.json ├── global-contracts │ ├── .gitignore │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── application │ │ │ └── auth │ │ │ │ ├── create-organization-member.command.ts │ │ │ │ ├── local-authenticate.command.ts │ │ │ │ ├── local-register.command.ts │ │ │ │ └── promote-to-group-admin.command.ts │ │ ├── domain │ │ │ ├── access-permissions │ │ │ │ └── access-permissions.contract.ts │ │ │ ├── core │ │ │ │ ├── base.entity.ts │ │ │ │ ├── core.constants.ts │ │ │ │ ├── entity.interface.ts │ │ │ │ ├── org-scoped.entity.ts │ │ │ │ └── user-and-org-scoped.entity.ts │ │ │ ├── organization │ │ │ │ └── organization.entity.ts │ │ │ └── user │ │ │ │ └── user.entity.ts │ │ └── shared │ │ │ ├── authentication │ │ │ └── authentication-token.interface.ts │ │ │ └── authorization │ │ │ └── roles.constants.ts │ ├── tsconfig.json │ └── tslint.json ├── global-utils-dev │ ├── .gitignore │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── jest.json │ ├── package.json │ ├── src │ │ ├── testing.utils.spec.ts │ │ ├── testing.utils.ts │ │ └── type.util.ts │ ├── tsconfig.json │ └── tslint.json ├── global-utils-prod │ ├── .gitignore │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── jest.json │ ├── package.json │ ├── src │ │ └── object.util.ts │ ├── tsconfig.json │ └── tslint.json ├── tsconfig-base.json └── tsconfig.json ├── tsconfig-all-ref.json ├── tsconfig-pkg-ref.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .github 3 | .vscode 4 | coverage 5 | 6 | # OS generated files # 7 | .DS_Store 8 | ehthumbs.db 9 | Icon? 10 | Thumbs.db 11 | 12 | # Node Files # 13 | node_modules 14 | npm-debug.log 15 | npm-debug.log.* 16 | 17 | # Typing # 18 | src/typings/tsd 19 | typings 20 | tsd_typings 21 | 22 | # Dist # 23 | dist 24 | .awcache 25 | .webpack.json 26 | compiled 27 | dll 28 | 29 | # IDE # 30 | .idea 31 | *.swp 32 | 33 | 34 | # Angular # 35 | *.ngfactory.ts 36 | *.css.shim.ts 37 | *.ngsummary.json 38 | *.shim.ngstyle.ts 39 | 40 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/recommended', 10 | 'prettier/@typescript-eslint', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | rules: { 19 | '@typescript-eslint/interface-name-prefix': 'off', 20 | '@typescript-eslint/explicit-function-return-type': 'off', 21 | '@typescript-eslint/explicit-module-boundary-types': 'off', 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vsicons.presets.nestjs": true 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 ahrnee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /backend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "trailingComma": "all", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /backend/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.userWords": [ 3 | "Injectable", 4 | "chartjs", 5 | "firebase", 6 | "nestjs", 7 | "reactstrap", 8 | "redbox", 9 | "superset", 10 | "tsnode", 11 | "workspaces", 12 | "signin", 13 | "signup" 14 | ], 15 | "typescript.updateImportsOnFileMove.enabled": "prompt", 16 | "typescript.preferences.importModuleSpecifier": "auto", 17 | "tslint.autoFixOnSave": true, 18 | "editor.formatOnSave": true, 19 | "prettier.singleQuote": true, 20 | "prettier.printWidth": 180, 21 | "typescript.preferences.quoteStyle": "single", 22 | "javascript.preferences.quoteStyle": "single", 23 | "cSpell.words": [ 24 | "CSRF", 25 | "Repo", 26 | "ahrnee", 27 | "alwaystrue", 28 | "appsecret", 29 | "authorizationcheck", 30 | "authorizationcheck", 31 | "backend", 32 | "fastify", 33 | "formbody", 34 | "maxfile", 35 | "maxsize", 36 | "microservices", 37 | "prestart", 38 | "supersecretpassword", 39 | "validators", 40 | "webapp", 41 | "websockets", 42 | "winstonlogger" 43 | ], 44 | "html.format.wrapLineLength": 180, 45 | "debug.node.autoAttach": "off", 46 | "jest.pathToConfig": "jest.json" 47 | } 48 | -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:dubnium AS dist 2 | COPY package.json yarn.lock ./ 3 | 4 | RUN yarn install 5 | 6 | COPY . ./ 7 | 8 | RUN yarn build 9 | 10 | FROM node:dubnium AS node_modules 11 | COPY package.json yarn.lock ./ 12 | 13 | RUN yarn install --prod 14 | 15 | FROM node:dubnium 16 | 17 | ARG PORT=3000 18 | 19 | RUN mkdir -p /usr/src/app 20 | 21 | WORKDIR /usr/src/app 22 | 23 | COPY --from=dist dist /usr/src/app/dist 24 | COPY --from=node_modules node_modules /usr/src/app/node_modules 25 | 26 | COPY . /usr/src/app 27 | 28 | EXPOSE $PORT 29 | 30 | CMD [ "yarn", "start:prod" ] 31 | -------------------------------------------------------------------------------- /backend/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Narek Hakobyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/backend/README.md -------------------------------------------------------------------------------- /backend/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "language": "ts", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "plugins": ["@nestjs/swagger/plugin"], 7 | "assets": ["i18n/**/*"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/nodemon-debug.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "src" 4 | ], 5 | "ext": "ts", 6 | "ignore": [ 7 | "src/**/*.spec.ts" 8 | ], 9 | "exec": "cross-env TS_NODE_CACHE=false node --inspect -r ts-node/register src/main.ts" 10 | } 11 | -------------------------------------------------------------------------------- /backend/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "ts-node src/main.ts" 6 | } 7 | -------------------------------------------------------------------------------- /backend/ormconfig.ts: -------------------------------------------------------------------------------- 1 | import './src/boilerplate.polyfill'; 2 | 3 | import * as dotenv from 'dotenv'; 4 | 5 | import { SnakeNamingStrategy } from './src/snake-naming.strategy'; 6 | 7 | if (!(module).hot /* for webpack HMR */) { 8 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'; 9 | } 10 | 11 | dotenv.config({ 12 | path: `.${process.env.NODE_ENV}.env`, 13 | }); 14 | 15 | // Replace \\n with \n to support multiline strings in AWS 16 | for (const envName of Object.keys(process.env)) { 17 | process.env[envName] = process.env[envName].replace(/\\n/g, '\n'); 18 | } 19 | 20 | module.exports = { 21 | type: 'postgres', 22 | host: process.env.DB_HOST, 23 | port: +process.env.DB_PORT, 24 | username: process.env.DB_USERNAME, 25 | password: process.env.DB_PASSWORD, 26 | database: process.env.DB_DATABASE, 27 | namingStrategy: new SnakeNamingStrategy(), 28 | entities: ['src/modules/**/*.entity{.ts,.js}'], 29 | migrations: ['src/migrations/*{.ts,.js}'], 30 | }; 31 | -------------------------------------------------------------------------------- /backend/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import './boilerplate.polyfill'; 2 | 3 | import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; 4 | import { I18nJsonParser, I18nModule } from 'nestjs-i18n'; 5 | import * as path from 'path'; 6 | 7 | import { contextMiddleware } from './middlewares'; 8 | import { UserModule } from './modules/users'; 9 | import { ConfigService } from 'shared/infra'; 10 | 11 | @Module({ 12 | imports: [ 13 | UserModule, 14 | I18nModule.forRootAsync({ 15 | useFactory: (configService: ConfigService) => ({ 16 | fallbackLanguage: configService.fallbackLanguage, 17 | parserOptions: { 18 | path: path.join(__dirname, '/i18n/'), 19 | watch: configService.isDevelopment, 20 | }, 21 | }), 22 | parser: I18nJsonParser, 23 | inject: [ConfigService], 24 | }), 25 | ], 26 | }) 27 | export class AppModule implements NestModule { 28 | configure(consumer: MiddlewareConsumer): MiddlewareConsumer | void { 29 | consumer.apply(contextMiddleware).forRoutes('*'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/src/common/abstract.entity.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | CreateDateColumn, 5 | PrimaryGeneratedColumn, 6 | UpdateDateColumn, 7 | } from 'typeorm'; 8 | 9 | import { UtilsService } from '../providers/utils.service'; 10 | import { AbstractDto } from './dto/AbstractDto'; 11 | 12 | export abstract class AbstractEntity { 13 | @PrimaryGeneratedColumn('uuid') 14 | id: string; 15 | 16 | @CreateDateColumn({ 17 | type: 'timestamp without time zone', 18 | name: 'created_at', 19 | }) 20 | createdAt: Date; 21 | 22 | @UpdateDateColumn({ 23 | type: 'timestamp without time zone', 24 | name: 'updated_at', 25 | }) 26 | updatedAt: Date; 27 | 28 | abstract dtoClass: new (entity: AbstractEntity, options?: any) => T; 29 | 30 | toDto(options?: any) { 31 | return UtilsService.toDto(this.dtoClass, this, options); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /backend/src/common/constants/order.ts: -------------------------------------------------------------------------------- 1 | export enum Order { 2 | ASC = 'ASC', 3 | DESC = 'DESC', 4 | } 5 | -------------------------------------------------------------------------------- /backend/src/common/constants/role-type.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export enum RoleType { 4 | USER = 'USER', 5 | ADMIN = 'ADMIN', 6 | } 7 | -------------------------------------------------------------------------------- /backend/src/common/dto/AbstractDto.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { AbstractEntity } from '../abstract.entity'; 4 | 5 | export class AbstractDto { 6 | id: string; 7 | createdAt: Date; 8 | updatedAt: Date; 9 | 10 | constructor(entity: AbstractEntity) { 11 | this.id = entity.id; 12 | this.createdAt = entity.createdAt; 13 | this.updatedAt = entity.updatedAt; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/common/dto/AbstractSearchDto.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; 4 | import { IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; 5 | 6 | import { ToInt } from '../../decorators/transforms.decorator'; 7 | 8 | export class AbstractSearchDto { 9 | @ApiProperty() 10 | @IsString() 11 | @IsNotEmpty() 12 | q: string; 13 | 14 | @ApiProperty() 15 | @IsNumber() 16 | @IsNotEmpty() 17 | @ToInt() 18 | page: number; 19 | 20 | @ApiPropertyOptional() 21 | @IsNumber() 22 | @IsOptional() 23 | @ToInt() 24 | take = 10; 25 | 26 | get skip() { 27 | return (this.page - 1) * this.take; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /backend/src/common/dto/PageMetaDto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | import { PageOptionsDto } from './PageOptionsDto'; 4 | 5 | interface IPageMetaDtoParameters { 6 | pageOptionsDto: PageOptionsDto; 7 | itemCount: number; 8 | } 9 | 10 | export class PageMetaDto { 11 | @ApiProperty() 12 | readonly page: number; 13 | 14 | @ApiProperty() 15 | readonly take: number; 16 | 17 | @ApiProperty() 18 | readonly itemCount: number; 19 | 20 | @ApiProperty() 21 | readonly pageCount: number; 22 | 23 | @ApiProperty() 24 | readonly hasPreviousPage: boolean; 25 | 26 | @ApiProperty() 27 | readonly hasNextPage: boolean; 28 | 29 | constructor({ pageOptionsDto, itemCount }: IPageMetaDtoParameters) { 30 | this.page = pageOptionsDto.page; 31 | this.take = pageOptionsDto.take; 32 | this.itemCount = itemCount; 33 | this.pageCount = Math.ceil(this.itemCount / this.take); 34 | this.hasPreviousPage = this.page > 1; 35 | this.hasNextPage = this.page < this.pageCount; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /backend/src/decorators/auth-user.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | 3 | // eslint-disable-next-line @typescript-eslint/naming-convention 4 | export const AuthUser = createParamDecorator( 5 | (data: unknown, ctx: ExecutionContext) => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return request.user; 8 | }, 9 | ); 10 | -------------------------------------------------------------------------------- /backend/src/decorators/roles.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | 3 | import { RoleType } from '../common/constants/role-type'; 4 | 5 | // eslint-disable-next-line @typescript-eslint/naming-convention 6 | export const Roles = (...roles: RoleType[]) => SetMetadata('roles', roles); 7 | -------------------------------------------------------------------------------- /backend/src/decorators/swagger.schema.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | import { ApiBody } from '@nestjs/swagger'; 3 | import { SchemaObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; 4 | 5 | export const ApiFile = ( 6 | fileName = 'file', 7 | options: Partial<{ isRequired: boolean; isArray: boolean }> = {}, 8 | ): MethodDecorator => ( 9 | target: any, 10 | propertyKey: string, 11 | descriptor: PropertyDescriptor, 12 | ) => { 13 | const { isRequired = false, isArray = false } = options; 14 | let fileSchema: SchemaObject = { 15 | type: 'string', 16 | format: 'binary', 17 | }; 18 | 19 | if (isArray) { 20 | fileSchema = { 21 | type: 'array', 22 | items: fileSchema, 23 | }; 24 | } 25 | return ApiBody({ 26 | required: isRequired, 27 | schema: { 28 | type: 'object', 29 | properties: { 30 | [fileName]: fileSchema, 31 | }, 32 | }, 33 | })(target, propertyKey, descriptor); 34 | }; 35 | -------------------------------------------------------------------------------- /backend/src/decorators/validators.decorator.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:naming-convention */ 2 | 3 | import { 4 | registerDecorator, 5 | ValidationArguments, 6 | ValidationOptions, 7 | } from 'class-validator'; 8 | 9 | export function IsPassword( 10 | validationOptions?: ValidationOptions, 11 | ): PropertyDecorator { 12 | return (object: any, propertyName: string) => { 13 | registerDecorator({ 14 | propertyName, 15 | name: 'isPassword', 16 | target: object.constructor, 17 | constraints: [], 18 | options: validationOptions, 19 | validator: { 20 | validate(value: string, _args: ValidationArguments) { 21 | return /^[a-zA-Z0-9!@#$%^&*]*$/.test(value); 22 | }, 23 | }, 24 | }); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/exceptions/file-not-image.exception.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { BadRequestException } from '@nestjs/common'; 4 | 5 | export class FileNotImageException extends BadRequestException { 6 | constructor(message?: string | any, error?: string) { 7 | if (message) { 8 | super(message, error); 9 | } else { 10 | super('error.file.not_image'); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/exceptions/user-not-found.exception.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { NotFoundException } from '@nestjs/common'; 4 | 5 | export class UserNotFoundException extends NotFoundException { 6 | constructor(error?: string) { 7 | super('error.user_not_found', error); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/filters/constraint-errors.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | 3 | export const constraintErrors: Record = { 4 | UQ_97672ac88f789774dd47f7c8be3: 'error.unique.email', 5 | }; 6 | -------------------------------------------------------------------------------- /backend/src/filters/query-failed.filter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ArgumentsHost, 3 | Catch, 4 | ExceptionFilter, 5 | HttpStatus, 6 | } from '@nestjs/common'; 7 | import { Reflector } from '@nestjs/core'; 8 | import { Response } from 'express'; 9 | import { STATUS_CODES } from 'http'; 10 | import { QueryFailedError } from 'typeorm'; 11 | 12 | import { constraintErrors } from './constraint-errors'; 13 | 14 | @Catch(QueryFailedError) 15 | export class QueryFailedFilter implements ExceptionFilter { 16 | constructor(public reflector: Reflector) {} 17 | 18 | catch(exception: any, host: ArgumentsHost) { 19 | const ctx = host.switchToHttp(); 20 | const response = ctx.getResponse(); 21 | 22 | const errorMessage = constraintErrors[exception.constraint]; 23 | 24 | const status = 25 | exception.constraint && exception.constraint.startsWith('UQ') 26 | ? HttpStatus.CONFLICT 27 | : HttpStatus.INTERNAL_SERVER_ERROR; 28 | 29 | response.status(status).json({ 30 | statusCode: status, 31 | error: STATUS_CODES[status], 32 | message: errorMessage, 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /backend/src/guards/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { AuthGuard as NestAuthGuard } from '@nestjs/passport'; 2 | 3 | // This should be used as guard class 4 | // eslint-disable-next-line @typescript-eslint/naming-convention 5 | export const AuthGuard = NestAuthGuard('jwt'); 6 | -------------------------------------------------------------------------------- /backend/src/guards/roles.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; 2 | import { Reflector } from '@nestjs/core'; 3 | 4 | import { UserEntity } from '../modules/users/user.entity'; 5 | 6 | @Injectable() 7 | export class RolesGuard implements CanActivate { 8 | constructor(private readonly _reflector: Reflector) {} 9 | 10 | canActivate(context: ExecutionContext): boolean { 11 | const roles = this._reflector.get( 12 | 'roles', 13 | context.getHandler(), 14 | ); 15 | 16 | if (!roles) { 17 | return true; 18 | } 19 | 20 | const request = context.switchToHttp().getRequest(); 21 | const user = request.user; 22 | 23 | return roles.includes(user.role); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/i18n/en/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "keywords": { 3 | "admin": "Administrator" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /backend/src/i18n/et/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "keywords": { 3 | "admin": "administraator" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /backend/src/i18n/ru/translations.json: -------------------------------------------------------------------------------- 1 | { 2 | "keywords": { 3 | "admin": "админ" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /backend/src/interceptors/auth-user-interceptor.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CallHandler, 3 | ExecutionContext, 4 | Injectable, 5 | NestInterceptor, 6 | } from '@nestjs/common'; 7 | import { Observable } from 'rxjs'; 8 | 9 | import { AuthService } from '../modules/auth/auth.service'; 10 | import { UserEntity } from '../modules/users/user.entity'; 11 | 12 | @Injectable() 13 | export class AuthUserInterceptor implements NestInterceptor { 14 | intercept(context: ExecutionContext, next: CallHandler): Observable { 15 | const request = context.switchToHttp().getRequest(); 16 | 17 | const user = request.user; 18 | AuthService.setAuthUser(user); 19 | 20 | return next.handle(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/interfaces/IAwsConfig.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export interface IAwsConfig { 4 | accessKeyId: string; 5 | secretAccessKey: string; 6 | bucketName: string; 7 | } 8 | -------------------------------------------------------------------------------- /backend/src/interfaces/IFile.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export interface IFile { 4 | encoding: string; 5 | buffer: Buffer; 6 | fieldname: string; 7 | mimetype: string; 8 | originalname: string; 9 | size: number; 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/middlewares/context.middelware.ts: -------------------------------------------------------------------------------- 1 | import * as requestContext from 'request-context'; 2 | 3 | export const contextMiddleware = requestContext.middleware('request'); 4 | -------------------------------------------------------------------------------- /backend/src/middlewares/index.ts: -------------------------------------------------------------------------------- 1 | export * from './context.middelware'; 2 | -------------------------------------------------------------------------------- /backend/src/modules/users/application/user/create-user/create-user.command.ts: -------------------------------------------------------------------------------- 1 | import { ICommand } from '@nestjs/cqrs'; 2 | 3 | export class CreateUserCommand implements ICommand { 4 | constructor( 5 | public readonly email: string, 6 | public readonly firstName: string, 7 | public readonly lastName: string, 8 | public readonly password: string, 9 | ) {} 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/modules/users/domain/invitation/invitation.aggregate.ts: -------------------------------------------------------------------------------- 1 | import { AggregateRoot, UniqueEntityID } from 'shared/domain'; 2 | import { Guard, Result } from 'shared/core'; 3 | 4 | interface InvitationProps { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /backend/src/modules/users/domain/user/events/email-verified.event.ts: -------------------------------------------------------------------------------- 1 | import { IDomainEvent, UniqueEntityID } from 'shared/domain'; 2 | import { User } from '../user.aggregate'; 3 | 4 | export class EmailVerified implements IDomainEvent { 5 | public dateTimeOccurred: Date; 6 | public user: User; 7 | 8 | constructor(user: User) { 9 | this.dateTimeOccurred = new Date(); 10 | this.user = user; 11 | } 12 | 13 | public getAggregateId(): UniqueEntityID { 14 | return this.user.id; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/modules/users/domain/user/events/index.ts: -------------------------------------------------------------------------------- 1 | export * from './email-verified.event'; 2 | export * from './user-created.event'; 3 | export * from './user-deleted.event'; 4 | export * from './user-logged-in.event'; 5 | -------------------------------------------------------------------------------- /backend/src/modules/users/domain/user/events/user-created.event.ts: -------------------------------------------------------------------------------- 1 | import { IDomainEvent, UniqueEntityID } from 'shared/domain'; 2 | import { User } from "../user.aggregate"; 3 | 4 | export class UserCreated implements IDomainEvent { 5 | public dateTimeOccurred: Date; 6 | public user: User; 7 | 8 | constructor(user: User) { 9 | this.dateTimeOccurred = new Date(); 10 | this.user = user; 11 | } 12 | 13 | getAggregateId(): UniqueEntityID { 14 | return this.user.id; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/modules/users/domain/user/events/user-deleted.event.ts: -------------------------------------------------------------------------------- 1 | import { IDomainEvent, UniqueEntityID } from 'shared/domain'; 2 | import { User } from "../user.aggregate"; 3 | 4 | export class UserDeleted implements IDomainEvent { 5 | public dateTimeOccurred: Date; 6 | public user: User; 7 | 8 | constructor (user: User) { 9 | this.dateTimeOccurred = new Date(); 10 | this.user = user; 11 | } 12 | 13 | getAggregateId (): UniqueEntityID { 14 | return this.user.id; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/modules/users/domain/user/events/user-logged-in.event.ts: -------------------------------------------------------------------------------- 1 | import { IDomainEvent, UniqueEntityID } from 'shared/domain'; 2 | import { User } from "../user.aggregate"; 3 | 4 | export class UserLoggedIn implements IDomainEvent { 5 | public dateTimeOccurred: Date; 6 | public user: User; 7 | 8 | constructor (user: User) { 9 | this.dateTimeOccurred = new Date(); 10 | this.user = user; 11 | } 12 | 13 | public getAggregateId (): UniqueEntityID { 14 | return this.user.id; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/modules/users/domain/user/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user.aggregate'; 2 | export * from './user-id.entity'; 3 | export * from './user-email.vo'; 4 | export * from './user-password.vo'; 5 | export * from './email-verification-token.vo'; 6 | export * from './user.repository'; 7 | -------------------------------------------------------------------------------- /backend/src/modules/users/domain/user/jwt.ts: -------------------------------------------------------------------------------- 1 | export interface JWTClaims { 2 | userId: string; 3 | isEmailVerified: boolean; 4 | email: string; 5 | username: string; 6 | adminUser: boolean; 7 | } 8 | 9 | export type JWTToken = string; 10 | 11 | export type SessionId = string; 12 | 13 | export type RefreshToken = string; 14 | -------------------------------------------------------------------------------- /backend/src/modules/users/domain/user/user-email.vo.ts: -------------------------------------------------------------------------------- 1 | import { Result } from 'shared/core'; 2 | import { ValueObject } from 'shared/domain'; 3 | 4 | export interface UserEmailProps { 5 | value: string; 6 | } 7 | 8 | export class UserEmail extends ValueObject { 9 | get value(): string { 10 | return this.props.value; 11 | } 12 | 13 | private constructor(props: UserEmailProps) { 14 | super(props); 15 | } 16 | 17 | private static isValidEmail(email: string) { 18 | var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 19 | return re.test(email); 20 | } 21 | 22 | private static format(email: string): string { 23 | return email.trim().toLowerCase(); 24 | } 25 | 26 | public static create(email: string): Result { 27 | if (!this.isValidEmail(email)) { 28 | return Result.fail('Email address not valid'); 29 | } else { 30 | return Result.ok(new UserEmail({ value: this.format(email) })); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /backend/src/modules/users/domain/user/user-id.entity.ts: -------------------------------------------------------------------------------- 1 | import { Result } from "shared/core"; 2 | import { Entity, UniqueEntityID } from "shared/domain"; 3 | 4 | export class UserId extends Entity { 5 | get id(): UniqueEntityID { 6 | return this._id; 7 | } 8 | 9 | private constructor(id?: UniqueEntityID) { 10 | super(null, id); 11 | } 12 | 13 | public static create(id?: UniqueEntityID): Result { 14 | return Result.ok(new UserId(id)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/modules/users/domain/user/user.repository.ts: -------------------------------------------------------------------------------- 1 | import { User, UserEmail } from "."; 2 | 3 | export interface IUserRepository { 4 | exists(userEmail: UserEmail): Promise; 5 | getUserByUserId(userId: string): Promise; 6 | save(user: User): Promise; 7 | } 8 | -------------------------------------------------------------------------------- /backend/src/modules/users/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./users.module"; 2 | -------------------------------------------------------------------------------- /backend/src/modules/users/infra/database/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./user.schema" 2 | -------------------------------------------------------------------------------- /backend/src/modules/users/infra/database/repositories/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./user.repository"; 2 | -------------------------------------------------------------------------------- /backend/src/modules/users/infra/http/user/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiPropertyOptional } from "@nestjs/swagger"; 2 | 3 | export class CreateUserDto { 4 | @ApiPropertyOptional() 5 | firstName: string; 6 | 7 | @ApiPropertyOptional() 8 | lastName: string; 9 | 10 | @ApiPropertyOptional() 11 | email: string; 12 | 13 | @ApiPropertyOptional() 14 | password: string; 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/modules/users/infra/http/user/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./create-user.dto"; 2 | export * from "./user.controller"; 3 | -------------------------------------------------------------------------------- /backend/src/modules/users/infra/http/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, HttpCode, HttpStatus, Post } from "@nestjs/common"; 2 | import { ApiConsumes, ApiOkResponse, ApiTags } from "@nestjs/swagger"; 3 | import { CommandBus, QueryBus } from "@nestjs/cqrs"; 4 | import { CreateUserDto } from "./create-user.dto"; 5 | import { CreateUserCommand } from "modules/users/application/user/create-user/create-user.command"; 6 | 7 | @Controller("users") 8 | @ApiTags("users") 9 | export class UserController { 10 | constructor(private readonly commandBus: CommandBus, private readonly queryBus: QueryBus) {} 11 | 12 | @Post("register") 13 | @HttpCode(HttpStatus.NO_CONTENT) 14 | @ApiOkResponse({ description: "Successfully Registered" }) 15 | @ApiConsumes("application/json") 16 | async userRegister(@Body() request: CreateUserDto) { 17 | const { email, password, firstName, lastName } = request; 18 | return this.commandBus.execute(new CreateUserCommand(email, firstName, lastName, password)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/modules/users/users.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { TypeOrmModule } from "@nestjs/typeorm"; 3 | import { UserRepository } from "./infra/database/repositories"; 4 | import { UserController } from "./infra/http/user"; 5 | 6 | @Module({ 7 | imports: [TypeOrmModule.forFeature([UserRepository])], 8 | controllers: [UserController], 9 | }) 10 | export class UserModule {} 11 | -------------------------------------------------------------------------------- /backend/src/providers/context.service.ts: -------------------------------------------------------------------------------- 1 | import * as requestContext from 'request-context'; 2 | 3 | export class ContextService { 4 | private static readonly _nameSpace = 'request'; 5 | 6 | static get(key: string): T { 7 | return requestContext.get(ContextService._getKeyWithNamespace(key)); 8 | } 9 | 10 | static set(key: string, value: any): void { 11 | requestContext.set(ContextService._getKeyWithNamespace(key), value); 12 | } 13 | 14 | private static _getKeyWithNamespace(key: string): string { 15 | return `${ContextService._nameSpace}.${key}`; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/shared/core/app-error.ts: -------------------------------------------------------------------------------- 1 | import { Result } from './result'; 2 | import { UseCaseError } from './use-case-error'; 3 | 4 | export namespace AppError { 5 | export class UnexpectedError extends Result { 6 | public constructor(err: any) { 7 | super(false, { 8 | message: `An unexpected error occurred.`, 9 | error: err, 10 | } as UseCaseError); 11 | console.log(`[AppError]: An unexpected error occurred`); 12 | console.error(err); 13 | } 14 | 15 | public static create(err: any): UnexpectedError { 16 | return new UnexpectedError(err); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/shared/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app-error'; 2 | export * from './guard'; 3 | export * from './result'; 4 | export * from './use-case-error'; 5 | export * from './use-case'; 6 | export * from './with-changes'; 7 | -------------------------------------------------------------------------------- /backend/src/shared/core/use-case-error.ts: -------------------------------------------------------------------------------- 1 | interface IUseCaseError { 2 | message: string; 3 | } 4 | 5 | export abstract class UseCaseError implements IUseCaseError { 6 | public readonly message: string; 7 | 8 | constructor(message: string) { 9 | this.message = message; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/shared/core/use-case.ts: -------------------------------------------------------------------------------- 1 | export interface UseCase { 2 | execute(request?: IRequest): Promise | IResponse; 3 | } 4 | -------------------------------------------------------------------------------- /backend/src/shared/core/with-changes.ts: -------------------------------------------------------------------------------- 1 | import { Result } from './result'; 2 | 3 | export interface WithChanges { 4 | changes: Changes; 5 | } 6 | 7 | export class Changes { 8 | private changes: Result[]; 9 | 10 | constructor() { 11 | this.changes = []; 12 | } 13 | 14 | public addChange(result: Result): void { 15 | this.changes.push(result); 16 | } 17 | 18 | public getChangeResult(): Result { 19 | return Result.combine(this.changes); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/shared/domain/aggregate-root.ts: -------------------------------------------------------------------------------- 1 | import { Entity } from './entity'; 2 | import { IDomainEvent } from './domain-event'; 3 | import { DomainEvent } from './domain-event'; 4 | import { UniqueEntityID } from './unique-entity-id'; 5 | import { IBusinessRule } from './business-rule'; 6 | 7 | export abstract class AggregateRoot extends Entity { 8 | private _domainEvents: IDomainEvent[] = []; 9 | 10 | get id(): UniqueEntityID { 11 | return this._id; 12 | } 13 | 14 | get domainEvents(): IDomainEvent[] { 15 | return this._domainEvents; 16 | } 17 | 18 | protected addDomainEvent(domainEvent: IDomainEvent): void { 19 | // Add the domain event to this aggregate's list of domain events 20 | this._domainEvents.push(domainEvent); 21 | // Add this aggregate instance to the domain event's list of aggregates who's 22 | // events it eventually needs to dispatch. 23 | DomainEvent.markAggregateForDispatch(this); 24 | } 25 | 26 | public clearEvents(): void { 27 | this._domainEvents.splice(0, this._domainEvents.length); 28 | } 29 | 30 | public CheckBusinessRule(rule: IBusinessRule): Boolean { 31 | return rule.isValid(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /backend/src/shared/domain/business-rule.ts: -------------------------------------------------------------------------------- 1 | export interface IBusinessRule { 2 | isValid: () => Boolean 3 | } 4 | -------------------------------------------------------------------------------- /backend/src/shared/domain/domain-service.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IDomainService { 3 | 4 | } 5 | -------------------------------------------------------------------------------- /backend/src/shared/domain/entity.ts: -------------------------------------------------------------------------------- 1 | import { UniqueEntityID } from './unique-entity-id'; 2 | 3 | const isEntity = (v: any): v is Entity => { 4 | return v instanceof Entity; 5 | }; 6 | 7 | export abstract class Entity { 8 | protected readonly _id: UniqueEntityID; 9 | public readonly props: T; 10 | 11 | constructor(props: T, id?: UniqueEntityID) { 12 | this._id = id ? id : new UniqueEntityID(); 13 | this.props = props; 14 | } 15 | 16 | public equals(object?: Entity): boolean { 17 | if (object == null || object == undefined) { 18 | return false; 19 | } 20 | 21 | if (this === object) { 22 | return true; 23 | } 24 | 25 | if (!isEntity(object)) { 26 | return false; 27 | } 28 | 29 | return this._id.equals(object._id); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /backend/src/shared/domain/identifier.ts: -------------------------------------------------------------------------------- 1 | export class Identifier { 2 | constructor(private value: T) { 3 | this.value = value; 4 | } 5 | 6 | equals(id?: Identifier): boolean { 7 | if (id === null || id === undefined) { 8 | return false; 9 | } 10 | if (!(id instanceof this.constructor)) { 11 | return false; 12 | } 13 | return id.toValue() === this.value; 14 | } 15 | 16 | toString() { 17 | return String(this.value); 18 | } 19 | 20 | /** 21 | * Return raw value of identifier 22 | */ 23 | 24 | toValue(): T { 25 | return this.value; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backend/src/shared/domain/index.ts: -------------------------------------------------------------------------------- 1 | export * from './aggregate-root'; 2 | export * from './business-rule'; 3 | export * from './domain-event'; 4 | export * from './domain-service'; 5 | export * from './entity'; 6 | export * from './identifier'; 7 | export * from './unique-entity-id'; 8 | export * from './value-object'; 9 | export * from './watched-list'; 10 | -------------------------------------------------------------------------------- /backend/src/shared/domain/unique-entity-id.ts: -------------------------------------------------------------------------------- 1 | import uuid from 'uuid/v4'; 2 | import { Identifier } from './identifier'; 3 | 4 | export class UniqueEntityID extends Identifier { 5 | constructor(id?: string | number) { 6 | super(id ? id : uuid()); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/shared/domain/value-object.ts: -------------------------------------------------------------------------------- 1 | interface ValueObjectProps { 2 | [index: string]: any; 3 | } 4 | 5 | /** 6 | * @desc ValueObjects are objects that we determine their 7 | * equality through their structrual property. 8 | */ 9 | 10 | export abstract class ValueObject { 11 | public props: T; 12 | 13 | constructor(props: T) { 14 | let baseProps: any = { 15 | ...props, 16 | }; 17 | 18 | this.props = baseProps; 19 | } 20 | 21 | public equals(vo?: ValueObject): boolean { 22 | if (vo === null || vo === undefined) { 23 | return false; 24 | } 25 | if (vo.props === undefined) { 26 | return false; 27 | } 28 | return JSON.stringify(this.props) === JSON.stringify(vo.props); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /backend/src/shared/infra/database/entity-base.ts: -------------------------------------------------------------------------------- 1 | import { PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | export class EntityBaseRaw { 4 | @PrimaryGeneratedColumn('uuid') 5 | id: string; 6 | } 7 | -------------------------------------------------------------------------------- /backend/src/shared/infra/database/index.ts: -------------------------------------------------------------------------------- 1 | export * from './entity-base'; 2 | -------------------------------------------------------------------------------- /backend/src/shared/infra/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./config-service"; 2 | export * from "./mapper"; 3 | -------------------------------------------------------------------------------- /backend/src/shared/infra/mapper.ts: -------------------------------------------------------------------------------- 1 | export interface Mapper {} 2 | -------------------------------------------------------------------------------- /backend/src/viveo-swagger.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; 3 | 4 | export function setupSwagger(app: INestApplication): void { 5 | const options = new DocumentBuilder() 6 | .setTitle('API') 7 | .setVersion('0.0.1') 8 | .addBearerAuth() 9 | .build(); 10 | 11 | const document = SwaggerModule.createDocument(app, options); 12 | SwaggerModule.setup('documentation', app, document); 13 | } 14 | -------------------------------------------------------------------------------- /backend/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call */ 2 | import { Test } from '@nestjs/testing'; 3 | import * as request from 'supertest'; 4 | 5 | import { AppModule } from '../src/app.module'; 6 | 7 | describe('AuthController (e2e)', () => { 8 | let app; 9 | 10 | beforeEach(async () => { 11 | const moduleFixture = await Test.createTestingModule({ 12 | imports: [AppModule], 13 | }).compile(); 14 | 15 | app = moduleFixture.createNestApplication(); 16 | await app.init(); 17 | }); 18 | 19 | it('/ (GET)', () => 20 | request(app.getHttpServer()) 21 | .get('/') 22 | .expect(200) 23 | .expect('Hello World!')); 24 | }); 25 | -------------------------------------------------------------------------------- /backend/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /backend/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "src/**/*.ts", 5 | "*.ts", 6 | "test/**/*.ts" 7 | ], 8 | "exclude": [ 9 | ".eslintrc.js" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": false, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "allowSyntheticDefaultImports": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es6", 12 | "sourceMap": true, 13 | "allowJs": false, 14 | "outDir": "./dist", 15 | "baseUrl": "./src" 16 | }, 17 | "include": [ 18 | "src/**/*" 19 | ], 20 | "exclude": [ 21 | "node_modules", 22 | "**/*.spec.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /cli/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["buildo", "prettier"], 3 | "parser": "typescript-eslint-parser", 4 | "parserOptions": { 5 | "sourceType": "module" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /cli/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 120 5 | } 6 | -------------------------------------------------------------------------------- /cli/cli.ts: -------------------------------------------------------------------------------- 1 | src/cli.ts -------------------------------------------------------------------------------- /cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "nestjs-bff-cli", 4 | "version": "1.1.4", 5 | "description": "A Nestjs Backend-For-Frontend CLI", 6 | "author": "ahrnee", 7 | "license": "MIT", 8 | "scripts": { 9 | "db-seed:dev": "ts-node cli migration-custom --filename seed-dev --direction up" 10 | }, 11 | "dependencies": { 12 | "@nestjs-bff/backend": "^1.1.4", 13 | "@nestjs-bff/global-contracts": "^1.1.4", 14 | "@nestjs-bff/global-utils-prod": "^1.1.4", 15 | "@nestjs/common": "^5.0.0", 16 | "@nestjs/core": "^5.0.0", 17 | "@types/inquirer": "0.0.43", 18 | "inquirer": "^6.2.1", 19 | "reflect-metadata": "^0.1.12", 20 | "rxjs": "^6.0.0", 21 | "yargs": "^12.0.5" 22 | }, 23 | "devDependencies": { 24 | "@nestjs-bff/global-utils-dev": "^1.1.4", 25 | "tslint": "~5.11.0", 26 | "typescript": "^3.2.2" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cli/src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import * as yargs from 'yargs'; 4 | import { AppConfig } from '../../backend/src/config/app.config'; 5 | import { getLogger } from '@nestjs-bff/backend/lib/shared/logging/logging.shared.module'; 6 | 7 | // global setup 8 | AppConfig.appName = 'nestjs-bff-cli'; 9 | 10 | global.nestjs_bff = { config: AppConfig }; 11 | const bffLogger = getLogger(); 12 | 13 | // prettier-ignore 14 | // tslint:disable-next-line:no-unused-expression 15 | yargs 16 | .env('BFFCLI') 17 | .commandDir('commands', { 18 | extensions: AppConfig.nodeEnv === 'dev' ? ['js', 'ts'] : ['js'], 19 | visit: (commandObject) => { 20 | commandObject.loggerService = bffLogger; 21 | return commandObject; 22 | }, 23 | }) 24 | .demandCommand(1) 25 | .help() 26 | .version() 27 | .argv; 28 | -------------------------------------------------------------------------------- /cli/src/commands/MigrationsList.ts: -------------------------------------------------------------------------------- 1 | import * as yargs from 'yargs'; // eslint-disable-line no-unused-vars 2 | import { MigrationsSharedService } from '@nestjs-bff/backend/lib/shared/migrations/migrations.shared.service'; 3 | import { NestjsBffAppContainer } from '../nestjsBffAppContainer'; 4 | import { LoggerSharedService } from '@nestjs-bff/backend/lib/shared/logging/logger.shared.service'; 5 | 6 | export let loggerService: LoggerSharedService; // This needs to be initialized by the commandLoader 7 | export interface IParams {} 8 | 9 | // Supply exports to satisfy Yargs command pattern: command, desc, builder, and handler 10 | export const command = 'migrations-list'; 11 | export const desc = `Lists all migrations and their current state`; 12 | export const builder: { [key: string]: yargs.Options } = {}; 13 | export async function handler(argv: any) { 14 | await NestjsBffAppContainer.ensureInitialized().then(() => { 15 | const migrationService = NestjsBffAppContainer.appInstance.get(MigrationsSharedService); 16 | migrationService.list('').then(() => { 17 | process.exit(0); 18 | }); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /cli/src/commands/MigrationsPrune.ts: -------------------------------------------------------------------------------- 1 | import * as yargs from 'yargs'; // eslint-disable-line no-unused-vars 2 | import { MigrationsSharedService } from '@nestjs-bff/backend/lib/shared/migrations/migrations.shared.service'; 3 | import { NestjsBffAppContainer } from '../nestjsBffAppContainer'; 4 | import { LoggerSharedService } from '@nestjs-bff/backend/lib/shared/logging/logger.shared.service'; 5 | 6 | export let loggerService: LoggerSharedService; // This needs to be initialized by the commandLoader 7 | export interface IParams {} 8 | 9 | // Supply exports to satisfy Yargs command pattern: command, desc, builder, and handler 10 | export const command = 'migrations-prune'; 11 | export const desc = 12 | 'Allows you to delete extraneous migrations by removing extraneous local migration files/database migrations.'; 13 | export const builder: { [key: string]: yargs.Options } = {}; 14 | export async function handler(argv: any) { 15 | await NestjsBffAppContainer.ensureInitialized().then(() => { 16 | const migrationService = NestjsBffAppContainer.appInstance.get(MigrationsSharedService); 17 | migrationService.prune('', true).then(() => { 18 | process.exit(0); 19 | }); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /cli/src/commands/MigrationsSync.ts: -------------------------------------------------------------------------------- 1 | import * as yargs from 'yargs'; // eslint-disable-line no-unused-vars 2 | import { MigrationsSharedService } from '@nestjs-bff/backend/lib/shared/migrations/migrations.shared.service'; 3 | import { NestjsBffAppContainer } from '../nestjsBffAppContainer'; 4 | import { LoggerSharedService } from '@nestjs-bff/backend/lib/shared/logging/logger.shared.service'; 5 | 6 | export let loggerService: LoggerSharedService; // This needs to be initialized by the commandLoader 7 | export interface IParams {} 8 | 9 | // Supply exports to satisfy Yargs command pattern: command, desc, builder, and handler 10 | export const command = 'migrations-sync'; 11 | export const desc = 'Imports any migrations that are on the file system but missing in the database.'; 12 | export const builder: { [key: string]: yargs.Options } = {}; 13 | export async function handler(argv: any) { 14 | await NestjsBffAppContainer.ensureInitialized().then(() => { 15 | const migrationService = NestjsBffAppContainer.appInstance.get(MigrationsSharedService); 16 | migrationService.sync('', true).then(() => { 17 | process.exit(0); 18 | }); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /cli/src/commands/hello.ts: -------------------------------------------------------------------------------- 1 | import { prompt as ask } from 'inquirer'; 2 | import * as yargs from 'yargs'; // eslint-disable-line no-unused-vars 3 | import { LoggerSharedService } from '@nestjs-bff/backend/lib/shared/logging/logger.shared.service'; 4 | 5 | export let loggerService: LoggerSharedService = null; // This needs to be initialized by the commandLoader 6 | 7 | async function askName(): Promise { 8 | loggerService.log(':wave: Hello stranger!'); 9 | const { name } = await ask([ 10 | { 11 | type: 'input', 12 | name: 'name', 13 | message: 'What is your name?', 14 | }, 15 | ]); 16 | return name; 17 | } 18 | 19 | export interface IParams { 20 | name?: string; 21 | } 22 | 23 | // Supply exports to satisfy Yargs command pattern: command, desc, builder, and handler 24 | export const command = 'hello'; 25 | export const desc = `Let's get to know each other`; 26 | export const builder: { [key: string]: yargs.Options } = { 27 | name: { type: 'string', required: false, description: 'your name' }, 28 | }; 29 | export async function handler({ name }: IParams) { 30 | loggerService.log(`Oh, nice to meet you, ${name || (await askName())}!`); 31 | } 32 | -------------------------------------------------------------------------------- /cli/src/nestjsBffAppContainer.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { CliAppModule } from '../../backend/src/app/host/cli/cliapp.module'; 4 | 5 | export class NestjsBffAppContainer { 6 | public static appInstance: INestApplication; 7 | 8 | public static async ensureInitialized(): Promise { 9 | if (!NestjsBffAppContainer.appInstance) 10 | await NestFactory.create(CliAppModule).then((nestApp: INestApplication) => { 11 | this.appInstance = nestApp; 12 | }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cli/src/types/node.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | interface Global { 3 | nestjs_bff: any; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /cli/tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } -------------------------------------------------------------------------------- /cli/tests/commands/__snapshots__/hello.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`hello says hello with the right name, after asking it if not provided 1`] = `"? What's your name? "`; 4 | 5 | exports[`hello says hello with the right name, after asking it if not provided 2`] = `":wave: Hello stranger!"`; 6 | 7 | exports[`hello says hello with the right name, after asking it if not provided 3`] = `"Oh, nice to meet you, gabro!"`; 8 | 9 | exports[`hello says hello with the right name, without asking it if provided 1`] = `"Oh, nice to meet you, gabro!"`; 10 | -------------------------------------------------------------------------------- /cli/tests/utils/index.ts: -------------------------------------------------------------------------------- 1 | const stdin = require('bdd-stdin'); 2 | 3 | export const ENTER = '\x0D'; 4 | 5 | export async function runWithAnswers( 6 | command: () => Promise, 7 | combo: Array = [] 8 | ): Promise { 9 | if (combo.length > 0) stdin(combo); 10 | return command(); 11 | } 12 | -------------------------------------------------------------------------------- /cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": false, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es6", 11 | "sourceMap": true, 12 | "allowJs": true, 13 | "outDir": "./dist", 14 | "baseUrl": "./src", 15 | "types": [], 16 | }, 17 | "include": ["src/**/*"], 18 | "exclude": ["node_modules", "**/*.spec.ts"] 19 | } 20 | -------------------------------------------------------------------------------- /cli/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended"], 3 | "jsRules": { 4 | "no-unused-expression": true 5 | }, 6 | "rules": { 7 | "curly": false, 8 | "eofline": false, 9 | "quotemark": [true, "single"], 10 | "max-line-length": [true, 200], 11 | "object-literal-sort-keys": false, 12 | "arrow-parens": false, 13 | "no-empty-interface": false, 14 | "no-empty": false, 15 | "max-classes-per-file": false, 16 | "ordered-imports": false, 17 | "interface-name": false, 18 | "member-ordering": [true, { "order": "fields-first" }] 19 | }, 20 | "rulesDirectory": [] 21 | } 22 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: 4 | env_file: 5 | - .development.env 6 | container_name: awesome_nest_boilerplate 7 | restart: always 8 | build: . 9 | ports: 10 | - "$PORT:$PORT" 11 | links: 12 | - postgres 13 | postgres: 14 | image: postgres 15 | restart: always 16 | environment: 17 | DB_PASSWORD: postgres 18 | ports: 19 | - "5433:5432" 20 | volumes: 21 | - ./db-data:/var/lib/postgresql/data 22 | adminer: 23 | image: adminer 24 | restart: always 25 | ports: 26 | - 8080:8080 27 | -------------------------------------------------------------------------------- /docs/ATTRIBUTIONS.md: -------------------------------------------------------------------------------- 1 | # Attributions 2 | 3 | This codebase contains code from the following open source libraries: 4 | 5 | - https://github.com/nestjs/nest (nestjs) 6 | - https://github.com/balmasi/migrate-mongoose (mongo migrations codebase - heavily modified) 7 | - https://gist.github.com/briandk/3d2e8b3ec8daf5a27a62 (contributing template) 8 | - https://github.com/coreui/coreui-free-bootstrap-admin-template (frontend template) 9 | 10 | #Licence File 11 | A copy of the licence files can be found for each of the above projects in the /attributions folder of this project 12 | -------------------------------------------------------------------------------- /docs/images/NestJS-BFF-ArchitectureOverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/docs/images/NestJS-BFF-ArchitectureOverview.png -------------------------------------------------------------------------------- /docs/images/dashboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/docs/images/dashboard.jpg -------------------------------------------------------------------------------- /docs/images/login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/docs/images/login.jpg -------------------------------------------------------------------------------- /docs/licences/MIT LICENSE from angular-6-jwt-authentication-example: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Jason Watmore 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/licences/MIT LICENSE from cli-typescript-boilerplate: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Gabriele Petronella 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/licences/MIT LICENSE from core-ui: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 creativeLabs Łukasz Holeczek. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/licences/MIT LICENSE from nestjs: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2017 Kamil Myśliwiec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = off 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /dist-server 6 | /tmp 7 | /out-tsc 8 | 9 | # dependencies 10 | /node_modules 11 | package-lock.json 12 | 13 | # IDEs and editors 14 | /.idea 15 | .project 16 | .classpath 17 | .c9/ 18 | *.launch 19 | .settings/ 20 | *.sublime-workspace 21 | 22 | # IDE - VSCode 23 | .vscode/* 24 | !.vscode/settings.json 25 | !.vscode/tasks.json 26 | !.vscode/launch.json 27 | !.vscode/extensions.json 28 | 29 | # misc 30 | /.sass-cache 31 | /connect.lock 32 | /coverage 33 | /libpeerconnection.log 34 | npm-debug.log 35 | yarn-error.log 36 | testem.log 37 | /typings 38 | 39 | # e2e 40 | /e2e/*.js 41 | /e2e/*.map 42 | 43 | # System Files 44 | .DS_Store 45 | Thumbs.db 46 | -------------------------------------------------------------------------------- /frontend/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "html.format.wrapLineLength": 140, 3 | "prettier.printWidth": 140, 4 | "cSpell.words": [ 5 | "backend" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /frontend/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Before opening an issue: 2 | 3 | - [Search for duplicate or closed issues](https://github.com/coreui/coreui-free-angular-admin-template/issues?utf8=%E2%9C%93&q=is%3Aissue) 4 | - Prepare a [reduced test case](https://css-tricks.com/reduced-test-cases/) for any bugs 5 | - Read the [contributing guidelines](https://github.com/coreui/coreui-free-angular-admin-template/blob/master/CONTRIBUTING.md) 6 | 7 | When asking general "how to" questions: 8 | 9 | - Please do not open an issue here 10 | 11 | When reporting a bug, include: 12 | 13 | - Operating system and version (Windows, Mac OS X, Android, iOS, Win10 Mobile) 14 | - Browser and version (Chrome, Firefox, Safari, IE, MS Edge, Opera 15+, Android Browser) 15 | - Reduced test cases and potential fixes using [CodePen](https://codepen.io/) or [JS Bin](https://jsbin.com/) 16 | 17 | When suggesting a feature, include: 18 | 19 | - As much detail as possible for what we should add and why it's important to CoreUI Admin Template 20 | - Relevant links to prior art, screenshots, or live demos whenever possible 21 | -------------------------------------------------------------------------------- /frontend/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { NestJSBFFPage } from './app.po'; 2 | 3 | describe('NestJS-BFF App', function() { 4 | let page: NestJSBFFPage; 5 | 6 | beforeEach(() => { 7 | page = new NestJSBFFPage(); 8 | }); 9 | 10 | it('should display footer containing NestJS-BFF', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toContain('NestJS-BFF'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /frontend/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class NestJSBFFPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.tagName('footer')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types":[ 8 | "jasmine", 9 | "node" 10 | ] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /frontend/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /frontend/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/backend/*": { 3 | "target": "http://localhost:1337", 4 | "secure": false, 5 | "logLevel": "debug", 6 | "changeOrigin": true, 7 | "pathRewrite": { 8 | "^/backend": "" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/frontend/src/app/app.component.css -------------------------------------------------------------------------------- /frontend/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | Welcome to {{ title }}! 5 |

6 | Angular Logo 7 |
8 |

Here are some links to help you start:

9 |
20 | 21 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { RouterTestingModule } from '@angular/router/testing'; 2 | import { TestBed, async } from '@angular/core/testing'; 3 | import { AppComponent } from './app.component'; 4 | describe('AppComponent', () => { 5 | beforeEach(async(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | imports: [ RouterTestingModule ] 11 | }).compileComponents(); 12 | })); 13 | it('should create the app', async(() => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | })); 18 | }); 19 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router, NavigationEnd } from '@angular/router'; 3 | 4 | @Component({ 5 | // tslint:disable-next-line 6 | selector: 'body', 7 | template: '' 8 | }) 9 | export class AppComponent implements OnInit { 10 | constructor(private router: Router) { } 11 | 12 | ngOnInit() { 13 | this.router.events.subscribe((evt) => { 14 | if (!(evt instanceof NavigationEnd)) { 15 | return; 16 | } 17 | window.scrollTo(0, 0); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/app/containers/default-layout/default-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { navItems } from './../../_nav'; 3 | import { AuthenticationService } from '../../services'; 4 | import { Router } from '@angular/router'; 5 | 6 | @Component({ 7 | selector: 'app-dashboard', 8 | templateUrl: './default-layout.component.html', 9 | }) 10 | export class DefaultLayoutComponent { 11 | public navItems = navItems; 12 | public sidebarMinimized = true; 13 | private changes: MutationObserver; 14 | public element: HTMLElement = document.body; 15 | constructor(private router: Router, private authenticationService: AuthenticationService) { 16 | this.changes = new MutationObserver(mutations => { 17 | this.sidebarMinimized = document.body.classList.contains('sidebar-minimized'); 18 | }); 19 | 20 | this.changes.observe(this.element, { 21 | attributes: true, 22 | }); 23 | } 24 | 25 | logout() { 26 | this.authenticationService.logout(); 27 | this.router.navigate(['/login']); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/app/containers/default-layout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './default-layout.component'; 2 | -------------------------------------------------------------------------------- /frontend/src/app/containers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './default-layout'; 2 | -------------------------------------------------------------------------------- /frontend/src/app/guards/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 3 | 4 | @Injectable({ providedIn: 'root' }) 5 | export class AuthGuard implements CanActivate { 6 | 7 | constructor(private router: Router) { } 8 | 9 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 10 | if (localStorage.getItem('currentUser')) { 11 | // logged in so return true 12 | return true; 13 | } 14 | 15 | // not logged in so redirect to login page with the return url 16 | this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }}); 17 | return false; 18 | } 19 | } -------------------------------------------------------------------------------- /frontend/src/app/guards/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth.guard'; -------------------------------------------------------------------------------- /frontend/src/app/interceptors/error.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; 3 | import { Observable, throwError } from 'rxjs'; 4 | import { catchError } from 'rxjs/operators'; 5 | 6 | import { AuthenticationService } from '../services'; 7 | 8 | @Injectable() 9 | export class ErrorInterceptor implements HttpInterceptor { 10 | constructor(private authenticationService: AuthenticationService) {} 11 | 12 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 13 | return next.handle(request).pipe( 14 | catchError(err => { 15 | console.log('error', err); 16 | 17 | if (err.status === 401) { 18 | // auto logout if 401 response returned from api 19 | this.authenticationService.logout(); 20 | location.reload(true); 21 | } 22 | 23 | const error = err.error.message || err.error; 24 | return throwError(error); 25 | }) 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/app/interceptors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './error.interceptor'; 2 | export * from './jwt.interceptor'; 3 | -------------------------------------------------------------------------------- /frontend/src/app/interceptors/jwt.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { 3 | HttpRequest, 4 | HttpHandler, 5 | HttpEvent, 6 | HttpInterceptor, 7 | } from '@angular/common/http'; 8 | import { Observable } from 'rxjs'; 9 | 10 | @Injectable() 11 | export class JwtInterceptor implements HttpInterceptor { 12 | intercept( 13 | request: HttpRequest, 14 | next: HttpHandler, 15 | ): Observable> { 16 | // add authorization header with jwt token if available 17 | const currentUser = JSON.parse(localStorage.getItem('currentUser')); 18 | if (currentUser && currentUser.token) { 19 | request = request.clone({ 20 | setHeaders: { 21 | Authorization: `Bearer ${currentUser.token}`, 22 | }, 23 | }); 24 | } 25 | 26 | return next.handle(request); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/app/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './authentication.service'; 2 | -------------------------------------------------------------------------------- /frontend/src/app/views/base/cards.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'cards.component.html' 5 | }) 6 | export class CardsComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/base/carousels.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { CarouselConfig } from 'ngx-bootstrap/carousel'; 3 | 4 | @Component({ 5 | templateUrl: 'carousels.component.html', providers: [ 6 | { provide: CarouselConfig, useValue: { interval: 1500, noPause: true } } 7 | ] 8 | }) 9 | export class CarouselsComponent { 10 | 11 | myInterval: number = 6000; 12 | slides: any[] = []; 13 | activeSlideIndex: number = 0; 14 | noWrapSlides: boolean = false; 15 | 16 | constructor() { 17 | for (let i = 0; i < 4; i++) { 18 | this.addSlide(); 19 | } 20 | } 21 | 22 | addSlide(): void { 23 | this.slides.push({ 24 | image: `https://loremflickr.com/900/500/sailing?random=${this.slides.length % 8 + 1}/` 25 | }); 26 | } 27 | 28 | removeSlide(index?: number): void { 29 | const toRemove = index ? index : this.activeSlideIndex; 30 | this.slides.splice(toRemove, 1); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/app/views/base/collapses.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Bootstrap Collapse 5 | 10 |
11 |
15 |

16 | Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo 17 | consequat. 18 |

19 |
20 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /frontend/src/app/views/base/collapses.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'collapses.component.html' 5 | }) 6 | export class CollapsesComponent { 7 | 8 | constructor() { } 9 | 10 | isCollapsed: boolean = false; 11 | 12 | collapsed(event: any): void { 13 | // console.log(event); 14 | } 15 | 16 | expanded(event: any): void { 17 | // console.log(event); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/app/views/base/forms.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'forms.component.html' 5 | }) 6 | export class FormsComponent { 7 | 8 | constructor() { } 9 | 10 | isCollapsed: boolean = false; 11 | iconCollapse: string = 'icon-arrow-up'; 12 | 13 | collapsed(event: any): void { 14 | // console.log(event); 15 | } 16 | 17 | expanded(event: any): void { 18 | // console.log(event); 19 | } 20 | 21 | toggleCollapse(): void { 22 | this.isCollapsed = !this.isCollapsed; 23 | this.iconCollapse = this.isCollapsed ? 'icon-arrow-down' : 'icon-arrow-up'; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/app/views/base/paginations.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'paginations.component.html', 5 | styles: ['.pager li.btn:active { box-shadow: none; }'], 6 | encapsulation: ViewEncapsulation.None 7 | }) 8 | export class PaginationsComponent { 9 | 10 | constructor() { } 11 | 12 | totalItems: number = 64; 13 | currentPage: number = 4; 14 | smallnumPages: number = 0; 15 | 16 | maxSize: number = 5; 17 | bigTotalItems: number = 675; 18 | bigCurrentPage: number = 1; 19 | numPages: number = 0; 20 | 21 | currentPager: number = 4; 22 | 23 | setPage(pageNo: number): void { 24 | this.currentPage = pageNo; 25 | } 26 | 27 | pageChanged(event: any): void { 28 | console.log('Page changed to: ' + event.page); 29 | console.log('Number items per page: ' + event.itemsPerPage); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/app/views/base/popovers.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, SecurityContext} from '@angular/core'; 2 | import {DomSanitizer} from '@angular/platform-browser'; 3 | 4 | 5 | @Component({ 6 | templateUrl: 'popovers.component.html' 7 | }) 8 | export class PopoversComponent { 9 | 10 | constructor(sanitizer: DomSanitizer) { 11 | this.html = sanitizer.sanitize(SecurityContext.HTML, this.html); 12 | } 13 | 14 | title: string = 'Welcome word'; 15 | content: string = 'Vivamus sagittis lacus vel augue laoreet rutrum faucibus.'; 16 | html: string = `Never trust not sanitized HTML!!!`; 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/app/views/base/switches.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'switches.component.html' 5 | }) 6 | export class SwitchesComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/base/tables.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'tables.component.html' 5 | }) 6 | export class TablesComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/base/tabs.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'tabs.component.html' 5 | }) 6 | export class TabsComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/base/tooltips.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, SecurityContext} from '@angular/core'; 2 | import {DomSanitizer} from '@angular/platform-browser'; 3 | 4 | @Component({ 5 | templateUrl: 'tooltips.component.html' 6 | }) 7 | export class TooltipsComponent { 8 | 9 | constructor(sanitizer: DomSanitizer) { 10 | this.html = sanitizer.sanitize(SecurityContext.HTML, this.html); 11 | } 12 | 13 | content: string = 'Vivamus sagittis lacus vel augue laoreet rutrum faucibus.'; 14 | html: string = `Never trust not sanitized HTML!!!`; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/views/buttons/brand-buttons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'brand-buttons.component.html' 5 | }) 6 | export class BrandButtonsComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/buttons/buttons-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { ButtonsComponent } from './buttons.component'; 5 | import { DropdownsComponent } from './dropdowns.component'; 6 | import { BrandButtonsComponent } from './brand-buttons.component'; 7 | 8 | const routes: Routes = [ 9 | { 10 | path: '', 11 | data: { 12 | title: 'Buttons' 13 | }, 14 | children: [ 15 | { 16 | path: 'buttons', 17 | component: ButtonsComponent, 18 | data: { 19 | title: 'Buttons' 20 | } 21 | }, 22 | { 23 | path: 'dropdowns', 24 | component: DropdownsComponent, 25 | data: { 26 | title: 'Dropdowns' 27 | } 28 | }, 29 | { 30 | path: 'brand-buttons', 31 | component: BrandButtonsComponent, 32 | data: { 33 | title: 'Brand buttons' 34 | } 35 | } 36 | ] 37 | } 38 | ]; 39 | 40 | @NgModule({ 41 | imports: [RouterModule.forChild(routes)], 42 | exports: [RouterModule] 43 | }) 44 | export class ButtonsRoutingModule {} 45 | -------------------------------------------------------------------------------- /frontend/src/app/views/buttons/buttons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'buttons.component.html' 5 | }) 6 | export class ButtonsComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/buttons/buttons.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { NgModule } from '@angular/core'; 4 | 5 | import { ButtonsComponent } from './buttons.component'; 6 | import { BrandButtonsComponent } from './brand-buttons.component'; 7 | 8 | // Dropdowns Component 9 | import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; 10 | import { DropdownsComponent } from './dropdowns.component'; 11 | 12 | // Buttons Routing 13 | import { ButtonsRoutingModule } from './buttons-routing.module'; 14 | 15 | // Angular 16 | 17 | @NgModule({ 18 | imports: [ 19 | CommonModule, 20 | ButtonsRoutingModule, 21 | BsDropdownModule.forRoot(), 22 | FormsModule 23 | ], 24 | declarations: [ 25 | ButtonsComponent, 26 | DropdownsComponent, 27 | BrandButtonsComponent 28 | ] 29 | }) 30 | export class ButtonsModule { } 31 | -------------------------------------------------------------------------------- /frontend/src/app/views/buttons/dropdowns.component.css: -------------------------------------------------------------------------------- 1 | .dropup .dropdown-menu { 2 | transform: none!important; 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/app/views/buttons/dropdowns.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'dropdowns.component.html', 5 | styleUrls: ['dropdowns.component.css'] 6 | }) 7 | export class DropdownsComponent { 8 | 9 | status: { isOpen: boolean } = { isOpen: false }; 10 | disabled: boolean = false; 11 | isDropup: boolean = true; 12 | autoClose: boolean = false; 13 | 14 | constructor() { } 15 | 16 | items: string[] = [ 17 | 'The first choice!', 18 | 'And another choice for you.', 19 | 'but wait! A third!' 20 | ]; 21 | 22 | onHidden(): void { 23 | console.log('Dropdown is hidden'); 24 | } 25 | onShown(): void { 26 | console.log('Dropdown is shown'); 27 | } 28 | isOpenChange(): void { 29 | console.log('Dropdown state is changed'); 30 | } 31 | 32 | toggleDropdown($event: MouseEvent): void { 33 | $event.preventDefault(); 34 | $event.stopPropagation(); 35 | this.status.isOpen = !this.status.isOpen; 36 | } 37 | 38 | change(value: boolean): void { 39 | this.status.isOpen = value; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/app/views/chartjs/chartjs-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { ChartJSComponent } from './chartjs.component'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: ChartJSComponent, 10 | data: { 11 | title: 'Charts' 12 | } 13 | } 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [RouterModule.forChild(routes)], 18 | exports: [RouterModule] 19 | }) 20 | export class ChartJSRoutingModule {} 21 | -------------------------------------------------------------------------------- /frontend/src/app/views/chartjs/chartjs.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ChartsModule } from 'ng2-charts/ng2-charts'; 3 | 4 | import { ChartJSComponent } from './chartjs.component'; 5 | import { ChartJSRoutingModule } from './chartjs-routing.module'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | ChartJSRoutingModule, 10 | ChartsModule 11 | ], 12 | declarations: [ ChartJSComponent ] 13 | }) 14 | export class ChartJSModule { } 15 | -------------------------------------------------------------------------------- /frontend/src/app/views/dashboard/dashboard-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, 3 | RouterModule } from '@angular/router'; 4 | 5 | import { DashboardComponent } from './dashboard.component'; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', 10 | component: DashboardComponent, 11 | data: { 12 | title: 'Dashboard' 13 | } 14 | } 15 | ]; 16 | 17 | @NgModule({ 18 | imports: [RouterModule.forChild(routes)], 19 | exports: [RouterModule] 20 | }) 21 | export class DashboardRoutingModule {} 22 | -------------------------------------------------------------------------------- /frontend/src/app/views/dashboard/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { ChartsModule } from 'ng2-charts/ng2-charts'; 4 | import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; 5 | import { ButtonsModule } from 'ngx-bootstrap/buttons'; 6 | 7 | import { DashboardComponent } from './dashboard.component'; 8 | import { DashboardRoutingModule } from './dashboard-routing.module'; 9 | 10 | @NgModule({ 11 | imports: [ 12 | FormsModule, 13 | DashboardRoutingModule, 14 | ChartsModule, 15 | BsDropdownModule, 16 | ButtonsModule.forRoot() 17 | ], 18 | declarations: [ DashboardComponent ] 19 | }) 20 | export class DashboardModule { } 21 | -------------------------------------------------------------------------------- /frontend/src/app/views/error/404.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |

404

7 |

Oops! You're lost.

8 |

The page you are looking for was not found.

9 |
10 |
11 |
12 | 13 |
14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /frontend/src/app/views/error/404.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: '404.component.html' 5 | }) 6 | export class P404Component { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/error/500.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |

500

7 |

Houston, we have a problem!

8 |

The page you are looking for is temporarily unavailable.

9 |
10 |
11 |
12 | 13 |
14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /frontend/src/app/views/error/500.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: '500.component.html' 5 | }) 6 | export class P500Component { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/icons/coreui-icons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'coreui-icons.component.html' 5 | }) 6 | export class CoreUIIconsComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/icons/flags.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'flags.component.html' 5 | }) 6 | export class FlagsComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/icons/font-awesome.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'font-awesome.component.html' 5 | }) 6 | export class FontAwesomeComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/icons/icons.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { CoreUIIconsComponent } from './coreui-icons.component'; 4 | import { FlagsComponent } from './flags.component'; 5 | import { FontAwesomeComponent } from './font-awesome.component'; 6 | import { SimpleLineIconsComponent } from './simple-line-icons.component'; 7 | 8 | import { IconsRoutingModule } from './icons-routing.module'; 9 | 10 | @NgModule({ 11 | imports: [ IconsRoutingModule ], 12 | declarations: [ 13 | CoreUIIconsComponent, 14 | FlagsComponent, 15 | FontAwesomeComponent, 16 | SimpleLineIconsComponent 17 | ] 18 | }) 19 | export class IconsModule { } 20 | -------------------------------------------------------------------------------- /frontend/src/app/views/icons/simple-line-icons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'simple-line-icons.component.html' 5 | }) 6 | export class SimpleLineIconsComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/notifications/badges.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'badges.component.html' 5 | }) 6 | export class BadgesComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/notifications/modals.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild } from '@angular/core'; 2 | import { ModalDirective } from 'ngx-bootstrap/modal'; 3 | 4 | @Component({ 5 | templateUrl: 'modals.component.html' 6 | }) 7 | export class ModalsComponent { 8 | public myModal; 9 | public largeModal; 10 | public smallModal; 11 | public primaryModal; 12 | public successModal; 13 | public warningModal; 14 | public dangerModal; 15 | public infoModal; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/app/views/notifications/notifications-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { AlertsComponent } from './alerts.component'; 5 | import { BadgesComponent } from './badges.component'; 6 | import { ModalsComponent } from './modals.component'; 7 | 8 | const routes: Routes = [ 9 | { 10 | path: '', 11 | data: { 12 | title: 'Notifications' 13 | }, 14 | children: [ 15 | { 16 | path: 'alerts', 17 | component: AlertsComponent, 18 | data: { 19 | title: 'Alerts' 20 | } 21 | }, 22 | { 23 | path: 'badges', 24 | component: BadgesComponent, 25 | data: { 26 | title: 'Badges' 27 | } 28 | }, 29 | { 30 | path: 'modals', 31 | component: ModalsComponent, 32 | data: { 33 | title: 'Modals' 34 | } 35 | } 36 | ] 37 | } 38 | ]; 39 | 40 | @NgModule({ 41 | imports: [RouterModule.forChild(routes)], 42 | exports: [RouterModule] 43 | }) 44 | export class NotificationsRoutingModule {} 45 | -------------------------------------------------------------------------------- /frontend/src/app/views/notifications/notifications.module.ts: -------------------------------------------------------------------------------- 1 | // Angular 2 | import { CommonModule } from '@angular/common'; 3 | import { NgModule } from '@angular/core'; 4 | 5 | // Alert Component 6 | import { AlertModule } from 'ngx-bootstrap/alert'; 7 | import { AlertsComponent } from './alerts.component'; 8 | 9 | import { BadgesComponent } from './badges.component'; 10 | 11 | // Modal Component 12 | import { ModalModule } from 'ngx-bootstrap/modal'; 13 | import { ModalsComponent } from './modals.component'; 14 | 15 | // Notifications Routing 16 | import { NotificationsRoutingModule } from './notifications-routing.module'; 17 | 18 | @NgModule({ 19 | imports: [ 20 | CommonModule, 21 | NotificationsRoutingModule, 22 | AlertModule.forRoot(), 23 | ModalModule.forRoot() 24 | ], 25 | declarations: [ 26 | AlertsComponent, 27 | BadgesComponent, 28 | ModalsComponent 29 | ] 30 | }) 31 | export class NotificationsModule { } 32 | -------------------------------------------------------------------------------- /frontend/src/app/views/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-dashboard', 5 | templateUrl: 'register.component.html' 6 | }) 7 | export class RegisterComponent { 8 | 9 | constructor() { } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/app/views/reminders/reminder.spec.ts: -------------------------------------------------------------------------------- 1 | import { ReminderEntity } from '@yourapp/global/lib/domain/reminder/reminder.entity'; 2 | 3 | describe('ReminderEntity', () => { 4 | it('should create an instance', () => { 5 | expect(new ReminderEntity()).toBeTruthy(); 6 | }); 7 | 8 | it('should accept values in the constructor', () => { 9 | const todo = new ReminderEntity({ 10 | title: 'hello', 11 | complete: true, 12 | }); 13 | expect(todo.title).toEqual('hello'); 14 | expect(todo.complete).toEqual(true); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /frontend/src/app/views/theme/colors.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { getStyle, rgbToHex } from '@coreui/coreui/dist/js/coreui-utilities'; 3 | 4 | @Component({ 5 | templateUrl: 'colors.component.html' 6 | }) 7 | export class ColorsComponent implements OnInit { 8 | public themeColors(): void { 9 | Array.from(document.querySelectorAll('.theme-color')).forEach(function(el) { 10 | const elem = document.getElementsByClassName(el.classList[0])[0]; 11 | const background = getStyle('background-color', elem); 12 | 13 | const table = document.createElement('table'); 14 | table.innerHTML = ` 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
HEX:${rgbToHex(background)}
RGB:${background}
25 | `; 26 | 27 | el.parentNode.appendChild(table); 28 | }); 29 | 30 | } 31 | 32 | ngOnInit(): void { 33 | this.themeColors(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/app/views/theme/theme-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { ColorsComponent } from './colors.component'; 5 | import { TypographyComponent } from './typography.component'; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', 10 | data: { 11 | title: 'Theme' 12 | }, 13 | children: [ 14 | { 15 | path: 'colors', 16 | component: ColorsComponent, 17 | data: { 18 | title: 'Colors' 19 | } 20 | }, 21 | { 22 | path: 'typography', 23 | component: TypographyComponent, 24 | data: { 25 | title: 'Typography' 26 | } 27 | } 28 | ] 29 | } 30 | ]; 31 | 32 | @NgModule({ 33 | imports: [RouterModule.forChild(routes)], 34 | exports: [RouterModule] 35 | }) 36 | export class ThemeRoutingModule {} 37 | -------------------------------------------------------------------------------- /frontend/src/app/views/theme/theme.module.ts: -------------------------------------------------------------------------------- 1 | // Angular 2 | import { CommonModule } from '@angular/common'; 3 | import { NgModule } from '@angular/core'; 4 | 5 | import { ColorsComponent } from './colors.component'; 6 | import { TypographyComponent } from './typography.component'; 7 | 8 | // Theme Routing 9 | import { ThemeRoutingModule } from './theme-routing.module'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | ThemeRoutingModule 15 | ], 16 | declarations: [ 17 | ColorsComponent, 18 | TypographyComponent 19 | ] 20 | }) 21 | export class ThemeModule { } 22 | -------------------------------------------------------------------------------- /frontend/src/app/views/theme/typography.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | templateUrl: 'typography.component.html' 5 | }) 6 | export class TypographyComponent { 7 | 8 | constructor() { } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/app/views/widgets/widgets-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { WidgetsComponent } from './widgets.component'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: WidgetsComponent, 10 | data: { 11 | title: 'Widgets' 12 | } 13 | } 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [RouterModule.forChild(routes)], 18 | exports: [RouterModule] 19 | }) 20 | export class WidgetsRoutingModule {} 21 | -------------------------------------------------------------------------------- /frontend/src/app/views/widgets/widgets.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ChartsModule } from 'ng2-charts/ng2-charts'; 3 | import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; 4 | 5 | import { WidgetsComponent } from './widgets.component'; 6 | import { WidgetsRoutingModule } from './widgets-routing.module'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | WidgetsRoutingModule, 11 | ChartsModule, 12 | BsDropdownModule 13 | ], 14 | declarations: [ WidgetsComponent ] 15 | }) 16 | export class WidgetsModule { } 17 | -------------------------------------------------------------------------------- /frontend/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/frontend/src/assets/.gitkeep -------------------------------------------------------------------------------- /frontend/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/frontend/src/assets/favicon.ico -------------------------------------------------------------------------------- /frontend/src/assets/img/avatars/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/frontend/src/assets/img/avatars/1.jpg -------------------------------------------------------------------------------- /frontend/src/assets/img/avatars/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/frontend/src/assets/img/avatars/2.jpg -------------------------------------------------------------------------------- /frontend/src/assets/img/avatars/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/frontend/src/assets/img/avatars/3.jpg -------------------------------------------------------------------------------- /frontend/src/assets/img/avatars/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/frontend/src/assets/img/avatars/4.jpg -------------------------------------------------------------------------------- /frontend/src/assets/img/avatars/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/frontend/src/assets/img/avatars/5.jpg -------------------------------------------------------------------------------- /frontend/src/assets/img/avatars/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/frontend/src/assets/img/avatars/6.jpg -------------------------------------------------------------------------------- /frontend/src/assets/img/avatars/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/frontend/src/assets/img/avatars/7.jpg -------------------------------------------------------------------------------- /frontend/src/assets/img/avatars/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsantanders/modular-monolith-nestjs/c7651846b92ba1ba72da36548a8af3c4938fe3c1/frontend/src/assets/img/avatars/8.jpg -------------------------------------------------------------------------------- /frontend/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /frontend/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | NestJS-BFF - Full-Stack TypeScript Web Application Solution 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /frontend/src/scss/_custom.scss: -------------------------------------------------------------------------------- 1 | // Here you can add other styles 2 | -------------------------------------------------------------------------------- /frontend/src/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | // Variable overrides 2 | -------------------------------------------------------------------------------- /frontend/src/scss/style.scss: -------------------------------------------------------------------------------- 1 | // If you want to override variables do it here 2 | @import "variables"; 3 | 4 | // Import styles 5 | @import "~@coreui/coreui/scss/coreui"; 6 | 7 | // If you want to add something do it here 8 | @import "custom"; 9 | -------------------------------------------------------------------------------- /frontend/src/scss/vendors/_variables.scss: -------------------------------------------------------------------------------- 1 | // Override Boostrap variables 2 | @import "../variables"; 3 | @import "~bootstrap/scss/mixins"; 4 | @import "~@coreui/coreui/scss/variables"; 5 | -------------------------------------------------------------------------------- /frontend/src/scss/vendors/chart.js/chart.scss: -------------------------------------------------------------------------------- 1 | // Import variables 2 | @import '../variables'; 3 | 4 | .chart-legend, 5 | .bar-legend, 6 | .line-legend, 7 | .pie-legend, 8 | .radar-legend, 9 | .polararea-legend, 10 | .doughnut-legend { 11 | list-style-type: none; 12 | margin-top: 5px; 13 | text-align: center; 14 | -webkit-padding-start: 0; 15 | -moz-padding-start: 0; 16 | padding-left: 0; 17 | } 18 | .chart-legend li, 19 | .bar-legend li, 20 | .line-legend li, 21 | .pie-legend li, 22 | .radar-legend li, 23 | .polararea-legend li, 24 | .doughnut-legend li { 25 | display: inline-block; 26 | white-space: nowrap; 27 | position: relative; 28 | margin-bottom: 4px; 29 | @include border-radius($border-radius); 30 | padding: 2px 8px 2px 28px; 31 | font-size: smaller; 32 | cursor: default; 33 | } 34 | .chart-legend li span, 35 | .bar-legend li span, 36 | .line-legend li span, 37 | .pie-legend li span, 38 | .radar-legend li span, 39 | .polararea-legend li span, 40 | .doughnut-legend li span { 41 | display: block; 42 | position: absolute; 43 | left: 0; 44 | top: 0; 45 | width: 20px; 46 | height: 20px; 47 | @include border-radius($border-radius); 48 | } 49 | -------------------------------------------------------------------------------- /frontend/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /frontend/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [], 8 | "paths": { 9 | "@angular/*": [ 10 | "../node_modules/@angular/*" 11 | ] 12 | } 13 | }, 14 | "exclude": [ 15 | "test.ts", 16 | "**/*.spec.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "test.ts", 14 | "polyfills.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es5", 11 | "lib": ["es2017", "dom"], 12 | "types":["jasmine"] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /global/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ -------------------------------------------------------------------------------- /global/.npmignore: -------------------------------------------------------------------------------- 1 | # Empty to override .gitignore ignoring the output folder -------------------------------------------------------------------------------- /global/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ahrnee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /global/README.md: -------------------------------------------------------------------------------- 1 | # nestjs-bff-global 2 | 3 | This is a companion package for [nestjs-bff](https://github.com/ahrnee/nestjs-bff). This package contains the global code that is shared between the backend api and the client. Typically, this is contracts and DTOs, but it can also contain global logic. 4 | 5 | ## Overview 6 | 7 | [NestJS-BFF](https://github.com/ahrnee/nestjs-bff) is a starter project for those looking to fast-track building a strongly typed, enterprise-grade, modern NodeJs application, with supporting tooling. 8 | 9 | This implementation uses the [BFF](https://samnewman.io/patterns/architectural/bff/) pattern, leveraging [NestJS](https://nestjs.com/) as the primary framework for the backend API. The frontend example is in [Angular](https://angular.io/), although any client-side Javascript framework can easily be used, including [React](https://reactjs.org/), or [Vue](https://vuejs.org/) js. 10 | 11 | ## Documentation 12 | 13 | Documentation and further details can be found in the [nestjs-bff github repo](https://github.com/ahrnee/nestjs-bff) 14 | -------------------------------------------------------------------------------- /global/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@yourapp/global", 4 | "version": "1.1.4", 5 | "description": "", 6 | "author": "ahrnee ", 7 | "license": "MIT", 8 | "homepage": "https://github.com/ahrnee/nestjs-bff", 9 | "directories": { 10 | "src": "src" 11 | }, 12 | "files": [ 13 | "lib", 14 | "README.md", 15 | "LICENSE.md" 16 | ], 17 | "types": "./lib", 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/ahrnee/nestjs-bff.git" 21 | }, 22 | "scripts": { 23 | "tsc": "tsc" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/ahrnee/nestjs-bff/issues" 27 | }, 28 | "dependencies": { 29 | "@nestjs-bff/global-contracts": "^1.1.4", 30 | "class-validator": "^0.9.1" 31 | }, 32 | "devDependencies": { 33 | "prettier": "^1.15.2", 34 | "tslint": "~5.11.0", 35 | "typescript": "~3.2.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /global/src/application/reminder-orchestration/send-reminder-to-archive.command.ts: -------------------------------------------------------------------------------- 1 | import { IsString } from 'class-validator'; 2 | 3 | export class SendReminderToArchiveCommand { 4 | @IsString() 5 | public readonly reminderId: string = ''; 6 | } 7 | -------------------------------------------------------------------------------- /global/src/domain/reminder-archive/reminder-archive.entity.ts: -------------------------------------------------------------------------------- 1 | import { UserAndOrgScopedEntity } from '@nestjs-bff/global-contracts/lib/domain/core/user-and-org-scoped.entity'; 2 | import { IsBoolean, IsDate, Length } from 'class-validator'; 3 | 4 | export class ReminderArchiveEntity extends UserAndOrgScopedEntity { 5 | @Length(2, 50) 6 | title?: string; 7 | 8 | @IsDate() 9 | deadline?: Date; 10 | 11 | @IsBoolean() 12 | complete?: boolean; 13 | 14 | @IsDate() 15 | archivedDate?: Date; 16 | } 17 | -------------------------------------------------------------------------------- /global/src/domain/reminder/reminder.entity.ts: -------------------------------------------------------------------------------- 1 | import { UserAndOrgScopedEntity } from '@nestjs-bff/global-contracts/lib/domain/core/user-and-org-scoped.entity'; 2 | import { IsBoolean, IsDate, Length } from 'class-validator'; 3 | 4 | export class ReminderEntity extends UserAndOrgScopedEntity { 5 | @Length(2, 50) 6 | title?: string; 7 | 8 | @IsBoolean() 9 | complete?: boolean; 10 | 11 | @IsDate() 12 | deadline?: Date; 13 | 14 | constructor(values: Partial = {}) { 15 | super(); 16 | Object.assign(this, values); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /global/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../pkg-bff/tsconfig-base", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src"], 8 | "references": [{ "path": "../pkg-bff/global-contracts" }] 9 | } 10 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.1.4", 3 | "packages": [".", "backend", "cli", "frontend", "global", "pkg-bff/*"], 4 | "useWorkspaces": false, 5 | "npmClient": "yarn", 6 | "command": { 7 | "publish": { 8 | "ignoreChanges": ["*.md"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg-bff/.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json -------------------------------------------------------------------------------- /pkg-bff/backend/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /lib-e2e 3 | -------------------------------------------------------------------------------- /pkg-bff/backend/.npmignore: -------------------------------------------------------------------------------- 1 | # Empty to override .gitignore ignoring the output folder -------------------------------------------------------------------------------- /pkg-bff/backend/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Jest Current Unit Test", 11 | "program": "${workspaceFolder}\\node_modules\\jest\\bin\\jest.js", 12 | "args": ["${relativeFile}", "--config", "${workspaceFolder}\\jest.json", "--runInBand", "--no-cache", "--detectOpenHandles", "--bail"], 13 | "runtimeArgs": ["--inspect-brk"], 14 | "cwd": "${workspaceRoot}", 15 | "protocol": "inspector", 16 | "console": "integratedTerminal", 17 | "internalConsoleOptions": "neverOpen" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /pkg-bff/backend/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.userWords": [ 3 | "Injectable", 4 | "chartjs", 5 | "firebase", 6 | "nestjs", 7 | "reactstrap", 8 | "redbox", 9 | "superset", 10 | "tsnode", 11 | "workspaces", 12 | "signin", 13 | "signup" 14 | ], 15 | "typescript.updateImportsOnFileMove.enabled": "prompt", 16 | "typescript.preferences.importModuleSpecifier": "auto", 17 | "tslint.autoFixOnSave": true, 18 | "editor.formatOnSave": true, 19 | "prettier.singleQuote": true, 20 | "prettier.printWidth": 180, 21 | "typescript.preferences.quoteStyle": "single", 22 | "javascript.preferences.quoteStyle": "single", 23 | "cSpell.words": [ 24 | "CSRF", 25 | "Repo", 26 | "ahrnee", 27 | "alwaystrue", 28 | "appsecret", 29 | "authorizationcheck", 30 | "authorizationcheck", 31 | "backend", 32 | "fastify", 33 | "formbody", 34 | "maxfile", 35 | "maxsize", 36 | "microservices", 37 | "prestart", 38 | "supersecretpassword", 39 | "validators", 40 | "webapp", 41 | "websockets", 42 | "winstonlogger" 43 | ], 44 | "html.format.wrapLineLength": 180, 45 | "debug.node.autoAttach": "off", 46 | "jest.pathToConfig": "jest.json" 47 | } 48 | -------------------------------------------------------------------------------- /pkg-bff/backend/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ahrnee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg-bff/backend/README.md: -------------------------------------------------------------------------------- 1 | # nestjs-bff-backend 2 | 3 | This is a companion package for [nestjs-bff](https://github.com/ahrnee/nestjs-bff). This package contains the infrastructure code for the backend api 4 | 5 | ## Overview 6 | 7 | [NestJS-BFF](https://github.com/ahrnee/nestjs-bff) is a starter project for those looking to fast-track building a strongly typed, enterprise-grade, modern NodeJs application, with supporting tooling. 8 | 9 | This implementation uses the [BFF](https://samnewman.io/patterns/architectural/bff/) pattern, leveraging [NestJS](https://nestjs.com/) as the primary framework for the backend API. The client-side example is in [Angular](https://angular.io/), although any client-side Javascript framework can easily be used, including [React](https://reactjs.org/), or [Vue](https://vuejs.org/) js. 10 | 11 | ## Documentation 12 | 13 | Documentation and further details can be found in the [nestjs-bff github repo](https://github.com/ahrnee/nestjs-bff) 14 | -------------------------------------------------------------------------------- /pkg-bff/backend/e2e/auth/auth-e2e.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { HttpAuthModule } from '../../src/host/http/application-service-host/auth/auth.module'; 3 | import { HttpWebAppBaseModule } from '../../src/host/http/web-app-base.module'; 4 | 5 | @Module({ 6 | imports: [HttpWebAppBaseModule, HttpAuthModule], 7 | controllers: [], 8 | providers: [], 9 | exports: undefined, 10 | }) 11 | export class AuthE2eModule {} 12 | -------------------------------------------------------------------------------- /pkg-bff/backend/e2e/core/global-setup-hook.js: -------------------------------------------------------------------------------- 1 | require('ts-node/register'); 2 | 3 | // If you want to reference other typescript modules, do it via require: 4 | const tsSetupModule = require('./global-setup-base'); 5 | 6 | module.exports = async function(globalConfig) { 7 | // Call your initialization methods here. 8 | await tsSetupModule.globalSetupBase(globalConfig); 9 | return null; 10 | }; 11 | -------------------------------------------------------------------------------- /pkg-bff/backend/e2e/core/global-tear-down-base.ts: -------------------------------------------------------------------------------- 1 | import { NestjsBffConfig } from '../../src/config/nestjs-bff.config'; 2 | import { getLogger } from '../../src/shared/logging/logging.shared.module'; 3 | 4 | export const globalTearDownBase = async (globalConfig: any, nestJsBffConfig?: any) => { 5 | // Setup 6 | nestJsBffConfig = nestJsBffConfig || NestjsBffConfig; 7 | const logger = getLogger(); 8 | logger.trace('-- Global TearDown Start -- '); 9 | // No teardown currently configured. The TestDB resets in the global setup 10 | logger.trace('-- Global TearDown End -- '); 11 | }; 12 | -------------------------------------------------------------------------------- /pkg-bff/backend/e2e/core/global-tear-down-hook.js: -------------------------------------------------------------------------------- 1 | require('ts-node/register'); 2 | 3 | // If you want to reference other typescript modules, do it via require: 4 | const tsTearDownModule = require('./global-tear-down-base'); 5 | 6 | module.exports = async function(globalConfig) { 7 | // Call your initialization methods here. 8 | await tsTearDownModule.globalTearDownBase(globalConfig); 9 | return null; 10 | }; 11 | -------------------------------------------------------------------------------- /pkg-bff/backend/e2e/core/test-object.utils.ts: -------------------------------------------------------------------------------- 1 | import { JwtTokenService } from '../../src/host/http/core/jwt/jwt-token.service'; 2 | 3 | export const setupTestDataJwtTokens = async (nestJsBffConfig: any, testData: any) => { 4 | const jwtTokenService = new JwtTokenService(nestJsBffConfig); 5 | 6 | testData.orgA.users.adminUser.jwt = await jwtTokenService.createToken(testData.orgA.users.adminUser.accessPermissionsEntity); 7 | testData.orgA.users.regularUser.jwt = await jwtTokenService.createToken(testData.orgA.users.regularUser.accessPermissionsEntity); 8 | testData.orgB.users.adminUser.jwt = await jwtTokenService.createToken(testData.orgB.users.adminUser.accessPermissionsEntity); 9 | testData.orgC.users.groupAdminUser.jwt = await jwtTokenService.createToken(testData.orgC.users.groupAdminUser.accessPermissionsEntity); 10 | }; 11 | -------------------------------------------------------------------------------- /pkg-bff/backend/e2e/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "ts-jest", 3 | "testEnvironment": "node", 4 | "globalSetup": "./core/global-setup-hook.js", 5 | "globalTeardown": "./core/global-tear-down-hook.js", 6 | "setupTestFrameworkScriptFile": "jest-extended", 7 | "testMatch": ["/**/*.e2e-spec.(ts|tsx|js)"], 8 | "testURL": "http://localhost/" 9 | } 10 | -------------------------------------------------------------------------------- /pkg-bff/backend/e2e/trace/trace.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import 'jest-extended'; 2 | import { getLogger } from '../../src/shared/logging/logging.shared.module'; 3 | 4 | describe('Trace', () => { 5 | const logger = getLogger(); 6 | 7 | beforeAll(async () => { 8 | logger.trace('-- beforeAll --', Date.now().toLocaleString()); 9 | }, 5 * 60 * 1000); 10 | 11 | it('adds 1 + 2 to equal 3 in TScript', async () => { 12 | logger.trace('-- during test --', Date.now().toLocaleString()); 13 | expect(1 + 2).toBe(3); 14 | }); 15 | 16 | afterAll(async () => { 17 | logger.trace('-- afterAll --', Date.now().toLocaleString()); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /pkg-bff/backend/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-base", 3 | "compilerOptions": { 4 | "outDir": "../lib-e2e", 5 | "allowJs": false, 6 | "rootDir": "." 7 | }, 8 | "references": [ 9 | { "path": "../../global-contracts" }, 10 | { "path": "../../global-utils-dev" }, 11 | { "path": "../../global-utils-prod" }, 12 | { "path": "../src" } 13 | ], 14 | "include": ["./**/*"] 15 | } 16 | -------------------------------------------------------------------------------- /pkg-bff/backend/jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootDir": "src", 3 | "preset": "ts-jest", 4 | "testEnvironment": "node", 5 | "testMatch": ["/**/*.spec.(ts|tsx|js)"], 6 | "modulePaths": ["/"], 7 | "collectCoverageFrom": ["/**/*.{js,jsx,tsx,ts}", "!**/node_modules/**", "!**/vendor/**"], 8 | "coverageReporters": ["json", "lcov"], 9 | "coverageDirectory": "coverage", 10 | "clearMocks": true 11 | } 12 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/application/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppSharedModule } from '../../shared/app/app.shared.module'; 3 | 4 | @Module({ 5 | imports: [AppSharedModule], 6 | providers: [], 7 | exports: [AppSharedModule], 8 | }) 9 | export class CoreModule {} 10 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/application/organization-orchestration/organization-orchestration.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { DomainAccessPermissionsModule } from '../../domain/access-permissions/access-permissions.module'; 3 | import { DomainAuthenticationModule } from '../../domain/authentication/authentication.module'; 4 | import { DomainOrganizationModule } from '../../domain/organization/organization.module'; 5 | import { DomainUserModule } from '../../domain/user/user.module'; 6 | import { CoreModule } from '../core/core.module'; 7 | import { OrganizationOrchestrationService } from './organization-orchestration.service'; 8 | 9 | @Module({ 10 | imports: [CoreModule, DomainAuthenticationModule, DomainAccessPermissionsModule, DomainUserModule, DomainOrganizationModule], 11 | providers: [OrganizationOrchestrationService], 12 | exports: [OrganizationOrchestrationService], 13 | }) 14 | export class OrganizationOrchestrationModule {} 15 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/application/user-auth/user-auth.constants.ts: -------------------------------------------------------------------------------- 1 | export const Messages = { 2 | UNAUTHORIZED_INVALID_PASSWORD: 'Invalid password', 3 | UNAUTHORIZED_INVALID_EMAIL: 'The email does not exist', 4 | UNAUTHORIZED_UNRECOGNIZED_BEARER: 'Unrecognized bearer of the token', 5 | }; 6 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/application/user-auth/user-auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { DomainAccessPermissionsModule } from '../../domain/access-permissions/access-permissions.module'; 3 | import { DomainAuthenticationModule } from '../../domain/authentication/authentication.module'; 4 | import { DomainOrganizationModule } from '../../domain/organization/organization.module'; 5 | import { DomainUserModule } from '../../domain/user/user.module'; 6 | import { CoreModule } from '../core/core.module'; 7 | import { UserAuthService } from './user-auth.service'; 8 | 9 | @Module({ 10 | imports: [CoreModule, DomainAuthenticationModule, DomainAccessPermissionsModule, DomainUserModule, DomainOrganizationModule], 11 | providers: [UserAuthService], 12 | exports: [UserAuthService], 13 | }) 14 | export class UserAuthModule {} 15 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/config/keys/jwt.private-key.test.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIBOgIBAAJBALA6Ud1u8QCBV4X59m4Puu1vpnOie9GsNHAlRUQ161mqPZh/hg4A 3 | RYQoSkwmwZHA4xvEcYeyjm0GPkK5nKtI8RcCAwEAAQJACp5pAmvF797bVEJcnAMs 4 | o1P/9qXKyjaTLlLAmryZAt0KoFBS4Ziaa7M0ItlUeuzZv6xFlAU+ARlUmsOej2Pr 5 | OQIhANmR2ev01axiyo82Xa0qJzeL2IPQgNmVP7M22ARMOLutAiEAz1sN5SwxqcVm 6 | 0TACjzwD8SbmueZ23+4vEBB4Ko33eFMCIQCaINvLby+rpnSuzanBEZqkm/ovLxcI 7 | jNWKhPC04rZSJQIgSGguWPluui7hcWjHbAb0BXClHwNYPWfp7T0jCREb+lsCIAJ4 8 | XZYzfe2+VUcYQHG96tJaOrq8pOKySRFw+rBcEGLa 9 | -----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /pkg-bff/backend/src/config/keys/jwt.public-key.test.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALA6Ud1u8QCBV4X59m4Puu1vpnOie9Gs 3 | NHAlRUQ161mqPZh/hg4ARYQoSkwmwZHA4xvEcYeyjm0GPkK5nKtI8RcCAwEAAQ== 4 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /pkg-bff/backend/src/config/nestjs.config.env.interface.ts: -------------------------------------------------------------------------------- 1 | export interface INestjsBffConfigEnv { 2 | readonly nodeEnv: 'dev' | 'test' | 'prod'; 3 | db: { 4 | mongo: { 5 | mongoConnectionUri: string; 6 | options: { 7 | dbName: string; 8 | }; 9 | }; 10 | }; 11 | http: { 12 | bffDomain: string; 13 | bffPort: number; 14 | bffRootUrl: string; 15 | spaDomain: string; 16 | spaPort: number; 17 | spaRootUrl: string; 18 | }; 19 | social: { 20 | facebook: { 21 | clientID: string; 22 | clientSecret: string; 23 | }; 24 | }; 25 | jwt: { 26 | jwtPrivateKey: string; 27 | jwtPublicKey: string; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/config/nestjs.config.test.ts: -------------------------------------------------------------------------------- 1 | import { extractKey } from '../shared/utils/key.shared.utils'; 2 | import { INestjsBffConfigEnv } from './nestjs.config.env.interface'; 3 | 4 | export const NestjsBffConfigEnv: INestjsBffConfigEnv = { 5 | nodeEnv: 'test', 6 | db: { 7 | mongo: { 8 | mongoConnectionUri: 'mongodb://localhost', 9 | options: { 10 | dbName: 'my-nestjs-bff-app-e2e' 11 | } 12 | } 13 | }, 14 | http: { 15 | bffDomain: 'localhost', 16 | bffPort: 1337, 17 | get bffRootUrl() { 18 | return `http://${this.bffDomain}:${this.bffPort}`; 19 | }, 20 | spaDomain: 'localhost', 21 | spaPort: 4200, 22 | get spaRootUrl() { 23 | return `http://${this.spaDomain}:${this.spaPort}`; 24 | } 25 | }, 26 | social: { 27 | facebook: { 28 | clientID: 'your-secret-clientID-here', // your App ID 29 | clientSecret: 'your-client-secret-here' // your App Secret 30 | } 31 | }, 32 | jwt: { 33 | jwtPrivateKey: extractKey(`${process.cwd()}/src/config/keys/jwt.private-key.test.pem`), 34 | jwtPublicKey: extractKey(`${process.cwd()}/src/config/keys/jwt.public-key.test.pem`) 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/_foo/foo.constants.ts: -------------------------------------------------------------------------------- 1 | export const FooProviderTokens = { 2 | Models: { 3 | Foo: 'FooModelToken', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/_foo/foo.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongoSharedProviderTokens } from '../../shared/database/mongo/mongo.shared.constants'; 3 | import { DomainCoreModule } from '../core/core.module'; 4 | import { FooProviderTokens } from './foo.constants'; 5 | import { FooSchema } from './model/foo.schema'; 6 | import { FooRepo } from './repo/foo.repo'; 7 | 8 | const FooModel = { 9 | provide: FooProviderTokens.Models.Foo, 10 | useFactory: mongoose => mongoose.connection.model('Foo', FooSchema), 11 | inject: [MongoSharedProviderTokens.Connections.Mongoose], 12 | }; 13 | 14 | @Module({ 15 | imports: [DomainCoreModule], 16 | providers: [FooRepo, FooModel], 17 | exports: [FooRepo], 18 | }) 19 | export class DomainFooModule {} 20 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/_foo/model/foo.entity.ts: -------------------------------------------------------------------------------- 1 | // import { UserAndOrgScopedEntity } from '@nestjs-bff/global-contracts/lib/entities/core/user-and-org-scoped.entity'; 2 | import { BaseEntity } from '@nestjs-bff/global-contracts/lib/domain/core/base.entity'; 3 | import { IsDefined, IsNotEmpty, Length } from 'class-validator'; 4 | 5 | export class FooEntity extends BaseEntity { 6 | @IsNotEmpty() 7 | public userId: string | undefined = undefined; 8 | 9 | @IsNotEmpty() 10 | public orgId: string | undefined = undefined; 11 | 12 | @Length(5, 50) 13 | name?: string; 14 | 15 | @IsDefined() 16 | @Length(2, 20) 17 | alwaysDefinedSlug?: string; 18 | } 19 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/_foo/model/foo.model.ts: -------------------------------------------------------------------------------- 1 | import { Document } from 'mongoose'; 2 | import { FooEntity } from './foo.entity'; 3 | 4 | export interface IFooModel extends FooEntity, Document {} 5 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/_foo/model/foo.schema.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from 'mongoose'; 2 | 3 | export const FooSchema = new Schema( 4 | { 5 | name: { 6 | required: true, 7 | type: Schema.Types.String, 8 | }, 9 | alwaysDefinedSlug: { 10 | required: true, 11 | type: Schema.Types.String, 12 | }, 13 | orgId: { 14 | type: Schema.Types.ObjectId, 15 | required: false, 16 | }, 17 | userId: { 18 | type: Schema.Types.ObjectId, 19 | required: false, 20 | }, 21 | }, 22 | { 23 | timestamps: true, 24 | collection: 'foo', 25 | }, 26 | ); 27 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/access-permissions/access-permissions.constants.ts: -------------------------------------------------------------------------------- 1 | export const AccessPermissionsProviderTokens = { 2 | Models: { 3 | AccessPermissions: 'AccessPermissionsModelToken', 4 | }, 5 | }; 6 | 7 | export const Messages = { 8 | UNAUTHORIZED: 'You do not have permissions to perform this action', 9 | }; 10 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/access-permissions/access-permissions.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongoSharedProviderTokens } from '../../shared/database/mongo/mongo.shared.constants'; 3 | import { DomainCoreModule } from '../core/core.module'; 4 | import { AccessPermissionsProviderTokens } from './access-permissions.constants'; 5 | import { AccessPermissionsSchema } from './model/access-permissions.schema'; 6 | import { AccessPermissionsRepo } from './repo/access-permissions.repo'; 7 | 8 | const AccessPermissionsModel = { 9 | provide: AccessPermissionsProviderTokens.Models.AccessPermissions, 10 | useFactory: mongoose => mongoose.connection.model('AccessPermissions', AccessPermissionsSchema), 11 | inject: [MongoSharedProviderTokens.Connections.Mongoose], 12 | }; 13 | 14 | @Module({ 15 | imports: [DomainCoreModule], 16 | providers: [AccessPermissionsModel, AccessPermissionsRepo], 17 | exports: [AccessPermissionsRepo], 18 | }) 19 | export class DomainAccessPermissionsModule {} 20 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/access-permissions/model/access-permissions.entity.ts: -------------------------------------------------------------------------------- 1 | import { OrganizationRoles, Roles } from '@nestjs-bff/global-contracts/lib/shared/authorization/roles.constants'; 2 | import { BaseEntity } from '@nestjs-bff/global-contracts/lib/domain/core/base.entity'; 3 | import { AccessPermissionsContract, UserOrgPermissionsContract } from '@nestjs-bff/global-contracts/lib/domain/access-permissions/access-permissions.contract'; 4 | import { IsArray, IsBoolean, IsIn, IsNotEmpty, ValidateNested } from 'class-validator'; 5 | 6 | export class AccessPermissionsEntity extends BaseEntity implements AccessPermissionsContract { 7 | @IsNotEmpty() 8 | userId?: string; 9 | 10 | @IsArray() 11 | @IsIn([Roles.user, Roles.groupAdmin, Roles.staffAdmin, Roles.systemAdmin], { each: true }) 12 | roles: string[] = [Roles.user]; 13 | 14 | @ValidateNested() 15 | organizations?: OrganizationAuthorization[] = []; 16 | } 17 | 18 | export class OrganizationAuthorization implements UserOrgPermissionsContract { 19 | @IsBoolean() 20 | primary?: boolean; 21 | 22 | @IsNotEmpty() 23 | orgId?: string; 24 | 25 | @IsArray() 26 | @IsIn([OrganizationRoles.member, OrganizationRoles.admin], { each: true }) 27 | organizationRoles: string[] = []; 28 | } 29 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/access-permissions/model/access-permissions.model.ts: -------------------------------------------------------------------------------- 1 | import { Document } from 'mongoose'; 2 | import { AccessPermissionsEntity } from './access-permissions.entity'; 3 | 4 | export interface IAccessPermissionsModel extends AccessPermissionsEntity, Document {} 5 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/access-permissions/model/access-permissions.schema.ts: -------------------------------------------------------------------------------- 1 | import { OrganizationRoles, Roles } from '@nestjs-bff/global-contracts/lib/shared/authorization/roles.constants'; 2 | import { Schema } from 'mongoose'; 3 | 4 | const UserOrgPermissionsSchema: Schema = new Schema({ 5 | primary: { 6 | type: Schema.Types.Boolean, 7 | required: true, 8 | }, 9 | orgId: { 10 | type: Schema.Types.ObjectId, 11 | required: false, 12 | }, 13 | organizationRoles: { 14 | type: [Schema.Types.String], 15 | enum: [OrganizationRoles.member, OrganizationRoles.admin, OrganizationRoles.facilitator], 16 | required: true, 17 | }, 18 | }); 19 | 20 | export const AccessPermissionsSchema: Schema = new Schema( 21 | { 22 | userId: { 23 | type: Schema.Types.ObjectId, 24 | required: false, 25 | }, 26 | roles: { 27 | type: [Schema.Types.String], 28 | enum: [Roles.user, Roles.groupAdmin, Roles.systemAdmin], 29 | required: true, 30 | }, 31 | organizations: { 32 | type: [UserOrgPermissionsSchema], 33 | required: true, 34 | }, 35 | }, 36 | { 37 | timestamps: true, 38 | collection: 'accesspermissions', 39 | }, 40 | ); 41 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/authentication/authentication.constants.ts: -------------------------------------------------------------------------------- 1 | export const AuthenticationProviderTokens = { 2 | Config: { 3 | Facebook: 'FacebookConfigToken', 4 | Google: 'GoogleConfigToken', 5 | Twitter: 'TwitterConfigToken', 6 | }, 7 | Models: { 8 | Authentication: 'AuthenticationModelToken', 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/authentication/model/authentication.model.ts: -------------------------------------------------------------------------------- 1 | import { Document } from 'mongoose'; 2 | import { AuthenticationEntity } from './authentication.entity'; 3 | 4 | export interface IAuthenticationModel extends AuthenticationEntity, Document {} 5 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/authentication/model/authentication.schema.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from 'mongoose'; 2 | 3 | export const AuthenticationSchema: Schema = new Schema( 4 | { 5 | userId: { 6 | type: Schema.Types.ObjectId, 7 | required: true, 8 | }, 9 | local: { 10 | email: { type: String, lowercase: true, unique: true, sparse: true }, 11 | hashedPassword: String, 12 | }, 13 | google: { 14 | id: String, 15 | accessToken: String, 16 | email: String, 17 | name: String, 18 | }, 19 | facebook: { 20 | id: String, 21 | accessToken: String, 22 | name: String, 23 | email: String, 24 | }, 25 | twitter: { 26 | id: String, 27 | accessToken: String, 28 | displayName: String, 29 | username: String, 30 | }, 31 | }, 32 | { 33 | timestamps: true, 34 | collection: 'authentication', 35 | }, 36 | ); 37 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/authentication/social/facebook-profile.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IFacebookProfile { 2 | readonly id: string; 3 | readonly name: string; 4 | readonly email: string; 5 | } 6 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/authentication/social/i-oauth-access-token.ts: -------------------------------------------------------------------------------- 1 | export interface IOauthAccessToken { 2 | readonly token: string; 3 | } 4 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/authentication/utils/encryption.util.ts: -------------------------------------------------------------------------------- 1 | import * as bcrypt from 'bcrypt-nodejs'; 2 | 3 | export const generateHashedPassword: (password: string) => string = password => { 4 | return bcrypt.hashSync(password, bcrypt.genSaltSync(8)); 5 | }; 6 | 7 | export const validPassword = (password, passwordHash) => { 8 | return bcrypt.compareSync(password, passwordHash); 9 | }; 10 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/authentication/validators/messages.constants.ts: -------------------------------------------------------------------------------- 1 | export const Messages = { 2 | USER_ID_REQUIRED: 'UserId is required', 3 | EMAIL_IN_USE: 'The email already exists', 4 | }; 5 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/core/core.constants.ts: -------------------------------------------------------------------------------- 1 | export const CoreProviderTokens = {}; 2 | 3 | export const Messages = { 4 | USER_ID_REQUIRED: 'UserId is required', 5 | ORG_ID_REQUIRED: 'OrgId is required', 6 | }; 7 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppSharedModule } from '../../shared/app/app.shared.module'; 3 | import { CachingSharedModule } from '../../shared/caching/caching.shared.module'; 4 | import { MongoSharedModule } from '../../shared/database/mongo/mongo.shared.module'; 5 | import { LoggingSharedModule } from '../../shared/logging/logging.shared.module'; 6 | 7 | @Module({ 8 | imports: [AppSharedModule, MongoSharedModule, LoggingSharedModule, CachingSharedModule], 9 | providers: [], 10 | exports: [AppSharedModule, MongoSharedModule, LoggingSharedModule, CachingSharedModule], 11 | }) 12 | export class DomainCoreModule {} 13 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/organization/model/organization.model.ts: -------------------------------------------------------------------------------- 1 | import { OrganizationEntity } from '@nestjs-bff/global-contracts/lib/domain/organization/organization.entity'; 2 | import { Document } from 'mongoose'; 3 | 4 | export interface IOrganizationModel extends OrganizationEntity, Document {} 5 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/organization/model/organization.schema.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from 'mongoose'; 2 | 3 | export const OrganizationSchema = new Schema( 4 | { 5 | name: { 6 | required: true, 7 | type: Schema.Types.String, 8 | }, 9 | slug: { 10 | required: true, 11 | type: Schema.Types.String, 12 | }, 13 | }, 14 | { 15 | timestamps: true, 16 | collection: 'organization', 17 | }, 18 | ); 19 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/organization/organization.constants.ts: -------------------------------------------------------------------------------- 1 | export const OrganizationProviderTokens = { 2 | Models: { 3 | Organization: 'OrganizationModelToken', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/organization/organization.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongoSharedProviderTokens } from '../../shared/database/mongo/mongo.shared.constants'; 3 | import { DomainCoreModule } from '../core/core.module'; 4 | import { OrganizationSchema } from './model/organization.schema'; 5 | import { OrganizationProviderTokens } from './organization.constants'; 6 | import { OrganizationRepo } from './repo/organization.repo'; 7 | 8 | const OrganizationModel = { 9 | provide: OrganizationProviderTokens.Models.Organization, 10 | useFactory: mongoose => mongoose.connection.model('Organization', OrganizationSchema), 11 | inject: [MongoSharedProviderTokens.Connections.Mongoose], 12 | }; 13 | 14 | @Module({ 15 | imports: [DomainCoreModule], 16 | providers: [OrganizationRepo, OrganizationModel], 17 | exports: [OrganizationRepo], 18 | }) 19 | export class DomainOrganizationModule {} 20 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/user/model/user.model.ts: -------------------------------------------------------------------------------- 1 | import { UserEntity } from '@nestjs-bff/global-contracts/lib/domain/user/user.entity'; 2 | import { Document } from 'mongoose'; 3 | 4 | export interface IUserModel extends UserEntity, Document {} 5 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/user/model/user.schema.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from 'mongoose'; 2 | 3 | export const UserSchema: Schema = new Schema( 4 | { 5 | username: { 6 | type: Schema.Types.String, 7 | required: true, 8 | }, 9 | displayName: { 10 | type: Schema.Types.String, 11 | required: true, 12 | }, 13 | }, 14 | { 15 | timestamps: true, 16 | collection: 'user', 17 | }, 18 | ); 19 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/user/user.constants.ts: -------------------------------------------------------------------------------- 1 | export const UserProviderTokens = { 2 | Models: { 3 | User: 'UserModelToken', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/domain/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongoSharedProviderTokens } from '../../shared/database/mongo/mongo.shared.constants'; 3 | import { DomainCoreModule } from '../core/core.module'; 4 | import { UserSchema } from './model/user.schema'; 5 | import { UserRepo } from './repo/user.repo'; 6 | import { UserProviderTokens } from './user.constants'; 7 | 8 | const UserModel = { 9 | provide: UserProviderTokens.Models.User, 10 | useFactory: mongoose => mongoose.connection.model('User', UserSchema), 11 | inject: [MongoSharedProviderTokens.Connections.Mongoose], 12 | }; 13 | 14 | @Module({ 15 | imports: [DomainCoreModule], 16 | controllers: [], 17 | providers: [UserRepo, UserModel], 18 | exports: [UserRepo], 19 | }) 20 | export class DomainUserModule {} 21 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/application-service-host/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { OrganizationOrchestrationModule } from '../../../../application/organization-orchestration/organization-orchestration.module'; 3 | import { UserAuthModule } from '../../../../application/user-auth/user-auth.module'; 4 | import { DomainAccessPermissionsModule } from '../../../../domain/access-permissions/access-permissions.module'; 5 | import { DomainAuthenticationModule } from '../../../../domain/authentication/authentication.module'; 6 | import { HttpCoreModule } from '../../core/core.module'; 7 | import { AuthController } from './auth.controller'; 8 | 9 | @Module({ 10 | imports: [HttpCoreModule, UserAuthModule, DomainAuthenticationModule, DomainAccessPermissionsModule, OrganizationOrchestrationModule], 11 | controllers: [AuthController], 12 | providers: [], 13 | exports: [], 14 | }) 15 | export class HttpAuthModule {} 16 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/application-service-host/organization-orchestration/organization-orchestration.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { OrganizationOrchestrationModule as DomainOrganizationOrchestrationModule } from '../../../../application/organization-orchestration/organization-orchestration.module'; 3 | import { HttpCoreModule } from '../../core/core.module'; 4 | import { OrganizationOrchestrationController } from './organization-orchestration.controller'; 5 | 6 | @Module({ 7 | imports: [HttpCoreModule, DomainOrganizationOrchestrationModule], 8 | controllers: [OrganizationOrchestrationController], 9 | providers: [], 10 | exports: [], 11 | }) 12 | export class HttpOrganizationOrchestrationModule {} 13 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/application-service-host/web-app-health-check/web-app-health-check.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { WebAppHealthCheckController } from './web-app-health-check.controller'; 3 | import { WebAppHealthCheckService } from './web-app-health-check.service'; 4 | 5 | describe('WebAppController', () => { 6 | let app: TestingModule; 7 | 8 | beforeAll(async () => { 9 | app = await Test.createTestingModule({ 10 | controllers: [WebAppHealthCheckController], 11 | providers: [WebAppHealthCheckService], 12 | }).compile(); 13 | }); 14 | 15 | describe('root', () => { 16 | it('should return "UP"', () => { 17 | const appController = app.get(WebAppHealthCheckController); 18 | expect(appController.root()).toBe('UP'); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/application-service-host/web-app-health-check/web-app-health-check.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { WebAppHealthCheckService } from './web-app-health-check.service'; 3 | 4 | @Controller() 5 | export class WebAppHealthCheckController { 6 | constructor(private readonly appService: WebAppHealthCheckService) {} 7 | 8 | @Get() 9 | public root(): string { 10 | return this.appService.root(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/application-service-host/web-app-health-check/web-app-health-check.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { WebAppHealthCheckController } from './web-app-health-check.controller'; 3 | import { WebAppHealthCheckService } from './web-app-health-check.service'; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [WebAppHealthCheckController], 8 | providers: [WebAppHealthCheckService], 9 | exports: [], 10 | }) 11 | export class WebAppHealthCheckModule {} 12 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/application-service-host/web-app-health-check/web-app-health-check.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class WebAppHealthCheckService { 5 | public root(): string { 6 | return 'UP'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/core/core.constants.ts: -------------------------------------------------------------------------------- 1 | export const HttpCoreProviderTokens = {}; 2 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { DomainAccessPermissionsModule } from '../../../domain/access-permissions/access-permissions.module'; 3 | import { DomainOrganizationModule } from '../../../domain/organization/organization.module'; 4 | import { AppSharedModule } from '../../../shared/app/app.shared.module'; 5 | import { CachingSharedModule } from '../../../shared/caching/caching.shared.module'; 6 | import { LoggingSharedModule } from '../../../shared/logging/logging.shared.module'; 7 | import { AuthorizationGuard } from './guards/authorization.guard'; 8 | import { JwtTokenService } from './jwt/jwt-token.service'; 9 | import { AttachAuthenticationHttpMiddleware } from './middleware/attach-authentication.middleware'; 10 | 11 | @Module({ 12 | imports: [AppSharedModule, DomainAccessPermissionsModule, DomainOrganizationModule, LoggingSharedModule, CachingSharedModule], 13 | providers: [AuthorizationGuard, AttachAuthenticationHttpMiddleware, JwtTokenService], 14 | exports: [LoggingSharedModule, AttachAuthenticationHttpMiddleware, JwtTokenService], 15 | }) 16 | export class HttpCoreModule {} 17 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/core/decorators/authorization.decorator.ts: -------------------------------------------------------------------------------- 1 | import { ReflectMetadata } from '@nestjs/common'; 2 | import { AuthCheckContract } from '../../../../shared/authchecks/authcheck.contract'; 3 | 4 | export const Authorization = (authchecks: Array>) => ReflectMetadata('authorization', authchecks); 5 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/core/decorators/roles.decorator.ts: -------------------------------------------------------------------------------- 1 | import { ReflectMetadata } from '@nestjs/common'; 2 | 3 | export const Roles = (...roles: string[]) => ReflectMetadata('roles', roles); 4 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/core/exceptions/http-exception.filter.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common'; 2 | import { HttpException } from '@nestjs/common'; 3 | import { LoggerSharedService } from '../../../../shared/logging/logger.shared.service'; 4 | 5 | @Catch(HttpException) 6 | export class HttpExceptionFilter implements ExceptionFilter { 7 | constructor(private readonly logger: LoggerSharedService) {} 8 | 9 | catch(exception: HttpException, host: ArgumentsHost) { 10 | const ctx = host.switchToHttp(); 11 | const response = ctx.getResponse(); 12 | const request = ctx.getRequest(); 13 | const status = exception.getStatus(); 14 | 15 | this.logger.warn(`Exception caught in global exception filter: ${exception.message}`, exception); 16 | 17 | response.status(status).json({ 18 | statusCode: exception.getStatus(), 19 | timestamp: new Date().toISOString(), 20 | path: request.url, 21 | message: exception.message, 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/core/exceptions/server.exception.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus } from '@nestjs/common'; 2 | 3 | class ServerHttpError extends HttpException { 4 | constructor(response: string | object, status: number, public metaData?: object) { 5 | super(response, status); 6 | Error.captureStackTrace(this, ServerHttpError); 7 | } 8 | } 9 | 10 | export class BadGatewayHttpError extends ServerHttpError { 11 | constructor(response: string | object, public metaData?: object) { 12 | super(response, HttpStatus.BAD_GATEWAY); 13 | } 14 | } 15 | 16 | export class BadRequestHttpError extends ServerHttpError { 17 | constructor(response: string | object, public metaData?: object) { 18 | super(response, HttpStatus.BAD_REQUEST); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/core/guards/roles.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; 2 | import { Reflector } from '@nestjs/core'; 3 | import { LoggerSharedService } from '../../../../shared/logging/logger.shared.service'; 4 | 5 | @Injectable() 6 | export class RolesHttpGuard implements CanActivate { 7 | constructor( 8 | private readonly reflector: Reflector, 9 | private readonly bffLoggerService: LoggerSharedService, 10 | ) {} 11 | 12 | public canActivate(context: ExecutionContext): boolean { 13 | const roles = this.reflector.get('roles', context.getHandler()); 14 | if (!roles) { 15 | return true; 16 | } 17 | 18 | const request = context.switchToHttp().getRequest(); 19 | const user = request.user; 20 | this.bffLoggerService.debug('RolesGuard', user); 21 | 22 | if (!user || !user.roles) { 23 | return false; 24 | } 25 | 26 | const hasRole = () => 27 | user.roles.some(role => !!roles.find(item => item === role)); 28 | return user && user.roles && hasRole(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/core/interceptors/timeout.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { timeout } from 'rxjs/operators'; 4 | 5 | @Injectable() 6 | export class TimeoutHttpInterceptor implements NestInterceptor { 7 | intercept(context: ExecutionContext, call$: Observable): Observable { 8 | return call$.pipe(timeout(5000)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/core/interceptors/transform.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { map } from 'rxjs/operators'; 4 | 5 | export interface IResponse { 6 | data: T; 7 | } 8 | 9 | @Injectable() 10 | export class TransformHttpInterceptor implements NestInterceptor> { 11 | intercept(context: ExecutionContext, call$: Observable): Observable> { 12 | return call$.pipe(map(data => ({ data }))); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/core/jwt/i-jwt-payload.ts: -------------------------------------------------------------------------------- 1 | export interface IJwtPayload { 2 | readonly iat: number; 3 | readonly exp: number; 4 | readonly aud: string; 5 | readonly iss: string; 6 | readonly sub: string; 7 | readonly roles: string[]; 8 | } 9 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/core/types/bff-request.contract.ts: -------------------------------------------------------------------------------- 1 | import { AccessPermissionsContract } from '@nestjs-bff/global-contracts/lib/domain/access-permissions/access-permissions.contract'; 2 | import { Request as ExpressRequest } from 'express'; 3 | 4 | export interface BffRequest extends ExpressRequest { 5 | accessPermissions?: AccessPermissionsContract; 6 | } 7 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/core/utils/core.utils.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | 3 | export interface IAuthHttpHeader { 4 | scheme: string; 5 | value: string; 6 | } 7 | 8 | export const parseAuthHeader = (hdrValue): IAuthHttpHeader | null => { 9 | if (typeof hdrValue !== 'string') return null; 10 | 11 | const matches = hdrValue.match(/(\S+)\s+(\S+)/); 12 | 13 | if (!matches) return null; 14 | 15 | return { scheme: matches[1].toLowerCase(), value: matches[2] }; 16 | }; 17 | 18 | export const getReqMetadataLite = (req: Request) => { 19 | return { 20 | ip: req.ip, 21 | ips: req.ips, 22 | originalUrl: req.originalUrl, 23 | userAgent: req.headers['user-agent'], 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/host/http/web-app-helper.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication, INestExpressApplication } from '@nestjs/common'; 2 | import { INestjsBffConfig } from '../../config/nestjs-bff.config'; 3 | import { LoggerSharedService } from '../../shared/logging/logger.shared.service'; 4 | import { MigrationsSharedService } from '../../shared/migrations/migrations.shared.service'; 5 | 6 | export class WebAppHelper { 7 | public static async setupNestjsBffApp( 8 | nestBffConfig: INestjsBffConfig, 9 | logger: LoggerSharedService, 10 | app: INestApplication & INestExpressApplication, 11 | ) { 12 | // RUN SETUP STEPS 13 | logger.debug(`AppConfig.migrations.autoRun: ${nestBffConfig.migrations.autoRun}`); 14 | if (nestBffConfig.migrations.autoRun) await app.get(MigrationsSharedService).autoRunMigrations(nestBffConfig.nodeEnv); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/app/app.shared.constants.ts: -------------------------------------------------------------------------------- 1 | export const AppSharedProviderTokens = { 2 | Config: { 3 | App: 'NestjsBffConfigToken', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/app/app.shared.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppSharedProviderTokens } from './app.shared.constants'; 3 | 4 | const NestjsBffConfigProvider = { 5 | provide: AppSharedProviderTokens.Config.App, 6 | useFactory: () => { 7 | // @ts-ignore 8 | return global.nestjs_bff.config; 9 | }, 10 | }; 11 | 12 | @Module({ 13 | imports: [], 14 | providers: [NestjsBffConfigProvider], 15 | exports: [NestjsBffConfigProvider], 16 | }) 17 | export class AppSharedModule {} 18 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/authchecks/always-false.authcheck.ts: -------------------------------------------------------------------------------- 1 | import { AuthCheckContract } from './authcheck.contract'; 2 | import { AuthorizationCheckParams } from './authorization-params'; 3 | 4 | export class AlwaysFalseAuthCheck extends AuthCheckContract { 5 | // tslint:disable-next-line:variable-name 6 | private static _singleton: AuthCheckContract = new AlwaysFalseAuthCheck(); 7 | public static get singleton(): AuthCheckContract { 8 | return AlwaysFalseAuthCheck._singleton; 9 | } 10 | public async isAuthorized(params: AuthorizationCheckParams): Promise { 11 | return false; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/authchecks/always-true.authcheck.ts: -------------------------------------------------------------------------------- 1 | import { AuthCheckContract } from './authcheck.contract'; 2 | import { AuthorizationCheckParams } from './authorization-params'; 3 | 4 | export class AlwaysTrueAuthCheck extends AuthCheckContract { 5 | private static _singleton: AuthCheckContract = new AlwaysTrueAuthCheck(); 6 | public static get singleton(): AuthCheckContract { 7 | return AlwaysTrueAuthCheck._singleton; 8 | } 9 | 10 | public async isAuthorized(params: AuthorizationCheckParams): Promise { 11 | return true; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/authchecks/authcheck.contract.ts: -------------------------------------------------------------------------------- 1 | import { AuthorizationCheckError } from '../exceptions/authorization-check.exception'; 2 | import { AuthorizationCheckParams } from './authorization-params'; 3 | 4 | export abstract class AuthCheckContract { 5 | abstract isAuthorized(params: AuthorizationCheckParams): Promise; 6 | 7 | async ensureAuthorized(params: AuthorizationCheckParams): Promise { 8 | if (!(await this.isAuthorized(params))) { 9 | throw new AuthorizationCheckError(params, `Authorization Error in ${this.constructor.name}: `); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/authchecks/authorization-params.ts: -------------------------------------------------------------------------------- 1 | import { AccessPermissionsContract } from '@nestjs-bff/global-contracts/lib/domain/access-permissions/access-permissions.contract'; 2 | 3 | export class AuthorizationCheckParams { 4 | accessPermissions: AccessPermissionsContract | null | undefined; 5 | origin: string = ''; 6 | operation?: TOperations; 7 | targetResource?: TResource; 8 | data?: any; 9 | } 10 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/authchecks/crud-operations.enum.ts: -------------------------------------------------------------------------------- 1 | export enum CrudOperations { 2 | create = 'create', 3 | read = 'read', 4 | update = 'update', 5 | delete = 'delete', 6 | } 7 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/authchecks/org-access.authcheck.ts: -------------------------------------------------------------------------------- 1 | import { AuthorizationCheckError } from '../exceptions/authorization-check.exception'; 2 | import { AuthCheckContract } from './authcheck.contract'; 3 | import { hasOrganization, isStaffAdmin } from './authcheck.utils'; 4 | import { AuthorizationCheckParams } from './authorization-params'; 5 | import { ScopedData } from './scoped-data'; 6 | 7 | export class OrgAccessAuthCheck extends AuthCheckContract { 8 | public async isAuthorized(params: AuthorizationCheckParams): Promise { 9 | if (!params.targetResource || !params.targetResource.orgId) 10 | throw new AuthorizationCheckError(params, 'OrgAccessAuthCheck - organizationSlugForRequestedResource can not be null'); 11 | 12 | if (!params.accessPermissions) return false; 13 | if (isStaffAdmin(params.accessPermissions)) return true; 14 | return hasOrganization(params.accessPermissions, params.targetResource.orgId); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/authchecks/org-roles.authcheck.ts: -------------------------------------------------------------------------------- 1 | import { AuthorizationCheckError } from '../exceptions/authorization-check.exception'; 2 | import { AuthCheckContract } from './authcheck.contract'; 3 | import { hasOrganizationRole, isStaffAdmin } from './authcheck.utils'; 4 | import { AuthorizationCheckParams } from './authorization-params'; 5 | import { ScopedData } from './scoped-data'; 6 | 7 | export class OrgRolesAuthCheck extends AuthCheckContract { 8 | constructor(private readonly qualifyingRoles: string[]) { 9 | super(); 10 | } 11 | 12 | public async isAuthorized(params: AuthorizationCheckParams): Promise { 13 | if (!params.targetResource || !params.targetResource.orgId) throw new AuthorizationCheckError(params, 'AuthorizationCheckParams - orgId can not be null'); 14 | 15 | if (!params.accessPermissions) return false; 16 | 17 | if (isStaffAdmin(params.accessPermissions)) return true; 18 | return hasOrganizationRole(params.accessPermissions, params.targetResource.orgId, this.qualifyingRoles); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/authchecks/role.authcheck.ts: -------------------------------------------------------------------------------- 1 | import { Roles } from '@nestjs-bff/global-contracts/lib/shared/authorization/roles.constants'; 2 | import { AuthCheckContract } from './authcheck.contract'; 3 | import { hasRole } from './authcheck.utils'; 4 | import { AuthorizationCheckParams } from './authorization-params'; 5 | 6 | export class RoleAuthCheck extends AuthCheckContract { 7 | constructor(private readonly qualifyingRole: string) { 8 | super(); 9 | // validation 10 | if (!Object.keys(Roles).includes(qualifyingRole)) { 11 | throw new Error(`Role ${qualifyingRole} not a valid role`); 12 | } 13 | } 14 | 15 | public async isAuthorized(params: AuthorizationCheckParams): Promise { 16 | if (!params.accessPermissions) return false; 17 | 18 | return hasRole(params.accessPermissions, this.qualifyingRole); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/authchecks/scoped-data.ts: -------------------------------------------------------------------------------- 1 | export class ScopedData { 2 | orgId?: string; 3 | userId?: string; 4 | } 5 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/caching/cache-store.shared.ts: -------------------------------------------------------------------------------- 1 | import * as CacheManager from 'cache-manager'; 2 | export type CacheStore = ReturnType; 3 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/caching/caching.shared.constants.ts: -------------------------------------------------------------------------------- 1 | export const CachingProviderTokens = { 2 | Services: { 3 | CacheStore: 'CacheStore', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/caching/caching.shared.module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CACHE_MANAGER, 3 | CacheModule as NestCacheModule, 4 | CacheStore, 5 | Module, 6 | } from '@nestjs/common'; 7 | import { CachingProviderTokens } from './caching.shared.constants'; 8 | 9 | const CacheModule = NestCacheModule.register({ 10 | ttl: 20, // seconds 11 | max: 1000, // max number of items in cache 12 | }); 13 | 14 | const CacheStoreProvider = { 15 | provide: CachingProviderTokens.Services.CacheStore, 16 | useFactory: (cacheStore: CacheStore): CacheStore => { 17 | return cacheStore; 18 | }, 19 | inject: [CACHE_MANAGER], 20 | }; 21 | 22 | @Module({ 23 | imports: [CacheModule], 24 | providers: [CacheStoreProvider], 25 | exports: [CacheModule, CacheStoreProvider], 26 | }) 27 | export class CachingSharedModule {} 28 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/caching/caching.utils.ts: -------------------------------------------------------------------------------- 1 | import { Validator } from 'class-validator'; 2 | import * as stringify from 'json-stable-stringify'; 3 | import { hash } from '../utils/hash.utils'; 4 | 5 | /** 6 | * @description: Utility functions to support caching operations 7 | * @notes: 8 | * - Uses json-stable-stringify (instead of JSON.Stringify) for determanistic string generation - regardless of parameter ordering 9 | * - Uses custom hash function as significantly faster than cryptogaphic hashes 10 | */ 11 | export class CachingUtils { 12 | private static validator = new Validator(); 13 | 14 | public static makeCacheKeyFromId(entityId: string): string { 15 | this.validator.isMongoId(entityId); 16 | return this.makeCacheKeyFromProperty(entityId, 'id'); 17 | } 18 | 19 | public static makeCacheKeyFromProperty( 20 | propertyName: string, 21 | propertyValue: string, 22 | ): string { 23 | this.validator.isNotEmpty(propertyValue); 24 | this.validator.isNotEmpty(propertyName); 25 | return `CacheKey-${propertyName}-${propertyValue}`; 26 | } 27 | 28 | public static makeCacheKeyFromObject(object: object): string { 29 | return hash(stringify(object)).toString(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/database/mongo/mongo.shared.constants.ts: -------------------------------------------------------------------------------- 1 | export const MongoSharedProviderTokens = { 2 | Connections: { 3 | Mongoose: 'MongooseConnectionToken', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/database/mongo/mongo.shared.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import * as mongoose from 'mongoose'; 3 | import { INestjsBffConfig } from '../../../config/nestjs-bff.config'; 4 | import { AppSharedProviderTokens } from '../../app/app.shared.constants'; 5 | import { AppSharedModule } from '../../app/app.shared.module'; 6 | import { MongoSharedProviderTokens } from './mongo.shared.constants'; 7 | 8 | const MongooseConnectionProvider = { 9 | provide: MongoSharedProviderTokens.Connections.Mongoose, 10 | useFactory: async (nestjsBffConfig: INestjsBffConfig): Promise => { 11 | const con = await mongoose.connect( 12 | nestjsBffConfig.db.mongo.mongoConnectionUri, 13 | nestjsBffConfig.db.mongo.options, 14 | ); 15 | mongoose.set('debug', nestjsBffConfig.db.mongo.debugLogging); 16 | return con; 17 | }, 18 | inject: [AppSharedProviderTokens.Config.App], 19 | }; 20 | 21 | @Module({ 22 | imports: [AppSharedModule], 23 | providers: [MongooseConnectionProvider], 24 | exports: [MongooseConnectionProvider], 25 | }) 26 | export class MongoSharedModule {} 27 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/exceptions/app.exception.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus } from '@nestjs/common'; 2 | 3 | export class AppError extends HttpException { 4 | constructor(message: string, public metaData?: object) { 5 | super(message, HttpStatus.BAD_GATEWAY); 6 | Error.captureStackTrace(this, AppError); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/exceptions/authorization-check.exception.ts: -------------------------------------------------------------------------------- 1 | import { AuthorizationCheckParams } from '../authchecks/authorization-params'; 2 | import { AuthorizationError } from './authorization.exception'; 3 | 4 | export class AuthorizationCheckError extends AuthorizationError { 5 | constructor(authorizationCheckParams: AuthorizationCheckParams, message?: string) { 6 | message = (message || 'Authorization failed: ') + `${JSON.stringify(authorizationCheckParams, null, 2)}`; 7 | super(message, authorizationCheckParams); 8 | Error.captureStackTrace(this, AuthorizationError); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/exceptions/authorization.exception.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus } from '@nestjs/common'; 2 | 3 | export class AuthorizationError extends HttpException { 4 | constructor(message: string, public metaData?: object) { 5 | super(message, HttpStatus.UNAUTHORIZED); 6 | Error.captureStackTrace(this, AuthorizationError); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/exceptions/validation.exception.ts: -------------------------------------------------------------------------------- 1 | import { HttpException, HttpStatus } from '@nestjs/common'; 2 | import { ValidatorOptions } from '@nestjs/common/interfaces/external/validator-options.interface'; 3 | 4 | export class ValidationError extends HttpException { 5 | public messages: string[]; 6 | 7 | constructor(messages: string[], public metaData?: { target?: any; options: ValidatorOptions; data: any }) { 8 | super(messages && messages.length > 0 ? messages[0] : 'Validation Error', HttpStatus.BAD_REQUEST); 9 | this.messages = messages; 10 | console.log(JSON.stringify(metaData, null, 2)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/logging/log-levels.const.ts: -------------------------------------------------------------------------------- 1 | export const LogLevels = { 2 | error: 'error', 3 | warning: 'warning', 4 | info: 'info', 5 | debug: 'debug', 6 | trace: 'trace', 7 | }; 8 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/logging/logger.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, MiddlewareFunction, NestMiddleware } from '@nestjs/common'; 2 | import { LoggerSharedService } from '../../shared/logging/logger.shared.service'; 3 | 4 | @Injectable() 5 | export class LoggerMiddleware implements NestMiddleware { 6 | constructor(private readonly logger: LoggerSharedService) {} 7 | 8 | resolve(context: string): MiddlewareFunction { 9 | return (req, res, next) => { 10 | this.logger.debug(`[${context}] Request...`); 11 | if (next) next(); 12 | }; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/logging/logger.shared.service.ts: -------------------------------------------------------------------------------- 1 | export abstract class LoggerSharedService { 2 | public abstract log(msg: string, ...logObjects: any[]): void; 3 | 4 | public abstract info(msg: string, ...logObjects: any[]): void; 5 | 6 | public abstract error(msg: string, ...logObjects: any[]): void; 7 | 8 | public abstract warn(msg: string, ...logObjects: any[]): void; 9 | 10 | public abstract debug(msg: string, ...logObjects: any[]): void; 11 | 12 | public abstract trace(msg: string, ...logObjects: any[]): void; 13 | } 14 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/logging/logging.constants.ts: -------------------------------------------------------------------------------- 1 | export const LoggingProviderTokens = {}; 2 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/logging/logging.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; 2 | import { Observable } from 'rxjs'; 3 | import { tap } from 'rxjs/operators'; 4 | import { LoggerSharedService } from '../../shared/logging/logger.shared.service'; 5 | 6 | @Injectable() 7 | export class LoggingInterceptor implements NestInterceptor { 8 | constructor(private readonly logger: LoggerSharedService) {} 9 | 10 | public intercept( 11 | context: ExecutionContext, 12 | call$: Observable, 13 | ): Observable { 14 | this.logger.trace('Before...'); 15 | 16 | const now = Date.now(); 17 | return call$.pipe( 18 | tap(() => this.logger.trace(`After... ${Date.now() - now}ms`)), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/logging/logging.shared.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { NestjsBffConfig } from '../../config/nestjs-bff.config'; 3 | import { LoggerConsoleSharedService } from './console-logger.shared.service'; 4 | import { LoggerWinstonSharedService } from './logger-winston.shared.service'; 5 | import { LoggerSharedService } from './logger.shared.service'; 6 | 7 | export const getLogger = (): LoggerSharedService => { 8 | return NestjsBffConfig.nodeEnv === 'prod' 9 | ? new LoggerWinstonSharedService(NestjsBffConfig) 10 | : new LoggerConsoleSharedService(NestjsBffConfig); 11 | }; 12 | 13 | const LoggerSharedServiceProvider = { 14 | provide: LoggerSharedService, 15 | useFactory: getLogger, 16 | }; 17 | 18 | @Module({ 19 | imports: [], 20 | providers: [LoggerSharedServiceProvider], 21 | exports: [LoggerSharedServiceProvider], 22 | }) 23 | export class LoggingSharedModule {} 24 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/migrations/interfaces/migration.interface.ts: -------------------------------------------------------------------------------- 1 | import { Document } from 'mongoose'; 2 | 3 | export enum MigrationState { 4 | Down = 'down', 5 | Up = 'up', 6 | } 7 | 8 | export interface IMigration extends Document { 9 | readonly name: string; 10 | readonly createdAt: Date; 11 | readonly state: MigrationState; 12 | readonly filename: string; 13 | } 14 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/migrations/migrations.shared.constants.ts: -------------------------------------------------------------------------------- 1 | export const MigrationsProviderTokens = { 2 | Models: { 3 | Migration: 'MigrationModelToken', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/migrations/migrations.shared.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { Mongoose } from 'mongoose'; 3 | import { MongoSharedProviderTokens } from '../database/mongo/mongo.shared.constants'; 4 | import { MongoSharedModule } from '../database/mongo/mongo.shared.module'; 5 | import { LoggingSharedModule } from '../logging/logging.shared.module'; 6 | import { MigrationsProviderTokens } from './migrations.shared.constants'; 7 | import { MigrationsSharedService } from './migrations.shared.service'; 8 | import { MigrationSchema } from './schemas/migration.schema'; 9 | 10 | const MigrationModelProvider = { 11 | provide: MigrationsProviderTokens.Models.Migration, 12 | useFactory: (mongoose: Mongoose) => 13 | mongoose.connection.model('Migration', MigrationSchema), 14 | inject: [MongoSharedProviderTokens.Connections.Mongoose], 15 | }; 16 | 17 | @Module({ 18 | imports: [MongoSharedModule, LoggingSharedModule], 19 | providers: [MigrationsSharedService, MigrationModelProvider], 20 | exports: [MigrationsSharedService], 21 | }) 22 | export class MigrationsSharedModule {} 23 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/migrations/schemas/migration.schema.ts: -------------------------------------------------------------------------------- 1 | import { Schema } from 'mongoose'; 2 | 3 | export const MigrationSchema = new Schema( 4 | { 5 | name: String, 6 | createdAt: Schema.Types.Date, 7 | state: { 8 | type: Schema.Types.String, 9 | enum: ['down', 'up'], 10 | default: 'down', 11 | }, 12 | }, 13 | { 14 | timestamps: true, 15 | toJSON: { 16 | virtuals: true, 17 | transform: (doc, ret, options) => { 18 | delete ret.id; 19 | delete ret.id; 20 | delete ret.__v; 21 | return ret; 22 | }, 23 | }, 24 | }, 25 | ); 26 | 27 | MigrationSchema.virtual('filename').get(function() { 28 | // @ts-ignore 29 | return `${this.createdAt.getTime()}-${this.name}.ts`; 30 | }); 31 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/testing/jest.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestingUtils } from '@nestjs-bff/global-utils-dev/lib/testing.utils'; 2 | import 'jest'; 3 | import { ObjectId } from 'mongodb'; 4 | 5 | // 6 | // Testing Jest capabilities 7 | // 8 | 9 | describe('GIVEN an Object with ObjectId types fields', () => { 10 | it(`should match an equivalent object with string fields after stringifying and parsing`, async () => { 11 | const o = { 12 | _id: new ObjectId('5c36b1295ec9e3fbdc3d1062'), 13 | name: 'Foo', 14 | orgId: new ObjectId('5c36b1295ec9e3fbdc3d1054'), 15 | }; 16 | 17 | const objectToMatch = { 18 | _id: '5c36b1295ec9e3fbdc3d1062', 19 | name: 'Foo', 20 | orgId: '5c36b1295ec9e3fbdc3d1054', 21 | }; 22 | 23 | // convert ObjectIds to Strings 24 | const os = TestingUtils.objectIdsToStrings(o); 25 | 26 | expect(o).not.toMatchObject(objectToMatch); 27 | expect(os).toMatchObject(objectToMatch); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/utils/hash.utils.ts: -------------------------------------------------------------------------------- 1 | // based on https://github.com/darkskyapp/string-hash/blob/master/index.js 2 | 3 | export function hash(str) { 4 | let _hash = 5381; 5 | let i = str.length; 6 | 7 | while (i) { 8 | // tslint:disable-next-line:no-bitwise 9 | _hash = (_hash * 33) ^ str.charCodeAt(--i); 10 | } 11 | 12 | /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed 13 | * integers. Since we want the results to be always positive, convert the 14 | * signed int to an unsigned by doing an unsigned bitshift. */ 15 | 16 | // tslint:disable-next-line:no-bitwise 17 | return _hash >>> 0; 18 | } 19 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/shared/utils/key.shared.utils.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | 3 | export function extractKey(path: string) { 4 | return readFileSync(path).toString(); 5 | } 6 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig-base", 3 | "compilerOptions": { 4 | "outDir": "../lib", 5 | "rootDir": "." 6 | }, 7 | "include": ["./**/*"], 8 | "references": [{ "path": "../../global-contracts" }, { "path": "../../global-utils-prod" }, { "path": "../../global-utils-dev" }] 9 | } 10 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/types/node.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace NodeJS { 2 | interface Global { 3 | nestjs_bff: any; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /pkg-bff/backend/src/types/typings.d.ts: -------------------------------------------------------------------------------- 1 | import { AccessPermissionsContract } from '@nestjs-bff/global-contracts/lib/domain/access-permissions/access-permissions.contract'; 2 | 3 | declare module '*.json' { 4 | const value: any; 5 | export default value; 6 | } 7 | 8 | declare global { 9 | namespace Express { 10 | export interface Request { 11 | accessPermissions?: AccessPermissionsContract; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg-bff/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base", 3 | "files": [], 4 | "include": [], 5 | "references": [{ "path": "./src" }, { "path": "./e2e" }] 6 | } 7 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ -------------------------------------------------------------------------------- /pkg-bff/global-contracts/.npmignore: -------------------------------------------------------------------------------- 1 | # Empty to override .gitignore ignoring the output folder -------------------------------------------------------------------------------- /pkg-bff/global-contracts/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ahrnee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/README.md: -------------------------------------------------------------------------------- 1 | # nestjs-bff-global 2 | 3 | This is a companion package for [nestjs-bff](https://github.com/ahrnee/nestjs-bff). This package contains the global code that is shared between the backend api and the client. Typically, this is contracts and DTOs, but it can also contain global logic. 4 | 5 | ## Overview 6 | 7 | [NestJS-BFF](https://github.com/ahrnee/nestjs-bff) is a starter project for those looking to fast-track building a strongly typed, enterprise-grade, modern NodeJs application, with supporting tooling. 8 | 9 | This implementation uses the [BFF](https://samnewman.io/patterns/architectural/bff/) pattern, leveraging [NestJS](https://nestjs.com/) as the primary framework for the backend API. The frontend example is in [Angular](https://angular.io/), although any client-side Javascript framework can easily be used, including [React](https://reactjs.org/), or [Vue](https://vuejs.org/) js. 10 | 11 | ## Documentation 12 | 13 | Documentation and further details can be found in the [nestjs-bff github repo](https://github.com/ahrnee/nestjs-bff) 14 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nestjs-bff/global-contracts", 3 | "version": "1.1.4", 4 | "description": "A backend and frontend module for nestjs-bff", 5 | "author": "ahrnee ", 6 | "license": "MIT", 7 | "homepage": "https://github.com/ahrnee/nestjs-bff", 8 | "directories": { 9 | "src": "src" 10 | }, 11 | "files": [ 12 | "lib", 13 | "README.md", 14 | "LICENSE.md" 15 | ], 16 | "types": "./lib", 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/ahrnee/nestjs-bff.git" 20 | }, 21 | "scripts": { 22 | "tsc": "tsc", 23 | "test": "jest --config=./jest.json" 24 | }, 25 | "publishConfig": { 26 | "access": "public" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/ahrnee/nestjs-bff/issues" 30 | }, 31 | "dependencies": { 32 | "class-validator": "^0.9.1" 33 | }, 34 | "devDependencies": { 35 | "jest": "^23.6.0", 36 | "jest-extended": "^0.11.0", 37 | "prettier": "^1.15.2", 38 | "tslint": "~5.11.0", 39 | "typescript": "~3.2.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/application/auth/create-organization-member.command.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsNotEmpty, IsString, Length } from 'class-validator'; 2 | export class CreateOrganizationMemberCommand { 3 | @IsNotEmpty() 4 | public readonly orgId: string = ''; 5 | 6 | @IsEmail() 7 | @Length(6, 64) 8 | public readonly username: string = ''; 9 | 10 | @IsString() 11 | @Length(1, 32) 12 | public readonly displayName: string = ''; 13 | 14 | @IsString() 15 | @Length(8, 64) 16 | public readonly password: string = ''; 17 | } 18 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/application/auth/local-authenticate.command.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsString, Length } from 'class-validator'; 2 | 3 | export class LocalAuthenticateCommand { 4 | @IsEmail() 5 | @Length(6, 64) 6 | public readonly username: string = ''; 7 | 8 | @IsString() 9 | @Length(8, 32) 10 | public readonly password: string = ''; 11 | } 12 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/application/auth/local-register.command.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsString, Length } from 'class-validator'; 2 | 3 | export class LocalRegisterCommand { 4 | @IsEmail() 5 | @Length(6, 64) 6 | public readonly username: string = ''; 7 | 8 | @IsString() 9 | @Length(1, 32) 10 | public readonly displayName: string = ''; 11 | 12 | @IsString() 13 | @Length(8, 32) 14 | public readonly password: string = ''; 15 | } 16 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/application/auth/promote-to-group-admin.command.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from 'class-validator'; 2 | 3 | export class PromoteToGroupAdminCommand { 4 | @IsNotEmpty() 5 | public readonly userId: string = ''; 6 | } 7 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/domain/access-permissions/access-permissions.contract.ts: -------------------------------------------------------------------------------- 1 | export class AccessPermissionsContract { 2 | userId?: string; 3 | roles: string[] = []; 4 | organizations?: UserOrgPermissionsContract[]; 5 | } 6 | export class UserOrgPermissionsContract { 7 | primary?: boolean; 8 | orgId?: string; 9 | organizationRoles: string[] = []; 10 | } 11 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/domain/core/base.entity.ts: -------------------------------------------------------------------------------- 1 | import { IEntity } from './entity.interface'; 2 | 3 | export abstract class BaseEntity implements IEntity { 4 | id?: any; 5 | } 6 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/domain/core/core.constants.ts: -------------------------------------------------------------------------------- 1 | export const ValidationGroups = { 2 | QUERY_REQUIRED: 'query-required', 3 | }; -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/domain/core/entity.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IEntity { 2 | // fields 3 | id?: any; 4 | } 5 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/domain/core/org-scoped.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from './base.entity'; 2 | import { IsNotEmpty } from 'class-validator'; 3 | 4 | export abstract class OrgScopedEntity extends BaseEntity { 5 | @IsNotEmpty() 6 | public orgId: string | undefined = undefined; 7 | } 8 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/domain/core/user-and-org-scoped.entity.ts: -------------------------------------------------------------------------------- 1 | import { OrgScopedEntity } from './org-scoped.entity'; 2 | import { IsNotEmpty } from 'class-validator'; 3 | 4 | export abstract class UserAndOrgScopedEntity extends OrgScopedEntity { 5 | @IsNotEmpty() 6 | public userId: string | undefined = undefined; 7 | } 8 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/domain/organization/organization.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../core/base.entity'; 2 | import { Length } from 'class-validator'; 3 | 4 | export class OrganizationEntity extends BaseEntity { 5 | @Length(2, 50) 6 | name?: string; 7 | 8 | @Length(2, 50) 9 | slug?: string; 10 | } 11 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/domain/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../core/base.entity'; 2 | import { Length } from 'class-validator'; 3 | 4 | export class UserEntity extends BaseEntity { 5 | @Length(2, 50) 6 | username?: string; 7 | 8 | @Length(2, 50) 9 | displayName?: string; 10 | } 11 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/shared/authentication/authentication-token.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IAuthenticationToken { 2 | readonly token: string; 3 | } 4 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/src/shared/authorization/roles.constants.ts: -------------------------------------------------------------------------------- 1 | export const Roles = { 2 | user: 'user', 3 | groupAdmin: 'groupAdmin', 4 | staffAdmin: 'staffAdmin', 5 | systemAdmin: 'systemAdmin', 6 | }; 7 | 8 | export const OrganizationRoles = { 9 | member: 'member', 10 | facilitator: 'facilitator', 11 | admin: 'admin', 12 | }; 13 | -------------------------------------------------------------------------------- /pkg-bff/global-contracts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /pkg-bff/global-utils-dev/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ -------------------------------------------------------------------------------- /pkg-bff/global-utils-dev/.npmignore: -------------------------------------------------------------------------------- 1 | # Empty to override .gitignore ignoring the output folder -------------------------------------------------------------------------------- /pkg-bff/global-utils-dev/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ahrnee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg-bff/global-utils-dev/README.md: -------------------------------------------------------------------------------- 1 | # nestjs-bff-global 2 | 3 | This is a companion package for [nestjs-bff](https://github.com/ahrnee/nestjs-bff). This package contains the global code that is shared between the backend api and the client. Typically, this is contracts and DTOs, but it can also contain global logic. 4 | 5 | ## Overview 6 | 7 | [NestJS-BFF](https://github.com/ahrnee/nestjs-bff) is a starter project for those looking to fast-track building a strongly typed, enterprise-grade, modern NodeJs application, with supporting tooling. 8 | 9 | This implementation uses the [BFF](https://samnewman.io/patterns/architectural/bff/) pattern, leveraging [NestJS](https://nestjs.com/) as the primary framework for the backend API. The frontend example is in [Angular](https://angular.io/), although any client-side Javascript framework can easily be used, including [React](https://reactjs.org/), or [Vue](https://vuejs.org/) js. 10 | 11 | ## Documentation 12 | 13 | Documentation and further details can be found in the [nestjs-bff github repo](https://github.com/ahrnee/nestjs-bff) 14 | -------------------------------------------------------------------------------- /pkg-bff/global-utils-dev/jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootDir": "src", 3 | "preset": "ts-jest", 4 | "testEnvironment": "node", 5 | "testMatch": ["/**/*.spec.(ts|tsx|js)"], 6 | "modulePaths": ["/"], 7 | "collectCoverageFrom": ["/**/*.{js,jsx,tsx,ts}", "!**/node_modules/**", "!**/vendor/**"], 8 | "coverageReporters": ["json", "lcov"], 9 | "coverageDirectory": "coverage", 10 | "clearMocks": true 11 | } 12 | -------------------------------------------------------------------------------- /pkg-bff/global-utils-dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nestjs-bff/global-utils-dev", 3 | "version": "1.1.4", 4 | "description": "A backend and frontend module for nestjs-bff", 5 | "author": "ahrnee ", 6 | "license": "MIT", 7 | "homepage": "https://github.com/ahrnee/nestjs-bff", 8 | "directories": { 9 | "src": "src" 10 | }, 11 | "files": [ 12 | "lib", 13 | "README.md", 14 | "LICENSE.md" 15 | ], 16 | "types": "./lib", 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/ahrnee/nestjs-bff.git" 20 | }, 21 | "scripts": { 22 | "tsc": "tsc", 23 | "test": "jest --config=./jest.json" 24 | }, 25 | "publishConfig": { 26 | "access": "public" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/ahrnee/nestjs-bff/issues" 30 | }, 31 | "devDependencies": { 32 | "@types/jest": "^23.3.12", 33 | "jest": "^23.6.0", 34 | "prettier": "^1.15.2", 35 | "ts-jest": "^23.10.5", 36 | "tslint": "~5.11.0", 37 | "typescript": "~3.2.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg-bff/global-utils-dev/src/testing.utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestingUtils } from './testing.utils'; 2 | import 'jest'; 3 | 4 | const performanceTestIterations = 1000; 5 | 6 | describe('GIVEN TestingUtils.objectIdsToStrings method', () => { 7 | it(`WHEN passing an Object with ObjectId types fields 8 | THEN execution time should be fast`, async () => { 9 | const o = { 10 | id: TestingUtils.generateMongoObjectIdString(), 11 | name: 'Foo', 12 | orgId: TestingUtils.generateMongoObjectIdString(), 13 | }; 14 | 15 | const objectIdsToStringsFunc = () => { 16 | TestingUtils.objectIdsToStrings(o); 17 | }; 18 | 19 | const processingTime = TestingUtils.measureExecutionTime(objectIdsToStringsFunc, performanceTestIterations); 20 | 21 | expect(processingTime).toBeLessThan(100); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /pkg-bff/global-utils-dev/src/testing.utils.ts: -------------------------------------------------------------------------------- 1 | export class TestingUtils { 2 | public static measureExecutionTime = (func: () => any, iterations: number): number => { 3 | const start = new Date(); 4 | for (let i = 0; i < iterations; i++) { 5 | func(); 6 | } 7 | const finish = new Date(); 8 | return finish.getTime() - start.getTime(); 9 | } 10 | 11 | public static generateMongoObjectIdString() { 12 | // tslint:disable-next-line:no-bitwise 13 | const timestamp = ((new Date().getTime() / 1000) | 0).toString(16); 14 | const objectId = 15 | timestamp + 16 | 'xxxxxxxxxxxxxxxx' 17 | .replace(/[x]/g, () => { 18 | // tslint:disable-next-line:no-bitwise 19 | return ((Math.random() * 16) | 0).toString(16); 20 | }) 21 | .toLowerCase(); 22 | 23 | return objectId; 24 | } 25 | 26 | public static objectIdsToStrings(obj: object): object { 27 | return JSON.parse(JSON.stringify(obj)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pkg-bff/global-utils-dev/src/type.util.ts: -------------------------------------------------------------------------------- 1 | export function reduceToType(type: { new (): T }, source: object): T { 2 | const destination = new type(); 3 | Object.keys(type).forEach((key) => (destination[key] = source[key])); 4 | return destination; 5 | } 6 | -------------------------------------------------------------------------------- /pkg-bff/global-utils-dev/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /pkg-bff/global-utils-prod/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ -------------------------------------------------------------------------------- /pkg-bff/global-utils-prod/.npmignore: -------------------------------------------------------------------------------- 1 | # Empty to override .gitignore ignoring the output folder -------------------------------------------------------------------------------- /pkg-bff/global-utils-prod/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ahrnee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg-bff/global-utils-prod/README.md: -------------------------------------------------------------------------------- 1 | # nestjs-bff-global 2 | 3 | This is a companion package for [nestjs-bff](https://github.com/ahrnee/nestjs-bff). This package contains the global code that is shared between the backend api and the client. Typically, this is contracts and DTOs, but it can also contain global logic. 4 | 5 | ## Overview 6 | 7 | [NestJS-BFF](https://github.com/ahrnee/nestjs-bff) is a starter project for those looking to fast-track building a strongly typed, enterprise-grade, modern NodeJs application, with supporting tooling. 8 | 9 | This implementation uses the [BFF](https://samnewman.io/patterns/architectural/bff/) pattern, leveraging [NestJS](https://nestjs.com/) as the primary framework for the backend API. The frontend example is in [Angular](https://angular.io/), although any client-side Javascript framework can easily be used, including [React](https://reactjs.org/), or [Vue](https://vuejs.org/) js. 10 | 11 | ## Documentation 12 | 13 | Documentation and further details can be found in the [nestjs-bff github repo](https://github.com/ahrnee/nestjs-bff) 14 | -------------------------------------------------------------------------------- /pkg-bff/global-utils-prod/jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootDir": "src", 3 | "preset": "ts-jest", 4 | "testEnvironment": "node", 5 | "testMatch": ["/**/*.spec.(ts|tsx|js)"], 6 | "modulePaths": ["/"], 7 | "collectCoverageFrom": ["/**/*.{js,jsx,tsx,ts}", "!**/node_modules/**", "!**/vendor/**"], 8 | "coverageReporters": ["json", "lcov"], 9 | "coverageDirectory": "coverage", 10 | "clearMocks": true 11 | } 12 | -------------------------------------------------------------------------------- /pkg-bff/global-utils-prod/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nestjs-bff/global-utils-prod", 3 | "version": "1.1.4", 4 | "description": "A backend and frontend module for nestjs-bff", 5 | "author": "ahrnee ", 6 | "license": "MIT", 7 | "homepage": "https://github.com/ahrnee/nestjs-bff", 8 | "directories": { 9 | "src": "src" 10 | }, 11 | "files": [ 12 | "lib", 13 | "README.md", 14 | "LICENSE.md" 15 | ], 16 | "types": "./lib", 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/ahrnee/nestjs-bff.git" 20 | }, 21 | "scripts": { 22 | "tsc": "tsc", 23 | "test": "jest --config=./jest.json" 24 | }, 25 | "publishConfig": { 26 | "access": "public" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/ahrnee/nestjs-bff/issues" 30 | }, 31 | "devDependencies": { 32 | "@types/jest": "^23.3.12", 33 | "jest": "^23.6.0", 34 | "prettier": "^1.15.2", 35 | "ts-jest": "^23.10.5", 36 | "tslint": "~5.11.0", 37 | "typescript": "~3.2.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg-bff/global-utils-prod/src/object.util.ts: -------------------------------------------------------------------------------- 1 | export const isPlainObject = (obj) => { 2 | return Object.prototype.toString.call(obj) === '[object Object]'; 3 | }; -------------------------------------------------------------------------------- /pkg-bff/global-utils-prod/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig-base", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /pkg-bff/tsconfig-base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "declaration": true, 5 | "declarationMap": true, 6 | "strict": true, 7 | "sourceMap": true, 8 | 9 | "forceConsistentCasingInFileNames": true, 10 | "noImplicitReturns": true, 11 | "noUnusedLocals": true, 12 | "module": "commonjs", 13 | "noImplicitAny": false, 14 | "removeComments": false, 15 | "noLib": false, 16 | "emitDecoratorMetadata": true, 17 | "experimentalDecorators": true, 18 | "target": "es6", 19 | "lib": ["es7", "dom"], 20 | "typeRoots": ["node_modules/@types", "../node_modules/@types"], 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pkg-bff/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-base", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { "path": "./backend" }, 7 | { "path": "./global-contracts" }, 8 | { "path": "./global-utils-dev" }, 9 | { "path": "./global-utils-prod" } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig-all-ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "include": [], 4 | "references": [{ "path": "./tsconfig-pkg-ref.json" }, { "path": "frontend" }, { "path": "backend" }, { "path": "cli" }] 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig-pkg-ref.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "include": [], 4 | "references": [{ "path": "global" }, { "path": "pkg-bff" }] 5 | } 6 | --------------------------------------------------------------------------------