nestforge-generator 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +371 -0
- package/README.pt-BR.md +370 -0
- package/dist/features/auth-strategy.js +19 -0
- package/dist/features/auth-strategy.js.map +1 -0
- package/dist/features/database.js +59 -0
- package/dist/features/database.js.map +1 -0
- package/dist/features/dependencies.js +57 -0
- package/dist/features/dependencies.js.map +1 -0
- package/dist/features/language.js +172 -0
- package/dist/features/language.js.map +1 -0
- package/dist/features/markers.js +116 -0
- package/dist/features/markers.js.map +1 -0
- package/dist/generator.js +120 -0
- package/dist/generator.js.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.js +137 -0
- package/dist/prompts.js.map +1 -0
- package/package.json +76 -0
- package/templates/drizzle/.env.example +45 -0
- package/templates/drizzle/.env.test +38 -0
- package/templates/drizzle/.github/workflows/ci.yml +119 -0
- package/templates/drizzle/ARCHITECTURE.md +227 -0
- package/templates/drizzle/ARCHITECTURE.pt-BR.md +225 -0
- package/templates/drizzle/CODE_OF_CONDUCT.md +29 -0
- package/templates/drizzle/CODE_OF_CONDUCT.pt-BR.md +27 -0
- package/templates/drizzle/CONTRIBUTING.md +57 -0
- package/templates/drizzle/CONTRIBUTING.pt-BR.md +54 -0
- package/templates/drizzle/Dockerfile +43 -0
- package/templates/drizzle/LICENSE +21 -0
- package/templates/drizzle/README.md +257 -0
- package/templates/drizzle/README.pt-BR.md +257 -0
- package/templates/drizzle/ROADMAP.md +143 -0
- package/templates/drizzle/ROADMAP.pt-BR.md +143 -0
- package/templates/drizzle/TESTING.md +136 -0
- package/templates/drizzle/TESTING.pt-BR.md +136 -0
- package/templates/drizzle/docker-compose.yml +82 -0
- package/templates/drizzle/docs/adding-a-module.md +585 -0
- package/templates/drizzle/docs/features-markers.md +457 -0
- package/templates/drizzle/drizzle.config.ts +49 -0
- package/templates/drizzle/nest-cli.json +8 -0
- package/templates/drizzle/package.json +96 -0
- package/templates/drizzle/src/app.module.ts +72 -0
- package/templates/drizzle/src/auth/auth.controller.ts +283 -0
- package/templates/drizzle/src/auth/auth.module.ts +81 -0
- package/templates/drizzle/src/auth/auth.service.spec.ts +111 -0
- package/templates/drizzle/src/auth/auth.service.ts +631 -0
- package/templates/drizzle/src/auth/drizzle-session.store.ts +263 -0
- package/templates/drizzle/src/auth/dto/forgot-password.dto.ts +9 -0
- package/templates/drizzle/src/auth/dto/login.dto.ts +10 -0
- package/templates/drizzle/src/auth/dto/refresh-token.dto.ts +9 -0
- package/templates/drizzle/src/auth/dto/register.dto.ts +11 -0
- package/templates/drizzle/src/auth/dto/reset-password.dto.ts +10 -0
- package/templates/drizzle/src/auth/guards/github-auth.guard.ts +10 -0
- package/templates/drizzle/src/auth/guards/google-auth.guard.ts +10 -0
- package/templates/drizzle/src/auth/guards/jwt-auth.guard.ts +25 -0
- package/templates/drizzle/src/auth/guards/session-auth.guard.spec.ts +77 -0
- package/templates/drizzle/src/auth/guards/session-auth.guard.ts +37 -0
- package/templates/drizzle/src/auth/session.service.spec.ts +74 -0
- package/templates/drizzle/src/auth/session.service.ts +96 -0
- package/templates/drizzle/src/auth/strategies/github.strategy.ts +41 -0
- package/templates/drizzle/src/auth/strategies/google.strategy.ts +43 -0
- package/templates/drizzle/src/auth/strategies/jwt.strategy.ts +25 -0
- package/templates/drizzle/src/auth/token.service.ts +175 -0
- package/templates/drizzle/src/common/constants/permissions.ts +8 -0
- package/templates/drizzle/src/common/constants/role-permissions.ts +9 -0
- package/templates/drizzle/src/common/constants/role.enum.ts +5 -0
- package/templates/drizzle/src/common/decorators/current-user.decorator.ts +8 -0
- package/templates/drizzle/src/common/decorators/permissions.decorator.ts +7 -0
- package/templates/drizzle/src/common/decorators/public.decorator.ts +4 -0
- package/templates/drizzle/src/common/decorators/roles.decorator.ts +6 -0
- package/templates/drizzle/src/common/filters/http-exception.filter.ts +34 -0
- package/templates/drizzle/src/common/guards/permissions.guard.spec.ts +54 -0
- package/templates/drizzle/src/common/guards/permissions.guard.ts +28 -0
- package/templates/drizzle/src/common/guards/roles.guard.spec.ts +37 -0
- package/templates/drizzle/src/common/guards/roles.guard.ts +24 -0
- package/templates/drizzle/src/common/interceptors/logging.interceptor.ts +23 -0
- package/templates/drizzle/src/common/middleware/csrf.middleware.spec.ts +97 -0
- package/templates/drizzle/src/common/middleware/csrf.middleware.ts +46 -0
- package/templates/drizzle/src/common/utils/avatar-storage.util.ts +32 -0
- package/templates/drizzle/src/common/utils/hash.util.ts +5 -0
- package/templates/drizzle/src/config/env.validation.ts +50 -0
- package/templates/drizzle/src/database/database-lifecycle.service.ts +30 -0
- package/templates/drizzle/src/database/database.constants.ts +2 -0
- package/templates/drizzle/src/database/database.decorators.ts +5 -0
- package/templates/drizzle/src/database/database.module.ts +96 -0
- package/templates/drizzle/src/database/database.types.ts +28 -0
- package/templates/drizzle/src/database/schema/index.ts +11 -0
- package/templates/drizzle/src/database/schema/mysql.schema.ts +215 -0
- package/templates/drizzle/src/database/schema/postgres.schema.ts +218 -0
- package/templates/drizzle/src/database/schema/sqlite.schema.ts +190 -0
- package/templates/drizzle/src/database/seed.ts +152 -0
- package/templates/drizzle/src/health/health.controller.ts +49 -0
- package/templates/drizzle/src/health/health.module.ts +19 -0
- package/templates/drizzle/src/health/indicators/drizzle-health.indicator.spec.ts +71 -0
- package/templates/drizzle/src/health/indicators/drizzle-health.indicator.ts +52 -0
- package/templates/drizzle/src/health/indicators/redis-health.indicator.spec.ts +54 -0
- package/templates/drizzle/src/health/indicators/redis-health.indicator.ts +33 -0
- package/templates/drizzle/src/mail/mail.module.ts +12 -0
- package/templates/drizzle/src/mail/mail.processor.ts +48 -0
- package/templates/drizzle/src/mail/mail.service.ts +24 -0
- package/templates/drizzle/src/mail/templates/email-templates.ts +28 -0
- package/templates/drizzle/src/main.ts +145 -0
- package/templates/drizzle/src/metrics/metrics.controller.ts +22 -0
- package/templates/drizzle/src/metrics/metrics.interceptor.ts +30 -0
- package/templates/drizzle/src/metrics/metrics.module.ts +12 -0
- package/templates/drizzle/src/metrics/metrics.service.ts +34 -0
- package/templates/drizzle/src/types/express-session.d.ts +13 -0
- package/templates/drizzle/src/users/dto/create-user.dto.ts +12 -0
- package/templates/drizzle/src/users/dto/find-users-query.dto.ts +12 -0
- package/templates/drizzle/src/users/dto/update-user.dto.ts +6 -0
- package/templates/drizzle/src/users/entities/user.entity.ts +23 -0
- package/templates/drizzle/src/users/users.controller.ts +165 -0
- package/templates/drizzle/src/users/users.module.ts +10 -0
- package/templates/drizzle/src/users/users.service.spec.ts +301 -0
- package/templates/drizzle/src/users/users.service.ts +213 -0
- package/templates/drizzle/test/auth.e2e-spec.ts +114 -0
- package/templates/drizzle/test/session-auth.e2e-spec.ts +163 -0
- package/templates/drizzle/test/users.e2e-spec.ts +212 -0
- package/templates/drizzle/test/utils/clean-database.ts +104 -0
- package/templates/drizzle/test/utils/e2e-setup.ts +77 -0
- package/templates/drizzle/tsconfig.build.json +4 -0
- package/templates/drizzle/tsconfig.json +25 -0
- package/templates/drizzle/vitest.config.ts +20 -0
- package/templates/drizzle/vitest.e2e.config.ts +19 -0
- package/templates/prisma/.env.example +44 -0
- package/templates/prisma/.env.test +37 -0
- package/templates/prisma/.github/workflows/ci.yml +109 -0
- package/templates/prisma/ARCHITECTURE.md +70 -0
- package/templates/prisma/ARCHITECTURE.pt-BR.md +63 -0
- package/templates/prisma/CODE_OF_CONDUCT.md +29 -0
- package/templates/prisma/CODE_OF_CONDUCT.pt-BR.md +27 -0
- package/templates/prisma/CONTRIBUTING.md +57 -0
- package/templates/prisma/CONTRIBUTING.pt-BR.md +54 -0
- package/templates/prisma/Dockerfile +33 -0
- package/templates/prisma/LICENSE +21 -0
- package/templates/prisma/README.md +267 -0
- package/templates/prisma/README.pt-BR.md +267 -0
- package/templates/prisma/ROADMAP.md +75 -0
- package/templates/prisma/ROADMAP.pt-BR.md +65 -0
- package/templates/prisma/TESTING.md +123 -0
- package/templates/prisma/TESTING.pt-BR.md +123 -0
- package/templates/prisma/docker-compose.yml +78 -0
- package/templates/prisma/docs/adding-a-module.md +230 -0
- package/templates/prisma/docs/features-markers.md +50 -0
- package/templates/prisma/nest-cli.json +8 -0
- package/templates/prisma/package.json +95 -0
- package/templates/prisma/prisma/schema.prisma +105 -0
- package/templates/prisma/prisma/seed.ts +46 -0
- package/templates/prisma/src/app.module.ts +72 -0
- package/templates/prisma/src/auth/auth.controller.ts +283 -0
- package/templates/prisma/src/auth/auth.module.ts +59 -0
- package/templates/prisma/src/auth/auth.service.spec.ts +53 -0
- package/templates/prisma/src/auth/auth.service.ts +224 -0
- package/templates/prisma/src/auth/dto/forgot-password.dto.ts +9 -0
- package/templates/prisma/src/auth/dto/login.dto.ts +10 -0
- package/templates/prisma/src/auth/dto/refresh-token.dto.ts +9 -0
- package/templates/prisma/src/auth/dto/register.dto.ts +11 -0
- package/templates/prisma/src/auth/dto/reset-password.dto.ts +10 -0
- package/templates/prisma/src/auth/guards/github-auth.guard.ts +10 -0
- package/templates/prisma/src/auth/guards/google-auth.guard.ts +10 -0
- package/templates/prisma/src/auth/guards/jwt-auth.guard.ts +25 -0
- package/templates/prisma/src/auth/guards/session-auth.guard.spec.ts +77 -0
- package/templates/prisma/src/auth/guards/session-auth.guard.ts +37 -0
- package/templates/prisma/src/auth/session.service.spec.ts +74 -0
- package/templates/prisma/src/auth/session.service.ts +96 -0
- package/templates/prisma/src/auth/strategies/github.strategy.ts +41 -0
- package/templates/prisma/src/auth/strategies/google.strategy.ts +43 -0
- package/templates/prisma/src/auth/strategies/jwt.strategy.ts +25 -0
- package/templates/prisma/src/auth/token.service.ts +84 -0
- package/templates/prisma/src/common/constants/permissions.ts +8 -0
- package/templates/prisma/src/common/constants/role-permissions.ts +9 -0
- package/templates/prisma/src/common/decorators/current-user.decorator.ts +8 -0
- package/templates/prisma/src/common/decorators/permissions.decorator.ts +7 -0
- package/templates/prisma/src/common/decorators/public.decorator.ts +4 -0
- package/templates/prisma/src/common/decorators/roles.decorator.ts +6 -0
- package/templates/prisma/src/common/filters/http-exception.filter.ts +34 -0
- package/templates/prisma/src/common/guards/permissions.guard.spec.ts +54 -0
- package/templates/prisma/src/common/guards/permissions.guard.ts +28 -0
- package/templates/prisma/src/common/guards/roles.guard.spec.ts +37 -0
- package/templates/prisma/src/common/guards/roles.guard.ts +24 -0
- package/templates/prisma/src/common/interceptors/logging.interceptor.ts +23 -0
- package/templates/prisma/src/common/middleware/csrf.middleware.spec.ts +97 -0
- package/templates/prisma/src/common/middleware/csrf.middleware.ts +46 -0
- package/templates/prisma/src/common/utils/avatar-storage.util.ts +32 -0
- package/templates/prisma/src/common/utils/hash.util.ts +5 -0
- package/templates/prisma/src/config/env.validation.ts +49 -0
- package/templates/prisma/src/database/prisma.module.ts +9 -0
- package/templates/prisma/src/database/prisma.service.ts +13 -0
- package/templates/prisma/src/health/health.controller.ts +49 -0
- package/templates/prisma/src/health/health.module.ts +19 -0
- package/templates/prisma/src/health/indicators/prisma-health.indicator.spec.ts +22 -0
- package/templates/prisma/src/health/indicators/prisma-health.indicator.ts +19 -0
- package/templates/prisma/src/health/indicators/redis-health.indicator.spec.ts +54 -0
- package/templates/prisma/src/health/indicators/redis-health.indicator.ts +33 -0
- package/templates/prisma/src/mail/mail.module.ts +12 -0
- package/templates/prisma/src/mail/mail.processor.ts +48 -0
- package/templates/prisma/src/mail/mail.service.ts +24 -0
- package/templates/prisma/src/mail/templates/email-templates.ts +28 -0
- package/templates/prisma/src/main.ts +92 -0
- package/templates/prisma/src/metrics/metrics.controller.ts +22 -0
- package/templates/prisma/src/metrics/metrics.interceptor.ts +30 -0
- package/templates/prisma/src/metrics/metrics.module.ts +12 -0
- package/templates/prisma/src/metrics/metrics.service.ts +34 -0
- package/templates/prisma/src/types/express-session.d.ts +13 -0
- package/templates/prisma/src/users/dto/create-user.dto.ts +12 -0
- package/templates/prisma/src/users/dto/find-users-query.dto.ts +12 -0
- package/templates/prisma/src/users/dto/update-user.dto.ts +6 -0
- package/templates/prisma/src/users/entities/user.entity.ts +20 -0
- package/templates/prisma/src/users/users.controller.ts +165 -0
- package/templates/prisma/src/users/users.module.ts +10 -0
- package/templates/prisma/src/users/users.service.spec.ts +104 -0
- package/templates/prisma/src/users/users.service.ts +114 -0
- package/templates/prisma/test/auth.e2e-spec.ts +75 -0
- package/templates/prisma/test/session-auth.e2e-spec.ts +163 -0
- package/templates/prisma/test/users.e2e-spec.ts +106 -0
- package/templates/prisma/test/utils/clean-database.ts +14 -0
- package/templates/prisma/test/utils/e2e-setup.ts +58 -0
- package/templates/prisma/tsconfig.build.json +4 -0
- package/templates/prisma/tsconfig.json +25 -0
- package/templates/prisma/vitest.config.ts +20 -0
- package/templates/prisma/vitest.e2e.config.ts +19 -0
- package/templates/typeorm/.env.example +45 -0
- package/templates/typeorm/.env.test +38 -0
- package/templates/typeorm/.github/workflows/ci.yml +116 -0
- package/templates/typeorm/ARCHITECTURE.md +158 -0
- package/templates/typeorm/ARCHITECTURE.pt-BR.md +156 -0
- package/templates/typeorm/CODE_OF_CONDUCT.md +29 -0
- package/templates/typeorm/CODE_OF_CONDUCT.pt-BR.md +27 -0
- package/templates/typeorm/CONTRIBUTING.md +57 -0
- package/templates/typeorm/CONTRIBUTING.pt-BR.md +54 -0
- package/templates/typeorm/Dockerfile +43 -0
- package/templates/typeorm/LICENSE +21 -0
- package/templates/typeorm/README.md +266 -0
- package/templates/typeorm/README.pt-BR.md +266 -0
- package/templates/typeorm/ROADMAP.md +79 -0
- package/templates/typeorm/ROADMAP.pt-BR.md +67 -0
- package/templates/typeorm/TESTING.md +114 -0
- package/templates/typeorm/TESTING.pt-BR.md +114 -0
- package/templates/typeorm/docker-compose.yml +78 -0
- package/templates/typeorm/docs/adding-a-module.md +369 -0
- package/templates/typeorm/docs/features-markers.md +50 -0
- package/templates/typeorm/nest-cli.json +8 -0
- package/templates/typeorm/package.json +98 -0
- package/templates/typeorm/src/app.module.ts +72 -0
- package/templates/typeorm/src/auth/auth.controller.ts +283 -0
- package/templates/typeorm/src/auth/auth.module.ts +86 -0
- package/templates/typeorm/src/auth/auth.service.spec.ts +111 -0
- package/templates/typeorm/src/auth/auth.service.ts +346 -0
- package/templates/typeorm/src/auth/dto/forgot-password.dto.ts +9 -0
- package/templates/typeorm/src/auth/dto/login.dto.ts +10 -0
- package/templates/typeorm/src/auth/dto/refresh-token.dto.ts +9 -0
- package/templates/typeorm/src/auth/dto/register.dto.ts +11 -0
- package/templates/typeorm/src/auth/dto/reset-password.dto.ts +10 -0
- package/templates/typeorm/src/auth/entities/email-verification-token.entity.ts +69 -0
- package/templates/typeorm/src/auth/entities/oauth-account.entity.ts +50 -0
- package/templates/typeorm/src/auth/entities/password-reset-token.entity.ts +69 -0
- package/templates/typeorm/src/auth/entities/refresh-token.entity.ts +69 -0
- package/templates/typeorm/src/auth/entities/session.entity.ts +50 -0
- package/templates/typeorm/src/auth/guards/github-auth.guard.ts +10 -0
- package/templates/typeorm/src/auth/guards/google-auth.guard.ts +10 -0
- package/templates/typeorm/src/auth/guards/jwt-auth.guard.ts +25 -0
- package/templates/typeorm/src/auth/guards/session-auth.guard.spec.ts +77 -0
- package/templates/typeorm/src/auth/guards/session-auth.guard.ts +37 -0
- package/templates/typeorm/src/auth/session.service.spec.ts +74 -0
- package/templates/typeorm/src/auth/session.service.ts +96 -0
- package/templates/typeorm/src/auth/strategies/github.strategy.ts +41 -0
- package/templates/typeorm/src/auth/strategies/google.strategy.ts +43 -0
- package/templates/typeorm/src/auth/strategies/jwt.strategy.ts +25 -0
- package/templates/typeorm/src/auth/token.service.ts +115 -0
- package/templates/typeorm/src/common/constants/permissions.ts +8 -0
- package/templates/typeorm/src/common/constants/role-permissions.ts +9 -0
- package/templates/typeorm/src/common/constants/role.enum.ts +5 -0
- package/templates/typeorm/src/common/decorators/current-user.decorator.ts +8 -0
- package/templates/typeorm/src/common/decorators/permissions.decorator.ts +7 -0
- package/templates/typeorm/src/common/decorators/public.decorator.ts +4 -0
- package/templates/typeorm/src/common/decorators/roles.decorator.ts +6 -0
- package/templates/typeorm/src/common/filters/http-exception.filter.ts +34 -0
- package/templates/typeorm/src/common/guards/permissions.guard.spec.ts +54 -0
- package/templates/typeorm/src/common/guards/permissions.guard.ts +28 -0
- package/templates/typeorm/src/common/guards/roles.guard.spec.ts +37 -0
- package/templates/typeorm/src/common/guards/roles.guard.ts +24 -0
- package/templates/typeorm/src/common/interceptors/logging.interceptor.ts +23 -0
- package/templates/typeorm/src/common/middleware/csrf.middleware.spec.ts +97 -0
- package/templates/typeorm/src/common/middleware/csrf.middleware.ts +46 -0
- package/templates/typeorm/src/common/utils/avatar-storage.util.ts +32 -0
- package/templates/typeorm/src/common/utils/hash.util.ts +5 -0
- package/templates/typeorm/src/config/env.validation.ts +50 -0
- package/templates/typeorm/src/database/data-source.ts +20 -0
- package/templates/typeorm/src/database/database.module.ts +39 -0
- package/templates/typeorm/src/database/seed.ts +81 -0
- package/templates/typeorm/src/database/typeorm-options.ts +49 -0
- package/templates/typeorm/src/health/health.controller.ts +49 -0
- package/templates/typeorm/src/health/health.module.ts +19 -0
- package/templates/typeorm/src/health/indicators/redis-health.indicator.spec.ts +54 -0
- package/templates/typeorm/src/health/indicators/redis-health.indicator.ts +33 -0
- package/templates/typeorm/src/health/indicators/typeorm-health.indicator.spec.ts +49 -0
- package/templates/typeorm/src/health/indicators/typeorm-health.indicator.ts +37 -0
- package/templates/typeorm/src/mail/mail.module.ts +12 -0
- package/templates/typeorm/src/mail/mail.processor.ts +48 -0
- package/templates/typeorm/src/mail/mail.service.ts +24 -0
- package/templates/typeorm/src/mail/templates/email-templates.ts +28 -0
- package/templates/typeorm/src/main.ts +107 -0
- package/templates/typeorm/src/metrics/metrics.controller.ts +22 -0
- package/templates/typeorm/src/metrics/metrics.interceptor.ts +30 -0
- package/templates/typeorm/src/metrics/metrics.module.ts +12 -0
- package/templates/typeorm/src/metrics/metrics.service.ts +34 -0
- package/templates/typeorm/src/types/express-session.d.ts +13 -0
- package/templates/typeorm/src/users/dto/create-user.dto.ts +12 -0
- package/templates/typeorm/src/users/dto/find-users-query.dto.ts +12 -0
- package/templates/typeorm/src/users/dto/update-user.dto.ts +6 -0
- package/templates/typeorm/src/users/entities/user.entity.ts +113 -0
- package/templates/typeorm/src/users/users.controller.ts +165 -0
- package/templates/typeorm/src/users/users.module.ts +13 -0
- package/templates/typeorm/src/users/users.service.spec.ts +183 -0
- package/templates/typeorm/src/users/users.service.ts +124 -0
- package/templates/typeorm/test/auth.e2e-spec.ts +111 -0
- package/templates/typeorm/test/session-auth.e2e-spec.ts +159 -0
- package/templates/typeorm/test/users.e2e-spec.ts +183 -0
- package/templates/typeorm/test/utils/clean-database.ts +65 -0
- package/templates/typeorm/test/utils/e2e-setup.ts +77 -0
- package/templates/typeorm/tsconfig.build.json +4 -0
- package/templates/typeorm/tsconfig.json +25 -0
- package/templates/typeorm/vitest.config.ts +20 -0
- package/templates/typeorm/vitest.e2e.config.ts +19 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// nestforge:feature-file:auth:password,auth:token
|
|
2
|
+
import { INestApplication } from '@nestjs/common';
|
|
3
|
+
import { PrismaClient, Role } from '@prisma/client';
|
|
4
|
+
import * as bcrypt from 'bcryptjs';
|
|
5
|
+
import request from 'supertest';
|
|
6
|
+
import { createTestApp } from './utils/e2e-setup';
|
|
7
|
+
import { cleanDatabase } from './utils/clean-database';
|
|
8
|
+
|
|
9
|
+
describe('Users (e2e)', () => {
|
|
10
|
+
let app: INestApplication;
|
|
11
|
+
let prisma: PrismaClient;
|
|
12
|
+
|
|
13
|
+
async function createUserWithRole(email: string, password: string, role: Role) {
|
|
14
|
+
const passwordHash = await bcrypt.hash(password, 10);
|
|
15
|
+
await prisma.user.create({
|
|
16
|
+
data: { name: 'Usuário de teste', email, passwordHash, role, emailVerifiedAt: new Date() },
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function loginAndGetToken(email: string, password: string) {
|
|
21
|
+
const response = await request(app.getHttpServer())
|
|
22
|
+
.post('/auth/login')
|
|
23
|
+
.send({ email, password })
|
|
24
|
+
.expect(200);
|
|
25
|
+
return response.body.accessToken as string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
beforeAll(async () => {
|
|
29
|
+
app = await createTestApp();
|
|
30
|
+
prisma = new PrismaClient();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
await cleanDatabase(prisma);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterAll(async () => {
|
|
38
|
+
await prisma.$disconnect();
|
|
39
|
+
await app.close();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('rejeita acesso sem token', async () => {
|
|
43
|
+
await request(app.getHttpServer()).get('/users').expect(401);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('ADMIN consegue criar, listar, atualizar e remover um usuário', async () => {
|
|
47
|
+
await createUserWithRole('admin.e2e@example.com', 'senhaForte123', Role.ADMIN);
|
|
48
|
+
const token = await loginAndGetToken('admin.e2e@example.com', 'senhaForte123');
|
|
49
|
+
const server = app.getHttpServer();
|
|
50
|
+
|
|
51
|
+
const createResponse = await request(server)
|
|
52
|
+
.post('/users')
|
|
53
|
+
.set('Authorization', `Bearer ${token}`)
|
|
54
|
+
.send({ name: 'Novo Usuário', email: 'novo.e2e@example.com', password: 'senhaForte123' })
|
|
55
|
+
.expect(201);
|
|
56
|
+
|
|
57
|
+
expect(createResponse.body).not.toHaveProperty('passwordHash');
|
|
58
|
+
const userId = createResponse.body.id;
|
|
59
|
+
|
|
60
|
+
const listResponse = await request(server)
|
|
61
|
+
.get('/users')
|
|
62
|
+
.set('Authorization', `Bearer ${token}`)
|
|
63
|
+
.expect(200);
|
|
64
|
+
|
|
65
|
+
expect(listResponse.body.meta.total).toBeGreaterThanOrEqual(1);
|
|
66
|
+
|
|
67
|
+
await request(server)
|
|
68
|
+
.patch(`/users/${userId}`)
|
|
69
|
+
.set('Authorization', `Bearer ${token}`)
|
|
70
|
+
.send({ name: 'Nome Atualizado' })
|
|
71
|
+
.expect(200);
|
|
72
|
+
|
|
73
|
+
await request(server)
|
|
74
|
+
.delete(`/users/${userId}`)
|
|
75
|
+
.set('Authorization', `Bearer ${token}`)
|
|
76
|
+
.expect(200);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// nestforge:feature:rbac
|
|
80
|
+
it('USER consegue ler mas não consegue criar usuário', async () => {
|
|
81
|
+
await createUserWithRole('user.e2e@example.com', 'senhaForte123', Role.USER);
|
|
82
|
+
const token = await loginAndGetToken('user.e2e@example.com', 'senhaForte123');
|
|
83
|
+
const server = app.getHttpServer();
|
|
84
|
+
|
|
85
|
+
await request(server).get('/users').set('Authorization', `Bearer ${token}`).expect(200);
|
|
86
|
+
|
|
87
|
+
await request(server)
|
|
88
|
+
.post('/users')
|
|
89
|
+
.set('Authorization', `Bearer ${token}`)
|
|
90
|
+
.send({ name: 'Não deveria criar', email: 'bloqueado.e2e@example.com', password: 'senhaForte123' })
|
|
91
|
+
.expect(403);
|
|
92
|
+
});
|
|
93
|
+
// nestforge:feature:rbac:end
|
|
94
|
+
|
|
95
|
+
it('GET /users/me retorna o usuário autenticado', async () => {
|
|
96
|
+
await createUserWithRole('me.e2e@example.com', 'senhaForte123', Role.USER);
|
|
97
|
+
const token = await loginAndGetToken('me.e2e@example.com', 'senhaForte123');
|
|
98
|
+
|
|
99
|
+
const response = await request(app.getHttpServer())
|
|
100
|
+
.get('/users/me')
|
|
101
|
+
.set('Authorization', `Bearer ${token}`)
|
|
102
|
+
.expect(200);
|
|
103
|
+
|
|
104
|
+
expect(response.body.email).toBe('me.e2e@example.com');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PrismaClient } from '@prisma/client';
|
|
2
|
+
|
|
3
|
+
export async function cleanDatabase(prisma: PrismaClient) {
|
|
4
|
+
await prisma.$transaction([
|
|
5
|
+
// nestforge:feature:auth:session
|
|
6
|
+
prisma.session.deleteMany(),
|
|
7
|
+
// nestforge:feature:auth:session:end
|
|
8
|
+
prisma.refreshToken.deleteMany(),
|
|
9
|
+
prisma.passwordResetToken.deleteMany(),
|
|
10
|
+
prisma.emailVerificationToken.deleteMany(),
|
|
11
|
+
prisma.oAuthAccount.deleteMany(),
|
|
12
|
+
prisma.user.deleteMany(),
|
|
13
|
+
]);
|
|
14
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ClassSerializerInterceptor, INestApplication } from '@nestjs/common';
|
|
2
|
+
import { Reflector } from '@nestjs/core';
|
|
3
|
+
import { Test } from '@nestjs/testing';
|
|
4
|
+
import { NestExpressApplication } from '@nestjs/platform-express';
|
|
5
|
+
// nestforge:feature:validation
|
|
6
|
+
import { ZodValidationPipe } from 'nestjs-zod';
|
|
7
|
+
// nestforge:feature:validation:end
|
|
8
|
+
import { AppModule } from '../../src/app.module';
|
|
9
|
+
import { HttpExceptionFilter } from '../../src/common/filters/http-exception.filter';
|
|
10
|
+
// nestforge:feature:auth:session
|
|
11
|
+
import session from 'express-session';
|
|
12
|
+
import { PrismaSessionStore } from '@quixo3/prisma-session-store';
|
|
13
|
+
import { PrismaService } from '../../src/database/prisma.service';
|
|
14
|
+
// nestforge:feature:auth:session:end
|
|
15
|
+
|
|
16
|
+
export async function createTestApp(): Promise<INestApplication> {
|
|
17
|
+
const moduleRef = await Test.createTestingModule({
|
|
18
|
+
imports: [AppModule],
|
|
19
|
+
}).compile();
|
|
20
|
+
|
|
21
|
+
const app = moduleRef.createNestApplication<NestExpressApplication>();
|
|
22
|
+
|
|
23
|
+
// nestforge:feature:auth:session
|
|
24
|
+
const prisma = app.get(PrismaService);
|
|
25
|
+
|
|
26
|
+
app.use(
|
|
27
|
+
session({
|
|
28
|
+
name: 'nestforge.sid',
|
|
29
|
+
secret: process.env.SESSION_SECRET as string,
|
|
30
|
+
resave: false,
|
|
31
|
+
saveUninitialized: false,
|
|
32
|
+
store: new PrismaSessionStore(prisma, {
|
|
33
|
+
checkPeriod: 2 * 60 * 1000,
|
|
34
|
+
dbRecordIdIsSessionId: true,
|
|
35
|
+
dbRecordIdFunction: undefined,
|
|
36
|
+
}),
|
|
37
|
+
cookie: {
|
|
38
|
+
httpOnly: true,
|
|
39
|
+
secure: false,
|
|
40
|
+
sameSite: 'lax',
|
|
41
|
+
maxAge: Number(
|
|
42
|
+
process.env.SESSION_MAX_AGE ?? 7 * 24 * 60 * 60 * 1000,
|
|
43
|
+
),
|
|
44
|
+
},
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
// nestforge:feature:auth:session:end
|
|
48
|
+
|
|
49
|
+
// mesmos pipes/filters/interceptors globais do main.ts, pra testar o comportamento real da API
|
|
50
|
+
// nestforge:feature:validation
|
|
51
|
+
app.useGlobalPipes(new ZodValidationPipe());
|
|
52
|
+
// nestforge:feature:validation:end
|
|
53
|
+
app.useGlobalFilters(new HttpExceptionFilter());
|
|
54
|
+
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
|
|
55
|
+
|
|
56
|
+
await app.init();
|
|
57
|
+
return app;
|
|
58
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "commonjs",
|
|
4
|
+
"declaration": true,
|
|
5
|
+
"removeComments": true,
|
|
6
|
+
"emitDecoratorMetadata": true,
|
|
7
|
+
"experimentalDecorators": true,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"target": "ES2022",
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"outDir": "./dist",
|
|
12
|
+
"baseUrl": "./",
|
|
13
|
+
"incremental": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"strict": true,
|
|
16
|
+
"strictNullChecks": true,
|
|
17
|
+
"noImplicitAny": true,
|
|
18
|
+
"forceConsistentCasingInFileNames": true,
|
|
19
|
+
"moduleResolution": "node",
|
|
20
|
+
"esModuleInterop": true,
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["src/*"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import swc from 'unplugin-swc';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [
|
|
6
|
+
swc.vite({
|
|
7
|
+
module: { type: 'es6' },
|
|
8
|
+
}),
|
|
9
|
+
],
|
|
10
|
+
test: {
|
|
11
|
+
globals: true,
|
|
12
|
+
root: './',
|
|
13
|
+
environment: 'node',
|
|
14
|
+
include: ['src/**/*.spec.ts'],
|
|
15
|
+
coverage: {
|
|
16
|
+
reporter: ['text', 'html'],
|
|
17
|
+
exclude: ['**/*.module.ts', '**/main.ts'],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import swc from 'unplugin-swc';
|
|
2
|
+
import { defineConfig } from 'vitest/config';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [
|
|
6
|
+
swc.vite({
|
|
7
|
+
module: { type: 'es6' },
|
|
8
|
+
}),
|
|
9
|
+
],
|
|
10
|
+
test: {
|
|
11
|
+
globals: true,
|
|
12
|
+
root: './',
|
|
13
|
+
environment: 'node',
|
|
14
|
+
include: ['test/**/*.e2e-spec.ts'],
|
|
15
|
+
testTimeout: 30000,
|
|
16
|
+
hookTimeout: 30000,
|
|
17
|
+
fileParallelism: false,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# App
|
|
2
|
+
NODE_ENV=development
|
|
3
|
+
PORT=3000
|
|
4
|
+
APP_URL=http://localhost:3000
|
|
5
|
+
CORS_ORIGINS=http://localhost:5173
|
|
6
|
+
|
|
7
|
+
# Database
|
|
8
|
+
DB_TYPE=postgres
|
|
9
|
+
DATABASE_URL="postgresql://nestforge:nestforge@localhost:5432/nestforge"
|
|
10
|
+
|
|
11
|
+
# JWT
|
|
12
|
+
# nestforge:feature:auth:token
|
|
13
|
+
JWT_ACCESS_SECRET=troque-este-segredo-em-producao
|
|
14
|
+
JWT_ACCESS_EXPIRES_IN=15m
|
|
15
|
+
JWT_REFRESH_SECRET=troque-este-outro-segredo-em-producao
|
|
16
|
+
JWT_REFRESH_EXPIRES_IN=7d
|
|
17
|
+
# nestforge:feature:auth:token:end
|
|
18
|
+
|
|
19
|
+
# Redis
|
|
20
|
+
REDIS_HOST=localhost
|
|
21
|
+
REDIS_PORT=6379
|
|
22
|
+
|
|
23
|
+
# Mail (Mailpit em dev)
|
|
24
|
+
MAIL_HOST=localhost
|
|
25
|
+
MAIL_PORT=1025
|
|
26
|
+
MAIL_FROM="NestForge <no-reply@nestforge.dev>"
|
|
27
|
+
|
|
28
|
+
# OAuth (opcional)
|
|
29
|
+
GOOGLE_CLIENT_ID=
|
|
30
|
+
GOOGLE_CLIENT_SECRET=
|
|
31
|
+
GITHUB_CLIENT_ID=
|
|
32
|
+
GITHUB_CLIENT_SECRET=
|
|
33
|
+
|
|
34
|
+
# Rate limit
|
|
35
|
+
THROTTLE_TTL=60
|
|
36
|
+
THROTTLE_LIMIT=100
|
|
37
|
+
|
|
38
|
+
# nestforge:feature:auth:session
|
|
39
|
+
# Session
|
|
40
|
+
SESSION_SECRET=troque-este-segredo-de-sessao-em-producao
|
|
41
|
+
SESSION_MAX_AGE=604800000
|
|
42
|
+
|
|
43
|
+
# CSRF
|
|
44
|
+
ENABLE_CSRF=true
|
|
45
|
+
# nestforge:feature:auth:session:end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
NODE_ENV=test
|
|
2
|
+
PORT=3000
|
|
3
|
+
APP_URL=http://localhost:3000
|
|
4
|
+
CORS_ORIGINS=http://localhost:5173
|
|
5
|
+
|
|
6
|
+
# Banco isolado, nunca o de desenvolvimento
|
|
7
|
+
DB_TYPE=postgres
|
|
8
|
+
DATABASE_URL="postgresql://nestforge:nestforge@localhost:5432/nestforge_test"
|
|
9
|
+
|
|
10
|
+
# nestforge:feature:auth:token
|
|
11
|
+
JWT_ACCESS_SECRET=test-access-secret-0123456789
|
|
12
|
+
JWT_ACCESS_EXPIRES_IN=15m
|
|
13
|
+
JWT_REFRESH_SECRET=test-refresh-secret-0123456789
|
|
14
|
+
JWT_REFRESH_EXPIRES_IN=7d
|
|
15
|
+
# nestforge:feature:auth:token:end
|
|
16
|
+
|
|
17
|
+
GOOGLE_CLIENT_ID=test-google-client-id
|
|
18
|
+
GOOGLE_CLIENT_SECRET=test-google-client-secret
|
|
19
|
+
GITHUB_CLIENT_ID=test-github-client-id
|
|
20
|
+
GITHUB_CLIENT_SECRET=test-github-client-secret
|
|
21
|
+
|
|
22
|
+
REDIS_HOST=localhost
|
|
23
|
+
REDIS_PORT=6379
|
|
24
|
+
|
|
25
|
+
MAIL_HOST=localhost
|
|
26
|
+
MAIL_PORT=1025
|
|
27
|
+
MAIL_FROM="NestForge <no-reply@nestforge.dev>"
|
|
28
|
+
|
|
29
|
+
# limite bem mais alto pra não interferir nos testes
|
|
30
|
+
THROTTLE_TTL=60
|
|
31
|
+
THROTTLE_LIMIT=1000
|
|
32
|
+
|
|
33
|
+
# nestforge:feature:auth:session
|
|
34
|
+
SESSION_SECRET=test-session-secret-01234567890123456789
|
|
35
|
+
SESSION_MAX_AGE=604800000
|
|
36
|
+
|
|
37
|
+
ENABLE_CSRF=true
|
|
38
|
+
# nestforge:feature:auth:session:end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build-test-lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
services:
|
|
14
|
+
# nestforge:feature:database:postgres
|
|
15
|
+
postgres:
|
|
16
|
+
image: postgres:16-alpine
|
|
17
|
+
env:
|
|
18
|
+
POSTGRES_USER: nestforge
|
|
19
|
+
POSTGRES_PASSWORD: nestforge
|
|
20
|
+
POSTGRES_DB: nestforge
|
|
21
|
+
ports:
|
|
22
|
+
- 5432:5432
|
|
23
|
+
options: >-
|
|
24
|
+
--health-cmd pg_isready
|
|
25
|
+
--health-interval 10s
|
|
26
|
+
--health-timeout 5s
|
|
27
|
+
--health-retries 5
|
|
28
|
+
# nestforge:feature:database:postgres:end
|
|
29
|
+
|
|
30
|
+
# nestforge:feature:database:mysql
|
|
31
|
+
mysql:
|
|
32
|
+
image: mysql:8
|
|
33
|
+
env:
|
|
34
|
+
MYSQL_USER: nestforge
|
|
35
|
+
MYSQL_PASSWORD: nestforge
|
|
36
|
+
MYSQL_ROOT_PASSWORD: nestforge
|
|
37
|
+
MYSQL_DATABASE: nestforge
|
|
38
|
+
ports:
|
|
39
|
+
- 3306:3306
|
|
40
|
+
options: >-
|
|
41
|
+
--health-cmd "mysqladmin ping -h localhost"
|
|
42
|
+
--health-interval 10s
|
|
43
|
+
--health-timeout 5s
|
|
44
|
+
--health-retries 5
|
|
45
|
+
# nestforge:feature:database:mysql:end
|
|
46
|
+
|
|
47
|
+
# nestforge:feature:redis
|
|
48
|
+
redis:
|
|
49
|
+
image: redis:7-alpine
|
|
50
|
+
ports:
|
|
51
|
+
- 6379:6379
|
|
52
|
+
options: >-
|
|
53
|
+
--health-cmd "redis-cli ping"
|
|
54
|
+
--health-interval 10s
|
|
55
|
+
--health-timeout 5s
|
|
56
|
+
--health-retries 5
|
|
57
|
+
# nestforge:feature:redis:end
|
|
58
|
+
|
|
59
|
+
env:
|
|
60
|
+
# nestforge:feature:database:postgres
|
|
61
|
+
DB_TYPE: postgres
|
|
62
|
+
DATABASE_URL: postgresql://nestforge:nestforge@localhost:5432/nestforge?schema=public
|
|
63
|
+
# nestforge:feature:database:postgres:end
|
|
64
|
+
|
|
65
|
+
# nestforge:feature:database:mysql
|
|
66
|
+
DB_TYPE: mysql
|
|
67
|
+
DATABASE_URL: mysql://nestforge:nestforge@localhost:3306/nestforge
|
|
68
|
+
# nestforge:feature:database:mysql:end
|
|
69
|
+
|
|
70
|
+
# nestforge:feature:database:sqlite
|
|
71
|
+
DB_TYPE: sqlite
|
|
72
|
+
DATABASE_URL: file:./ci-dev.db
|
|
73
|
+
# nestforge:feature:database:sqlite:end
|
|
74
|
+
|
|
75
|
+
# nestforge:feature:auth:token
|
|
76
|
+
JWT_ACCESS_SECRET: ci-access-secret-0123456789
|
|
77
|
+
JWT_REFRESH_SECRET: ci-refresh-secret-0123456789
|
|
78
|
+
# nestforge:feature:auth:token:end
|
|
79
|
+
|
|
80
|
+
steps:
|
|
81
|
+
- name: Checkout
|
|
82
|
+
uses: actions/checkout@v4
|
|
83
|
+
|
|
84
|
+
- name: Setup Node
|
|
85
|
+
uses: actions/setup-node@v4
|
|
86
|
+
with:
|
|
87
|
+
node-version: 20
|
|
88
|
+
cache: npm
|
|
89
|
+
|
|
90
|
+
- name: Instalar dependências
|
|
91
|
+
run: npm ci
|
|
92
|
+
|
|
93
|
+
- name: Rodar migrations
|
|
94
|
+
run: npx typeorm-ts-node-commonjs -d src/database/data-source.ts migration:run
|
|
95
|
+
|
|
96
|
+
- name: Lint
|
|
97
|
+
run: npm run lint
|
|
98
|
+
|
|
99
|
+
- name: Build
|
|
100
|
+
run: npm run build
|
|
101
|
+
|
|
102
|
+
- name: Testes
|
|
103
|
+
run: npm run test
|
|
104
|
+
|
|
105
|
+
# nestforge:feature:database:postgres
|
|
106
|
+
- name: Criar banco de teste
|
|
107
|
+
run: PGPASSWORD=nestforge psql -h localhost -U nestforge -d nestforge -c "CREATE DATABASE nestforge_test;"
|
|
108
|
+
# nestforge:feature:database:postgres:end
|
|
109
|
+
|
|
110
|
+
# nestforge:feature:database:mysql
|
|
111
|
+
- name: Criar banco de teste
|
|
112
|
+
run: mysql -h 127.0.0.1 -u root -pnestforge -e "CREATE DATABASE IF NOT EXISTS nestforge_test;"
|
|
113
|
+
# nestforge:feature:database:mysql:end
|
|
114
|
+
|
|
115
|
+
- name: Testes e2e
|
|
116
|
+
run: npm run test:e2e
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
**English** | [Português](ARCHITECTURE.pt-BR.md)
|
|
4
|
+
|
|
5
|
+
This document explains how NestForge with TypeORM is organized and why certain design decisions were made.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Request → main.ts (global pipes, filters, and interceptors)
|
|
11
|
+
→ Authentication and authorization guards
|
|
12
|
+
→ Controller (validates DTO and delegates)
|
|
13
|
+
→ Service (business rules)
|
|
14
|
+
→ TypeORM Repository
|
|
15
|
+
→ PostgreSQL, MySQL, or SQLite
|
|
16
|
+
→ ClassSerializerInterceptor
|
|
17
|
+
→ Response
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Each domain module follows the same structure:
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
<module>/
|
|
24
|
+
├── dto/ # Zod schemas and DTOs
|
|
25
|
+
├── entities/ # entities persisted by TypeORM
|
|
26
|
+
├── <module>.controller.ts # receives the request and calls the service
|
|
27
|
+
├── <module>.service.ts # business rules
|
|
28
|
+
├── <module>.service.spec.ts
|
|
29
|
+
└── <module>.module.ts # dependencies, repositories, and exports
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Controllers do not access repositories directly. Every operation goes through the service, keeping business rules centralized and testable.
|
|
33
|
+
|
|
34
|
+
Modules register their entities with:
|
|
35
|
+
|
|
36
|
+
TypeOrmModule.forFeature([
|
|
37
|
+
UserEntity,
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
Services receive repositories with:
|
|
41
|
+
|
|
42
|
+
@InjectRepository(UserEntity) private readonly usersRepository: Repository<UserEntity>
|
|
43
|
+
|
|
44
|
+
The global connection is located in src/database/database.module.ts. PostgreSQL-, MySQL-, and SQLite-specific options are located in src/database/typeorm-options.ts.
|
|
45
|
+
|
|
46
|
+
## Design decisions
|
|
47
|
+
|
|
48
|
+
### Why Zod instead of class-validator?
|
|
49
|
+
|
|
50
|
+
With Zod, the schema is the primary source for validation and documentation. nestjs-zod transforms schemas into DTOs, while patchNestJsSwagger() allows Swagger to interpret these schemas.
|
|
51
|
+
|
|
52
|
+
This reduces duplication between validation and documentation decorators.
|
|
53
|
+
|
|
54
|
+
### Why use TypeORM repositories directly?
|
|
55
|
+
|
|
56
|
+
Repository<Entity> already provides a testable, typed persistence abstraction. Creating another generic repository layer on top would add indirection without benefiting this starter.
|
|
57
|
+
|
|
58
|
+
Unit tests replace repositories with objects containing vi.fn(), without starting a database or the complete Nest application.
|
|
59
|
+
|
|
60
|
+
An additional layer can be created later if the project requires complex persistence rules or multiple data sources.
|
|
61
|
+
|
|
62
|
+
### Why is synchronize disabled?
|
|
63
|
+
|
|
64
|
+
The template uses:
|
|
65
|
+
|
|
66
|
+
synchronize: false
|
|
67
|
+
|
|
68
|
+
Database changes must go through versioned migrations. This prevents automatic and potentially destructive schema changes, especially in production.
|
|
69
|
+
|
|
70
|
+
Migrations are generated and run with:
|
|
71
|
+
|
|
72
|
+
npm run migration:generate -- src/database/migrations/MigrationName
|
|
73
|
+
npm run migration:run
|
|
74
|
+
|
|
75
|
+
### Why are permissions a map in code?
|
|
76
|
+
|
|
77
|
+
The ROLE_PERMISSIONS map is suitable for projects with a few fixed roles and makes permissions easy to audit.
|
|
78
|
+
|
|
79
|
+
If the project needs dynamically created roles, the map can be replaced with entities such as Role, Permission, and RolePermission.
|
|
80
|
+
|
|
81
|
+
### Why BullMQ for email delivery?
|
|
82
|
+
|
|
83
|
+
SMTP is an external operation that can fail or take time. Putting delivery in a queue allows the request to respond after the work is queued, while MailProcessor handles delivery and subsequent retries.
|
|
84
|
+
|
|
85
|
+
### Why store refresh token hashes?
|
|
86
|
+
|
|
87
|
+
A JWT cannot be revoked before it expires. Storing the hash enables:
|
|
88
|
+
|
|
89
|
+
- logout;
|
|
90
|
+
- refresh token rotation;
|
|
91
|
+
- invalidation after a password change;
|
|
92
|
+
- prevention of revoked token reuse.
|
|
93
|
+
|
|
94
|
+
The original token is not persisted.
|
|
95
|
+
|
|
96
|
+
### Why does UserEntity use @Exclude()?
|
|
97
|
+
|
|
98
|
+
The repository needs to access passwordHash during operations such as login, but this field must never appear in an HTTP response.
|
|
99
|
+
|
|
100
|
+
@Exclude() combined with ClassSerializerInterceptor creates a serialization barrier that prevents the hash from leaking.
|
|
101
|
+
|
|
102
|
+
### Why does Session/Cookies use persistent storage?
|
|
103
|
+
|
|
104
|
+
The Session/Cookies strategy uses express-session with connect-typeorm. Sessions are stored in the sessions table instead of process memory.
|
|
105
|
+
|
|
106
|
+
This allows the application to restart or scale without losing all active sessions.
|
|
107
|
+
|
|
108
|
+
### How does CSRF protection work?
|
|
109
|
+
|
|
110
|
+
With Session/Cookies, the application uses a CSRF token associated with the session. Requests that change state must send this token through the header:
|
|
111
|
+
|
|
112
|
+
x-csrf-token
|
|
113
|
+
|
|
114
|
+
The middleware compares the received token with the token stored in the session.
|
|
115
|
+
|
|
116
|
+
With JWT and a Bearer token, the browser does not automatically send the credential in a cookie, so this CSRF flow is not required.
|
|
117
|
+
|
|
118
|
+
## Authentication strategies
|
|
119
|
+
|
|
120
|
+
### JWT
|
|
121
|
+
|
|
122
|
+
1. Registration or login validates the user.
|
|
123
|
+
2. TokenService issues access and refresh tokens.
|
|
124
|
+
3. The refresh token hash is stored in the database.
|
|
125
|
+
4. JwtAuthGuard validates the Bearer token.
|
|
126
|
+
5. Refresh revokes the previous token and issues a new pair.
|
|
127
|
+
6. Logout revokes the refresh token.
|
|
128
|
+
|
|
129
|
+
### Session/Cookies
|
|
130
|
+
|
|
131
|
+
1. Registration or login validates the user.
|
|
132
|
+
2. The session is regenerated to prevent session fixation.
|
|
133
|
+
3. The user and CSRF token are stored in the session.
|
|
134
|
+
4. The browser receives the nestforge.sid cookie.
|
|
135
|
+
5. SessionAuthGuard protects the routes.
|
|
136
|
+
6. Logout destroys the session.
|
|
137
|
+
|
|
138
|
+
### OAuth
|
|
139
|
+
|
|
140
|
+
Google and GitHub are linked through OAuthAccountEntity. If the email has not been registered yet, a user is created and associated with the provider.
|
|
141
|
+
|
|
142
|
+
## Database and entities
|
|
143
|
+
|
|
144
|
+
The main entities are:
|
|
145
|
+
|
|
146
|
+
- UserEntity;
|
|
147
|
+
- OAuthAccountEntity;
|
|
148
|
+
- RefreshTokenEntity, when tokens are enabled;
|
|
149
|
+
- SessionEntity, when Session/Cookies is enabled;
|
|
150
|
+
- password recovery and email verification entities, when applicable.
|
|
151
|
+
|
|
152
|
+
Conditional entities use generator markers so that only the required files and relationships remain in the final project.
|
|
153
|
+
|
|
154
|
+
## Where to add a module
|
|
155
|
+
|
|
156
|
+
To add a new domain, see docs/adding-a-module.md (docs/adding-a-module.md).
|
|
157
|
+
|
|
158
|
+
---
|