rl-core-api 0.6.0 → 0.8.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.
Files changed (75) hide show
  1. package/README.md +54 -0
  2. package/dist/core/auth/ownershipScope.util.d.ts +10 -0
  3. package/dist/core/auth/ownershipScope.util.js +14 -0
  4. package/dist/core/auth/permissionScope.util.d.ts +2 -0
  5. package/dist/core/auth/permissionScope.util.js +7 -1
  6. package/dist/core/config/env.schema.d.ts +3 -0
  7. package/dist/core/config/env.schema.js +3 -0
  8. package/dist/core/core.module.js +3 -1
  9. package/dist/core/database/migrations/1710000000000-InitialSchema.js +8 -1
  10. package/dist/core/database/migrations/index.js +0 -2
  11. package/dist/core/database/seeds/seedPermissions.d.ts +20 -1
  12. package/dist/core/database/seeds/seedPermissions.js +31 -38
  13. package/dist/core/events/appEvent.enum.d.ts +6 -0
  14. package/dist/core/events/appEvent.enum.js +1 -0
  15. package/dist/core/health/health.controller.d.ts +4 -1
  16. package/dist/core/health/health.controller.js +9 -3
  17. package/dist/core/query/applyOwnershipScope.util.d.ts +7 -0
  18. package/dist/core/query/applyOwnershipScope.util.js +16 -0
  19. package/dist/core/queue/bullConnection.module.d.ts +2 -0
  20. package/dist/core/queue/bullConnection.module.js +27 -0
  21. package/dist/core/queue/job.interface.d.ts +70 -0
  22. package/dist/core/queue/job.interface.js +2 -0
  23. package/dist/core/queue/job.producer.d.ts +15 -0
  24. package/dist/core/queue/job.producer.js +124 -0
  25. package/dist/core/queue/jobEvents.interface.d.ts +4 -0
  26. package/dist/core/queue/jobEvents.interface.js +4 -0
  27. package/dist/core/queue/jobEvents.service.d.ts +12 -0
  28. package/dist/core/queue/jobEvents.service.js +53 -0
  29. package/dist/core/queue/jobStatus.controller.d.ts +8 -0
  30. package/dist/core/queue/jobStatus.controller.js +44 -0
  31. package/dist/core/queue/jobStatus.response.d.ts +20 -0
  32. package/dist/core/queue/jobStatus.response.js +84 -0
  33. package/dist/core/queue/jobWorkerHost.d.ts +17 -0
  34. package/dist/core/queue/jobWorkerHost.js +72 -0
  35. package/dist/core/queue/queue.constants.d.ts +11 -0
  36. package/dist/core/queue/queue.constants.js +18 -0
  37. package/dist/core/queue/queue.module.d.ts +2 -0
  38. package/dist/core/queue/queue.module.js +38 -0
  39. package/dist/core/queue/queueAdmin.controller.d.ts +11 -0
  40. package/dist/core/queue/queueAdmin.controller.js +93 -0
  41. package/dist/core/queue/queueAdmin.response.d.ts +23 -0
  42. package/dist/core/queue/queueAdmin.response.js +87 -0
  43. package/dist/core/queue/queueAdmin.service.d.ts +15 -0
  44. package/dist/core/queue/queueAdmin.service.js +85 -0
  45. package/dist/core/queue/redisConnection.d.ts +2 -0
  46. package/dist/core/queue/redisConnection.js +11 -0
  47. package/dist/core/websocket/baseGateway.d.ts +1 -0
  48. package/dist/core/websocket/baseGateway.js +3 -0
  49. package/dist/features/audit/audit.module.js +1 -0
  50. package/dist/features/audit/presentation/index.d.ts +1 -0
  51. package/dist/features/audit/presentation/index.js +1 -0
  52. package/dist/features/audit/presentation/userPermissionsChanged.listener.d.ts +7 -0
  53. package/dist/features/audit/presentation/userPermissionsChanged.listener.js +43 -0
  54. package/dist/features/notifications/notifications.module.js +1 -1
  55. package/dist/features/notifications/presentation/domainEvents.listener.d.ts +2 -1
  56. package/dist/features/notifications/presentation/domainEvents.listener.js +19 -0
  57. package/dist/features/rbac/domain/rbac.catalog.js +19 -16
  58. package/dist/features/rbac/domain/rbac.service.d.ts +11 -1
  59. package/dist/features/rbac/domain/rbac.service.js +76 -3
  60. package/dist/features/rbac/domain/teamScope.service.d.ts +1 -0
  61. package/dist/features/rbac/domain/teamScope.service.js +10 -4
  62. package/dist/features/rbac/infra/schema/scope.schema.js +1 -1
  63. package/dist/features/rbac/presentation/commands/createScope.command.js +1 -1
  64. package/dist/features/rbac/presentation/commands/setUserPermissions.command.d.ts +3 -0
  65. package/dist/features/rbac/presentation/commands/setUserPermissions.command.js +24 -0
  66. package/dist/features/rbac/presentation/index.d.ts +1 -0
  67. package/dist/features/rbac/presentation/index.js +1 -0
  68. package/dist/features/rbac/presentation/rbac.controller.d.ts +5 -0
  69. package/dist/features/rbac/presentation/rbac.controller.js +57 -0
  70. package/dist/index.d.ts +17 -3
  71. package/dist/index.js +48 -2
  72. package/dist/rlCore.module.js +8 -0
  73. package/package.json +4 -1
  74. package/dist/core/database/migrations/1787270400000-GroupManager.d.ts +0 -6
  75. package/dist/core/database/migrations/1787270400000-GroupManager.js +0 -26
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PaginatedQueueJobsResponse = exports.QueueJobResponse = exports.QueueCountsResponse = void 0;
13
+ const swagger_1 = require("@nestjs/swagger");
14
+ const paginationMeta_response_1 = require("../utils/paginationMeta.response");
15
+ class QueueCountsResponse {
16
+ }
17
+ exports.QueueCountsResponse = QueueCountsResponse;
18
+ __decorate([
19
+ (0, swagger_1.ApiProperty)({ example: 3 }),
20
+ __metadata("design:type", Number)
21
+ ], QueueCountsResponse.prototype, "waiting", void 0);
22
+ __decorate([
23
+ (0, swagger_1.ApiProperty)({ example: 1 }),
24
+ __metadata("design:type", Number)
25
+ ], QueueCountsResponse.prototype, "active", void 0);
26
+ __decorate([
27
+ (0, swagger_1.ApiProperty)({ example: 128 }),
28
+ __metadata("design:type", Number)
29
+ ], QueueCountsResponse.prototype, "completed", void 0);
30
+ __decorate([
31
+ (0, swagger_1.ApiProperty)({ example: 2 }),
32
+ __metadata("design:type", Number)
33
+ ], QueueCountsResponse.prototype, "failed", void 0);
34
+ __decorate([
35
+ (0, swagger_1.ApiProperty)({ example: 0 }),
36
+ __metadata("design:type", Number)
37
+ ], QueueCountsResponse.prototype, "delayed", void 0);
38
+ class QueueJobResponse {
39
+ }
40
+ exports.QueueJobResponse = QueueJobResponse;
41
+ __decorate([
42
+ (0, swagger_1.ApiProperty)({ example: "42" }),
43
+ __metadata("design:type", String)
44
+ ], QueueJobResponse.prototype, "id", void 0);
45
+ __decorate([
46
+ (0, swagger_1.ApiProperty)({ example: "funkos:import" }),
47
+ __metadata("design:type", String)
48
+ ], QueueJobResponse.prototype, "name", void 0);
49
+ __decorate([
50
+ (0, swagger_1.ApiProperty)({ example: "failed" }),
51
+ __metadata("design:type", String)
52
+ ], QueueJobResponse.prototype, "state", void 0);
53
+ __decorate([
54
+ (0, swagger_1.ApiProperty)({ type: String, format: "uuid", nullable: true }),
55
+ __metadata("design:type", Object)
56
+ ], QueueJobResponse.prototype, "ownerId", void 0);
57
+ __decorate([
58
+ (0, swagger_1.ApiProperty)({ example: 2 }),
59
+ __metadata("design:type", Number)
60
+ ], QueueJobResponse.prototype, "attemptsMade", void 0);
61
+ __decorate([
62
+ (0, swagger_1.ApiProperty)({ type: Number, nullable: true, example: 69 }),
63
+ __metadata("design:type", Object)
64
+ ], QueueJobResponse.prototype, "percent", void 0);
65
+ __decorate([
66
+ (0, swagger_1.ApiProperty)({ type: String, nullable: true }),
67
+ __metadata("design:type", Object)
68
+ ], QueueJobResponse.prototype, "failedReason", void 0);
69
+ __decorate([
70
+ (0, swagger_1.ApiProperty)({ format: "date-time" }),
71
+ __metadata("design:type", String)
72
+ ], QueueJobResponse.prototype, "createdAt", void 0);
73
+ __decorate([
74
+ (0, swagger_1.ApiProperty)({ type: String, format: "date-time", nullable: true }),
75
+ __metadata("design:type", Object)
76
+ ], QueueJobResponse.prototype, "finishedAt", void 0);
77
+ class PaginatedQueueJobsResponse {
78
+ }
79
+ exports.PaginatedQueueJobsResponse = PaginatedQueueJobsResponse;
80
+ __decorate([
81
+ (0, swagger_1.ApiProperty)({ type: QueueJobResponse, isArray: true }),
82
+ __metadata("design:type", Array)
83
+ ], PaginatedQueueJobsResponse.prototype, "items", void 0);
84
+ __decorate([
85
+ (0, swagger_1.ApiProperty)({ type: paginationMeta_response_1.PaginationMetaResponse }),
86
+ __metadata("design:type", paginationMeta_response_1.PaginationMetaResponse)
87
+ ], PaginatedQueueJobsResponse.prototype, "meta", void 0);
@@ -0,0 +1,15 @@
1
+ import { QueueCounts, QueueJobSummary } from "./job.interface";
2
+ import { JobProducer } from "./job.producer";
3
+ import { Paginated } from "../utils/pagination.util";
4
+ export declare const LISTABLE_STATES: readonly ["waiting", "active", "completed", "failed", "delayed"];
5
+ export type ListableState = (typeof LISTABLE_STATES)[number];
6
+ export declare class QueueAdminService {
7
+ private readonly producer;
8
+ constructor(producer: JobProducer);
9
+ counts(): Promise<QueueCounts>;
10
+ list(state: ListableState, page: number, limit: number): Promise<Paginated<QueueJobSummary>>;
11
+ retry(jobId: string): Promise<void>;
12
+ remove(jobId: string): Promise<void>;
13
+ private findJob;
14
+ private toSummary;
15
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.QueueAdminService = exports.LISTABLE_STATES = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const job_producer_1 = require("./job.producer");
15
+ const pagination_util_1 = require("../utils/pagination.util");
16
+ exports.LISTABLE_STATES = [
17
+ "waiting",
18
+ "active",
19
+ "completed",
20
+ "failed",
21
+ "delayed",
22
+ ];
23
+ let QueueAdminService = class QueueAdminService {
24
+ constructor(producer) {
25
+ this.producer = producer;
26
+ }
27
+ async counts() {
28
+ const queue = this.producer.requireQueue();
29
+ const counts = await queue.getJobCounts(...exports.LISTABLE_STATES);
30
+ return {
31
+ waiting: counts.waiting ?? 0,
32
+ active: counts.active ?? 0,
33
+ completed: counts.completed ?? 0,
34
+ failed: counts.failed ?? 0,
35
+ delayed: counts.delayed ?? 0,
36
+ };
37
+ }
38
+ async list(state, page, limit) {
39
+ const queue = this.producer.requireQueue();
40
+ const start = (page - 1) * limit;
41
+ const [jobs, counts] = await Promise.all([
42
+ queue.getJobs([state], start, start + limit - 1),
43
+ queue.getJobCounts(state),
44
+ ]);
45
+ const items = jobs.filter(Boolean).map((job) => this.toSummary(job, state));
46
+ return new pagination_util_1.Paginated(items, counts[state] ?? 0, page, limit);
47
+ }
48
+ async retry(jobId) {
49
+ const job = await this.findJob(jobId);
50
+ await job.retry();
51
+ }
52
+ async remove(jobId) {
53
+ const job = await this.findJob(jobId);
54
+ await job.remove();
55
+ }
56
+ async findJob(jobId) {
57
+ const queue = this.producer.requireQueue();
58
+ const job = await queue.getJob(jobId);
59
+ if (!job) {
60
+ throw new common_1.NotFoundException("Job não encontrado");
61
+ }
62
+ return job;
63
+ }
64
+ toSummary(job, state) {
65
+ const progress = job.progress;
66
+ return {
67
+ id: String(job.id),
68
+ name: job.name,
69
+ state,
70
+ ownerId: job.data?.ownerId ?? null,
71
+ attemptsMade: job.attemptsMade,
72
+ percent: typeof progress === "object" && progress !== null
73
+ ? (progress.percent ?? null)
74
+ : null,
75
+ failedReason: job.failedReason ?? null,
76
+ createdAt: new Date(job.timestamp).toISOString(),
77
+ finishedAt: job.finishedOn ? new Date(job.finishedOn).toISOString() : null,
78
+ };
79
+ }
80
+ };
81
+ exports.QueueAdminService = QueueAdminService;
82
+ exports.QueueAdminService = QueueAdminService = __decorate([
83
+ (0, common_1.Injectable)(),
84
+ __metadata("design:paramtypes", [job_producer_1.JobProducer])
85
+ ], QueueAdminService);
@@ -0,0 +1,2 @@
1
+ import { RedisOptions } from "ioredis";
2
+ export declare const buildRedisOptions: () => RedisOptions;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildRedisOptions = void 0;
4
+ const buildRedisOptions = () => ({
5
+ host: process.env.REDIS_HOST,
6
+ port: Number(process.env.REDIS_PORT) || 6379,
7
+ password: process.env.REDIS_PASSWORD || undefined,
8
+ maxRetriesPerRequest: null,
9
+ enableReadyCheck: false,
10
+ });
11
+ exports.buildRedisOptions = buildRedisOptions;
@@ -17,6 +17,7 @@ export declare abstract class BaseGateway implements OnGatewayConnection, OnGate
17
17
  handleConnection(socket: Socket): Promise<void>;
18
18
  handleDisconnect(socket: Socket): void;
19
19
  requestSessionRefresh(userId: string): void;
20
+ emitToUser<T>(userId: string, event: string, data: T): void;
20
21
  protected emitTo<T>(rooms: string[], event: string, data: T): void;
21
22
  private scheduleExpiry;
22
23
  }
@@ -43,6 +43,9 @@ class BaseGateway {
43
43
  requestSessionRefresh(userId) {
44
44
  this.emitTo([(0, socket_constants_1.userRoom)(userId)], socket_constants_1.SESSION_REFRESH_EVENT, { userId });
45
45
  }
46
+ emitToUser(userId, event, data) {
47
+ this.emitTo([(0, socket_constants_1.userRoom)(userId)], event, data);
48
+ }
46
49
  emitTo(rooms, event, data) {
47
50
  const targets = rooms.filter((room) => room.length > 0);
48
51
  if (targets.length === 0) {
@@ -29,6 +29,7 @@ exports.AuditModule = AuditModule = __decorate([
29
29
  domain_1.AuditService,
30
30
  { provide: core_1.APP_INTERCEPTOR, useClass: presentation_1.AuditInterceptor },
31
31
  presentation_1.HttpErrorListener,
32
+ presentation_1.UserPermissionsChangedListener,
32
33
  presentation_1.UserRolesChangedListener,
33
34
  infra_1.AuditDataSubscriber,
34
35
  ],
@@ -2,4 +2,5 @@ export * from "./audit.controller";
2
2
  export * from "./audit.interceptor";
3
3
  export * from "./decorators/auditable.decorator";
4
4
  export * from "./httpError.listener";
5
+ export * from "./userPermissionsChanged.listener";
5
6
  export * from "./userRolesChanged.listener";
@@ -18,4 +18,5 @@ __exportStar(require("./audit.controller"), exports);
18
18
  __exportStar(require("./audit.interceptor"), exports);
19
19
  __exportStar(require("./decorators/auditable.decorator"), exports);
20
20
  __exportStar(require("./httpError.listener"), exports);
21
+ __exportStar(require("./userPermissionsChanged.listener"), exports);
21
22
  __exportStar(require("./userRolesChanged.listener"), exports);
@@ -0,0 +1,7 @@
1
+ import { UserPermissionsChangedEvent } from "../../../core/events/appEvent.enum";
2
+ import { AuditService } from "../domain/audit.service";
3
+ export declare class UserPermissionsChangedListener {
4
+ private readonly audit;
5
+ constructor(audit: AuditService);
6
+ onPermissionsChanged(event: UserPermissionsChangedEvent): Promise<void>;
7
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.UserPermissionsChangedListener = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const event_emitter_1 = require("@nestjs/event-emitter");
15
+ const appEvent_enum_1 = require("../../../core/events/appEvent.enum");
16
+ const audit_service_1 = require("../domain/audit.service");
17
+ const auditDataChange_schema_1 = require("../infra/schema/auditDataChange.schema");
18
+ const asLabel = (codes) => codes.length ? [...codes].sort().join(", ") : "—";
19
+ let UserPermissionsChangedListener = class UserPermissionsChangedListener {
20
+ constructor(audit) {
21
+ this.audit = audit;
22
+ }
23
+ async onPermissionsChanged(event) {
24
+ await this.audit.recordChange({
25
+ entity: "User",
26
+ entityId: event.userId,
27
+ action: auditDataChange_schema_1.AuditDataAction.UPDATE,
28
+ oldData: { directPermissions: asLabel(event.previousCodes) },
29
+ newData: { directPermissions: asLabel(event.codes) },
30
+ });
31
+ }
32
+ };
33
+ exports.UserPermissionsChangedListener = UserPermissionsChangedListener;
34
+ __decorate([
35
+ (0, event_emitter_1.OnEvent)(appEvent_enum_1.AppEvent.USER_PERMISSIONS_CHANGED),
36
+ __metadata("design:type", Function),
37
+ __metadata("design:paramtypes", [Object]),
38
+ __metadata("design:returntype", Promise)
39
+ ], UserPermissionsChangedListener.prototype, "onPermissionsChanged", null);
40
+ exports.UserPermissionsChangedListener = UserPermissionsChangedListener = __decorate([
41
+ (0, common_1.Injectable)(),
42
+ __metadata("design:paramtypes", [audit_service_1.AuditService])
43
+ ], UserPermissionsChangedListener);
@@ -40,6 +40,6 @@ exports.NotificationsModule = NotificationsModule = __decorate([
40
40
  notifications_publisher_1.NotificationsPublisher,
41
41
  domainEvents_listener_1.DomainEventsListener,
42
42
  ],
43
- exports: [notifications_service_1.NotificationsService, notifications_publisher_1.NotificationsPublisher],
43
+ exports: [notifications_service_1.NotificationsService, notifications_publisher_1.NotificationsPublisher, notifications_gateway_1.NotificationsGateway],
44
44
  })
45
45
  ], NotificationsModule);
@@ -1,4 +1,4 @@
1
- import { SystemCleanupFailedEvent, UserLockedEvent, UserPasswordExpiringEvent, UserPasswordResetRequestedEvent, UserRolesChangedEvent, UserTwoFactorResetEvent } from "../../../core/events/appEvent.enum";
1
+ import { SystemCleanupFailedEvent, UserLockedEvent, UserPasswordExpiringEvent, UserPasswordResetRequestedEvent, UserPermissionsChangedEvent, UserRolesChangedEvent, UserTwoFactorResetEvent } from "../../../core/events/appEvent.enum";
2
2
  import { NotificationsGateway } from "./notifications.gateway";
3
3
  import { NotificationsPublisher } from "./notifications.publisher";
4
4
  export declare class DomainEventsListener {
@@ -9,6 +9,7 @@ export declare class DomainEventsListener {
9
9
  onUserLocked(event: UserLockedEvent): Promise<void>;
10
10
  onTwoFactorReset(event: UserTwoFactorResetEvent): Promise<void>;
11
11
  onRolesChanged(event: UserRolesChangedEvent): Promise<void>;
12
+ onPermissionsChanged(event: UserPermissionsChangedEvent): Promise<void>;
12
13
  onPasswordResetRequested(event: UserPasswordResetRequestedEvent): Promise<void>;
13
14
  onPasswordExpiring(event: UserPasswordExpiringEvent): Promise<void>;
14
15
  onCleanupFailed(event: SystemCleanupFailedEvent): Promise<void>;
@@ -63,6 +63,19 @@ let DomainEventsListener = DomainEventsListener_1 = class DomainEventsListener {
63
63
  this.gateway.requestSessionRefresh(event.userId);
64
64
  });
65
65
  }
66
+ async onPermissionsChanged(event) {
67
+ await this.safePublish(appEvent_enum_1.AppEvent.USER_PERMISSIONS_CHANGED, async () => {
68
+ await this.publisher.publishAndPush({
69
+ title: "Seus acessos foram ajustados",
70
+ body: event.codes.length
71
+ ? "Um administrador ajustou permissões específicas da sua conta. Parte do sistema pode aparecer diferente para você."
72
+ : "Um administrador removeu as permissões específicas da sua conta. Seu acesso volta a ser o dos seus papéis.",
73
+ type: notificationType_enum_1.NotificationType.INFO,
74
+ targets: [{ type: scopeType_enum_1.ScopeType.USER, key: event.userId }],
75
+ });
76
+ this.gateway.requestSessionRefresh(event.userId);
77
+ });
78
+ }
66
79
  async onPasswordResetRequested(event) {
67
80
  await this.safePublish(appEvent_enum_1.AppEvent.USER_PASSWORD_RESET_REQUESTED, () => this.publisher.publishAndPush({
68
81
  title: "Redefinição de senha solicitada",
@@ -118,6 +131,12 @@ __decorate([
118
131
  __metadata("design:paramtypes", [Object]),
119
132
  __metadata("design:returntype", Promise)
120
133
  ], DomainEventsListener.prototype, "onRolesChanged", null);
134
+ __decorate([
135
+ (0, event_emitter_1.OnEvent)(appEvent_enum_1.AppEvent.USER_PERMISSIONS_CHANGED),
136
+ __metadata("design:type", Function),
137
+ __metadata("design:paramtypes", [Object]),
138
+ __metadata("design:returntype", Promise)
139
+ ], DomainEventsListener.prototype, "onPermissionsChanged", null);
121
140
  __decorate([
122
141
  (0, event_emitter_1.OnEvent)(appEvent_enum_1.AppEvent.USER_PASSWORD_RESET_REQUESTED),
123
142
  __metadata("design:type", Function),
@@ -8,31 +8,31 @@ exports.RESOURCES = [
8
8
  { name: "groups", description: "Gestão de grupos" },
9
9
  { name: "audit", description: "Logs e auditoria" },
10
10
  { name: "notifications", description: "Notificações" },
11
- { name: "reports", description: "Relatórios" },
12
- { name: "resources", description: "Catálogo RBACresources" },
13
- { name: "actions", description: "Catálogo RBACactions" },
14
- { name: "scopes", description: "Catálogo RBAC scopes" },
11
+ { name: "resources", description: "Controle de acesso — áreas" },
12
+ { name: "actions", description: "Controle de acesso ações" },
13
+ { name: "scopes", description: "Controle de acesso alcances" },
14
+ { name: "queues", description: "Fila de processamento em segundo plano" },
15
15
  ];
16
16
  exports.ACTIONS = [
17
17
  { name: "create", description: "Criar" },
18
18
  { name: "read", description: "Ler/visualizar" },
19
19
  { name: "update", description: "Atualizar" },
20
20
  { name: "delete", description: "Remover" },
21
- { name: "export", description: "Exportar" },
22
21
  { name: "promote", description: "Alterar papel/promover" },
22
+ { name: "grant", description: "Conceder permissão avulsa, fora de papel" },
23
+ { name: "manage", description: "Operar (reprocessar, remover)" },
23
24
  { name: "deactivate", description: "Ativar/desativar" },
24
25
  { name: "reset-password", description: "Resetar senha de usuário" },
25
26
  { name: "invite", description: "Enviar link de primeiro acesso" },
26
27
  { name: "update-email", description: "Alterar email de usuário" },
27
28
  {
28
29
  name: "read-trail",
29
- description: "Ler trilha de auditoria de dados (old/new data)",
30
+ description: "Ler trilha de auditoria (tela Auditoria)",
30
31
  },
31
32
  ];
32
33
  exports.SCOPES = [
33
34
  { name: "own", description: "Apenas recursos próprios" },
34
35
  { name: "team", description: "Recursos da equipe" },
35
- { name: "department", description: "Recursos do departamento" },
36
36
  { name: "any", description: "Todos os recursos" },
37
37
  ];
38
38
  exports.PERMISSIONS = [
@@ -54,6 +54,10 @@ exports.PERMISSIONS = [
54
54
  code: "users:deactivate:any",
55
55
  description: "Ativar/desativar qualquer usuário",
56
56
  },
57
+ {
58
+ code: "users:grant:any",
59
+ description: "Conceder permissão diretamente a um usuário, fora de papel — restrito a administradores",
60
+ },
57
61
  {
58
62
  code: "users:reset-password:any",
59
63
  description: "Resetar senha de qualquer usuário",
@@ -103,12 +107,6 @@ exports.PERMISSIONS = [
103
107
  code: "audit:read-trail:any",
104
108
  description: "Ler trilha de auditoria de dados (old/new data) — restrito a administradores",
105
109
  },
106
- { code: "reports:read:any", description: "Ler todos os relatórios" },
107
- {
108
- code: "reports:read:department",
109
- description: "Ler relatórios do departamento",
110
- },
111
- { code: "reports:export:any", description: "Exportar todos os relatórios" },
112
110
  {
113
111
  code: "notifications:read:own",
114
112
  description: "Ler e marcar como lidas as próprias notificações",
@@ -147,6 +145,14 @@ exports.PERMISSIONS = [
147
145
  description: "Atualizar scopes do catálogo RBAC",
148
146
  },
149
147
  { code: "scopes:delete:any", description: "Remover scopes do catálogo RBAC" },
148
+ {
149
+ code: "queues:read:any",
150
+ description: "Ver a fila de processamento e os jobs de qualquer usuário",
151
+ },
152
+ {
153
+ code: "queues:manage:any",
154
+ description: "Reprocessar e remover jobs da fila",
155
+ },
150
156
  ];
151
157
  exports.ROLES = [
152
158
  {
@@ -164,8 +170,6 @@ exports.ROLES = [
164
170
  "users:invite:team",
165
171
  "users:reset-password:team",
166
172
  "users:deactivate:team",
167
- "reports:read:department",
168
- "reports:read:any",
169
173
  "audit:read:any",
170
174
  "notifications:read:own",
171
175
  ],
@@ -175,7 +179,6 @@ exports.ROLES = [
175
179
  description: "Somente leitura",
176
180
  permissions: [
177
181
  "users:read:own",
178
- "reports:read:department",
179
182
  "notifications:read:own",
180
183
  ],
181
184
  },
@@ -1,3 +1,4 @@
1
+ import { EventEmitter2 } from "@nestjs/event-emitter";
1
2
  import { ActionRepository } from "../infra/repositories/action.repository";
2
3
  import { GroupRepository } from "../infra/repositories/group.repository";
3
4
  import { PermissionRepository } from "../infra/repositories/permission.repository";
@@ -31,7 +32,8 @@ export declare class RbacService {
31
32
  private readonly roleRepo;
32
33
  private readonly groupRepo;
33
34
  private readonly userRepo;
34
- constructor(resourceRepo: ResourceRepository, actionRepo: ActionRepository, scopeRepo: ScopeRepository, permissionRepo: PermissionRepository, roleRepo: RoleRepository, groupRepo: GroupRepository, userRepo: UserRepository);
35
+ private readonly events;
36
+ constructor(resourceRepo: ResourceRepository, actionRepo: ActionRepository, scopeRepo: ScopeRepository, permissionRepo: PermissionRepository, roleRepo: RoleRepository, groupRepo: GroupRepository, userRepo: UserRepository, events: EventEmitter2);
35
37
  getUserPermissionCodes(userId: string): Promise<string[]>;
36
38
  getUserRoleNames(userId: string): Promise<string[]>;
37
39
  listResources(): Promise<Resource[]>;
@@ -58,6 +60,14 @@ export declare class RbacService {
58
60
  deleteRole(id: string): Promise<void>;
59
61
  addPermissionToRole(roleId: string, permissionId: string, actorPermissions?: string[]): Promise<Role>;
60
62
  removePermissionFromRole(roleId: string, permissionId: string): Promise<Role>;
63
+ listUserDirectPermissions(userId: string): Promise<Permission[]>;
64
+ setUserDirectPermissions(userId: string, permissionIds: string[], actorPermissions?: string[]): Promise<Permission[]>;
65
+ grantDirectPermission(userId: string, permissionId: string, actorPermissions?: string[]): Promise<Permission[]>;
66
+ revokeDirectPermission(userId: string, permissionId: string): Promise<Permission[]>;
67
+ private findUserWithDirectPermissions;
68
+ private replaceDirectPermissions;
69
+ private sameCodes;
70
+ private sortedByCode;
61
71
  listGroups(): Promise<Group[]>;
62
72
  listManagedGroups(managerId: string): Promise<Group[]>;
63
73
  getGroup(id: string): Promise<Group>;
@@ -11,7 +11,9 @@ var __metadata = (this && this.__metadata) || function (k, v) {
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.RbacService = void 0;
13
13
  const common_1 = require("@nestjs/common");
14
+ const event_emitter_1 = require("@nestjs/event-emitter");
14
15
  const typeorm_1 = require("typeorm");
16
+ const appEvent_enum_1 = require("../../../core/events/appEvent.enum");
15
17
  const action_repository_1 = require("../infra/repositories/action.repository");
16
18
  const group_repository_1 = require("../infra/repositories/group.repository");
17
19
  const permission_repository_1 = require("../infra/repositories/permission.repository");
@@ -20,7 +22,7 @@ const role_repository_1 = require("../infra/repositories/role.repository");
20
22
  const scope_repository_1 = require("../infra/repositories/scope.repository");
21
23
  const user_repository_1 = require("../../users/infra/repositories/user.repository");
22
24
  let RbacService = class RbacService {
23
- constructor(resourceRepo, actionRepo, scopeRepo, permissionRepo, roleRepo, groupRepo, userRepo) {
25
+ constructor(resourceRepo, actionRepo, scopeRepo, permissionRepo, roleRepo, groupRepo, userRepo, events) {
24
26
  this.resourceRepo = resourceRepo;
25
27
  this.actionRepo = actionRepo;
26
28
  this.scopeRepo = scopeRepo;
@@ -28,6 +30,7 @@ let RbacService = class RbacService {
28
30
  this.roleRepo = roleRepo;
29
31
  this.groupRepo = groupRepo;
30
32
  this.userRepo = userRepo;
33
+ this.events = events;
31
34
  }
32
35
  async getUserPermissionCodes(userId) {
33
36
  const user = await this.userRepo.findOne({
@@ -184,7 +187,10 @@ let RbacService = class RbacService {
184
187
  await this.scopeRepo.softRemove(scope);
185
188
  }
186
189
  listPermissions() {
187
- return this.permissionRepo.find({ order: { code: "ASC" } });
190
+ return this.permissionRepo.find({
191
+ order: { code: "ASC" },
192
+ relations: { resource: true, action: true, scope: true },
193
+ });
188
194
  }
189
195
  async createPermission(dto) {
190
196
  const exists = await this.permissionRepo.findOne({
@@ -342,6 +348,72 @@ let RbacService = class RbacService {
342
348
  await this.roleRepo.save(role);
343
349
  return this.getRole(roleId);
344
350
  }
351
+ async listUserDirectPermissions(userId) {
352
+ const user = await this.findUserWithDirectPermissions(userId);
353
+ return this.sortedByCode(user.directPermissions ?? []);
354
+ }
355
+ async setUserDirectPermissions(userId, permissionIds, actorPermissions) {
356
+ const user = await this.findUserWithDirectPermissions(userId);
357
+ const permissions = permissionIds.length
358
+ ? await this.permissionRepo.findBy({ id: (0, typeorm_1.In)(permissionIds) })
359
+ : [];
360
+ if (permissions.length !== permissionIds.length) {
361
+ throw new common_1.NotFoundException("Alguma das permissões informadas não existe");
362
+ }
363
+ this.assertGrantableByActor(permissions, actorPermissions);
364
+ return this.replaceDirectPermissions(user, permissions);
365
+ }
366
+ async grantDirectPermission(userId, permissionId, actorPermissions) {
367
+ const user = await this.findUserWithDirectPermissions(userId);
368
+ const permission = await this.permissionRepo.findOneBy({
369
+ id: permissionId,
370
+ });
371
+ if (!permission) {
372
+ throw new common_1.NotFoundException("Permissão não encontrada");
373
+ }
374
+ this.assertGrantableByActor([permission], actorPermissions);
375
+ const current = user.directPermissions ?? [];
376
+ if (current.some((p) => p.id === permissionId)) {
377
+ return this.sortedByCode(current);
378
+ }
379
+ return this.replaceDirectPermissions(user, [...current, permission]);
380
+ }
381
+ async revokeDirectPermission(userId, permissionId) {
382
+ const user = await this.findUserWithDirectPermissions(userId);
383
+ const current = user.directPermissions ?? [];
384
+ return this.replaceDirectPermissions(user, current.filter((p) => p.id !== permissionId));
385
+ }
386
+ async findUserWithDirectPermissions(userId) {
387
+ const user = await this.userRepo.findOne({
388
+ where: { id: userId },
389
+ relations: { directPermissions: true },
390
+ });
391
+ if (!user) {
392
+ throw new common_1.NotFoundException("Usuário não encontrado");
393
+ }
394
+ return user;
395
+ }
396
+ async replaceDirectPermissions(user, permissions) {
397
+ const previousCodes = (user.directPermissions ?? []).map((p) => p.code);
398
+ const codes = permissions.map((p) => p.code);
399
+ user.directPermissions = permissions;
400
+ await this.userRepo.save(user);
401
+ if (!this.sameCodes(previousCodes, codes)) {
402
+ this.events.emit(appEvent_enum_1.AppEvent.USER_PERMISSIONS_CHANGED, {
403
+ userId: user.id,
404
+ codes,
405
+ previousCodes,
406
+ });
407
+ }
408
+ return this.sortedByCode(permissions);
409
+ }
410
+ sameCodes(a, b) {
411
+ const before = new Set(a);
412
+ return a.length === b.length && b.every((code) => before.has(code));
413
+ }
414
+ sortedByCode(permissions) {
415
+ return [...permissions].sort((a, b) => a.code.localeCompare(b.code));
416
+ }
345
417
  listGroups() {
346
418
  return this.groupRepo.find({
347
419
  relations: { roles: true },
@@ -446,5 +518,6 @@ exports.RbacService = RbacService = __decorate([
446
518
  permission_repository_1.PermissionRepository,
447
519
  role_repository_1.RoleRepository,
448
520
  group_repository_1.GroupRepository,
449
- user_repository_1.UserRepository])
521
+ user_repository_1.UserRepository,
522
+ event_emitter_1.EventEmitter2])
450
523
  ], RbacService);
@@ -6,6 +6,7 @@ export declare class TeamScopeService {
6
6
  private readonly rbac;
7
7
  constructor(groupRepo: GroupRepository, rbac: RbacService);
8
8
  reachableUserIds(actor: Actor, action: string): Promise<string[] | null>;
9
+ private withSelf;
9
10
  managedGroupIds(managerId: string): Promise<string[]>;
10
11
  private reachOf;
11
12
  assertCanAct(actor: Actor, targetUserId: string, action: string): Promise<void>;
@@ -23,11 +23,17 @@ let TeamScopeService = class TeamScopeService {
23
23
  if (this.has(actor, action, permissionScope_util_1.SCOPE_ANY)) {
24
24
  return null;
25
25
  }
26
- if (!this.has(actor, action, permissionScope_util_1.SCOPE_TEAM)) {
27
- return [];
26
+ if (this.has(actor, action, permissionScope_util_1.SCOPE_TEAM)) {
27
+ const reach = await this.reachOf(actor.id);
28
+ return this.withSelf(reach.userIds, actor.id);
28
29
  }
29
- const reach = await this.reachOf(actor.id);
30
- return reach.userIds;
30
+ if (this.has(actor, action, permissionScope_util_1.SCOPE_OWN)) {
31
+ return [actor.id];
32
+ }
33
+ return [];
34
+ }
35
+ withSelf(userIds, actorId) {
36
+ return userIds.includes(actorId) ? userIds : [...userIds, actorId];
31
37
  }
32
38
  async managedGroupIds(managerId) {
33
39
  return this.groupRepo.findManagedGroupIds(managerId);
@@ -17,7 +17,7 @@ let Scope = class Scope extends base_schema_1.BaseEntity {
17
17
  };
18
18
  exports.Scope = Scope;
19
19
  __decorate([
20
- (0, swagger_1.ApiProperty)({ description: "own | team | department | any" }),
20
+ (0, swagger_1.ApiProperty)({ description: "own | team | any" }),
21
21
  (0, typeorm_1.Column)({ type: "varchar", length: 100, unique: true }),
22
22
  __metadata("design:type", String)
23
23
  ], Scope.prototype, "name", void 0);