@sudobility/entity_service 1.0.1

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 (72) hide show
  1. package/CLAUDE.md +124 -0
  2. package/dist/helpers/EntityHelper.cjs +234 -0
  3. package/dist/helpers/EntityHelper.d.ts +60 -0
  4. package/dist/helpers/EntityHelper.d.ts.map +1 -0
  5. package/dist/helpers/EntityHelper.js +234 -0
  6. package/dist/helpers/EntityHelper.js.map +1 -0
  7. package/dist/helpers/EntityMemberHelper.cjs +215 -0
  8. package/dist/helpers/EntityMemberHelper.d.ts +45 -0
  9. package/dist/helpers/EntityMemberHelper.d.ts.map +1 -0
  10. package/dist/helpers/EntityMemberHelper.js +215 -0
  11. package/dist/helpers/EntityMemberHelper.js.map +1 -0
  12. package/dist/helpers/InvitationHelper.cjs +251 -0
  13. package/dist/helpers/InvitationHelper.d.ts +59 -0
  14. package/dist/helpers/InvitationHelper.d.ts.map +1 -0
  15. package/dist/helpers/InvitationHelper.js +251 -0
  16. package/dist/helpers/InvitationHelper.js.map +1 -0
  17. package/dist/helpers/PermissionHelper.cjs +197 -0
  18. package/dist/helpers/PermissionHelper.d.ts +86 -0
  19. package/dist/helpers/PermissionHelper.d.ts.map +1 -0
  20. package/dist/helpers/PermissionHelper.js +197 -0
  21. package/dist/helpers/PermissionHelper.js.map +1 -0
  22. package/dist/helpers/index.cjs +15 -0
  23. package/dist/helpers/index.d.ts +8 -0
  24. package/dist/helpers/index.d.ts.map +1 -0
  25. package/dist/helpers/index.js +15 -0
  26. package/dist/helpers/index.js.map +1 -0
  27. package/dist/index.cjs +76 -0
  28. package/dist/index.d.ts +36 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +76 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/middleware/hono.cjs +148 -0
  33. package/dist/middleware/hono.d.ts +102 -0
  34. package/dist/middleware/hono.d.ts.map +1 -0
  35. package/dist/middleware/hono.js +148 -0
  36. package/dist/middleware/hono.js.map +1 -0
  37. package/dist/middleware/index.cjs +12 -0
  38. package/dist/middleware/index.d.ts +5 -0
  39. package/dist/middleware/index.d.ts.map +1 -0
  40. package/dist/middleware/index.js +12 -0
  41. package/dist/middleware/index.js.map +1 -0
  42. package/dist/migrations/001_add_entities.cjs +269 -0
  43. package/dist/migrations/001_add_entities.d.ts +29 -0
  44. package/dist/migrations/001_add_entities.d.ts.map +1 -0
  45. package/dist/migrations/001_add_entities.js +269 -0
  46. package/dist/migrations/001_add_entities.js.map +1 -0
  47. package/dist/migrations/index.cjs +10 -0
  48. package/dist/migrations/index.d.ts +5 -0
  49. package/dist/migrations/index.d.ts.map +1 -0
  50. package/dist/migrations/index.js +10 -0
  51. package/dist/migrations/index.js.map +1 -0
  52. package/dist/schema/entities.cjs +304 -0
  53. package/dist/schema/entities.d.ts +1047 -0
  54. package/dist/schema/entities.d.ts.map +1 -0
  55. package/dist/schema/entities.js +304 -0
  56. package/dist/schema/entities.js.map +1 -0
  57. package/dist/types/index.cjs +14 -0
  58. package/dist/types/index.d.ts +71 -0
  59. package/dist/types/index.d.ts.map +1 -0
  60. package/dist/types/index.js +14 -0
  61. package/dist/types/index.js.map +1 -0
  62. package/dist/utils/index.cjs +21 -0
  63. package/dist/utils/index.d.ts +5 -0
  64. package/dist/utils/index.d.ts.map +1 -0
  65. package/dist/utils/index.js +21 -0
  66. package/dist/utils/index.js.map +1 -0
  67. package/dist/utils/slug-generator.cjs +92 -0
  68. package/dist/utils/slug-generator.d.ts +41 -0
  69. package/dist/utils/slug-generator.d.ts.map +1 -0
  70. package/dist/utils/slug-generator.js +92 -0
  71. package/dist/utils/slug-generator.js.map +1 -0
  72. package/package.json +78 -0
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Entity Invitation Helper Class
4
+ * @description Operations for managing entity invitations
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.InvitationHelper = void 0;
8
+ const drizzle_orm_1 = require("drizzle-orm");
9
+ const types_1 = require("../types");
10
+ const utils_1 = require("../utils");
11
+ /**
12
+ * Helper class for entity invitation operations.
13
+ */
14
+ class InvitationHelper {
15
+ constructor(config) {
16
+ this.config = config;
17
+ }
18
+ /**
19
+ * Create an invitation to join an entity.
20
+ */
21
+ async createInvitation(entityId, invitedByUserId, request) {
22
+ // Check if user is already a member
23
+ const existingMember = await this.config.db
24
+ .select()
25
+ .from(this.config.membersTable)
26
+ .innerJoin(this.config.usersTable, (0, drizzle_orm_1.eq)(this.config.membersTable.user_id, this.config.usersTable.uuid))
27
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.membersTable.entity_id, entityId), (0, drizzle_orm_1.eq)(this.config.usersTable.email, request.email)))
28
+ .limit(1);
29
+ if (existingMember.length > 0) {
30
+ throw new Error('User is already a member of this entity');
31
+ }
32
+ // Check for existing pending invitation
33
+ const existingInvite = await this.config.db
34
+ .select()
35
+ .from(this.config.invitationsTable)
36
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.invitationsTable.entity_id, entityId), (0, drizzle_orm_1.eq)(this.config.invitationsTable.email, request.email), (0, drizzle_orm_1.eq)(this.config.invitationsTable.status, types_1.InvitationStatus.PENDING)))
37
+ .limit(1);
38
+ if (existingInvite.length > 0) {
39
+ throw new Error('An invitation is already pending for this email');
40
+ }
41
+ const token = (0, utils_1.generateInvitationToken)();
42
+ const expiresAt = (0, utils_1.calculateInvitationExpiry)();
43
+ const [invitation] = await this.config.db
44
+ .insert(this.config.invitationsTable)
45
+ .values({
46
+ entity_id: entityId,
47
+ email: request.email,
48
+ role: request.role,
49
+ status: types_1.InvitationStatus.PENDING,
50
+ invited_by_user_id: invitedByUserId,
51
+ token,
52
+ expires_at: new Date(expiresAt),
53
+ })
54
+ .returning();
55
+ return this.mapRecordToInvitation(invitation);
56
+ }
57
+ /**
58
+ * Get an invitation by token.
59
+ */
60
+ async getInvitationByToken(token) {
61
+ const results = await this.config.db
62
+ .select()
63
+ .from(this.config.invitationsTable)
64
+ .where((0, drizzle_orm_1.eq)(this.config.invitationsTable.token, token))
65
+ .limit(1);
66
+ if (results.length === 0) {
67
+ return null;
68
+ }
69
+ return this.mapRecordToInvitation(results[0]);
70
+ }
71
+ /**
72
+ * Get an invitation by ID.
73
+ */
74
+ async getInvitation(invitationId) {
75
+ const results = await this.config.db
76
+ .select()
77
+ .from(this.config.invitationsTable)
78
+ .where((0, drizzle_orm_1.eq)(this.config.invitationsTable.id, invitationId))
79
+ .limit(1);
80
+ if (results.length === 0) {
81
+ return null;
82
+ }
83
+ return this.mapRecordToInvitation(results[0]);
84
+ }
85
+ /**
86
+ * Get all invitations for an entity.
87
+ */
88
+ async getEntityInvitations(entityId, options) {
89
+ // Build conditions
90
+ const conditions = [(0, drizzle_orm_1.eq)(this.config.invitationsTable.entity_id, entityId)];
91
+ if (options?.status) {
92
+ conditions.push((0, drizzle_orm_1.eq)(this.config.invitationsTable.status, options.status));
93
+ }
94
+ let query = this.config.db
95
+ .select()
96
+ .from(this.config.invitationsTable)
97
+ .where((0, drizzle_orm_1.and)(...conditions))
98
+ .$dynamic();
99
+ if (options?.limit) {
100
+ query = query.limit(options.limit);
101
+ }
102
+ if (options?.offset) {
103
+ query = query.offset(options.offset);
104
+ }
105
+ const results = await query;
106
+ return results.map((r) => this.mapRecordToInvitation(r));
107
+ }
108
+ /**
109
+ * Get all pending invitations for a user email.
110
+ */
111
+ async getUserPendingInvitations(email) {
112
+ const results = await this.config.db
113
+ .select({
114
+ invitation: this.config.invitationsTable,
115
+ entity: this.config.entitiesTable,
116
+ })
117
+ .from(this.config.invitationsTable)
118
+ .innerJoin(this.config.entitiesTable, (0, drizzle_orm_1.eq)(this.config.invitationsTable.entity_id, this.config.entitiesTable.id))
119
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.invitationsTable.email, email), (0, drizzle_orm_1.eq)(this.config.invitationsTable.status, types_1.InvitationStatus.PENDING)));
120
+ return results.map(({ invitation, entity }) => ({
121
+ ...this.mapRecordToInvitation(invitation),
122
+ entity: {
123
+ id: entity.id,
124
+ entitySlug: entity.entity_slug,
125
+ entityType: entity.entity_type,
126
+ displayName: entity.display_name,
127
+ description: entity.description,
128
+ avatarUrl: entity.avatar_url,
129
+ ownerUserId: entity.owner_user_id,
130
+ createdAt: entity.created_at?.toISOString() ?? new Date().toISOString(),
131
+ updatedAt: entity.updated_at?.toISOString() ?? new Date().toISOString(),
132
+ },
133
+ }));
134
+ }
135
+ /**
136
+ * Accept an invitation.
137
+ */
138
+ async acceptInvitation(token, userId) {
139
+ const invitation = await this.getInvitationByToken(token);
140
+ if (!invitation) {
141
+ throw new Error('Invitation not found');
142
+ }
143
+ if (invitation.status !== types_1.InvitationStatus.PENDING) {
144
+ throw new Error('Invitation is no longer pending');
145
+ }
146
+ if (new Date(invitation.expiresAt) < new Date()) {
147
+ // Mark as expired
148
+ await this.config.db
149
+ .update(this.config.invitationsTable)
150
+ .set({
151
+ status: types_1.InvitationStatus.EXPIRED,
152
+ updated_at: new Date(),
153
+ })
154
+ .where((0, drizzle_orm_1.eq)(this.config.invitationsTable.id, invitation.id));
155
+ throw new Error('Invitation has expired');
156
+ }
157
+ // Add user as member
158
+ await this.config.db.insert(this.config.membersTable).values({
159
+ entity_id: invitation.entityId,
160
+ user_id: userId,
161
+ role: invitation.role,
162
+ });
163
+ // Mark invitation as accepted
164
+ await this.config.db
165
+ .update(this.config.invitationsTable)
166
+ .set({
167
+ status: types_1.InvitationStatus.ACCEPTED,
168
+ accepted_at: new Date(),
169
+ updated_at: new Date(),
170
+ })
171
+ .where((0, drizzle_orm_1.eq)(this.config.invitationsTable.id, invitation.id));
172
+ }
173
+ /**
174
+ * Decline an invitation.
175
+ */
176
+ async declineInvitation(token) {
177
+ const invitation = await this.getInvitationByToken(token);
178
+ if (!invitation) {
179
+ throw new Error('Invitation not found');
180
+ }
181
+ if (invitation.status !== types_1.InvitationStatus.PENDING) {
182
+ throw new Error('Invitation is no longer pending');
183
+ }
184
+ await this.config.db
185
+ .update(this.config.invitationsTable)
186
+ .set({
187
+ status: types_1.InvitationStatus.DECLINED,
188
+ updated_at: new Date(),
189
+ })
190
+ .where((0, drizzle_orm_1.eq)(this.config.invitationsTable.id, invitation.id));
191
+ }
192
+ /**
193
+ * Cancel an invitation (by entity admin).
194
+ */
195
+ async cancelInvitation(invitationId) {
196
+ await this.config.db
197
+ .delete(this.config.invitationsTable)
198
+ .where((0, drizzle_orm_1.eq)(this.config.invitationsTable.id, invitationId));
199
+ }
200
+ /**
201
+ * Process pending invitations for a new user.
202
+ * Called when a user signs up to auto-accept any pending invitations.
203
+ */
204
+ async processNewUserInvitations(userId, email) {
205
+ const pendingInvitations = await this.getUserPendingInvitations(email);
206
+ for (const invitation of pendingInvitations) {
207
+ try {
208
+ await this.acceptInvitation(invitation.token, userId);
209
+ }
210
+ catch (error) {
211
+ // Log but don't fail - user account creation is more important
212
+ console.error(`Failed to auto-accept invitation ${invitation.id}:`, error);
213
+ }
214
+ }
215
+ }
216
+ /**
217
+ * Expire old invitations.
218
+ * Should be called periodically (e.g., by a cron job).
219
+ */
220
+ async expireOldInvitations() {
221
+ const result = await this.config.db
222
+ .update(this.config.invitationsTable)
223
+ .set({
224
+ status: types_1.InvitationStatus.EXPIRED,
225
+ updated_at: new Date(),
226
+ })
227
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.invitationsTable.status, types_1.InvitationStatus.PENDING), (0, drizzle_orm_1.lt)(this.config.invitationsTable.expires_at, new Date())))
228
+ .returning();
229
+ return result.length;
230
+ }
231
+ /**
232
+ * Map database record to EntityInvitation type.
233
+ */
234
+ mapRecordToInvitation(record) {
235
+ return {
236
+ id: record.id,
237
+ entityId: record.entity_id,
238
+ email: record.email,
239
+ role: record.role,
240
+ status: record.status,
241
+ invitedByUserId: record.invited_by_user_id,
242
+ token: record.token,
243
+ expiresAt: record.expires_at?.toISOString() ?? new Date().toISOString(),
244
+ acceptedAt: record.accepted_at?.toISOString() ?? null,
245
+ createdAt: record.created_at?.toISOString() ?? new Date().toISOString(),
246
+ updatedAt: record.updated_at?.toISOString() ?? new Date().toISOString(),
247
+ };
248
+ }
249
+ }
250
+ exports.InvitationHelper = InvitationHelper;
251
+ //# sourceMappingURL=InvitationHelper.js.map
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @fileoverview Entity Invitation Helper Class
3
+ * @description Operations for managing entity invitations
4
+ */
5
+ import { type EntityInvitation, type InviteMemberRequest, type InvitationHelperConfig, type ListInvitationsOptions } from '../types';
6
+ /**
7
+ * Helper class for entity invitation operations.
8
+ */
9
+ export declare class InvitationHelper {
10
+ private readonly config;
11
+ constructor(config: InvitationHelperConfig);
12
+ /**
13
+ * Create an invitation to join an entity.
14
+ */
15
+ createInvitation(entityId: string, invitedByUserId: string, request: InviteMemberRequest): Promise<EntityInvitation>;
16
+ /**
17
+ * Get an invitation by token.
18
+ */
19
+ getInvitationByToken(token: string): Promise<EntityInvitation | null>;
20
+ /**
21
+ * Get an invitation by ID.
22
+ */
23
+ getInvitation(invitationId: string): Promise<EntityInvitation | null>;
24
+ /**
25
+ * Get all invitations for an entity.
26
+ */
27
+ getEntityInvitations(entityId: string, options?: ListInvitationsOptions): Promise<EntityInvitation[]>;
28
+ /**
29
+ * Get all pending invitations for a user email.
30
+ */
31
+ getUserPendingInvitations(email: string): Promise<EntityInvitation[]>;
32
+ /**
33
+ * Accept an invitation.
34
+ */
35
+ acceptInvitation(token: string, userId: string): Promise<void>;
36
+ /**
37
+ * Decline an invitation.
38
+ */
39
+ declineInvitation(token: string): Promise<void>;
40
+ /**
41
+ * Cancel an invitation (by entity admin).
42
+ */
43
+ cancelInvitation(invitationId: string): Promise<void>;
44
+ /**
45
+ * Process pending invitations for a new user.
46
+ * Called when a user signs up to auto-accept any pending invitations.
47
+ */
48
+ processNewUserInvitations(userId: string, email: string): Promise<void>;
49
+ /**
50
+ * Expire old invitations.
51
+ * Should be called periodically (e.g., by a cron job).
52
+ */
53
+ expireOldInvitations(): Promise<number>;
54
+ /**
55
+ * Map database record to EntityInvitation type.
56
+ */
57
+ private mapRecordToInvitation;
58
+ }
59
+ //# sourceMappingURL=InvitationHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InvitationHelper.d.ts","sourceRoot":"","sources":["../../src/helpers/InvitationHelper.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC5B,MAAM,UAAU,CAAC;AAMlB;;GAEG;AACH,qBAAa,gBAAgB;IACf,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,sBAAsB;IAE3D;;OAEG;IACG,gBAAgB,CACpB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,gBAAgB,CAAC;IAyD5B;;OAEG;IACG,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAc3E;;OAEG;IACG,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAc3E;;OAEG;IACG,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAyB9B;;OAEG;IACG,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAkC3E;;OAEG;IACG,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0CpE;;OAEG;IACG,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBrD;;OAEG;IACG,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM3D;;;OAGG;IACG,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa7E;;;OAGG;IACG,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;IAkB7C;;OAEG;IACH,OAAO,CAAC,qBAAqB;CAe9B"}
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Entity Invitation Helper Class
4
+ * @description Operations for managing entity invitations
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.InvitationHelper = void 0;
8
+ const drizzle_orm_1 = require("drizzle-orm");
9
+ const types_1 = require("../types");
10
+ const utils_1 = require("../utils");
11
+ /**
12
+ * Helper class for entity invitation operations.
13
+ */
14
+ class InvitationHelper {
15
+ constructor(config) {
16
+ this.config = config;
17
+ }
18
+ /**
19
+ * Create an invitation to join an entity.
20
+ */
21
+ async createInvitation(entityId, invitedByUserId, request) {
22
+ // Check if user is already a member
23
+ const existingMember = await this.config.db
24
+ .select()
25
+ .from(this.config.membersTable)
26
+ .innerJoin(this.config.usersTable, (0, drizzle_orm_1.eq)(this.config.membersTable.user_id, this.config.usersTable.uuid))
27
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.membersTable.entity_id, entityId), (0, drizzle_orm_1.eq)(this.config.usersTable.email, request.email)))
28
+ .limit(1);
29
+ if (existingMember.length > 0) {
30
+ throw new Error('User is already a member of this entity');
31
+ }
32
+ // Check for existing pending invitation
33
+ const existingInvite = await this.config.db
34
+ .select()
35
+ .from(this.config.invitationsTable)
36
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.invitationsTable.entity_id, entityId), (0, drizzle_orm_1.eq)(this.config.invitationsTable.email, request.email), (0, drizzle_orm_1.eq)(this.config.invitationsTable.status, types_1.InvitationStatus.PENDING)))
37
+ .limit(1);
38
+ if (existingInvite.length > 0) {
39
+ throw new Error('An invitation is already pending for this email');
40
+ }
41
+ const token = (0, utils_1.generateInvitationToken)();
42
+ const expiresAt = (0, utils_1.calculateInvitationExpiry)();
43
+ const [invitation] = await this.config.db
44
+ .insert(this.config.invitationsTable)
45
+ .values({
46
+ entity_id: entityId,
47
+ email: request.email,
48
+ role: request.role,
49
+ status: types_1.InvitationStatus.PENDING,
50
+ invited_by_user_id: invitedByUserId,
51
+ token,
52
+ expires_at: new Date(expiresAt),
53
+ })
54
+ .returning();
55
+ return this.mapRecordToInvitation(invitation);
56
+ }
57
+ /**
58
+ * Get an invitation by token.
59
+ */
60
+ async getInvitationByToken(token) {
61
+ const results = await this.config.db
62
+ .select()
63
+ .from(this.config.invitationsTable)
64
+ .where((0, drizzle_orm_1.eq)(this.config.invitationsTable.token, token))
65
+ .limit(1);
66
+ if (results.length === 0) {
67
+ return null;
68
+ }
69
+ return this.mapRecordToInvitation(results[0]);
70
+ }
71
+ /**
72
+ * Get an invitation by ID.
73
+ */
74
+ async getInvitation(invitationId) {
75
+ const results = await this.config.db
76
+ .select()
77
+ .from(this.config.invitationsTable)
78
+ .where((0, drizzle_orm_1.eq)(this.config.invitationsTable.id, invitationId))
79
+ .limit(1);
80
+ if (results.length === 0) {
81
+ return null;
82
+ }
83
+ return this.mapRecordToInvitation(results[0]);
84
+ }
85
+ /**
86
+ * Get all invitations for an entity.
87
+ */
88
+ async getEntityInvitations(entityId, options) {
89
+ // Build conditions
90
+ const conditions = [(0, drizzle_orm_1.eq)(this.config.invitationsTable.entity_id, entityId)];
91
+ if (options?.status) {
92
+ conditions.push((0, drizzle_orm_1.eq)(this.config.invitationsTable.status, options.status));
93
+ }
94
+ let query = this.config.db
95
+ .select()
96
+ .from(this.config.invitationsTable)
97
+ .where((0, drizzle_orm_1.and)(...conditions))
98
+ .$dynamic();
99
+ if (options?.limit) {
100
+ query = query.limit(options.limit);
101
+ }
102
+ if (options?.offset) {
103
+ query = query.offset(options.offset);
104
+ }
105
+ const results = await query;
106
+ return results.map((r) => this.mapRecordToInvitation(r));
107
+ }
108
+ /**
109
+ * Get all pending invitations for a user email.
110
+ */
111
+ async getUserPendingInvitations(email) {
112
+ const results = await this.config.db
113
+ .select({
114
+ invitation: this.config.invitationsTable,
115
+ entity: this.config.entitiesTable,
116
+ })
117
+ .from(this.config.invitationsTable)
118
+ .innerJoin(this.config.entitiesTable, (0, drizzle_orm_1.eq)(this.config.invitationsTable.entity_id, this.config.entitiesTable.id))
119
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.invitationsTable.email, email), (0, drizzle_orm_1.eq)(this.config.invitationsTable.status, types_1.InvitationStatus.PENDING)));
120
+ return results.map(({ invitation, entity }) => ({
121
+ ...this.mapRecordToInvitation(invitation),
122
+ entity: {
123
+ id: entity.id,
124
+ entitySlug: entity.entity_slug,
125
+ entityType: entity.entity_type,
126
+ displayName: entity.display_name,
127
+ description: entity.description,
128
+ avatarUrl: entity.avatar_url,
129
+ ownerUserId: entity.owner_user_id,
130
+ createdAt: entity.created_at?.toISOString() ?? new Date().toISOString(),
131
+ updatedAt: entity.updated_at?.toISOString() ?? new Date().toISOString(),
132
+ },
133
+ }));
134
+ }
135
+ /**
136
+ * Accept an invitation.
137
+ */
138
+ async acceptInvitation(token, userId) {
139
+ const invitation = await this.getInvitationByToken(token);
140
+ if (!invitation) {
141
+ throw new Error('Invitation not found');
142
+ }
143
+ if (invitation.status !== types_1.InvitationStatus.PENDING) {
144
+ throw new Error('Invitation is no longer pending');
145
+ }
146
+ if (new Date(invitation.expiresAt) < new Date()) {
147
+ // Mark as expired
148
+ await this.config.db
149
+ .update(this.config.invitationsTable)
150
+ .set({
151
+ status: types_1.InvitationStatus.EXPIRED,
152
+ updated_at: new Date(),
153
+ })
154
+ .where((0, drizzle_orm_1.eq)(this.config.invitationsTable.id, invitation.id));
155
+ throw new Error('Invitation has expired');
156
+ }
157
+ // Add user as member
158
+ await this.config.db.insert(this.config.membersTable).values({
159
+ entity_id: invitation.entityId,
160
+ user_id: userId,
161
+ role: invitation.role,
162
+ });
163
+ // Mark invitation as accepted
164
+ await this.config.db
165
+ .update(this.config.invitationsTable)
166
+ .set({
167
+ status: types_1.InvitationStatus.ACCEPTED,
168
+ accepted_at: new Date(),
169
+ updated_at: new Date(),
170
+ })
171
+ .where((0, drizzle_orm_1.eq)(this.config.invitationsTable.id, invitation.id));
172
+ }
173
+ /**
174
+ * Decline an invitation.
175
+ */
176
+ async declineInvitation(token) {
177
+ const invitation = await this.getInvitationByToken(token);
178
+ if (!invitation) {
179
+ throw new Error('Invitation not found');
180
+ }
181
+ if (invitation.status !== types_1.InvitationStatus.PENDING) {
182
+ throw new Error('Invitation is no longer pending');
183
+ }
184
+ await this.config.db
185
+ .update(this.config.invitationsTable)
186
+ .set({
187
+ status: types_1.InvitationStatus.DECLINED,
188
+ updated_at: new Date(),
189
+ })
190
+ .where((0, drizzle_orm_1.eq)(this.config.invitationsTable.id, invitation.id));
191
+ }
192
+ /**
193
+ * Cancel an invitation (by entity admin).
194
+ */
195
+ async cancelInvitation(invitationId) {
196
+ await this.config.db
197
+ .delete(this.config.invitationsTable)
198
+ .where((0, drizzle_orm_1.eq)(this.config.invitationsTable.id, invitationId));
199
+ }
200
+ /**
201
+ * Process pending invitations for a new user.
202
+ * Called when a user signs up to auto-accept any pending invitations.
203
+ */
204
+ async processNewUserInvitations(userId, email) {
205
+ const pendingInvitations = await this.getUserPendingInvitations(email);
206
+ for (const invitation of pendingInvitations) {
207
+ try {
208
+ await this.acceptInvitation(invitation.token, userId);
209
+ }
210
+ catch (error) {
211
+ // Log but don't fail - user account creation is more important
212
+ console.error(`Failed to auto-accept invitation ${invitation.id}:`, error);
213
+ }
214
+ }
215
+ }
216
+ /**
217
+ * Expire old invitations.
218
+ * Should be called periodically (e.g., by a cron job).
219
+ */
220
+ async expireOldInvitations() {
221
+ const result = await this.config.db
222
+ .update(this.config.invitationsTable)
223
+ .set({
224
+ status: types_1.InvitationStatus.EXPIRED,
225
+ updated_at: new Date(),
226
+ })
227
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.invitationsTable.status, types_1.InvitationStatus.PENDING), (0, drizzle_orm_1.lt)(this.config.invitationsTable.expires_at, new Date())))
228
+ .returning();
229
+ return result.length;
230
+ }
231
+ /**
232
+ * Map database record to EntityInvitation type.
233
+ */
234
+ mapRecordToInvitation(record) {
235
+ return {
236
+ id: record.id,
237
+ entityId: record.entity_id,
238
+ email: record.email,
239
+ role: record.role,
240
+ status: record.status,
241
+ invitedByUserId: record.invited_by_user_id,
242
+ token: record.token,
243
+ expiresAt: record.expires_at?.toISOString() ?? new Date().toISOString(),
244
+ acceptedAt: record.accepted_at?.toISOString() ?? null,
245
+ createdAt: record.created_at?.toISOString() ?? new Date().toISOString(),
246
+ updatedAt: record.updated_at?.toISOString() ?? new Date().toISOString(),
247
+ };
248
+ }
249
+ }
250
+ exports.InvitationHelper = InvitationHelper;
251
+ //# sourceMappingURL=InvitationHelper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InvitationHelper.js","sourceRoot":"","sources":["../../src/helpers/InvitationHelper.ts"],"names":[],"mappings":"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"}