├── README.md ├── data └── README.md ├── src ├── app │ ├── constants │ │ ├── app.constant.ts │ │ └── app.enum.constant.ts │ ├── app.module.ts │ ├── docs │ │ └── app.doc.ts │ └── serializations │ │ └── app.hello.serialization.ts ├── languages │ ├── en │ │ ├── health.json │ │ ├── message.json │ │ ├── app.json │ │ ├── auth.json │ │ ├── setting.json │ │ ├── file.json │ │ ├── role.json │ │ ├── apiKey.json │ │ └── user.json │ └── id │ │ └── app.json ├── common │ ├── aws │ │ ├── constants │ │ │ └── aws.s3.constant.ts │ │ ├── interfaces │ │ │ └── aws.interface.ts │ │ ├── aws.module.ts │ │ └── serializations │ │ │ └── aws.s3.serialization.ts │ ├── policy │ │ ├── constants │ │ │ ├── policy.constant.ts │ │ │ ├── policy.status-code.constant.ts │ │ │ └── policy.enum.constant.ts │ │ ├── policy.module.ts │ │ ├── decorators │ │ │ └── policy.decorator.ts │ │ └── interfaces │ │ │ └── policy.interface.ts │ ├── message │ │ ├── constants │ │ │ └── message.enum.constant.ts │ │ ├── middleware │ │ │ └── message.middleware.module.ts │ │ ├── serializations │ │ │ └── message.language.serialization.ts │ │ ├── interfaces │ │ │ ├── message.interface.ts │ │ │ └── message.service.interface.ts │ │ ├── docs │ │ │ └── message.public.doc.ts │ │ ├── controllers │ │ │ └── message.public.controller.ts │ │ └── message.module.ts │ ├── error │ │ ├── constants │ │ │ ├── error.enum.constant.ts │ │ │ ├── error.constant.ts │ │ │ └── error.status-code.constant.ts │ │ ├── serializations │ │ │ └── error.serialization.ts │ │ ├── decorators │ │ │ └── error.decorator.ts │ │ ├── error.module.ts │ │ ├── guards │ │ │ └── error.meta.guard.ts │ │ └── interfaces │ │ │ └── error.interface.ts │ ├── auth │ │ ├── constants │ │ │ ├── auth.enum.constant.ts │ │ │ └── auth.status-code.constant.ts │ │ ├── decorators │ │ │ └── auth.google.decorator.ts │ │ ├── interfaces │ │ │ └── auth.interface.ts │ │ ├── guards │ │ │ ├── jwt-access │ │ │ │ ├── auth.jwt-access.guard.ts │ │ │ │ └── auth.jwt-access.strategy.ts │ │ │ ├── jwt-refresh │ │ │ │ ├── auth.jwt-refresh.guard.ts │ │ │ │ └── auth.jwt-refresh.strategy.ts │ │ │ └── google-oauth2 │ │ │ │ ├── auth.google-oauth2-login.guard.ts │ │ │ │ ├── auth.google-oauth2-sign-up.guard.ts │ │ │ │ ├── auth.google-oauth2-login.strategy.ts │ │ │ │ └── auth.google-oauth2-sign-up.strategy.ts │ │ └── auth.module.ts │ ├── api-key │ │ ├── constants │ │ │ ├── api-key.enum.constant.ts │ │ │ ├── api-key.constant.ts │ │ │ ├── api-key.status-code.constant.ts │ │ │ ├── api-key.doc.constant.ts │ │ │ └── api-key.list.constant.ts │ │ ├── serializations │ │ │ ├── api-key.list.serialization.ts │ │ │ ├── api-key.reset.serialization.ts │ │ │ └── api-key.create.serialization.ts │ │ ├── dtos │ │ │ ├── api-key.update.dto.ts │ │ │ ├── api-key.request.dto.ts │ │ │ ├── api-key.active.dto.ts │ │ │ ├── api-key.update-date.dto.ts │ │ │ └── api-key.create.dto.ts │ │ ├── interfaces │ │ │ └── api-key.interface.ts │ │ ├── api-key.module.ts │ │ ├── repository │ │ │ ├── repositories │ │ │ │ └── api-key.repository.ts │ │ │ └── api-key.repository.module.ts │ │ └── guards │ │ │ ├── api-key.not-found.guard.ts │ │ │ ├── api-key.put-to-request.guard.ts │ │ │ ├── api-key.expired.guard.ts │ │ │ ├── api-key.active.guard.ts │ │ │ └── payload │ │ │ └── api-key.payload.type.guard.ts │ ├── doc │ │ └── constants │ │ │ └── doc.enum.constant.ts │ ├── logger │ │ ├── constants │ │ │ ├── logger.constant.ts │ │ │ └── logger.enum.constant.ts │ │ ├── interfaces │ │ │ └── logger.interface.ts │ │ ├── logger.module.ts │ │ ├── decorators │ │ │ └── logger.decorator.ts │ │ ├── dtos │ │ │ └── logger.create.dto.ts │ │ └── repository │ │ │ ├── logger.repository.module.ts │ │ │ └── repositories │ │ │ └── logger.repository.ts │ ├── helper │ │ ├── constants │ │ │ ├── helper.function.constant.ts │ │ │ └── helper.enum.constant.ts │ │ ├── interfaces │ │ │ ├── helper.number-service.interface.ts │ │ │ ├── helper.hash-service.interface.ts │ │ │ ├── helper.string-service.interface.ts │ │ │ ├── helper.file-service.interface.ts │ │ │ ├── helper.encryption-service.interface.ts │ │ │ └── helper.array-service.interface.ts │ │ └── services │ │ │ ├── helper.hash.service.ts │ │ │ └── helper.number.service.ts │ ├── file │ │ ├── constants │ │ │ ├── file.constant.ts │ │ │ ├── file.status-code.constant.ts │ │ │ └── file.enum.constant.ts │ │ ├── dtos │ │ │ ├── file.single.dto.ts │ │ │ └── file.multiple.dto.ts │ │ ├── interfaces │ │ │ └── file.interface.ts │ │ ├── pipes │ │ │ └── file.required.pipe.ts │ │ └── interceptors │ │ │ ├── file.custom-max-files.interceptor.ts │ │ │ └── file.custom-max-size.interceptor.ts │ ├── setting │ │ ├── constants │ │ │ ├── setting.status-code.constant.ts │ │ │ ├── setting.enum.constant.ts │ │ │ ├── setting.doc.constant.ts │ │ │ └── setting.list.constant.ts │ │ ├── serializations │ │ │ └── setting.list.serialization.ts │ │ ├── dtos │ │ │ ├── setting.update-value.dto.ts │ │ │ └── setting.request.dto.ts │ │ ├── decorators │ │ │ ├── setting.admin.decorator.ts │ │ │ ├── setting.public.decorator.ts │ │ │ └── setting.decorator.ts │ │ ├── setting.module.ts │ │ ├── repository │ │ │ ├── repositories │ │ │ │ └── setting.repository.ts │ │ │ ├── setting.repository.module.ts │ │ │ └── entities │ │ │ │ └── setting.entity.ts │ │ ├── guards │ │ │ ├── setting.not-found.guard.ts │ │ │ └── setting.put-to-request.guard.ts │ │ └── middleware │ │ │ └── maintenance │ │ │ └── setting.maintenance.middleware.ts │ ├── debugger │ │ ├── interfaces │ │ │ ├── debugger.options-service.interface.ts │ │ │ ├── debugger.service.interface.ts │ │ │ └── debugger.interface.ts │ │ ├── debugger.options.module.ts │ │ ├── constants │ │ │ └── debugger.constant.ts │ │ └── middleware │ │ │ └── debugger.middleware.module.ts │ ├── database │ │ ├── interfaces │ │ │ └── database.options-service.interface.ts │ │ ├── constants │ │ │ ├── database.function.constant.ts │ │ │ └── database.constant.ts │ │ ├── database.options.module.ts │ │ ├── abstracts │ │ │ ├── database.base-entity.abstract.ts │ │ │ └── mongo │ │ │ │ └── entities │ │ │ │ ├── database.mongo.uuid.entity.abstract.ts │ │ │ │ └── database.mongo.object-id.entity.abstract.ts │ │ └── decorators │ │ │ └── database.decorator.ts │ ├── request │ │ ├── constants │ │ │ ├── request.enum.constant.ts │ │ │ ├── request.constant.ts │ │ │ └── request.status-code.constant.ts │ │ ├── middleware │ │ │ ├── helmet │ │ │ │ └── request.helmet.middleware.ts │ │ │ ├── timezone │ │ │ │ └── request.timezone.middleware.ts │ │ │ ├── id │ │ │ │ └── request.id.middleware.ts │ │ │ ├── user-agent │ │ │ │ └── request.user-agent.middleware.ts │ │ │ └── timestamp │ │ │ │ └── request.timestamp.middleware.ts │ │ ├── interfaces │ │ │ └── request.interface.ts │ │ ├── validations │ │ │ ├── request.safe-string.validation.ts │ │ │ ├── request.only-digits.validation.ts │ │ │ ├── request.is-start-with.validation.ts │ │ │ ├── request.max-greater-than.validation.ts │ │ │ ├── request.min-greater-than.validation.ts │ │ │ ├── request.max-greater-than-equal.validation.ts │ │ │ ├── request.min-greater-than-equal.validation.ts │ │ │ ├── request.max-date-today.validation.ts │ │ │ ├── request.min-date-today.validation.ts │ │ │ ├── request.is-password-weak.validation.ts │ │ │ ├── request.is-password-medium.validation.ts │ │ │ ├── request.is-password-strong.validation.ts │ │ │ └── request.mobile-number-allowed.validation.ts │ │ └── guards │ │ │ └── request.param.guard.ts │ ├── dashboard │ │ ├── interfaces │ │ │ ├── dashboard.interface.ts │ │ │ └── dashboard.service.interface.ts │ │ ├── dashboard.module.ts │ │ ├── constants │ │ │ └── dashboard.doc.constant.ts │ │ ├── dtos │ │ │ └── dashboard.ts │ │ └── serializations │ │ │ └── dashboard.serialization.ts │ ├── pagination │ │ ├── pagination.module.ts │ │ ├── constants │ │ │ ├── pagination.enum.constant.ts │ │ │ └── pagination.constant.ts │ │ ├── dtos │ │ │ └── pagination.list.dto.ts │ │ ├── interfaces │ │ │ ├── pagination.interface.ts │ │ │ └── pagination.service.interface.ts │ │ └── pipes │ │ │ ├── pagination.search.pipe.ts │ │ │ └── pagination.filter-in-enum.pipe.ts │ └── response │ │ ├── middleware │ │ ├── response.middleware.module.ts │ │ └── time │ │ │ └── response.time.middleware.ts │ │ ├── constants │ │ └── response.constant.ts │ │ ├── serializations │ │ └── response.id.serialization.ts │ │ ├── response.module.ts │ │ └── interceptors │ │ └── response.custom-headers.interceptor.ts ├── modules │ ├── user │ │ ├── constants │ │ │ ├── user.enum.constant.ts │ │ │ ├── user.constant.ts │ │ │ ├── user.status-code.constant.ts │ │ │ ├── user.list.constant.ts │ │ │ └── user.doc.constant.ts │ │ ├── serializations │ │ │ ├── user.refresh.serialization.ts │ │ │ ├── user.login.serialization.ts │ │ │ ├── user.list.serialization.ts │ │ │ └── user.profile.serialization.ts │ │ ├── dtos │ │ │ ├── user.import.dto.ts │ │ │ ├── user.sign-up.dto.ts │ │ │ ├── user.update-name.dto.ts │ │ │ ├── user.login.dto.ts │ │ │ ├── user.request.dto.ts │ │ │ ├── user.update-username.dto.ts │ │ │ ├── user.update-google-sso.dto.ts │ │ │ └── user.change-password.dto.ts │ │ ├── user.module.ts │ │ ├── docs │ │ │ ├── user.user.doc.ts │ │ │ └── user.public.doc.ts │ │ ├── interfaces │ │ │ └── user.interface.ts │ │ ├── repository │ │ │ ├── user.repository.module.ts │ │ │ └── repositories │ │ │ │ └── user.repository.ts │ │ ├── guards │ │ │ ├── payload │ │ │ │ └── user.payload.put-to-request.guard.ts │ │ │ ├── user.put-to-request.guard.ts │ │ │ ├── user.not-found.guard.ts │ │ │ ├── user.can-not-ourself.guard.ts │ │ │ ├── user.blocked.guard.ts │ │ │ ├── user.active.guard.ts │ │ │ └── user.inactive-permanent.guard.ts │ │ └── controllers │ │ │ └── user.user.controller.ts │ └── role │ │ ├── constants │ │ ├── role.constant.ts │ │ ├── role.enum.constant.ts │ │ ├── role.status-code.constant.ts │ │ ├── role.list.constant.ts │ │ └── role.doc.constant.ts │ │ ├── dtos │ │ ├── role.request.dto.ts │ │ ├── role.update-permission.dto.ts │ │ └── role.update.dto.ts │ │ ├── role.module.ts │ │ ├── serializations │ │ └── role.list.serialization.ts │ │ ├── decorators │ │ ├── role.decorator.ts │ │ └── role.admin.decorator.ts │ │ ├── repository │ │ ├── repositories │ │ │ └── role.repository.ts │ │ └── role.repository.module.ts │ │ └── guards │ │ ├── role.put-to-request.guard.ts │ │ ├── role.not-found.guard.ts │ │ └── role.active.guard.ts ├── jobs │ ├── router │ │ └── jobs.router.module.ts │ └── jobs.module.ts ├── configs │ ├── user.config.ts │ ├── doc.config.ts │ ├── message.config.ts │ ├── helper.config.ts │ ├── aws.config.ts │ ├── database.config.ts │ ├── index.ts │ ├── debugger.config.ts │ ├── file.config.ts │ └── app.config.ts ├── health │ ├── health.module.ts │ ├── docs │ │ └── health.doc.ts │ ├── indicators │ │ └── health.aws-s3.indicator.ts │ └── serializations │ │ └── health.serialization.ts ├── router │ └── routes │ │ ├── routes.auth.module.ts │ │ ├── routes.user.module.ts │ │ ├── routes.admin.module.ts │ │ └── routes.public.module.ts ├── cli.ts └── migration │ ├── migration.module.ts │ └── seeds │ └── migration.setting.seed.ts ├── .prettierrc ├── .husky └── pre-commit ├── .eslintignore ├── tsconfig.build.json ├── dockerfile ├── nest-cli.json ├── .dockerignore ├── .eslintrc ├── cspell.json ├── .github ├── dependabot.yml └── workflows │ └── linter.yml ├── .gitignore ├── test └── jest.json ├── tsconfig.json ├── docker-compose.yml └── .env.example /README.md: -------------------------------------------------------------------------------- 1 | 2 | #NestJs Boilerplate 🔥 🚀 3 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | Data `json`, `csv`, or `excel` for migration -------------------------------------------------------------------------------- /src/app/constants/app.constant.ts: -------------------------------------------------------------------------------- 1 | export const APP_LANGUAGE = 'en'; 2 | -------------------------------------------------------------------------------- /src/languages/en/health.json: -------------------------------------------------------------------------------- 1 | { 2 | "check": "Healthy succeed" 3 | } -------------------------------------------------------------------------------- /src/languages/en/message.json: -------------------------------------------------------------------------------- 1 | { 2 | "languages": "message enum languages" 3 | } -------------------------------------------------------------------------------- /src/common/aws/constants/aws.s3.constant.ts: -------------------------------------------------------------------------------- 1 | export const AwsS3MaxPartNumber = 10000; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "tabWidth": 4 5 | } -------------------------------------------------------------------------------- /src/languages/en/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "This is test endpoint service {serviceName}." 3 | } 4 | -------------------------------------------------------------------------------- /src/common/policy/constants/policy.constant.ts: -------------------------------------------------------------------------------- 1 | export const POLICY_RULE_META_KEY = 'PolicyRuleMetaKey'; 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint 5 | yarn deadcode 6 | yarn test 7 | -------------------------------------------------------------------------------- /src/common/message/constants/message.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_MESSAGE_LANGUAGE { 2 | EN = 'en', 3 | ID = 'id', 4 | } 5 | -------------------------------------------------------------------------------- /src/common/error/constants/error.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ERROR_TYPE { 2 | DEFAULT = 'DEFAULT', 3 | IMPORT = 'IMPORT', 4 | } 5 | -------------------------------------------------------------------------------- /src/common/auth/constants/auth.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_AUTH_LOGIN_WITH { 2 | LOCAL = 'LOCAL', 3 | GOOGLE = 'GOOGLE', 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/user/constants/user.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_USER_SIGN_UP_FROM { 2 | LOCAL = 'LOCAL', 3 | GOOGLE = 'GOOGLE', 4 | } 5 | -------------------------------------------------------------------------------- /src/common/api-key/constants/api-key.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_API_KEY_TYPE { 2 | SERVICE = 'SERVICE', 3 | PUBLIC = 'PUBLIC', 4 | } 5 | -------------------------------------------------------------------------------- /src/modules/role/constants/role.constant.ts: -------------------------------------------------------------------------------- 1 | export const ROLE_IS_ACTIVE_META_KEY = 'RoleIsActiveMetaKey'; 2 | export const ROLE_TYPE_META_KEY = 'RoleTypeMetaKey'; 3 | -------------------------------------------------------------------------------- /src/common/error/constants/error.constant.ts: -------------------------------------------------------------------------------- 1 | export const ERROR_CLASS_META_KEY = 'ErrorMetaClassKey'; 2 | export const ERROR_FUNCTION_META_KEY = 'ErrorMetaFunctionKey'; 3 | -------------------------------------------------------------------------------- /src/modules/role/constants/role.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_ROLE_TYPE { 2 | SUPER_ADMIN = 'SUPER_ADMIN', 3 | USER = 'USER', 4 | ADMIN = 'ADMIN', 5 | } 6 | -------------------------------------------------------------------------------- /src/common/api-key/constants/api-key.constant.ts: -------------------------------------------------------------------------------- 1 | export const API_KEY_ACTIVE_META_KEY = 'ApiKeyActiveMetaKey'; 2 | export const API_KEY_TYPE_META_KEY = 'ApiKeyTypeMetaKey'; 3 | -------------------------------------------------------------------------------- /src/common/doc/constants/doc.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_DOC_REQUEST_BODY_TYPE { 2 | JSON = 'JSON', 3 | FORM_DATA = 'FORM_DATA', 4 | TEXT = 'TEXT', 5 | } 6 | -------------------------------------------------------------------------------- /src/common/logger/constants/logger.constant.ts: -------------------------------------------------------------------------------- 1 | export const LOGGER_ACTION_META_KEY = 'LoggerActionMetaKey'; 2 | export const LOGGER_OPTIONS_META_KEY = 'LoggerOptionsMetaKey'; 3 | -------------------------------------------------------------------------------- /src/common/helper/constants/helper.function.constant.ts: -------------------------------------------------------------------------------- 1 | import ms from 'ms'; 2 | 3 | export function seconds(msValue: string): number { 4 | return ms(msValue) / 1000; 5 | } 6 | -------------------------------------------------------------------------------- /src/languages/id/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "This is test endpoint service {serviceName}.", 3 | "helloTimeout": "This is test endpoint service {serviceName} timeout." 4 | } 5 | -------------------------------------------------------------------------------- /src/app/constants/app.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_APP_ENVIRONMENT { 2 | PRODUCTION = 'production', 3 | STAGING = 'staging', 4 | DEVELOPMENT = 'development', 5 | } 6 | -------------------------------------------------------------------------------- /src/common/file/constants/file.constant.ts: -------------------------------------------------------------------------------- 1 | export const FILE_CUSTOM_MAX_SIZE_META_KEY = 'FileCustomMaxSizeMetaKey'; 2 | export const FILE_CUSTOM_MAX_FILES_META_KEY = 'FileCustomMaxFilesMetaKey'; 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # /node_modules/* in the project root is ignored by default 2 | # build artefacts 3 | dist/* 4 | coverage/* 5 | node_modules/* 6 | logs/* 7 | data/* 8 | .husky/* 9 | .github/* 10 | -------------------------------------------------------------------------------- /src/common/setting/constants/setting.status-code.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_SETTING_STATUS_CODE_ERROR { 2 | SETTING_NOT_FOUND_ERROR = 5040, 3 | SETTING_VALUE_NOT_ALLOWED_ERROR = 5041, 4 | } 5 | -------------------------------------------------------------------------------- /src/common/aws/interfaces/aws.interface.ts: -------------------------------------------------------------------------------- 1 | import { ObjectCannedACL } from '@aws-sdk/client-s3'; 2 | 3 | export interface IAwsS3PutItemOptions { 4 | path: string; 5 | acl?: ObjectCannedACL; 6 | } 7 | -------------------------------------------------------------------------------- /src/common/policy/constants/policy.status-code.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_POLICY_STATUS_CODE_ERROR { 2 | POLICY_ABILITY_FORBIDDEN_ERROR = 50010, 3 | POLICY_ALREADY_AGREE_ERROR = 50011, 4 | } 5 | -------------------------------------------------------------------------------- /src/common/debugger/interfaces/debugger.options-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { LoggerOptions } from 'winston'; 2 | 3 | export interface IDebuggerOptionService { 4 | createLogger(): LoggerOptions; 5 | } 6 | -------------------------------------------------------------------------------- /src/common/file/dtos/file.single.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class FileSingleDto { 4 | @ApiProperty({ type: 'string', format: 'binary' }) 5 | file: any; 6 | } 7 | -------------------------------------------------------------------------------- /src/common/error/constants/error.status-code.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_ERROR_STATUS_CODE_ERROR { 2 | ERROR_UNKNOWN = 5050, 3 | ERROR_SERVICE_UNAVAILABLE = 5051, 4 | ERROR_REQUEST_TIMEOUT = 5052, 5 | } 6 | -------------------------------------------------------------------------------- /src/common/setting/serializations/setting.list.serialization.ts: -------------------------------------------------------------------------------- 1 | import { SettingGetSerialization } from './setting.get.serialization'; 2 | 3 | export class SettingListSerialization extends SettingGetSerialization {} 4 | -------------------------------------------------------------------------------- /src/common/file/interfaces/file.interface.ts: -------------------------------------------------------------------------------- 1 | export type IFile = Express.Multer.File; 2 | 3 | export type IFileExtract> = IFile & { 4 | extract: Record[]; 5 | dto?: T[]; 6 | }; 7 | -------------------------------------------------------------------------------- /src/common/setting/constants/setting.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_SETTING_DATA_TYPE { 2 | BOOLEAN = 'BOOLEAN', 3 | STRING = 'STRING', 4 | ARRAY_OF_STRING = 'ARRAY_OF_STRING', 5 | NUMBER = 'NUMBER', 6 | } 7 | -------------------------------------------------------------------------------- /src/common/auth/constants/auth.status-code.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_AUTH_STATUS_CODE_ERROR { 2 | AUTH_JWT_ACCESS_TOKEN_ERROR = 5000, 3 | AUTH_JWT_REFRESH_TOKEN_ERROR = 5001, 4 | AUTH_GOOGLE_SSO_ERROR = 5002, 5 | } 6 | -------------------------------------------------------------------------------- /src/common/database/interfaces/database.options-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { MongooseModuleOptions } from '@nestjs/mongoose'; 2 | 3 | export interface IDatabaseOptionsService { 4 | createOptions(): MongooseModuleOptions; 5 | } 6 | -------------------------------------------------------------------------------- /src/common/file/dtos/file.multiple.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class FileMultipleDto { 4 | @ApiProperty({ type: 'array', items: { type: 'string', format: 'binary' } }) 5 | files: any[]; 6 | } 7 | -------------------------------------------------------------------------------- /src/jobs/router/jobs.router.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | 3 | @Module({ 4 | providers: [], 5 | exports: [], 6 | imports: [], 7 | controllers: [], 8 | }) 9 | export class JobsRouterModule {} 10 | -------------------------------------------------------------------------------- /src/modules/user/serializations/user.refresh.serialization.ts: -------------------------------------------------------------------------------- 1 | import { UserLoginSerialization } from 'src/modules/user/serializations/user.login.serialization'; 2 | 3 | export class UserRefreshSerialization extends UserLoginSerialization {} 4 | -------------------------------------------------------------------------------- /src/common/api-key/serializations/api-key.list.serialization.ts: -------------------------------------------------------------------------------- 1 | import { ApiKeyGetSerialization } from 'src/common/api-key/serializations/api-key.get.serialization'; 2 | 3 | export class ApiKeyListSerialization extends ApiKeyGetSerialization {} 4 | -------------------------------------------------------------------------------- /src/modules/user/constants/user.constant.ts: -------------------------------------------------------------------------------- 1 | export const USER_ACTIVE_META_KEY = 'UserActiveMetaKey'; 2 | export const USER_INACTIVE_PERMANENT_META_KEY = 'UserInactivePermanentMetaKey'; 3 | export const USER_BLOCKED_META_KEY = 'UserBlockedMetaKey'; 4 | -------------------------------------------------------------------------------- /src/common/api-key/dtos/api-key.update.dto.ts: -------------------------------------------------------------------------------- 1 | import { PickType } from '@nestjs/swagger'; 2 | import { ApiKeyCreateDto } from './api-key.create.dto'; 3 | 4 | export class ApiKeyUpdateDto extends PickType(ApiKeyCreateDto, [ 5 | 'name', 6 | ] as const) {} 7 | -------------------------------------------------------------------------------- /src/common/api-key/serializations/api-key.reset.serialization.ts: -------------------------------------------------------------------------------- 1 | import { ApiKeyCreateSerialization } from 'src/common/api-key/serializations/api-key.create.serialization'; 2 | 3 | export class ApiKeyResetSerialization extends ApiKeyCreateSerialization {} 4 | -------------------------------------------------------------------------------- /src/common/error/serializations/error.serialization.ts: -------------------------------------------------------------------------------- 1 | import { ResponseMetadataSerialization } from 'src/common/response/serializations/response.default.serialization'; 2 | 3 | export class ErrorMetadataSerialization extends ResponseMetadataSerialization {} 4 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "dist", 6 | "*coverage", 7 | "logs", 8 | "test", 9 | "data" 10 | ], 11 | "include": [ 12 | "src" 13 | ], 14 | } 15 | -------------------------------------------------------------------------------- /src/common/database/constants/database.function.constant.ts: -------------------------------------------------------------------------------- 1 | import { Types } from 'mongoose'; 2 | import { v4 as uuidV4 } from 'uuid'; 3 | 4 | export const DatabaseDefaultUUID = uuidV4; 5 | 6 | export const DatabaseDefaultObjectId = () => new Types.ObjectId(); 7 | -------------------------------------------------------------------------------- /src/languages/en/auth.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "accessTokenUnauthorized": "Access Token UnAuthorized", 4 | "refreshTokenUnauthorized": "Refresh Token UnAuthorized", 5 | "googleSSO": "Google SSO something error" 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/modules/user/dtos/user.import.dto.ts: -------------------------------------------------------------------------------- 1 | import { OmitType } from '@nestjs/swagger'; 2 | import { UserCreateDto } from './user.create.dto'; 3 | 4 | export class UserImportDto extends OmitType(UserCreateDto, [ 5 | 'role', 6 | 'password', 7 | ] as const) {} 8 | -------------------------------------------------------------------------------- /src/modules/user/dtos/user.sign-up.dto.ts: -------------------------------------------------------------------------------- 1 | import { OmitType } from '@nestjs/swagger'; 2 | import { UserCreateDto } from './user.create.dto'; 3 | 4 | export class UserSignUpDto extends OmitType(UserCreateDto, [ 5 | 'role', 6 | 'signUpFrom', 7 | ] as const) {} 8 | -------------------------------------------------------------------------------- /src/common/logger/interfaces/logger.interface.ts: -------------------------------------------------------------------------------- 1 | import { ENUM_LOGGER_LEVEL } from 'src/common/logger/constants/logger.enum.constant'; 2 | 3 | export interface ILoggerOptions { 4 | description?: string; 5 | tags?: string[]; 6 | level?: ENUM_LOGGER_LEVEL; 7 | } 8 | -------------------------------------------------------------------------------- /src/common/request/constants/request.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_REQUEST_METHOD { 2 | GET = 'GET', 3 | POST = 'POST', 4 | PUT = 'PUT', 5 | PATCH = 'PATCH', 6 | DELETE = 'DELETE', 7 | OPTIONS = 'OPTIONS', 8 | HEAD = 'HEAD', 9 | } 10 | -------------------------------------------------------------------------------- /src/languages/en/setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "list": "List Setting Success.", 3 | "get": "Get Setting Success.", 4 | "update": "Update Succeed", 5 | "error": { 6 | "notFound": "Setting not found", 7 | "valueNotAllowed": "Setting value not allowed" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/user/dtos/user.update-name.dto.ts: -------------------------------------------------------------------------------- 1 | import { PickType } from '@nestjs/swagger'; 2 | import { UserCreateDto } from './user.create.dto'; 3 | 4 | export class UserUpdateNameDto extends PickType(UserCreateDto, [ 5 | 'firstName', 6 | 'lastName', 7 | ] as const) {} 8 | -------------------------------------------------------------------------------- /src/configs/user.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | export default registerAs( 4 | 'user', 5 | (): Record => ({ 6 | uploadPath: '/user', 7 | mobileNumberCountryCodeAllowed: ['628', '658'], 8 | }) 9 | ); 10 | -------------------------------------------------------------------------------- /src/modules/user/dtos/user.login.dto.ts: -------------------------------------------------------------------------------- 1 | import { PickType } from '@nestjs/swagger'; 2 | import { UserCreateDto } from 'src/modules/user/dtos/user.create.dto'; 3 | 4 | export class UserLoginDto extends PickType(UserCreateDto, [ 5 | 'email', 6 | 'password', 7 | ] as const) {} 8 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine 2 | LABEL maintainer "seettuwa@baibay.id" 3 | 4 | WORKDIR /app 5 | EXPOSE 3000 6 | 7 | COPY package.json yarn.lock ./ 8 | RUN touch .env 9 | 10 | RUN mkdir data 11 | RUN set -x && yarn 12 | 13 | COPY . . 14 | 15 | CMD [ "yarn", "start:dev" ] 16 | -------------------------------------------------------------------------------- /src/common/dashboard/interfaces/dashboard.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IDashboardStartAndEndDate { 2 | startDate: Date; 3 | endDate: Date; 4 | } 5 | 6 | export interface IDashboardMonthAndYear { 7 | month: number; 8 | year: number; 9 | total: number; 10 | } 11 | -------------------------------------------------------------------------------- /src/common/setting/constants/setting.doc.constant.ts: -------------------------------------------------------------------------------- 1 | export const SettingDocParamsId = [ 2 | { 3 | name: 'setting', 4 | allowEmptyValue: false, 5 | required: true, 6 | type: 'string', 7 | description: 'setting id', 8 | }, 9 | ]; 10 | -------------------------------------------------------------------------------- /src/modules/role/dtos/role.request.dto.ts: -------------------------------------------------------------------------------- 1 | import { Type } from 'class-transformer'; 2 | import { IsNotEmpty, IsUUID } from 'class-validator'; 3 | 4 | export class RoleRequestDto { 5 | @IsNotEmpty() 6 | @IsUUID('4') 7 | @Type(() => String) 8 | role: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/role/dtos/role.update-permission.dto.ts: -------------------------------------------------------------------------------- 1 | import { OmitType } from '@nestjs/swagger'; 2 | import { RoleCreateDto } from 'src/modules/role/dtos/role.create.dto'; 3 | 4 | export class RoleUpdatePermissionDto extends OmitType(RoleCreateDto, [ 5 | 'name', 6 | ] as const) {} 7 | -------------------------------------------------------------------------------- /src/modules/user/dtos/user.request.dto.ts: -------------------------------------------------------------------------------- 1 | import { Type } from 'class-transformer'; 2 | import { IsNotEmpty, IsUUID } from 'class-validator'; 3 | 4 | export class UserRequestDto { 5 | @IsNotEmpty() 6 | @IsUUID('4') 7 | @Type(() => String) 8 | user: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/common/logger/constants/logger.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_LOGGER_LEVEL { 2 | DEBUG = 'DEBUG', 3 | INFO = 'INFO', 4 | WARN = 'WARN', 5 | FATAL = 'FATAL', 6 | } 7 | 8 | export enum ENUM_LOGGER_ACTION { 9 | LOGIN = 'LOGIN', 10 | TEST = 'TEST', 11 | } 12 | -------------------------------------------------------------------------------- /src/common/setting/dtos/setting.update-value.dto.ts: -------------------------------------------------------------------------------- 1 | import { OmitType } from '@nestjs/swagger'; 2 | import { SettingCreateDto } from './setting.create.dto'; 3 | 4 | export class SettingUpdateValueDto extends OmitType(SettingCreateDto, [ 5 | 'name', 6 | 'description', 7 | ] as const) {} 8 | -------------------------------------------------------------------------------- /src/common/setting/dtos/setting.request.dto.ts: -------------------------------------------------------------------------------- 1 | import { Type } from 'class-transformer'; 2 | import { IsNotEmpty, IsUUID } from 'class-validator'; 3 | 4 | export class SettingRequestDto { 5 | @IsNotEmpty() 6 | @IsUUID('4') 7 | @Type(() => String) 8 | setting: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/common/api-key/dtos/api-key.request.dto.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Type } from 'class-transformer'; 3 | import { IsNotEmpty, IsUUID } from 'class-validator'; 4 | 5 | export class ApiKeyRequestDto { 6 | 7 | @IsNotEmpty() 8 | @IsUUID('4') 9 | @Type(() => String) 10 | apiKey: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/common/aws/aws.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AwsS3Service } from './services/aws.s3.service'; 3 | 4 | @Module({ 5 | exports: [AwsS3Service], 6 | providers: [AwsS3Service], 7 | imports: [], 8 | controllers: [], 9 | }) 10 | export class AwsModule {} 11 | -------------------------------------------------------------------------------- /src/common/database/constants/database.constant.ts: -------------------------------------------------------------------------------- 1 | export const DATABASE_CONNECTION_NAME = 'PrimaryConnectionDatabase'; 2 | 3 | export const DATABASE_DELETED_AT_FIELD_NAME = 'deletedAt'; 4 | export const DATABASE_UPDATED_AT_FIELD_NAME = 'updatedAt'; 5 | export const DATABASE_CREATED_AT_FIELD_NAME = 'createdAt'; 6 | -------------------------------------------------------------------------------- /src/common/request/constants/request.constant.ts: -------------------------------------------------------------------------------- 1 | export const REQUEST_PARAM_CLASS_DTOS_META_KEY = 'RequestParamClassDtosMetaKey'; 2 | 3 | export const REQUEST_CUSTOM_TIMEOUT_META_KEY = 'RequestCustomTimeoutMetaKey'; 4 | export const REQUEST_CUSTOM_TIMEOUT_VALUE_META_KEY = 5 | 'RequestCustomTimeoutValueMetaKey'; 6 | -------------------------------------------------------------------------------- /src/modules/role/constants/role.status-code.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_ROLE_STATUS_CODE_ERROR { 2 | ROLE_NOT_FOUND_ERROR = 5100, 3 | ROLE_PAYLOAD_TYPE_INVALID_ERROR = 5101, 4 | ROLE_EXIST_ERROR = 5102, 5 | ROLE_IS_ACTIVE_ERROR = 5103, 6 | ROLE_INACTIVE_ERROR = 5104, 7 | ROLE_USED_ERROR = 5105, 8 | } 9 | -------------------------------------------------------------------------------- /src/common/file/constants/file.status-code.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_FILE_STATUS_CODE_ERROR { 2 | FILE_NEEDED_ERROR = 5030, 3 | FILE_MAX_SIZE_ERROR = 5031, 4 | FILE_EXTENSION_ERROR = 5032, 5 | FILE_MAX_FILES_ERROR = 5033, 6 | FILE_VALIDATION_DTO_ERROR = 5034, 7 | FILE_NEED_EXTRACT_FIRST_ERROR = 5035, 8 | } 9 | -------------------------------------------------------------------------------- /src/common/helper/interfaces/helper.number-service.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IHelperNumberService { 2 | check(number: string): boolean; 3 | create(number: string): number; 4 | random(length: number): number; 5 | randomInRange(min: number, max: number): number; 6 | percent(value: number, total: number): number; 7 | } 8 | -------------------------------------------------------------------------------- /src/common/pagination/pagination.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { PaginationService } from './services/pagination.service'; 3 | 4 | @Global() 5 | @Module({ 6 | providers: [PaginationService], 7 | exports: [PaginationService], 8 | imports: [], 9 | }) 10 | export class PaginationModule {} 11 | -------------------------------------------------------------------------------- /src/common/request/constants/request.status-code.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_REQUEST_STATUS_CODE_ERROR { 2 | REQUEST_VALIDATION_ERROR = 5070, 3 | REQUEST_TIMESTAMP_INVALID_ERROR = 5071, 4 | REQUEST_USER_AGENT_INVALID_ERROR = 5072, 5 | REQUEST_USER_AGENT_OS_INVALID_ERROR = 5073, 6 | REQUEST_USER_AGENT_BROWSER_INVALID_ERROR = 5074, 7 | } 8 | -------------------------------------------------------------------------------- /src/common/dashboard/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { DashboardService } from 'src/common/dashboard/services/dashboard.service'; 3 | 4 | @Module({ 5 | controllers: [], 6 | providers: [DashboardService], 7 | exports: [DashboardService], 8 | imports: [], 9 | }) 10 | export class DashboardModule {} 11 | -------------------------------------------------------------------------------- /src/common/policy/policy.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { PolicyAbilityFactory } from 'src/common/policy/factories/policy.ability.factory'; 3 | 4 | @Global() 5 | @Module({ 6 | providers: [PolicyAbilityFactory], 7 | exports: [PolicyAbilityFactory], 8 | imports: [], 9 | }) 10 | export class PolicyModule {} 11 | -------------------------------------------------------------------------------- /src/languages/en/file.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "notFound": "File not found", 4 | "maxSize": "File size too big", 5 | "maxFiles": "Files are to many", 6 | "mimeInvalid": "File extension not valid", 7 | "needExtractFirst": "Extract data needed", 8 | "validationDto": "Import Data invalid" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/common/debugger/debugger.options.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { DebuggerOptionService } from 'src/common/debugger/services/debugger.options.service'; 3 | 4 | @Module({ 5 | providers: [DebuggerOptionService], 6 | exports: [DebuggerOptionService], 7 | imports: [], 8 | }) 9 | export class DebuggerOptionsModule {} 10 | -------------------------------------------------------------------------------- /src/configs/doc.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | export default registerAs( 4 | 'doc', 5 | (): Record => ({ 6 | name: `${process.env.APP_NAME} APIs Specification`, 7 | description: 'Section for describe whole APIs', 8 | version: '1.0', 9 | prefix: '/docs', 10 | }) 11 | ); 12 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src", 4 | "compilerOptions": { 5 | "plugins": ["@nestjs/swagger"], 6 | "assets": [ 7 | { 8 | "include": "languages/**/*", 9 | "outDir": "dist/src" 10 | } 11 | ], 12 | "webpack": false, 13 | "deleteOutDir": true, 14 | "watchAssets": false 15 | } 16 | } -------------------------------------------------------------------------------- /src/common/helper/interfaces/helper.hash-service.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IHelperHashService { 2 | randomSalt(length: number): string; 3 | bcrypt(passwordString: string, salt: string): string; 4 | bcryptCompare(passwordString: string, passwordHashed: string): boolean; 5 | sha256(string: string): string; 6 | sha256Compare(hashOne: string, hashTwo: string): boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/common/api-key/constants/api-key.status-code.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_API_KEY_STATUS_CODE_ERROR { 2 | API_KEY_NEEDED_ERROR = 5020, 3 | API_KEY_NOT_FOUND_ERROR = 5021, 4 | API_KEY_IS_ACTIVE_ERROR = 5022, 5 | API_KEY_NOT_ACTIVE_YET_ERROR = 5023, 6 | API_KEY_EXPIRED_ERROR = 5024, 7 | API_KEY_INVALID_ERROR = 5025, 8 | API_KEY_TYPE_INVALID_ERROR = 5026, 9 | } 10 | -------------------------------------------------------------------------------- /src/common/database/database.options.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { DatabaseOptionsService } from 'src/common/database/services/database.options.service'; 3 | 4 | @Module({ 5 | providers: [DatabaseOptionsService], 6 | exports: [DatabaseOptionsService], 7 | imports: [], 8 | controllers: [], 9 | }) 10 | export class DatabaseOptionsModule {} 11 | -------------------------------------------------------------------------------- /src/health/health.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AwsModule } from 'src/common/aws/aws.module'; 3 | import { HealthAwsS3Indicator } from 'src/health/indicators/health.aws-s3.indicator'; 4 | 5 | @Module({ 6 | providers: [HealthAwsS3Indicator], 7 | exports: [HealthAwsS3Indicator], 8 | imports: [AwsModule], 9 | }) 10 | export class HealthModule {} 11 | -------------------------------------------------------------------------------- /src/common/dashboard/interfaces/dashboard.service.interface.ts: -------------------------------------------------------------------------------- 1 | import { DashboardDto } from 'src/common/dashboard/dtos/dashboard'; 2 | import { IDashboardStartAndEndDate } from 'src/common/dashboard/interfaces/dashboard.interface'; 3 | 4 | export interface IDashboardService { 5 | getStartAndEndDate(date?: DashboardDto): IDashboardStartAndEndDate; 6 | getPercentage(value: number, total: number): number; 7 | } 8 | -------------------------------------------------------------------------------- /src/modules/role/role.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { RoleService } from './services/role.service'; 3 | import { RoleRepositoryModule } from 'src/modules/role/repository/role.repository.module'; 4 | 5 | @Module({ 6 | controllers: [], 7 | providers: [RoleService], 8 | exports: [RoleService], 9 | imports: [RoleRepositoryModule], 10 | }) 11 | export class RoleModule {} 12 | -------------------------------------------------------------------------------- /src/modules/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { UserRepositoryModule } from 'src/modules/user/repository/user.repository.module'; 3 | import { UserService } from './services/user.service'; 4 | 5 | @Module({ 6 | imports: [UserRepositoryModule], 7 | exports: [UserService], 8 | providers: [UserService], 9 | controllers: [], 10 | }) 11 | export class UserModule {} 12 | -------------------------------------------------------------------------------- /src/common/logger/logger.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { LoggerRepositoryModule } from 'src/common/logger/repository/logger.repository.module'; 3 | import { LoggerService } from './services/logger.service'; 4 | 5 | @Global() 6 | @Module({ 7 | providers: [LoggerService], 8 | exports: [LoggerService], 9 | imports: [LoggerRepositoryModule], 10 | }) 11 | export class LoggerModule {} 12 | -------------------------------------------------------------------------------- /src/common/api-key/dtos/api-key.active.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { IsNotEmpty, IsBoolean } from 'class-validator'; 3 | 4 | export class ApiKeyActiveDto { 5 | @ApiProperty({ 6 | name: 'isActive', 7 | required: true, 8 | nullable: false, 9 | }) 10 | @IsBoolean() 11 | @IsNotEmpty() 12 | @IsBoolean() 13 | @IsNotEmpty() 14 | isActive: boolean; 15 | } 16 | -------------------------------------------------------------------------------- /src/common/request/middleware/helmet/request.helmet.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware } from '@nestjs/common'; 2 | import { Request, Response, NextFunction } from 'express'; 3 | import helmet from 'helmet'; 4 | 5 | @Injectable() 6 | export class RequestHelmetMiddleware implements NestMiddleware { 7 | use(req: Request, res: Response, next: NextFunction): void { 8 | helmet()(req, res, next); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/common/pagination/constants/pagination.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_PAGINATION_ORDER_DIRECTION_TYPE { 2 | ASC = 'asc', 3 | DESC = 'desc', 4 | } 5 | 6 | export enum ENUM_PAGINATION_FILTER_CASE_OPTIONS { 7 | UPPERCASE = 'UPPERCASE', 8 | LOWERCASE = 'LOWERCASE', 9 | } 10 | 11 | export enum ENUM_PAGINATION_FILTER_DATE_TIME_OPTIONS { 12 | START_OF_DAY = 'START_OF_DAY', 13 | END_OF_DAY = 'END_OF_DAY', 14 | } 15 | -------------------------------------------------------------------------------- /src/common/debugger/interfaces/debugger.service.interface.ts: -------------------------------------------------------------------------------- 1 | import { IDebuggerLog } from 'src/common/debugger/interfaces/debugger.interface'; 2 | 3 | export interface IDebuggerService { 4 | info(requestId: string, log: IDebuggerLog, data?: any): void; 5 | debug(requestId: string, log: IDebuggerLog, data?: any): void; 6 | warn(requestId: string, log: IDebuggerLog, data?: any): void; 7 | error(requestId: string, log: IDebuggerLog, data?: any): void; 8 | } 9 | -------------------------------------------------------------------------------- /src/common/response/middleware/response.middleware.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; 2 | import { ResponseTimeMiddleware } from 'src/common/response/middleware/time/response.time.middleware'; 3 | 4 | @Module({}) 5 | export class ResponseMiddlewareModule implements NestModule { 6 | configure(consumer: MiddlewareConsumer): void { 7 | consumer.apply(ResponseTimeMiddleware).forRoutes('*'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/common/response/middleware/time/response.time.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware } from '@nestjs/common'; 2 | import { Request, Response, NextFunction } from 'express'; 3 | import responseTime from 'response-time'; 4 | 5 | @Injectable() 6 | export class ResponseTimeMiddleware implements NestMiddleware { 7 | async use(req: Request, res: Response, next: NextFunction): Promise { 8 | responseTime()(req, res, next); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/common/api-key/interfaces/api-key.interface.ts: -------------------------------------------------------------------------------- 1 | import { ENUM_API_KEY_TYPE } from 'src/common/api-key/constants/api-key.enum.constant'; 2 | import { ApiKeyDoc } from 'src/common/api-key/repository/entities/api-key.entity'; 3 | 4 | export interface IApiKeyPayload { 5 | _id: string; 6 | key: string; 7 | type: ENUM_API_KEY_TYPE; 8 | name: string; 9 | } 10 | 11 | export interface IApiKeyCreated { 12 | secret: string; 13 | doc: ApiKeyDoc; 14 | } 15 | -------------------------------------------------------------------------------- /src/common/response/constants/response.constant.ts: -------------------------------------------------------------------------------- 1 | export const RESPONSE_SERIALIZATION_META_KEY = 'ResponseSerializationMetaKey'; 2 | export const RESPONSE_SERIALIZATION_OPTIONS_META_KEY = 3 | 'class_serializer:options'; 4 | export const RESPONSE_MESSAGE_PROPERTIES_META_KEY = 5 | 'ResponseSerializationPropertiesMetaKey'; 6 | export const RESPONSE_MESSAGE_PATH_META_KEY = 'ResponseMessagePathMetaKey'; 7 | export const RESPONSE_FILE_TYPE_META_KEY = 'ResponseFileTypeMetaKey'; 8 | -------------------------------------------------------------------------------- /src/common/response/serializations/response.id.serialization.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { Type } from 'class-transformer'; 3 | 4 | export class ResponseIdSerialization { 5 | @ApiProperty({ 6 | description: 'Id that representative with your target data', 7 | example: '631d9f32a65cf07250b8938c', 8 | required: true, 9 | nullable: false, 10 | }) 11 | @Type(() => String) 12 | _id: string; 13 | } 14 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | #husky 2 | 3 | # Build dependencies 4 | node_modules/ 5 | coverage/ 6 | dist/ 7 | data/* 8 | .husky/ 9 | .github/ 10 | prod/ 11 | 12 | # Logs 13 | logs/ 14 | 15 | # Environment (contains sensitive data) 16 | .env* 17 | 18 | # Versioning and metadata 19 | .git 20 | .gitignore 21 | .dockerignore 22 | .eslintignore 23 | 24 | # Files not required for production 25 | .editorconfig 26 | dockerfile 27 | docker-compose.yml 28 | cspell.json 29 | README.md 30 | nodemon.json -------------------------------------------------------------------------------- /src/common/error/decorators/error.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, SetMetadata } from '@nestjs/common'; 2 | import { 3 | ERROR_CLASS_META_KEY, 4 | ERROR_FUNCTION_META_KEY, 5 | } from 'src/common/error/constants/error.constant'; 6 | 7 | export function ErrorMeta(cls: string, func: string): MethodDecorator { 8 | return applyDecorators( 9 | SetMetadata(ERROR_CLASS_META_KEY, cls), 10 | SetMetadata(ERROR_FUNCTION_META_KEY, func) 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/configs/message.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | import { APP_LANGUAGE } from 'src/app/constants/app.constant'; 3 | import { ENUM_MESSAGE_LANGUAGE } from 'src/common/message/constants/message.enum.constant'; 4 | 5 | export default registerAs( 6 | 'message', 7 | (): Record => ({ 8 | availableLanguage: Object.values(ENUM_MESSAGE_LANGUAGE), 9 | language: process.env.APP_LANGUAGE ?? APP_LANGUAGE, 10 | }) 11 | ); 12 | -------------------------------------------------------------------------------- /src/common/message/middleware/message.middleware.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; 2 | import { MessageCustomLanguageMiddleware } from 'src/common/message/middleware/custom-language/message.custom-language.middleware'; 3 | 4 | @Module({}) 5 | export class MessageMiddlewareModule implements NestModule { 6 | configure(consumer: MiddlewareConsumer): void { 7 | consumer.apply(MessageCustomLanguageMiddleware).forRoutes('*'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/common/message/serializations/message.language.serialization.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { ENUM_MESSAGE_LANGUAGE } from 'src/common/message/constants/message.enum.constant'; 3 | 4 | export class MessageLanguageSerialization { 5 | @ApiProperty({ 6 | required: true, 7 | nullable: false, 8 | enum: ENUM_MESSAGE_LANGUAGE, 9 | type: 'array', 10 | isArray: true, 11 | }) 12 | language: ENUM_MESSAGE_LANGUAGE[]; 13 | } 14 | -------------------------------------------------------------------------------- /src/common/setting/decorators/setting.admin.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, UseGuards } from '@nestjs/common'; 2 | import { SettingNotFoundGuard } from 'src/common/setting/guards/setting.not-found.guard'; 3 | import { SettingPutToRequestGuard } from 'src/common/setting/guards/setting.put-to-request.guard'; 4 | 5 | export function SettingAdminUpdateGuard(): MethodDecorator { 6 | return applyDecorators( 7 | UseGuards(SettingPutToRequestGuard, SettingNotFoundGuard) 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/common/setting/decorators/setting.public.decorator.ts: -------------------------------------------------------------------------------- 1 | import { UseGuards, applyDecorators } from '@nestjs/common'; 2 | import { SettingNotFoundGuard } from 'src/common/setting/guards/setting.not-found.guard'; 3 | import { SettingPutToRequestGuard } from 'src/common/setting/guards/setting.put-to-request.guard'; 4 | 5 | export function SettingPublicGetGuard(): MethodDecorator { 6 | return applyDecorators( 7 | UseGuards(SettingPutToRequestGuard, SettingNotFoundGuard) 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/common/debugger/constants/debugger.constant.ts: -------------------------------------------------------------------------------- 1 | export const DEBUGGER_NAME = 'system'; 2 | 3 | export const DEBUGGER_HTTP_FORMAT = 4 | "':remote-addr' - ':remote-user' - '[:date[iso]]' - 'HTTP/:http-version' - '[:status]' - ':method' - ':url' - 'Request Header :: :req-headers' - 'Request Params :: :req-params' - 'Request Body :: :req-body' - 'Response Header :: :res[header]' - 'Response Body :: :res-body' - ':response-time ms' - ':referrer' - ':user-agent'"; 5 | export const DEBUGGER_HTTP_NAME = 'http'; 6 | -------------------------------------------------------------------------------- /src/common/setting/constants/setting.list.constant.ts: -------------------------------------------------------------------------------- 1 | import { ENUM_PAGINATION_ORDER_DIRECTION_TYPE } from 'src/common/pagination/constants/pagination.enum.constant'; 2 | 3 | export const SETTING_DEFAULT_PER_PAGE = 20; 4 | export const SETTING_DEFAULT_ORDER_BY = 'createdAt'; 5 | export const SETTING_DEFAULT_ORDER_DIRECTION = 6 | ENUM_PAGINATION_ORDER_DIRECTION_TYPE.ASC; 7 | export const SETTING_DEFAULT_AVAILABLE_SEARCH = ['name']; 8 | export const SETTING_DEFAULT_AVAILABLE_ORDER_BY = ['name', 'createdAt']; 9 | -------------------------------------------------------------------------------- /src/configs/helper.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | import { seconds } from 'src/common/helper/constants/helper.function.constant'; 3 | 4 | export default registerAs( 5 | 'helper', 6 | (): Record => ({ 7 | salt: { 8 | length: 8, 9 | }, 10 | jwt: { 11 | secretKey: '123456', 12 | expirationTime: seconds('1h'), 13 | notBeforeExpirationTime: seconds('0'), 14 | }, 15 | }) 16 | ); 17 | -------------------------------------------------------------------------------- /src/common/message/interfaces/message.interface.ts: -------------------------------------------------------------------------------- 1 | export type IMessage = Record; 2 | 3 | export type IMessageOptionsProperties = Record; 4 | 5 | export interface IMessageOptions { 6 | readonly customLanguages?: string[]; 7 | readonly properties?: IMessageOptionsProperties; 8 | } 9 | 10 | export interface IMessageErrorOptions { 11 | readonly customLanguages?: string[]; 12 | } 13 | 14 | export type IMessageSetOptions = Omit; 15 | -------------------------------------------------------------------------------- /src/common/database/abstracts/database.base-entity.abstract.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DATABASE_CREATED_AT_FIELD_NAME, 3 | DATABASE_DELETED_AT_FIELD_NAME, 4 | DATABASE_UPDATED_AT_FIELD_NAME, 5 | } from 'src/common/database/constants/database.constant'; 6 | 7 | export abstract class DatabaseBaseEntityAbstract { 8 | abstract _id: T; 9 | abstract [DATABASE_DELETED_AT_FIELD_NAME]?: Date; 10 | abstract [DATABASE_CREATED_AT_FIELD_NAME]?: Date; 11 | abstract [DATABASE_UPDATED_AT_FIELD_NAME]?: Date; 12 | } 13 | -------------------------------------------------------------------------------- /src/common/api-key/serializations/api-key.create.serialization.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, PickType } from '@nestjs/swagger'; 2 | import { ApiKeyGetSerialization } from 'src/common/api-key/serializations/api-key.get.serialization'; 3 | 4 | export class ApiKeyCreateSerialization extends PickType( 5 | ApiKeyGetSerialization, 6 | ['key', '_id'] as const 7 | ) { 8 | @ApiProperty({ 9 | description: 'Secret key of ApiKey, only show at once', 10 | example: true, 11 | required: true, 12 | }) 13 | secret: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/user/dtos/user.update-username.dto.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | import { Type } from 'class-transformer'; 4 | import { IsNotEmpty, IsString, MaxLength } from 'class-validator'; 5 | 6 | export class UserUpdateUsernameDto { 7 | @ApiProperty({ 8 | example: faker.internet.userName(), 9 | required: true, 10 | }) 11 | @IsString() 12 | @IsNotEmpty() 13 | @MaxLength(100) 14 | @Type(() => String) 15 | readonly username: string; 16 | } 17 | -------------------------------------------------------------------------------- /src/modules/role/serializations/role.list.serialization.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, OmitType } from '@nestjs/swagger'; 2 | import { RoleGetSerialization } from './role.get.serialization'; 3 | import { Transform } from 'class-transformer'; 4 | 5 | export class RoleListSerialization extends OmitType(RoleGetSerialization, [ 6 | 'permissions', 7 | ] as const) { 8 | @ApiProperty({ 9 | description: 'count of permissions', 10 | required: true, 11 | }) 12 | @Transform(({ value }) => value.length) 13 | permissions: number; 14 | } 15 | -------------------------------------------------------------------------------- /src/router/routes/routes.auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AuthModule } from 'src/common/auth/auth.module'; 3 | import { AwsModule } from 'src/common/aws/aws.module'; 4 | import { UserAuthController } from 'src/modules/user/controllers/user.auth.controller'; 5 | import { UserModule } from 'src/modules/user/user.module'; 6 | 7 | @Module({ 8 | controllers: [UserAuthController], 9 | providers: [], 10 | exports: [], 11 | imports: [UserModule, AuthModule, AwsModule], 12 | }) 13 | export class RoutesAuthModule {} 14 | -------------------------------------------------------------------------------- /src/router/routes/routes.user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ApiKeyModule } from 'src/common/api-key/api-key.module'; 3 | import { RoleModule } from 'src/modules/role/role.module'; 4 | import { UserUserController } from 'src/modules/user/controllers/user.user.controller'; 5 | import { UserModule } from 'src/modules/user/user.module'; 6 | 7 | @Module({ 8 | controllers: [UserUserController], 9 | providers: [], 10 | exports: [], 11 | imports: [UserModule, ApiKeyModule, RoleModule], 12 | }) 13 | export class RoutesUserModule {} 14 | -------------------------------------------------------------------------------- /src/modules/role/dtos/role.update.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { faker } from '@faker-js/faker'; 3 | import { IsNotEmpty, IsString } from 'class-validator'; 4 | import { Type } from 'class-transformer'; 5 | 6 | export class RoleUpdateDto { 7 | @ApiProperty({ 8 | description: 'Description of role', 9 | example: faker.lorem.sentence(), 10 | required: false, 11 | nullable: true, 12 | }) 13 | @IsString() 14 | @IsNotEmpty() 15 | @Type(() => String) 16 | readonly description: string; 17 | } 18 | -------------------------------------------------------------------------------- /src/common/api-key/api-key.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ApiKeyXApiKeyStrategy } from 'src/common/api-key/guards/x-api-key/api-key.x-api-key.strategy'; 3 | import { ApiKeyRepositoryModule } from 'src/common/api-key/repository/api-key.repository.module'; 4 | import { ApiKeyService } from 'src/common/api-key/services/api-key.service'; 5 | 6 | @Module({ 7 | providers: [ApiKeyService, ApiKeyXApiKeyStrategy], 8 | exports: [ApiKeyService], 9 | controllers: [], 10 | imports: [ApiKeyRepositoryModule], 11 | }) 12 | export class ApiKeyModule {} 13 | -------------------------------------------------------------------------------- /src/modules/user/docs/user.user.doc.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { 3 | Doc, 4 | DocAuth, 5 | DocGuard, 6 | DocResponse, 7 | } from 'src/common/doc/decorators/doc.decorator'; 8 | 9 | export function UserUserDeleteSelfDoc(): MethodDecorator { 10 | return applyDecorators( 11 | Doc({ 12 | operation: 'modules.user.user', 13 | }), 14 | DocAuth({ 15 | jwtAccessToken: true, 16 | }), 17 | DocGuard({ role: true }), 18 | DocResponse('user.deleteSelf') 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/user/interfaces/user.interface.ts: -------------------------------------------------------------------------------- 1 | import { 2 | RoleDoc, 3 | RoleEntity, 4 | } from 'src/modules/role/repository/entities/role.entity'; 5 | import { 6 | UserDoc, 7 | UserEntity, 8 | } from 'src/modules/user/repository/entities/user.entity'; 9 | 10 | export interface IUserEntity extends Omit { 11 | role: RoleEntity; 12 | } 13 | 14 | export interface IUserDoc extends Omit { 15 | role: RoleDoc; 16 | } 17 | 18 | export interface IUserGoogleEntity { 19 | accessToken: string; 20 | refreshToken: string; 21 | } 22 | -------------------------------------------------------------------------------- /src/configs/aws.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | export default registerAs( 4 | 'aws', 5 | (): Record => ({ 6 | credential: { 7 | key: process.env.AWS_CREDENTIAL_KEY, 8 | secret: process.env.AWS_CREDENTIAL_SECRET, 9 | }, 10 | s3: { 11 | bucket: process.env.AWS_S3_BUCKET ?? 'bucket', 12 | region: process.env.AWS_S3_REGION, 13 | baseUrl: `https://${process.env.AWS_S3_BUCKET}.s3.${process.env.AWS_S3_REGION}.amazonaws.com`, 14 | }, 15 | }) 16 | ); 17 | -------------------------------------------------------------------------------- /src/common/setting/setting.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { SettingMiddlewareModule } from 'src/common/setting/middleware/setting.middleware.module'; 3 | import { SettingRepositoryModule } from 'src/common/setting/repository/setting.repository.module'; 4 | import { SettingService } from './services/setting.service'; 5 | 6 | @Global() 7 | @Module({ 8 | imports: [SettingRepositoryModule, SettingMiddlewareModule], 9 | exports: [SettingService], 10 | providers: [SettingService], 11 | controllers: [], 12 | }) 13 | export class SettingModule {} 14 | -------------------------------------------------------------------------------- /src/configs/database.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | export default registerAs( 4 | 'database', 5 | (): Record => ({ 6 | host: 7 | process.env?.DATABASE_HOST ?? 8 | 'mongodb://localhost:27017,localhost:27018,localhost:27019', 9 | name: process.env?.DATABASE_NAME ?? 'seettuwa', 10 | user: process.env?.DATABASE_USER, 11 | password: process?.env.DATABASE_PASSWORD, 12 | debug: process.env.DATABASE_DEBUG === 'true', 13 | options: process.env?.DATABASE_OPTIONS, 14 | }) 15 | ); 16 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { JobsModule } from 'src/jobs/jobs.module'; 3 | import { AppController } from './controllers/app.controller'; 4 | import { RouterModule } from 'src/router/router.module'; 5 | import { CommonModule } from 'src/common/common.module'; 6 | 7 | @Module({ 8 | controllers: [AppController], 9 | providers: [], 10 | imports: [ 11 | CommonModule, 12 | 13 | // Jobs 14 | JobsModule.forRoot(), 15 | 16 | // Routes 17 | RouterModule.forRoot(), 18 | ], 19 | }) 20 | export class AppModule {} 21 | -------------------------------------------------------------------------------- /src/common/policy/constants/policy.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_POLICY_ACTION { 2 | MANAGE = 'manage', 3 | READ = 'read', 4 | CREATE = 'create', 5 | UPDATE = 'update', 6 | DELETE = 'delete', 7 | EXPORT = 'export', 8 | IMPORT = 'import', 9 | } 10 | 11 | export enum ENUM_POLICY_REQUEST_ACTION { 12 | MANAGE, 13 | READ, 14 | CREATE, 15 | UPDATE, 16 | DELETE, 17 | EXPORT, 18 | IMPORT, 19 | } 20 | 21 | export enum ENUM_POLICY_SUBJECT { 22 | API_KEY = 'API_KEY', 23 | SETTING = 'SETTING', 24 | ROLE = 'ROLE', 25 | USER = 'USER', 26 | } 27 | -------------------------------------------------------------------------------- /src/common/dashboard/constants/dashboard.doc.constant.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | 3 | export const DashboardDocQueryStartDate = [ 4 | { 5 | name: 'startDate', 6 | allowEmptyValue: true, 7 | required: false, 8 | type: 'string', 9 | example: faker.date.recent().toString(), 10 | }, 11 | ]; 12 | 13 | export const DashboardDocQueryEndDate = [ 14 | { 15 | name: 'endDate', 16 | allowEmptyValue: true, 17 | required: false, 18 | type: 'string', 19 | example: faker.date.recent().toString(), 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /src/common/request/middleware/timezone/request.timezone.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware } from '@nestjs/common'; 2 | import { Response, NextFunction } from 'express'; 3 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 4 | 5 | @Injectable() 6 | export class RequestTimezoneMiddleware implements NestMiddleware { 7 | async use( 8 | req: IRequestApp, 9 | res: Response, 10 | next: NextFunction 11 | ): Promise { 12 | req.__timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; 13 | next(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "parserOptions": { 4 | "project": "tsconfig.json", 5 | "tsconfigRootDir": ".", 6 | "sourceType": "module" 7 | }, 8 | "plugins": ["@typescript-eslint/eslint-plugin"], 9 | "extends": [ 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "prettier" 13 | ], 14 | "root": true, 15 | "env": { 16 | "node": true, 17 | "jest": true 18 | }, 19 | "ignorePatterns": [".eslintrc.js"], 20 | "rules": { 21 | "@typescript-eslint/no-explicit-any": "off" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/common/message/docs/message.public.doc.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { Doc, DocResponse } from 'src/common/doc/decorators/doc.decorator'; 3 | import { MessageLanguageSerialization } from 'src/common/message/serializations/message.language.serialization'; 4 | 5 | export function MessagePublicLanguageDoc(): MethodDecorator { 6 | return applyDecorators( 7 | Doc({ operation: 'common.public.message' }), 8 | DocResponse('apiKey.languages', { 9 | serialization: MessageLanguageSerialization, 10 | }) 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/common/policy/decorators/policy.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata, UseGuards, applyDecorators } from '@nestjs/common'; 2 | import { POLICY_RULE_META_KEY } from 'src/common/policy/constants/policy.constant'; 3 | import { PolicyGuard } from 'src/common/policy/guards/policy.ability.guard'; 4 | import { IPolicyRule } from 'src/common/policy/interfaces/policy.interface'; 5 | 6 | export function PolicyAbilityProtected( 7 | ...handlers: IPolicyRule[] 8 | ): MethodDecorator { 9 | return applyDecorators( 10 | UseGuards(PolicyGuard), 11 | SetMetadata(POLICY_RULE_META_KEY, handlers) 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en", 4 | "words": [ 5 | "alphanum", 6 | "DIQU", 7 | "Kvnwr", 8 | "Zgoh", 9 | "QUDRX", 10 | "MILIS", 11 | "ength", 12 | "strongpassword", 13 | "weakpassword", 14 | "apikey", 15 | "requestid", 16 | "dtos", 17 | "blabla" 18 | ], 19 | "ignorePaths": [ 20 | "node_modules/**", 21 | "endpoints/**", 22 | "*coverage/**", 23 | ".husky/**", 24 | ".github/**", 25 | "dist/**", 26 | "logs/**" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/common/api-key/constants/api-key.doc.constant.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | 3 | export const ApiKeyDocQueryIsActive = [ 4 | { 5 | name: 'isActive', 6 | allowEmptyValue: true, 7 | required: false, 8 | type: 'string', 9 | example: 'true,false', 10 | description: "boolean value with ',' delimiter", 11 | }, 12 | ]; 13 | 14 | export const ApiKeyDocParamsId = [ 15 | { 16 | name: 'apiKey', 17 | allowEmptyValue: false, 18 | required: true, 19 | type: 'string', 20 | example: faker.string.uuid(), 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /src/common/error/error.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import { APP_FILTER, APP_GUARD } from '@nestjs/core'; 3 | import { ErrorHttpFilter } from './filters/error.http.filter'; 4 | import { ErrorMetaGuard } from './guards/error.meta.guard'; 5 | 6 | @Global() 7 | @Module({ 8 | controllers: [], 9 | providers: [ 10 | { 11 | provide: APP_FILTER, 12 | useClass: ErrorHttpFilter, 13 | }, 14 | { 15 | provide: APP_GUARD, 16 | useClass: ErrorMetaGuard, 17 | }, 18 | ], 19 | imports: [], 20 | }) 21 | export class ErrorModule {} 22 | -------------------------------------------------------------------------------- /src/common/response/response.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { APP_INTERCEPTOR } from '@nestjs/core'; 3 | import { ResponseMiddlewareModule } from 'src/common/response/middleware/response.middleware.module'; 4 | import { ResponseCustomHeadersInterceptor } from './interceptors/response.custom-headers.interceptor'; 5 | 6 | @Module({ 7 | controllers: [], 8 | providers: [ 9 | { 10 | provide: APP_INTERCEPTOR, 11 | useClass: ResponseCustomHeadersInterceptor, 12 | }, 13 | ], 14 | imports: [ResponseMiddlewareModule], 15 | }) 16 | export class ResponseModule {} 17 | -------------------------------------------------------------------------------- /src/languages/en/role.json: -------------------------------------------------------------------------------- 1 | { 2 | "list": "List Role Success.", 3 | "get": "Get Role Success.", 4 | "create": "Create Succeed", 5 | "update": "Update Succeed", 6 | "updatePermission": "Update permission Succeed", 7 | "delete": "Delete Succeed", 8 | "inactive": "Inactive Succeed", 9 | "active": "Active Succeed", 10 | "error": { 11 | "notFound": "Role not found", 12 | "inactive": "Role is inactive", 13 | "exist": "Role exist", 14 | "used": "Role in used", 15 | "isActiveInvalid": "Role is active invalid", 16 | "typeForbidden": "Role type not allowed" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/common/auth/decorators/auth.google.decorator.ts: -------------------------------------------------------------------------------- 1 | import { UseGuards, applyDecorators } from '@nestjs/common'; 2 | import { AuthGoogleOauth2LoginGuard } from 'src/common/auth/guards/google-oauth2/auth.google-oauth2-login.guard'; 3 | import { AuthGoogleOauth2SignUpGuard } from 'src/common/auth/guards/google-oauth2/auth.google-oauth2-sign-up.guard'; 4 | 5 | export function AuthGoogleOAuth2SignUpProtected(): MethodDecorator { 6 | return applyDecorators(UseGuards(AuthGoogleOauth2SignUpGuard)); 7 | } 8 | 9 | export function AuthGoogleOAuth2LoginProtected(): MethodDecorator { 10 | return applyDecorators(UseGuards(AuthGoogleOauth2LoginGuard)); 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/role/decorators/role.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 3 | import { 4 | RoleDoc, 5 | RoleEntity, 6 | } from 'src/modules/role/repository/entities/role.entity'; 7 | 8 | export const GetRole = createParamDecorator( 9 | (returnPlain: boolean, ctx: ExecutionContext): RoleDoc | RoleEntity => { 10 | const { __role } = ctx 11 | .switchToHttp() 12 | .getRequest(); 13 | return returnPlain ? __role.toObject() : __role; 14 | } 15 | ); 16 | -------------------------------------------------------------------------------- /src/common/debugger/interfaces/debugger.interface.ts: -------------------------------------------------------------------------------- 1 | import { RotatingFileStream } from 'rotating-file-stream'; 2 | import { Response } from 'express'; 3 | 4 | export interface IDebuggerLog { 5 | description: string; 6 | class?: string; 7 | function?: string; 8 | path?: string; 9 | } 10 | 11 | export interface IDebuggerHttpConfigOptions { 12 | readonly stream: RotatingFileStream; 13 | } 14 | 15 | export interface IDebuggerHttpConfig { 16 | readonly debuggerHttpFormat: string; 17 | readonly debuggerHttpOptions?: IDebuggerHttpConfigOptions; 18 | } 19 | 20 | export interface IDebuggerHttpMiddleware extends Response { 21 | body: string; 22 | } 23 | -------------------------------------------------------------------------------- /src/health/docs/health.doc.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { 3 | Doc, 4 | DocAuth, 5 | DocResponse, 6 | } from 'src/common/doc/decorators/doc.decorator'; 7 | import { HealthSerialization } from 'src/health/serializations/health.serialization'; 8 | 9 | export function HealthCheckDoc(): MethodDecorator { 10 | return applyDecorators( 11 | Doc({ 12 | operation: 'health', 13 | }), 14 | DocAuth({ 15 | jwtAccessToken: true, 16 | }), 17 | DocResponse('health.check', { 18 | serialization: HealthSerialization, 19 | }) 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/common/helper/interfaces/helper.string-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { IHelperStringRandomOptions } from 'src/common/helper/interfaces/helper.interface'; 2 | 3 | export interface IHelperStringService { 4 | checkEmail(email: string): boolean; 5 | randomReference(length: number, prefix?: string): string; 6 | random(length: number, options?: IHelperStringRandomOptions): string; 7 | censor(value: string): string; 8 | checkPasswordWeak(password: string, length?: number): boolean; 9 | checkPasswordMedium(password: string, length?: number): boolean; 10 | checkPasswordStrong(password: string, length?: number): boolean; 11 | checkSafeString(text: string): boolean; 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/role/constants/role.list.constant.ts: -------------------------------------------------------------------------------- 1 | import { ENUM_PAGINATION_ORDER_DIRECTION_TYPE } from 'src/common/pagination/constants/pagination.enum.constant'; 2 | import { ENUM_ROLE_TYPE } from 'src/modules/role/constants/role.enum.constant'; 3 | 4 | export const ROLE_DEFAULT_ORDER_BY = 'createdAt'; 5 | export const ROLE_DEFAULT_ORDER_DIRECTION = 6 | ENUM_PAGINATION_ORDER_DIRECTION_TYPE.ASC; 7 | export const ROLE_DEFAULT_PER_PAGE = 20; 8 | export const ROLE_DEFAULT_AVAILABLE_ORDER_BY = ['name', 'createdAt']; 9 | export const ROLE_DEFAULT_AVAILABLE_SEARCH = ['name']; 10 | export const ROLE_DEFAULT_IS_ACTIVE = [true, false]; 11 | export const ROLE_DEFAULT_TYPE = Object.values(ENUM_ROLE_TYPE); 12 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | 2 | version: 2 3 | updates: 4 | - package-ecosystem: "npm" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | day: tuesday 9 | time: "01:00" 10 | open-pull-requests-limit: 3 11 | target-branch: "development" 12 | commit-message: 13 | prefix: "npm" 14 | labels: 15 | - dependabot 16 | - package-ecosystem: "github-actions" 17 | directory: "/" 18 | schedule: 19 | interval: "monthly" 20 | day: tuesday 21 | time: "00:00" 22 | open-pull-requests-limit: 3 23 | target-branch: "development" 24 | commit-message: 25 | prefix: "github-action" 26 | labels: 27 | - dependabot 28 | -------------------------------------------------------------------------------- /src/common/request/middleware/id/request.id.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware } from '@nestjs/common'; 2 | import { Response, NextFunction } from 'express'; 3 | import { DatabaseDefaultUUID } from 'src/common/database/constants/database.function.constant'; 4 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 5 | 6 | @Injectable() 7 | export class RequestIdMiddleware implements NestMiddleware { 8 | async use( 9 | req: IRequestApp, 10 | res: Response, 11 | next: NextFunction 12 | ): Promise { 13 | const uuid: string = DatabaseDefaultUUID(); 14 | 15 | req.__id = uuid; 16 | next(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@nestjs/common'; 2 | import { NestFactory } from '@nestjs/core'; 3 | import { CommandModule, CommandService } from 'nestjs-command'; 4 | import { MigrationModule } from 'src/migration/migration.module'; 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.createApplicationContext(MigrationModule, { 8 | logger: ['error'], 9 | }); 10 | 11 | const logger = new Logger(); 12 | 13 | try { 14 | await app.select(CommandModule).get(CommandService).exec(); 15 | process.exit(0); 16 | } catch (err: unknown) { 17 | logger.error(err, 'Migration'); 18 | process.exit(1); 19 | } 20 | } 21 | 22 | bootstrap(); 23 | -------------------------------------------------------------------------------- /src/modules/user/constants/user.status-code.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_USER_STATUS_CODE_ERROR { 2 | USER_NOT_FOUND_ERROR = 5200, 3 | USER_USERNAME_EXISTS_ERROR = 5201, 4 | USER_EMAIL_EXIST_ERROR = 5202, 5 | USER_MOBILE_NUMBER_EXIST_ERROR = 5203, 6 | USER_IS_ACTIVE_ERROR = 5204, 7 | USER_INACTIVE_ERROR = 5205, 8 | USER_INACTIVE_PERMANENT_ERROR = 5206, 9 | USER_PASSWORD_NOT_MATCH_ERROR = 5207, 10 | USER_PASSWORD_NEW_MUST_DIFFERENCE_ERROR = 5208, 11 | USER_PASSWORD_EXPIRED_ERROR = 5209, 12 | USER_PASSWORD_ATTEMPT_MAX_ERROR = 5210, 13 | USER_BLOCKED_ERROR = 5211, 14 | } 15 | 16 | export enum ENUM_USER_STATUS_CODE_SUCCESS { 17 | USER_PASSWORD_EXPIRED_ERROR = 1000, 18 | } 19 | -------------------------------------------------------------------------------- /src/common/auth/interfaces/auth.interface.ts: -------------------------------------------------------------------------------- 1 | import { ENUM_AUTH_LOGIN_WITH } from 'src/common/auth/constants/auth.enum.constant'; 2 | 3 | // Auth 4 | export interface IAuthPassword { 5 | salt: string; 6 | passwordHash: string; 7 | passwordExpired: Date; 8 | passwordCreated: Date; 9 | } 10 | 11 | export interface IAuthPayloadOptions { 12 | loginWith: ENUM_AUTH_LOGIN_WITH; 13 | } 14 | 15 | export interface IAuthRefreshTokenOptions { 16 | // in milis 17 | notBeforeExpirationTime?: number | string; 18 | } 19 | 20 | export interface IAuthGooglePayload { 21 | email: string; 22 | firstName: string; 23 | lastName: string; 24 | accessToken: string; 25 | refreshToken: string; 26 | } 27 | -------------------------------------------------------------------------------- /src/common/setting/decorators/setting.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 3 | import { 4 | SettingDoc, 5 | SettingEntity, 6 | } from 'src/common/setting/repository/entities/setting.entity'; 7 | 8 | export const GetSetting = createParamDecorator( 9 | ( 10 | returnPlain: boolean, 11 | ctx: ExecutionContext 12 | ): SettingDoc | SettingEntity => { 13 | const { __setting } = ctx 14 | .switchToHttp() 15 | .getRequest(); 16 | return returnPlain ? __setting.toObject() : __setting; 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /src/common/api-key/constants/api-key.list.constant.ts: -------------------------------------------------------------------------------- 1 | import { ENUM_API_KEY_TYPE } from 'src/common/api-key/constants/api-key.enum.constant'; 2 | import { ENUM_PAGINATION_ORDER_DIRECTION_TYPE } from 'src/common/pagination/constants/pagination.enum.constant'; 3 | 4 | export const API_KEY_DEFAULT_PER_PAGE = 20; 5 | export const API_KEY_DEFAULT_ORDER_BY = 'createdAt'; 6 | export const API_KEY_DEFAULT_ORDER_DIRECTION = 7 | ENUM_PAGINATION_ORDER_DIRECTION_TYPE.ASC; 8 | export const API_KEY_DEFAULT_AVAILABLE_ORDER_BY = ['name', 'key', 'createdAt']; 9 | export const API_KEY_DEFAULT_AVAILABLE_SEARCH = ['name', 'key']; 10 | export const API_KEY_DEFAULT_IS_ACTIVE = [true, false]; 11 | export const API_KEY_DEFAULT_TYPE = Object.values(ENUM_API_KEY_TYPE); 12 | -------------------------------------------------------------------------------- /src/common/pagination/constants/pagination.constant.ts: -------------------------------------------------------------------------------- 1 | import { ENUM_PAGINATION_ORDER_DIRECTION_TYPE } from 'src/common/pagination/constants/pagination.enum.constant'; 2 | 3 | export const PAGINATION_PER_PAGE = 20; 4 | export const PAGINATION_MAX_PER_PAGE = 100; 5 | export const PAGINATION_PAGE = 1; 6 | export const PAGINATION_MAX_PAGE = 20; 7 | export const PAGINATION_ORDER_BY = 'createdAt'; 8 | export const PAGINATION_ORDER_DIRECTION: ENUM_PAGINATION_ORDER_DIRECTION_TYPE = 9 | ENUM_PAGINATION_ORDER_DIRECTION_TYPE.ASC; 10 | export const PAGINATION_AVAILABLE_ORDER_BY: string[] = ['createdAt']; 11 | export const PAGINATION_AVAILABLE_ORDER_DIRECTION: ENUM_PAGINATION_ORDER_DIRECTION_TYPE[] = 12 | Object.values(ENUM_PAGINATION_ORDER_DIRECTION_TYPE); 13 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: Linter 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | linter: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: ['18.x'] 14 | 15 | steps: 16 | - name: Git checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Git sort sha 20 | run: echo ${{ steps.vars.outputs.sha_short }} 21 | 22 | - name: Setup node version ${{ matrix.node-version }} 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | 27 | - name: Install dependencies 28 | run: yarn --frozen-lockfile 29 | 30 | - name: Linter 31 | run: yarn lint 32 | -------------------------------------------------------------------------------- /src/common/request/middleware/user-agent/request.user-agent.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware } from '@nestjs/common'; 2 | import { Response, NextFunction } from 'express'; 3 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 4 | import { UAParser, IResult } from 'ua-parser-js'; 5 | 6 | @Injectable() 7 | export class RequestUserAgentMiddleware implements NestMiddleware { 8 | async use( 9 | req: IRequestApp, 10 | res: Response, 11 | next: NextFunction 12 | ): Promise { 13 | const parserUserAgent = new UAParser(req['User-Agent']); 14 | const userAgent: IResult = parserUserAgent.getResult(); 15 | 16 | req.__userAgent = userAgent; 17 | next(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/common/file/constants/file.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_FILE_IMAGE_MIME { 2 | JPG = 'image/jpg', 3 | JPEG = 'image/jpeg', 4 | PNG = 'image/png', 5 | } 6 | 7 | export enum ENUM_FILE_EXCEL_MIME { 8 | XLS = 'application/vnd.ms-excel', 9 | XLSX = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 10 | CSV = 'text/csv', 11 | } 12 | 13 | export enum ENUM_FILE_AUDIO_MIME { 14 | MPEG = 'audio/mpeg', 15 | MP3 = 'audio/mp3', 16 | MP4 = 'audio/mp4', 17 | } 18 | 19 | export enum ENUM_FILE_VIDEO_MIME { 20 | MP4 = 'video/mp4', 21 | APPLICATION_MP4 = 'application/mp4', 22 | } 23 | 24 | export enum ENUM_FILE_TYPE { 25 | AUDIO = 'audio', 26 | IMAGE = 'image', 27 | EXCEL = 'excel', 28 | VIDEO = 'video', 29 | } 30 | -------------------------------------------------------------------------------- /src/common/pagination/dtos/pagination.list.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiHideProperty } from '@nestjs/swagger'; 2 | import { ENUM_PAGINATION_ORDER_DIRECTION_TYPE } from 'src/common/pagination/constants/pagination.enum.constant'; 3 | import { IPaginationOrder } from 'src/common/pagination/interfaces/pagination.interface'; 4 | 5 | export class PaginationListDto { 6 | @ApiHideProperty() 7 | _search: Record; 8 | 9 | @ApiHideProperty() 10 | _limit: number; 11 | 12 | @ApiHideProperty() 13 | _offset: number; 14 | 15 | @ApiHideProperty() 16 | _order: IPaginationOrder; 17 | 18 | @ApiHideProperty() 19 | _availableOrderBy: string[]; 20 | 21 | @ApiHideProperty() 22 | _availableOrderDirection: ENUM_PAGINATION_ORDER_DIRECTION_TYPE[]; 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | .warmup/ 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json 36 | 37 | # yarn 38 | .yarn/* 39 | .pnp.* 40 | !.yarn/cache 41 | !.yarn/patches 42 | !.yarn/plugins 43 | !.yarn/releases 44 | !.yarn/sdks 45 | !.yarn/versions 46 | 47 | # environment 48 | .env* 49 | !.env.example 50 | config.yaml 51 | config.yml 52 | -------------------------------------------------------------------------------- /src/configs/index.ts: -------------------------------------------------------------------------------- 1 | import AppConfig from './app.config'; 2 | import AuthConfig from './auth.config'; 3 | import DatabaseConfig from './database.config'; 4 | import HelperConfig from './helper.config'; 5 | import AwsConfig from './aws.config'; 6 | import UserConfig from './user.config'; 7 | import FileConfig from './file.config'; 8 | import RequestConfig from './request.config'; 9 | import DocConfig from './doc.config'; 10 | import DebuggerConfig from './debugger.config'; 11 | import MessageConfig from './message.config'; 12 | 13 | export default [ 14 | AppConfig, 15 | AuthConfig, 16 | DatabaseConfig, 17 | HelperConfig, 18 | AwsConfig, 19 | UserConfig, 20 | RequestConfig, 21 | FileConfig, 22 | DocConfig, 23 | DebuggerConfig, 24 | MessageConfig, 25 | ]; 26 | -------------------------------------------------------------------------------- /src/configs/debugger.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | 3 | export default registerAs( 4 | 'debugger', 5 | (): Record => ({ 6 | http: { 7 | writeIntoFile: process.env.DEBUGGER_HTTP_WRITE_INTO_FILE === 'true', 8 | writeIntoConsole: 9 | process.env.DEBUGGER_HTTP_WRITE_INTO_CONSOLE === 'true', 10 | maxFiles: 5, 11 | maxSize: '2M', 12 | }, 13 | system: { 14 | writeIntoFile: 15 | process.env.DEBUGGER_SYSTEM_WRITE_INTO_FILE === 'true', 16 | writeIntoConsole: 17 | process.env.DEBUGGER_SYSTEM_WRITE_INTO_CONSOLE === 'true', 18 | maxFiles: '7d', 19 | maxSize: '2m', 20 | }, 21 | }) 22 | ); 23 | -------------------------------------------------------------------------------- /test/jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "testTimeout": 5000, 3 | "rootDir": "../", 4 | "modulePaths": [ 5 | "." 6 | ], 7 | "testEnvironment": "node", 8 | "testMatch": [ 9 | "/test/**/*.spec.ts" 10 | ], 11 | "collectCoverage": false, 12 | "coverageDirectory": "coverage", 13 | "collectCoverageFrom": [ 14 | "./src/common/**/services/*.service.ts" 15 | ], 16 | "coverageThreshold": { 17 | "global": { 18 | "branches": 100, 19 | "functions": 100, 20 | "lines": 100, 21 | "statements": 100 22 | } 23 | }, 24 | "moduleFileExtensions": [ 25 | "js", 26 | "ts", 27 | "json" 28 | ], 29 | "transform": { 30 | "^.+\\.(t|j)s$": "ts-jest" 31 | } 32 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "useDefineForClassFields": false, 10 | "target": "ESNext", 11 | "sourceMap": true, 12 | "outDir": "./dist", 13 | "baseUrl": "./", 14 | "incremental": true, 15 | "skipLibCheck": true, 16 | "allowJs": false, 17 | "esModuleInterop": true, 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true 20 | }, 21 | "include": [ 22 | "src", 23 | "test", 24 | ], 25 | "exclude": [ 26 | "node_modules", 27 | "dist", 28 | "*coverage", 29 | "logs", 30 | "data" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/role/repository/repositories/role.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Model } from 'mongoose'; 3 | import { DatabaseMongoUUIDRepositoryAbstract } from 'src/common/database/abstracts/mongo/repositories/database.mongo.uuid.repository.abstract'; 4 | import { DatabaseModel } from 'src/common/database/decorators/database.decorator'; 5 | import { 6 | RoleDoc, 7 | RoleEntity, 8 | } from 'src/modules/role/repository/entities/role.entity'; 9 | 10 | @Injectable() 11 | export class RoleRepository extends DatabaseMongoUUIDRepositoryAbstract< 12 | RoleEntity, 13 | RoleDoc 14 | > { 15 | constructor( 16 | @DatabaseModel(RoleEntity.name) 17 | private readonly roleModel: Model 18 | ) { 19 | super(roleModel); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/configs/file.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | import bytes from 'bytes'; 3 | 4 | // if we use was api gateway, there has limitation of the payload size 5 | // the payload size 10mb 6 | export default registerAs( 7 | 'file', 8 | (): Record => ({ 9 | image: { 10 | maxFileSize: bytes('1mb'), // 1mb 11 | maxFiles: 3, // 3 files 12 | }, 13 | excel: { 14 | maxFileSize: bytes('5.5mb'), // 5.5mb 15 | maxFiles: 1, // 1 files 16 | }, 17 | audio: { 18 | maxFileSize: bytes('5.5mb'), // 5.5mb 19 | maxFiles: 1, // 1 files 20 | }, 21 | video: { 22 | maxFileSize: bytes('5.5mb'), // 5.5mb 23 | maxFiles: 1, // 1 files 24 | }, 25 | }) 26 | ); 27 | -------------------------------------------------------------------------------- /src/common/api-key/repository/repositories/api-key.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Model } from 'mongoose'; 3 | import { 4 | ApiKeyEntity, 5 | ApiKeyDoc, 6 | } from 'src/common/api-key/repository/entities/api-key.entity'; 7 | import { DatabaseMongoUUIDRepositoryAbstract } from 'src/common/database/abstracts/mongo/repositories/database.mongo.uuid.repository.abstract'; 8 | import { DatabaseModel } from 'src/common/database/decorators/database.decorator'; 9 | 10 | @Injectable() 11 | export class ApiKeyRepository extends DatabaseMongoUUIDRepositoryAbstract< 12 | ApiKeyEntity, 13 | ApiKeyDoc 14 | > { 15 | constructor( 16 | @DatabaseModel(ApiKeyEntity.name) 17 | private readonly ApiKeyDoc: Model 18 | ) { 19 | super(ApiKeyDoc); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/common/api-key/guards/api-key.not-found.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | NotFoundException, 6 | } from '@nestjs/common'; 7 | import { ENUM_API_KEY_STATUS_CODE_ERROR } from 'src/common/api-key/constants/api-key.status-code.constant'; 8 | 9 | @Injectable() 10 | export class ApiKeyNotFoundGuard implements CanActivate { 11 | async canActivate(context: ExecutionContext): Promise { 12 | const { __apiKey } = context.switchToHttp().getRequest(); 13 | 14 | if (!__apiKey) { 15 | throw new NotFoundException({ 16 | statusCode: 17 | ENUM_API_KEY_STATUS_CODE_ERROR.API_KEY_NOT_FOUND_ERROR, 18 | message: 'apiKey.error.notFound', 19 | }); 20 | } 21 | return true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/common/api-key/guards/api-key.put-to-request.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; 2 | import { ApiKeyDoc } from 'src/common/api-key/repository/entities/api-key.entity'; 3 | import { ApiKeyService } from 'src/common/api-key/services/api-key.service'; 4 | 5 | @Injectable() 6 | export class ApiKeyPutToRequestGuard implements CanActivate { 7 | constructor(private readonly apiKeyService: ApiKeyService) {} 8 | 9 | async canActivate(context: ExecutionContext): Promise { 10 | const request = context.switchToHttp().getRequest(); 11 | const { params } = request; 12 | const { apiKey } = params; 13 | 14 | const check: ApiKeyDoc = await this.apiKeyService.findOneById(apiKey); 15 | request.__apiKey = check; 16 | 17 | return true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/common/auth/guards/jwt-access/auth.jwt-access.guard.ts: -------------------------------------------------------------------------------- 1 | import { AuthGuard } from '@nestjs/passport'; 2 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 3 | import { ENUM_AUTH_STATUS_CODE_ERROR } from 'src/common/auth/constants/auth.status-code.constant'; 4 | 5 | @Injectable() 6 | export class AuthJwtAccessGuard extends AuthGuard('jwt') { 7 | handleRequest(err: Error, user: TUser, info: Error): TUser { 8 | if (err || !user) { 9 | throw new UnauthorizedException({ 10 | statusCode: 11 | ENUM_AUTH_STATUS_CODE_ERROR.AUTH_JWT_ACCESS_TOKEN_ERROR, 12 | message: 'auth.error.accessTokenUnauthorized', 13 | _error: err ? err.message : info.message, 14 | }); 15 | } 16 | 17 | return user; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/common/setting/repository/repositories/setting.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Model } from 'mongoose'; 3 | import { DatabaseMongoUUIDRepositoryAbstract } from 'src/common/database/abstracts/mongo/repositories/database.mongo.uuid.repository.abstract'; 4 | import { DatabaseModel } from 'src/common/database/decorators/database.decorator'; 5 | import { 6 | SettingDoc, 7 | SettingEntity, 8 | } from 'src/common/setting/repository/entities/setting.entity'; 9 | 10 | @Injectable() 11 | export class SettingRepository extends DatabaseMongoUUIDRepositoryAbstract< 12 | SettingEntity, 13 | SettingDoc 14 | > { 15 | constructor( 16 | @DatabaseModel(SettingEntity.name) 17 | private readonly settingModel: Model 18 | ) { 19 | super(settingModel); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/common/debugger/middleware/debugger.middleware.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; 2 | import { 3 | DebuggerHttpMiddleware, 4 | DebuggerHttpResponseMiddleware, 5 | DebuggerHttpWriteIntoConsoleMiddleware, 6 | DebuggerHttpWriteIntoFileMiddleware, 7 | } from 'src/common/debugger/middleware/http/debugger.http.middleware'; 8 | 9 | @Module({}) 10 | export class DebuggerMiddlewareModule implements NestModule { 11 | configure(consumer: MiddlewareConsumer): void { 12 | consumer 13 | .apply( 14 | DebuggerHttpResponseMiddleware, 15 | DebuggerHttpMiddleware, 16 | DebuggerHttpWriteIntoConsoleMiddleware, 17 | DebuggerHttpWriteIntoFileMiddleware 18 | ) 19 | .forRoutes('*'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/common/setting/guards/setting.not-found.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | NotFoundException, 6 | } from '@nestjs/common'; 7 | import { ENUM_SETTING_STATUS_CODE_ERROR } from 'src/common/setting/constants/setting.status-code.constant'; 8 | 9 | @Injectable() 10 | export class SettingNotFoundGuard implements CanActivate { 11 | async canActivate(context: ExecutionContext): Promise { 12 | const { __setting } = context.switchToHttp().getRequest(); 13 | 14 | if (!__setting) { 15 | throw new NotFoundException({ 16 | statusCode: 17 | ENUM_SETTING_STATUS_CODE_ERROR.SETTING_NOT_FOUND_ERROR, 18 | message: 'setting.error.notFound', 19 | }); 20 | } 21 | 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/common/auth/guards/jwt-refresh/auth.jwt-refresh.guard.ts: -------------------------------------------------------------------------------- 1 | import { AuthGuard } from '@nestjs/passport'; 2 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 3 | import { ENUM_AUTH_STATUS_CODE_ERROR } from 'src/common/auth/constants/auth.status-code.constant'; 4 | 5 | @Injectable() 6 | export class AuthJwtRefreshGuard extends AuthGuard('jwtRefresh') { 7 | handleRequest(err: Error, user: TUser, info: Error): TUser { 8 | if (err || !user) { 9 | throw new UnauthorizedException({ 10 | statusCode: 11 | ENUM_AUTH_STATUS_CODE_ERROR.AUTH_JWT_REFRESH_TOKEN_ERROR, 12 | message: 'auth.error.refreshTokenUnauthorized', 13 | _error: err ? err.message : info.message, 14 | }); 15 | } 16 | 17 | return user; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/user/dtos/user.update-google-sso.dto.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | import { IsNotEmpty, IsString } from 'class-validator'; 4 | 5 | export class UserUpdateGoogleSSODto { 6 | @ApiProperty({ 7 | example: faker.string.alphanumeric(30), 8 | description: 'Will be valid SSO Token Encode string from google API', 9 | required: true, 10 | }) 11 | @IsString() 12 | @IsNotEmpty() 13 | readonly accessToken: string; 14 | 15 | @ApiProperty({ 16 | example: faker.string.alphanumeric(30), 17 | description: 18 | 'Will be valid SSO Secret Token Encode string from google API', 19 | required: true, 20 | }) 21 | @ApiProperty() 22 | @IsString() 23 | @IsNotEmpty() 24 | readonly refreshToken: string; 25 | } 26 | -------------------------------------------------------------------------------- /src/common/logger/decorators/logger.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, SetMetadata, UseInterceptors } from '@nestjs/common'; 2 | import { 3 | LOGGER_ACTION_META_KEY, 4 | LOGGER_OPTIONS_META_KEY, 5 | } from 'src/common/logger/constants/logger.constant'; 6 | import { ENUM_LOGGER_ACTION } from 'src/common/logger/constants/logger.enum.constant'; 7 | import { LoggerInterceptor } from 'src/common/logger/interceptors/logger.interceptor'; 8 | import { ILoggerOptions } from 'src/common/logger/interfaces/logger.interface'; 9 | 10 | export function Logger( 11 | action: ENUM_LOGGER_ACTION, 12 | options?: ILoggerOptions 13 | ): MethodDecorator { 14 | return applyDecorators( 15 | UseInterceptors(LoggerInterceptor), 16 | SetMetadata(LOGGER_ACTION_META_KEY, action), 17 | SetMetadata(LOGGER_OPTIONS_META_KEY, options ?? {}) 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/jobs/jobs.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, ForwardReference, Module, Type } from '@nestjs/common'; 2 | import { ScheduleModule } from '@nestjs/schedule'; 3 | import { JobsRouterModule } from './router/jobs.router.module'; 4 | 5 | @Module({}) 6 | export class JobsModule { 7 | static forRoot(): DynamicModule { 8 | const imports: ( 9 | | DynamicModule 10 | | Type 11 | | Promise 12 | | ForwardReference 13 | )[] = []; 14 | 15 | if (process.env.JOB_ENABLE === 'true') { 16 | imports.push(ScheduleModule.forRoot(), JobsRouterModule); 17 | } 18 | 19 | return { 20 | module: JobsModule, 21 | providers: [], 22 | exports: [], 23 | controllers: [], 24 | imports, 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/common/logger/dtos/logger.create.dto.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ENUM_LOGGER_ACTION, 3 | ENUM_LOGGER_LEVEL, 4 | } from 'src/common/logger/constants/logger.enum.constant'; 5 | import { ENUM_REQUEST_METHOD } from 'src/common/request/constants/request.enum.constant'; 6 | import { ENUM_ROLE_TYPE } from 'src/modules/role/constants/role.enum.constant'; 7 | 8 | export class LoggerCreateDto { 9 | action: ENUM_LOGGER_ACTION; 10 | description: string; 11 | apiKey?: string; 12 | user?: string; 13 | requestId?: string; 14 | method: ENUM_REQUEST_METHOD; 15 | path: string; 16 | role?: string; 17 | type?: ENUM_ROLE_TYPE; 18 | tags?: string[]; 19 | params?: Record; 20 | bodies?: Record; 21 | statusCode?: number; 22 | } 23 | 24 | export class LoggerCreateRawDto extends LoggerCreateDto { 25 | level: ENUM_LOGGER_LEVEL; 26 | } 27 | -------------------------------------------------------------------------------- /src/languages/en/apiKey.json: -------------------------------------------------------------------------------- 1 | { 2 | "list":"Get list of api keys succeed", 3 | "get":"Get Detail of api key succeed", 4 | "create":"Create api key succeed", 5 | "reset":"Reset api key succeed", 6 | "update":"Update api key succeed", 7 | "inactive":"Inactive api key succeed", 8 | "active":"Active api key succeed", 9 | "updateDate":"Update date api key succeed", 10 | "delete":"Delete api key succeed", 11 | "error": { 12 | "exist": "API Key Exist", 13 | "isActiveInvalid": "API Key is active invalid", 14 | "expired": "API Key expired", 15 | "notFound": "API Key not found", 16 | "keyNeeded": "Api Key is missing", 17 | "inactive": "Auth API Inactive", 18 | "notActiveYet":"Api Key not active yet", 19 | "invalid": "Invalid API Key", 20 | "typeInvalid": "Api Key type invalid" 21 | } 22 | } -------------------------------------------------------------------------------- /src/common/setting/guards/setting.put-to-request.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; 2 | import { SettingDoc } from 'src/common/setting/repository/entities/setting.entity'; 3 | import { SettingService } from 'src/common/setting/services/setting.service'; 4 | 5 | @Injectable() 6 | export class SettingPutToRequestGuard implements CanActivate { 7 | constructor(private readonly settingService: SettingService) {} 8 | 9 | async canActivate(context: ExecutionContext): Promise { 10 | const request = context.switchToHttp().getRequest(); 11 | const { params } = request; 12 | const { setting } = params; 13 | 14 | const check: SettingDoc = await this.settingService.findOneById( 15 | setting 16 | ); 17 | request.__setting = check; 18 | 19 | return true; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/user/constants/user.list.constant.ts: -------------------------------------------------------------------------------- 1 | import { ENUM_PAGINATION_ORDER_DIRECTION_TYPE } from 'src/common/pagination/constants/pagination.enum.constant'; 2 | 3 | export const USER_DEFAULT_PER_PAGE = 20; 4 | export const USER_DEFAULT_ORDER_BY = 'createdAt'; 5 | export const USER_DEFAULT_ORDER_DIRECTION = 6 | ENUM_PAGINATION_ORDER_DIRECTION_TYPE.ASC; 7 | export const USER_DEFAULT_AVAILABLE_ORDER_BY = [ 8 | 'username', 9 | 'firstName', 10 | 'lastName', 11 | 'email', 12 | 'mobileNumber', 13 | 'createdAt', 14 | ]; 15 | export const USER_DEFAULT_AVAILABLE_SEARCH = [ 16 | 'username', 17 | 'firstName', 18 | 'lastName', 19 | 'email', 20 | 'mobileNumber', 21 | ]; 22 | 23 | export const USER_DEFAULT_IS_ACTIVE = [true, false]; 24 | export const USER_DEFAULT_BLOCKED = [true, false]; 25 | export const USER_DEFAULT_INACTIVE_PERMANENT = [true, false]; 26 | -------------------------------------------------------------------------------- /src/health/indicators/health.aws-s3.indicator.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | HealthCheckError, 4 | HealthIndicator, 5 | HealthIndicatorResult, 6 | } from '@nestjs/terminus'; 7 | import { AwsS3Service } from 'src/common/aws/services/aws.s3.service'; 8 | 9 | @Injectable() 10 | export class HealthAwsS3Indicator extends HealthIndicator { 11 | constructor(private readonly awsS3Service: AwsS3Service) { 12 | super(); 13 | } 14 | 15 | async isHealthy(key: string): Promise { 16 | try { 17 | await this.awsS3Service.checkBucketExistence(); 18 | return this.getStatus(key, true); 19 | } catch (err: unknown) { 20 | throw new HealthCheckError( 21 | 'HealthAwsS3Indicator failed', 22 | this.getStatus(key, false) 23 | ); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/role/repository/role.repository.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | import { DATABASE_CONNECTION_NAME } from 'src/common/database/constants/database.constant'; 4 | import { 5 | RoleEntity, 6 | RoleSchema, 7 | } from 'src/modules/role/repository/entities/role.entity'; 8 | import { RoleRepository } from 'src/modules/role/repository/repositories/role.repository'; 9 | 10 | @Module({ 11 | providers: [RoleRepository], 12 | exports: [RoleRepository], 13 | controllers: [], 14 | imports: [ 15 | MongooseModule.forFeature( 16 | [ 17 | { 18 | name: RoleEntity.name, 19 | schema: RoleSchema, 20 | }, 21 | ], 22 | DATABASE_CONNECTION_NAME 23 | ), 24 | ], 25 | }) 26 | export class RoleRepositoryModule {} 27 | -------------------------------------------------------------------------------- /src/modules/user/repository/user.repository.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | import { DATABASE_CONNECTION_NAME } from 'src/common/database/constants/database.constant'; 4 | import { 5 | UserEntity, 6 | UserSchema, 7 | } from 'src/modules/user/repository/entities/user.entity'; 8 | import { UserRepository } from 'src/modules/user/repository/repositories/user.repository'; 9 | 10 | @Module({ 11 | providers: [UserRepository], 12 | exports: [UserRepository], 13 | controllers: [], 14 | imports: [ 15 | MongooseModule.forFeature( 16 | [ 17 | { 18 | name: UserEntity.name, 19 | schema: UserSchema, 20 | }, 21 | ], 22 | DATABASE_CONNECTION_NAME 23 | ), 24 | ], 25 | }) 26 | export class UserRepositoryModule {} 27 | -------------------------------------------------------------------------------- /src/common/helper/constants/helper.enum.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ENUM_HELPER_DATE_FORMAT { 2 | DATE = 'YYYY-MM-DD', 3 | FRIENDLY_DATE = 'MMM, DD YYYY', 4 | FRIENDLY_DATE_TIME = 'MMM, DD YYYY HH:MM:SS', 5 | YEAR_MONTH = 'YYYY-MM', 6 | MONTH_DATE = 'MM-DD', 7 | ONLY_YEAR = 'YYYY', 8 | ONLY_MONTH = 'MM', 9 | ONLY_DATE = 'DD', 10 | ISO_DATE = 'YYYY-MM-DDTHH:MM:SSZ', 11 | DAY_LONG = 'dddd', 12 | DAY_SHORT = 'ddd', 13 | HOUR_LONG = 'HH', 14 | HOUR_SHORT = 'H', 15 | MINUTE_LONG = 'mm', 16 | MINUTE_SHORT = 'm', 17 | SECOND_LONG = 'ss', 18 | SECOND_SHORT = 's', 19 | } 20 | 21 | export enum ENUM_HELPER_DATE_DIFF { 22 | MILIS = 'milis', 23 | SECONDS = 'seconds', 24 | HOURS = 'hours', 25 | DAYS = 'days', 26 | MINUTES = 'minutes', 27 | } 28 | 29 | export enum ENUM_HELPER_FILE_TYPE { 30 | XLSX = 'xlsx', 31 | XLS = 'xls', 32 | CSV = 'csv', 33 | } 34 | -------------------------------------------------------------------------------- /src/common/logger/repository/logger.repository.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | import { DATABASE_CONNECTION_NAME } from 'src/common/database/constants/database.constant'; 4 | import { 5 | LoggerEntity, 6 | LoggerSchema, 7 | } from 'src/common/logger/repository/entities/logger.entity'; 8 | import { LoggerRepository } from 'src/common/logger/repository/repositories/logger.repository'; 9 | 10 | @Module({ 11 | providers: [LoggerRepository], 12 | exports: [LoggerRepository], 13 | controllers: [], 14 | imports: [ 15 | MongooseModule.forFeature( 16 | [ 17 | { 18 | name: LoggerEntity.name, 19 | schema: LoggerSchema, 20 | }, 21 | ], 22 | DATABASE_CONNECTION_NAME 23 | ), 24 | ], 25 | }) 26 | export class LoggerRepositoryModule {} 27 | -------------------------------------------------------------------------------- /src/common/api-key/repository/api-key.repository.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | import { 4 | ApiKeyEntity, 5 | ApiKeySchema, 6 | } from 'src/common/api-key/repository/entities/api-key.entity'; 7 | import { ApiKeyRepository } from 'src/common/api-key/repository/repositories/api-key.repository'; 8 | import { DATABASE_CONNECTION_NAME } from 'src/common/database/constants/database.constant'; 9 | 10 | @Module({ 11 | providers: [ApiKeyRepository], 12 | exports: [ApiKeyRepository], 13 | controllers: [], 14 | imports: [ 15 | MongooseModule.forFeature( 16 | [ 17 | { 18 | name: ApiKeyEntity.name, 19 | schema: ApiKeySchema, 20 | }, 21 | ], 22 | DATABASE_CONNECTION_NAME 23 | ), 24 | ], 25 | }) 26 | export class ApiKeyRepositoryModule {} 27 | -------------------------------------------------------------------------------- /src/common/dashboard/dtos/dashboard.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { Type } from 'class-transformer'; 3 | import { IsDate, IsOptional, ValidateIf } from 'class-validator'; 4 | import { MinGreaterThan } from 'src/common/request/validations/request.min-greater-than.validation'; 5 | 6 | export class DashboardDto { 7 | @ApiProperty({ 8 | name: 'startDate', 9 | required: false, 10 | nullable: true, 11 | }) 12 | @IsDate() 13 | @IsOptional() 14 | @Type(() => Date) 15 | @ValidateIf((e) => e.startDate !== '' || e.endDate !== '') 16 | startDate?: Date; 17 | 18 | @ApiProperty({ 19 | name: 'endDate', 20 | required: false, 21 | nullable: true, 22 | }) 23 | @IsDate() 24 | @IsOptional() 25 | @MinGreaterThan('startDate') 26 | @Type(() => Date) 27 | @ValidateIf((e) => e.startDate !== '' || e.endDate !== '') 28 | endDate?: Date; 29 | } 30 | -------------------------------------------------------------------------------- /src/common/helper/interfaces/helper.file-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHelperFileWriteExcelOptions, 3 | IHelperFileReadExcelOptions, 4 | IHelperFileRows, 5 | IHelperFileCreateExcelWorkbookOptions, 6 | } from 'src/common/helper/interfaces/helper.interface'; 7 | import { WorkBook } from 'xlsx'; 8 | 9 | export interface IHelperFileService { 10 | createExcelWorkbook( 11 | rows: IHelperFileRows[], 12 | options?: IHelperFileCreateExcelWorkbookOptions 13 | ): WorkBook; 14 | writeExcelToBuffer( 15 | workbook: WorkBook, 16 | options?: IHelperFileWriteExcelOptions 17 | ): Buffer; 18 | readExcelFromBuffer( 19 | file: Buffer, 20 | options?: IHelperFileReadExcelOptions 21 | ): IHelperFileRows[]; 22 | convertToBytes(megabytes: string): number; 23 | createJson(path: string, data: Record[]): boolean; 24 | readJson(path: string): Record[]; 25 | } 26 | -------------------------------------------------------------------------------- /src/common/setting/repository/setting.repository.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { MongooseModule } from '@nestjs/mongoose'; 3 | import { DATABASE_CONNECTION_NAME } from 'src/common/database/constants/database.constant'; 4 | import { 5 | SettingEntity, 6 | SettingSchema, 7 | } from 'src/common/setting/repository/entities/setting.entity'; 8 | import { SettingRepository } from 'src/common/setting/repository/repositories/setting.repository'; 9 | 10 | @Module({ 11 | providers: [SettingRepository], 12 | exports: [SettingRepository], 13 | controllers: [], 14 | imports: [ 15 | MongooseModule.forFeature( 16 | [ 17 | { 18 | name: SettingEntity.name, 19 | schema: SettingSchema, 20 | }, 21 | ], 22 | DATABASE_CONNECTION_NAME 23 | ), 24 | ], 25 | }) 26 | export class SettingRepositoryModule {} 27 | -------------------------------------------------------------------------------- /src/health/serializations/health.serialization.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class HealthSerialization { 4 | @ApiProperty({ 5 | required: true, 6 | nullable: false, 7 | example: 'ok', 8 | }) 9 | status: string; 10 | 11 | @ApiProperty({ 12 | required: true, 13 | nullable: false, 14 | example: { 15 | awsBucket: { 16 | status: 'up', 17 | }, 18 | }, 19 | }) 20 | info: Record; 21 | 22 | @ApiProperty({ 23 | required: true, 24 | nullable: false, 25 | example: {}, 26 | }) 27 | error: Record; 28 | 29 | @ApiProperty({ 30 | required: true, 31 | nullable: false, 32 | example: { 33 | awsBucket: { 34 | status: 'up', 35 | }, 36 | }, 37 | }) 38 | details: Record; 39 | } 40 | -------------------------------------------------------------------------------- /src/common/auth/guards/google-oauth2/auth.google-oauth2-login.guard.ts: -------------------------------------------------------------------------------- 1 | import { AuthGuard } from '@nestjs/passport'; 2 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 3 | import { ENUM_AUTH_STATUS_CODE_ERROR } from 'src/common/auth/constants/auth.status-code.constant'; 4 | 5 | @Injectable() 6 | export class AuthGoogleOauth2LoginGuard extends AuthGuard('googleLogin') { 7 | constructor() { 8 | super({ 9 | accessType: 'offline', 10 | prompt: 'consent', 11 | }); 12 | } 13 | 14 | handleRequest(err: Error, user: TUser, info: Error): TUser { 15 | if (err || !user) { 16 | throw new UnauthorizedException({ 17 | statusCode: ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GOOGLE_SSO_ERROR, 18 | message: 'auth.error.googleSSO', 19 | _error: err ? err.message : info.message, 20 | }); 21 | } 22 | 23 | return user; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/common/auth/guards/google-oauth2/auth.google-oauth2-sign-up.guard.ts: -------------------------------------------------------------------------------- 1 | import { AuthGuard } from '@nestjs/passport'; 2 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 3 | import { ENUM_AUTH_STATUS_CODE_ERROR } from 'src/common/auth/constants/auth.status-code.constant'; 4 | 5 | @Injectable() 6 | export class AuthGoogleOauth2SignUpGuard extends AuthGuard('googleSignUp') { 7 | constructor() { 8 | super({ 9 | accessType: 'offline', 10 | prompt: 'consent', 11 | }); 12 | } 13 | 14 | handleRequest(err: Error, user: TUser, info: Error): TUser { 15 | if (err || !user) { 16 | throw new UnauthorizedException({ 17 | statusCode: ENUM_AUTH_STATUS_CODE_ERROR.AUTH_GOOGLE_SSO_ERROR, 18 | message: 'auth.error.googleSSO', 19 | _error: err ? err.message : info.message, 20 | }); 21 | } 22 | 23 | return user; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/common/file/pipes/file.required.pipe.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PipeTransform, 3 | Injectable, 4 | UnprocessableEntityException, 5 | } from '@nestjs/common'; 6 | import { ENUM_FILE_STATUS_CODE_ERROR } from 'src/common/file/constants/file.status-code.constant'; 7 | import { IFile } from 'src/common/file/interfaces/file.interface'; 8 | 9 | @Injectable() 10 | export class FileRequiredPipe implements PipeTransform { 11 | async transform(value: IFile | IFile[]): Promise { 12 | await this.validate(value); 13 | 14 | return value; 15 | } 16 | 17 | async validate(value: IFile | IFile[]): Promise { 18 | if (!value || (Array.isArray(value) && value.length === 0)) { 19 | throw new UnprocessableEntityException({ 20 | statusCode: ENUM_FILE_STATUS_CODE_ERROR.FILE_NEEDED_ERROR, 21 | message: 'file.error.notFound', 22 | }); 23 | } 24 | 25 | return; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/common/request/interfaces/request.interface.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | import { IApiKeyPayload } from 'src/common/api-key/interfaces/api-key.interface'; 3 | import { RequestPaginationSerialization } from 'src/common/request/serializations/request.pagination.serialization'; 4 | import { IResult } from 'ua-parser-js'; 5 | 6 | export interface IRequestApp extends Request { 7 | apiKey?: IApiKeyPayload; 8 | user?: Record; 9 | 10 | __id: string; 11 | __xTimestamp?: number; 12 | __timestamp: number; 13 | __timezone: string; 14 | __customLang: string[]; 15 | __xCustomLang: string; 16 | __version: string; 17 | __repoVersion: string; 18 | __userAgent: IResult; 19 | 20 | __class?: string; 21 | __function?: string; 22 | 23 | __filters?: Record< 24 | string, 25 | string | number | boolean | Array 26 | >; 27 | __pagination?: RequestPaginationSerialization; 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/user/guards/payload/user.payload.put-to-request.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; 2 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 3 | import { UserDoc } from 'src/modules/user/repository/entities/user.entity'; 4 | import { UserService } from 'src/modules/user/services/user.service'; 5 | 6 | @Injectable() 7 | export class UserPayloadPutToRequestGuard implements CanActivate { 8 | constructor(private readonly userService: UserService) {} 9 | 10 | async canActivate(context: ExecutionContext): Promise { 11 | const request = context 12 | .switchToHttp() 13 | .getRequest(); 14 | const { user } = request; 15 | 16 | const check: UserDoc = await this.userService.findOneById(user._id, { 17 | join: true, 18 | }); 19 | request.__user = check; 20 | 21 | return true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/common/helper/interfaces/helper.encryption-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHelperJwtOptions, 3 | IHelperJwtVerifyOptions, 4 | } from 'src/common/helper/interfaces/helper.interface'; 5 | 6 | export interface IHelperEncryptionService { 7 | base64Encrypt(data: string): string; 8 | base64Decrypt(data: string): string; 9 | base64Compare(clientBasicToken: string, ourBasicToken: string): boolean; 10 | aes256Encrypt( 11 | data: string | Record | Record[], 12 | key: string, 13 | iv: string 14 | ): string; 15 | aes256Decrypt( 16 | encrypted: string, 17 | key: string, 18 | iv: string 19 | ): string | Record | Record[]; 20 | jwtEncrypt( 21 | payload: Record, 22 | options: IHelperJwtOptions 23 | ): string; 24 | jwtDecrypt(token: string): Record; 25 | jwtVerify(token: string, options: IHelperJwtVerifyOptions): boolean; 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/role/constants/role.doc.constant.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { ENUM_ROLE_TYPE } from 'src/modules/role/constants/role.enum.constant'; 3 | 4 | export const RoleDocQueryIsActive = [ 5 | { 6 | name: 'isActive', 7 | allowEmptyValue: true, 8 | required: false, 9 | type: 'string', 10 | example: 'true,false', 11 | description: "boolean value with ',' delimiter", 12 | }, 13 | ]; 14 | 15 | export const RoleDocQueryType = [ 16 | { 17 | name: 'type', 18 | allowEmptyValue: true, 19 | required: false, 20 | type: 'string', 21 | example: Object.values(ENUM_ROLE_TYPE).join(','), 22 | description: "enum value with ',' delimiter", 23 | }, 24 | ]; 25 | 26 | export const RoleDocParamsId = [ 27 | { 28 | name: 'role', 29 | allowEmptyValue: false, 30 | required: true, 31 | type: 'string', 32 | example: faker.string.uuid(), 33 | }, 34 | ]; 35 | -------------------------------------------------------------------------------- /src/modules/role/guards/role.put-to-request.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; 2 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 3 | import { RoleDoc } from 'src/modules/role/repository/entities/role.entity'; 4 | import { RoleService } from 'src/modules/role/services/role.service'; 5 | 6 | @Injectable() 7 | export class RolePutToRequestGuard implements CanActivate { 8 | constructor(private readonly roleService: RoleService) {} 9 | 10 | async canActivate(context: ExecutionContext): Promise { 11 | const request = context 12 | .switchToHttp() 13 | .getRequest(); 14 | const { params } = request; 15 | const { role } = params; 16 | 17 | const check: RoleDoc = await this.roleService.findOneById(role, { 18 | join: true, 19 | }); 20 | request.__role = check; 21 | 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/modules/user/guards/user.put-to-request.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; 2 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 3 | import { UserDoc } from 'src/modules/user/repository/entities/user.entity'; 4 | import { UserService } from 'src/modules/user/services/user.service'; 5 | 6 | @Injectable() 7 | export class UserPutToRequestGuard implements CanActivate { 8 | constructor(private readonly userService: UserService) {} 9 | 10 | async canActivate(context: ExecutionContext): Promise { 11 | const request = context 12 | .switchToHttp() 13 | .getRequest(); 14 | const { params } = request; 15 | const { user } = params; 16 | 17 | const check: UserDoc = await this.userService.findOneById(user, { 18 | join: true, 19 | }); 20 | request.__user = check; 21 | 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/common/helper/services/helper.hash.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { compareSync, genSaltSync, hashSync } from 'bcryptjs'; 3 | import { SHA256, enc } from 'crypto-js'; 4 | import { IHelperHashService } from 'src/common/helper/interfaces/helper.hash-service.interface'; 5 | 6 | @Injectable() 7 | export class HelperHashService implements IHelperHashService { 8 | randomSalt(length: number): string { 9 | return genSaltSync(length); 10 | } 11 | 12 | bcrypt(passwordString: string, salt: string): string { 13 | return hashSync(passwordString, salt); 14 | } 15 | 16 | bcryptCompare(passwordString: string, passwordHashed: string): boolean { 17 | return compareSync(passwordString, passwordHashed); 18 | } 19 | 20 | sha256(string: string): string { 21 | return SHA256(string).toString(enc.Hex); 22 | } 23 | 24 | sha256Compare(hashOne: string, hashTwo: string): boolean { 25 | return hashOne === hashTwo; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/role/guards/role.not-found.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | NotFoundException, 6 | } from '@nestjs/common'; 7 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 8 | import { ENUM_ROLE_STATUS_CODE_ERROR } from 'src/modules/role/constants/role.status-code.constant'; 9 | import { RoleDoc } from 'src/modules/role/repository/entities/role.entity'; 10 | 11 | @Injectable() 12 | export class RoleNotFoundGuard implements CanActivate { 13 | async canActivate(context: ExecutionContext): Promise { 14 | const { __role } = context 15 | .switchToHttp() 16 | .getRequest(); 17 | 18 | if (!__role) { 19 | throw new NotFoundException({ 20 | statusCode: ENUM_ROLE_STATUS_CODE_ERROR.ROLE_NOT_FOUND_ERROR, 21 | message: 'role.error.notFound', 22 | }); 23 | } 24 | 25 | return true; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/user/guards/user.not-found.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | NotFoundException, 6 | } from '@nestjs/common'; 7 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 8 | import { ENUM_USER_STATUS_CODE_ERROR } from 'src/modules/user/constants/user.status-code.constant'; 9 | import { UserDoc } from 'src/modules/user/repository/entities/user.entity'; 10 | 11 | @Injectable() 12 | export class UserNotFoundGuard implements CanActivate { 13 | async canActivate(context: ExecutionContext): Promise { 14 | const { __user } = context 15 | .switchToHttp() 16 | .getRequest(); 17 | 18 | if (!__user) { 19 | throw new NotFoundException({ 20 | statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR, 21 | message: 'user.error.notFound', 22 | }); 23 | } 24 | 25 | return true; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/app/docs/app.doc.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators } from '@nestjs/common'; 2 | import { AppHelloSerialization } from 'src/app/serializations/app.hello.serialization'; 3 | import { 4 | Doc, 5 | DocAuth, 6 | DocGuard, 7 | DocResponse, 8 | } from 'src/common/doc/decorators/doc.decorator'; 9 | 10 | export function AppHelloDoc(): MethodDecorator { 11 | return applyDecorators( 12 | Doc({ 13 | operation: 'hello', 14 | }), 15 | DocResponse('app.hello', { 16 | serialization: AppHelloSerialization, 17 | }) 18 | ); 19 | } 20 | 21 | export function AppHelloApiKeyDoc(): MethodDecorator { 22 | return applyDecorators( 23 | Doc({ 24 | operation: 'hello', 25 | }), 26 | DocAuth({ apiKey: true }), 27 | DocGuard({ timestamp: true, userAgent: true }), 28 | DocResponse('app.helloApiKey', { 29 | serialization: AppHelloSerialization, 30 | }) 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/common/message/interfaces/message.service.interface.ts: -------------------------------------------------------------------------------- 1 | import { ValidationError } from '@nestjs/common'; 2 | import { 3 | IErrors, 4 | IErrorsImport, 5 | IValidationErrorImport, 6 | } from 'src/common/error/interfaces/error.interface'; 7 | import { 8 | IMessageErrorOptions, 9 | IMessageOptions, 10 | IMessageSetOptions, 11 | } from 'src/common/message/interfaces/message.interface'; 12 | 13 | export interface IMessageService { 14 | getAvailableLanguages(): string[]; 15 | getLanguage(): string; 16 | filterLanguage(customLanguages: string[]): string[]; 17 | setMessage(lang: string, key: string, options?: IMessageSetOptions): string; 18 | getRequestErrorsMessage( 19 | requestErrors: ValidationError[], 20 | options?: IMessageErrorOptions 21 | ): IErrors[]; 22 | getImportErrorsMessage( 23 | errors: IValidationErrorImport[], 24 | options?: IMessageErrorOptions 25 | ): IErrorsImport[]; 26 | get(key: string, options?: IMessageOptions): T; 27 | } 28 | -------------------------------------------------------------------------------- /src/common/logger/repository/repositories/logger.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Model } from 'mongoose'; 3 | import { ApiKeyEntity } from 'src/common/api-key/repository/entities/api-key.entity'; 4 | import { DatabaseMongoUUIDRepositoryAbstract } from 'src/common/database/abstracts/mongo/repositories/database.mongo.uuid.repository.abstract'; 5 | import { DatabaseModel } from 'src/common/database/decorators/database.decorator'; 6 | import { 7 | LoggerDoc, 8 | LoggerEntity, 9 | } from 'src/common/logger/repository/entities/logger.entity'; 10 | 11 | @Injectable() 12 | export class LoggerRepository extends DatabaseMongoUUIDRepositoryAbstract< 13 | LoggerEntity, 14 | LoggerDoc 15 | > { 16 | constructor( 17 | @DatabaseModel(LoggerEntity.name) 18 | private readonly LoggerDoc: Model 19 | ) { 20 | super(LoggerDoc, { 21 | path: 'apiKey', 22 | match: '_id', 23 | model: ApiKeyEntity.name, 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/user/repository/repositories/user.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { Model } from 'mongoose'; 3 | import { DatabaseMongoUUIDRepositoryAbstract } from 'src/common/database/abstracts/mongo/repositories/database.mongo.uuid.repository.abstract'; 4 | import { DatabaseModel } from 'src/common/database/decorators/database.decorator'; 5 | import { RoleEntity } from 'src/modules/role/repository/entities/role.entity'; 6 | import { 7 | UserDoc, 8 | UserEntity, 9 | } from 'src/modules/user/repository/entities/user.entity'; 10 | 11 | @Injectable() 12 | export class UserRepository extends DatabaseMongoUUIDRepositoryAbstract< 13 | UserEntity, 14 | UserDoc 15 | > { 16 | constructor( 17 | @DatabaseModel(UserEntity.name) 18 | private readonly userModel: Model 19 | ) { 20 | super(userModel, { 21 | path: 'role', 22 | localField: 'role', 23 | foreignField: '_id', 24 | model: RoleEntity.name, 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/common/dashboard/serializations/dashboard.serialization.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | 4 | export class DashboardSerialization { 5 | @ApiProperty({ 6 | name: 'name', 7 | nullable: true, 8 | }) 9 | name?: string; 10 | 11 | @ApiProperty({ 12 | name: 'month', 13 | nullable: true, 14 | }) 15 | month?: number; 16 | 17 | @ApiProperty({ 18 | name: 'year', 19 | nullable: true, 20 | }) 21 | year?: number; 22 | 23 | @ApiProperty({ 24 | required: true, 25 | nullable: false, 26 | name: 'total', 27 | example: faker.number.int({ min: 1000, max: 9999 }), 28 | description: 'Total user', 29 | }) 30 | total: number; 31 | 32 | @ApiProperty({ 33 | name: 'percent', 34 | description: 'Percent of target', 35 | required: true, 36 | nullable: false, 37 | example: faker.number.float({ max: 100 }), 38 | }) 39 | percent: number; 40 | } 41 | -------------------------------------------------------------------------------- /src/modules/user/guards/user.can-not-ourself.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | NotFoundException, 6 | } from '@nestjs/common'; 7 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 8 | import { ENUM_USER_STATUS_CODE_ERROR } from 'src/modules/user/constants/user.status-code.constant'; 9 | import { UserDoc } from 'src/modules/user/repository/entities/user.entity'; 10 | 11 | @Injectable() 12 | export class UserCanNotOurSelfGuard implements CanActivate { 13 | async canActivate(context: ExecutionContext): Promise { 14 | const { __user, user } = context 15 | .switchToHttp() 16 | .getRequest(); 17 | 18 | if (__user._id === user._id) { 19 | throw new NotFoundException({ 20 | statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_NOT_FOUND_ERROR, 21 | message: 'user.error.notFound', 22 | }); 23 | } 24 | 25 | return true; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/common/pagination/interfaces/pagination.interface.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ENUM_PAGINATION_FILTER_CASE_OPTIONS, 3 | ENUM_PAGINATION_FILTER_DATE_TIME_OPTIONS, 4 | ENUM_PAGINATION_ORDER_DIRECTION_TYPE, 5 | } from 'src/common/pagination/constants/pagination.enum.constant'; 6 | 7 | export type IPaginationOrder = Record< 8 | string, 9 | ENUM_PAGINATION_ORDER_DIRECTION_TYPE 10 | >; 11 | 12 | export interface IPaginationPaging { 13 | limit: number; 14 | offset: number; 15 | } 16 | 17 | export interface IPaginationOptions { 18 | paging?: IPaginationPaging; 19 | order?: IPaginationOrder; 20 | } 21 | 22 | export interface IPaginationFilterDateOptions { 23 | time?: ENUM_PAGINATION_FILTER_DATE_TIME_OPTIONS; 24 | } 25 | 26 | export interface IPaginationFilterStringContainOptions { 27 | case?: ENUM_PAGINATION_FILTER_CASE_OPTIONS; 28 | trim?: boolean; 29 | fullMatch?: boolean; 30 | } 31 | 32 | export interface IPaginationFilterStringEqualOptions 33 | extends IPaginationFilterStringContainOptions { 34 | isNumber?: boolean; 35 | } 36 | -------------------------------------------------------------------------------- /src/router/routes/routes.admin.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ApiKeyModule } from 'src/common/api-key/api-key.module'; 3 | import { ApiKeyAdminController } from 'src/common/api-key/controllers/api-key.admin.controller'; 4 | import { AuthModule } from 'src/common/auth/auth.module'; 5 | import { RoleAdminController } from 'src/modules/role/controllers/role.admin.controller'; 6 | import { RoleModule } from 'src/modules/role/role.module'; 7 | import { SettingAdminController } from 'src/common/setting/controllers/setting.admin.controller'; 8 | import { UserAdminController } from 'src/modules/user/controllers/user.admin.controller'; 9 | import { UserModule } from 'src/modules/user/user.module'; 10 | 11 | @Module({ 12 | controllers: [ 13 | SettingAdminController, 14 | ApiKeyAdminController, 15 | RoleAdminController, 16 | UserAdminController, 17 | ], 18 | providers: [], 19 | exports: [], 20 | imports: [ApiKeyModule, RoleModule, UserModule, AuthModule], 21 | }) 22 | export class RoutesAdminModule {} 23 | -------------------------------------------------------------------------------- /src/modules/user/serializations/user.login.serialization.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | 4 | export class UserLoginSerialization { 5 | @ApiProperty({ 6 | example: 'Bearer', 7 | required: true, 8 | nullable: false, 9 | }) 10 | readonly tokenType: string; 11 | 12 | @ApiProperty({ 13 | example: 1660190937231, 14 | description: 'Expire in timestamp', 15 | required: true, 16 | nullable: false, 17 | }) 18 | readonly expiresIn: string; 19 | 20 | @ApiProperty({ 21 | example: faker.string.alphanumeric(30), 22 | description: 'Will be valid JWT Encode string', 23 | required: true, 24 | nullable: false, 25 | }) 26 | readonly accessToken: string; 27 | 28 | @ApiProperty({ 29 | example: faker.string.alphanumeric(30), 30 | description: 'Will be valid JWT Encode string', 31 | required: true, 32 | nullable: false, 33 | }) 34 | readonly refreshToken: string; 35 | } 36 | -------------------------------------------------------------------------------- /src/configs/app.config.ts: -------------------------------------------------------------------------------- 1 | import { registerAs } from '@nestjs/config'; 2 | import { version } from 'package.json'; 3 | import { ENUM_APP_ENVIRONMENT } from 'src/app/constants/app.enum.constant'; 4 | 5 | export default registerAs( 6 | 'app', 7 | (): Record => ({ 8 | name: process.env.APP_NAME ?? 'seettuwa', 9 | env: process.env.APP_ENV ?? ENUM_APP_ENVIRONMENT.DEVELOPMENT, 10 | 11 | repoVersion: version, 12 | versioning: { 13 | enable: process.env.HTTP_VERSIONING_ENABLE === 'true' ?? false, 14 | prefix: 'v', 15 | version: process.env.HTTP_VERSION ?? '1', 16 | }, 17 | 18 | globalPrefix: '/api', 19 | http: { 20 | enable: process.env.HTTP_ENABLE === 'true' ?? false, 21 | host: process.env.HTTP_HOST ?? 'localhost', 22 | port: process.env.HTTP_PORT 23 | ? Number.parseInt(process.env.HTTP_PORT) 24 | : 3000, 25 | }, 26 | 27 | jobEnable: process.env.JOB_ENABLE === 'true' ?? false, 28 | }) 29 | ); 30 | -------------------------------------------------------------------------------- /src/modules/user/serializations/user.list.serialization.ts: -------------------------------------------------------------------------------- 1 | import { ApiHideProperty, ApiProperty, OmitType } from '@nestjs/swagger'; 2 | import { Exclude, Type } from 'class-transformer'; 3 | import { AwsS3Serialization } from 'src/common/aws/serializations/aws.s3.serialization'; 4 | import { RoleListSerialization } from 'src/modules/role/serializations/role.list.serialization'; 5 | import { UserProfileSerialization } from 'src/modules/user/serializations/user.profile.serialization'; 6 | 7 | export class UserListSerialization extends OmitType(UserProfileSerialization, [ 8 | 'photo', 9 | 'signUpDate', 10 | 'signUpFrom', 11 | 'role', 12 | ] as const) { 13 | @ApiProperty({ 14 | type: () => RoleListSerialization, 15 | required: true, 16 | nullable: false, 17 | }) 18 | @Type(() => RoleListSerialization) 19 | readonly role: RoleListSerialization; 20 | 21 | @ApiHideProperty() 22 | @Exclude() 23 | readonly photo?: AwsS3Serialization; 24 | 25 | @ApiHideProperty() 26 | @Exclude() 27 | readonly signUpDate: Date; 28 | } 29 | -------------------------------------------------------------------------------- /src/common/request/middleware/timestamp/request.timestamp.middleware.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestMiddleware } from '@nestjs/common'; 2 | import { Response, NextFunction } from 'express'; 3 | import { HelperDateService } from 'src/common/helper/services/helper.date.service'; 4 | import { HelperNumberService } from 'src/common/helper/services/helper.number.service'; 5 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 6 | 7 | @Injectable() 8 | export class RequestTimestampMiddleware implements NestMiddleware { 9 | constructor( 10 | private readonly helperNumberService: HelperNumberService, 11 | private readonly helperDateService: HelperDateService 12 | ) {} 13 | 14 | async use( 15 | req: IRequestApp, 16 | res: Response, 17 | next: NextFunction 18 | ): Promise { 19 | req.__xTimestamp = req['x-timestamp'] 20 | ? this.helperNumberService.create(req['x-timestamp']) 21 | : undefined; 22 | req.__timestamp = this.helperDateService.timestamp(); 23 | next(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/common/database/decorators/database.decorator.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InjectConnection, 3 | InjectModel, 4 | Schema, 5 | SchemaOptions, 6 | } from '@nestjs/mongoose'; 7 | import { 8 | DATABASE_CONNECTION_NAME, 9 | DATABASE_CREATED_AT_FIELD_NAME, 10 | DATABASE_UPDATED_AT_FIELD_NAME, 11 | } from 'src/common/database/constants/database.constant'; 12 | 13 | export function DatabaseConnection( 14 | connectionName?: string 15 | ): ParameterDecorator { 16 | return InjectConnection(connectionName ?? DATABASE_CONNECTION_NAME); 17 | } 18 | 19 | export function DatabaseModel( 20 | entity: any, 21 | connectionName?: string 22 | ): ParameterDecorator { 23 | return InjectModel(entity, connectionName ?? DATABASE_CONNECTION_NAME); 24 | } 25 | 26 | export function DatabaseEntity(options?: SchemaOptions): ClassDecorator { 27 | return Schema({ 28 | ...options, 29 | versionKey: false, 30 | timestamps: { 31 | createdAt: DATABASE_CREATED_AT_FIELD_NAME, 32 | updatedAt: DATABASE_UPDATED_AT_FIELD_NAME, 33 | }, 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /src/common/api-key/dtos/api-key.update-date.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { faker } from '@faker-js/faker'; 3 | import { IsDate, IsNotEmpty } from 'class-validator'; 4 | import { Type } from 'class-transformer'; 5 | import { MinGreaterThanEqual } from 'src/common/request/validations/request.min-greater-than-equal.validation'; 6 | import { MinDateToday } from 'src/common/request/validations/request.min-date-today.validation'; 7 | 8 | export class ApiKeyUpdateDateDto { 9 | @ApiProperty({ 10 | description: 'Api Key start date', 11 | example: faker.date.recent(), 12 | required: false, 13 | nullable: true, 14 | }) 15 | @IsNotEmpty() 16 | @Type(() => Date) 17 | @IsDate() 18 | @MinDateToday() 19 | startDate: Date; 20 | 21 | @ApiProperty({ 22 | description: 'Api Key end date', 23 | example: faker.date.recent(), 24 | required: false, 25 | nullable: true, 26 | }) 27 | @IsNotEmpty() 28 | @Type(() => Date) 29 | @IsDate() 30 | @MinGreaterThanEqual('startDate') 31 | endDate: Date; 32 | } 33 | -------------------------------------------------------------------------------- /src/common/error/guards/error.meta.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; 2 | import { Reflector } from '@nestjs/core'; 3 | import { 4 | ERROR_CLASS_META_KEY, 5 | ERROR_FUNCTION_META_KEY, 6 | } from 'src/common/error/constants/error.constant'; 7 | 8 | @Injectable() 9 | export class ErrorMetaGuard implements CanActivate { 10 | constructor(private readonly reflector: Reflector) {} 11 | 12 | async canActivate(context: ExecutionContext): Promise { 13 | const request = context.switchToHttp().getRequest(); 14 | const cls = this.reflector.get( 15 | ERROR_CLASS_META_KEY, 16 | context.getHandler() 17 | ); 18 | const func = this.reflector.get( 19 | ERROR_FUNCTION_META_KEY, 20 | context.getHandler() 21 | ); 22 | 23 | const className = context.getClass().name; 24 | const methodKey = context.getHandler().name; 25 | 26 | request.__class = cls ?? className; 27 | request.__function = func ?? methodKey; 28 | 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/router/routes/routes.public.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TerminusModule } from '@nestjs/terminus'; 3 | import { AuthModule } from 'src/common/auth/auth.module'; 4 | import { HealthModule } from 'src/health/health.module'; 5 | import { HealthPublicController } from 'src/health/controllers/health.public.controller'; 6 | import { MessagePublicController } from 'src/common/message/controllers/message.public.controller'; 7 | import { SettingPublicController } from 'src/common/setting/controllers/setting.public.controller'; 8 | import { UserPublicController } from 'src/modules/user/controllers/user.public.controller'; 9 | import { UserModule } from 'src/modules/user/user.module'; 10 | import { RoleModule } from 'src/modules/role/role.module'; 11 | 12 | @Module({ 13 | controllers: [ 14 | HealthPublicController, 15 | MessagePublicController, 16 | SettingPublicController, 17 | UserPublicController, 18 | ], 19 | providers: [], 20 | exports: [], 21 | imports: [TerminusModule, HealthModule, UserModule, AuthModule, RoleModule], 22 | }) 23 | export class RoutesPublicModule {} 24 | -------------------------------------------------------------------------------- /src/modules/user/dtos/user.change-password.dto.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | import { IsString, IsNotEmpty, MaxLength } from 'class-validator'; 4 | import { IsPasswordStrong } from 'src/common/request/validations/request.is-password-strong.validation'; 5 | 6 | export class UserChangePasswordDto { 7 | @ApiProperty({ 8 | description: 9 | "new string password, newPassword can't same with oldPassword", 10 | example: `${faker.string.alphanumeric(5).toLowerCase()}${faker.string 11 | .alphanumeric(5) 12 | .toUpperCase()}@@!123`, 13 | required: true, 14 | }) 15 | @IsPasswordStrong() 16 | @IsNotEmpty() 17 | @MaxLength(50) 18 | readonly newPassword: string; 19 | 20 | @ApiProperty({ 21 | description: 'old string password', 22 | example: `${faker.string.alphanumeric(5).toLowerCase()}${faker.string 23 | .alphanumeric(5) 24 | .toUpperCase()}@@!123`, 25 | required: true, 26 | }) 27 | @IsString() 28 | @IsNotEmpty() 29 | readonly oldPassword: string; 30 | } 31 | -------------------------------------------------------------------------------- /src/app/serializations/app.hello.serialization.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | import { Type } from 'class-transformer'; 4 | import { IResult } from 'ua-parser-js'; 5 | 6 | export class AppHelloSerialization { 7 | @ApiProperty({ 8 | required: true, 9 | nullable: false, 10 | example: { 11 | ua: faker.internet.userAgent(), 12 | browser: {}, 13 | engine: {}, 14 | os: {}, 15 | device: {}, 16 | cpu: {}, 17 | }, 18 | }) 19 | readonly userAgent: IResult; 20 | 21 | @ApiProperty({ 22 | required: true, 23 | nullable: false, 24 | example: faker.date.recent(), 25 | }) 26 | @Type(() => String) 27 | readonly date: Date; 28 | 29 | @ApiProperty({ 30 | required: true, 31 | nullable: false, 32 | example: faker.date.recent(), 33 | }) 34 | readonly format: string; 35 | 36 | @ApiProperty({ 37 | required: true, 38 | nullable: false, 39 | example: 1660190937231, 40 | }) 41 | readonly timestamp: number; 42 | } 43 | -------------------------------------------------------------------------------- /src/common/request/validations/request.safe-string.validation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | registerDecorator, 4 | ValidationOptions, 5 | ValidatorConstraint, 6 | ValidatorConstraintInterface, 7 | } from 'class-validator'; 8 | import { HelperStringService } from 'src/common/helper/services/helper.string.service'; 9 | 10 | @ValidatorConstraint({ async: true }) 11 | @Injectable() 12 | export class SafeStringConstraint implements ValidatorConstraintInterface { 13 | constructor(protected readonly helperStringService: HelperStringService) {} 14 | 15 | validate(value: string): boolean { 16 | return value ? this.helperStringService.checkSafeString(value) : false; 17 | } 18 | } 19 | 20 | export function SafeString(validationOptions?: ValidationOptions) { 21 | return function (object: Record, propertyName: string): void { 22 | registerDecorator({ 23 | name: 'SafeString', 24 | target: object.constructor, 25 | propertyName: propertyName, 26 | options: validationOptions, 27 | validator: SafeStringConstraint, 28 | }); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/modules/user/docs/user.public.doc.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, HttpStatus } from '@nestjs/common'; 2 | import { ENUM_DOC_REQUEST_BODY_TYPE } from 'src/common/doc/constants/doc.enum.constant'; 3 | import { 4 | Doc, 5 | DocRequest, 6 | DocResponse, 7 | } from 'src/common/doc/decorators/doc.decorator'; 8 | import { UserLoginSerialization } from 'src/modules/user/serializations/user.login.serialization'; 9 | 10 | export function UserPublicLoginDoc(): MethodDecorator { 11 | return applyDecorators( 12 | Doc({ 13 | operation: 'modules.public.user', 14 | }), 15 | DocRequest({ bodyType: ENUM_DOC_REQUEST_BODY_TYPE.JSON }), 16 | DocResponse('user.login', { 17 | serialization: UserLoginSerialization, 18 | }) 19 | ); 20 | } 21 | 22 | export function UserPublicSignUpDoc(): MethodDecorator { 23 | return applyDecorators( 24 | Doc({ 25 | operation: 'modules.public.user', 26 | }), 27 | DocRequest({ bodyType: ENUM_DOC_REQUEST_BODY_TYPE.JSON }), 28 | DocResponse('user.signUp', { 29 | httpStatus: HttpStatus.CREATED, 30 | }) 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/common/api-key/guards/api-key.expired.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | BadRequestException, 6 | } from '@nestjs/common'; 7 | import { ENUM_API_KEY_STATUS_CODE_ERROR } from 'src/common/api-key/constants/api-key.status-code.constant'; 8 | import { HelperDateService } from 'src/common/helper/services/helper.date.service'; 9 | 10 | @Injectable() 11 | export class ApiKeyExpiredGuard implements CanActivate { 12 | constructor(private readonly helperDateService: HelperDateService) {} 13 | 14 | async canActivate(context: ExecutionContext): Promise { 15 | const { __apiKey } = context.switchToHttp().getRequest(); 16 | const today: Date = this.helperDateService.create(); 17 | 18 | if ( 19 | __apiKey.startDate && 20 | __apiKey.endDate && 21 | today > __apiKey.endDate 22 | ) { 23 | throw new BadRequestException({ 24 | statusCode: 25 | ENUM_API_KEY_STATUS_CODE_ERROR.API_KEY_EXPIRED_ERROR, 26 | message: 'apiKey.error.expired', 27 | }); 28 | } 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/migration/migration.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { CommandModule } from 'nestjs-command'; 3 | import { ApiKeyModule } from 'src/common/api-key/api-key.module'; 4 | import { AuthModule } from 'src/common/auth/auth.module'; 5 | import { CommonModule } from 'src/common/common.module'; 6 | import { UserModule } from 'src/modules/user/user.module'; 7 | import { RoleModule } from 'src/modules/role/role.module'; 8 | import { MigrationApiKeySeed } from 'src/migration/seeds/migration.api-key.seed'; 9 | import { MigrationSettingSeed } from 'src/migration/seeds/migration.setting.seed'; 10 | import { MigrationRoleSeed } from 'src/migration/seeds/migration.role.seed'; 11 | import { MigrationUserSeed } from 'src/migration/seeds/migration.user.seed'; 12 | 13 | @Module({ 14 | imports: [ 15 | CommonModule, 16 | CommandModule, 17 | ApiKeyModule, 18 | AuthModule, 19 | RoleModule, 20 | UserModule, 21 | ], 22 | providers: [ 23 | MigrationApiKeySeed, 24 | MigrationSettingSeed, 25 | MigrationRoleSeed, 26 | MigrationUserSeed, 27 | ], 28 | exports: [], 29 | }) 30 | export class MigrationModule {} 31 | -------------------------------------------------------------------------------- /src/common/request/validations/request.only-digits.validation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | registerDecorator, 4 | ValidationOptions, 5 | ValidatorConstraint, 6 | ValidatorConstraintInterface, 7 | } from 'class-validator'; 8 | import { HelperNumberService } from 'src/common/helper/services/helper.number.service'; 9 | 10 | @ValidatorConstraint({ async: true }) 11 | @Injectable() 12 | export class IsOnlyDigitsConstraint implements ValidatorConstraintInterface { 13 | constructor(protected readonly helperNumberService: HelperNumberService) {} 14 | 15 | validate(value: string): boolean { 16 | return value ? this.helperNumberService.check(value) : false; 17 | } 18 | } 19 | 20 | export function IsOnlyDigits(validationOptions?: ValidationOptions) { 21 | return function (object: Record, propertyName: string): void { 22 | registerDecorator({ 23 | name: 'IsOnlyDigits', 24 | target: object.constructor, 25 | propertyName: propertyName, 26 | options: validationOptions, 27 | constraints: [], 28 | validator: IsOnlyDigitsConstraint, 29 | }); 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/modules/user/controllers/user.user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Delete } from '@nestjs/common'; 2 | import { ApiTags } from '@nestjs/swagger'; 3 | import { AuthJwtUserAccessProtected } from 'src/common/auth/decorators/auth.jwt.decorator'; 4 | import { Response } from 'src/common/response/decorators/response.decorator'; 5 | import { 6 | GetUser, 7 | UserProtected, 8 | } from 'src/modules/user/decorators/user.decorator'; 9 | import { UserUserDeleteSelfDoc } from 'src/modules/user/docs/user.user.doc'; 10 | import { UserDoc } from 'src/modules/user/repository/entities/user.entity'; 11 | import { UserService } from 'src/modules/user/services/user.service'; 12 | 13 | @ApiTags('modules.user.user') 14 | @Controller({ 15 | version: '1', 16 | path: '/user', 17 | }) 18 | export class UserUserController { 19 | constructor(private readonly userService: UserService) {} 20 | 21 | @UserUserDeleteSelfDoc() 22 | @Response('user.deleteSelf') 23 | @UserProtected() 24 | @AuthJwtUserAccessProtected() 25 | @Delete('/delete') 26 | async deleteSelf(@GetUser() user: UserDoc): Promise { 27 | await this.userService.inactivePermanent(user); 28 | 29 | return; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/common/request/validations/request.is-start-with.validation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | registerDecorator, 4 | ValidationArguments, 5 | ValidationOptions, 6 | ValidatorConstraint, 7 | ValidatorConstraintInterface, 8 | } from 'class-validator'; 9 | 10 | @ValidatorConstraint({ async: true }) 11 | @Injectable() 12 | export class IsStartWithConstraint implements ValidatorConstraintInterface { 13 | validate(value: string, args: ValidationArguments): boolean { 14 | const [prefix] = args.constraints; 15 | const check = prefix.find((val: string) => value.startsWith(val)); 16 | return check ?? false; 17 | } 18 | } 19 | 20 | export function IsStartWith( 21 | prefix: string[], 22 | validationOptions?: ValidationOptions 23 | ) { 24 | return function (object: Record, propertyName: string): void { 25 | registerDecorator({ 26 | name: 'IsStartWith', 27 | target: object.constructor, 28 | propertyName: propertyName, 29 | options: validationOptions, 30 | constraints: [prefix], 31 | validator: IsStartWithConstraint, 32 | }); 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/common/request/validations/request.max-greater-than.validation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | registerDecorator, 4 | ValidationArguments, 5 | ValidationOptions, 6 | ValidatorConstraint, 7 | ValidatorConstraintInterface, 8 | } from 'class-validator'; 9 | 10 | @ValidatorConstraint({ async: true }) 11 | @Injectable() 12 | export class MaxGreaterThanConstraint implements ValidatorConstraintInterface { 13 | validate(value: string, args: ValidationArguments): boolean { 14 | const [property] = args.constraints; 15 | const relatedValue = args.object[property]; 16 | return value < relatedValue; 17 | } 18 | } 19 | 20 | export function MaxGreaterThan( 21 | property: string, 22 | validationOptions?: ValidationOptions 23 | ) { 24 | return function (object: Record, propertyName: string): void { 25 | registerDecorator({ 26 | name: 'MaxGreaterThan', 27 | target: object.constructor, 28 | propertyName: propertyName, 29 | options: validationOptions, 30 | constraints: [property], 31 | validator: MaxGreaterThanConstraint, 32 | }); 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/common/request/validations/request.min-greater-than.validation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | registerDecorator, 4 | ValidationArguments, 5 | ValidationOptions, 6 | ValidatorConstraint, 7 | ValidatorConstraintInterface, 8 | } from 'class-validator'; 9 | 10 | @ValidatorConstraint({ async: true }) 11 | @Injectable() 12 | export class MinGreaterThanConstraint implements ValidatorConstraintInterface { 13 | validate(value: string, args: ValidationArguments): boolean { 14 | const [property] = args.constraints; 15 | const relatedValue = args.object[property]; 16 | return value > relatedValue; 17 | } 18 | } 19 | 20 | export function MinGreaterThan( 21 | property: string, 22 | validationOptions?: ValidationOptions 23 | ) { 24 | return function (object: Record, propertyName: string): void { 25 | registerDecorator({ 26 | name: 'MinGreaterThan', 27 | target: object.constructor, 28 | propertyName: propertyName, 29 | options: validationOptions, 30 | constraints: [property], 31 | validator: MinGreaterThanConstraint, 32 | }); 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/common/helper/services/helper.number.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { faker } from '@faker-js/faker'; 3 | import { IHelperNumberService } from 'src/common/helper/interfaces/helper.number-service.interface'; 4 | 5 | @Injectable() 6 | export class HelperNumberService implements IHelperNumberService { 7 | check(number: string): boolean { 8 | const regex = /^-?\d+$/; 9 | return regex.test(number); 10 | } 11 | 12 | create(number: string): number { 13 | return Number(number); 14 | } 15 | 16 | random(length: number): number { 17 | const min: number = Number.parseInt(`1`.padEnd(length, '0')); 18 | const max: number = Number.parseInt(`9`.padEnd(length, '9')); 19 | return this.randomInRange(min, max); 20 | } 21 | 22 | randomInRange(min: number, max: number): number { 23 | return faker.number.int({ min, max }); 24 | } 25 | 26 | percent(value: number, total: number): number { 27 | let tValue = value / total; 28 | if (Number.isNaN(tValue) || !Number.isFinite(tValue)) { 29 | tValue = 0; 30 | } 31 | return Number.parseFloat((tValue * 100).toFixed(2)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/common/database/abstracts/mongo/entities/database.mongo.uuid.entity.abstract.ts: -------------------------------------------------------------------------------- 1 | import { Prop } from '@nestjs/mongoose'; 2 | import { DatabaseBaseEntityAbstract } from 'src/common/database/abstracts/database.base-entity.abstract'; 3 | import { 4 | DATABASE_CREATED_AT_FIELD_NAME, 5 | DATABASE_DELETED_AT_FIELD_NAME, 6 | DATABASE_UPDATED_AT_FIELD_NAME, 7 | } from 'src/common/database/constants/database.constant'; 8 | import { DatabaseDefaultUUID } from 'src/common/database/constants/database.function.constant'; 9 | 10 | export abstract class DatabaseMongoUUIDEntityAbstract extends DatabaseBaseEntityAbstract { 11 | @Prop({ 12 | type: String, 13 | default: DatabaseDefaultUUID, 14 | }) 15 | _id: string; 16 | 17 | @Prop({ 18 | required: false, 19 | index: true, 20 | type: Date, 21 | }) 22 | [DATABASE_DELETED_AT_FIELD_NAME]?: Date; 23 | 24 | @Prop({ 25 | required: false, 26 | index: 'asc', 27 | type: Date, 28 | }) 29 | [DATABASE_CREATED_AT_FIELD_NAME]?: Date; 30 | 31 | @Prop({ 32 | required: false, 33 | index: 'desc', 34 | type: Date, 35 | }) 36 | [DATABASE_UPDATED_AT_FIELD_NAME]?: Date; 37 | } 38 | -------------------------------------------------------------------------------- /src/common/setting/middleware/maintenance/setting.maintenance.middleware.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NestMiddleware, 4 | ServiceUnavailableException, 5 | } from '@nestjs/common'; 6 | import { Response, NextFunction } from 'express'; 7 | import { ENUM_ERROR_STATUS_CODE_ERROR } from 'src/common/error/constants/error.status-code.constant'; 8 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 9 | import { SettingService } from 'src/common/setting/services/setting.service'; 10 | 11 | @Injectable() 12 | export class SettingMaintenanceMiddleware implements NestMiddleware { 13 | constructor(private readonly settingService: SettingService) {} 14 | 15 | async use( 16 | req: IRequestApp, 17 | res: Response, 18 | next: NextFunction 19 | ): Promise { 20 | const maintenance: boolean = await this.settingService.getMaintenance(); 21 | 22 | if (maintenance) { 23 | throw new ServiceUnavailableException({ 24 | statusCode: 25 | ENUM_ERROR_STATUS_CODE_ERROR.ERROR_SERVICE_UNAVAILABLE, 26 | message: 'http.serverError.serviceUnavailable', 27 | }); 28 | } 29 | 30 | next(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/common/file/interceptors/file.custom-max-files.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NestInterceptor, 4 | ExecutionContext, 5 | CallHandler, 6 | } from '@nestjs/common'; 7 | import { Observable } from 'rxjs'; 8 | import { HttpArgumentsHost } from '@nestjs/common/interfaces'; 9 | import { FILE_CUSTOM_MAX_FILES_META_KEY } from 'src/common/file/constants/file.constant'; 10 | import { Reflector } from '@nestjs/core'; 11 | 12 | @Injectable() 13 | export class FileCustomMaxFilesInterceptor implements NestInterceptor { 14 | constructor(private readonly reflector: Reflector) {} 15 | 16 | async intercept( 17 | context: ExecutionContext, 18 | next: CallHandler 19 | ): Promise> { 20 | if (context.getType() === 'http') { 21 | const ctx: HttpArgumentsHost = context.switchToHttp(); 22 | const request = ctx.getRequest(); 23 | 24 | const maxFiles: number = this.reflector.get( 25 | FILE_CUSTOM_MAX_FILES_META_KEY, 26 | context.getHandler() 27 | ); 28 | request.__customMaxFiles = maxFiles; 29 | 30 | return next.handle(); 31 | } 32 | 33 | return next.handle(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/common/file/interceptors/file.custom-max-size.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NestInterceptor, 4 | ExecutionContext, 5 | CallHandler, 6 | } from '@nestjs/common'; 7 | import { Observable } from 'rxjs'; 8 | import { HttpArgumentsHost } from '@nestjs/common/interfaces'; 9 | import { FILE_CUSTOM_MAX_SIZE_META_KEY } from 'src/common/file/constants/file.constant'; 10 | import { Reflector } from '@nestjs/core'; 11 | 12 | @Injectable() 13 | export class FileCustomMaxSizeInterceptor implements NestInterceptor { 14 | constructor(private readonly reflector: Reflector) {} 15 | 16 | async intercept( 17 | context: ExecutionContext, 18 | next: CallHandler 19 | ): Promise> { 20 | if (context.getType() === 'http') { 21 | const ctx: HttpArgumentsHost = context.switchToHttp(); 22 | const request = ctx.getRequest(); 23 | 24 | const customSize: string = this.reflector.get( 25 | FILE_CUSTOM_MAX_SIZE_META_KEY, 26 | context.getHandler() 27 | ); 28 | request.__customMaxFileSize = customSize; 29 | 30 | return next.handle(); 31 | } 32 | 33 | return next.handle(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/common/request/validations/request.max-greater-than-equal.validation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | registerDecorator, 4 | ValidationArguments, 5 | ValidationOptions, 6 | ValidatorConstraint, 7 | ValidatorConstraintInterface, 8 | } from 'class-validator'; 9 | 10 | @ValidatorConstraint({ async: true }) 11 | @Injectable() 12 | export class MaxGreaterThanEqualConstraint 13 | implements ValidatorConstraintInterface 14 | { 15 | validate(value: string, args: ValidationArguments): boolean { 16 | const [property] = args.constraints; 17 | const relatedValue = args.object[property]; 18 | return value <= relatedValue; 19 | } 20 | } 21 | 22 | export function MaxGreaterThanEqual( 23 | property: string, 24 | validationOptions?: ValidationOptions 25 | ) { 26 | return function (object: Record, propertyName: string): void { 27 | registerDecorator({ 28 | name: 'MaxGreaterThanEqual', 29 | target: object.constructor, 30 | propertyName: propertyName, 31 | options: validationOptions, 32 | constraints: [property], 33 | validator: MaxGreaterThanEqualConstraint, 34 | }); 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /src/common/request/validations/request.min-greater-than-equal.validation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | registerDecorator, 4 | ValidationArguments, 5 | ValidationOptions, 6 | ValidatorConstraint, 7 | ValidatorConstraintInterface, 8 | } from 'class-validator'; 9 | 10 | @ValidatorConstraint({ async: true }) 11 | @Injectable() 12 | export class MinGreaterThanEqualConstraint 13 | implements ValidatorConstraintInterface 14 | { 15 | validate(value: string, args: ValidationArguments): boolean { 16 | const [property] = args.constraints; 17 | const relatedValue = args.object[property]; 18 | return value >= relatedValue; 19 | } 20 | } 21 | 22 | export function MinGreaterThanEqual( 23 | property: string, 24 | validationOptions?: ValidationOptions 25 | ) { 26 | return function (object: Record, propertyName: string): void { 27 | registerDecorator({ 28 | name: 'MinGreaterThanEqual', 29 | target: object.constructor, 30 | propertyName: propertyName, 31 | options: validationOptions, 32 | constraints: [property], 33 | validator: MinGreaterThanEqualConstraint, 34 | }); 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /src/common/message/controllers/message.public.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, VERSION_NEUTRAL } from '@nestjs/common'; 2 | import { ApiTags } from '@nestjs/swagger'; 3 | import { MessagePublicLanguageDoc } from 'src/common/message/docs/message.public.doc'; 4 | import { MessageLanguageSerialization } from 'src/common/message/serializations/message.language.serialization'; 5 | import { MessageService } from 'src/common/message/services/message.service'; 6 | import { Response } from 'src/common/response/decorators/response.decorator'; 7 | import { IResponse } from 'src/common/response/interfaces/response.interface'; 8 | 9 | @ApiTags('common.public.message') 10 | @Controller({ 11 | version: VERSION_NEUTRAL, 12 | path: '/message', 13 | }) 14 | export class MessagePublicController { 15 | constructor(private readonly messageService: MessageService) {} 16 | 17 | @MessagePublicLanguageDoc() 18 | @Response('message.languages', { 19 | serialization: MessageLanguageSerialization, 20 | }) 21 | @Get('/languages') 22 | async languages(): Promise { 23 | const languages: string[] = this.messageService.getAvailableLanguages(); 24 | 25 | return { 26 | data: { languages }, 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/common/policy/interfaces/policy.interface.ts: -------------------------------------------------------------------------------- 1 | import { InferSubjects, MongoAbility } from '@casl/ability'; 2 | import { 3 | ENUM_POLICY_ACTION, 4 | ENUM_POLICY_SUBJECT, 5 | } from 'src/common/policy/constants/policy.enum.constant'; 6 | import { ENUM_ROLE_TYPE } from 'src/modules/role/constants/role.enum.constant'; 7 | import { UserPayloadPermissionSerialization } from 'src/modules/user/serializations/user.payload.serialization'; 8 | 9 | export interface IPolicyRule { 10 | subject: ENUM_POLICY_SUBJECT; 11 | action: ENUM_POLICY_ACTION[]; 12 | } 13 | export interface IPolicyRuleAbility { 14 | subject: ENUM_POLICY_SUBJECT; 15 | action: ENUM_POLICY_ACTION; 16 | } 17 | 18 | export type IPolicySubjectAbility = InferSubjects | 'all'; 19 | 20 | export type IPolicyAbility = MongoAbility< 21 | [ENUM_POLICY_ACTION, IPolicySubjectAbility] 22 | >; 23 | 24 | interface IPolicyHandler { 25 | handle(ability: IPolicyAbility): boolean; 26 | } 27 | 28 | type IPolicyHandlerCallback = (ability: IPolicyAbility) => boolean; 29 | 30 | export type PolicyHandler = IPolicyHandler | IPolicyHandlerCallback; 31 | 32 | export interface IPolicyRequest { 33 | type: ENUM_ROLE_TYPE; 34 | permissions: UserPayloadPermissionSerialization[]; 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/user/serializations/user.profile.serialization.ts: -------------------------------------------------------------------------------- 1 | import { ApiHideProperty, OmitType } from '@nestjs/swagger'; 2 | import { Exclude } from 'class-transformer'; 3 | import { UserGetSerialization } from './user.get.serialization'; 4 | 5 | export class UserProfileSerialization extends OmitType(UserGetSerialization, [ 6 | 'isActive', 7 | 'blocked', 8 | 'passwordExpired', 9 | 'passwordCreated', 10 | 'passwordAttempt', 11 | 'inactiveDate', 12 | 'inactivePermanent', 13 | 'blockedDate', 14 | ] as const) { 15 | @ApiHideProperty() 16 | @Exclude() 17 | readonly isActive: boolean; 18 | 19 | @ApiHideProperty() 20 | @Exclude() 21 | readonly inactivePermanent: boolean; 22 | 23 | @ApiHideProperty() 24 | @Exclude() 25 | readonly blocked: boolean; 26 | 27 | @ApiHideProperty() 28 | @Exclude() 29 | readonly passwordExpired: Date; 30 | 31 | @ApiHideProperty() 32 | @Exclude() 33 | readonly passwordCreated: Date; 34 | 35 | @ApiHideProperty() 36 | @Exclude() 37 | readonly passwordAttempt: number; 38 | 39 | @ApiHideProperty() 40 | @Exclude() 41 | readonly inactiveDate?: Date; 42 | 43 | @ApiHideProperty() 44 | @Exclude() 45 | readonly blockedDate?: Date; 46 | } 47 | -------------------------------------------------------------------------------- /src/common/database/abstracts/mongo/entities/database.mongo.object-id.entity.abstract.ts: -------------------------------------------------------------------------------- 1 | import { Prop } from '@nestjs/mongoose'; 2 | import { Types } from 'mongoose'; 3 | import { DatabaseBaseEntityAbstract } from 'src/common/database/abstracts/database.base-entity.abstract'; 4 | import { 5 | DATABASE_CREATED_AT_FIELD_NAME, 6 | DATABASE_DELETED_AT_FIELD_NAME, 7 | DATABASE_UPDATED_AT_FIELD_NAME, 8 | } from 'src/common/database/constants/database.constant'; 9 | import { DatabaseDefaultObjectId } from 'src/common/database/constants/database.function.constant'; 10 | 11 | export abstract class DatabaseMongoObjectIdEntityAbstract extends DatabaseBaseEntityAbstract { 12 | @Prop({ 13 | type: Types.ObjectId, 14 | default: DatabaseDefaultObjectId, 15 | }) 16 | _id: Types.ObjectId; 17 | 18 | @Prop({ 19 | required: false, 20 | index: true, 21 | type: Date, 22 | }) 23 | [DATABASE_DELETED_AT_FIELD_NAME]?: Date; 24 | 25 | @Prop({ 26 | required: false, 27 | index: 'asc', 28 | type: Date, 29 | }) 30 | [DATABASE_CREATED_AT_FIELD_NAME]?: Date; 31 | 32 | @Prop({ 33 | required: false, 34 | index: 'desc', 35 | type: Date, 36 | }) 37 | [DATABASE_UPDATED_AT_FIELD_NAME]?: Date; 38 | } 39 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | service: 4 | build: . 5 | container_name: service 6 | hostname: service 7 | ports: 8 | - 3000:3000 9 | networks: 10 | - app-network 11 | volumes: 12 | - ./src/:/app/src/ 13 | - .env/:/app/.env 14 | restart: unless-stopped 15 | depends_on: 16 | - mongo1 17 | - mongo2 18 | - mongo3 19 | mongo1: 20 | container_name: mongo1 21 | hostname: mongo1 22 | image: mongo 23 | restart: always 24 | networks: 25 | - app-network 26 | volumes: 27 | - dbdata1:/data/db 28 | command: mongod --bind_ip_all --replSet rs0 29 | 30 | mongo2: 31 | container_name: mongo2 32 | hostname: mongo2 33 | image: mongo 34 | networks: 35 | - app-network 36 | restart: always 37 | volumes: 38 | - dbdata2:/data/db 39 | command: mongod --bind_ip_all --replSet rs0 40 | 41 | mongo3: 42 | container_name: mongo3 43 | hostname: mongo3 44 | image: mongo 45 | networks: 46 | - app-network 47 | restart: always 48 | volumes: 49 | - dbdata3:/data/db 50 | command: mongod --bind_ip_all --replSet rs0 51 | networks: 52 | app-network: 53 | name: app-network 54 | driver: bridge 55 | volumes: 56 | dbdata1: 57 | dbdata2: 58 | dbdata3: -------------------------------------------------------------------------------- /src/common/api-key/guards/api-key.active.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | BadRequestException, 6 | } from '@nestjs/common'; 7 | import { Reflector } from '@nestjs/core'; 8 | import { API_KEY_ACTIVE_META_KEY } from 'src/common/api-key/constants/api-key.constant'; 9 | import { ENUM_API_KEY_STATUS_CODE_ERROR } from 'src/common/api-key/constants/api-key.status-code.constant'; 10 | 11 | @Injectable() 12 | export class ApiKeyActiveGuard implements CanActivate { 13 | constructor(private reflector: Reflector) {} 14 | 15 | async canActivate(context: ExecutionContext): Promise { 16 | const required: boolean[] = this.reflector.getAllAndOverride( 17 | API_KEY_ACTIVE_META_KEY, 18 | [context.getHandler(), context.getClass()] 19 | ); 20 | 21 | if (!required) { 22 | return true; 23 | } 24 | 25 | const { __apiKey } = context.switchToHttp().getRequest(); 26 | 27 | if (!required.includes(__apiKey.isActive)) { 28 | throw new BadRequestException({ 29 | statusCode: 30 | ENUM_API_KEY_STATUS_CODE_ERROR.API_KEY_IS_ACTIVE_ERROR, 31 | message: 'apiKey.error.isActiveInvalid', 32 | }); 33 | } 34 | return true; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/common/request/validations/request.max-date-today.validation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | registerDecorator, 4 | ValidationOptions, 5 | ValidatorConstraint, 6 | ValidatorConstraintInterface, 7 | } from 'class-validator'; 8 | import { HelperDateService } from 'src/common/helper/services/helper.date.service'; 9 | 10 | @ValidatorConstraint({ async: true }) 11 | @Injectable() 12 | export class MaxDateTodayConstraint implements ValidatorConstraintInterface { 13 | constructor(private readonly helperDateService: HelperDateService) {} 14 | 15 | validate(value: string): boolean { 16 | const todayDate = this.helperDateService.endOfDay(); 17 | const valueDate = this.helperDateService.startOfDay( 18 | this.helperDateService.create(value) 19 | ); 20 | return valueDate <= todayDate; 21 | } 22 | } 23 | 24 | export function MaxDateToday(validationOptions?: ValidationOptions) { 25 | return function (object: Record, propertyName: string): any { 26 | registerDecorator({ 27 | name: 'MaxDateToday', 28 | target: object.constructor, 29 | propertyName: propertyName, 30 | options: validationOptions, 31 | constraints: [], 32 | validator: MaxDateTodayConstraint, 33 | }); 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /src/common/request/validations/request.min-date-today.validation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | registerDecorator, 4 | ValidationOptions, 5 | ValidatorConstraint, 6 | ValidatorConstraintInterface, 7 | } from 'class-validator'; 8 | import { HelperDateService } from 'src/common/helper/services/helper.date.service'; 9 | 10 | @ValidatorConstraint({ async: true }) 11 | @Injectable() 12 | export class MinDateTodayConstraint implements ValidatorConstraintInterface { 13 | constructor(private readonly helperDateService: HelperDateService) {} 14 | 15 | validate(value: string): boolean { 16 | const todayDate = this.helperDateService.endOfDay(); 17 | const valueDate = this.helperDateService.startOfDay( 18 | this.helperDateService.create(value) 19 | ); 20 | return valueDate >= todayDate; 21 | } 22 | } 23 | 24 | export function MinDateToday(validationOptions?: ValidationOptions) { 25 | return function (object: Record, propertyName: string): any { 26 | registerDecorator({ 27 | name: 'MinDateToday', 28 | target: object.constructor, 29 | propertyName: propertyName, 30 | options: validationOptions, 31 | constraints: [], 32 | validator: MinDateTodayConstraint, 33 | }); 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /src/migration/seeds/migration.setting.seed.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'nestjs-command'; 2 | import { Injectable } from '@nestjs/common'; 3 | import { SettingService } from 'src/common/setting/services/setting.service'; 4 | import { ENUM_SETTING_DATA_TYPE } from 'src/common/setting/constants/setting.enum.constant'; 5 | 6 | @Injectable() 7 | export class MigrationSettingSeed { 8 | constructor(private readonly settingService: SettingService) {} 9 | 10 | @Command({ 11 | command: 'seed:setting', 12 | describe: 'seeds settings', 13 | }) 14 | async seeds(): Promise { 15 | try { 16 | await this.settingService.create({ 17 | name: 'maintenance', 18 | description: 'Maintenance Mode', 19 | type: ENUM_SETTING_DATA_TYPE.BOOLEAN, 20 | value: 'false', 21 | }); 22 | } catch (err: any) { 23 | throw new Error(err.message); 24 | } 25 | 26 | return; 27 | } 28 | 29 | @Command({ 30 | command: 'remove:setting', 31 | describe: 'remove settings', 32 | }) 33 | async remove(): Promise { 34 | try { 35 | await this.settingService.deleteMany({}); 36 | } catch (err: any) { 37 | throw new Error(err.message); 38 | } 39 | 40 | return; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/common/setting/repository/entities/setting.entity.ts: -------------------------------------------------------------------------------- 1 | import { Prop, SchemaFactory } from '@nestjs/mongoose'; 2 | import { DatabaseMongoUUIDEntityAbstract } from 'src/common/database/abstracts/mongo/entities/database.mongo.uuid.entity.abstract'; 3 | import { DatabaseEntity } from 'src/common/database/decorators/database.decorator'; 4 | import { ENUM_SETTING_DATA_TYPE } from 'src/common/setting/constants/setting.enum.constant'; 5 | import { Document } from 'mongoose'; 6 | 7 | export const SettingDatabaseName = 'settings'; 8 | 9 | @DatabaseEntity({ collection: SettingDatabaseName }) 10 | export class SettingEntity extends DatabaseMongoUUIDEntityAbstract { 11 | @Prop({ 12 | required: true, 13 | index: true, 14 | unique: true, 15 | trim: true, 16 | type: String, 17 | }) 18 | name: string; 19 | 20 | @Prop({ 21 | required: false, 22 | type: String, 23 | }) 24 | description?: string; 25 | 26 | @Prop({ 27 | required: false, 28 | type: String, 29 | enum: ENUM_SETTING_DATA_TYPE, 30 | }) 31 | type: ENUM_SETTING_DATA_TYPE; 32 | 33 | @Prop({ 34 | required: true, 35 | trim: true, 36 | type: String, 37 | }) 38 | value: string; 39 | } 40 | 41 | export const SettingSchema = SchemaFactory.createForClass(SettingEntity); 42 | 43 | export type SettingDoc = SettingEntity & Document; 44 | -------------------------------------------------------------------------------- /src/common/pagination/interfaces/pagination.service.interface.ts: -------------------------------------------------------------------------------- 1 | import { IPaginationOrder } from 'src/common/pagination/interfaces/pagination.interface'; 2 | 3 | export interface IPaginationService { 4 | offset(page: number, perPage: number): number; 5 | totalPage(totalData: number, perPage: number): number; 6 | offsetWithoutMax(page: number, perPage: number): number; 7 | totalPageWithoutMax(totalData: number, perPage: number): number; 8 | page(page?: number): number; 9 | perPage(perPage?: number): number; 10 | order( 11 | orderByValue?: string, 12 | orderDirectionValue?: string, 13 | availableOrderBy?: string[] 14 | ): IPaginationOrder; 15 | search( 16 | searchValue?: string, 17 | availableSearch?: string[] 18 | ): Record | undefined; 19 | filterEqual(field: string, filterValue: T): Record; 20 | filterContain( 21 | field: string, 22 | filterValue: string 23 | ): Record; 24 | filterContainFullMatch( 25 | field: string, 26 | filterValue: string 27 | ): Record; 28 | filterIn( 29 | field: string, 30 | filterValue: T[] 31 | ): Record; 32 | filterDate(field: string, filterValue: Date): Record; 33 | } 34 | -------------------------------------------------------------------------------- /src/common/request/validations/request.is-password-weak.validation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | registerDecorator, 4 | ValidationArguments, 5 | ValidationOptions, 6 | ValidatorConstraint, 7 | ValidatorConstraintInterface, 8 | } from 'class-validator'; 9 | import { HelperStringService } from 'src/common/helper/services/helper.string.service'; 10 | 11 | @ValidatorConstraint({ async: true }) 12 | @Injectable() 13 | export class IsPasswordWeakConstraint implements ValidatorConstraintInterface { 14 | constructor(protected readonly helperStringService: HelperStringService) {} 15 | 16 | validate(value: string, args: ValidationArguments): boolean { 17 | const [length] = args.constraints; 18 | return value 19 | ? this.helperStringService.checkPasswordMedium(value, length) 20 | : false; 21 | } 22 | } 23 | 24 | export function IsPasswordWeak( 25 | minLength = 8, 26 | validationOptions?: ValidationOptions 27 | ) { 28 | return function (object: Record, propertyName: string): void { 29 | registerDecorator({ 30 | name: 'IsPasswordWeak', 31 | target: object.constructor, 32 | propertyName: propertyName, 33 | options: validationOptions, 34 | constraints: [minLength], 35 | validator: IsPasswordWeakConstraint, 36 | }); 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/common/aws/serializations/aws.s3.serialization.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { ApiProperty } from '@nestjs/swagger'; 3 | import { Type } from 'class-transformer'; 4 | 5 | export class AwsS3Serialization { 6 | @ApiProperty({ 7 | required: true, 8 | nullable: false, 9 | example: faker.system.directoryPath(), 10 | }) 11 | @Type(() => String) 12 | path: string; 13 | 14 | @ApiProperty({ 15 | required: true, 16 | nullable: false, 17 | example: faker.system.filePath(), 18 | }) 19 | @Type(() => String) 20 | pathWithFilename: string; 21 | 22 | @ApiProperty({ 23 | required: true, 24 | nullable: false, 25 | example: faker.system.fileName(), 26 | }) 27 | @Type(() => String) 28 | filename: string; 29 | 30 | @ApiProperty({ 31 | required: true, 32 | nullable: false, 33 | example: `${faker.internet.url()}/${faker.system.filePath()}`, 34 | }) 35 | @Type(() => String) 36 | completedUrl: string; 37 | 38 | @ApiProperty({ 39 | required: true, 40 | nullable: false, 41 | example: faker.internet.url(), 42 | }) 43 | @Type(() => String) 44 | baseUrl: string; 45 | 46 | @ApiProperty({ 47 | required: true, 48 | nullable: false, 49 | example: faker.system.mimeType(), 50 | }) 51 | @Type(() => String) 52 | mime: string; 53 | } 54 | -------------------------------------------------------------------------------- /src/common/request/validations/request.is-password-medium.validation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | registerDecorator, 4 | ValidationArguments, 5 | ValidationOptions, 6 | ValidatorConstraint, 7 | ValidatorConstraintInterface, 8 | } from 'class-validator'; 9 | import { HelperStringService } from 'src/common/helper/services/helper.string.service'; 10 | 11 | @ValidatorConstraint({ async: true }) 12 | @Injectable() 13 | export class IsPasswordMediumConstraint 14 | implements ValidatorConstraintInterface 15 | { 16 | constructor(protected readonly helperStringService: HelperStringService) {} 17 | 18 | validate(value: string, args: ValidationArguments): boolean { 19 | const [length] = args.constraints; 20 | return value 21 | ? this.helperStringService.checkPasswordMedium(value, length) 22 | : false; 23 | } 24 | } 25 | 26 | export function IsPasswordMedium( 27 | minLength = 8, 28 | validationOptions?: ValidationOptions 29 | ) { 30 | return function (object: Record, propertyName: string): void { 31 | registerDecorator({ 32 | name: 'IsPasswordMedium', 33 | target: object.constructor, 34 | propertyName: propertyName, 35 | options: validationOptions, 36 | constraints: [minLength], 37 | validator: IsPasswordMediumConstraint, 38 | }); 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/common/request/validations/request.is-password-strong.validation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | registerDecorator, 4 | ValidationArguments, 5 | ValidationOptions, 6 | ValidatorConstraint, 7 | ValidatorConstraintInterface, 8 | } from 'class-validator'; 9 | import { HelperStringService } from 'src/common/helper/services/helper.string.service'; 10 | 11 | @ValidatorConstraint({ async: true }) 12 | @Injectable() 13 | export class IsPasswordStrongConstraint 14 | implements ValidatorConstraintInterface 15 | { 16 | constructor(protected readonly helperStringService: HelperStringService) {} 17 | 18 | validate(value: string, args: ValidationArguments): boolean { 19 | const [length] = args.constraints; 20 | return value 21 | ? this.helperStringService.checkPasswordStrong(value, length) 22 | : false; 23 | } 24 | } 25 | 26 | export function IsPasswordStrong( 27 | minLength = 8, 28 | validationOptions?: ValidationOptions 29 | ) { 30 | return function (object: Record, propertyName: string): void { 31 | registerDecorator({ 32 | name: 'IsPasswordStrong', 33 | target: object.constructor, 34 | propertyName: propertyName, 35 | options: validationOptions, 36 | constraints: [minLength], 37 | validator: IsPasswordStrongConstraint, 38 | }); 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/modules/user/constants/user.doc.constant.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | 3 | export const UserDocQueryIsActive = [ 4 | { 5 | name: 'isActive', 6 | allowEmptyValue: true, 7 | required: false, 8 | type: 'string', 9 | example: 'true,false', 10 | description: "boolean value with ',' delimiter", 11 | }, 12 | ]; 13 | 14 | export const UserDocQueryBlocked = [ 15 | { 16 | name: 'blocked', 17 | allowEmptyValue: true, 18 | required: false, 19 | type: 'string', 20 | example: 'true,false', 21 | description: "boolean value with ',' delimiter", 22 | }, 23 | ]; 24 | 25 | export const UserDocQueryInactivePermanent = [ 26 | { 27 | name: 'inactivePermanent', 28 | allowEmptyValue: true, 29 | required: false, 30 | type: 'string', 31 | example: 'true,false', 32 | description: "boolean value with ',' delimiter", 33 | }, 34 | ]; 35 | 36 | export const UserDocQueryRole = [ 37 | { 38 | name: 'role', 39 | allowEmptyValue: true, 40 | required: false, 41 | type: 'string', 42 | example: faker.string.uuid(), 43 | }, 44 | ]; 45 | 46 | export const UserDocParamsId = [ 47 | { 48 | name: 'user', 49 | allowEmptyValue: false, 50 | required: true, 51 | type: 'string', 52 | example: faker.string.uuid(), 53 | }, 54 | ]; 55 | -------------------------------------------------------------------------------- /src/common/request/validations/request.mobile-number-allowed.validation.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { 3 | registerDecorator, 4 | ValidationOptions, 5 | ValidatorConstraint, 6 | ValidatorConstraintInterface, 7 | } from 'class-validator'; 8 | import { SettingService } from 'src/common/setting/services/setting.service'; 9 | 10 | @ValidatorConstraint({ async: true }) 11 | @Injectable() 12 | export class MobileNumberAllowedConstraint 13 | implements ValidatorConstraintInterface 14 | { 15 | constructor(private readonly settingService: SettingService) {} 16 | 17 | async validate(value: string): Promise { 18 | const mobileNumbersSetting: string[] = 19 | await this.settingService.getMobileNumberCountryCodeAllowed(); 20 | mobileNumbersSetting; 21 | const check = mobileNumbersSetting.find((val) => value.startsWith(val)); 22 | 23 | return !!check; 24 | } 25 | } 26 | 27 | export function MobileNumberAllowed(validationOptions?: ValidationOptions) { 28 | return function (object: Record, propertyName: string): void { 29 | registerDecorator({ 30 | name: 'MobileNumberAllowed', 31 | target: object.constructor, 32 | propertyName: propertyName, 33 | options: validationOptions, 34 | constraints: [], 35 | validator: MobileNumberAllowedConstraint, 36 | }); 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/common/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module, Provider } from '@nestjs/common'; 2 | import { AuthGoogleOAuth2LoginStrategy } from 'src/common/auth/guards/google-oauth2/auth.google-oauth2-login.strategy'; 3 | import { AuthGoogleOAuth2SignUpStrategy } from 'src/common/auth/guards/google-oauth2/auth.google-oauth2-sign-up.strategy'; 4 | import { AuthJwtAccessStrategy } from 'src/common/auth/guards/jwt-access/auth.jwt-access.strategy'; 5 | import { AuthJwtRefreshStrategy } from 'src/common/auth/guards/jwt-refresh/auth.jwt-refresh.strategy'; 6 | import { AuthService } from 'src/common/auth/services/auth.service'; 7 | 8 | @Module({ 9 | providers: [AuthService], 10 | exports: [AuthService], 11 | controllers: [], 12 | imports: [], 13 | }) 14 | export class AuthModule { 15 | static forRoot(): DynamicModule { 16 | const providers: Provider[] = [ 17 | AuthJwtAccessStrategy, 18 | AuthJwtRefreshStrategy, 19 | ]; 20 | 21 | if ( 22 | process.env.SSO_GOOGLE_CLIENT_ID && 23 | process.env.SSO_GOOGLE_CLIENT_SECRET 24 | ) { 25 | providers.push(AuthGoogleOAuth2LoginStrategy); 26 | providers.push(AuthGoogleOAuth2SignUpStrategy); 27 | } 28 | 29 | return { 30 | module: AuthModule, 31 | providers, 32 | exports: [], 33 | controllers: [], 34 | imports: [], 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/modules/user/guards/user.blocked.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | BadRequestException, 6 | } from '@nestjs/common'; 7 | import { Reflector } from '@nestjs/core'; 8 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 9 | import { USER_BLOCKED_META_KEY } from 'src/modules/user/constants/user.constant'; 10 | import { ENUM_USER_STATUS_CODE_ERROR } from 'src/modules/user/constants/user.status-code.constant'; 11 | import { UserDoc } from 'src/modules/user/repository/entities/user.entity'; 12 | 13 | @Injectable() 14 | export class UserBlockedGuard implements CanActivate { 15 | constructor(private reflector: Reflector) {} 16 | 17 | async canActivate(context: ExecutionContext): Promise { 18 | const required: boolean[] = this.reflector.getAllAndOverride( 19 | USER_BLOCKED_META_KEY, 20 | [context.getHandler(), context.getClass()] 21 | ); 22 | 23 | if (!required) { 24 | return true; 25 | } 26 | 27 | const { __user } = context 28 | .switchToHttp() 29 | .getRequest(); 30 | 31 | if (!required.includes(__user.blocked)) { 32 | throw new BadRequestException({ 33 | statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_BLOCKED_ERROR, 34 | message: 'user.error.blocked', 35 | }); 36 | } 37 | return true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/common/error/interfaces/error.interface.ts: -------------------------------------------------------------------------------- 1 | import { ValidationError } from 'class-validator'; 2 | import { ERROR_TYPE } from 'src/common/error/constants/error.enum.constant'; 3 | import { IMessage } from 'src/common/message/interfaces/message.interface'; 4 | import { IResponseCustomPropertyMetadata } from 'src/common/response/interfaces/response.interface'; 5 | 6 | // error default 7 | export interface IErrors { 8 | readonly message: string | IMessage; 9 | readonly property: string; 10 | } 11 | 12 | // error import 13 | export interface IErrorsImport { 14 | row: number; 15 | file?: string; 16 | errors: IErrors[]; 17 | } 18 | 19 | export interface IValidationErrorImport extends Omit { 20 | errors: ValidationError[]; 21 | } 22 | 23 | // error exception 24 | 25 | export type IErrorCustomPropertyMetadata = Pick< 26 | IResponseCustomPropertyMetadata, 27 | 'messageProperties' 28 | >; 29 | 30 | export interface IErrorMetadata { 31 | customProperty?: IErrorCustomPropertyMetadata; 32 | [key: string]: any; 33 | } 34 | 35 | export interface IErrorException { 36 | statusCode: number; 37 | message: string; 38 | errors?: ValidationError[] | IValidationErrorImport[]; 39 | data?: Record; 40 | _error?: string; 41 | _errorType?: ERROR_TYPE; 42 | _metadata?: IErrorMetadata; 43 | } 44 | 45 | export interface IErrorHttpFilter 46 | extends Omit { 47 | message: string | IMessage; 48 | } 49 | -------------------------------------------------------------------------------- /src/modules/user/guards/user.active.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | BadRequestException, 6 | } from '@nestjs/common'; 7 | import { Reflector } from '@nestjs/core'; 8 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 9 | import { USER_ACTIVE_META_KEY } from 'src/modules/user/constants/user.constant'; 10 | import { ENUM_USER_STATUS_CODE_ERROR } from 'src/modules/user/constants/user.status-code.constant'; 11 | import { UserDoc } from 'src/modules/user/repository/entities/user.entity'; 12 | 13 | @Injectable() 14 | export class UserActiveGuard implements CanActivate { 15 | constructor(private reflector: Reflector) {} 16 | 17 | async canActivate(context: ExecutionContext): Promise { 18 | const required: boolean[] = this.reflector.getAllAndOverride( 19 | USER_ACTIVE_META_KEY, 20 | [context.getHandler(), context.getClass()] 21 | ); 22 | 23 | if (!required) { 24 | return true; 25 | } 26 | 27 | const { __user } = context 28 | .switchToHttp() 29 | .getRequest(); 30 | 31 | if (!required.includes(__user.isActive)) { 32 | throw new BadRequestException({ 33 | statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_IS_ACTIVE_ERROR, 34 | message: 'user.error.isActiveInvalid', 35 | }); 36 | } 37 | return true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/common/message/message.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common'; 2 | import * as path from 'path'; 3 | import { I18nModule, HeaderResolver, I18nJsonLoader } from 'nestjs-i18n'; 4 | import { ConfigService } from '@nestjs/config'; 5 | import { MessageService } from './services/message.service'; 6 | import { ENUM_MESSAGE_LANGUAGE } from './constants/message.enum.constant'; 7 | import { MessageMiddlewareModule } from 'src/common/message/middleware/message.middleware.module'; 8 | 9 | @Global() 10 | @Module({ 11 | providers: [MessageService], 12 | exports: [MessageService], 13 | imports: [ 14 | I18nModule.forRootAsync({ 15 | useFactory: (configService: ConfigService) => ({ 16 | fallbackLanguage: configService 17 | .get('message.availableLanguage') 18 | .join(','), 19 | fallbacks: Object.values(ENUM_MESSAGE_LANGUAGE).reduce( 20 | (a, v) => ({ ...a, [`${v}-*`]: v }), 21 | {} 22 | ), 23 | loaderOptions: { 24 | path: path.join(__dirname, '../../languages'), 25 | watch: true, 26 | }, 27 | }), 28 | loader: I18nJsonLoader, 29 | inject: [ConfigService], 30 | resolvers: [new HeaderResolver(['x-custom-lang'])], 31 | }), 32 | MessageMiddlewareModule, 33 | ], 34 | controllers: [], 35 | }) 36 | export class MessageModule {} 37 | -------------------------------------------------------------------------------- /src/modules/role/guards/role.active.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | BadRequestException, 6 | } from '@nestjs/common'; 7 | import { Reflector } from '@nestjs/core'; 8 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 9 | import { ROLE_IS_ACTIVE_META_KEY } from 'src/modules/role/constants/role.constant'; 10 | import { ENUM_ROLE_STATUS_CODE_ERROR } from 'src/modules/role/constants/role.status-code.constant'; 11 | import { RoleDoc } from 'src/modules/role/repository/entities/role.entity'; 12 | 13 | @Injectable() 14 | export class RoleActiveGuard implements CanActivate { 15 | constructor(private reflector: Reflector) {} 16 | 17 | async canActivate(context: ExecutionContext): Promise { 18 | const required: boolean[] = this.reflector.getAllAndOverride( 19 | ROLE_IS_ACTIVE_META_KEY, 20 | [context.getHandler(), context.getClass()] 21 | ); 22 | 23 | if (!required) { 24 | return true; 25 | } 26 | 27 | const { __role } = context 28 | .switchToHttp() 29 | .getRequest(); 30 | 31 | if (!required.includes(__role.isActive)) { 32 | throw new BadRequestException({ 33 | statusCode: ENUM_ROLE_STATUS_CODE_ERROR.ROLE_IS_ACTIVE_ERROR, 34 | message: 'role.error.isActiveInvalid', 35 | }); 36 | } 37 | return true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/common/api-key/guards/payload/api-key.payload.type.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | BadRequestException, 6 | } from '@nestjs/common'; 7 | import { Reflector } from '@nestjs/core'; 8 | import { API_KEY_TYPE_META_KEY } from 'src/common/api-key/constants/api-key.constant'; 9 | import { ENUM_API_KEY_TYPE } from 'src/common/api-key/constants/api-key.enum.constant'; 10 | import { ENUM_API_KEY_STATUS_CODE_ERROR } from 'src/common/api-key/constants/api-key.status-code.constant'; 11 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 12 | 13 | @Injectable() 14 | export class ApiKeyPayloadTypeGuard implements CanActivate { 15 | constructor(private reflector: Reflector) {} 16 | 17 | async canActivate(context: ExecutionContext): Promise { 18 | const required: ENUM_API_KEY_TYPE[] = this.reflector.getAllAndOverride< 19 | ENUM_API_KEY_TYPE[] 20 | >(API_KEY_TYPE_META_KEY, [context.getHandler(), context.getClass()]); 21 | 22 | if (!required) { 23 | return true; 24 | } 25 | 26 | const { apiKey } = context.switchToHttp().getRequest(); 27 | 28 | if (!required.includes(apiKey.type)) { 29 | throw new BadRequestException({ 30 | statusCode: 31 | ENUM_API_KEY_STATUS_CODE_ERROR.API_KEY_TYPE_INVALID_ERROR, 32 | message: 'apiKey.error.typeInvalid', 33 | }); 34 | } 35 | return true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/modules/user/guards/user.inactive-permanent.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | BadRequestException, 6 | } from '@nestjs/common'; 7 | import { Reflector } from '@nestjs/core'; 8 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 9 | import { USER_INACTIVE_PERMANENT_META_KEY } from 'src/modules/user/constants/user.constant'; 10 | import { ENUM_USER_STATUS_CODE_ERROR } from 'src/modules/user/constants/user.status-code.constant'; 11 | import { UserDoc } from 'src/modules/user/repository/entities/user.entity'; 12 | 13 | @Injectable() 14 | export class UserInactivePermanentGuard implements CanActivate { 15 | constructor(private reflector: Reflector) {} 16 | 17 | async canActivate(context: ExecutionContext): Promise { 18 | const required: boolean[] = this.reflector.getAllAndOverride( 19 | USER_INACTIVE_PERMANENT_META_KEY, 20 | [context.getHandler(), context.getClass()] 21 | ); 22 | 23 | if (!required) { 24 | return true; 25 | } 26 | 27 | const { __user } = context 28 | .switchToHttp() 29 | .getRequest(); 30 | 31 | if (!required.includes(__user.inactivePermanent)) { 32 | throw new BadRequestException({ 33 | statusCode: ENUM_USER_STATUS_CODE_ERROR.USER_INACTIVE_ERROR, 34 | message: 'user.error.inactivePermanent', 35 | }); 36 | } 37 | return true; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=SEETTUWA 2 | APP_ENV=development 3 | APP_LANGUAGE=en 4 | 5 | HTTP_ENABLE=true 6 | HTTP_HOST=localhost 7 | HTTP_PORT= 3000 8 | HTTP_VERSIONING_ENABLE=true 9 | HTTP_VERSION=1 10 | 11 | DEBUGGER_HTTP_WRITE_INTO_FILE=false 12 | DEBUGGER_HTTP_WRITE_INTO_CONSOLE=false 13 | DEBUGGER_SYSTEM_WRITE_INTO_FILE=false 14 | DEBUGGER_SYSTEM_WRITE_INTO_CONSOLE=false 15 | 16 | JOB_ENABLE=false 17 | 18 | DATABASE_HOST=mongodb://localhost:30001,localhost:30002,localhost:30003 19 | DATABASE_NAME=seettuwa 20 | DATABASE_USER= 21 | DATABASE_PASSWORD= 22 | DATABASE_DEBUG=false 23 | DATABASE_OPTIONS=replicaSet=rs0&retryWrites=true&w=majority 24 | 25 | AUTH_JWT_SUBJECT=AckDevelopment 26 | AUTH_JWT_ISSUER=seettuwa 27 | AUTH_JWT_AUDIENCE=https://example.com 28 | 29 | AUTH_JWT_ACCESS_TOKEN_SECRET_KEY=1234567890 30 | AUTH_JWT_ACCESS_TOKEN_EXPIRED=1h 31 | 32 | AUTH_JWT_REFRESH_TOKEN_SECRET_KEY=0987654321 33 | AUTH_JWT_REFRESH_TOKEN_EXPIRED=14d 34 | AUTH_JWT_REFRESH_TOKEN_NOT_BEFORE_EXPIRATION=1h 35 | 36 | AUTH_JWT_PAYLOAD_ENCRYPT=false 37 | AUTH_JWT_PAYLOAD_ACCESS_TOKEN_ENCRYPT_KEY=qwerty 38 | AUTH_JWT_PAYLOAD_ACCESS_TOKEN_ENCRYPT_IV=123456 39 | AUTH_JWT_PAYLOAD_REFRESH_TOKEN_ENCRYPT_KEY=ytrewq 40 | AUTH_JWT_PAYLOAD_REFRESH_TOKEN_ENCRYPT_IV=654321 41 | 42 | AWS_CREDENTIAL_KEY= 43 | AWS_CREDENTIAL_SECRET= 44 | AWS_S3_REGION=ap-southeast-3 45 | AWS_S3_BUCKET= 46 | 47 | SSO_GOOGLE_CLIENT_ID= 48 | SSO_GOOGLE_CLIENT_SECRET= 49 | SSO_GOOGLE_CALLBACK_URL_LOGIN=http://localhost:3000/api/v1/public/user/login/google/callback 50 | SSO_GOOGLE_CALLBACK_URL_SIGN_UP=http://localhost:3000/api/v1/public/user/sign-up/google/callback -------------------------------------------------------------------------------- /src/common/helper/interfaces/helper.array-service.interface.ts: -------------------------------------------------------------------------------- 1 | import { IHelperArrayRemove } from 'src/common/helper/interfaces/helper.interface'; 2 | 3 | export interface IHelperArrayService { 4 | getLeftByIndex(array: T[], index: number): T; 5 | getRightByIndex(array: T[], index: number): T; 6 | getLeftByLength(array: T[], length: number): T[]; 7 | getRightByLength(array: T[], length: number): T[]; 8 | getLast(array: T[]): T; 9 | getFirst(array: T[]): T; 10 | getFirstIndexByValue(array: T[], value: T): number; 11 | getLastIndexByValue(array: T[], value: T): number; 12 | removeByValue(array: T[], value: T): IHelperArrayRemove; 13 | removeLeftByLength(array: T[], length: number): T[]; 14 | removeRightByLength(array: Array, length: number): T[]; 15 | joinToString(array: Array, delimiter: string): string; 16 | reverse(array: T[]): T[]; 17 | unique(array: T[]): T[]; 18 | shuffle(array: T[]): T[]; 19 | merge(a: T[], b: T[]): T[]; 20 | mergeUnique(a: T[], b: T[]): T[]; 21 | filterIncludeByValue(array: T[], value: T): T[]; 22 | filterNotIncludeByValue(array: T[], value: T): T[]; 23 | filterNotIncludeUniqueByArray(a: T[], b: T[]): T[]; 24 | filterIncludeUniqueByArray(a: T[], b: T[]): T[]; 25 | equals(a: T[], b: T[]): boolean; 26 | notEquals(a: T[], b: T[]): boolean; 27 | in(a: T[], b: T[]): boolean; 28 | notIn(a: T[], b: T[]): boolean; 29 | includes(a: T[], b: T): boolean; 30 | chunk(a: T[], size: number): T[][]; 31 | } 32 | -------------------------------------------------------------------------------- /src/common/api-key/dtos/api-key.create.dto.ts: -------------------------------------------------------------------------------- 1 | import { faker } from '@faker-js/faker'; 2 | import { ApiProperty, PartialType } from '@nestjs/swagger'; 3 | import { IsEnum, IsNotEmpty, IsString, MaxLength } from 'class-validator'; 4 | import { ENUM_API_KEY_TYPE } from 'src/common/api-key/constants/api-key.enum.constant'; 5 | import { ApiKeyUpdateDateDto } from 'src/common/api-key/dtos/api-key.update-date.dto'; 6 | 7 | export class ApiKeyCreateDto extends PartialType(ApiKeyUpdateDateDto) { 8 | @ApiProperty({ 9 | description: 'Api Key name', 10 | example: `testapiname`, 11 | required: true, 12 | }) 13 | @IsNotEmpty() 14 | @IsString() 15 | @MaxLength(50) 16 | name: string; 17 | 18 | @ApiProperty({ 19 | description: 'Api Key name', 20 | example: ENUM_API_KEY_TYPE.PUBLIC, 21 | required: true, 22 | enum: ENUM_API_KEY_TYPE, 23 | }) 24 | @IsNotEmpty() 25 | @IsEnum(ENUM_API_KEY_TYPE) 26 | type: ENUM_API_KEY_TYPE; 27 | } 28 | 29 | export class ApiKeyCreateRawDto extends ApiKeyCreateDto { 30 | @ApiProperty({ 31 | name: 'key', 32 | example: faker.string.alphanumeric(10), 33 | required: true, 34 | nullable: false, 35 | }) 36 | @IsNotEmpty() 37 | @IsString() 38 | @MaxLength(50) 39 | key: string; 40 | 41 | @ApiProperty({ 42 | name: 'secret', 43 | example: faker.string.alphanumeric(20), 44 | required: true, 45 | nullable: false, 46 | }) 47 | @IsNotEmpty() 48 | @IsString() 49 | @MaxLength(100) 50 | secret: string; 51 | } 52 | -------------------------------------------------------------------------------- /src/common/auth/guards/google-oauth2/auth.google-oauth2-login.strategy.ts: -------------------------------------------------------------------------------- 1 | import { PassportStrategy } from '@nestjs/passport'; 2 | import { Strategy, VerifyCallback } from 'passport-google-oauth20'; 3 | import { Injectable } from '@nestjs/common'; 4 | import { ConfigService } from '@nestjs/config'; 5 | import { Profile } from 'passport'; 6 | import { IAuthGooglePayload } from 'src/common/auth/interfaces/auth.interface'; 7 | 8 | @Injectable() 9 | export class AuthGoogleOAuth2LoginStrategy extends PassportStrategy( 10 | Strategy, 11 | 'googleLogin' 12 | ) { 13 | constructor(private readonly configService: ConfigService) { 14 | super({ 15 | clientID: configService.get('auth.googleOAuth2.clientId'), 16 | clientSecret: configService.get( 17 | 'auth.googleOAuth2.clientSecret' 18 | ), 19 | callbackURL: configService.get( 20 | 'auth.googleOAuth2.callbackUrlLogin' 21 | ), 22 | scope: ['profile', 'email', 'openid'], 23 | }); 24 | } 25 | 26 | async validate( 27 | accessToken: string, 28 | refreshToken: string, 29 | profile: Profile, 30 | done: VerifyCallback 31 | ): Promise { 32 | const { name, emails } = profile; 33 | const user: IAuthGooglePayload = { 34 | email: emails[0].value, 35 | firstName: name.givenName, 36 | lastName: name.familyName, 37 | accessToken, 38 | refreshToken, 39 | }; 40 | 41 | done(null, user); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/common/auth/guards/google-oauth2/auth.google-oauth2-sign-up.strategy.ts: -------------------------------------------------------------------------------- 1 | import { PassportStrategy } from '@nestjs/passport'; 2 | import { Strategy, VerifyCallback } from 'passport-google-oauth20'; 3 | import { Injectable } from '@nestjs/common'; 4 | import { ConfigService } from '@nestjs/config'; 5 | import { Profile } from 'passport'; 6 | import { IAuthGooglePayload } from 'src/common/auth/interfaces/auth.interface'; 7 | 8 | @Injectable() 9 | export class AuthGoogleOAuth2SignUpStrategy extends PassportStrategy( 10 | Strategy, 11 | 'googleSignUp' 12 | ) { 13 | constructor(private readonly configService: ConfigService) { 14 | super({ 15 | clientID: configService.get('auth.googleOAuth2.clientId'), 16 | clientSecret: configService.get( 17 | 'auth.googleOAuth2.clientSecret' 18 | ), 19 | callbackURL: configService.get( 20 | 'auth.googleOAuth2.callbackUrlSignUp' 21 | ), 22 | scope: ['profile', 'email', 'openid'], 23 | }); 24 | } 25 | 26 | async validate( 27 | accessToken: string, 28 | refreshToken: string, 29 | profile: Profile, 30 | done: VerifyCallback 31 | ): Promise { 32 | const { name, emails } = profile; 33 | const user: IAuthGooglePayload = { 34 | email: emails[0].value, 35 | firstName: name.givenName, 36 | lastName: name.familyName, 37 | accessToken, 38 | refreshToken, 39 | }; 40 | 41 | done(null, user); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/common/pagination/pipes/pagination.search.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, mixin, Type } from '@nestjs/common'; 2 | import { PipeTransform, Scope } from '@nestjs/common/interfaces'; 3 | import { REQUEST } from '@nestjs/core'; 4 | import { PaginationService } from 'src/common/pagination/services/pagination.service'; 5 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 6 | 7 | export function PaginationSearchPipe( 8 | availableSearch: string[] 9 | ): Type { 10 | @Injectable({ scope: Scope.REQUEST }) 11 | class MixinPaginationSearchPipe implements PipeTransform { 12 | constructor( 13 | @Inject(REQUEST) protected readonly request: IRequestApp, 14 | private readonly paginationService: PaginationService 15 | ) {} 16 | 17 | async transform( 18 | value: Record 19 | ): Promise> { 20 | const searchText = value?.search ?? ''; 21 | const search: Record = this.paginationService.search( 22 | value?.search, 23 | availableSearch 24 | ); 25 | 26 | this.request.__pagination = { 27 | ...this.request.__pagination, 28 | search: searchText, 29 | availableSearch, 30 | }; 31 | 32 | return { 33 | ...value, 34 | _search: search, 35 | _availableSearch: availableSearch, 36 | }; 37 | } 38 | } 39 | 40 | return mixin(MixinPaginationSearchPipe); 41 | } 42 | -------------------------------------------------------------------------------- /src/common/auth/guards/jwt-access/auth.jwt-access.strategy.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt, Strategy } from 'passport-jwt'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Injectable } from '@nestjs/common'; 4 | import { ConfigService } from '@nestjs/config'; 5 | import { AuthService } from 'src/common/auth/services/auth.service'; 6 | 7 | @Injectable() 8 | export class AuthJwtAccessStrategy extends PassportStrategy(Strategy, 'jwt') { 9 | constructor( 10 | private readonly configService: ConfigService, 11 | private readonly authService: AuthService 12 | ) { 13 | super({ 14 | jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme( 15 | configService.get('auth.prefixAuthorization') 16 | ), 17 | ignoreExpiration: false, 18 | jsonWebTokenOptions: { 19 | ignoreNotBefore: false, 20 | audience: configService.get('auth.audience'), 21 | issuer: configService.get('auth.issuer'), 22 | subject: configService.get('auth.subject'), 23 | }, 24 | secretOrKey: configService.get( 25 | 'auth.accessToken.secretKey' 26 | ), 27 | }); 28 | } 29 | 30 | async validate({ 31 | data, 32 | }: Record): Promise> { 33 | const payloadEncryption: boolean = 34 | await this.authService.getPayloadEncryption(); 35 | 36 | return payloadEncryption 37 | ? this.authService.decryptAccessToken({ data }) 38 | : data; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/modules/role/decorators/role.admin.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common'; 2 | import { ROLE_IS_ACTIVE_META_KEY } from 'src/modules/role/constants/role.constant'; 3 | import { RoleActiveGuard } from 'src/modules/role/guards/role.active.guard'; 4 | import { RoleNotFoundGuard } from 'src/modules/role/guards/role.not-found.guard'; 5 | import { RolePutToRequestGuard } from 'src/modules/role/guards/role.put-to-request.guard'; 6 | 7 | export function RoleAdminGetGuard(): MethodDecorator { 8 | return applyDecorators(UseGuards(RolePutToRequestGuard, RoleNotFoundGuard)); 9 | } 10 | 11 | export function RoleAdminUpdateGuard(): MethodDecorator { 12 | return applyDecorators( 13 | UseGuards(RolePutToRequestGuard, RoleNotFoundGuard, RoleActiveGuard), 14 | SetMetadata(ROLE_IS_ACTIVE_META_KEY, [true]) 15 | ); 16 | } 17 | 18 | export function RoleAdminDeleteGuard(): MethodDecorator { 19 | return applyDecorators( 20 | UseGuards(RolePutToRequestGuard, RoleNotFoundGuard, RoleActiveGuard), 21 | SetMetadata(ROLE_IS_ACTIVE_META_KEY, [true]) 22 | ); 23 | } 24 | 25 | export function RoleAdminUpdateActiveGuard(): MethodDecorator { 26 | return applyDecorators( 27 | UseGuards(RolePutToRequestGuard, RoleNotFoundGuard, RoleActiveGuard), 28 | SetMetadata(ROLE_IS_ACTIVE_META_KEY, [false]) 29 | ); 30 | } 31 | 32 | export function RoleAdminUpdateInactiveGuard(): MethodDecorator { 33 | return applyDecorators( 34 | UseGuards(RolePutToRequestGuard, RoleNotFoundGuard, RoleActiveGuard), 35 | SetMetadata(ROLE_IS_ACTIVE_META_KEY, [true]) 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/common/pagination/pipes/pagination.filter-in-enum.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, mixin, Type } from '@nestjs/common'; 2 | import { PipeTransform, Scope } from '@nestjs/common/interfaces'; 3 | import { REQUEST } from '@nestjs/core'; 4 | import { PaginationService } from 'src/common/pagination/services/pagination.service'; 5 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 6 | 7 | export function PaginationFilterInEnumPipe( 8 | field: string, 9 | defaultValue: T, 10 | defaultEnum: Record, 11 | raw: boolean 12 | ): Type { 13 | @Injectable({ scope: Scope.REQUEST }) 14 | class MixinPaginationFilterInEnumPipe implements PipeTransform { 15 | constructor( 16 | @Inject(REQUEST) protected readonly request: IRequestApp, 17 | private readonly paginationService: PaginationService 18 | ) {} 19 | 20 | async transform( 21 | value: string 22 | ): Promise> { 23 | let finalValue: T[] = defaultValue as T[]; 24 | 25 | if (value) { 26 | finalValue = value 27 | .split(',') 28 | .map((val: string) => defaultEnum[val]) 29 | .filter((val: string) => val) as T[]; 30 | } 31 | 32 | if (raw) { 33 | return { 34 | [field]: finalValue, 35 | }; 36 | } 37 | 38 | return this.paginationService.filterIn(field, finalValue); 39 | } 40 | } 41 | 42 | return mixin(MixinPaginationFilterInEnumPipe); 43 | } 44 | -------------------------------------------------------------------------------- /src/common/request/guards/request.param.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | CanActivate, 4 | ExecutionContext, 5 | BadRequestException, 6 | } from '@nestjs/common'; 7 | import { Reflector } from '@nestjs/core'; 8 | import { ClassConstructor, plainToInstance } from 'class-transformer'; 9 | import { validate, ValidationError } from 'class-validator'; 10 | import { REQUEST_PARAM_CLASS_DTOS_META_KEY } from 'src/common/request/constants/request.constant'; 11 | import { ENUM_REQUEST_STATUS_CODE_ERROR } from 'src/common/request/constants/request.status-code.constant'; 12 | 13 | @Injectable() 14 | export class RequestParamRawGuard implements CanActivate { 15 | constructor(private readonly reflector: Reflector) {} 16 | 17 | async canActivate(context: ExecutionContext): Promise { 18 | const { params } = context.switchToHttp().getRequest(); 19 | const classDtos: ClassConstructor[] = this.reflector.get< 20 | ClassConstructor[] 21 | >(REQUEST_PARAM_CLASS_DTOS_META_KEY, context.getHandler()); 22 | 23 | for (const clsDto of classDtos) { 24 | const request = plainToInstance(clsDto, params); 25 | 26 | const errors: ValidationError[] = await validate(request); 27 | 28 | if (errors.length > 0) { 29 | throw new BadRequestException({ 30 | statusCode: 31 | ENUM_REQUEST_STATUS_CODE_ERROR.REQUEST_VALIDATION_ERROR, 32 | message: 'http.clientError.badRequest', 33 | errors: errors, 34 | }); 35 | } 36 | } 37 | 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/common/response/interceptors/response.custom-headers.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NestInterceptor, 4 | ExecutionContext, 5 | CallHandler, 6 | } from '@nestjs/common'; 7 | import { Observable } from 'rxjs'; 8 | import { HttpArgumentsHost } from '@nestjs/common/interfaces'; 9 | import { Response } from 'express'; 10 | import { IRequestApp } from 'src/common/request/interfaces/request.interface'; 11 | 12 | // only for response success and error in controller 13 | @Injectable() 14 | export class ResponseCustomHeadersInterceptor 15 | implements NestInterceptor> 16 | { 17 | async intercept( 18 | context: ExecutionContext, 19 | next: CallHandler 20 | ): Promise | string>> { 21 | if (context.getType() === 'http') { 22 | const ctx: HttpArgumentsHost = context.switchToHttp(); 23 | const responseExpress: Response = ctx.getResponse(); 24 | const request: IRequestApp = ctx.getRequest(); 25 | 26 | responseExpress.setHeader('x-custom-lang', request.__xCustomLang); 27 | responseExpress.setHeader( 28 | 'x-timestamp', 29 | request.__xTimestamp ?? request.__timestamp 30 | ); 31 | responseExpress.setHeader('x-timezone', request.__timezone); 32 | responseExpress.setHeader('x-request-id', request.__id); 33 | responseExpress.setHeader('x-version', request.__version); 34 | responseExpress.setHeader('x-repo-version', request.__repoVersion); 35 | 36 | return next.handle(); 37 | } 38 | 39 | return next.handle(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/languages/en/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "list": "List User Success.", 3 | "get": "Get User Success.", 4 | "create": "Create User Success.", 5 | "delete": "Delete User Success.", 6 | "update": "Update User Success.", 7 | "inactive": "Inactive Succeed", 8 | "active": "Active Succeed", 9 | "import": "Import user Succeed", 10 | "blocked": "Success blocked user", 11 | "refresh": "Refresh token success", 12 | "changePassword": "Active Succeed", 13 | "info": "Get info payload Succeed", 14 | "profile": "Profile Success", 15 | "updateProfile": "Update profile Succeed", 16 | "claimUsername": "Claim username Succeed", 17 | "upload": "Upload Success", 18 | "login": "Login success.", 19 | "signUp": "Sign up Success", 20 | "loginGoogle": "Request login with google succeed", 21 | "loginGoogleCallback": "Login with google succeed", 22 | "signUpGoogle": "Request sign up with google succeed", 23 | "signUpGoogleCallback": "Sign up with google succeed", 24 | "error": { 25 | "notFound": "User not found.", 26 | "emailExist": "Email user used", 27 | "mobileNumberExist": "Mobile Number user used", 28 | "passwordExpired": "User password expired", 29 | "passwordAttemptMax": "Password attempt user max", 30 | "passwordNotMatch": "Password not match", 31 | "blocked": "User blocked", 32 | "inactivePermanent": "User inactive permanent", 33 | "inactive": "User is inactive", 34 | "isActiveInvalid": "User is active invalid", 35 | "usernameExist": "Username exist", 36 | "newPasswordMustDifference": "Old password must difference" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/common/auth/guards/jwt-refresh/auth.jwt-refresh.strategy.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt, Strategy } from 'passport-jwt'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Injectable } from '@nestjs/common'; 4 | import { ConfigService } from '@nestjs/config'; 5 | import { AuthService } from 'src/common/auth/services/auth.service'; 6 | 7 | @Injectable() 8 | export class AuthJwtRefreshStrategy extends PassportStrategy( 9 | Strategy, 10 | 'jwtRefresh' 11 | ) { 12 | constructor( 13 | private readonly configService: ConfigService, 14 | private readonly authService: AuthService 15 | ) { 16 | super({ 17 | jwtFromRequest: ExtractJwt.fromAuthHeaderWithScheme( 18 | configService.get('auth.prefixAuthorization') 19 | ), 20 | ignoreExpiration: false, 21 | jsonWebTokenOptions: { 22 | ignoreNotBefore: false, 23 | audience: configService.get('auth.audience'), 24 | issuer: configService.get('auth.issuer'), 25 | subject: configService.get('auth.subject'), 26 | }, 27 | secretOrKey: configService.get( 28 | 'auth.refreshToken.secretKey' 29 | ), 30 | }); 31 | } 32 | 33 | async validate({ 34 | data, 35 | }: Record): Promise> { 36 | const payloadEncryption: boolean = 37 | await this.authService.getPayloadEncryption(); 38 | 39 | return payloadEncryption 40 | ? this.authService.decryptRefreshToken({ data }) 41 | : data; 42 | } 43 | } 44 | --------------------------------------------------------------------------------