@baruk/core 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.
Files changed (113) hide show
  1. package/README.md +6 -0
  2. package/dist/audit/audit.d.ts +22 -0
  3. package/dist/audit/audit.d.ts.map +1 -0
  4. package/dist/audit/audit.js +28 -0
  5. package/dist/audit/audit.js.map +1 -0
  6. package/dist/audit/repository.d.ts +22 -0
  7. package/dist/audit/repository.d.ts.map +1 -0
  8. package/dist/audit/repository.js +29 -0
  9. package/dist/audit/repository.js.map +1 -0
  10. package/dist/auth/password-reset.d.ts +20 -0
  11. package/dist/auth/password-reset.d.ts.map +1 -0
  12. package/dist/auth/password-reset.js +56 -0
  13. package/dist/auth/password-reset.js.map +1 -0
  14. package/dist/auth/password.d.ts +21 -0
  15. package/dist/auth/password.d.ts.map +1 -0
  16. package/dist/auth/password.js +67 -0
  17. package/dist/auth/password.js.map +1 -0
  18. package/dist/auth/service.d.ts +82 -0
  19. package/dist/auth/service.d.ts.map +1 -0
  20. package/dist/auth/service.js +214 -0
  21. package/dist/auth/service.js.map +1 -0
  22. package/dist/auth/session-token.d.ts +8 -0
  23. package/dist/auth/session-token.d.ts.map +1 -0
  24. package/dist/auth/session-token.js +19 -0
  25. package/dist/auth/session-token.js.map +1 -0
  26. package/dist/clients/repository.d.ts +53 -0
  27. package/dist/clients/repository.d.ts.map +1 -0
  28. package/dist/clients/repository.js +113 -0
  29. package/dist/clients/repository.js.map +1 -0
  30. package/dist/dashboard/repository.d.ts +19 -0
  31. package/dist/dashboard/repository.d.ts.map +1 -0
  32. package/dist/dashboard/repository.js +36 -0
  33. package/dist/dashboard/repository.js.map +1 -0
  34. package/dist/db/client.d.ts +19 -0
  35. package/dist/db/client.d.ts.map +1 -0
  36. package/dist/db/client.js +21 -0
  37. package/dist/db/client.js.map +1 -0
  38. package/dist/db/context.d.ts +27 -0
  39. package/dist/db/context.d.ts.map +1 -0
  40. package/dist/db/context.js +36 -0
  41. package/dist/db/context.js.map +1 -0
  42. package/dist/db/load-env.d.ts +8 -0
  43. package/dist/db/load-env.d.ts.map +1 -0
  44. package/dist/db/load-env.js +44 -0
  45. package/dist/db/load-env.js.map +1 -0
  46. package/dist/db/migrate.d.ts +2 -0
  47. package/dist/db/migrate.d.ts.map +1 -0
  48. package/dist/db/migrate.js +139 -0
  49. package/dist/db/migrate.js.map +1 -0
  50. package/dist/db/seed.d.ts +2 -0
  51. package/dist/db/seed.d.ts.map +1 -0
  52. package/dist/db/seed.js +64 -0
  53. package/dist/db/seed.js.map +1 -0
  54. package/dist/import/clients-import.d.ts +39 -0
  55. package/dist/import/clients-import.d.ts.map +1 -0
  56. package/dist/import/clients-import.js +142 -0
  57. package/dist/import/clients-import.js.map +1 -0
  58. package/dist/index.d.ts +40 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +40 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/mail/resend.d.ts +13 -0
  63. package/dist/mail/resend.d.ts.map +1 -0
  64. package/dist/mail/resend.js +16 -0
  65. package/dist/mail/resend.js.map +1 -0
  66. package/dist/notifications/repository.d.ts +23 -0
  67. package/dist/notifications/repository.d.ts.map +1 -0
  68. package/dist/notifications/repository.js +43 -0
  69. package/dist/notifications/repository.js.map +1 -0
  70. package/dist/portal/repository.d.ts +11 -0
  71. package/dist/portal/repository.d.ts.map +1 -0
  72. package/dist/portal/repository.js +32 -0
  73. package/dist/portal/repository.js.map +1 -0
  74. package/dist/products/catalog.d.ts +14 -0
  75. package/dist/products/catalog.d.ts.map +1 -0
  76. package/dist/products/catalog.js +7 -0
  77. package/dist/products/catalog.js.map +1 -0
  78. package/dist/products/repository.d.ts +22 -0
  79. package/dist/products/repository.d.ts.map +1 -0
  80. package/dist/products/repository.js +56 -0
  81. package/dist/products/repository.js.map +1 -0
  82. package/dist/rbac/authorize.d.ts +18 -0
  83. package/dist/rbac/authorize.d.ts.map +1 -0
  84. package/dist/rbac/authorize.js +35 -0
  85. package/dist/rbac/authorize.js.map +1 -0
  86. package/dist/rbac/catalog.d.ts +21 -0
  87. package/dist/rbac/catalog.d.ts.map +1 -0
  88. package/dist/rbac/catalog.js +59 -0
  89. package/dist/rbac/catalog.js.map +1 -0
  90. package/dist/requests/repository.d.ts +66 -0
  91. package/dist/requests/repository.d.ts.map +1 -0
  92. package/dist/requests/repository.js +106 -0
  93. package/dist/requests/repository.js.map +1 -0
  94. package/dist/tenancy/provisioning.d.ts +25 -0
  95. package/dist/tenancy/provisioning.d.ts.map +1 -0
  96. package/dist/tenancy/provisioning.js +119 -0
  97. package/dist/tenancy/provisioning.js.map +1 -0
  98. package/dist/users/repository.d.ts +19 -0
  99. package/dist/users/repository.d.ts.map +1 -0
  100. package/dist/users/repository.js +78 -0
  101. package/dist/users/repository.js.map +1 -0
  102. package/package.json +41 -0
  103. package/src/db/migrations/0001_extensions.sql +5 -0
  104. package/src/db/migrations/0002_core_identity.sql +78 -0
  105. package/src/db/migrations/0003_rbac.sql +68 -0
  106. package/src/db/migrations/0004_directory.sql +35 -0
  107. package/src/db/migrations/0005_audit.sql +35 -0
  108. package/src/db/migrations/0006_rls_policies.sql +69 -0
  109. package/src/db/migrations/0007_requests_notifications_portal.sql +103 -0
  110. package/src/db/migrations/0008_client_deleted_by.sql +5 -0
  111. package/src/db/migrations/0009_client_scope_rls.sql +50 -0
  112. package/src/db/migrations/0010_product_entitlements.sql +43 -0
  113. package/src/db/migrations/0011_password_reset.sql +18 -0
@@ -0,0 +1,56 @@
1
+ import { uuidv7 } from '@baruk/shared';
2
+ import { withContext } from '../db/context.js';
3
+ import { PRODUCTS } from './catalog.js';
4
+ /**
5
+ * Produtos acessíveis ao usuário: cruza suas memberships ativas com os
6
+ * entitlements (tenant_product) ativos. Rode dentro de `withContext({ userId })`
7
+ * — é assim que o Hub sabe a que produtos a pessoa tem direito. Sem entitlement
8
+ * = sem acesso (bloqueio no Hub).
9
+ */
10
+ export async function listProductsForUser(tx, userId) {
11
+ const rows = await tx `
12
+ SELECT DISTINCT t.id AS tenant_id, t.display_name AS tenant_name, t.code AS tenant_code,
13
+ p.key AS product_key, p.name AS product_name, p.kind, p.base_url
14
+ FROM membership m
15
+ JOIN tenant t ON t.id = m.tenant_id
16
+ JOIN tenant_product tp ON tp.tenant_id = m.tenant_id AND tp.status = 'active'
17
+ JOIN product p ON p.id = tp.product_id AND p.is_active
18
+ WHERE m.user_id = ${userId} AND m.status = 'active' AND m.deleted_at IS NULL
19
+ ORDER BY t.display_name, p.name
20
+ `;
21
+ return rows.map((r) => ({
22
+ tenantId: r.tenant_id,
23
+ tenantName: r.tenant_name,
24
+ tenantCode: r.tenant_code,
25
+ productKey: r.product_key,
26
+ productName: r.product_name,
27
+ kind: r.kind,
28
+ baseUrl: r.base_url,
29
+ }));
30
+ }
31
+ /** Materializa o catálogo global de produtos (idempotente). */
32
+ export async function ensureProductCatalog(db) {
33
+ for (const p of PRODUCTS) {
34
+ await db `
35
+ INSERT INTO product (id, key, name, kind, base_url, is_active)
36
+ VALUES (${uuidv7()}, ${p.key}, ${p.name}, ${p.kind}, ${p.baseUrl ?? null}, ${p.isActive ?? true})
37
+ ON CONFLICT (key) DO UPDATE
38
+ SET name = EXCLUDED.name, kind = EXCLUDED.kind,
39
+ base_url = EXCLUDED.base_url, is_active = EXCLUDED.is_active
40
+ `;
41
+ }
42
+ }
43
+ /** Concede (ou reativa) um produto a um tenant. */
44
+ export async function ensureTenantProduct(db, tenantId, productKey) {
45
+ await withContext(db, { tenantId }, async (tx) => {
46
+ const prod = await tx `SELECT id FROM product WHERE key = ${productKey}`;
47
+ if (!prod[0])
48
+ throw new Error(`Produto desconhecido: ${productKey}`);
49
+ await tx `
50
+ INSERT INTO tenant_product (id, tenant_id, product_id, status)
51
+ VALUES (${uuidv7()}, current_setting('app.current_tenant')::uuid, ${prod[0].id}, 'active')
52
+ ON CONFLICT (tenant_id, product_id) DO UPDATE SET status = 'active'
53
+ `;
54
+ });
55
+ }
56
+ //# sourceMappingURL=repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.js","sourceRoot":"","sources":["../../src/products/repository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAYxC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,EAAM,EAAE,MAAc;IAC9D,MAAM,IAAI,GAAG,MAAM,EAAE,CAUpB;;;;;;;wBAOqB,MAAM;;GAE3B,CAAC;IACF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtB,QAAQ,EAAE,CAAC,CAAC,SAAS;QACrB,UAAU,EAAE,CAAC,CAAC,WAAW;QACzB,UAAU,EAAE,CAAC,CAAC,WAAW;QACzB,UAAU,EAAE,CAAC,CAAC,WAAW;QACzB,WAAW,EAAE,CAAC,CAAC,YAAY;QAC3B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,OAAO,EAAE,CAAC,CAAC,QAAQ;KACpB,CAAC,CAAC,CAAC;AACN,CAAC;AAED,+DAA+D;AAC/D,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,EAAM;IAC/C,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,EAAE,CAAA;;gBAEI,MAAM,EAAE,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,IAAI,IAAI,KAAK,CAAC,CAAC,QAAQ,IAAI,IAAI;;;;KAIhG,CAAC;IACJ,CAAC;AACH,CAAC;AAED,mDAAmD;AACnD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,EAAM,EAAE,QAAgB,EAAE,UAAkB;IACpF,MAAM,WAAW,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QAC/C,MAAM,IAAI,GAAG,MAAM,EAAE,CAAkB,sCAAsC,UAAU,EAAE,CAAC;QAC1F,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,UAAU,EAAE,CAAC,CAAC;QACrE,MAAM,EAAE,CAAA;;gBAEI,MAAM,EAAE,kDAAkD,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;;KAE/E,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { Tx } from '../db/client.js';
2
+ /**
3
+ * Avaliação de permissão (04-rbac §6) — CONVERGIDO sobre @baruk/identity
4
+ * (RFC-IDENTITY-003 §3.4). Decisão sempre no backend; a RLS no banco é a última
5
+ * linha de defesa. `canDo` delega ao avaliador canônico (aceita `Iterable`, logo
6
+ * `ReadonlySet`); `assertPermission` PRESERVA o `ForbiddenError` do domínio — o
7
+ * contrato de erro/HTTP fica intacto (o canônico lançaria um `Error` genérico).
8
+ */
9
+ export declare function canDo(permissions: ReadonlySet<string>, required: string): boolean;
10
+ export declare function assertPermission(permissions: ReadonlySet<string>, required: string): void;
11
+ /**
12
+ * Carrega as permissões efetivas de um usuário DENTRO do tenant corrente.
13
+ * Permanece no core: é I/O de domínio (RLS/tenant), não primitiva canônica.
14
+ * Deve rodar dentro de `withContext({ tenantId, userId })` — a RLS de
15
+ * `user_role`/`role_permission` já restringe as linhas ao tenant ativo.
16
+ */
17
+ export declare function loadUserPermissions(tx: Tx, userId: string): Promise<Set<string>>;
18
+ //# sourceMappingURL=authorize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../src/rbac/authorize.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAE1C;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEjF;AAED,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAIzF;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAUtF"}
@@ -0,0 +1,35 @@
1
+ import { ForbiddenError } from '@baruk/shared';
2
+ import { canDo as canonicalCanDo } from '@baruk/identity';
3
+ /**
4
+ * Avaliação de permissão (04-rbac §6) — CONVERGIDO sobre @baruk/identity
5
+ * (RFC-IDENTITY-003 §3.4). Decisão sempre no backend; a RLS no banco é a última
6
+ * linha de defesa. `canDo` delega ao avaliador canônico (aceita `Iterable`, logo
7
+ * `ReadonlySet`); `assertPermission` PRESERVA o `ForbiddenError` do domínio — o
8
+ * contrato de erro/HTTP fica intacto (o canônico lançaria um `Error` genérico).
9
+ */
10
+ export function canDo(permissions, required) {
11
+ return canonicalCanDo(permissions, required);
12
+ }
13
+ export function assertPermission(permissions, required) {
14
+ if (!canDo(permissions, required)) {
15
+ throw new ForbiddenError(`Permissão necessária: ${required}`);
16
+ }
17
+ }
18
+ /**
19
+ * Carrega as permissões efetivas de um usuário DENTRO do tenant corrente.
20
+ * Permanece no core: é I/O de domínio (RLS/tenant), não primitiva canônica.
21
+ * Deve rodar dentro de `withContext({ tenantId, userId })` — a RLS de
22
+ * `user_role`/`role_permission` já restringe as linhas ao tenant ativo.
23
+ */
24
+ export async function loadUserPermissions(tx, userId) {
25
+ const rows = await tx `
26
+ SELECT DISTINCT p.key
27
+ FROM user_role ur
28
+ JOIN role_permission rp ON rp.role_id = ur.role_id
29
+ JOIN permission p ON p.id = rp.permission_id
30
+ WHERE ur.user_id = ${userId}
31
+ AND ur.deleted_at IS NULL
32
+ `;
33
+ return new Set(rows.map((r) => r.key));
34
+ }
35
+ //# sourceMappingURL=authorize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/rbac/authorize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,KAAK,IAAI,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG1D;;;;;;GAMG;AACH,MAAM,UAAU,KAAK,CAAC,WAAgC,EAAE,QAAgB;IACtE,OAAO,cAAc,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,WAAgC,EAAE,QAAgB;IACjF,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,cAAc,CAAC,yBAAyB,QAAQ,EAAE,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,EAAM,EAAE,MAAc;IAC9D,MAAM,IAAI,GAAG,MAAM,EAAE,CAAmB;;;;;yBAKjB,MAAM;;GAE5B,CAAC;IACF,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Catálogo de permissões e papéis de sistema (04-rbac-permissions.md).
3
+ * É a fonte que o seed materializa no banco. A vertical registra permissões
4
+ * adicionais via seu próprio seed — sem alterar o Core.
5
+ */
6
+ export interface PermissionDef {
7
+ key: string;
8
+ resource: string;
9
+ action: string;
10
+ description: string;
11
+ }
12
+ export declare const PERMISSIONS: readonly PermissionDef[];
13
+ export declare const ALL_PERMISSION_KEYS: readonly string[];
14
+ export interface RoleDef {
15
+ key: string;
16
+ name: string;
17
+ permissions: readonly string[];
18
+ }
19
+ /** Papéis de sistema (seed, imutáveis). São templates globais (tenant_id NULL). */
20
+ export declare const SYSTEM_ROLES: readonly RoleDef[];
21
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/rbac/catalog.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,WAAW,EAAE,SAAS,aAAa,EAmB/C,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAAkC,CAAC;AAEpF,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC;AAID,mFAAmF;AACnF,eAAO,MAAM,YAAY,EAAE,SAAS,OAAO,EA6B1C,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Catálogo de permissões e papéis de sistema (04-rbac-permissions.md).
3
+ * É a fonte que o seed materializa no banco. A vertical registra permissões
4
+ * adicionais via seu próprio seed — sem alterar o Core.
5
+ */
6
+ export const PERMISSIONS = [
7
+ { key: 'client.read', resource: 'client', action: 'read', description: 'Ver clientes' },
8
+ { key: 'client.write', resource: 'client', action: 'write', description: 'Criar/editar clientes' },
9
+ { key: 'client.delete', resource: 'client', action: 'delete', description: 'Remover clientes' },
10
+ { key: 'client.import', resource: 'client', action: 'manage', description: 'Importar clientes (CSV)' },
11
+ { key: 'request.read', resource: 'request', action: 'read', description: 'Ver solicitações' },
12
+ { key: 'request.create', resource: 'request', action: 'write', description: 'Criar solicitações' },
13
+ { key: 'request.comment', resource: 'request', action: 'write', description: 'Comentar solicitações' },
14
+ { key: 'request.status.update', resource: 'request', action: 'manage', description: 'Alterar status de solicitações' },
15
+ { key: 'user.manage', resource: 'user', action: 'manage', description: 'Gerenciar usuários e memberships' },
16
+ { key: 'document.read', resource: 'document', action: 'read', description: 'Ver documentos' },
17
+ { key: 'document.write', resource: 'document', action: 'write', description: 'Enviar documentos' },
18
+ { key: 'finance.charge.create', resource: 'finance', action: 'write', description: 'Emitir cobranças' },
19
+ { key: 'vault.credential.decrypt', resource: 'vault', action: 'manage', description: 'Descriptografar credencial do cofre' },
20
+ { key: 'communication.send', resource: 'communication', action: 'write', description: 'Enviar comunicações' },
21
+ { key: 'audit.read', resource: 'audit', action: 'read', description: 'Ler trilha de auditoria' },
22
+ { key: 'tenant.settings.update', resource: 'tenant', action: 'manage', description: 'Alterar configurações do tenant' },
23
+ { key: 'rbac.role.update', resource: 'rbac', action: 'manage', description: 'Gerenciar papéis e permissões' },
24
+ { key: 'user.invite', resource: 'user', action: 'manage', description: 'Convidar usuários' },
25
+ ];
26
+ export const ALL_PERMISSION_KEYS = PERMISSIONS.map((p) => p.key);
27
+ const ALL = ALL_PERMISSION_KEYS;
28
+ /** Papéis de sistema (seed, imutáveis). São templates globais (tenant_id NULL). */
29
+ export const SYSTEM_ROLES = [
30
+ { key: 'owner', name: 'Owner', permissions: ALL },
31
+ { key: 'admin', name: 'Administrador', permissions: ALL.filter((p) => p !== 'tenant.settings.update') },
32
+ {
33
+ key: 'accountant',
34
+ name: 'Contador',
35
+ permissions: [
36
+ 'client.read', 'client.write', 'client.import', 'document.read', 'document.write',
37
+ 'communication.send', 'vault.credential.decrypt',
38
+ 'request.read', 'request.create', 'request.comment', 'request.status.update',
39
+ ],
40
+ },
41
+ {
42
+ key: 'assistant',
43
+ name: 'Assistente',
44
+ permissions: ['client.read', 'document.read', 'document.write', 'request.read', 'request.comment'],
45
+ },
46
+ { key: 'finance', name: 'Financeiro', permissions: ['client.read', 'finance.charge.create'] },
47
+ {
48
+ key: 'communication',
49
+ name: 'Comunicação',
50
+ permissions: ['client.read', 'communication.send', 'request.read', 'request.comment'],
51
+ },
52
+ { key: 'readonly', name: 'Somente leitura', permissions: ['client.read', 'document.read', 'request.read'] },
53
+ {
54
+ key: 'client_titular',
55
+ name: 'Cliente — Titular',
56
+ permissions: ['document.read', 'communication.send', 'request.read', 'request.comment'],
57
+ },
58
+ ];
59
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/rbac/catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import type { Tx } from '../db/client.js';
2
+ /**
3
+ * Solicitações (service_request) — o núcleo do ciclo operacional escritório↔cliente.
4
+ * Tenant-scoped por RLS. Endpoints de portal restringem adicionalmente por
5
+ * `clientId` (o cliente só vê as próprias solicitações) — passado como filtro.
6
+ */
7
+ export type RequestStatus = 'draft' | 'sent' | 'viewed' | 'awaiting_client' | 'answered' | 'in_review' | 'done' | 'canceled' | 'overdue';
8
+ export type RequestPriority = 'low' | 'medium' | 'high' | 'urgent';
9
+ export interface ServiceRequestRecord {
10
+ id: string;
11
+ tenantId: string;
12
+ clientId: string;
13
+ title: string;
14
+ description: string | null;
15
+ category: string;
16
+ competenceLabel: string | null;
17
+ priority: RequestPriority;
18
+ status: RequestStatus;
19
+ dueDate: string | null;
20
+ responsibleUserId: string | null;
21
+ createdBy: string | null;
22
+ createdAt: string;
23
+ updatedAt: string;
24
+ }
25
+ export interface CreateRequestInput {
26
+ clientId: string;
27
+ title: string;
28
+ description?: string | null;
29
+ category?: string;
30
+ competenceLabel?: string | null;
31
+ priority?: RequestPriority;
32
+ dueDate?: string | null;
33
+ responsibleUserId?: string | null;
34
+ status?: RequestStatus;
35
+ createdBy?: string | null;
36
+ }
37
+ export declare function createRequest(tx: Tx, input: CreateRequestInput): Promise<ServiceRequestRecord>;
38
+ export interface ListRequestFilter {
39
+ clientId?: string;
40
+ status?: RequestStatus;
41
+ search?: string;
42
+ limit?: number;
43
+ offset?: number;
44
+ }
45
+ export declare function listRequests(tx: Tx, filter?: ListRequestFilter): Promise<{
46
+ items: ServiceRequestRecord[];
47
+ total: number;
48
+ }>;
49
+ export declare function getRequest(tx: Tx, id: string, clientId?: string): Promise<ServiceRequestRecord>;
50
+ export declare function updateRequestStatus(tx: Tx, id: string, status: RequestStatus, clientId?: string): Promise<number>;
51
+ export interface CommentRecord {
52
+ id: string;
53
+ requestId: string;
54
+ authorUserId: string | null;
55
+ authorKind: 'staff' | 'client' | 'system';
56
+ body: string;
57
+ createdAt: string;
58
+ }
59
+ export declare function addComment(tx: Tx, input: {
60
+ requestId: string;
61
+ body: string;
62
+ authorUserId?: string | null;
63
+ authorKind?: 'staff' | 'client' | 'system';
64
+ }): Promise<CommentRecord>;
65
+ export declare function listComments(tx: Tx, requestId: string): Promise<CommentRecord[]>;
66
+ //# sourceMappingURL=repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/requests/repository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ import { NotFoundError, uuidv7 } from '@baruk/shared';
2
+ function mapRow(r) {
3
+ return {
4
+ id: r.id,
5
+ tenantId: r.tenant_id,
6
+ clientId: r.client_id,
7
+ title: r.title,
8
+ description: r.description,
9
+ category: r.category,
10
+ competenceLabel: r.competence_label,
11
+ priority: r.priority,
12
+ status: r.status,
13
+ dueDate: r.due_date ? r.due_date.toISOString().slice(0, 10) : null,
14
+ responsibleUserId: r.responsible_user_id,
15
+ createdBy: r.created_by,
16
+ createdAt: r.created_at.toISOString(),
17
+ updatedAt: r.updated_at.toISOString(),
18
+ };
19
+ }
20
+ export async function createRequest(tx, input) {
21
+ const rows = await tx `
22
+ INSERT INTO service_request (
23
+ id, tenant_id, client_id, title, description, category, competence_label,
24
+ priority, status, due_date, responsible_user_id, created_by
25
+ ) VALUES (
26
+ ${uuidv7()}, current_setting('app.current_tenant')::uuid, ${input.clientId},
27
+ ${input.title}, ${input.description ?? null}, ${input.category ?? 'geral'},
28
+ ${input.competenceLabel ?? null}, ${input.priority ?? 'medium'},
29
+ ${input.status ?? 'sent'}, ${input.dueDate ?? null},
30
+ ${input.responsibleUserId ?? null}, ${input.createdBy ?? null}
31
+ )
32
+ RETURNING *
33
+ `;
34
+ return mapRow(rows[0]);
35
+ }
36
+ export async function listRequests(tx, filter = {}) {
37
+ const limit = Math.min(filter.limit ?? 20, 100);
38
+ const offset = filter.offset ?? 0;
39
+ const like = filter.search ? `%${filter.search}%` : null;
40
+ const rows = await tx `
41
+ SELECT sr.*, count(*) OVER() AS total
42
+ FROM service_request sr
43
+ WHERE sr.deleted_at IS NULL
44
+ AND (${filter.clientId ?? null}::uuid IS NULL OR sr.client_id = ${filter.clientId ?? null})
45
+ AND (${filter.status ?? null}::text IS NULL OR sr.status = ${filter.status ?? null})
46
+ AND (${like}::text IS NULL OR sr.title ILIKE ${like} OR sr.description ILIKE ${like})
47
+ ORDER BY sr.created_at DESC
48
+ LIMIT ${limit} OFFSET ${offset}
49
+ `;
50
+ const total = rows[0] ? Number(rows[0].total) : 0;
51
+ return { items: rows.map(mapRow), total };
52
+ }
53
+ export async function getRequest(tx, id, clientId) {
54
+ const rows = await tx `
55
+ SELECT * FROM service_request
56
+ WHERE id = ${id} AND deleted_at IS NULL
57
+ AND (${clientId ?? null}::uuid IS NULL OR client_id = ${clientId ?? null})
58
+ `;
59
+ if (!rows[0])
60
+ throw new NotFoundError('Solicitação não encontrada');
61
+ return mapRow(rows[0]);
62
+ }
63
+ export async function updateRequestStatus(tx, id, status, clientId) {
64
+ const rows = await tx `
65
+ UPDATE service_request
66
+ SET status = ${status}, updated_at = now(), version = version + 1
67
+ WHERE id = ${id} AND deleted_at IS NULL
68
+ AND (${clientId ?? null}::uuid IS NULL OR client_id = ${clientId ?? null})
69
+ RETURNING id
70
+ `;
71
+ return rows.count;
72
+ }
73
+ export async function addComment(tx, input) {
74
+ const rows = await tx `
75
+ INSERT INTO request_comment (id, tenant_id, request_id, author_user_id, author_kind, body)
76
+ VALUES (
77
+ ${uuidv7()}, current_setting('app.current_tenant')::uuid, ${input.requestId},
78
+ ${input.authorUserId ?? null}, ${input.authorKind ?? 'staff'}, ${input.body}
79
+ )
80
+ RETURNING id, request_id, author_user_id, author_kind, body, created_at
81
+ `;
82
+ const r = rows[0];
83
+ return {
84
+ id: r.id,
85
+ requestId: r.request_id,
86
+ authorUserId: r.author_user_id,
87
+ authorKind: r.author_kind,
88
+ body: r.body,
89
+ createdAt: r.created_at.toISOString(),
90
+ };
91
+ }
92
+ export async function listComments(tx, requestId) {
93
+ const rows = await tx `
94
+ SELECT id, request_id, author_user_id, author_kind, body, created_at
95
+ FROM request_comment WHERE request_id = ${requestId} ORDER BY created_at ASC
96
+ `;
97
+ return rows.map((r) => ({
98
+ id: r.id,
99
+ requestId: r.request_id,
100
+ authorUserId: r.author_user_id,
101
+ authorKind: r.author_kind,
102
+ body: r.body,
103
+ createdAt: r.created_at.toISOString(),
104
+ }));
105
+ }
106
+ //# sourceMappingURL=repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.js","sourceRoot":"","sources":["../../src/requests/repository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { type TenantStatus } from '@baruk/shared';
2
+ import type { Db } from '../db/client.js';
3
+ /**
4
+ * Provisionamento de tenancy (01-multi-tenancy §5 — founding pattern). Todas as
5
+ * funções são IDEMPOTENTES: rodar o seed duas vezes não duplica nada. As
6
+ * inserções tenant-scoped derivam `tenant_id` da GUC, satisfazendo a RLS.
7
+ */
8
+ /** Materializa o catálogo global de permissões e os papéis de sistema (templates). */
9
+ export declare function ensureRbacCatalog(db: Db): Promise<void>;
10
+ export interface EnsureTenantInput {
11
+ code: string;
12
+ displayName: string;
13
+ legalName?: string | null;
14
+ status?: TenantStatus;
15
+ primaryProduct?: string;
16
+ }
17
+ /** Cria (ou retorna) o tenant pelo `code`. */
18
+ export declare function ensureTenant(db: Db, input: EnsureTenantInput): Promise<string>;
19
+ /** Cria (ou retorna) a organização/unidade default de um tenant. */
20
+ export declare function ensureOrganization(db: Db, tenantId: string, name: string): Promise<string>;
21
+ /** Vincula um usuário a um tenant (membership ativa). */
22
+ export declare function ensureMembership(db: Db, tenantId: string, userId: string): Promise<string>;
23
+ /** Atribui um papel de sistema (por key) a um usuário dentro de um tenant. */
24
+ export declare function assignSystemRole(db: Db, tenantId: string, userId: string, roleKey: string): Promise<void>;
25
+ //# sourceMappingURL=provisioning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provisioning.d.ts","sourceRoot":"","sources":["../../src/tenancy/provisioning.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAI1C;;;;GAIG;AAEH,sFAAsF;AACtF,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC7D;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,8CAA8C;AAC9C,wBAAsB,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAcpF;AAED,oEAAoE;AACpE,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,CAejB;AAED,yDAAyD;AACzD,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBhG;AAED,8EAA8E;AAC9E,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAkBf"}
@@ -0,0 +1,119 @@
1
+ import { uuidv7 } from '@baruk/shared';
2
+ import { withContext } from '../db/context.js';
3
+ import { PERMISSIONS, SYSTEM_ROLES } from '../rbac/catalog.js';
4
+ /**
5
+ * Provisionamento de tenancy (01-multi-tenancy §5 — founding pattern). Todas as
6
+ * funções são IDEMPOTENTES: rodar o seed duas vezes não duplica nada. As
7
+ * inserções tenant-scoped derivam `tenant_id` da GUC, satisfazendo a RLS.
8
+ */
9
+ /** Materializa o catálogo global de permissões e os papéis de sistema (templates). */
10
+ export async function ensureRbacCatalog(db) {
11
+ for (const p of PERMISSIONS) {
12
+ await db `
13
+ INSERT INTO permission (id, key, resource, action, description)
14
+ VALUES (${uuidv7()}, ${p.key}, ${p.resource}, ${p.action}, ${p.description})
15
+ ON CONFLICT (key) DO NOTHING
16
+ `;
17
+ }
18
+ for (const role of SYSTEM_ROLES) {
19
+ const existing = await db `
20
+ SELECT id FROM role WHERE key = ${role.key} AND tenant_id IS NULL
21
+ `;
22
+ let roleId = existing[0]?.id;
23
+ if (!roleId) {
24
+ const inserted = await db `
25
+ INSERT INTO role (id, tenant_id, key, name, is_system)
26
+ VALUES (${uuidv7()}, NULL, ${role.key}, ${role.name}, true)
27
+ RETURNING id
28
+ `;
29
+ roleId = inserted[0].id;
30
+ }
31
+ for (const permKey of role.permissions) {
32
+ const perm = await db `SELECT id FROM permission WHERE key = ${permKey}`;
33
+ if (!perm[0])
34
+ continue;
35
+ await db `
36
+ INSERT INTO role_permission (role_id, permission_id, tenant_id)
37
+ VALUES (${roleId}, ${perm[0].id}, NULL)
38
+ ON CONFLICT (role_id, permission_id) DO NOTHING
39
+ `;
40
+ }
41
+ }
42
+ }
43
+ /** Cria (ou retorna) o tenant pelo `code`. */
44
+ export async function ensureTenant(db, input) {
45
+ const existing = await db `
46
+ SELECT id FROM tenant WHERE lower(code) = lower(${input.code}) AND deleted_at IS NULL
47
+ `;
48
+ if (existing[0])
49
+ return existing[0].id;
50
+ const rows = await db `
51
+ INSERT INTO tenant (id, code, legal_name, display_name, status, primary_product)
52
+ VALUES (
53
+ ${uuidv7()}, ${input.code}, ${input.legalName ?? null}, ${input.displayName},
54
+ ${input.status ?? 'active'}, ${input.primaryProduct ?? 'contabil'}
55
+ )
56
+ RETURNING id
57
+ `;
58
+ return rows[0].id;
59
+ }
60
+ /** Cria (ou retorna) a organização/unidade default de um tenant. */
61
+ export async function ensureOrganization(db, tenantId, name) {
62
+ return withContext(db, { tenantId }, async (tx) => {
63
+ const existing = await tx `
64
+ SELECT id FROM organization
65
+ WHERE tenant_id = current_setting('app.current_tenant')::uuid
66
+ AND name = ${name} AND deleted_at IS NULL
67
+ `;
68
+ if (existing[0])
69
+ return existing[0].id;
70
+ const rows = await tx `
71
+ INSERT INTO organization (id, tenant_id, name, type)
72
+ VALUES (${uuidv7()}, current_setting('app.current_tenant')::uuid, ${name}, 'headquarters')
73
+ RETURNING id
74
+ `;
75
+ return rows[0].id;
76
+ });
77
+ }
78
+ /** Vincula um usuário a um tenant (membership ativa). */
79
+ export async function ensureMembership(db, tenantId, userId) {
80
+ return withContext(db, { tenantId, userId }, async (tx) => {
81
+ const existing = await tx `
82
+ SELECT id FROM membership
83
+ WHERE user_id = ${userId}
84
+ AND tenant_id = current_setting('app.current_tenant')::uuid
85
+ AND deleted_at IS NULL
86
+ `;
87
+ if (existing[0])
88
+ return existing[0].id;
89
+ const rows = await tx `
90
+ INSERT INTO membership (id, tenant_id, user_id, status)
91
+ VALUES (${uuidv7()}, current_setting('app.current_tenant')::uuid, ${userId}, 'active')
92
+ RETURNING id
93
+ `;
94
+ return rows[0].id;
95
+ });
96
+ }
97
+ /** Atribui um papel de sistema (por key) a um usuário dentro de um tenant. */
98
+ export async function assignSystemRole(db, tenantId, userId, roleKey) {
99
+ await withContext(db, { tenantId, userId }, async (tx) => {
100
+ const role = await tx `
101
+ SELECT id FROM role WHERE key = ${roleKey} AND tenant_id IS NULL
102
+ `;
103
+ if (!role[0])
104
+ throw new Error(`Papel de sistema desconhecido: ${roleKey}`);
105
+ const existing = await tx `
106
+ SELECT id FROM user_role
107
+ WHERE user_id = ${userId} AND role_id = ${role[0].id}
108
+ AND tenant_id = current_setting('app.current_tenant')::uuid
109
+ AND deleted_at IS NULL AND organization_id IS NULL AND client_id IS NULL
110
+ `;
111
+ if (existing[0])
112
+ return;
113
+ await tx `
114
+ INSERT INTO user_role (id, tenant_id, user_id, role_id)
115
+ VALUES (${uuidv7()}, current_setting('app.current_tenant')::uuid, ${userId}, ${role[0].id})
116
+ `;
117
+ });
118
+ }
119
+ //# sourceMappingURL=provisioning.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provisioning.js","sourceRoot":"","sources":["../../src/tenancy/provisioning.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAqB,MAAM,eAAe,CAAC;AAE1D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE/D;;;;GAIG;AAEH,sFAAsF;AACtF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,EAAM;IAC5C,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QAC5B,MAAM,EAAE,CAAA;;gBAEI,MAAM,EAAE,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,WAAW;;KAE3E,CAAC;IACJ,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAkB;wCACP,IAAI,CAAC,GAAG;KAC3C,CAAC;QACF,IAAI,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAkB;;kBAE/B,MAAM,EAAE,WAAW,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,IAAI;;OAEpD,CAAC;YACF,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC;QAC3B,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACvC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAkB,yCAAyC,OAAO,EAAE,CAAC;YAC1F,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,SAAS;YACvB,MAAM,EAAE,CAAA;;kBAEI,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;;OAEhC,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAUD,8CAA8C;AAC9C,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EAAM,EAAE,KAAwB;IACjE,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAkB;sDACS,KAAK,CAAC,IAAI;GAC7D,CAAC;IACF,IAAI,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACvC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAkB;;;QAGjC,MAAM,EAAE,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,SAAS,IAAI,IAAI,KAAK,KAAK,CAAC,WAAW;QACzE,KAAK,CAAC,MAAM,IAAI,QAAQ,KAAK,KAAK,CAAC,cAAc,IAAI,UAAU;;;GAGpE,CAAC;IACF,OAAO,IAAI,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC;AACrB,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,EAAM,EACN,QAAgB,EAChB,IAAY;IAEZ,OAAO,WAAW,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QAChD,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAkB;;;qBAG1B,IAAI;KACpB,CAAC;QACF,IAAI,QAAQ,CAAC,CAAC,CAAC;YAAE,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAkB;;gBAE3B,MAAM,EAAE,kDAAkD,IAAI;;KAEzE,CAAC;QACF,OAAO,IAAI,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EAAM,EAAE,QAAgB,EAAE,MAAc;IAC7E,OAAO,WAAW,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QACxD,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAkB;;wBAEvB,MAAM;;;KAGzB,CAAC;QACF,IAAI,QAAQ,CAAC,CAAC,CAAC;YAAE,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAkB;;gBAE3B,MAAM,EAAE,kDAAkD,MAAM;;KAE3E,CAAC;QACF,OAAO,IAAI,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,EAAM,EACN,QAAgB,EAChB,MAAc,EACd,OAAe;IAEf,MAAM,WAAW,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE;QACvD,MAAM,IAAI,GAAG,MAAM,EAAE,CAAkB;wCACH,OAAO;KAC1C,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,OAAO,EAAE,CAAC,CAAC;QAC3E,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAkB;;wBAEvB,MAAM,kBAAkB,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;;;KAGrD,CAAC;QACF,IAAI,QAAQ,CAAC,CAAC,CAAC;YAAE,OAAO;QACxB,MAAM,EAAE,CAAA;;gBAEI,MAAM,EAAE,kDAAkD,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;KAC1F,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { Tx } from '../db/client.js';
2
+ /**
3
+ * Gestão de usuários e memberships DENTRO do tenant corrente (RLS garante o
4
+ * escopo). Inclui a proteção contra remoção/rebaixamento do último administrador.
5
+ */
6
+ export interface TenantMember {
7
+ userId: string;
8
+ email: string;
9
+ fullName: string;
10
+ userType: string;
11
+ membershipStatus: string;
12
+ roles: string[];
13
+ }
14
+ export declare function listTenantMembers(tx: Tx): Promise<TenantMember[]>;
15
+ /** Substitui o(s) papel(is) do usuário no tenant por um único papel (por key). */
16
+ export declare function setUserRole(tx: Tx, userId: string, roleKey: string): Promise<void>;
17
+ /** Ativa/desativa (ou revoga) a membership. Bloqueia desativar o último admin. */
18
+ export declare function setMembershipStatus(tx: Tx, userId: string, status: 'active' | 'invited' | 'revoked'): Promise<number>;
19
+ //# sourceMappingURL=repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/users/repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAE1C;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAID,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAuBvE;AAeD,kFAAkF;AAClF,wBAAsB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBxF;AAWD,kFAAkF;AAClF,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,EAAE,EACN,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GACvC,OAAO,CAAC,MAAM,CAAC,CAWjB"}