@hed-hog/core 0.0.319 → 0.0.322

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.
Files changed (58) hide show
  1. package/dist/core.module.d.ts.map +1 -1
  2. package/dist/core.module.js +3 -0
  3. package/dist/core.module.js.map +1 -1
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/mail-profile/dto/create.dto.d.ts +23 -0
  9. package/dist/mail-profile/dto/create.dto.d.ts.map +1 -0
  10. package/dist/mail-profile/dto/create.dto.js +139 -0
  11. package/dist/mail-profile/dto/create.dto.js.map +1 -0
  12. package/dist/mail-profile/dto/update.dto.d.ts +6 -0
  13. package/dist/mail-profile/dto/update.dto.d.ts.map +1 -0
  14. package/dist/mail-profile/dto/update.dto.js +9 -0
  15. package/dist/mail-profile/dto/update.dto.js.map +1 -0
  16. package/dist/mail-profile/mail-profile.controller.d.ts +94 -0
  17. package/dist/mail-profile/mail-profile.controller.d.ts.map +1 -0
  18. package/dist/mail-profile/mail-profile.controller.js +95 -0
  19. package/dist/mail-profile/mail-profile.controller.js.map +1 -0
  20. package/dist/mail-profile/mail-profile.module.d.ts +3 -0
  21. package/dist/mail-profile/mail-profile.module.d.ts.map +1 -0
  22. package/dist/mail-profile/mail-profile.module.js +31 -0
  23. package/dist/mail-profile/mail-profile.module.js.map +1 -0
  24. package/dist/mail-profile/mail-profile.service.d.ts +109 -0
  25. package/dist/mail-profile/mail-profile.service.d.ts.map +1 -0
  26. package/dist/mail-profile/mail-profile.service.js +278 -0
  27. package/dist/mail-profile/mail-profile.service.js.map +1 -0
  28. package/dist/menu/menu.controller.d.ts +3 -3
  29. package/dist/menu/menu.service.d.ts +3 -3
  30. package/dist/user/user.controller.d.ts +15 -0
  31. package/dist/user/user.controller.d.ts.map +1 -1
  32. package/dist/user/user.controller.js +22 -0
  33. package/dist/user/user.controller.js.map +1 -1
  34. package/dist/user/user.service.d.ts +15 -0
  35. package/dist/user/user.service.d.ts.map +1 -1
  36. package/dist/user/user.service.js +99 -0
  37. package/dist/user/user.service.js.map +1 -1
  38. package/hedhog/data/menu.yaml +35 -3
  39. package/hedhog/data/route.yaml +64 -0
  40. package/hedhog/frontend/app/mail/page.tsx.ejs +5 -0
  41. package/hedhog/frontend/app/mail/profiles/page.tsx.ejs +828 -0
  42. package/hedhog/frontend/app/users/page.tsx.ejs +25 -1383
  43. package/hedhog/frontend/app/users/permissions.tsx.ejs +109 -55
  44. package/hedhog/frontend/app/users/user-access-sheet.tsx.ejs +304 -0
  45. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +1385 -0
  46. package/hedhog/frontend/messages/en.json +91 -0
  47. package/hedhog/frontend/messages/pt.json +91 -0
  48. package/hedhog/table/mail_profile.yaml +75 -0
  49. package/package.json +4 -4
  50. package/src/core.module.ts +3 -0
  51. package/src/index.ts +2 -0
  52. package/src/mail-profile/dto/create.dto.ts +145 -0
  53. package/src/mail-profile/dto/update.dto.ts +4 -0
  54. package/src/mail-profile/mail-profile.controller.ts +56 -0
  55. package/src/mail-profile/mail-profile.module.ts +18 -0
  56. package/src/mail-profile/mail-profile.service.ts +308 -0
  57. package/src/user/user.controller.ts +13 -0
  58. package/src/user/user.service.ts +98 -5
@@ -0,0 +1,308 @@
1
+ import { DeleteDTO } from '@hed-hog/api';
2
+ import { MailService as MailMainService } from '@hed-hog/api-mail';
3
+ import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
4
+ import { PrismaService } from '@hed-hog/api-prisma';
5
+ import {
6
+ BadRequestException,
7
+ Inject,
8
+ Injectable,
9
+ NotFoundException,
10
+ forwardRef,
11
+ } from '@nestjs/common';
12
+ import { MailService } from '../mail/mail.service';
13
+ import { CreateDTO } from './dto/create.dto';
14
+ import { UpdateDTO } from './dto/update.dto';
15
+
16
+ @Injectable()
17
+ export class MailProfileService {
18
+ constructor(
19
+ @Inject(forwardRef(() => PrismaService))
20
+ private readonly prismaService: PrismaService,
21
+ @Inject(forwardRef(() => PaginationService))
22
+ private readonly paginationService: PaginationService,
23
+ @Inject(forwardRef(() => MailMainService))
24
+ private readonly mailMainService: MailMainService,
25
+ @Inject(forwardRef(() => MailService))
26
+ private readonly mailService: MailService,
27
+ ) {}
28
+
29
+ async list(paginationParams: PaginationDTO) {
30
+ const fields = ['slug', 'name', 'provider', 'from_email', 'reply_to_email'];
31
+ const OR = this.prismaService.createInsensitiveSearch(fields, paginationParams);
32
+
33
+ return this.paginationService.paginate(
34
+ this.prismaService.mail_profile,
35
+ paginationParams,
36
+ {
37
+ where: OR.length > 0 ? { OR } : {},
38
+ select: {
39
+ id: true,
40
+ slug: true,
41
+ name: true,
42
+ provider: true,
43
+ from_name: true,
44
+ from_email: true,
45
+ reply_to_name: true,
46
+ reply_to_email: true,
47
+ created_at: true,
48
+ updated_at: true,
49
+ },
50
+ orderBy: [{ updated_at: 'desc' }, { id: 'desc' }],
51
+ },
52
+ );
53
+ }
54
+
55
+ async get(id: number) {
56
+ const profile = await this.prismaService.mail_profile.findUnique({
57
+ where: { id },
58
+ });
59
+
60
+ if (!profile) {
61
+ throw new NotFoundException(`Mail profile with id ${id} not found.`);
62
+ }
63
+
64
+ return profile;
65
+ }
66
+
67
+ async create(data: CreateDTO) {
68
+ await this.ensureSlugIsAvailable(data.slug);
69
+ this.validateProviderFields(data as unknown as Record<string, unknown>);
70
+
71
+ return this.prismaService.mail_profile.create({
72
+ data: this.normalizeData(data as unknown as Record<string, unknown>) as any,
73
+ });
74
+ }
75
+
76
+ async update({ id, data }: { id: number; data: UpdateDTO }) {
77
+ const existing = await this.prismaService.mail_profile.findUnique({
78
+ where: { id },
79
+ });
80
+
81
+ if (!existing) {
82
+ throw new NotFoundException(`Mail profile with id ${id} not found.`);
83
+ }
84
+
85
+ if (data.slug && data.slug !== existing.slug) {
86
+ await this.ensureSlugIsAvailable(data.slug);
87
+ }
88
+
89
+ const merged = {
90
+ ...existing,
91
+ ...data,
92
+ } satisfies Record<string, unknown>;
93
+
94
+ this.validateProviderFields(merged);
95
+
96
+ return this.prismaService.mail_profile.update({
97
+ where: { id },
98
+ data: this.normalizeData(merged) as any,
99
+ });
100
+ }
101
+
102
+ async delete({ ids }: DeleteDTO): Promise<{ count: number }> {
103
+ if (!ids?.length) {
104
+ throw new BadRequestException('You must select at least one item to delete.');
105
+ }
106
+
107
+ return this.prismaService.mail_profile.deleteMany({
108
+ where: {
109
+ id: {
110
+ in: ids,
111
+ },
112
+ },
113
+ });
114
+ }
115
+
116
+ async test(data: CreateDTO) {
117
+ this.validateProviderFields(data as unknown as Record<string, unknown>);
118
+
119
+ const normalized = this.normalizeData(data as unknown as Record<string, unknown>);
120
+ const config = this.buildMailConfig(normalized);
121
+ const destinationEmail = normalized.from_email;
122
+ const sender = normalized.from_name
123
+ ? `${normalized.from_name} <${normalized.from_email}>`
124
+ : normalized.from_email;
125
+ const replyTo = normalized.reply_to_email
126
+ ? normalized.reply_to_name
127
+ ? `${normalized.reply_to_name} <${normalized.reply_to_email}>`
128
+ : normalized.reply_to_email
129
+ : undefined;
130
+
131
+ try {
132
+ this.mailMainService.setConfig(config);
133
+
134
+ await this.mailMainService.send({
135
+ to: destinationEmail,
136
+ from: sender,
137
+ replyTo,
138
+ subject: `[HedHog] Teste do perfil ${normalized.name}`,
139
+ body: `
140
+ <p>Este e-mail confirma que o perfil <strong>${normalized.name}</strong> foi validado com sucesso.</p>
141
+ <p><strong>Slug:</strong> ${normalized.slug}</p>
142
+ <p><strong>Provedor:</strong> ${normalized.provider}</p>
143
+ <p><strong>Remetente:</strong> ${sender}</p>
144
+ `,
145
+ });
146
+
147
+ return {
148
+ success: true,
149
+ destination: destinationEmail,
150
+ };
151
+ } finally {
152
+ await this.mailService.reloadConfig().catch(() => undefined);
153
+ }
154
+ }
155
+
156
+ private async ensureSlugIsAvailable(slug: string) {
157
+ const existing = await this.prismaService.mail_profile.findUnique({
158
+ where: { slug },
159
+ select: { id: true },
160
+ });
161
+
162
+ if (existing) {
163
+ throw new BadRequestException(`Mail profile with slug "${slug}" already exists.`);
164
+ }
165
+ }
166
+
167
+ private validateProviderFields(input: Record<string, unknown>) {
168
+ const provider = String(input.provider ?? '').toUpperCase();
169
+
170
+ if (!provider) {
171
+ throw new BadRequestException('Provider is required.');
172
+ }
173
+
174
+ if (!String(input.from_email ?? '').trim()) {
175
+ throw new BadRequestException('Sender email is required.');
176
+ }
177
+
178
+ switch (provider) {
179
+ case 'SMTP':
180
+ if (!String(input.smtp_host ?? '').trim()) {
181
+ throw new BadRequestException('SMTP host is required.');
182
+ }
183
+ if (!input.smtp_port) {
184
+ throw new BadRequestException('SMTP port is required.');
185
+ }
186
+ if (!String(input.smtp_username ?? '').trim()) {
187
+ throw new BadRequestException('SMTP username is required.');
188
+ }
189
+ if (!String(input.smtp_password ?? '').trim()) {
190
+ throw new BadRequestException('SMTP password is required.');
191
+ }
192
+ break;
193
+ case 'GMAIL':
194
+ if (!String(input.gmail_client_id ?? '').trim()) {
195
+ throw new BadRequestException('Gmail client ID is required.');
196
+ }
197
+ if (!String(input.gmail_client_secret ?? '').trim()) {
198
+ throw new BadRequestException('Gmail client secret is required.');
199
+ }
200
+ if (!String(input.gmail_refresh_token ?? '').trim()) {
201
+ throw new BadRequestException('Gmail refresh token is required.');
202
+ }
203
+ break;
204
+ case 'SES':
205
+ if (!String(input.ses_access_key_id ?? '').trim()) {
206
+ throw new BadRequestException('SES access key ID is required.');
207
+ }
208
+ if (!String(input.ses_secret_access_key ?? '').trim()) {
209
+ throw new BadRequestException('SES secret access key is required.');
210
+ }
211
+ if (!String(input.ses_region ?? '').trim()) {
212
+ throw new BadRequestException('SES region is required.');
213
+ }
214
+ break;
215
+ default:
216
+ throw new BadRequestException(`Invalid provider: ${provider}`);
217
+ }
218
+ }
219
+
220
+ private normalizeData(input: Record<string, unknown>) {
221
+ const provider = String(input.provider ?? '').toUpperCase();
222
+
223
+ return {
224
+ slug: this.normalizeString(input.slug),
225
+ name: this.normalizeString(input.name),
226
+ provider,
227
+ from_name: this.normalizeNullableString(input.from_name),
228
+ from_email: this.normalizeString(input.from_email),
229
+ reply_to_name: this.normalizeNullableString(input.reply_to_name),
230
+ reply_to_email: this.normalizeNullableString(input.reply_to_email),
231
+ smtp_host: provider === 'SMTP' ? this.normalizeNullableString(input.smtp_host) : null,
232
+ smtp_port:
233
+ provider === 'SMTP' && input.smtp_port !== undefined && input.smtp_port !== null
234
+ ? Number(input.smtp_port)
235
+ : null,
236
+ smtp_secure: provider === 'SMTP' ? Boolean(input.smtp_secure) : false,
237
+ smtp_username:
238
+ provider === 'SMTP' ? this.normalizeNullableString(input.smtp_username) : null,
239
+ smtp_password:
240
+ provider === 'SMTP' ? this.normalizeNullableString(input.smtp_password) : null,
241
+ gmail_client_id:
242
+ provider === 'GMAIL' ? this.normalizeNullableString(input.gmail_client_id) : null,
243
+ gmail_client_secret:
244
+ provider === 'GMAIL'
245
+ ? this.normalizeNullableString(input.gmail_client_secret)
246
+ : null,
247
+ gmail_refresh_token:
248
+ provider === 'GMAIL'
249
+ ? this.normalizeNullableString(input.gmail_refresh_token)
250
+ : null,
251
+ ses_access_key_id:
252
+ provider === 'SES' ? this.normalizeNullableString(input.ses_access_key_id) : null,
253
+ ses_secret_access_key:
254
+ provider === 'SES'
255
+ ? this.normalizeNullableString(input.ses_secret_access_key)
256
+ : null,
257
+ ses_region: provider === 'SES' ? this.normalizeNullableString(input.ses_region) : null,
258
+ };
259
+ }
260
+
261
+ private buildMailConfig(input: ReturnType<MailProfileService['normalizeData']>) {
262
+ switch (input.provider) {
263
+ case 'SMTP':
264
+ return {
265
+ global: true,
266
+ type: 'SMTP' as const,
267
+ host: input.smtp_host ?? '',
268
+ port: input.smtp_port ?? 587,
269
+ secure: input.smtp_secure,
270
+ username: input.smtp_username ?? '',
271
+ password: input.smtp_password ?? '',
272
+ };
273
+ case 'GMAIL':
274
+ return {
275
+ global: true,
276
+ type: 'GMAIL' as const,
277
+ clientId: input.gmail_client_id ?? '',
278
+ clientSecret: input.gmail_client_secret ?? '',
279
+ refreshToken: input.gmail_refresh_token ?? '',
280
+ from: input.from_email,
281
+ };
282
+ case 'SES':
283
+ return {
284
+ global: true,
285
+ type: 'SES' as const,
286
+ region: input.ses_region ?? '',
287
+ accessKeyId: input.ses_access_key_id ?? '',
288
+ secretAccessKey: input.ses_secret_access_key ?? '',
289
+ from: input.from_email,
290
+ };
291
+ default:
292
+ throw new BadRequestException(`Invalid provider: ${input.provider}`);
293
+ }
294
+ }
295
+
296
+ private normalizeString(value: unknown) {
297
+ return String(value ?? '').trim();
298
+ }
299
+
300
+ private normalizeNullableString(value: unknown) {
301
+ if (value === undefined || value === null) {
302
+ return null;
303
+ }
304
+
305
+ const normalized = String(value).trim();
306
+ return normalized.length > 0 ? normalized : null;
307
+ }
308
+ }
@@ -113,6 +113,19 @@ export class UserController {
113
113
  return this.userService.getUserRoles(userId);
114
114
  }
115
115
 
116
+ @Get(':userId/menu')
117
+ async getUserMenus(
118
+ @Param('userId', ParseIntPipe) userId: number,
119
+ @Locale() locale: string,
120
+ ) {
121
+ return this.userService.getUserMenus(locale, userId);
122
+ }
123
+
124
+ @Get(':userId/route')
125
+ async getUserRoutes(@Param('userId', ParseIntPipe) userId: number, @Locale() locale: string) {
126
+ return this.userService.getUserRoutes(locale, userId);
127
+ }
128
+
116
129
  @Post(':userId/role/:roleId')
117
130
  async assignRoleToUser(
118
131
  @Param('userId', ParseIntPipe) userId: number,
@@ -2,11 +2,11 @@ import { getLocaleText } from '@hed-hog/api-locale';
2
2
  import { PaginationService } from '@hed-hog/api-pagination';
3
3
  import { PrismaService } from '@hed-hog/api-prisma';
4
4
  import {
5
- BadRequestException,
6
- Inject,
7
- Injectable,
8
- NotFoundException,
9
- forwardRef,
5
+ BadRequestException,
6
+ Inject,
7
+ Injectable,
8
+ NotFoundException,
9
+ forwardRef,
10
10
  } from '@nestjs/common';
11
11
  import { ChallengeService } from '../challenge/challenge.service';
12
12
  import { DeleteDTO } from '../dto/delete.dto';
@@ -372,6 +372,99 @@ export class UserService {
372
372
  return this.mapRolesWithAssignment(allRoles, userRoles);
373
373
  }
374
374
 
375
+ async getUserMenus(locale: string, userId: number) {
376
+ const roleUsers = await this.prismaService.role_user.findMany({
377
+ where: { user_id: userId },
378
+ select: { role_id: true },
379
+ });
380
+ const roleIds = roleUsers.map((ru) => ru.role_id);
381
+
382
+ const menus = await this.prismaService.menu.findMany({
383
+ where: {
384
+ role_menu: {
385
+ some: { role_id: { in: roleIds } },
386
+ },
387
+ },
388
+ include: {
389
+ menu_locale: {
390
+ where: { locale: { code: locale } },
391
+ select: { name: true },
392
+ },
393
+ role_menu: {
394
+ where: { role_id: { in: roleIds } },
395
+ include: {
396
+ role: {
397
+ select: {
398
+ slug: true,
399
+ role_locale: {
400
+ where: { locale: { code: locale } },
401
+ select: { name: true },
402
+ },
403
+ },
404
+ },
405
+ },
406
+ },
407
+ },
408
+ orderBy: { order: 'asc' },
409
+ });
410
+
411
+ return menus.map((menu) => ({
412
+ id: menu.id,
413
+ menuId: (menu as any).menu_id ?? null,
414
+ url: (menu as any).url,
415
+ icon: (menu as any).icon,
416
+ order: (menu as any).order,
417
+ name: (menu as any).menu_locale?.[0]?.name || (menu as any).url || '',
418
+ roles: ((menu as any).role_menu ?? []).map((rm: any) => ({
419
+ name: rm.role?.role_locale?.[0]?.name ?? rm.role?.slug ?? '',
420
+ slug: rm.role?.slug ?? '',
421
+ })),
422
+ }));
423
+ }
424
+
425
+ async getUserRoutes(locale: string, userId: number) {
426
+ const roleUsers = await this.prismaService.role_user.findMany({
427
+ where: { user_id: userId },
428
+ select: { role_id: true },
429
+ });
430
+ const roleIds = roleUsers.map((ru) => ru.role_id);
431
+
432
+ const routes = await this.prismaService.route.findMany({
433
+ where: {
434
+ role_route: {
435
+ some: { role_id: { in: roleIds } },
436
+ },
437
+ },
438
+ include: {
439
+ role_route: {
440
+ where: { role_id: { in: roleIds } },
441
+ include: {
442
+ role: {
443
+ select: {
444
+ slug: true,
445
+ role_locale: {
446
+ where: { locale: { code: locale } },
447
+ select: { name: true },
448
+ },
449
+ },
450
+ },
451
+ },
452
+ },
453
+ },
454
+ orderBy: [{ method: 'asc' }, { url: 'asc' }],
455
+ });
456
+
457
+ return routes.map((route) => ({
458
+ id: route.id,
459
+ url: route.url,
460
+ method: route.method,
461
+ roles: ((route as any).role_route ?? []).map((rr: any) => ({
462
+ name: rr.role?.role_locale?.[0]?.name ?? rr.role?.slug ?? '',
463
+ slug: rr.role?.slug ?? '',
464
+ })),
465
+ }));
466
+ }
467
+
375
468
  async assignRoleToUser(locale: string, userId: number, roleId: number) {
376
469
  await Promise.all([
377
470
  this.validateUserExists(locale, userId),