nestforge-generator 0.3.0 → 0.4.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/README.md +13 -1
- package/README.pt-BR.md +13 -1
- package/dist/features/auth-strategy.js +7 -7
- package/dist/features/dependencies.js +3 -3
- package/dist/features/language.js +1 -1
- package/dist/features/language.js.map +1 -1
- package/dist/features/markers.js +11 -11
- package/dist/features/markers.js.map +1 -1
- package/dist/generator.js +7 -1
- package/dist/generator.js.map +1 -1
- package/dist/project-name.js +50 -0
- package/dist/project-name.js.map +1 -0
- package/dist/prompts.js +12 -10
- package/dist/prompts.js.map +1 -1
- package/package.json +1 -1
- package/templates/drizzle/drizzle.config.ts +2 -2
- package/templates/drizzle/src/auth/auth.controller.ts +35 -35
- package/templates/drizzle/src/auth/auth.service.spec.ts +4 -4
- package/templates/drizzle/src/auth/auth.service.ts +9 -9
- package/templates/drizzle/src/auth/dto/forgot-password.dto.ts +2 -2
- package/templates/drizzle/src/auth/dto/login.dto.ts +3 -3
- package/templates/drizzle/src/auth/dto/register.dto.ts +4 -4
- package/templates/drizzle/src/auth/dto/reset-password.dto.ts +3 -3
- package/templates/drizzle/src/auth/guards/github-auth.guard.ts +2 -2
- package/templates/drizzle/src/auth/guards/google-auth.guard.ts +2 -2
- package/templates/drizzle/src/auth/guards/session-auth.guard.spec.ts +4 -4
- package/templates/drizzle/src/auth/guards/session-auth.guard.ts +2 -2
- package/templates/drizzle/src/auth/session.service.spec.ts +5 -5
- package/templates/drizzle/src/auth/session.service.ts +5 -5
- package/templates/drizzle/src/auth/strategies/github.strategy.ts +3 -3
- package/templates/drizzle/src/auth/token.service.ts +3 -3
- package/templates/drizzle/src/common/filters/http-exception.filter.ts +1 -1
- package/templates/drizzle/src/common/guards/permissions.guard.spec.ts +7 -7
- package/templates/drizzle/src/common/guards/roles.guard.spec.ts +4 -4
- package/templates/drizzle/src/common/middleware/csrf.middleware.spec.ts +5 -5
- package/templates/drizzle/src/common/middleware/csrf.middleware.ts +2 -2
- package/templates/drizzle/src/common/utils/avatar-storage.util.ts +2 -2
- package/templates/drizzle/src/config/env.validation.ts +1 -1
- package/templates/drizzle/src/database/seed.ts +4 -4
- package/templates/drizzle/src/health/health.controller.ts +2 -2
- package/templates/drizzle/src/health/indicators/drizzle-health.indicator.spec.ts +5 -5
- package/templates/drizzle/src/health/indicators/drizzle-health.indicator.ts +2 -2
- package/templates/drizzle/src/health/indicators/redis-health.indicator.spec.ts +5 -5
- package/templates/drizzle/src/health/indicators/redis-health.indicator.ts +3 -3
- package/templates/drizzle/src/mail/mail.processor.ts +4 -4
- package/templates/drizzle/src/mail/templates/email-templates.ts +7 -7
- package/templates/drizzle/src/metrics/metrics.service.ts +3 -3
- package/templates/drizzle/src/users/dto/create-user.dto.ts +5 -5
- package/templates/drizzle/src/users/dto/find-users-query.dto.ts +3 -3
- package/templates/drizzle/src/users/users.controller.ts +21 -21
- package/templates/drizzle/src/users/users.service.spec.ts +12 -12
- package/templates/drizzle/src/users/users.service.ts +4 -4
- package/templates/drizzle/test/auth.e2e-spec.ts +8 -8
- package/templates/drizzle/test/session-auth.e2e-spec.ts +9 -9
- package/templates/drizzle/test/users.e2e-spec.ts +18 -18
- package/templates/prisma/prisma/seed.ts +46 -46
- package/templates/prisma/src/auth/auth.controller.ts +35 -35
- package/templates/prisma/src/auth/auth.service.spec.ts +3 -3
- package/templates/prisma/src/auth/auth.service.ts +11 -11
- package/templates/prisma/src/auth/dto/forgot-password.dto.ts +2 -2
- package/templates/prisma/src/auth/dto/login.dto.ts +3 -3
- package/templates/prisma/src/auth/dto/register.dto.ts +4 -4
- package/templates/prisma/src/auth/dto/reset-password.dto.ts +3 -3
- package/templates/prisma/src/auth/guards/github-auth.guard.ts +10 -10
- package/templates/prisma/src/auth/guards/google-auth.guard.ts +10 -10
- package/templates/prisma/src/auth/guards/session-auth.guard.spec.ts +4 -4
- package/templates/prisma/src/auth/guards/session-auth.guard.ts +2 -2
- package/templates/prisma/src/auth/session.service.spec.ts +5 -5
- package/templates/prisma/src/auth/session.service.ts +5 -5
- package/templates/prisma/src/auth/strategies/github.strategy.ts +3 -3
- package/templates/prisma/src/auth/token.service.ts +2 -2
- package/templates/prisma/src/common/filters/http-exception.filter.ts +34 -34
- package/templates/prisma/src/common/guards/permissions.guard.spec.ts +7 -7
- package/templates/prisma/src/common/guards/roles.guard.spec.ts +4 -4
- package/templates/prisma/src/common/middleware/csrf.middleware.spec.ts +5 -5
- package/templates/prisma/src/common/middleware/csrf.middleware.ts +2 -2
- package/templates/prisma/src/common/utils/avatar-storage.util.ts +32 -32
- package/templates/prisma/src/config/env.validation.ts +1 -1
- package/templates/prisma/src/health/health.controller.ts +2 -2
- package/templates/prisma/src/health/indicators/prisma-health.indicator.spec.ts +4 -4
- package/templates/prisma/src/health/indicators/prisma-health.indicator.ts +1 -1
- package/templates/prisma/src/health/indicators/redis-health.indicator.spec.ts +5 -5
- package/templates/prisma/src/health/indicators/redis-health.indicator.ts +3 -3
- package/templates/prisma/src/mail/mail.processor.ts +4 -4
- package/templates/prisma/src/mail/templates/email-templates.ts +7 -7
- package/templates/prisma/src/metrics/metrics.service.ts +34 -34
- package/templates/prisma/src/users/dto/create-user.dto.ts +12 -12
- package/templates/prisma/src/users/dto/find-users-query.dto.ts +12 -12
- package/templates/prisma/src/users/users.controller.ts +21 -21
- package/templates/prisma/src/users/users.service.spec.ts +11 -11
- package/templates/prisma/src/users/users.service.ts +4 -4
- package/templates/prisma/test/auth.e2e-spec.ts +8 -8
- package/templates/prisma/test/session-auth.e2e-spec.ts +9 -9
- package/templates/prisma/test/users.e2e-spec.ts +14 -14
- package/templates/typeorm/src/auth/auth.controller.ts +35 -35
- package/templates/typeorm/src/auth/auth.service.spec.ts +4 -4
- package/templates/typeorm/src/auth/auth.service.ts +9 -9
- package/templates/typeorm/src/auth/dto/forgot-password.dto.ts +2 -2
- package/templates/typeorm/src/auth/dto/login.dto.ts +3 -3
- package/templates/typeorm/src/auth/dto/register.dto.ts +4 -4
- package/templates/typeorm/src/auth/dto/reset-password.dto.ts +3 -3
- package/templates/typeorm/src/auth/guards/github-auth.guard.ts +2 -2
- package/templates/typeorm/src/auth/guards/google-auth.guard.ts +2 -2
- package/templates/typeorm/src/auth/guards/session-auth.guard.spec.ts +4 -4
- package/templates/typeorm/src/auth/guards/session-auth.guard.ts +2 -2
- package/templates/typeorm/src/auth/session.service.spec.ts +5 -5
- package/templates/typeorm/src/auth/session.service.ts +5 -5
- package/templates/typeorm/src/auth/strategies/github.strategy.ts +3 -3
- package/templates/typeorm/src/auth/token.service.ts +3 -3
- package/templates/typeorm/src/common/filters/http-exception.filter.ts +1 -1
- package/templates/typeorm/src/common/guards/permissions.guard.spec.ts +7 -7
- package/templates/typeorm/src/common/guards/roles.guard.spec.ts +4 -4
- package/templates/typeorm/src/common/middleware/csrf.middleware.spec.ts +5 -5
- package/templates/typeorm/src/common/middleware/csrf.middleware.ts +2 -2
- package/templates/typeorm/src/common/utils/avatar-storage.util.ts +2 -2
- package/templates/typeorm/src/config/env.validation.ts +1 -1
- package/templates/typeorm/src/database/data-source.ts +2 -2
- package/templates/typeorm/src/database/seed.ts +3 -3
- package/templates/typeorm/src/database/typeorm-options.ts +2 -2
- package/templates/typeorm/src/health/health.controller.ts +2 -2
- package/templates/typeorm/src/health/indicators/redis-health.indicator.spec.ts +5 -5
- package/templates/typeorm/src/health/indicators/redis-health.indicator.ts +3 -3
- package/templates/typeorm/src/health/indicators/typeorm-health.indicator.spec.ts +4 -4
- package/templates/typeorm/src/health/indicators/typeorm-health.indicator.ts +2 -2
- package/templates/typeorm/src/mail/mail.processor.ts +4 -4
- package/templates/typeorm/src/mail/templates/email-templates.ts +7 -7
- package/templates/typeorm/src/metrics/metrics.service.ts +3 -3
- package/templates/typeorm/src/users/dto/create-user.dto.ts +5 -5
- package/templates/typeorm/src/users/dto/find-users-query.dto.ts +3 -3
- package/templates/typeorm/src/users/users.controller.ts +21 -21
- package/templates/typeorm/src/users/users.service.spec.ts +12 -12
- package/templates/typeorm/src/users/users.service.ts +4 -4
- package/templates/typeorm/test/auth.e2e-spec.ts +8 -8
- package/templates/typeorm/test/session-auth.e2e-spec.ts +9 -9
- package/templates/typeorm/test/users.e2e-spec.ts +18 -18
|
@@ -27,13 +27,13 @@ export class MailProcessor extends WorkerHost {
|
|
|
27
27
|
|
|
28
28
|
switch (job.name) {
|
|
29
29
|
case MailJob.VerifyEmail:
|
|
30
|
-
await this.send(to, '
|
|
30
|
+
await this.send(to, 'Confirm your email', verifyEmailTemplate(name, token));
|
|
31
31
|
break;
|
|
32
32
|
case MailJob.ResetPassword:
|
|
33
|
-
await this.send(to, '
|
|
33
|
+
await this.send(to, 'Password reset', resetPasswordTemplate(name, token));
|
|
34
34
|
break;
|
|
35
35
|
default:
|
|
36
|
-
this.logger.warn(`
|
|
36
|
+
this.logger.warn(`Unknown email job: ${job.name}`);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -45,4 +45,4 @@ export class MailProcessor extends WorkerHost {
|
|
|
45
45
|
html,
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
|
-
}
|
|
48
|
+
}
|
|
@@ -6,10 +6,10 @@ export function verifyEmailTemplate(name: string, token: string): string {
|
|
|
6
6
|
|
|
7
7
|
return `
|
|
8
8
|
<div style="font-family: sans-serif; line-height: 1.5;">
|
|
9
|
-
<h2>
|
|
10
|
-
<p>
|
|
9
|
+
<h2>Hello, ${name}!</h2>
|
|
10
|
+
<p>Confirm your email by clicking the link below:</p>
|
|
11
11
|
<p><a href="${link}">${link}</a></p>
|
|
12
|
-
<p>
|
|
12
|
+
<p>This link expires in 24 hours.</p>
|
|
13
13
|
</div>
|
|
14
14
|
`;
|
|
15
15
|
}
|
|
@@ -19,10 +19,10 @@ export function resetPasswordTemplate(name: string, token: string): string {
|
|
|
19
19
|
|
|
20
20
|
return `
|
|
21
21
|
<div style="font-family: sans-serif; line-height: 1.5;">
|
|
22
|
-
<h2>
|
|
23
|
-
<p>
|
|
22
|
+
<h2>Hello, ${name}!</h2>
|
|
23
|
+
<p>We received a request to reset your password. Click the link below to continue:</p>
|
|
24
24
|
<p><a href="${link}">${link}</a></p>
|
|
25
|
-
<p>
|
|
25
|
+
<p>If you did not request this, you can ignore this email. The link expires in 1 hour.</p>
|
|
26
26
|
</div>
|
|
27
27
|
`;
|
|
28
|
-
}
|
|
28
|
+
}
|
|
@@ -7,7 +7,7 @@ export class MetricsService {
|
|
|
7
7
|
|
|
8
8
|
readonly httpRequestDuration = new client.Histogram({
|
|
9
9
|
name: 'http_request_duration_seconds',
|
|
10
|
-
help: '
|
|
10
|
+
help: 'HTTP request duration in seconds',
|
|
11
11
|
labelNames: ['method', 'route', 'status_code'],
|
|
12
12
|
buckets: [0.05, 0.1, 0.3, 0.5, 1, 2, 5],
|
|
13
13
|
registers: [this.registry],
|
|
@@ -15,7 +15,7 @@ export class MetricsService {
|
|
|
15
15
|
|
|
16
16
|
readonly httpRequestsTotal = new client.Counter({
|
|
17
17
|
name: 'http_requests_total',
|
|
18
|
-
help: 'Total
|
|
18
|
+
help: 'Total number of HTTP requests received',
|
|
19
19
|
labelNames: ['method', 'route', 'status_code'],
|
|
20
20
|
registers: [this.registry],
|
|
21
21
|
});
|
|
@@ -31,4 +31,4 @@ export class MetricsService {
|
|
|
31
31
|
getContentType(): string {
|
|
32
32
|
return this.registry.contentType;
|
|
33
33
|
}
|
|
34
|
-
}
|
|
34
|
+
}
|
|
@@ -3,10 +3,10 @@ import { createZodDto } from 'nestjs-zod';
|
|
|
3
3
|
import { Role } from '../../common/constants/role.enum';
|
|
4
4
|
|
|
5
5
|
export const createUserSchema = z.object({
|
|
6
|
-
name: z.string().min(2).describe('
|
|
7
|
-
email: z.string().email().describe('
|
|
8
|
-
password: z.string().min(8).describe('
|
|
9
|
-
role: z.nativeEnum(Role).optional().describe('
|
|
6
|
+
name: z.string().min(2).describe('Full name'),
|
|
7
|
+
email: z.string().email().describe('User email'),
|
|
8
|
+
password: z.string().min(8).describe('Password (minimum 8 characters)'),
|
|
9
|
+
role: z.nativeEnum(Role).optional().describe('User role (default: USER)'),
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
-
export class CreateUserDto extends createZodDto(createUserSchema) { }
|
|
12
|
+
export class CreateUserDto extends createZodDto(createUserSchema) { }
|
|
@@ -3,10 +3,10 @@ import { createZodDto } from 'nestjs-zod';
|
|
|
3
3
|
import { Role } from '../../common/constants/role.enum';
|
|
4
4
|
|
|
5
5
|
export const findUsersQuerySchema = z.object({
|
|
6
|
-
page: z.coerce.number().int().min(1).default(1).describe('
|
|
7
|
-
limit: z.coerce.number().int().min(1).max(100).default(10).describe('
|
|
6
|
+
page: z.coerce.number().int().min(1).default(1).describe('Current page'),
|
|
7
|
+
limit: z.coerce.number().int().min(1).max(100).default(10).describe('Items per page (maximum 100)'),
|
|
8
8
|
search: z.string().optional().describe('Busca por nome ou e-mail'),
|
|
9
9
|
role: z.nativeEnum(Role).optional().describe('Filtra por role'),
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
-
export class FindUsersQueryDto extends createZodDto(findUsersQuerySchema) { }
|
|
12
|
+
export class FindUsersQueryDto extends createZodDto(findUsersQuerySchema) { }
|
|
@@ -57,9 +57,9 @@ export class UsersController {
|
|
|
57
57
|
@Permissions(Permission.UserCreate)
|
|
58
58
|
// nestforge:feature:rbac:end
|
|
59
59
|
// nestforge:feature:swagger
|
|
60
|
-
@ApiOperation({ summary: '
|
|
61
|
-
@ApiResponse({ status: 201, description: '
|
|
62
|
-
@ApiResponse({ status: 403, description: '
|
|
60
|
+
@ApiOperation({ summary: 'Creates a user (requires the user:create permission)' })
|
|
61
|
+
@ApiResponse({ status: 201, description: 'User created', schema: { example: USER_EXAMPLE } })
|
|
62
|
+
@ApiResponse({ status: 403, description: 'Missing the user:create permission', schema: { example: FORBIDDEN_EXAMPLE } })
|
|
63
63
|
// nestforge:feature:swagger:end
|
|
64
64
|
create(@Body() dto: CreateUserDto) {
|
|
65
65
|
return this.usersService.create(dto);
|
|
@@ -70,10 +70,10 @@ export class UsersController {
|
|
|
70
70
|
@Permissions(Permission.UserRead)
|
|
71
71
|
// nestforge:feature:rbac:end
|
|
72
72
|
// nestforge:feature:swagger
|
|
73
|
-
@ApiOperation({ summary: '
|
|
73
|
+
@ApiOperation({ summary: 'Lists users with pagination and filters (requires the user:read permission)' })
|
|
74
74
|
@ApiResponse({
|
|
75
75
|
status: 200,
|
|
76
|
-
description: '
|
|
76
|
+
description: 'Paginated list',
|
|
77
77
|
schema: {
|
|
78
78
|
example: {
|
|
79
79
|
data: [USER_EXAMPLE],
|
|
@@ -88,8 +88,8 @@ export class UsersController {
|
|
|
88
88
|
|
|
89
89
|
@Get('me')
|
|
90
90
|
// nestforge:feature:swagger
|
|
91
|
-
@ApiOperation({ summary: '
|
|
92
|
-
@ApiResponse({ status: 200, description: '
|
|
91
|
+
@ApiOperation({ summary: 'Returns the authenticated user' })
|
|
92
|
+
@ApiResponse({ status: 200, description: 'Authenticated user', schema: { example: USER_EXAMPLE } })
|
|
93
93
|
// nestforge:feature:swagger:end
|
|
94
94
|
findMe(@CurrentUser() user: { id: string }) {
|
|
95
95
|
return this.usersService.findOne(user.id);
|
|
@@ -97,17 +97,17 @@ export class UsersController {
|
|
|
97
97
|
|
|
98
98
|
@Post('me/avatar')
|
|
99
99
|
// nestforge:feature:swagger
|
|
100
|
-
@ApiOperation({ summary: '
|
|
100
|
+
@ApiOperation({ summary: 'Uploads an avatar for the authenticated user' })
|
|
101
101
|
@ApiConsumes('multipart/form-data')
|
|
102
102
|
@ApiResponse({
|
|
103
103
|
status: 201,
|
|
104
|
-
description: 'Avatar
|
|
104
|
+
description: 'Avatar updated',
|
|
105
105
|
schema: { example: { ...USER_EXAMPLE, avatarUrl: '/uploads/avatars/f47ac10b.png' } },
|
|
106
106
|
})
|
|
107
107
|
@ApiResponse({
|
|
108
108
|
status: 400,
|
|
109
|
-
description: '
|
|
110
|
-
schema: { example: { statusCode: 400, message: '
|
|
109
|
+
description: 'Unsupported image format or file larger than 2 MB',
|
|
110
|
+
schema: { example: { statusCode: 400, message: 'Unsupported image format (use PNG, JPEG, or WEBP)' } },
|
|
111
111
|
})
|
|
112
112
|
// nestforge:feature:swagger:end
|
|
113
113
|
@UseInterceptors(
|
|
@@ -129,9 +129,9 @@ export class UsersController {
|
|
|
129
129
|
@Permissions(Permission.UserRead)
|
|
130
130
|
// nestforge:feature:rbac:end
|
|
131
131
|
// nestforge:feature:swagger
|
|
132
|
-
@ApiOperation({ summary: '
|
|
133
|
-
@ApiResponse({ status: 200, description: '
|
|
134
|
-
@ApiResponse({ status: 404, description: '
|
|
132
|
+
@ApiOperation({ summary: 'Finds a user by ID (requires the user:read permission)' })
|
|
133
|
+
@ApiResponse({ status: 200, description: 'User found', schema: { example: USER_EXAMPLE } })
|
|
134
|
+
@ApiResponse({ status: 404, description: 'User not found', schema: { example: { statusCode: 404, message: 'User not found' } } })
|
|
135
135
|
// nestforge:feature:swagger:end
|
|
136
136
|
findOne(@Param('id') id: string) {
|
|
137
137
|
return this.usersService.findOne(id);
|
|
@@ -142,9 +142,9 @@ export class UsersController {
|
|
|
142
142
|
@Permissions(Permission.UserUpdate)
|
|
143
143
|
// nestforge:feature:rbac:end
|
|
144
144
|
// nestforge:feature:swagger
|
|
145
|
-
@ApiOperation({ summary: '
|
|
146
|
-
@ApiResponse({ status: 200, description: '
|
|
147
|
-
@ApiResponse({ status: 403, description: '
|
|
145
|
+
@ApiOperation({ summary: 'Updates a user (requires the user:update permission)' })
|
|
146
|
+
@ApiResponse({ status: 200, description: 'User updated', schema: { example: USER_EXAMPLE } })
|
|
147
|
+
@ApiResponse({ status: 403, description: 'Missing the user:update permission', schema: { example: FORBIDDEN_EXAMPLE } })
|
|
148
148
|
// nestforge:feature:swagger:end
|
|
149
149
|
update(@Param('id') id: string, @Body() dto: UpdateUserDto) {
|
|
150
150
|
return this.usersService.update(id, dto);
|
|
@@ -155,11 +155,11 @@ export class UsersController {
|
|
|
155
155
|
@Permissions(Permission.UserDelete)
|
|
156
156
|
// nestforge:feature:rbac:end
|
|
157
157
|
// nestforge:feature:swagger
|
|
158
|
-
@ApiOperation({ summary: '
|
|
159
|
-
@ApiResponse({ status: 200, description: '
|
|
160
|
-
@ApiResponse({ status: 403, description: '
|
|
158
|
+
@ApiOperation({ summary: 'Deletes a user (requires the user:delete permission)' })
|
|
159
|
+
@ApiResponse({ status: 200, description: 'User deleted', schema: { example: { message: 'User deleted successfully' } } })
|
|
160
|
+
@ApiResponse({ status: 403, description: 'Missing the user:delete permission', schema: { example: FORBIDDEN_EXAMPLE } })
|
|
161
161
|
// nestforge:feature:swagger:end
|
|
162
162
|
remove(@Param('id') id: string) {
|
|
163
163
|
return this.usersService.remove(id);
|
|
164
164
|
}
|
|
165
|
-
}
|
|
165
|
+
}
|
|
@@ -81,7 +81,7 @@ describe('UsersService', () => {
|
|
|
81
81
|
);
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
it('
|
|
84
|
+
it('throws ConflictException when the email already exists during creation', async () => {
|
|
85
85
|
database.select.mockReturnValueOnce(
|
|
86
86
|
createSelectBuilder([
|
|
87
87
|
{
|
|
@@ -94,14 +94,14 @@ describe('UsersService', () => {
|
|
|
94
94
|
usersService.create({
|
|
95
95
|
name: 'Jeiel',
|
|
96
96
|
email: 'jeiel@example.com',
|
|
97
|
-
password: '
|
|
97
|
+
password: 'strongPassword123',
|
|
98
98
|
}),
|
|
99
99
|
).rejects.toBeInstanceOf(ConflictException);
|
|
100
100
|
|
|
101
101
|
expect(database.insert).not.toHaveBeenCalled();
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
it('
|
|
104
|
+
it('creates a user and hides passwordHash during serialization', async () => {
|
|
105
105
|
database.select
|
|
106
106
|
.mockReturnValueOnce(createSelectBuilder([]))
|
|
107
107
|
.mockReturnValueOnce(
|
|
@@ -111,7 +111,7 @@ describe('UsersService', () => {
|
|
|
111
111
|
const result = await usersService.create({
|
|
112
112
|
name: 'Jeiel',
|
|
113
113
|
email: 'jeiel@example.com',
|
|
114
|
-
password: '
|
|
114
|
+
password: 'strongPassword123',
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
expect(result.email).toBe(
|
|
@@ -133,7 +133,7 @@ describe('UsersService', () => {
|
|
|
133
133
|
);
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
-
it('
|
|
136
|
+
it('throws NotFoundException when the user does not exist', async () => {
|
|
137
137
|
database.select.mockReturnValueOnce(
|
|
138
138
|
createSelectBuilder([]),
|
|
139
139
|
);
|
|
@@ -143,7 +143,7 @@ describe('UsersService', () => {
|
|
|
143
143
|
).rejects.toBeInstanceOf(NotFoundException);
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
-
it('
|
|
146
|
+
it('returns paginated data from findAll', async () => {
|
|
147
147
|
const usersBuilder = createSelectBuilder([
|
|
148
148
|
mockUser,
|
|
149
149
|
]);
|
|
@@ -179,7 +179,7 @@ describe('UsersService', () => {
|
|
|
179
179
|
);
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
-
it('
|
|
182
|
+
it('applies search and role filters in findAll', async () => {
|
|
183
183
|
const usersBuilder = createSelectBuilder([
|
|
184
184
|
mockUser,
|
|
185
185
|
]);
|
|
@@ -214,7 +214,7 @@ describe('UsersService', () => {
|
|
|
214
214
|
);
|
|
215
215
|
});
|
|
216
216
|
|
|
217
|
-
it('
|
|
217
|
+
it('updates an existing user', async () => {
|
|
218
218
|
const updatedUser = new UserEntity({
|
|
219
219
|
...mockUser,
|
|
220
220
|
name: 'Novo Nome',
|
|
@@ -248,7 +248,7 @@ describe('UsersService', () => {
|
|
|
248
248
|
expect(updateWhere).toHaveBeenCalled();
|
|
249
249
|
});
|
|
250
250
|
|
|
251
|
-
it('
|
|
251
|
+
it('deletes an existing user', async () => {
|
|
252
252
|
database.select.mockReturnValueOnce(
|
|
253
253
|
createSelectBuilder([mockUser]),
|
|
254
254
|
);
|
|
@@ -261,11 +261,11 @@ describe('UsersService', () => {
|
|
|
261
261
|
expect(deleteWhere).toHaveBeenCalled();
|
|
262
262
|
|
|
263
263
|
expect(result).toEqual({
|
|
264
|
-
message: '
|
|
264
|
+
message: 'User deleted successfully',
|
|
265
265
|
});
|
|
266
266
|
});
|
|
267
267
|
|
|
268
|
-
it('
|
|
268
|
+
it('updates a user avatar', async () => {
|
|
269
269
|
const updatedUser = new UserEntity({
|
|
270
270
|
...mockUser,
|
|
271
271
|
avatarUrl: '/uploads/avatars/x.png',
|
|
@@ -298,4 +298,4 @@ describe('UsersService', () => {
|
|
|
298
298
|
|
|
299
299
|
expect(updateWhere).toHaveBeenCalled();
|
|
300
300
|
});
|
|
301
|
-
});
|
|
301
|
+
});
|
|
@@ -44,7 +44,7 @@ export class UsersService {
|
|
|
44
44
|
|
|
45
45
|
if (existing) {
|
|
46
46
|
throw new ConflictException(
|
|
47
|
-
'
|
|
47
|
+
'Email already registered',
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -144,7 +144,7 @@ export class UsersService {
|
|
|
144
144
|
|
|
145
145
|
if (!user) {
|
|
146
146
|
throw new NotFoundException(
|
|
147
|
-
'
|
|
147
|
+
'User not found',
|
|
148
148
|
);
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -190,7 +190,7 @@ export class UsersService {
|
|
|
190
190
|
.where(eq(users.id, id));
|
|
191
191
|
|
|
192
192
|
return {
|
|
193
|
-
message: '
|
|
193
|
+
message: 'User deleted successfully',
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
196
|
|
|
@@ -210,4 +210,4 @@ export class UsersService {
|
|
|
210
210
|
|
|
211
211
|
return this.findOne(id);
|
|
212
212
|
}
|
|
213
|
-
}
|
|
213
|
+
}
|
|
@@ -25,7 +25,7 @@ describe('Auth (e2e)', () => {
|
|
|
25
25
|
await app.close();
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
it('
|
|
28
|
+
it('registers, logs in, refreshes, and revokes the token', async () => {
|
|
29
29
|
const server = app.getHttpServer();
|
|
30
30
|
|
|
31
31
|
const registerResponse = await request(server)
|
|
@@ -33,7 +33,7 @@ describe('Auth (e2e)', () => {
|
|
|
33
33
|
.send({
|
|
34
34
|
name: 'Jeiel',
|
|
35
35
|
email: 'jeiel.e2e@example.com',
|
|
36
|
-
password: '
|
|
36
|
+
password: 'strongPassword123',
|
|
37
37
|
})
|
|
38
38
|
.expect(201);
|
|
39
39
|
|
|
@@ -49,7 +49,7 @@ describe('Auth (e2e)', () => {
|
|
|
49
49
|
.post('/auth/login')
|
|
50
50
|
.send({
|
|
51
51
|
email: 'jeiel.e2e@example.com',
|
|
52
|
-
password: '
|
|
52
|
+
password: 'strongPassword123',
|
|
53
53
|
})
|
|
54
54
|
.expect(200);
|
|
55
55
|
|
|
@@ -82,13 +82,13 @@ describe('Auth (e2e)', () => {
|
|
|
82
82
|
.expect(401);
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
it('
|
|
85
|
+
it('rejects registration with a duplicate email', async () => {
|
|
86
86
|
const server = app.getHttpServer();
|
|
87
87
|
|
|
88
88
|
const payload = {
|
|
89
89
|
name: 'Jeiel',
|
|
90
|
-
email: '
|
|
91
|
-
password: '
|
|
90
|
+
email: 'duplicate.e2e@example.com',
|
|
91
|
+
password: 'strongPassword123',
|
|
92
92
|
};
|
|
93
93
|
|
|
94
94
|
await request(server)
|
|
@@ -102,7 +102,7 @@ describe('Auth (e2e)', () => {
|
|
|
102
102
|
.expect(409);
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
it('
|
|
105
|
+
it('rejects login with invalid credentials', async () => {
|
|
106
106
|
await request(app.getHttpServer())
|
|
107
107
|
.post('/auth/login')
|
|
108
108
|
.send({
|
|
@@ -111,4 +111,4 @@ describe('Auth (e2e)', () => {
|
|
|
111
111
|
})
|
|
112
112
|
.expect(401);
|
|
113
113
|
});
|
|
114
|
-
});
|
|
114
|
+
});
|
|
@@ -28,7 +28,7 @@ describe('Session auth (e2e)', () => {
|
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
it('
|
|
31
|
+
it('registers, authenticates, and closes a CSRF-protected session', async () => {
|
|
32
32
|
const agent = request.agent(app.getHttpServer());
|
|
33
33
|
|
|
34
34
|
await agent
|
|
@@ -43,13 +43,13 @@ describe('Session auth (e2e)', () => {
|
|
|
43
43
|
|
|
44
44
|
expect(csrfToken).toMatch(/^[a-f0-9]{64}$/);
|
|
45
45
|
|
|
46
|
-
//
|
|
46
|
+
// State-changing requests must be rejected without the token.
|
|
47
47
|
await agent
|
|
48
48
|
.post('/auth/register')
|
|
49
49
|
.send({
|
|
50
50
|
name: 'Jeiel',
|
|
51
51
|
email: 'jeiel.session@example.com',
|
|
52
|
-
password: '
|
|
52
|
+
password: 'strongPassword123',
|
|
53
53
|
})
|
|
54
54
|
.expect(403);
|
|
55
55
|
|
|
@@ -59,7 +59,7 @@ describe('Session auth (e2e)', () => {
|
|
|
59
59
|
.send({
|
|
60
60
|
name: 'Jeiel',
|
|
61
61
|
email: 'jeiel.session@example.com',
|
|
62
|
-
password: '
|
|
62
|
+
password: 'strongPassword123',
|
|
63
63
|
})
|
|
64
64
|
.expect(201);
|
|
65
65
|
|
|
@@ -97,7 +97,7 @@ describe('Session auth (e2e)', () => {
|
|
|
97
97
|
.expect(401);
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
-
it('
|
|
100
|
+
it('logs in and refreshes the session CSRF token', async () => {
|
|
101
101
|
const registrationAgent = request.agent(
|
|
102
102
|
app.getHttpServer(),
|
|
103
103
|
);
|
|
@@ -113,9 +113,9 @@ describe('Session auth (e2e)', () => {
|
|
|
113
113
|
.post('/auth/register')
|
|
114
114
|
.set('x-csrf-token', registrationCsrfToken)
|
|
115
115
|
.send({
|
|
116
|
-
name: '
|
|
116
|
+
name: 'Session User',
|
|
117
117
|
email: 'login.session@example.com',
|
|
118
|
-
password: '
|
|
118
|
+
password: 'strongPassword123',
|
|
119
119
|
})
|
|
120
120
|
.expect(201);
|
|
121
121
|
|
|
@@ -141,7 +141,7 @@ describe('Session auth (e2e)', () => {
|
|
|
141
141
|
.set('x-csrf-token', loginCsrfToken)
|
|
142
142
|
.send({
|
|
143
143
|
email: 'login.session@example.com',
|
|
144
|
-
password: '
|
|
144
|
+
password: 'strongPassword123',
|
|
145
145
|
})
|
|
146
146
|
.expect(200);
|
|
147
147
|
|
|
@@ -160,4 +160,4 @@ describe('Session auth (e2e)', () => {
|
|
|
160
160
|
.get('/users/me')
|
|
161
161
|
.expect(200);
|
|
162
162
|
});
|
|
163
|
-
});
|
|
163
|
+
});
|
|
@@ -28,7 +28,7 @@ describe('Users (e2e)', () => {
|
|
|
28
28
|
|
|
29
29
|
await database.insert(users).values({
|
|
30
30
|
id: randomUUID(),
|
|
31
|
-
name: '
|
|
31
|
+
name: 'Test User',
|
|
32
32
|
email,
|
|
33
33
|
passwordHash,
|
|
34
34
|
role,
|
|
@@ -70,22 +70,22 @@ describe('Users (e2e)', () => {
|
|
|
70
70
|
await app.close();
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
it('
|
|
73
|
+
it('rejects access without a token', async () => {
|
|
74
74
|
await request(app.getHttpServer())
|
|
75
75
|
.get('/users')
|
|
76
76
|
.expect(401);
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
it('ADMIN
|
|
79
|
+
it('allows ADMIN to create, list, update, and delete a user', async () => {
|
|
80
80
|
await createUserWithRole(
|
|
81
81
|
'admin.e2e@example.com',
|
|
82
|
-
'
|
|
82
|
+
'strongPassword123',
|
|
83
83
|
Role.ADMIN,
|
|
84
84
|
);
|
|
85
85
|
|
|
86
86
|
const token = await loginAndGetToken(
|
|
87
87
|
'admin.e2e@example.com',
|
|
88
|
-
'
|
|
88
|
+
'strongPassword123',
|
|
89
89
|
);
|
|
90
90
|
|
|
91
91
|
const server = app.getHttpServer();
|
|
@@ -99,9 +99,9 @@ describe('Users (e2e)', () => {
|
|
|
99
99
|
`Bearer ${token}`,
|
|
100
100
|
)
|
|
101
101
|
.send({
|
|
102
|
-
name: '
|
|
103
|
-
email: '
|
|
104
|
-
password: '
|
|
102
|
+
name: 'New User',
|
|
103
|
+
email: 'new.e2e@example.com',
|
|
104
|
+
password: 'strongPassword123',
|
|
105
105
|
})
|
|
106
106
|
.expect(201);
|
|
107
107
|
|
|
@@ -146,16 +146,16 @@ describe('Users (e2e)', () => {
|
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
// nestforge:feature:rbac
|
|
149
|
-
it('USER
|
|
149
|
+
it('allows USER to read but not create users', async () => {
|
|
150
150
|
await createUserWithRole(
|
|
151
151
|
'user.e2e@example.com',
|
|
152
|
-
'
|
|
152
|
+
'strongPassword123',
|
|
153
153
|
Role.USER,
|
|
154
154
|
);
|
|
155
155
|
|
|
156
156
|
const token = await loginAndGetToken(
|
|
157
157
|
'user.e2e@example.com',
|
|
158
|
-
'
|
|
158
|
+
'strongPassword123',
|
|
159
159
|
);
|
|
160
160
|
|
|
161
161
|
const server = app.getHttpServer();
|
|
@@ -175,24 +175,24 @@ describe('Users (e2e)', () => {
|
|
|
175
175
|
`Bearer ${token}`,
|
|
176
176
|
)
|
|
177
177
|
.send({
|
|
178
|
-
name: '
|
|
179
|
-
email: '
|
|
180
|
-
password: '
|
|
178
|
+
name: 'Should Not Be Created',
|
|
179
|
+
email: 'blocked.e2e@example.com',
|
|
180
|
+
password: 'strongPassword123',
|
|
181
181
|
})
|
|
182
182
|
.expect(403);
|
|
183
183
|
});
|
|
184
184
|
// nestforge:feature:rbac:end
|
|
185
185
|
|
|
186
|
-
it('GET /users/me
|
|
186
|
+
it('returns the authenticated user from GET /users/me', async () => {
|
|
187
187
|
await createUserWithRole(
|
|
188
188
|
'me.e2e@example.com',
|
|
189
|
-
'
|
|
189
|
+
'strongPassword123',
|
|
190
190
|
Role.USER,
|
|
191
191
|
);
|
|
192
192
|
|
|
193
193
|
const token = await loginAndGetToken(
|
|
194
194
|
'me.e2e@example.com',
|
|
195
|
-
'
|
|
195
|
+
'strongPassword123',
|
|
196
196
|
);
|
|
197
197
|
|
|
198
198
|
const response = await request(
|
|
@@ -209,4 +209,4 @@ describe('Users (e2e)', () => {
|
|
|
209
209
|
'me.e2e@example.com',
|
|
210
210
|
);
|
|
211
211
|
});
|
|
212
|
-
});
|
|
212
|
+
});
|