@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.
- package/README.md +92 -13
- package/dist/additional-details/index.d.ts +2 -2
- package/dist/additional-details/index.js +6 -10
- package/dist/audit/dto.d.ts +3 -8
- package/dist/audit/dto.js +7 -48
- package/dist/audit/index.d.ts +71 -10
- package/dist/audit/index.js +234 -55
- package/dist/auth-client/index.d.ts +2 -0
- package/dist/auth-client/index.js +12 -0
- package/dist/auth.d.ts +21 -0
- package/dist/auth.js +47 -3
- package/dist/clients/dto.d.ts +5 -1
- package/dist/clients/dto.js +12 -1
- package/dist/clients/index.d.ts +8 -4
- package/dist/clients/index.js +11 -8
- package/dist/emulation-handshakes/index.d.ts +13 -0
- package/dist/emulation-handshakes/index.js +36 -0
- package/dist/errors.js +2 -2
- package/dist/index.d.ts +14 -10
- package/dist/index.js +23 -4
- package/dist/leads/dto.d.ts +5 -1
- package/dist/leads/dto.js +12 -1
- package/dist/leads/index.d.ts +8 -5
- package/dist/leads/index.js +23 -21
- package/dist/leave/leave-adjustments.js +1 -0
- package/dist/leave/leave-entitlements.d.ts +3 -1
- package/dist/leave/leave-entitlements.js +3 -0
- package/dist/leave/leave-requests.d.ts +3 -1
- package/dist/leave/leave-requests.js +3 -0
- package/dist/nest/index.d.ts +26 -1
- package/dist/nest/index.js +58 -1
- package/dist/notifications/index.d.ts +1 -3
- package/dist/organization-payment-methods/index.d.ts +5 -6
- package/dist/organization-payment-methods/index.js +4 -17
- package/dist/organizations/dto.d.ts +9 -1
- package/dist/organizations/dto.js +40 -1
- package/dist/organizations/index.d.ts +13 -3
- package/dist/organizations/index.js +59 -3
- package/dist/permissions/index.d.ts +6 -4
- package/dist/permissions/index.js +19 -12
- package/dist/projects/dto.d.ts +5 -1
- package/dist/projects/dto.js +12 -1
- package/dist/projects/index.d.ts +8 -4
- package/dist/projects/index.js +11 -8
- package/dist/service-client/index.d.ts +39 -1
- package/dist/service-client/index.js +89 -2
- package/dist/staff/dto.d.ts +14 -0
- package/dist/staff/dto.js +63 -0
- package/dist/staff/index.d.ts +50 -0
- package/dist/staff/index.js +139 -0
- package/dist/staff-client.d.ts +37 -0
- package/dist/staff-client.js +52 -0
- package/dist/tasks/dto.d.ts +5 -1
- package/dist/tasks/dto.js +12 -1
- package/dist/tasks/index.d.ts +15 -10
- package/dist/tasks/index.js +34 -31
- package/dist/tenancy.d.ts +13 -0
- package/dist/tenancy.js +25 -0
- package/dist/timesheets/timesheet-entries.js +1 -0
- package/dist/timesheets/timesheets.d.ts +3 -1
- package/dist/timesheets/timesheets.js +3 -0
- package/dist/user-api-credentials/dto.d.ts +12 -0
- package/dist/user-api-credentials/dto.js +56 -0
- package/dist/user-api-credentials/index.d.ts +47 -0
- package/dist/user-api-credentials/index.js +101 -0
- package/dist/users/dto.d.ts +5 -1
- package/dist/users/dto.js +13 -2
- package/dist/users/index.d.ts +7 -3
- package/dist/users/index.js +16 -29
- package/dist/validators.d.ts +2 -0
- package/dist/validators.js +6 -1
- 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,
|
|
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
|
|
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,
|
|
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;
|
package/dist/tasks/dto.d.ts
CHANGED
|
@@ -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;
|