@managemint-solutions/sdk 0.55.0 → 0.56.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.
@@ -1,7 +1,7 @@
1
1
  import type { NoteEntity } from '@managemint-solutions/entities/notes';
2
2
  import type { AddNoteDto } from '@managemint-solutions/entities/notes/dto';
3
3
  import { NotesEntityTypes } from '@managemint-solutions/entities/notes/enum';
4
- import type { AuthContext } from '../auth';
4
+ import type { ClientContext } from '../auth';
5
5
  import { type AuditResource } from '../audit';
6
6
  import type { TypedSupabaseClient } from '../client';
7
7
  export * from './dto';
@@ -9,7 +9,7 @@ export declare class NotesResource {
9
9
  private readonly supabase;
10
10
  private readonly auth;
11
11
  private readonly table;
12
- constructor(supabase: TypedSupabaseClient, auth: AuthContext, audit: AuditResource);
12
+ constructor(supabase: TypedSupabaseClient, auth: ClientContext, audit: AuditResource);
13
13
  getById(noteId: string): Promise<NoteEntity>;
14
14
  list(entity: NotesEntityTypes, entityId: string): Promise<NoteEntity[]>;
15
15
  create(entity: NotesEntityTypes, entityId: string, input: AddNoteDto): Promise<NoteEntity>;
@@ -20,6 +20,7 @@ const enum_2 = require("@managemint-solutions/entities/audit-trail/enum");
20
20
  const audit_1 = require("../audit");
21
21
  const errors_1 = require("../errors");
22
22
  const won_1 = require("../leads/won");
23
+ const tenancy_1 = require("../tenancy");
23
24
  __exportStar(require("./dto"), exports);
24
25
  // The public contract is the shared NoteEntity from @managemint-solutions/entities; the casts at
25
26
  // the returns declare the shape the select string below produces (the columns are defined in the
@@ -38,10 +39,7 @@ class NotesResource {
38
39
  });
39
40
  }
40
41
  async getById(noteId) {
41
- const { data, error } = await this.supabase
42
- .from('notes')
43
- .select(NOTE_SELECT)
44
- .eq('organization_id', this.auth.organization_id)
42
+ const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('notes').select(NOTE_SELECT), this.auth)
45
43
  .eq('mms_id', noteId)
46
44
  .single();
47
45
  if (error)
@@ -49,10 +47,7 @@ class NotesResource {
49
47
  return data;
50
48
  }
51
49
  async list(entity, entityId) {
52
- const { data, error } = await this.supabase
53
- .from('notes')
54
- .select(NOTE_SELECT)
55
- .eq('organization_id', this.auth.organization_id)
50
+ const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('notes').select(NOTE_SELECT), this.auth)
56
51
  .eq('entity_type', entity)
57
52
  .eq('entity_id', entityId)
58
53
  .order('created_at', { ascending: false });
@@ -61,14 +56,15 @@ class NotesResource {
61
56
  return data;
62
57
  }
63
58
  async create(entity, entityId, input) {
59
+ const organizationId = (0, tenancy_1.requireOrganization)(this.auth);
64
60
  if (entity === enum_1.NotesEntityTypes.LEAD) {
65
- await (0, won_1.refuseWonLead)(this.supabase, this.auth.organization_id, entityId);
61
+ await (0, won_1.refuseWonLead)(this.supabase, organizationId, entityId);
66
62
  }
67
63
  const row = await this.table.insert({
68
64
  content: input.content,
69
65
  entity_type: entity,
70
66
  entity_id: entityId,
71
- organization_id: this.auth.organization_id,
67
+ organization_id: organizationId,
72
68
  user_id: this.auth.mms_id,
73
69
  });
74
70
  return this.getById(row.mms_id);
@@ -13,6 +13,7 @@ import { OrganizationPaymentMethodsResource, type PaymentMethodLookupColumn } fr
13
13
  import { OrganizationsResource, OrganizationSignupResource } from '../organizations';
14
14
  import { PermissionsResource } from '../permissions';
15
15
  import { StaffPermissionsResource, StaffResource } from '../staff';
16
+ import { SupportingFilesResource } from '../supporting-files';
16
17
  import { UserApiCredentialsResource } from '../user-api-credentials';
17
18
  import { UsersResource } from '../users';
18
19
  import { WebhookEventsResource } from '../webhook-events';
@@ -41,6 +42,8 @@ export declare class SupabaseOrganizationScope {
41
42
  readonly organizations: OrganizationsResource;
42
43
  readonly paymentMethod: OrganizationPaymentMethodsResource;
43
44
  readonly permissions: PermissionsResource;
45
+ /** For the admin api's staff downloads: Storage's read policy names no staff, the service role passes it. */
46
+ readonly supportingFiles: SupportingFilesResource;
44
47
  readonly users: UsersResource;
45
48
  constructor(supabase: TypedSupabaseClient, actor: AuditActor, onAuditError?: AuditErrorHandler);
46
49
  /**
@@ -18,6 +18,7 @@ const organization_payment_methods_1 = require("../organization-payment-methods"
18
18
  const organizations_1 = require("../organizations");
19
19
  const permissions_1 = require("../permissions");
20
20
  const staff_1 = require("../staff");
21
+ const supporting_files_1 = require("../supporting-files");
21
22
  const user_api_credentials_1 = require("../user-api-credentials");
22
23
  const users_1 = require("../users");
23
24
  const webhook_events_1 = require("../webhook-events");
@@ -39,6 +40,8 @@ class SupabaseOrganizationScope {
39
40
  organizations;
40
41
  paymentMethod;
41
42
  permissions;
43
+ /** For the admin api's staff downloads: Storage's read policy names no staff, the service role passes it. */
44
+ supportingFiles;
42
45
  users;
43
46
  constructor(supabase, actor, onAuditError) {
44
47
  this.supabase = supabase;
@@ -54,6 +57,7 @@ class SupabaseOrganizationScope {
54
57
  this.organizations = new organizations_1.OrganizationsResource(supabase, actor, this.audit);
55
58
  this.paymentMethod = new organization_payment_methods_1.OrganizationPaymentMethodsResource(supabase, actor);
56
59
  this.permissions = new permissions_1.PermissionsResource(supabase, actor, this.audit);
60
+ this.supportingFiles = new supporting_files_1.SupportingFilesResource(supabase, actor, this.audit);
57
61
  this.users = new users_1.UsersResource(supabase, actor, this.audit);
58
62
  }
59
63
  /**
@@ -1,11 +1,14 @@
1
1
  import { type StaffContext } from './auth';
2
2
  import { AuditResource } from './audit';
3
+ import { AdditionalDetailsResource } from './additional-details';
3
4
  import type { SupabaseClientConfig, TypedSupabaseClient } from './client';
4
5
  import { ClientsResource } from './clients';
5
6
  import { LeadsResource } from './leads';
7
+ import { NotesResource } from './notes';
6
8
  import { OrganizationsResource } from './organizations';
7
9
  import { ProjectsResource } from './projects';
8
10
  import { StaffPermissionsResource, StaffResource } from './staff';
11
+ import { SupportingFilesResource } from './supporting-files';
9
12
  import { TasksResource } from './tasks';
10
13
  import { UserApiCredentialsReads } from './user-api-credentials';
11
14
  import { UsersResource } from './users';
@@ -22,14 +25,18 @@ export declare class SupabaseStaffClient {
22
25
  /** The staff member the token belongs to - the caller, for a service that records who acted. */
23
26
  readonly auth: StaffContext;
24
27
  readonly audit: AuditResource;
28
+ readonly additionalDetails: AdditionalDetailsResource;
25
29
  readonly clients: ClientsResource;
26
30
  readonly leads: LeadsResource;
31
+ readonly notes: NotesResource;
27
32
  readonly projects: ProjectsResource;
28
33
  readonly tasks: TasksResource;
29
34
  readonly users: UsersResource;
30
35
  readonly organizations: OrganizationsResource;
31
36
  readonly staff: StaffResource;
32
37
  readonly staffPermissions: StaffPermissionsResource;
38
+ /** Lists and parents only: Storage names no staff, so a download is signed on the service client. */
39
+ readonly supportingFiles: SupportingFilesResource;
33
40
  readonly userApiCredentials: UserApiCredentialsReads;
34
41
  constructor({ url, key, accessToken, auth, client }: SupabaseStaffClientConfig);
35
42
  }
@@ -6,9 +6,11 @@ const audit_1 = require("./audit");
6
6
  const additional_details_1 = require("./additional-details");
7
7
  const clients_1 = require("./clients");
8
8
  const leads_1 = require("./leads");
9
+ const notes_1 = require("./notes");
9
10
  const organizations_1 = require("./organizations");
10
11
  const projects_1 = require("./projects");
11
12
  const staff_1 = require("./staff");
13
+ const supporting_files_1 = require("./supporting-files");
12
14
  const tasks_1 = require("./tasks");
13
15
  const user_api_credentials_1 = require("./user-api-credentials");
14
16
  const users_1 = require("./users");
@@ -22,28 +24,34 @@ class SupabaseStaffClient {
22
24
  /** The staff member the token belongs to - the caller, for a service that records who acted. */
23
25
  auth;
24
26
  audit;
27
+ additionalDetails;
25
28
  clients;
26
29
  leads;
30
+ notes;
27
31
  projects;
28
32
  tasks;
29
33
  users;
30
34
  organizations;
31
35
  staff;
32
36
  staffPermissions;
37
+ /** Lists and parents only: Storage names no staff, so a download is signed on the service client. */
38
+ supportingFiles;
33
39
  userApiCredentials;
34
40
  constructor({ url, key, accessToken, auth, client }) {
35
41
  this.supabase = client ?? (0, auth_1.createRawClient)(url, key, accessToken);
36
42
  this.auth = auth;
37
43
  this.audit = new audit_1.AuditResource(this.supabase, auth);
38
44
  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);
45
+ this.additionalDetails = new additional_details_1.AdditionalDetailsResource(this.supabase, auth, this.audit);
46
+ this.leads = new leads_1.LeadsResource(this.supabase, auth, this.audit, this.additionalDetails);
47
+ this.notes = new notes_1.NotesResource(this.supabase, auth, this.audit);
41
48
  this.projects = new projects_1.ProjectsResource(this.supabase, auth, this.audit);
42
49
  this.tasks = new tasks_1.TasksResource(this.supabase, auth, this.audit);
43
50
  this.users = new users_1.UsersResource(this.supabase, auth, this.audit);
44
51
  this.organizations = new organizations_1.OrganizationsResource(this.supabase, auth, this.audit);
45
52
  this.staff = new staff_1.StaffResource(this.supabase);
46
53
  this.staffPermissions = new staff_1.StaffPermissionsResource(this.supabase);
54
+ this.supportingFiles = new supporting_files_1.SupportingFilesResource(this.supabase, auth, this.audit);
47
55
  this.userApiCredentials = new user_api_credentials_1.UserApiCredentialsReads(this.supabase);
48
56
  }
49
57
  }
@@ -1,7 +1,6 @@
1
1
  import type { SupportingFileEntity, SupportingFilePresignedUrl, SupportingFileUploadTarget } from '@managemint-solutions/entities/supporting-files';
2
2
  import { SupportingFilesEntityTypes } from '@managemint-solutions/entities/supporting-files/enum';
3
- import type { AuthContext } from '../auth';
4
- import { type AuditResource } from '../audit';
3
+ import { type AuditResource, type ResourceActor } from '../audit';
5
4
  import type { TypedSupabaseClient } from '../client';
6
5
  export * from './dto';
7
6
  export * from './paths';
@@ -9,12 +8,21 @@ export declare class SupportingFilesResource {
9
8
  private readonly supabase;
10
9
  private readonly auth;
11
10
  private readonly table;
12
- constructor(supabase: TypedSupabaseClient, auth: AuthContext, audit: AuditResource);
11
+ constructor(supabase: TypedSupabaseClient, auth: ResourceActor, audit: AuditResource);
12
+ /**
13
+ * A short-lived download link. Storage checks the bucket's read policy as this client's
14
+ * caller, which names no staff - the admin api signs on the service client's organization
15
+ * scope instead.
16
+ */
13
17
  getPresignedUrl(supportingFileId: string): Promise<SupportingFilePresignedUrl>;
14
- /** The record a file hangs off - what the api checks the caller can reach before a download or delete. */
18
+ /**
19
+ * The record a file hangs off, and its organization - what the api checks the caller can
20
+ * reach before a download or delete.
21
+ */
15
22
  getParent(supportingFileId: string): Promise<{
16
23
  entity_type: SupportingFilesEntityTypes;
17
24
  entity_id: string;
25
+ organization_id: string;
18
26
  }>;
19
27
  list(entity: SupportingFilesEntityTypes, entityId: string): Promise<SupportingFileEntity[]>;
20
28
  /**
@@ -20,6 +20,7 @@ const enum_2 = require("@managemint-solutions/entities/audit-trail/enum");
20
20
  const audit_1 = require("../audit");
21
21
  const errors_1 = require("../errors");
22
22
  const won_1 = require("../leads/won");
23
+ const tenancy_1 = require("../tenancy");
23
24
  const paths_1 = require("./paths");
24
25
  __exportStar(require("./dto"), exports);
25
26
  __exportStar(require("./paths"), exports);
@@ -60,41 +61,40 @@ class SupportingFilesResource {
60
61
  entityType: enum_2.AuditTrailEntityType.SUPPORTING_FILES,
61
62
  });
62
63
  }
64
+ /**
65
+ * A short-lived download link. Storage checks the bucket's read policy as this client's
66
+ * caller, which names no staff - the admin api signs on the service client's organization
67
+ * scope instead.
68
+ */
63
69
  async getPresignedUrl(supportingFileId) {
64
- const { data, error } = await this.supabase
65
- .from('supporting_files')
66
- .select('name, extension, entity_type, entity_id')
70
+ const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('supporting_files').select('organization_id, name, extension, entity_type, entity_id'), this.auth)
67
71
  .eq('mms_id', supportingFileId)
68
- .eq('organization_id', this.auth.organization_id)
69
72
  .single();
70
73
  if (error)
71
74
  throw (0, errors_1.mapPostgrestError)(error);
72
75
  const { data: signed, error: signedError } = await this.supabase.storage
73
76
  .from(BUCKET)
74
- .createSignedUrl((0, paths_1.storagePath)(this.auth.organization_id, data.entity_type, data.entity_id, data.name, data.extension), SIGNED_URL_TTL_SECONDS);
77
+ .createSignedUrl((0, paths_1.storagePath)(data.organization_id, data.entity_type, data.entity_id, data.name, data.extension), SIGNED_URL_TTL_SECONDS);
75
78
  if (signedError)
76
79
  throw (0, errors_1.mapStorageError)(signedError);
77
80
  return { url: signed.signedUrl, ttl: SIGNED_URL_TTL_SECONDS };
78
81
  }
79
- /** The record a file hangs off - what the api checks the caller can reach before a download or delete. */
82
+ /**
83
+ * The record a file hangs off, and its organization - what the api checks the caller can
84
+ * reach before a download or delete.
85
+ */
80
86
  async getParent(supportingFileId) {
81
- const { data, error } = await this.supabase
82
- .from('supporting_files')
83
- .select('entity_type, entity_id')
87
+ const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('supporting_files').select('entity_type, entity_id, organization_id'), this.auth)
84
88
  .eq('mms_id', supportingFileId)
85
- .eq('organization_id', this.auth.organization_id)
86
89
  .single();
87
90
  if (error)
88
91
  throw (0, errors_1.mapPostgrestError)(error);
89
92
  return data;
90
93
  }
91
94
  async list(entity, entityId) {
92
- const { data, error } = await this.supabase
93
- .from('supporting_files')
94
- .select('mms_id, name, extension')
95
+ const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('supporting_files').select('mms_id, name, extension'), this.auth)
95
96
  .eq('entity_type', entity)
96
97
  .eq('entity_id', entityId)
97
- .eq('organization_id', this.auth.organization_id)
98
98
  .order('created_at', { ascending: false });
99
99
  if (error)
100
100
  throw (0, errors_1.mapPostgrestError)(error);
@@ -128,6 +128,7 @@ class SupportingFilesResource {
128
128
  * object was refreshed in place). Returns the record's whole list.
129
129
  */
130
130
  async record(entity, entityId, names) {
131
+ const organizationId = (0, tenancy_1.requireOrganization)(this.auth);
131
132
  const files = await this.prepare(entity, entityId, names);
132
133
  for (let index = 0; index < files.length; index += BATCH_SIZE) {
133
134
  await Promise.all(files.slice(index, index + BATCH_SIZE).map(async ({ name, extension, path }) => {
@@ -148,7 +149,7 @@ class SupportingFilesResource {
148
149
  .select('name, extension')
149
150
  .eq('entity_type', entity)
150
151
  .eq('entity_id', entityId)
151
- .eq('organization_id', this.auth.organization_id);
152
+ .eq('organization_id', organizationId);
152
153
  if (existingError)
153
154
  throw (0, errors_1.mapPostgrestError)(existingError);
154
155
  const existingFileNames = existing.map(fileNameOf);
@@ -159,7 +160,7 @@ class SupportingFilesResource {
159
160
  extension,
160
161
  entity_type: entity,
161
162
  entity_id: entityId,
162
- organization_id: this.auth.organization_id,
163
+ organization_id: organizationId,
163
164
  created_at: new Date().toISOString(),
164
165
  created_by: this.auth.mms_id,
165
166
  })));
@@ -172,7 +173,7 @@ class SupportingFilesResource {
172
173
  const { error: storageError } = await this.supabase.storage
173
174
  .from(BUCKET)
174
175
  .remove([
175
- (0, paths_1.storagePath)(this.auth.organization_id, data.entity_type, data.entity_id, data.name, data.extension),
176
+ (0, paths_1.storagePath)((0, tenancy_1.requireOrganization)(this.auth), data.entity_type, data.entity_id, data.name, data.extension),
176
177
  ]);
177
178
  if (storageError) {
178
179
  const fileName = fileNameOf({
@@ -188,8 +189,9 @@ class SupportingFilesResource {
188
189
  * are one key. A converted lead takes no new files.
189
190
  */
190
191
  async prepare(entity, entityId, names) {
192
+ const organizationId = (0, tenancy_1.requireOrganization)(this.auth);
191
193
  if (entity === enum_1.SupportingFilesEntityTypes.LEAD) {
192
- await (0, won_1.refuseWonLead)(this.supabase, this.auth.organization_id, entityId);
194
+ await (0, won_1.refuseWonLead)(this.supabase, organizationId, entityId);
193
195
  }
194
196
  return names.map((fileName) => {
195
197
  const { name, extension } = (0, paths_1.splitFileName)((0, paths_1.sanitizeFileName)(fileName));
@@ -203,7 +205,7 @@ class SupportingFilesResource {
203
205
  return {
204
206
  name,
205
207
  extension: extension,
206
- path: (0, paths_1.storagePath)(this.auth.organization_id, entity, entityId, name, extension),
208
+ path: (0, paths_1.storagePath)(organizationId, entity, entityId, name, extension),
207
209
  };
208
210
  });
209
211
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@managemint-solutions/sdk",
3
- "version": "0.55.0",
3
+ "version": "0.56.0",
4
4
  "description": "Typed Supabase data-access SDK for ManageMint Solutions",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Scott Bebington <scottbebington@gmail.com>",