@managemint-solutions/sdk 0.52.0 → 0.54.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 (72) hide show
  1. package/README.md +92 -13
  2. package/dist/additional-details/index.d.ts +2 -2
  3. package/dist/additional-details/index.js +6 -10
  4. package/dist/audit/dto.d.ts +3 -8
  5. package/dist/audit/dto.js +7 -48
  6. package/dist/audit/index.d.ts +71 -10
  7. package/dist/audit/index.js +234 -55
  8. package/dist/auth-client/index.d.ts +2 -0
  9. package/dist/auth-client/index.js +12 -0
  10. package/dist/auth.d.ts +21 -0
  11. package/dist/auth.js +47 -3
  12. package/dist/clients/dto.d.ts +5 -1
  13. package/dist/clients/dto.js +12 -1
  14. package/dist/clients/index.d.ts +8 -4
  15. package/dist/clients/index.js +11 -8
  16. package/dist/emulation-handshakes/index.d.ts +13 -0
  17. package/dist/emulation-handshakes/index.js +36 -0
  18. package/dist/errors.js +2 -2
  19. package/dist/index.d.ts +14 -10
  20. package/dist/index.js +23 -4
  21. package/dist/leads/dto.d.ts +5 -1
  22. package/dist/leads/dto.js +12 -1
  23. package/dist/leads/index.d.ts +8 -5
  24. package/dist/leads/index.js +23 -21
  25. package/dist/leave/leave-adjustments.js +1 -0
  26. package/dist/leave/leave-entitlements.d.ts +3 -1
  27. package/dist/leave/leave-entitlements.js +3 -0
  28. package/dist/leave/leave-requests.d.ts +3 -1
  29. package/dist/leave/leave-requests.js +3 -0
  30. package/dist/nest/index.d.ts +26 -1
  31. package/dist/nest/index.js +58 -1
  32. package/dist/notifications/index.d.ts +1 -3
  33. package/dist/organization-payment-methods/index.d.ts +5 -6
  34. package/dist/organization-payment-methods/index.js +4 -17
  35. package/dist/organizations/dto.d.ts +9 -1
  36. package/dist/organizations/dto.js +40 -1
  37. package/dist/organizations/index.d.ts +13 -3
  38. package/dist/organizations/index.js +59 -3
  39. package/dist/permissions/index.d.ts +6 -4
  40. package/dist/permissions/index.js +19 -12
  41. package/dist/projects/dto.d.ts +5 -1
  42. package/dist/projects/dto.js +12 -1
  43. package/dist/projects/index.d.ts +8 -4
  44. package/dist/projects/index.js +11 -8
  45. package/dist/service-client/index.d.ts +39 -1
  46. package/dist/service-client/index.js +89 -2
  47. package/dist/staff/dto.d.ts +14 -0
  48. package/dist/staff/dto.js +63 -0
  49. package/dist/staff/index.d.ts +50 -0
  50. package/dist/staff/index.js +139 -0
  51. package/dist/staff-client.d.ts +37 -0
  52. package/dist/staff-client.js +52 -0
  53. package/dist/tasks/dto.d.ts +5 -1
  54. package/dist/tasks/dto.js +12 -1
  55. package/dist/tasks/index.d.ts +15 -10
  56. package/dist/tasks/index.js +34 -31
  57. package/dist/tenancy.d.ts +13 -0
  58. package/dist/tenancy.js +25 -0
  59. package/dist/timesheets/timesheet-entries.js +1 -0
  60. package/dist/timesheets/timesheets.d.ts +3 -1
  61. package/dist/timesheets/timesheets.js +3 -0
  62. package/dist/user-api-credentials/dto.d.ts +12 -0
  63. package/dist/user-api-credentials/dto.js +56 -0
  64. package/dist/user-api-credentials/index.d.ts +47 -0
  65. package/dist/user-api-credentials/index.js +101 -0
  66. package/dist/users/dto.d.ts +5 -1
  67. package/dist/users/dto.js +13 -2
  68. package/dist/users/index.d.ts +7 -3
  69. package/dist/users/index.js +16 -29
  70. package/dist/validators.d.ts +2 -0
  71. package/dist/validators.js +6 -1
  72. package/package.json +2 -2
@@ -4,6 +4,7 @@ import { BillingResource } from '../billing';
4
4
  import { InvoicesResource } from '../billing/invoices';
5
5
  import { ClientsResource } from '../clients';
6
6
  import { ConfigsResource } from '../configs';
7
+ import { EmulationHandshakesResource } from '../emulation-handshakes';
7
8
  import { FeatureRequestsResource } from '../feature-requests';
8
9
  import { LeaveEntitlementsResource, LeaveRequestsResource, LeaveTypesResource } from '../leave';
9
10
  import { NotificationsOutboxResource } from '../notifications';
@@ -11,6 +12,8 @@ import { OrganizationInvitesResource } from '../organization-invites';
11
12
  import { OrganizationPaymentMethodsResource, type PaymentMethodLookupColumn } from '../organization-payment-methods';
12
13
  import { OrganizationsResource, OrganizationSignupResource } from '../organizations';
13
14
  import { PermissionsResource } from '../permissions';
15
+ import { StaffPermissionsResource, StaffResource } from '../staff';
16
+ import { UserApiCredentialsResource } from '../user-api-credentials';
14
17
  import { UsersResource } from '../users';
15
18
  import { WebhookEventsResource } from '../webhook-events';
16
19
  export type SupabaseServiceClientConfig = {
@@ -61,7 +64,8 @@ export declare class SupabaseOrganizationScope {
61
64
  /**
62
65
  * The client for work that has no caller and no RLS context to run under: inbound webhooks
63
66
  * (Paystack, Resend, the Supabase `send_email` hook), the notification queue worker, unauthenticated
64
- * signup, the website's feature-request intake, and the writes RLS reserves for the service role
67
+ * signup, the website's feature-request intake, the admin api's writes (staff, their flags, user
68
+ * api credentials, the emulation mint), and the writes RLS reserves for the service role
65
69
  * outright - the ledger, the outbox, the webhook event logs, `organizations` INSERT, `permissions`
66
70
  * INSERT/DELETE.
67
71
  *
@@ -78,7 +82,34 @@ export declare class SupabaseServiceClient {
78
82
  readonly signup: OrganizationSignupResource;
79
83
  readonly paystackEvents: WebhookEventsResource;
80
84
  readonly resendEvents: WebhookEventsResource;
85
+ readonly staff: StaffResource;
86
+ readonly staffPermissions: StaffPermissionsResource;
87
+ readonly userApiCredentials: UserApiCredentialsResource;
88
+ readonly emulationHandshakes: EmulationHandshakesResource;
81
89
  constructor(config: SupabaseServiceClientConfig);
90
+ /**
91
+ * A one-time `token_hash` that signs `email` in - the admin api verifies it itself, at once,
92
+ * through `SupabaseAuthClient.exchangeMagicLinkToken`. Nothing is sent: the link only exists
93
+ * to be verified.
94
+ */
95
+ mintMagicLink(email: string): Promise<{
96
+ token_hash: string;
97
+ }>;
98
+ /**
99
+ * The auth user behind a staff pre-registration (`staff.preRegister`), confirmed and with the
100
+ * `staff` role - which is what links the two (`handle_auth_user_role_set`).
101
+ */
102
+ createStaffAuthUser(input: {
103
+ email: string;
104
+ password: string;
105
+ }): Promise<{
106
+ id: string;
107
+ }>;
108
+ /**
109
+ * Bans or unbans an auth user, so a deactivated user or staff member cannot sign in or refresh.
110
+ * A ban has no "forever" in GoTrue; a hundred years is the usual stand-in.
111
+ */
112
+ setAuthUserBanned(userId: string, banned: boolean): Promise<void>;
82
113
  /**
83
114
  * Scope to one organization once it is known. `actorMmsId` stays `null` for system work -
84
115
  * `audit_logs.created_by` is nullable precisely so an unattributed write is still recorded.
@@ -119,6 +150,13 @@ export declare class SupabaseServiceClient {
119
150
  user_id: string;
120
151
  organization_id: string;
121
152
  } | null>;
153
+ /**
154
+ * Deletes every audit row written before `cutoff` and says how many went - the daily
155
+ * retention cron. Every tenant at once: the period is the same for all of them. Batched by
156
+ * the internal identity, oldest first, so no single statement holds a long lock, and each
157
+ * delete is a range rather than a list of ids.
158
+ */
159
+ purgeAuditLogsBefore(cutoff: string): Promise<number>;
122
160
  /**
123
161
  * A ledger row by id alone. Also unscoped by necessity: a `charge.success` carries the
124
162
  * transaction id in its metadata, and the row is what says which tenant it belongs to.
@@ -8,6 +8,7 @@ const billing_1 = require("../billing");
8
8
  const invoices_1 = require("../billing/invoices");
9
9
  const clients_1 = require("../clients");
10
10
  const configs_1 = require("../configs");
11
+ const emulation_handshakes_1 = require("../emulation-handshakes");
11
12
  const feature_requests_1 = require("../feature-requests");
12
13
  const leave_1 = require("../leave");
13
14
  const notifications_1 = require("../notifications");
@@ -16,6 +17,8 @@ const organization_invites_1 = require("../organization-invites");
16
17
  const organization_payment_methods_1 = require("../organization-payment-methods");
17
18
  const organizations_1 = require("../organizations");
18
19
  const permissions_1 = require("../permissions");
20
+ const staff_1 = require("../staff");
21
+ const user_api_credentials_1 = require("../user-api-credentials");
19
22
  const users_1 = require("../users");
20
23
  const webhook_events_1 = require("../webhook-events");
21
24
  /**
@@ -49,7 +52,7 @@ class SupabaseOrganizationScope {
49
52
  this.leaveEntitlements = new leave_1.LeaveEntitlementsResource(supabase, actor, this.audit);
50
53
  this.leaveRequests = new leave_1.LeaveRequestsResource(supabase, actor, this.audit);
51
54
  this.organizations = new organizations_1.OrganizationsResource(supabase, actor, this.audit);
52
- this.paymentMethod = new organization_payment_methods_1.OrganizationPaymentMethodsResource(supabase, actor, this.audit);
55
+ this.paymentMethod = new organization_payment_methods_1.OrganizationPaymentMethodsResource(supabase, actor);
53
56
  this.permissions = new permissions_1.PermissionsResource(supabase, actor, this.audit);
54
57
  this.users = new users_1.UsersResource(supabase, actor, this.audit);
55
58
  }
@@ -91,7 +94,8 @@ exports.SupabaseOrganizationScope = SupabaseOrganizationScope;
91
94
  /**
92
95
  * The client for work that has no caller and no RLS context to run under: inbound webhooks
93
96
  * (Paystack, Resend, the Supabase `send_email` hook), the notification queue worker, unauthenticated
94
- * signup, the website's feature-request intake, and the writes RLS reserves for the service role
97
+ * signup, the website's feature-request intake, the admin api's writes (staff, their flags, user
98
+ * api credentials, the emulation mint), and the writes RLS reserves for the service role
95
99
  * outright - the ledger, the outbox, the webhook event logs, `organizations` INSERT, `permissions`
96
100
  * INSERT/DELETE.
97
101
  *
@@ -111,6 +115,10 @@ class SupabaseServiceClient {
111
115
  signup;
112
116
  paystackEvents;
113
117
  resendEvents;
118
+ staff;
119
+ staffPermissions;
120
+ userApiCredentials;
121
+ emulationHandshakes;
114
122
  constructor(config) {
115
123
  this.config = config;
116
124
  this.supabase = (0, auth_1.createRawClient)(config.url, config.serviceRoleKey);
@@ -125,6 +133,51 @@ class SupabaseServiceClient {
125
133
  table: 'notifications',
126
134
  entityType: enum_1.AuditTrailEntityType.NOTIFICATION,
127
135
  }));
136
+ this.staff = new staff_1.StaffResource(this.supabase);
137
+ this.staffPermissions = new staff_1.StaffPermissionsResource(this.supabase);
138
+ this.userApiCredentials = new user_api_credentials_1.UserApiCredentialsResource(this.supabase, (organizationId) => this.forOrganization(organizationId).table({
139
+ table: 'user_api_credentials',
140
+ entityType: enum_1.AuditTrailEntityType.USER_API_CREDENTIAL,
141
+ related: { entityType: enum_1.AuditTrailEntityType.USER, column: 'user_id' },
142
+ }));
143
+ this.emulationHandshakes = new emulation_handshakes_1.EmulationHandshakesResource(this.supabase);
144
+ }
145
+ /**
146
+ * A one-time `token_hash` that signs `email` in - the admin api verifies it itself, at once,
147
+ * through `SupabaseAuthClient.exchangeMagicLinkToken`. Nothing is sent: the link only exists
148
+ * to be verified.
149
+ */
150
+ async mintMagicLink(email) {
151
+ const { data, error } = await this.supabase.auth.admin.generateLink({ type: 'magiclink', email });
152
+ if (error)
153
+ throw (0, errors_1.mapAuthError)(error);
154
+ return { token_hash: data.properties.hashed_token };
155
+ }
156
+ /**
157
+ * The auth user behind a staff pre-registration (`staff.preRegister`), confirmed and with the
158
+ * `staff` role - which is what links the two (`handle_auth_user_role_set`).
159
+ */
160
+ async createStaffAuthUser(input) {
161
+ const { data, error } = await this.supabase.auth.admin.createUser({
162
+ email: input.email,
163
+ password: input.password,
164
+ email_confirm: true,
165
+ role: 'staff',
166
+ });
167
+ if (error)
168
+ throw (0, errors_1.mapAuthError)(error);
169
+ return { id: data.user.id };
170
+ }
171
+ /**
172
+ * Bans or unbans an auth user, so a deactivated user or staff member cannot sign in or refresh.
173
+ * A ban has no "forever" in GoTrue; a hundred years is the usual stand-in.
174
+ */
175
+ async setAuthUserBanned(userId, banned) {
176
+ const { error } = await this.supabase.auth.admin.updateUserById(userId, {
177
+ ban_duration: banned ? '876000h' : 'none',
178
+ });
179
+ if (error)
180
+ throw (0, errors_1.mapAuthError)(error);
128
181
  }
129
182
  /**
130
183
  * Scope to one organization once it is known. `actorMmsId` stays `null` for system work -
@@ -209,6 +262,40 @@ class SupabaseServiceClient {
209
262
  throw (0, errors_1.mapPostgrestError)(error);
210
263
  return (data ?? null);
211
264
  }
265
+ /**
266
+ * Deletes every audit row written before `cutoff` and says how many went - the daily
267
+ * retention cron. Every tenant at once: the period is the same for all of them. Batched by
268
+ * the internal identity, oldest first, so no single statement holds a long lock, and each
269
+ * delete is a range rather than a list of ids.
270
+ */
271
+ async purgeAuditLogsBefore(cutoff) {
272
+ // Supabase's default `max_rows`: a larger limit would come back short and end the loop early.
273
+ const batchSize = 1000;
274
+ let deleted = 0;
275
+ for (;;) {
276
+ const { data, error } = await this.supabase
277
+ .from('audit_logs')
278
+ .select('id')
279
+ .lt('created_at', cutoff)
280
+ .order('id', { ascending: true })
281
+ .limit(batchSize);
282
+ if (error)
283
+ throw (0, errors_1.mapPostgrestError)(error);
284
+ const batch = (data ?? []);
285
+ if (batch.length === 0)
286
+ return deleted;
287
+ const { error: deleteError } = await this.supabase
288
+ .from('audit_logs')
289
+ .delete()
290
+ .lt('created_at', cutoff)
291
+ .lte('id', batch[batch.length - 1].id);
292
+ if (deleteError)
293
+ throw (0, errors_1.mapPostgrestError)(deleteError);
294
+ deleted += batch.length;
295
+ if (batch.length < batchSize)
296
+ return deleted;
297
+ }
298
+ }
212
299
  /**
213
300
  * A ledger row by id alone. Also unscoped by necessity: a `charge.success` carries the
214
301
  * transaction id in its metadata, and the row is what says which tenant it belongs to.
@@ -0,0 +1,14 @@
1
+ import type { CreateStaffDto as CreateStaffDtoType, StaffIdDto as StaffIdDtoType, UpdateStaffPermissionsDto as UpdateStaffPermissionsDtoType } from '@managemint-solutions/entities/staff/dto';
2
+ import { UpdatePermissionsDto } from '../permissions/dto';
3
+ export declare class StaffIdDto implements StaffIdDtoType {
4
+ readonly staffId: string;
5
+ }
6
+ export declare class CreateStaffDto implements CreateStaffDtoType {
7
+ readonly email: string;
8
+ readonly name: string;
9
+ readonly password: string;
10
+ }
11
+ export declare class UpdateStaffPermissionsDto extends UpdatePermissionsDto implements UpdateStaffPermissionsDtoType {
12
+ readonly is_admin: boolean;
13
+ readonly view_dashboard: boolean;
14
+ }
@@ -0,0 +1,63 @@
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.UpdateStaffPermissionsDto = exports.CreateStaffDto = exports.StaffIdDto = void 0;
13
+ const class_validator_1 = require("class-validator");
14
+ const dto_1 = require("../permissions/dto");
15
+ const validators_1 = require("../validators");
16
+ class StaffIdDto {
17
+ // v4: a staff member's `mms_id` is their GoTrue auth uid, as a user's is (see UserIdDto).
18
+ staffId;
19
+ }
20
+ exports.StaffIdDto = StaffIdDto;
21
+ __decorate([
22
+ (0, class_validator_1.IsUUID)('4', { message: 'Invalid staff ID format' }),
23
+ __metadata("design:type", String)
24
+ ], StaffIdDto.prototype, "staffId", void 0);
25
+ // The email and password rules are AddUserDto's: a staff member is a GoTrue user like any other.
26
+ class CreateStaffDto {
27
+ email;
28
+ name;
29
+ password;
30
+ }
31
+ exports.CreateStaffDto = CreateStaffDto;
32
+ __decorate([
33
+ (0, class_validator_1.IsEmail)({}, { message: 'Invalid email address' }),
34
+ __metadata("design:type", String)
35
+ ], CreateStaffDto.prototype, "email", void 0);
36
+ __decorate([
37
+ (0, class_validator_1.IsString)({ message: 'Name is required' }),
38
+ (0, class_validator_1.MinLength)(1, { message: 'Name is required' }),
39
+ (0, class_validator_1.MaxLength)(100, { message: 'Name cannot be longer than 100 characters' }),
40
+ __metadata("design:type", String)
41
+ ], CreateStaffDto.prototype, "name", void 0);
42
+ __decorate([
43
+ (0, class_validator_1.IsString)({ message: 'Password is required' }),
44
+ (0, class_validator_1.MinLength)(8, { message: 'Password must be at least 8 characters' }),
45
+ (0, validators_1.MatchesPasswordRules)(),
46
+ __metadata("design:type", String)
47
+ ], CreateStaffDto.prototype, "password", void 0);
48
+ // The tenant flag set plus the two columns a staff row carries that it leaves out: admin, which
49
+ // staff grant through the api (the service refuses self), and the dashboard flag, so a row read
50
+ // back from GET can be sent as is. There is no owner.
51
+ class UpdateStaffPermissionsDto extends dto_1.UpdatePermissionsDto {
52
+ is_admin;
53
+ view_dashboard;
54
+ }
55
+ exports.UpdateStaffPermissionsDto = UpdateStaffPermissionsDto;
56
+ __decorate([
57
+ (0, class_validator_1.IsBoolean)({ message: 'Admin must be a boolean' }),
58
+ __metadata("design:type", Boolean)
59
+ ], UpdateStaffPermissionsDto.prototype, "is_admin", void 0);
60
+ __decorate([
61
+ (0, class_validator_1.IsBoolean)({ message: 'View Dashboard must be a boolean' }),
62
+ __metadata("design:type", Boolean)
63
+ ], UpdateStaffPermissionsDto.prototype, "view_dashboard", void 0);
@@ -0,0 +1,50 @@
1
+ import type { StaffEntity, StaffPermissionsEntity } from '@managemint-solutions/entities/staff';
2
+ import type { UpdateStaffPermissionsDto } from '@managemint-solutions/entities/staff/dto';
3
+ import type { TypedSupabaseClient } from '../client';
4
+ export * from './dto';
5
+ /** Admin bypasses every individual flag, so the guard reads both at once. */
6
+ type StaffPermissionFlags = {
7
+ is_admin: boolean;
8
+ } & Record<string, boolean>;
9
+ /**
10
+ * The `staff` table. Staff read it on their own client; every write is the service role's (staff
11
+ * hold SELECT only), so `preRegister`, `removePending` and `setActive` belong to the service
12
+ * client. Unaudited: an `audit_logs` row belongs to an organization, and staff have none.
13
+ */
14
+ export declare class StaffResource {
15
+ private readonly supabase;
16
+ constructor(supabase: TypedSupabaseClient);
17
+ /** Every staff member, by name. A pending pre-registration is not one yet. */
18
+ list(): Promise<StaffEntity[]>;
19
+ getById(staffId: string): Promise<StaffEntity>;
20
+ /**
21
+ * The address, written before its auth user exists - the first half of creating a staff
22
+ * member. `mms_id` stays null until GoTrue creates the auth user with the `staff` role and
23
+ * `handle_auth_user_role_set` links the two.
24
+ */
25
+ preRegister(input: {
26
+ email: string;
27
+ name: string;
28
+ created_by: string;
29
+ }): Promise<void>;
30
+ /** Undoes a pre-registration whose auth user was never created. A linked row is never touched. */
31
+ removePending(email: string): Promise<void>;
32
+ /** Deactivates, stamping who and when, or reactivates, clearing them. */
33
+ setActive(staffId: string, active: boolean, by: string): Promise<StaffEntity>;
34
+ }
35
+ /**
36
+ * The `staff_permissions` table: one row per staff member, made by the role trigger alongside
37
+ * the link. Staff read it on their own client; `updateForStaff` is the service client's.
38
+ */
39
+ export declare class StaffPermissionsResource {
40
+ private readonly supabase;
41
+ constructor(supabase: TypedSupabaseClient);
42
+ /**
43
+ * The one lookup the admin api's guard makes per gated request: the flag the route asked for,
44
+ * plus the admin bypass. Null when there is no row, which the guard treats as a denial.
45
+ */
46
+ flagsFor(staffId: string, flag: string): Promise<StaffPermissionFlags | null>;
47
+ /** The whole row. `*`: every flag, so a new flag column needs no select edit. */
48
+ getForStaff(staffId: string): Promise<StaffPermissionsEntity>;
49
+ updateForStaff(staffId: string, patch: UpdateStaffPermissionsDto, by: string): Promise<StaffPermissionsEntity>;
50
+ }
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.StaffPermissionsResource = exports.StaffResource = void 0;
18
+ const audit_1 = require("../audit");
19
+ const errors_1 = require("../errors");
20
+ const permissions_1 = require("../permissions");
21
+ __exportStar(require("./dto"), exports);
22
+ const STAFF_SELECT = 'mms_id, email, name, active, created_at, created_by, deactivated_at, deactivated_by';
23
+ /**
24
+ * The `staff` table. Staff read it on their own client; every write is the service role's (staff
25
+ * hold SELECT only), so `preRegister`, `removePending` and `setActive` belong to the service
26
+ * client. Unaudited: an `audit_logs` row belongs to an organization, and staff have none.
27
+ */
28
+ class StaffResource {
29
+ supabase;
30
+ constructor(supabase) {
31
+ this.supabase = supabase;
32
+ }
33
+ /** Every staff member, by name. A pending pre-registration is not one yet. */
34
+ async list() {
35
+ const { data, error } = await this.supabase
36
+ .from('staff')
37
+ .select(STAFF_SELECT)
38
+ .not('mms_id', 'is', null)
39
+ .order('name');
40
+ if (error)
41
+ throw (0, errors_1.mapPostgrestError)(error);
42
+ return (data ?? []);
43
+ }
44
+ async getById(staffId) {
45
+ const { data, error } = await this.supabase
46
+ .from('staff')
47
+ .select(STAFF_SELECT)
48
+ .eq('mms_id', staffId)
49
+ .single();
50
+ if (error)
51
+ throw (0, errors_1.mapPostgrestError)(error);
52
+ return data;
53
+ }
54
+ /**
55
+ * The address, written before its auth user exists - the first half of creating a staff
56
+ * member. `mms_id` stays null until GoTrue creates the auth user with the `staff` role and
57
+ * `handle_auth_user_role_set` links the two.
58
+ */
59
+ async preRegister(input) {
60
+ const { error } = await this.supabase.from('staff').insert(input);
61
+ if (error)
62
+ throw (0, errors_1.mapPostgrestError)(error);
63
+ }
64
+ /** Undoes a pre-registration whose auth user was never created. A linked row is never touched. */
65
+ async removePending(email) {
66
+ const { error } = await this.supabase
67
+ .from('staff')
68
+ .delete()
69
+ .eq('email', email)
70
+ .is('mms_id', null);
71
+ if (error)
72
+ throw (0, errors_1.mapPostgrestError)(error);
73
+ }
74
+ /** Deactivates, stamping who and when, or reactivates, clearing them. */
75
+ async setActive(staffId, active, by) {
76
+ const { data, error } = await this.supabase
77
+ .from('staff')
78
+ .update({
79
+ active,
80
+ deactivated_at: active ? null : new Date().toISOString(),
81
+ deactivated_by: active ? null : by,
82
+ })
83
+ .eq('mms_id', staffId)
84
+ .select(STAFF_SELECT)
85
+ .single();
86
+ if (error)
87
+ throw (0, errors_1.mapPostgrestError)(error);
88
+ return data;
89
+ }
90
+ }
91
+ exports.StaffResource = StaffResource;
92
+ /**
93
+ * The `staff_permissions` table: one row per staff member, made by the role trigger alongside
94
+ * the link. Staff read it on their own client; `updateForStaff` is the service client's.
95
+ */
96
+ class StaffPermissionsResource {
97
+ supabase;
98
+ constructor(supabase) {
99
+ this.supabase = supabase;
100
+ }
101
+ /**
102
+ * The one lookup the admin api's guard makes per gated request: the flag the route asked for,
103
+ * plus the admin bypass. Null when there is no row, which the guard treats as a denial.
104
+ */
105
+ async flagsFor(staffId, flag) {
106
+ (0, permissions_1.checkPermissionFlag)(flag);
107
+ const { data, error } = await this.supabase
108
+ .from('staff_permissions')
109
+ .select(`is_admin, ${flag}`)
110
+ .eq('staff_id', staffId)
111
+ .maybeSingle();
112
+ if (error)
113
+ throw (0, errors_1.mapPostgrestError)(error);
114
+ return (data ?? null);
115
+ }
116
+ /** The whole row. `*`: every flag, so a new flag column needs no select edit. */
117
+ async getForStaff(staffId) {
118
+ const { data, error } = await this.supabase
119
+ .from('staff_permissions')
120
+ .select('*')
121
+ .eq('staff_id', staffId)
122
+ .single();
123
+ if (error)
124
+ throw (0, errors_1.mapPostgrestError)(error);
125
+ return (0, audit_1.withoutInternalId)(data);
126
+ }
127
+ async updateForStaff(staffId, patch, by) {
128
+ const { data, error } = await this.supabase
129
+ .from('staff_permissions')
130
+ .update({ ...patch, updated_at: new Date().toISOString(), last_updated_by: by })
131
+ .eq('staff_id', staffId)
132
+ .select('*')
133
+ .single();
134
+ if (error)
135
+ throw (0, errors_1.mapPostgrestError)(error);
136
+ return (0, audit_1.withoutInternalId)(data);
137
+ }
138
+ }
139
+ exports.StaffPermissionsResource = StaffPermissionsResource;
@@ -0,0 +1,37 @@
1
+ import { type StaffContext } from './auth';
2
+ import { AuditResource } from './audit';
3
+ import type { SupabaseClientConfig, TypedSupabaseClient } from './client';
4
+ import { ClientsResource } from './clients';
5
+ import { LeadsResource } from './leads';
6
+ import { OrganizationsResource } from './organizations';
7
+ import { ProjectsResource } from './projects';
8
+ import { StaffPermissionsResource, StaffResource } from './staff';
9
+ import { TasksResource } from './tasks';
10
+ import { UserApiCredentialsReads } from './user-api-credentials';
11
+ import { UsersResource } from './users';
12
+ type SupabaseStaffClientConfig = Omit<SupabaseClientConfig, 'auth' | 'onAuditError'> & {
13
+ auth: StaffContext;
14
+ };
15
+ /**
16
+ * A staff member's data client, for the admin api: every query carries the staff token, so the
17
+ * `staff` role's policies decide what comes back. Reads span every organization and every write
18
+ * is refused with a 403. A resource not listed here is simply not available to staff.
19
+ */
20
+ export declare class SupabaseStaffClient {
21
+ readonly supabase: TypedSupabaseClient;
22
+ /** The staff member the token belongs to - the caller, for a service that records who acted. */
23
+ readonly auth: StaffContext;
24
+ readonly audit: AuditResource;
25
+ readonly clients: ClientsResource;
26
+ readonly leads: LeadsResource;
27
+ readonly projects: ProjectsResource;
28
+ readonly tasks: TasksResource;
29
+ readonly users: UsersResource;
30
+ readonly organizations: OrganizationsResource;
31
+ readonly staff: StaffResource;
32
+ readonly staffPermissions: StaffPermissionsResource;
33
+ readonly userApiCredentials: UserApiCredentialsReads;
34
+ constructor({ url, key, accessToken, auth, client }: SupabaseStaffClientConfig);
35
+ }
36
+ export declare const createSupabaseStaffClient: (config: SupabaseStaffClientConfig) => SupabaseStaffClient;
37
+ export {};
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSupabaseStaffClient = exports.SupabaseStaffClient = void 0;
4
+ const auth_1 = require("./auth");
5
+ const audit_1 = require("./audit");
6
+ const additional_details_1 = require("./additional-details");
7
+ const clients_1 = require("./clients");
8
+ const leads_1 = require("./leads");
9
+ const organizations_1 = require("./organizations");
10
+ const projects_1 = require("./projects");
11
+ const staff_1 = require("./staff");
12
+ const tasks_1 = require("./tasks");
13
+ const user_api_credentials_1 = require("./user-api-credentials");
14
+ const users_1 = require("./users");
15
+ /**
16
+ * A staff member's data client, for the admin api: every query carries the staff token, so the
17
+ * `staff` role's policies decide what comes back. Reads span every organization and every write
18
+ * is refused with a 403. A resource not listed here is simply not available to staff.
19
+ */
20
+ class SupabaseStaffClient {
21
+ supabase; // escape hatch for not-yet-migrated queries
22
+ /** The staff member the token belongs to - the caller, for a service that records who acted. */
23
+ auth;
24
+ audit;
25
+ clients;
26
+ leads;
27
+ projects;
28
+ tasks;
29
+ users;
30
+ organizations;
31
+ staff;
32
+ staffPermissions;
33
+ userApiCredentials;
34
+ constructor({ url, key, accessToken, auth, client }) {
35
+ this.supabase = client ?? (0, auth_1.createRawClient)(url, key, accessToken);
36
+ this.auth = auth;
37
+ this.audit = new audit_1.AuditResource(this.supabase, auth);
38
+ this.clients = new clients_1.ClientsResource(this.supabase, auth, this.audit);
39
+ const additionalDetails = new additional_details_1.AdditionalDetailsResource(this.supabase, auth, this.audit);
40
+ this.leads = new leads_1.LeadsResource(this.supabase, auth, this.audit, additionalDetails);
41
+ this.projects = new projects_1.ProjectsResource(this.supabase, auth, this.audit);
42
+ this.tasks = new tasks_1.TasksResource(this.supabase, auth, this.audit);
43
+ this.users = new users_1.UsersResource(this.supabase, auth, this.audit);
44
+ this.organizations = new organizations_1.OrganizationsResource(this.supabase, auth, this.audit);
45
+ this.staff = new staff_1.StaffResource(this.supabase);
46
+ this.staffPermissions = new staff_1.StaffPermissionsResource(this.supabase);
47
+ this.userApiCredentials = new user_api_credentials_1.UserApiCredentialsReads(this.supabase);
48
+ }
49
+ }
50
+ exports.SupabaseStaffClient = SupabaseStaffClient;
51
+ const createSupabaseStaffClient = (config) => new SupabaseStaffClient(config);
52
+ exports.createSupabaseStaffClient = createSupabaseStaffClient;
@@ -1,4 +1,4 @@
1
- import type { AddTaskLinkDto as AddTaskLinkDtoType, CreateTaskDto as CreateTaskDtoType, GetTasksDto as GetTasksDtoType, TaskIdDto as TaskIdDtoType, TaskLinkIdDto as TaskLinkIdDtoType, UpdateTaskDto as UpdateTaskDtoType } from '@managemint-solutions/entities/tasks/dto';
1
+ import type { AddTaskLinkDto as AddTaskLinkDtoType, CreateTaskDto as CreateTaskDtoType, GetTasksDto as GetTasksDtoType, StaffGetTasksDto as StaffGetTasksDtoType, TaskIdDto as TaskIdDtoType, TaskLinkIdDto as TaskLinkIdDtoType, UpdateTaskDto as UpdateTaskDtoType } from '@managemint-solutions/entities/tasks/dto';
2
2
  import { TaskLinkType } from '@managemint-solutions/entities/tasks/enum';
3
3
  export declare class TaskIdDto implements TaskIdDtoType {
4
4
  readonly taskId: string;
@@ -31,6 +31,10 @@ export declare class GetTasksDto implements Omit<GetTasksDtoType, 'status_id_in'
31
31
  readonly priority_in?: number[] | null;
32
32
  readonly archive: boolean;
33
33
  }
34
+ /** A staff read across organizations, optionally narrowed to one of them. */
35
+ export declare class StaffGetTasksDto extends GetTasksDto implements Omit<StaffGetTasksDtoType, 'status_id_in' | 'status_id_not_in' | 'label_id_in' | 'priority_in'> {
36
+ readonly organization_id?: string | null;
37
+ }
34
38
  export declare class CreateTaskDto implements CreateTaskDtoType {
35
39
  readonly name: string;
36
40
  readonly description?: string | null;
package/dist/tasks/dto.js CHANGED
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.UpdateTaskDto = exports.CreateTaskDto = exports.GetTasksDto = exports.AddTaskLinkDto = exports.TaskLinkIdDto = exports.TaskIdDto = void 0;
12
+ exports.UpdateTaskDto = exports.CreateTaskDto = exports.StaffGetTasksDto = exports.GetTasksDto = exports.AddTaskLinkDto = exports.TaskLinkIdDto = exports.TaskIdDto = void 0;
13
13
  const class_validator_1 = require("class-validator");
14
14
  const class_transformer_1 = require("class-transformer");
15
15
  const enum_1 = require("@managemint-solutions/entities/tasks/enum");
@@ -206,6 +206,17 @@ __decorate([
206
206
  (0, class_validator_1.IsBoolean)({ message: 'Include archived must be a boolean' }),
207
207
  __metadata("design:type", Boolean)
208
208
  ], GetTasksDto.prototype, "archive", void 0);
209
+ /** A staff read across organizations, optionally narrowed to one of them. */
210
+ class StaffGetTasksDto extends GetTasksDto {
211
+ organization_id;
212
+ }
213
+ exports.StaffGetTasksDto = StaffGetTasksDto;
214
+ __decorate([
215
+ (0, class_validator_1.IsOptional)(),
216
+ (0, validators_1.IsNullable)(),
217
+ (0, class_validator_1.IsUUID)('7', { message: 'Invalid organization ID format' }),
218
+ __metadata("design:type", Object)
219
+ ], StaffGetTasksDto.prototype, "organization_id", void 0);
209
220
  class CreateTaskDto {
210
221
  name;
211
222
  description;