@managemint-solutions/sdk 0.56.0 → 0.65.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 (55) hide show
  1. package/README.md +209 -7
  2. package/dist/audit/document-changes.d.ts +39 -0
  3. package/dist/audit/document-changes.js +332 -0
  4. package/dist/audit/index.d.ts +50 -2
  5. package/dist/audit/index.js +87 -44
  6. package/dist/client.d.ts +6 -0
  7. package/dist/client.js +9 -0
  8. package/dist/clients/dto.d.ts +18 -3
  9. package/dist/clients/dto.js +72 -2
  10. package/dist/clients/index.js +16 -0
  11. package/dist/configs/dto.js +4 -12
  12. package/dist/documents/content.d.ts +39 -0
  13. package/dist/documents/content.js +443 -0
  14. package/dist/documents/document-templates.d.ts +37 -0
  15. package/dist/documents/document-templates.js +143 -0
  16. package/dist/documents/documents.d.ts +154 -0
  17. package/dist/documents/documents.js +330 -0
  18. package/dist/documents/dto.d.ts +138 -0
  19. package/dist/documents/dto.js +594 -0
  20. package/dist/documents/index.d.ts +4 -0
  21. package/dist/documents/index.js +24 -0
  22. package/dist/documents/render/html.d.ts +64 -0
  23. package/dist/documents/render/html.js +413 -0
  24. package/dist/documents/render/index.d.ts +2 -0
  25. package/dist/documents/render/index.js +23 -0
  26. package/dist/documents/render/values.d.ts +86 -0
  27. package/dist/documents/render/values.js +142 -0
  28. package/dist/feature-requests/dto.js +4 -13
  29. package/dist/index.d.ts +8 -1
  30. package/dist/index.js +31 -3
  31. package/dist/leads/index.js +1 -3
  32. package/dist/notifications/index.d.ts +6 -0
  33. package/dist/notifications/index.js +16 -0
  34. package/dist/organization-payment-methods/dto.d.ts +10 -0
  35. package/dist/organization-payment-methods/dto.js +56 -0
  36. package/dist/organization-payment-methods/index.d.ts +8 -1
  37. package/dist/organization-payment-methods/index.js +23 -0
  38. package/dist/payments/index.d.ts +67 -0
  39. package/dist/payments/index.js +137 -0
  40. package/dist/permissions/dto.d.ts +4 -0
  41. package/dist/permissions/dto.js +20 -8
  42. package/dist/service-client/index.d.ts +44 -0
  43. package/dist/service-client/index.js +77 -0
  44. package/dist/staff-client.d.ts +3 -0
  45. package/dist/staff-client.js +5 -0
  46. package/dist/supporting-files/index.js +4 -6
  47. package/dist/supporting-files/paths.d.ts +2 -0
  48. package/dist/supporting-files/paths.js +3 -1
  49. package/dist/validators.d.ts +12 -0
  50. package/dist/validators.js +23 -1
  51. package/dist/visibility/dto.d.ts +15 -0
  52. package/dist/visibility/dto.js +68 -0
  53. package/dist/visibility/index.d.ts +59 -0
  54. package/dist/visibility/index.js +165 -0
  55. package/package.json +6 -2
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PaymentsResource = void 0;
4
+ const enum_1 = require("@managemint-solutions/entities/payments/enum");
5
+ const enum_2 = require("@managemint-solutions/entities/audit-trail/enum");
6
+ const audit_1 = require("../audit");
7
+ const errors_1 = require("../errors");
8
+ const tenancy_1 = require("../tenancy");
9
+ const transforms_1 = require("../transforms");
10
+ const PAYMENT_SELECT = `
11
+ mms_id,
12
+ created_at,
13
+ created_by:user_profiles!created_by ${transforms_1.USER_PROFILE_EMBED},
14
+ direction,
15
+ status,
16
+ amount_minor,
17
+ currency,
18
+ method,
19
+ reference,
20
+ payer_email,
21
+ paid_at
22
+ `;
23
+ /**
24
+ * `public.payments`: money received for a record, keyed by `entity_type` + `entity_id` like the
25
+ * visibility table. Read on the caller's client (RLS shows a payment while its record is
26
+ * readable); written on the service client's organization scope alone - members hold only a
27
+ * SELECT grant. A checkout starts as an `open` row (one per record, by a partial unique index)
28
+ * and is settled to `paid`. Each write is audited and, through its `entity_type`/`entity_id`,
29
+ * filed under the record it pays for.
30
+ */
31
+ class PaymentsResource {
32
+ supabase;
33
+ actor;
34
+ table;
35
+ constructor(supabase, actor, audit) {
36
+ this.supabase = supabase;
37
+ this.actor = actor;
38
+ this.table = (0, audit_1.auditedTable)(supabase, actor, audit, {
39
+ table: 'payments',
40
+ entityType: enum_2.AuditTrailEntityType.PAYMENTS,
41
+ });
42
+ }
43
+ /** The money received for one record, oldest first - paid rows only. */
44
+ async list(entityType, entityId) {
45
+ const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('payments').select(PAYMENT_SELECT), this.actor)
46
+ .eq('entity_type', entityType)
47
+ .eq('entity_id', entityId)
48
+ .eq('status', enum_1.PaymentStatus.PAID)
49
+ .order('paid_at', { ascending: true });
50
+ if (error)
51
+ throw (0, errors_1.mapPostgrestError)(error);
52
+ return data;
53
+ }
54
+ /** How many paid payments each of `entityIds` has - one query for a page of a list. */
55
+ async paidCounts(entityType, entityIds) {
56
+ if (entityIds.length === 0)
57
+ return {};
58
+ const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('payments').select('entity_id'), this.actor)
59
+ .eq('entity_type', entityType)
60
+ .eq('status', enum_1.PaymentStatus.PAID)
61
+ .in('entity_id', entityIds);
62
+ if (error)
63
+ throw (0, errors_1.mapPostgrestError)(error);
64
+ const counts = {};
65
+ for (const { entity_id } of (data ?? [])) {
66
+ counts[entity_id] = (counts[entity_id] ?? 0) + 1;
67
+ }
68
+ return counts;
69
+ }
70
+ /**
71
+ * The ids of the organization's records of one kind with a settled payment, each once - the
72
+ * documents list's payment filter reads it before the page.
73
+ */
74
+ async paidEntityIds(entityType) {
75
+ const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('payments').select('entity_id'), this.actor)
76
+ .eq('entity_type', entityType)
77
+ .eq('status', enum_1.PaymentStatus.PAID);
78
+ if (error)
79
+ throw (0, errors_1.mapPostgrestError)(error);
80
+ return [...new Set((data ?? []).map((row) => row.entity_id))];
81
+ }
82
+ /** The record's open checkout, if one is under way. */
83
+ async findOpen(entityType, entityId) {
84
+ const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('payments').select(`${PAYMENT_SELECT}, access_code`), this.actor)
85
+ .eq('entity_type', entityType)
86
+ .eq('entity_id', entityId)
87
+ .eq('status', enum_1.PaymentStatus.OPEN)
88
+ .maybeSingle();
89
+ if (error)
90
+ throw (0, errors_1.mapPostgrestError)(error);
91
+ return (data ?? null);
92
+ }
93
+ /** A payment by the provider's reference (unique), whatever its status. */
94
+ async findByReference(reference) {
95
+ const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('payments').select(PAYMENT_SELECT), this.actor)
96
+ .eq('reference', reference)
97
+ .maybeSingle();
98
+ if (error)
99
+ throw (0, errors_1.mapPostgrestError)(error);
100
+ return (data ?? null);
101
+ }
102
+ /**
103
+ * Records one payment, by the actor (null for the system). A Paystack reference recorded
104
+ * already, or a second open checkout for the record, is refused by a unique index - 409 with
105
+ * `code` 23505.
106
+ */
107
+ async create(row) {
108
+ await this.table.insert({
109
+ ...row,
110
+ organization_id: (0, tenancy_1.requireOrganization)(this.actor),
111
+ created_by: this.actor.mms_id,
112
+ created_at: new Date().toISOString(),
113
+ });
114
+ }
115
+ /**
116
+ * Removes a payment marked by hand (EFT) from the record it pays for. False when it is not one -
117
+ * a Paystack payment is money Paystack holds, refunded there, never removed here.
118
+ */
119
+ async removeManual(paymentId, entityType, entityId) {
120
+ const removed = await this.table.removeWhere(paymentId, (query) => query.eq('entity_type', entityType).eq('entity_id', entityId).eq('method', enum_1.PaymentMethod.EFT));
121
+ return removed !== null;
122
+ }
123
+ /** Settles an open checkout. False when it is no longer open (settled or replaced first). */
124
+ async settle(paymentId, row) {
125
+ const settled = await this.table.updateWhere(paymentId, (query) => query.eq('status', enum_1.PaymentStatus.OPEN), {
126
+ ...row,
127
+ status: enum_1.PaymentStatus.PAID,
128
+ });
129
+ return settled !== null;
130
+ }
131
+ /** Drops an open checkout Paystack says failed, so a new one can open. False when it moved on. */
132
+ async removeOpen(paymentId) {
133
+ const removed = await this.table.removeWhere(paymentId, (query) => query.eq('status', enum_1.PaymentStatus.OPEN));
134
+ return removed !== null;
135
+ }
136
+ }
137
+ exports.PaymentsResource = PaymentsResource;
@@ -20,6 +20,10 @@ export declare class UpdatePermissionsDto implements UpdatePermissionsDtoType {
20
20
  readonly read_timesheets: boolean;
21
21
  readonly update_timesheets: boolean;
22
22
  readonly delete_timesheets: boolean;
23
+ readonly create_documents: boolean;
24
+ readonly read_documents: boolean;
25
+ readonly update_documents: boolean;
26
+ readonly delete_documents: boolean;
23
27
  readonly create_leave: boolean;
24
28
  readonly read_leave: boolean;
25
29
  readonly update_leave: boolean;
@@ -42,14 +42,10 @@ class UpdatePermissionsDto {
42
42
  read_timesheets;
43
43
  update_timesheets;
44
44
  delete_timesheets;
45
- // @IsBoolean({ message: 'Create Documents must be a boolean' })
46
- // readonly create_documents!: boolean;
47
- // @IsBoolean({ message: 'Read Documents must be a boolean' })
48
- // readonly read_documents!: boolean;
49
- // @IsBoolean({ message: 'Update Documents must be a boolean' })
50
- // readonly update_documents!: boolean;
51
- // @IsBoolean({ message: 'Delete Documents must be a boolean' })
52
- // readonly delete_documents!: boolean;
45
+ create_documents;
46
+ read_documents;
47
+ update_documents;
48
+ delete_documents;
53
49
  create_leave;
54
50
  read_leave;
55
51
  update_leave;
@@ -192,6 +188,22 @@ __decorate([
192
188
  (0, class_validator_1.IsBoolean)({ message: 'Delete Timesheets must be a boolean' }),
193
189
  __metadata("design:type", Boolean)
194
190
  ], UpdatePermissionsDto.prototype, "delete_timesheets", void 0);
191
+ __decorate([
192
+ (0, class_validator_1.IsBoolean)({ message: 'Create Documents must be a boolean' }),
193
+ __metadata("design:type", Boolean)
194
+ ], UpdatePermissionsDto.prototype, "create_documents", void 0);
195
+ __decorate([
196
+ (0, class_validator_1.IsBoolean)({ message: 'Read Documents must be a boolean' }),
197
+ __metadata("design:type", Boolean)
198
+ ], UpdatePermissionsDto.prototype, "read_documents", void 0);
199
+ __decorate([
200
+ (0, class_validator_1.IsBoolean)({ message: 'Update Documents must be a boolean' }),
201
+ __metadata("design:type", Boolean)
202
+ ], UpdatePermissionsDto.prototype, "update_documents", void 0);
203
+ __decorate([
204
+ (0, class_validator_1.IsBoolean)({ message: 'Delete Documents must be a boolean' }),
205
+ __metadata("design:type", Boolean)
206
+ ], UpdatePermissionsDto.prototype, "delete_documents", void 0);
195
207
  __decorate([
196
208
  (0, class_validator_1.IsBoolean)({ message: 'Create Leave must be a boolean' }),
197
209
  __metadata("design:type", Boolean)
@@ -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 { DocumentsResource } from '../documents';
7
8
  import { EmulationHandshakesResource } from '../emulation-handshakes';
8
9
  import { FeatureRequestsResource } from '../feature-requests';
9
10
  import { LeaveEntitlementsResource, LeaveRequestsResource, LeaveTypesResource } from '../leave';
@@ -11,11 +12,13 @@ import { NotificationsOutboxResource } from '../notifications';
11
12
  import { OrganizationInvitesResource } from '../organization-invites';
12
13
  import { OrganizationPaymentMethodsResource, type PaymentMethodLookupColumn } from '../organization-payment-methods';
13
14
  import { OrganizationsResource, OrganizationSignupResource } from '../organizations';
15
+ import { PaymentsResource } from '../payments';
14
16
  import { PermissionsResource } from '../permissions';
15
17
  import { StaffPermissionsResource, StaffResource } from '../staff';
16
18
  import { SupportingFilesResource } from '../supporting-files';
17
19
  import { UserApiCredentialsResource } from '../user-api-credentials';
18
20
  import { UsersResource } from '../users';
21
+ import { VisibilityResource } from '../visibility';
19
22
  import { WebhookEventsResource } from '../webhook-events';
20
23
  export type SupabaseServiceClientConfig = {
21
24
  url: string;
@@ -34,6 +37,13 @@ export declare class SupabaseOrganizationScope {
34
37
  readonly actor: AuditActor;
35
38
  readonly audit: AuditResource;
36
39
  readonly billing: BillingResource;
40
+ /**
41
+ * For the public pay page, which has no caller (the document behind a pay link, and its PDF),
42
+ * the Paystack webhook, and the daily send cap, which counts private documents too.
43
+ */
44
+ readonly documents: DocumentsResource;
45
+ /** Money received: written here alone, by the Paystack webhook. */
46
+ readonly payments: PaymentsResource;
37
47
  readonly invoices: InvoicesResource;
38
48
  readonly invites: OrganizationInvitesResource;
39
49
  readonly leaveTypes: LeaveTypesResource;
@@ -45,6 +55,8 @@ export declare class SupabaseOrganizationScope {
45
55
  /** For the admin api's staff downloads: Storage's read policy names no staff, the service role passes it. */
46
56
  readonly supportingFiles: SupportingFilesResource;
47
57
  readonly users: UsersResource;
58
+ /** Who private records are shared with: listed and written here, after the api's own checks. */
59
+ readonly visibility: VisibilityResource;
48
60
  constructor(supabase: TypedSupabaseClient, actor: AuditActor, onAuditError?: AuditErrorHandler);
49
61
  /**
50
62
  * An audited table bound to this organization and actor - the shape the api hand-rolls today
@@ -52,6 +64,18 @@ export declare class SupabaseOrganizationScope {
52
64
  * subscriptions and both webhook handler services.
53
65
  */
54
66
  table(options: AuditedTableOptions): AuditedTable;
67
+ /**
68
+ * The organization's templates whose `column` starts with `start` (literally, any case),
69
+ * private and archived ones included. Service role because both columns are unique across the
70
+ * whole organization (`uq_document_templates_organization_name`,
71
+ * `uq_document_templates_organization_prefix`) while a caller reads only the templates shared
72
+ * with them.
73
+ */
74
+ templatesStartingWith(column: 'name' | 'number_prefix', start: string): Promise<{
75
+ mms_id: string;
76
+ name: string;
77
+ number_prefix: string;
78
+ }[]>;
55
79
  /**
56
80
  * Gives `userId` a new leave calendar feed token (stored as its SHA-256), which also retires
57
81
  * any address they subscribed with before. Service role only: the token is a credential.
@@ -153,6 +177,26 @@ export declare class SupabaseServiceClient {
153
177
  user_id: string;
154
178
  organization_id: string;
155
179
  } | null>;
180
+ /**
181
+ * The recurring parents due a draft: not paused, not ended, `repeat_next_at` on or before
182
+ * `onOrBefore` (YYYY-MM-DD). Across every organization, like the leave rollover's tenants - the
183
+ * cron then works inside each row's own organization.
184
+ */
185
+ documentsDueForDraft(onOrBefore: string): Promise<{
186
+ mms_id: string;
187
+ organization_id: string;
188
+ }[]>;
189
+ /** The drafts due to issue: `issue_at` on or before `onOrBefore`, across every organization. */
190
+ documentsDueToIssue(onOrBefore: string): Promise<{
191
+ mms_id: string;
192
+ organization_id: string;
193
+ }[]>;
194
+ /**
195
+ * Which organization a document belongs to, by its id alone. Unscoped by necessity, like the
196
+ * calendar feed: a public pay link (whose signature the api has checked) names only the
197
+ * document, and the row is what says which tenant it belongs to.
198
+ */
199
+ findDocumentOrganization(documentId: string): Promise<string | null>;
156
200
  /**
157
201
  * Deletes every audit row written before `cutoff` and says how many went - the daily
158
202
  * retention cron. Every tenant at once: the period is the same for all of them. Batched by
@@ -4,10 +4,12 @@ exports.createSupabaseServiceClient = exports.SupabaseServiceClient = exports.Su
4
4
  const auth_1 = require("../auth");
5
5
  const audit_1 = require("../audit");
6
6
  const enum_1 = require("@managemint-solutions/entities/audit-trail/enum");
7
+ const enum_2 = require("@managemint-solutions/entities/documents/enum");
7
8
  const billing_1 = require("../billing");
8
9
  const invoices_1 = require("../billing/invoices");
9
10
  const clients_1 = require("../clients");
10
11
  const configs_1 = require("../configs");
12
+ const documents_1 = require("../documents");
11
13
  const emulation_handshakes_1 = require("../emulation-handshakes");
12
14
  const feature_requests_1 = require("../feature-requests");
13
15
  const leave_1 = require("../leave");
@@ -16,11 +18,13 @@ const errors_1 = require("../errors");
16
18
  const organization_invites_1 = require("../organization-invites");
17
19
  const organization_payment_methods_1 = require("../organization-payment-methods");
18
20
  const organizations_1 = require("../organizations");
21
+ const payments_1 = require("../payments");
19
22
  const permissions_1 = require("../permissions");
20
23
  const staff_1 = require("../staff");
21
24
  const supporting_files_1 = require("../supporting-files");
22
25
  const user_api_credentials_1 = require("../user-api-credentials");
23
26
  const users_1 = require("../users");
27
+ const visibility_1 = require("../visibility");
24
28
  const webhook_events_1 = require("../webhook-events");
25
29
  /**
26
30
  * The organization-scoped half of the service client. Everything here is attributed to one
@@ -32,6 +36,13 @@ class SupabaseOrganizationScope {
32
36
  actor;
33
37
  audit;
34
38
  billing;
39
+ /**
40
+ * For the public pay page, which has no caller (the document behind a pay link, and its PDF),
41
+ * the Paystack webhook, and the daily send cap, which counts private documents too.
42
+ */
43
+ documents;
44
+ /** Money received: written here alone, by the Paystack webhook. */
45
+ payments;
35
46
  invoices;
36
47
  invites;
37
48
  leaveTypes;
@@ -43,11 +54,15 @@ class SupabaseOrganizationScope {
43
54
  /** For the admin api's staff downloads: Storage's read policy names no staff, the service role passes it. */
44
55
  supportingFiles;
45
56
  users;
57
+ /** Who private records are shared with: listed and written here, after the api's own checks. */
58
+ visibility;
46
59
  constructor(supabase, actor, onAuditError) {
47
60
  this.supabase = supabase;
48
61
  this.actor = actor;
49
62
  this.audit = new audit_1.AuditResource(supabase, actor, onAuditError);
50
63
  this.billing = new billing_1.BillingResource(supabase, actor, this.audit);
64
+ this.documents = new documents_1.DocumentsResource(supabase, actor, this.audit);
65
+ this.payments = new payments_1.PaymentsResource(supabase, actor, this.audit);
51
66
  this.invoices = new invoices_1.InvoicesResource(supabase, actor, this.audit);
52
67
  this.invites = new organization_invites_1.OrganizationInvitesResource(supabase, actor);
53
68
  // The leave-year rollover writes next period's entitlements as the system (`mms_id: null`).
@@ -59,6 +74,7 @@ class SupabaseOrganizationScope {
59
74
  this.permissions = new permissions_1.PermissionsResource(supabase, actor, this.audit);
60
75
  this.supportingFiles = new supporting_files_1.SupportingFilesResource(supabase, actor, this.audit);
61
76
  this.users = new users_1.UsersResource(supabase, actor, this.audit);
77
+ this.visibility = new visibility_1.VisibilityResource(supabase, actor, this.audit);
62
78
  }
63
79
  /**
64
80
  * An audited table bound to this organization and actor - the shape the api hand-rolls today
@@ -68,6 +84,23 @@ class SupabaseOrganizationScope {
68
84
  table(options) {
69
85
  return (0, audit_1.auditedTable)(this.supabase, this.actor, this.audit, options);
70
86
  }
87
+ /**
88
+ * The organization's templates whose `column` starts with `start` (literally, any case),
89
+ * private and archived ones included. Service role because both columns are unique across the
90
+ * whole organization (`uq_document_templates_organization_name`,
91
+ * `uq_document_templates_organization_prefix`) while a caller reads only the templates shared
92
+ * with them.
93
+ */
94
+ async templatesStartingWith(column, start) {
95
+ const { data, error } = await this.supabase
96
+ .from('document_templates')
97
+ .select('mms_id, name, number_prefix')
98
+ .eq('organization_id', this.actor.organization_id)
99
+ .ilike(column, `${start.replace(/[\\%_]/g, '\\$&')}%`);
100
+ if (error)
101
+ throw (0, errors_1.mapPostgrestError)(error);
102
+ return (data ?? []);
103
+ }
71
104
  /**
72
105
  * Gives `userId` a new leave calendar feed token (stored as its SHA-256), which also retires
73
106
  * any address they subscribed with before. Service role only: the token is a credential.
@@ -266,6 +299,50 @@ class SupabaseServiceClient {
266
299
  throw (0, errors_1.mapPostgrestError)(error);
267
300
  return (data ?? null);
268
301
  }
302
+ /**
303
+ * The recurring parents due a draft: not paused, not ended, `repeat_next_at` on or before
304
+ * `onOrBefore` (YYYY-MM-DD). Across every organization, like the leave rollover's tenants - the
305
+ * cron then works inside each row's own organization.
306
+ */
307
+ async documentsDueForDraft(onOrBefore) {
308
+ const { data, error } = await this.supabase
309
+ .from('documents')
310
+ .select('mms_id, organization_id')
311
+ .not('repeat', 'is', null)
312
+ .eq('repeat_paused', false)
313
+ .eq('status', enum_2.DocumentStatus.DRAFT)
314
+ .lte('repeat_next_at', onOrBefore);
315
+ if (error)
316
+ throw (0, errors_1.mapPostgrestError)(error);
317
+ return (data ?? []);
318
+ }
319
+ /** The drafts due to issue: `issue_at` on or before `onOrBefore`, across every organization. */
320
+ async documentsDueToIssue(onOrBefore) {
321
+ const { data, error } = await this.supabase
322
+ .from('documents')
323
+ .select('mms_id, organization_id')
324
+ .eq('status', enum_2.DocumentStatus.DRAFT)
325
+ .is('repeat', null)
326
+ .lte('issue_at', onOrBefore);
327
+ if (error)
328
+ throw (0, errors_1.mapPostgrestError)(error);
329
+ return (data ?? []);
330
+ }
331
+ /**
332
+ * Which organization a document belongs to, by its id alone. Unscoped by necessity, like the
333
+ * calendar feed: a public pay link (whose signature the api has checked) names only the
334
+ * document, and the row is what says which tenant it belongs to.
335
+ */
336
+ async findDocumentOrganization(documentId) {
337
+ const { data, error } = await this.supabase
338
+ .from('documents')
339
+ .select('organization_id')
340
+ .eq('mms_id', documentId)
341
+ .maybeSingle();
342
+ if (error)
343
+ throw (0, errors_1.mapPostgrestError)(error);
344
+ return data?.organization_id ?? null;
345
+ }
269
346
  /**
270
347
  * Deletes every audit row written before `cutoff` and says how many went - the daily
271
348
  * retention cron. Every tenant at once: the period is the same for all of them. Batched by
@@ -3,6 +3,7 @@ import { AuditResource } from './audit';
3
3
  import { AdditionalDetailsResource } from './additional-details';
4
4
  import type { SupabaseClientConfig, TypedSupabaseClient } from './client';
5
5
  import { ClientsResource } from './clients';
6
+ import { DocumentsResource, DocumentTemplatesResource } from './documents';
6
7
  import { LeadsResource } from './leads';
7
8
  import { NotesResource } from './notes';
8
9
  import { OrganizationsResource } from './organizations';
@@ -31,6 +32,8 @@ export declare class SupabaseStaffClient {
31
32
  readonly notes: NotesResource;
32
33
  readonly projects: ProjectsResource;
33
34
  readonly tasks: TasksResource;
35
+ readonly documentTemplates: DocumentTemplatesResource;
36
+ readonly documents: DocumentsResource;
34
37
  readonly users: UsersResource;
35
38
  readonly organizations: OrganizationsResource;
36
39
  readonly staff: StaffResource;
@@ -5,6 +5,7 @@ const auth_1 = require("./auth");
5
5
  const audit_1 = require("./audit");
6
6
  const additional_details_1 = require("./additional-details");
7
7
  const clients_1 = require("./clients");
8
+ const documents_1 = require("./documents");
8
9
  const leads_1 = require("./leads");
9
10
  const notes_1 = require("./notes");
10
11
  const organizations_1 = require("./organizations");
@@ -30,6 +31,8 @@ class SupabaseStaffClient {
30
31
  notes;
31
32
  projects;
32
33
  tasks;
34
+ documentTemplates;
35
+ documents;
33
36
  users;
34
37
  organizations;
35
38
  staff;
@@ -47,6 +50,8 @@ class SupabaseStaffClient {
47
50
  this.notes = new notes_1.NotesResource(this.supabase, auth, this.audit);
48
51
  this.projects = new projects_1.ProjectsResource(this.supabase, auth, this.audit);
49
52
  this.tasks = new tasks_1.TasksResource(this.supabase, auth, this.audit);
53
+ this.documentTemplates = new documents_1.DocumentTemplatesResource(this.supabase, auth, this.audit);
54
+ this.documents = new documents_1.DocumentsResource(this.supabase, auth, this.audit);
50
55
  this.users = new users_1.UsersResource(this.supabase, auth, this.audit);
51
56
  this.organizations = new organizations_1.OrganizationsResource(this.supabase, auth, this.audit);
52
57
  this.staff = new staff_1.StaffResource(this.supabase);
@@ -24,8 +24,6 @@ const tenancy_1 = require("../tenancy");
24
24
  const paths_1 = require("./paths");
25
25
  __exportStar(require("./dto"), exports);
26
26
  __exportStar(require("./paths"), exports);
27
- /** Storage bucket every supporting file lives in. */
28
- const BUCKET = 'supporting-files';
29
27
  /** How long a presigned download URL stays valid. */
30
28
  const SIGNED_URL_TTL_SECONDS = 300;
31
29
  /** Storage calls per file (minting an upload URL, checking an object) run concurrently in batches of this size. */
@@ -73,7 +71,7 @@ class SupportingFilesResource {
73
71
  if (error)
74
72
  throw (0, errors_1.mapPostgrestError)(error);
75
73
  const { data: signed, error: signedError } = await this.supabase.storage
76
- .from(BUCKET)
74
+ .from(paths_1.SUPPORTING_FILES_BUCKET)
77
75
  .createSignedUrl((0, paths_1.storagePath)(data.organization_id, data.entity_type, data.entity_id, data.name, data.extension), SIGNED_URL_TTL_SECONDS);
78
76
  if (signedError)
79
77
  throw (0, errors_1.mapStorageError)(signedError);
@@ -112,7 +110,7 @@ class SupportingFilesResource {
112
110
  for (let index = 0; index < files.length; index += BATCH_SIZE) {
113
111
  targets.push(...(await Promise.all(files.slice(index, index + BATCH_SIZE).map(async ({ name, extension, path }) => {
114
112
  const { data, error } = await this.supabase.storage
115
- .from(BUCKET)
113
+ .from(paths_1.SUPPORTING_FILES_BUCKET)
116
114
  .createSignedUploadUrl(path, { upsert: true });
117
115
  if (error) {
118
116
  throw (0, errors_1.mapStorageError)(error, `Failed to prepare upload: ${fileNameOf({ name, extension })}`);
@@ -134,7 +132,7 @@ class SupportingFilesResource {
134
132
  await Promise.all(files.slice(index, index + BATCH_SIZE).map(async ({ name, extension, path }) => {
135
133
  // A missing object comes back as `data: false` (storage-js keeps the 404 in `error`);
136
134
  // any other failure is thrown by storage-js itself.
137
- const { data: present } = await this.supabase.storage.from(BUCKET).exists(path);
135
+ const { data: present } = await this.supabase.storage.from(paths_1.SUPPORTING_FILES_BUCKET).exists(path);
138
136
  if (!present) {
139
137
  throw new errors_1.SupabaseClientError({
140
138
  status: 400,
@@ -171,7 +169,7 @@ class SupportingFilesResource {
171
169
  // The row goes first - the helper's pre-read hands back what the object path is built from.
172
170
  const data = await this.table.remove(supportingFileId);
173
171
  const { error: storageError } = await this.supabase.storage
174
- .from(BUCKET)
172
+ .from(paths_1.SUPPORTING_FILES_BUCKET)
175
173
  .remove([
176
174
  (0, paths_1.storagePath)((0, tenancy_1.requireOrganization)(this.auth), data.entity_type, data.entity_id, data.name, data.extension),
177
175
  ]);
@@ -1,3 +1,5 @@
1
+ /** Storage bucket every supporting file lives in, and every document image. */
2
+ export declare const SUPPORTING_FILES_BUCKET = "supporting-files";
1
3
  /**
2
4
  * The client-supplied file name, checked before it is allowed anywhere near an object key.
3
5
  *
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.storagePath = exports.splitFileName = exports.sanitizeFileName = void 0;
3
+ exports.storagePath = exports.splitFileName = exports.sanitizeFileName = exports.SUPPORTING_FILES_BUCKET = void 0;
4
4
  const errors_1 = require("../errors");
5
+ /** Storage bucket every supporting file lives in, and every document image. */
6
+ exports.SUPPORTING_FILES_BUCKET = 'supporting-files';
5
7
  /** Longest object-key segment a file name may become. */
6
8
  const MAX_FILE_NAME_LENGTH = 255;
7
9
  /** What a file name may be made of - no separators, no control characters, no leading dot. */
@@ -26,3 +26,15 @@ export declare function IsNullable(validationOptions?: ValidationOptions): Prope
26
26
  * blank submission tripping it. Goes above `IsNullable()`, which then skips the rules.
27
27
  */
28
28
  export declare function EmptyToNull(): PropertyDecorator;
29
+ /**
30
+ * Whitespace on either side of a pasted value is not part of the answer. Trimming here rather
31
+ * than in the api means the rules measure what will actually be stored - a name of three spaces
32
+ * is empty, not three characters long. Goes above `EmptyToNull()`, so a blank box becomes null.
33
+ */
34
+ export declare function Trim(): PropertyDecorator;
35
+ /**
36
+ * A class-validator decorator from a check that returns the first problem it finds, or null. The
37
+ * problem is the message, so a 400 always says exactly what is wrong and where. `object` is the
38
+ * DTO being validated, for a check that reads a sibling field.
39
+ */
40
+ export declare const fromProblem: (name: string, problem: (value: unknown, object: Record<string, unknown>) => string | null) => () => PropertyDecorator;
@@ -6,9 +6,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  return c > 3 && r && Object.defineProperty(target, key, r), r;
7
7
  };
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.MatchesPasswordRules = exports.IsClockTime = exports.isClockTime = exports.IsCalendarDate = exports.isCalendarDate = void 0;
9
+ exports.fromProblem = exports.MatchesPasswordRules = exports.IsClockTime = exports.isClockTime = exports.IsCalendarDate = exports.isCalendarDate = void 0;
10
10
  exports.IsNullable = IsNullable;
11
11
  exports.EmptyToNull = EmptyToNull;
12
+ exports.Trim = Trim;
12
13
  const class_validator_1 = require("class-validator");
13
14
  const class_transformer_1 = require("class-transformer");
14
15
  const CALENDAR_DATE = /^\d{4}-\d{2}-\d{2}$/;
@@ -72,3 +73,24 @@ function IsNullable(validationOptions) {
72
73
  function EmptyToNull() {
73
74
  return (0, class_transformer_1.Transform)(({ value }) => (value === '' ? null : value), { toClassOnly: true });
74
75
  }
76
+ /**
77
+ * Whitespace on either side of a pasted value is not part of the answer. Trimming here rather
78
+ * than in the api means the rules measure what will actually be stored - a name of three spaces
79
+ * is empty, not three characters long. Goes above `EmptyToNull()`, so a blank box becomes null.
80
+ */
81
+ function Trim() {
82
+ return (0, class_transformer_1.Transform)(({ value }) => (typeof value === 'string' ? value.trim() : value), { toClassOnly: true });
83
+ }
84
+ /**
85
+ * A class-validator decorator from a check that returns the first problem it finds, or null. The
86
+ * problem is the message, so a 400 always says exactly what is wrong and where. `object` is the
87
+ * DTO being validated, for a check that reads a sibling field.
88
+ */
89
+ const fromProblem = (name, problem) => () => (0, class_validator_1.ValidateBy)({
90
+ name,
91
+ validator: {
92
+ validate: (value, args) => problem(value, args?.object) === null,
93
+ defaultMessage: (args) => problem(args?.value, args?.object) ?? 'Invalid value',
94
+ },
95
+ });
96
+ exports.fromProblem = fromProblem;
@@ -0,0 +1,15 @@
1
+ import { DocumentVisibility } from '@managemint-solutions/entities/documents/enum';
2
+ import type { UpdateVisibilityDto as UpdateVisibilityDtoType } from '@managemint-solutions/entities/visibility/dto';
3
+ import { VisibilityRole } from '@managemint-solutions/entities/visibility/enum';
4
+ /**
5
+ * A record's setting and its whole share list, replaced together. That the people belong to the
6
+ * organization and the creator is not among them is the api's check (the composite key refuses
7
+ * another organization's user too).
8
+ */
9
+ export declare class UpdateVisibilityDto implements UpdateVisibilityDtoType {
10
+ readonly visibility: DocumentVisibility;
11
+ readonly shares: {
12
+ user_id: string;
13
+ role: VisibilityRole;
14
+ }[];
15
+ }