@sudobility/entity_service 1.0.7 → 1.0.8

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 (43) hide show
  1. package/dist/helpers/EntityHelper.js +25 -29
  2. package/dist/helpers/EntityHelper.js.map +1 -1
  3. package/dist/helpers/EntityMemberHelper.js +26 -30
  4. package/dist/helpers/EntityMemberHelper.js.map +1 -1
  5. package/dist/helpers/InvitationHelper.js +28 -32
  6. package/dist/helpers/InvitationHelper.js.map +1 -1
  7. package/dist/helpers/PermissionHelper.js +13 -17
  8. package/dist/helpers/PermissionHelper.js.map +1 -1
  9. package/dist/helpers/index.js +4 -11
  10. package/dist/helpers/index.js.map +1 -1
  11. package/dist/index.js +9 -38
  12. package/dist/index.js.map +1 -1
  13. package/dist/middleware/hono.js +18 -24
  14. package/dist/middleware/hono.js.map +1 -1
  15. package/dist/middleware/index.js +1 -8
  16. package/dist/middleware/index.js.map +1 -1
  17. package/dist/migrations/001_add_entities.js +2 -6
  18. package/dist/migrations/001_add_entities.js.map +1 -1
  19. package/dist/migrations/index.js +1 -6
  20. package/dist/migrations/index.js.map +1 -1
  21. package/dist/schema/entities.js +125 -135
  22. package/dist/schema/entities.js.map +1 -1
  23. package/dist/types/index.js +1 -8
  24. package/dist/types/index.js.map +1 -1
  25. package/dist/utils/index.js +1 -17
  26. package/dist/utils/index.js.map +1 -1
  27. package/dist/utils/slug-generator.js +6 -14
  28. package/dist/utils/slug-generator.js.map +1 -1
  29. package/package.json +9 -16
  30. package/dist/helpers/EntityHelper.cjs +0 -253
  31. package/dist/helpers/EntityMemberHelper.cjs +0 -254
  32. package/dist/helpers/InvitationHelper.cjs +0 -256
  33. package/dist/helpers/PermissionHelper.cjs +0 -193
  34. package/dist/helpers/index.cjs +0 -15
  35. package/dist/index.cjs +0 -76
  36. package/dist/middleware/hono.cjs +0 -148
  37. package/dist/middleware/index.cjs +0 -12
  38. package/dist/migrations/001_add_entities.cjs +0 -330
  39. package/dist/migrations/index.cjs +0 -10
  40. package/dist/schema/entities.cjs +0 -311
  41. package/dist/types/index.cjs +0 -14
  42. package/dist/utils/index.cjs +0 -21
  43. package/dist/utils/slug-generator.cjs +0 -92
@@ -1,253 +0,0 @@
1
- "use strict";
2
- /**
3
- * @fileoverview Entity Helper Class
4
- * @description CRUD operations for entities (personal and organization workspaces)
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.EntityHelper = 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 CRUD operations.
13
- */
14
- class EntityHelper {
15
- constructor(config) {
16
- this.config = config;
17
- }
18
- /**
19
- * Create a personal entity for a user.
20
- * Called automatically when a user first logs in.
21
- * @param firebaseUid - The Firebase UID (used as user_id)
22
- * @param email - Optional email for display name
23
- */
24
- async createPersonalEntity(firebaseUid, email) {
25
- const slug = (0, utils_1.generateEntitySlug)();
26
- const displayName = email?.split('@')[0] ?? 'Personal';
27
- const [entity] = await this.config.db
28
- .insert(this.config.entitiesTable)
29
- .values({
30
- entity_slug: slug,
31
- entity_type: types_1.EntityType.PERSONAL,
32
- display_name: displayName,
33
- })
34
- .returning();
35
- // Add user as admin (personal entities use admin role, not owner)
36
- await this.config.db.insert(this.config.membersTable).values({
37
- entity_id: entity.id,
38
- user_id: firebaseUid,
39
- role: types_1.EntityRole.ADMIN,
40
- is_active: true,
41
- });
42
- return this.mapRecordToEntity(entity);
43
- }
44
- /**
45
- * Get or create a personal entity for a user.
46
- * Ensures exactly one personal entity exists per user.
47
- * @param firebaseUid - The Firebase UID (used as user_id)
48
- * @param email - Optional email for display name
49
- */
50
- async getOrCreatePersonalEntity(firebaseUid, email) {
51
- // Check for existing personal entity where user is admin
52
- const existing = await this.config.db
53
- .select({ entity: this.config.entitiesTable })
54
- .from(this.config.membersTable)
55
- .innerJoin(this.config.entitiesTable, (0, drizzle_orm_1.eq)(this.config.membersTable.entity_id, this.config.entitiesTable.id))
56
- .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.membersTable.user_id, firebaseUid), (0, drizzle_orm_1.eq)(this.config.membersTable.role, types_1.EntityRole.ADMIN), (0, drizzle_orm_1.eq)(this.config.membersTable.is_active, true), (0, drizzle_orm_1.eq)(this.config.entitiesTable.entity_type, types_1.EntityType.PERSONAL)))
57
- .limit(1);
58
- if (existing.length > 0) {
59
- return this.mapRecordToEntity(existing[0].entity);
60
- }
61
- return this.createPersonalEntity(firebaseUid, email);
62
- }
63
- /**
64
- * Create an organization entity.
65
- * @param firebaseUid - The Firebase UID (used as user_id)
66
- * @param request - Entity creation request
67
- */
68
- async createOrganizationEntity(firebaseUid, request) {
69
- // Determine slug
70
- let slug;
71
- if (request.entitySlug) {
72
- slug = (0, utils_1.normalizeSlug)(request.entitySlug);
73
- if (!(0, utils_1.validateSlug)(slug)) {
74
- throw new Error('Invalid entity slug format');
75
- }
76
- // Check availability
77
- if (!(await this.isSlugAvailable(slug))) {
78
- throw new Error('Entity slug is already taken');
79
- }
80
- }
81
- else {
82
- slug = await this.generateUniqueSlug();
83
- }
84
- const [entity] = await this.config.db
85
- .insert(this.config.entitiesTable)
86
- .values({
87
- entity_slug: slug,
88
- entity_type: types_1.EntityType.ORGANIZATION,
89
- display_name: request.displayName,
90
- description: request.description ?? null,
91
- })
92
- .returning();
93
- // Add creator as owner
94
- await this.config.db.insert(this.config.membersTable).values({
95
- entity_id: entity.id,
96
- user_id: firebaseUid,
97
- role: types_1.EntityRole.OWNER,
98
- is_active: true,
99
- });
100
- return this.mapRecordToEntity(entity);
101
- }
102
- /**
103
- * Get entity by ID.
104
- */
105
- async getEntity(entityId) {
106
- const results = await this.config.db
107
- .select()
108
- .from(this.config.entitiesTable)
109
- .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.id, entityId))
110
- .limit(1);
111
- if (results.length === 0) {
112
- return null;
113
- }
114
- return this.mapRecordToEntity(results[0]);
115
- }
116
- /**
117
- * Get entity by slug.
118
- */
119
- async getEntityBySlug(slug) {
120
- const results = await this.config.db
121
- .select()
122
- .from(this.config.entitiesTable)
123
- .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.entity_slug, slug))
124
- .limit(1);
125
- if (results.length === 0) {
126
- return null;
127
- }
128
- return this.mapRecordToEntity(results[0]);
129
- }
130
- /**
131
- * Get all entities a user is a member of.
132
- * If the user has no entities, a personal entity is automatically created.
133
- * @param firebaseUid - The Firebase UID (used as user_id)
134
- * @param email - Optional email for display name if creating personal entity
135
- */
136
- async getUserEntities(firebaseUid, email) {
137
- const results = await this.config.db
138
- .select({
139
- entity: this.config.entitiesTable,
140
- role: this.config.membersTable.role,
141
- })
142
- .from(this.config.membersTable)
143
- .innerJoin(this.config.entitiesTable, (0, drizzle_orm_1.eq)(this.config.membersTable.entity_id, this.config.entitiesTable.id))
144
- .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.membersTable.user_id, firebaseUid), (0, drizzle_orm_1.eq)(this.config.membersTable.is_active, true)));
145
- // If user has no entities, create a personal entity for them
146
- if (results.length === 0) {
147
- const personalEntity = await this.createPersonalEntity(firebaseUid, email);
148
- return [
149
- {
150
- ...personalEntity,
151
- userRole: types_1.EntityRole.OWNER,
152
- },
153
- ];
154
- }
155
- return results.map(({ entity, role }) => ({
156
- ...this.mapRecordToEntity(entity),
157
- userRole: role,
158
- }));
159
- }
160
- /**
161
- * Update entity details.
162
- */
163
- async updateEntity(entityId, request) {
164
- const updates = {
165
- updated_at: new Date(),
166
- };
167
- if (request.displayName !== undefined) {
168
- updates.display_name = request.displayName;
169
- }
170
- if (request.description !== undefined) {
171
- updates.description = request.description;
172
- }
173
- if (request.avatarUrl !== undefined) {
174
- updates.avatar_url = request.avatarUrl;
175
- }
176
- if (request.entitySlug !== undefined) {
177
- const slug = (0, utils_1.normalizeSlug)(request.entitySlug);
178
- if (!(0, utils_1.validateSlug)(slug)) {
179
- throw new Error('Invalid entity slug format');
180
- }
181
- // Check if changing slug
182
- const existing = await this.getEntity(entityId);
183
- if (existing && existing.entitySlug !== slug) {
184
- if (!(await this.isSlugAvailable(slug))) {
185
- throw new Error('Entity slug is already taken');
186
- }
187
- updates.entity_slug = slug;
188
- }
189
- }
190
- const [updated] = await this.config.db
191
- .update(this.config.entitiesTable)
192
- .set(updates)
193
- .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.id, entityId))
194
- .returning();
195
- return this.mapRecordToEntity(updated);
196
- }
197
- /**
198
- * Delete an entity.
199
- * Only organizations can be deleted; personal entities cannot.
200
- */
201
- async deleteEntity(entityId) {
202
- const entity = await this.getEntity(entityId);
203
- if (!entity) {
204
- throw new Error('Entity not found');
205
- }
206
- if (entity.entityType === types_1.EntityType.PERSONAL) {
207
- throw new Error('Personal entities cannot be deleted');
208
- }
209
- await this.config.db
210
- .delete(this.config.entitiesTable)
211
- .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.id, entityId));
212
- }
213
- /**
214
- * Check if a slug is available.
215
- */
216
- async isSlugAvailable(slug) {
217
- const results = await this.config.db
218
- .select({ id: this.config.entitiesTable.id })
219
- .from(this.config.entitiesTable)
220
- .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.entity_slug, slug))
221
- .limit(1);
222
- return results.length === 0;
223
- }
224
- /**
225
- * Generate a unique slug.
226
- */
227
- async generateUniqueSlug() {
228
- for (let attempts = 0; attempts < 10; attempts++) {
229
- const slug = (0, utils_1.generateEntitySlug)();
230
- if (await this.isSlugAvailable(slug)) {
231
- return slug;
232
- }
233
- }
234
- throw new Error('Failed to generate unique slug');
235
- }
236
- /**
237
- * Map database record to Entity type.
238
- */
239
- mapRecordToEntity(record) {
240
- return {
241
- id: record.id,
242
- entitySlug: record.entity_slug,
243
- entityType: record.entity_type,
244
- displayName: record.display_name,
245
- description: record.description,
246
- avatarUrl: record.avatar_url,
247
- createdAt: record.created_at?.toISOString() ?? new Date().toISOString(),
248
- updatedAt: record.updated_at?.toISOString() ?? new Date().toISOString(),
249
- };
250
- }
251
- }
252
- exports.EntityHelper = EntityHelper;
253
- //# sourceMappingURL=EntityHelper.js.map
@@ -1,254 +0,0 @@
1
- "use strict";
2
- /**
3
- * @fileoverview Entity Member Helper Class
4
- * @description Operations for managing entity members and their roles
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.EntityMemberHelper = void 0;
8
- const drizzle_orm_1 = require("drizzle-orm");
9
- const types_1 = require("../types");
10
- /**
11
- * Helper class for entity member operations.
12
- */
13
- class EntityMemberHelper {
14
- constructor(config) {
15
- this.config = config;
16
- }
17
- /**
18
- * Get all members of an entity.
19
- * By default, only returns active members.
20
- */
21
- async getMembers(entityId, options) {
22
- // Build conditions - default to active members only
23
- const conditions = [(0, drizzle_orm_1.eq)(this.config.membersTable.entity_id, entityId)];
24
- if (options?.role) {
25
- conditions.push((0, drizzle_orm_1.eq)(this.config.membersTable.role, options.role));
26
- }
27
- // Filter by is_active (default to true if not specified)
28
- const isActive = options?.isActive ?? true;
29
- conditions.push((0, drizzle_orm_1.eq)(this.config.membersTable.is_active, isActive));
30
- let query = this.config.db
31
- .select({
32
- member: this.config.membersTable,
33
- user: {
34
- id: this.config.usersTable.firebase_uid,
35
- email: this.config.usersTable.email,
36
- displayName: this.config.usersTable.display_name,
37
- },
38
- })
39
- .from(this.config.membersTable)
40
- .leftJoin(this.config.usersTable, (0, drizzle_orm_1.eq)(this.config.membersTable.user_id, this.config.usersTable.firebase_uid))
41
- .where((0, drizzle_orm_1.and)(...conditions))
42
- .$dynamic();
43
- if (options?.limit) {
44
- query = query.limit(options.limit);
45
- }
46
- if (options?.offset) {
47
- query = query.offset(options.offset);
48
- }
49
- const results = await query;
50
- return results.map(({ member, user }) => this.mapRecordToMember(member, user));
51
- }
52
- /**
53
- * Get a specific member by user ID.
54
- * Only returns active members by default.
55
- */
56
- async getMember(entityId, userId, includeInactive = false) {
57
- const conditions = [
58
- (0, drizzle_orm_1.eq)(this.config.membersTable.entity_id, entityId),
59
- (0, drizzle_orm_1.eq)(this.config.membersTable.user_id, userId),
60
- ];
61
- if (!includeInactive) {
62
- conditions.push((0, drizzle_orm_1.eq)(this.config.membersTable.is_active, true));
63
- }
64
- const results = await this.config.db
65
- .select({
66
- member: this.config.membersTable,
67
- user: {
68
- id: this.config.usersTable.firebase_uid,
69
- email: this.config.usersTable.email,
70
- displayName: this.config.usersTable.display_name,
71
- },
72
- })
73
- .from(this.config.membersTable)
74
- .leftJoin(this.config.usersTable, (0, drizzle_orm_1.eq)(this.config.membersTable.user_id, this.config.usersTable.firebase_uid))
75
- .where((0, drizzle_orm_1.and)(...conditions))
76
- .limit(1);
77
- if (results.length === 0) {
78
- return null;
79
- }
80
- return this.mapRecordToMember(results[0].member, results[0].user);
81
- }
82
- /**
83
- * Get user's role in an entity.
84
- * Only returns role for active members.
85
- */
86
- async getUserRole(entityId, userId) {
87
- const results = await this.config.db
88
- .select({ role: this.config.membersTable.role })
89
- .from(this.config.membersTable)
90
- .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.membersTable.entity_id, entityId), (0, drizzle_orm_1.eq)(this.config.membersTable.user_id, userId), (0, drizzle_orm_1.eq)(this.config.membersTable.is_active, true)))
91
- .limit(1);
92
- if (results.length === 0) {
93
- return null;
94
- }
95
- return results[0].role;
96
- }
97
- /**
98
- * Add a member to an entity.
99
- * @param entityId - The entity ID
100
- * @param firebaseUid - The Firebase UID (used as user_id)
101
- * @param role - The member's role
102
- */
103
- async addMember(entityId, firebaseUid, role) {
104
- // Check if there's an existing inactive membership to reactivate
105
- const existing = await this.getMember(entityId, firebaseUid, true);
106
- let member;
107
- if (existing && !existing.isActive) {
108
- // Reactivate existing membership
109
- const [updated] = await this.config.db
110
- .update(this.config.membersTable)
111
- .set({
112
- role,
113
- is_active: true,
114
- updated_at: new Date(),
115
- })
116
- .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.membersTable.entity_id, entityId), (0, drizzle_orm_1.eq)(this.config.membersTable.user_id, firebaseUid)))
117
- .returning();
118
- member = updated;
119
- }
120
- else {
121
- // Create new membership
122
- const [inserted] = await this.config.db
123
- .insert(this.config.membersTable)
124
- .values({
125
- entity_id: entityId,
126
- user_id: firebaseUid,
127
- role,
128
- is_active: true,
129
- })
130
- .returning();
131
- member = inserted;
132
- }
133
- // Fetch user info for response
134
- const users = await this.config.db
135
- .select({
136
- id: this.config.usersTable.firebase_uid,
137
- email: this.config.usersTable.email,
138
- displayName: this.config.usersTable.display_name,
139
- })
140
- .from(this.config.usersTable)
141
- .where((0, drizzle_orm_1.eq)(this.config.usersTable.firebase_uid, firebaseUid))
142
- .limit(1);
143
- return this.mapRecordToMember(member, users[0] ?? null);
144
- }
145
- /**
146
- * Update a member's role.
147
- * Cannot change the owner's role. Cannot set anyone to owner (ownership transfer is separate).
148
- */
149
- async updateMemberRole(entityId, userId, role) {
150
- // Cannot assign owner role via this method
151
- if (role === types_1.EntityRole.OWNER) {
152
- throw new Error('Cannot assign owner role. Use ownership transfer instead.');
153
- }
154
- // Check constraints for personal entities
155
- const entity = await this.config.db
156
- .select()
157
- .from(this.config.entitiesTable)
158
- .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.id, entityId))
159
- .limit(1);
160
- if (entity.length > 0 && entity[0].entity_type === types_1.EntityType.PERSONAL) {
161
- throw new Error('Cannot change roles in personal entities');
162
- }
163
- // Check if user is the owner - cannot change owner's role
164
- const currentMember = await this.getMember(entityId, userId);
165
- if (currentMember?.role === types_1.EntityRole.OWNER) {
166
- throw new Error('Cannot change the owner\'s role');
167
- }
168
- const [updated] = await this.config.db
169
- .update(this.config.membersTable)
170
- .set({
171
- role,
172
- updated_at: new Date(),
173
- })
174
- .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.membersTable.entity_id, entityId), (0, drizzle_orm_1.eq)(this.config.membersTable.user_id, userId), (0, drizzle_orm_1.eq)(this.config.membersTable.is_active, true)))
175
- .returning();
176
- if (!updated) {
177
- throw new Error('Member not found or inactive');
178
- }
179
- // Fetch user info for response
180
- const users = await this.config.db
181
- .select({
182
- id: this.config.usersTable.firebase_uid,
183
- email: this.config.usersTable.email,
184
- displayName: this.config.usersTable.display_name,
185
- })
186
- .from(this.config.usersTable)
187
- .where((0, drizzle_orm_1.eq)(this.config.usersTable.firebase_uid, userId))
188
- .limit(1);
189
- return this.mapRecordToMember(updated, users[0] ?? null);
190
- }
191
- /**
192
- * Remove a member from an entity (soft delete).
193
- * Sets is_active = false instead of deleting the record.
194
- */
195
- async removeMember(entityId, userId) {
196
- // Check constraints for personal entities
197
- const entity = await this.config.db
198
- .select()
199
- .from(this.config.entitiesTable)
200
- .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.id, entityId))
201
- .limit(1);
202
- if (entity.length > 0 && entity[0].entity_type === types_1.EntityType.PERSONAL) {
203
- throw new Error('Cannot remove members from personal entities');
204
- }
205
- // Check if user is the owner - cannot remove owner
206
- const member = await this.getMember(entityId, userId);
207
- if (!member) {
208
- throw new Error('Member not found');
209
- }
210
- if (member.role === types_1.EntityRole.OWNER) {
211
- throw new Error('Cannot remove the entity owner');
212
- }
213
- // Soft delete - set is_active = false
214
- await this.config.db
215
- .update(this.config.membersTable)
216
- .set({
217
- is_active: false,
218
- updated_at: new Date(),
219
- })
220
- .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.membersTable.entity_id, entityId), (0, drizzle_orm_1.eq)(this.config.membersTable.user_id, userId)));
221
- }
222
- /**
223
- * Check if a user is a member of an entity.
224
- */
225
- async isMember(entityId, userId) {
226
- const role = await this.getUserRole(entityId, userId);
227
- return role !== null;
228
- }
229
- /**
230
- * Map database record to EntityMember type.
231
- */
232
- mapRecordToMember(record, user) {
233
- const member = {
234
- id: record.id,
235
- entityId: record.entity_id,
236
- userId: record.user_id,
237
- role: record.role,
238
- isActive: record.is_active ?? true,
239
- joinedAt: record.joined_at?.toISOString() ?? new Date().toISOString(),
240
- createdAt: record.created_at?.toISOString() ?? new Date().toISOString(),
241
- updatedAt: record.updated_at?.toISOString() ?? new Date().toISOString(),
242
- };
243
- if (user) {
244
- member.user = {
245
- id: user.id,
246
- email: user.email,
247
- displayName: user.displayName,
248
- };
249
- }
250
- return member;
251
- }
252
- }
253
- exports.EntityMemberHelper = EntityMemberHelper;
254
- //# sourceMappingURL=EntityMemberHelper.js.map