@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.
- package/README.md +209 -7
- package/dist/audit/document-changes.d.ts +39 -0
- package/dist/audit/document-changes.js +332 -0
- package/dist/audit/index.d.ts +50 -2
- package/dist/audit/index.js +87 -44
- package/dist/client.d.ts +6 -0
- package/dist/client.js +9 -0
- package/dist/clients/dto.d.ts +18 -3
- package/dist/clients/dto.js +72 -2
- package/dist/clients/index.js +16 -0
- package/dist/configs/dto.js +4 -12
- package/dist/documents/content.d.ts +39 -0
- package/dist/documents/content.js +443 -0
- package/dist/documents/document-templates.d.ts +37 -0
- package/dist/documents/document-templates.js +143 -0
- package/dist/documents/documents.d.ts +154 -0
- package/dist/documents/documents.js +330 -0
- package/dist/documents/dto.d.ts +138 -0
- package/dist/documents/dto.js +594 -0
- package/dist/documents/index.d.ts +4 -0
- package/dist/documents/index.js +24 -0
- package/dist/documents/render/html.d.ts +64 -0
- package/dist/documents/render/html.js +413 -0
- package/dist/documents/render/index.d.ts +2 -0
- package/dist/documents/render/index.js +23 -0
- package/dist/documents/render/values.d.ts +86 -0
- package/dist/documents/render/values.js +142 -0
- package/dist/feature-requests/dto.js +4 -13
- package/dist/index.d.ts +8 -1
- package/dist/index.js +31 -3
- package/dist/leads/index.js +1 -3
- package/dist/notifications/index.d.ts +6 -0
- package/dist/notifications/index.js +16 -0
- package/dist/organization-payment-methods/dto.d.ts +10 -0
- package/dist/organization-payment-methods/dto.js +56 -0
- package/dist/organization-payment-methods/index.d.ts +8 -1
- package/dist/organization-payment-methods/index.js +23 -0
- package/dist/payments/index.d.ts +67 -0
- package/dist/payments/index.js +137 -0
- package/dist/permissions/dto.d.ts +4 -0
- package/dist/permissions/dto.js +20 -8
- package/dist/service-client/index.d.ts +44 -0
- package/dist/service-client/index.js +77 -0
- package/dist/staff-client.d.ts +3 -0
- package/dist/staff-client.js +5 -0
- package/dist/supporting-files/index.js +4 -6
- package/dist/supporting-files/paths.d.ts +2 -0
- package/dist/supporting-files/paths.js +3 -1
- package/dist/validators.d.ts +12 -0
- package/dist/validators.js +23 -1
- package/dist/visibility/dto.d.ts +15 -0
- package/dist/visibility/dto.js +68 -0
- package/dist/visibility/index.d.ts +59 -0
- package/dist/visibility/index.js +165 -0
- 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;
|
package/dist/permissions/dto.js
CHANGED
|
@@ -42,14 +42,10 @@ class UpdatePermissionsDto {
|
|
|
42
42
|
read_timesheets;
|
|
43
43
|
update_timesheets;
|
|
44
44
|
delete_timesheets;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
package/dist/staff-client.d.ts
CHANGED
|
@@ -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;
|
package/dist/staff-client.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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,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. */
|
package/dist/validators.d.ts
CHANGED
|
@@ -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;
|
package/dist/validators.js
CHANGED
|
@@ -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
|
+
}
|