@managemint-solutions/sdk 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +75 -3
  2. package/dist/additional-details/index.d.ts +2 -2
  3. package/dist/additional-details/index.js +6 -10
  4. package/dist/audit/index.d.ts +11 -5
  5. package/dist/audit/index.js +20 -12
  6. package/dist/auth-client/index.d.ts +2 -0
  7. package/dist/auth-client/index.js +12 -0
  8. package/dist/auth.d.ts +21 -0
  9. package/dist/auth.js +47 -3
  10. package/dist/clients/dto.d.ts +5 -1
  11. package/dist/clients/dto.js +12 -1
  12. package/dist/clients/index.d.ts +5 -3
  13. package/dist/clients/index.js +8 -8
  14. package/dist/emulation-handshakes/index.d.ts +13 -0
  15. package/dist/emulation-handshakes/index.js +36 -0
  16. package/dist/errors.js +2 -2
  17. package/dist/index.d.ts +13 -9
  18. package/dist/index.js +22 -4
  19. package/dist/leads/dto.d.ts +5 -1
  20. package/dist/leads/dto.js +12 -1
  21. package/dist/leads/index.d.ts +5 -3
  22. package/dist/leads/index.js +20 -19
  23. package/dist/nest/index.d.ts +26 -1
  24. package/dist/nest/index.js +58 -1
  25. package/dist/notifications/index.d.ts +1 -3
  26. package/dist/organizations/dto.d.ts +9 -1
  27. package/dist/organizations/dto.js +40 -1
  28. package/dist/organizations/index.d.ts +11 -3
  29. package/dist/organizations/index.js +30 -2
  30. package/dist/permissions/index.d.ts +6 -4
  31. package/dist/permissions/index.js +17 -12
  32. package/dist/projects/dto.d.ts +5 -1
  33. package/dist/projects/dto.js +12 -1
  34. package/dist/projects/index.d.ts +5 -3
  35. package/dist/projects/index.js +8 -8
  36. package/dist/service-client/index.d.ts +32 -1
  37. package/dist/service-client/index.js +54 -1
  38. package/dist/staff/dto.d.ts +14 -0
  39. package/dist/staff/dto.js +63 -0
  40. package/dist/staff/index.d.ts +50 -0
  41. package/dist/staff/index.js +139 -0
  42. package/dist/staff-client.d.ts +37 -0
  43. package/dist/staff-client.js +52 -0
  44. package/dist/tasks/dto.d.ts +5 -1
  45. package/dist/tasks/dto.js +12 -1
  46. package/dist/tasks/index.d.ts +5 -3
  47. package/dist/tasks/index.js +18 -19
  48. package/dist/tenancy.d.ts +13 -0
  49. package/dist/tenancy.js +25 -0
  50. package/dist/user-api-credentials/dto.d.ts +12 -0
  51. package/dist/user-api-credentials/dto.js +56 -0
  52. package/dist/user-api-credentials/index.d.ts +47 -0
  53. package/dist/user-api-credentials/index.js +101 -0
  54. package/dist/users/dto.d.ts +5 -1
  55. package/dist/users/dto.js +13 -2
  56. package/dist/users/index.d.ts +5 -3
  57. package/dist/users/index.js +13 -29
  58. package/dist/validators.d.ts +2 -0
  59. package/dist/validators.js +6 -1
  60. package/package.json +2 -2
@@ -0,0 +1,101 @@
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.UserApiCredentialsResource = exports.UserApiCredentialsReads = void 0;
18
+ const audit_1 = require("../audit");
19
+ const errors_1 = require("../errors");
20
+ __exportStar(require("./dto"), exports);
21
+ // Exactly the columns the staff grant covers: never `secret_hash`, which only the mint reads.
22
+ const CREDENTIAL_SELECT = 'mms_id, user_id, organization_id, client_id, label, created_at, created_by, expires_at, revoked_at, revoked_by, last_used_at';
23
+ /** A written row as a reader sees it: without the secret hash. */
24
+ const toCredential = (row) => {
25
+ const { secret_hash: _secretHash, ...credential } = row;
26
+ return credential;
27
+ };
28
+ /**
29
+ * Credentials as staff read them, on their own client, through the column grant that leaves the
30
+ * secret hash out. Tenant users have no grant at all.
31
+ */
32
+ class UserApiCredentialsReads {
33
+ supabase;
34
+ constructor(supabase) {
35
+ this.supabase = supabase;
36
+ }
37
+ /** A user's credentials, newest first. */
38
+ async listForUser(userId) {
39
+ const { data, error } = await this.supabase
40
+ .from('user_api_credentials')
41
+ .select(CREDENTIAL_SELECT)
42
+ .eq('user_id', userId)
43
+ .order('created_at', { ascending: false });
44
+ if (error)
45
+ throw (0, errors_1.mapPostgrestError)(error);
46
+ return (data ?? []);
47
+ }
48
+ }
49
+ exports.UserApiCredentialsReads = UserApiCredentialsReads;
50
+ /**
51
+ * The writes and the secret check, on the service client only: every write is the service
52
+ * role's, and nobody else may read the hash. A credential's writes are audited against its own
53
+ * organization as the system - the staff member who issued or revoked it is on the row itself
54
+ * (`created_by`, `revoked_by`), and is not a user an audit row can name.
55
+ */
56
+ class UserApiCredentialsResource {
57
+ supabase;
58
+ tableFor;
59
+ constructor(supabase, tableFor) {
60
+ this.supabase = supabase;
61
+ this.tableFor = tableFor;
62
+ }
63
+ /** Issues a credential. Its `secret_hash` is redacted in the audit row and never returned. */
64
+ async create(row) {
65
+ return toCredential(await this.tableFor(row.organization_id).insert(row));
66
+ }
67
+ /** The credential a client id names, with its secret hash. Null when none does. */
68
+ async findByClientId(clientId) {
69
+ const { data, error } = await this.supabase
70
+ .from('user_api_credentials')
71
+ .select('*')
72
+ .eq('client_id', clientId)
73
+ .maybeSingle();
74
+ if (error)
75
+ throw (0, errors_1.mapPostgrestError)(error);
76
+ return data ? (0, audit_1.withoutInternalId)(data) : null;
77
+ }
78
+ /** Revokes a live credential. Null when it was revoked already. */
79
+ async revoke(credentialId, revokedBy) {
80
+ // The audited table is the credential's organization's, so that is read first.
81
+ const { data, error } = await this.supabase
82
+ .from('user_api_credentials')
83
+ .select('organization_id')
84
+ .eq('mms_id', credentialId)
85
+ .single();
86
+ if (error)
87
+ throw (0, errors_1.mapPostgrestError)(error);
88
+ const row = await this.tableFor(data.organization_id).updateWhere(credentialId, (query) => query.is('revoked_at', null), { revoked_at: new Date().toISOString(), revoked_by: revokedBy });
89
+ return row ? toCredential(row) : null;
90
+ }
91
+ /** Stamps a successful mint, unaudited: when it was last used, not a change to the credential. */
92
+ async markUsed(credentialId) {
93
+ const { error } = await this.supabase
94
+ .from('user_api_credentials')
95
+ .update({ last_used_at: new Date().toISOString() })
96
+ .eq('mms_id', credentialId);
97
+ if (error)
98
+ throw (0, errors_1.mapPostgrestError)(error);
99
+ }
100
+ }
101
+ exports.UserApiCredentialsResource = UserApiCredentialsResource;
@@ -1,4 +1,4 @@
1
- import type { CreateUserDto as CreateUserDtoType, GetUsersDto as GetUsersDtoType, UpdateMyProfileDto as UpdateMyProfileDtoType, UpdateUserDto as UpdateUserDtoType, UserIdDto as UserIdDtoType } from '@managemint-solutions/entities/users/dto';
1
+ import type { CreateUserDto as CreateUserDtoType, GetUsersDto as GetUsersDtoType, StaffGetUsersDto as StaffGetUsersDtoType, UpdateMyProfileDto as UpdateMyProfileDtoType, UpdateUserDto as UpdateUserDtoType, UserIdDto as UserIdDtoType } from '@managemint-solutions/entities/users/dto';
2
2
  import { EmergencyContactRelationship, EmploymentType } from '@managemint-solutions/entities/users/enum';
3
3
  export declare class UserIdDto implements UserIdDtoType {
4
4
  readonly userId: string;
@@ -28,6 +28,10 @@ export declare class GetUsersQueryDto implements Omit<GetUsersDtoType, 'active_i
28
28
  readonly active_in?: boolean[] | null;
29
29
  readonly manager_id?: string | null;
30
30
  }
31
+ /** A staff read across organizations, optionally narrowed to one of them. */
32
+ export declare class StaffGetUsersDto extends GetUsersQueryDto implements Omit<StaffGetUsersDtoType, 'active_in'> {
33
+ readonly organization_id?: string | null;
34
+ }
31
35
  export declare class UpdateUserDto implements UpdateUserDtoType {
32
36
  readonly name: string;
33
37
  readonly surname: string;
package/dist/users/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.UpdateMyProfileDto = exports.UpdateUserDto = exports.GetUsersQueryDto = exports.AddUserDto = exports.UserIdDto = void 0;
12
+ exports.UpdateMyProfileDto = exports.UpdateUserDto = exports.StaffGetUsersDto = exports.GetUsersQueryDto = exports.AddUserDto = exports.UserIdDto = void 0;
13
13
  const class_validator_1 = require("class-validator");
14
14
  const class_transformer_1 = require("class-transformer");
15
15
  const validators_1 = require("../validators");
@@ -51,7 +51,7 @@ __decorate([
51
51
  __decorate([
52
52
  (0, class_validator_1.IsString)({ message: 'Password is required' }),
53
53
  (0, class_validator_1.MinLength)(8, { message: 'Password must be at least 8 characters' }),
54
- (0, class_validator_1.Matches)(/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*])/, { message: 'Password must contain at least 1 uppercase letter, 1 lowercase letter, 1 number, and 1 special character (!@#$%^&*)' }),
54
+ (0, validators_1.MatchesPasswordRules)(),
55
55
  __metadata("design:type", String)
56
56
  ], AddUserDto.prototype, "password", void 0);
57
57
  __decorate([
@@ -134,6 +134,17 @@ __decorate([
134
134
  (0, class_validator_1.IsUUID)('4', { message: 'Invalid manager ID format' }),
135
135
  __metadata("design:type", Object)
136
136
  ], GetUsersQueryDto.prototype, "manager_id", void 0);
137
+ /** A staff read across organizations, optionally narrowed to one of them. */
138
+ class StaffGetUsersDto extends GetUsersQueryDto {
139
+ organization_id;
140
+ }
141
+ exports.StaffGetUsersDto = StaffGetUsersDto;
142
+ __decorate([
143
+ (0, class_validator_1.IsOptional)(),
144
+ (0, validators_1.IsNullable)(),
145
+ (0, class_validator_1.IsUUID)('7', { message: 'Invalid organization ID format' }),
146
+ __metadata("design:type", Object)
147
+ ], StaffGetUsersDto.prototype, "organization_id", void 0);
137
148
  class UpdateUserDto {
138
149
  name;
139
150
  surname;
@@ -1,6 +1,6 @@
1
1
  import type { MmsList } from '@managemint-solutions/entities/common';
2
2
  import type { UserIdentity, UserSummary } from '@managemint-solutions/entities/users';
3
- import { type AuditActor, type AuditResource, type AuditRow, EntityAuditTrail } from '../audit';
3
+ import { type AuditResource, type AuditRow, EntityAuditTrail, type ResourceActor } from '../audit';
4
4
  import type { TypedSupabaseClient } from '../client';
5
5
  import type { GetUsersQueryDto } from './dto';
6
6
  export * from './dto';
@@ -26,8 +26,10 @@ export declare class UsersResource {
26
26
  private readonly table;
27
27
  /** This record's history - its own rows and the rows that hang off it. */
28
28
  readonly audit: EntityAuditTrail;
29
- constructor(supabase: TypedSupabaseClient, auth: AuditActor, audit: AuditResource);
30
- list(query: GetUsersQueryDto): Promise<MmsList<UserSummary>>;
29
+ constructor(supabase: TypedSupabaseClient, auth: ResourceActor, audit: AuditResource);
30
+ list(query: GetUsersQueryDto & {
31
+ organization_id?: string | null;
32
+ }): Promise<MmsList<UserSummary>>;
31
33
  getRow(userId: string): Promise<UserRow>;
32
34
  /**
33
35
  * The public profile behind a `*_by` / `manager_id` column. `user_profiles` is a
@@ -20,6 +20,7 @@ const working_days_1 = require("../leave/working-days");
20
20
  const audit_1 = require("../audit");
21
21
  const errors_1 = require("../errors");
22
22
  const paths_1 = require("../supporting-files/paths");
23
+ const tenancy_1 = require("../tenancy");
23
24
  const transforms_1 = require("../transforms");
24
25
  const paths_2 = require("./paths");
25
26
  __exportStar(require("./dto"), exports);
@@ -48,12 +49,14 @@ class UsersResource {
48
49
  const trimmedSearch = query.search?.trim();
49
50
  // `order`/`range` return `this`, so the builder stays a filter builder and the conditional
50
51
  // filters below can be reassigned onto it without a cast.
51
- let usersQuery = this.supabase
52
+ let usersQuery = (0, tenancy_1.scopeToOrganization)(this.supabase
52
53
  .from('users')
53
- .select(USER_LIST_SELECT, { count: 'estimated' })
54
- .eq('organization_id', this.auth.organization_id)
54
+ .select((0, tenancy_1.withOrganization)(USER_LIST_SELECT, this.auth), { count: 'estimated' }), this.auth)
55
55
  .order('created_at', { ascending: false })
56
56
  .range(offset, offset + pageSize - 1);
57
+ if (query.organization_id) {
58
+ usersQuery = usersQuery.eq('organization_id', query.organization_id);
59
+ }
57
60
  // No filter shows everyone: inactive users must stay reachable so they can
58
61
  // be reactivated from their detail page.
59
62
  if (query.active_in && query.active_in.length > 0) {
@@ -72,12 +75,7 @@ class UsersResource {
72
75
  return (0, transforms_1.toMmsList)(data, page, pageSize, count);
73
76
  }
74
77
  async getRow(userId) {
75
- const { data, error } = await this.supabase
76
- .from('users')
77
- .select('*')
78
- .eq('mms_id', userId)
79
- .eq('organization_id', this.auth.organization_id)
80
- .single();
78
+ const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('users').select('*').eq('mms_id', userId), this.auth).single();
81
79
  if (error)
82
80
  throw (0, errors_1.mapPostgrestError)(error);
83
81
  return (0, audit_1.withoutInternalId)(data);
@@ -106,11 +104,7 @@ class UsersResource {
106
104
  * `active` is not consulted: a deactivated report's history stays visible to their manager.
107
105
  */
108
106
  async idsManagedBy(managerId) {
109
- const { data, error } = await this.supabase
110
- .from('users')
111
- .select('mms_id')
112
- .eq('organization_id', this.auth.organization_id)
113
- .or(`manager_id.eq.${managerId},secondary_manager_id.eq.${managerId}`);
107
+ const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('users').select('mms_id'), this.auth).or(`manager_id.eq.${managerId},secondary_manager_id.eq.${managerId}`);
114
108
  if (error)
115
109
  throw (0, errors_1.mapPostgrestError)(error);
116
110
  return (data ?? []).map((row) => row.mms_id);
@@ -120,10 +114,9 @@ class UsersResource {
120
114
  * are timestamps), and the working week. Everyone in the organization, or just `userIds`.
121
115
  */
122
116
  async employment(userIds) {
123
- let employmentQuery = this.supabase
117
+ let employmentQuery = (0, tenancy_1.scopeToOrganization)(this.supabase
124
118
  .from('users')
125
- .select('mms_id, name, surname, email, profile_image, active, start_date, end_date, working_days')
126
- .eq('organization_id', this.auth.organization_id);
119
+ .select('mms_id, name, surname, email, profile_image, active, start_date, end_date, working_days'), this.auth);
127
120
  if (userIds) {
128
121
  employmentQuery = employmentQuery.in('mms_id', userIds);
129
122
  }
@@ -138,22 +131,13 @@ class UsersResource {
138
131
  }
139
132
  /** Seats in use. Counts rows rather than fetching them. */
140
133
  async activeCount() {
141
- const { count, error } = await this.supabase
142
- .from('users')
143
- .select('mms_id', { count: 'exact', head: true })
144
- .eq('organization_id', this.auth.organization_id)
145
- .eq('active', true);
134
+ const { count, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('users').select('mms_id', { count: 'exact', head: true }), this.auth).eq('active', true);
146
135
  if (error)
147
136
  throw (0, errors_1.mapPostgrestError)(error);
148
137
  return count ?? 0;
149
138
  }
150
139
  async isActive(userId) {
151
- const { data, error } = await this.supabase
152
- .from('users')
153
- .select('active')
154
- .eq('mms_id', userId)
155
- .eq('organization_id', this.auth.organization_id)
156
- .single();
140
+ const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('users').select('active').eq('mms_id', userId), this.auth).single();
157
141
  if (error)
158
142
  throw (0, errors_1.mapPostgrestError)(error);
159
143
  return data.active;
@@ -179,7 +163,7 @@ class UsersResource {
179
163
  */
180
164
  async setProfileImage(userId, file) {
181
165
  const { name, extension } = (0, paths_1.splitFileName)((0, paths_1.sanitizeFileName)(file.name));
182
- const path = (0, paths_2.profileImagePath)(this.auth.organization_id, userId, name, extension);
166
+ const path = (0, paths_2.profileImagePath)((0, tenancy_1.requireOrganization)(this.auth), userId, name, extension);
183
167
  const previous = (0, paths_2.profileImageObjectPath)((await this.getRow(userId)).profile_image);
184
168
  const { error } = await this.supabase.storage
185
169
  .from(paths_2.PROFILE_PICTURES_BUCKET)
@@ -17,6 +17,8 @@ export declare const isClockTime: (value: unknown, allowEndOfDay?: boolean) => v
17
17
  export declare const IsClockTime: ({ allowEndOfDay, ...options }?: ValidationOptions & {
18
18
  allowEndOfDay?: boolean;
19
19
  }) => PropertyDecorator;
20
+ /** The password rules every GoTrue account is held to: a tenant user's and a staff member's. */
21
+ export declare const MatchesPasswordRules: () => PropertyDecorator;
20
22
  export declare function IsNullable(validationOptions?: ValidationOptions): PropertyDecorator;
21
23
  /**
22
24
  * An empty string from a cleared form field means "none". It becomes null before validation,
@@ -6,7 +6,7 @@ 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.IsClockTime = exports.isClockTime = exports.IsCalendarDate = exports.isCalendarDate = void 0;
9
+ exports.MatchesPasswordRules = exports.IsClockTime = exports.isClockTime = exports.IsCalendarDate = exports.isCalendarDate = void 0;
10
10
  exports.IsNullable = IsNullable;
11
11
  exports.EmptyToNull = EmptyToNull;
12
12
  const class_validator_1 = require("class-validator");
@@ -56,6 +56,11 @@ ClockTimeConstraint = __decorate([
56
56
  */
57
57
  const IsClockTime = ({ allowEndOfDay = false, ...options } = {}) => (0, class_validator_1.Validate)(ClockTimeConstraint, [allowEndOfDay], options);
58
58
  exports.IsClockTime = IsClockTime;
59
+ /** The password rules every GoTrue account is held to: a tenant user's and a staff member's. */
60
+ const MatchesPasswordRules = () => (0, class_validator_1.Matches)(/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*])/, {
61
+ message: 'Password must contain at least 1 uppercase letter, 1 lowercase letter, 1 number, and 1 special character (!@#$%^&*)',
62
+ });
63
+ exports.MatchesPasswordRules = MatchesPasswordRules;
59
64
  function IsNullable(validationOptions) {
60
65
  return (0, class_validator_1.ValidateIf)((_object, value) => value !== null, validationOptions);
61
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@managemint-solutions/sdk",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "description": "Typed Supabase data-access SDK for ManageMint Solutions",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Scott Bebington <scottbebington@gmail.com>",
@@ -51,7 +51,7 @@
51
51
  "testEnvironment": "node"
52
52
  },
53
53
  "dependencies": {
54
- "@managemint-solutions/entities": "^1.35.0"
54
+ "@managemint-solutions/entities": "^1.36.0"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "@nestjs/common": "^11.0.0",