@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
package/CLAUDE.md ADDED
@@ -0,0 +1,124 @@
1
+ # Entity Service
2
+
3
+ Shared backend library for multi-tenant entity/organization management.
4
+
5
+ ## Overview
6
+
7
+ This library provides:
8
+ - **Entity management**: Personal workspaces and organizations
9
+ - **Member management**: Role-based access control (admin, manager, viewer)
10
+ - **Invitation system**: Email invitations with auto-accept on signup
11
+ - **Permission checking**: Granular permission checks
12
+ - **Hono middleware**: Entity context injection for routes
13
+
14
+ ## Usage
15
+
16
+ ### Setup
17
+
18
+ ```typescript
19
+ import { createEntityHelpers } from '@shapeshyft/entity-service';
20
+
21
+ const helpers = createEntityHelpers({
22
+ db: drizzleDb,
23
+ entitiesTable: schema.entities,
24
+ membersTable: schema.entityMembers,
25
+ invitationsTable: schema.entityInvitations,
26
+ usersTable: schema.users,
27
+ });
28
+ ```
29
+
30
+ ### Entity Operations
31
+
32
+ ```typescript
33
+ // Get or create personal entity (on user login)
34
+ const personalEntity = await helpers.entity.getOrCreatePersonalEntity(userId, email);
35
+
36
+ // Create organization
37
+ const org = await helpers.entity.createOrganizationEntity(userId, {
38
+ displayName: 'My Organization',
39
+ entitySlug: 'my-org', // optional
40
+ });
41
+
42
+ // Get user's entities
43
+ const entities = await helpers.entity.getUserEntities(userId);
44
+ ```
45
+
46
+ ### Member Operations
47
+
48
+ ```typescript
49
+ // Add member
50
+ await helpers.members.addMember(entityId, userId, EntityRole.MANAGER);
51
+
52
+ // Update role
53
+ await helpers.members.updateMemberRole(entityId, userId, EntityRole.ADMIN);
54
+
55
+ // Remove member
56
+ await helpers.members.removeMember(entityId, userId);
57
+ ```
58
+
59
+ ### Invitation Operations
60
+
61
+ ```typescript
62
+ // Create invitation
63
+ const invitation = await helpers.invitations.createInvitation(entityId, invitedBy, {
64
+ email: 'user@example.com',
65
+ role: EntityRole.VIEWER,
66
+ });
67
+
68
+ // Process pending invitations for new user
69
+ await helpers.invitations.processNewUserInvitations(userId, email);
70
+ ```
71
+
72
+ ### Hono Middleware
73
+
74
+ ```typescript
75
+ import { createEntityContextMiddleware } from '@shapeshyft/entity-service';
76
+
77
+ const entityContext = createEntityContextMiddleware(config, {
78
+ getUserId: (c) => c.get('userId'),
79
+ });
80
+
81
+ app.use('/api/v1/entities/:entitySlug/*', entityContext);
82
+
83
+ app.get('/api/v1/entities/:entitySlug/projects', (c) => {
84
+ const { entity, userRole, permissions } = c.get('entityContext');
85
+ // Use entity context...
86
+ });
87
+ ```
88
+
89
+ ## Database Schema
90
+
91
+ The library provides factory functions for creating tables in any PostgreSQL schema:
92
+
93
+ ```typescript
94
+ import { createEntitiesTable, createEntityMembersTable } from '@shapeshyft/entity-service';
95
+ import { pgSchema } from 'drizzle-orm/pg-core';
96
+
97
+ const mySchema = pgSchema('my_app');
98
+
99
+ export const entities = createEntitiesTable(mySchema, 'my_app');
100
+ export const entityMembers = createEntityMembersTable(mySchema, 'my_app');
101
+ ```
102
+
103
+ ## Role Permissions
104
+
105
+ | Permission | Admin | Manager | Viewer |
106
+ |------------|-------|---------|--------|
107
+ | View entity | ✓ | ✓ | ✓ |
108
+ | Edit entity | ✓ | ✗ | ✗ |
109
+ | Delete entity | ✓ | ✗ | ✗ |
110
+ | Manage members | ✓ | ✗ | ✗ |
111
+ | Invite members | ✓ | ✗ | ✗ |
112
+ | Manage projects | ✓ | ✓ | ✗ |
113
+ | Create projects | ✓ | ✓ | ✗ |
114
+ | View projects | ✓ | ✓ | ✓ |
115
+ | Manage API keys | ✓ | ✓ | ✗ |
116
+ | View API keys | ✓ | ✓ | ✓ |
117
+
118
+ ## Build
119
+
120
+ ```bash
121
+ bun run build # Build both ESM and CJS
122
+ bun run typecheck # Run TypeScript checks
123
+ bun run clean # Clean build artifacts
124
+ ```
@@ -0,0 +1,234 @@
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
+ */
22
+ async createPersonalEntity(userId, email) {
23
+ const slug = (0, utils_1.generateEntitySlug)();
24
+ const displayName = email?.split('@')[0] ?? 'Personal';
25
+ const [entity] = await this.config.db
26
+ .insert(this.config.entitiesTable)
27
+ .values({
28
+ entity_slug: slug,
29
+ entity_type: types_1.EntityType.PERSONAL,
30
+ display_name: displayName,
31
+ owner_user_id: userId,
32
+ })
33
+ .returning();
34
+ // Add user as admin member
35
+ await this.config.db.insert(this.config.membersTable).values({
36
+ entity_id: entity.id,
37
+ user_id: userId,
38
+ role: types_1.EntityRole.ADMIN,
39
+ });
40
+ return this.mapRecordToEntity(entity);
41
+ }
42
+ /**
43
+ * Get or create a personal entity for a user.
44
+ * Ensures exactly one personal entity exists per user.
45
+ */
46
+ async getOrCreatePersonalEntity(userId, email) {
47
+ // Check for existing personal entity
48
+ const existing = await this.config.db
49
+ .select()
50
+ .from(this.config.entitiesTable)
51
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.entitiesTable.owner_user_id, userId), (0, drizzle_orm_1.eq)(this.config.entitiesTable.entity_type, types_1.EntityType.PERSONAL)))
52
+ .limit(1);
53
+ if (existing.length > 0) {
54
+ return this.mapRecordToEntity(existing[0]);
55
+ }
56
+ return this.createPersonalEntity(userId, email);
57
+ }
58
+ /**
59
+ * Create an organization entity.
60
+ */
61
+ async createOrganizationEntity(userId, request) {
62
+ // Determine slug
63
+ let slug;
64
+ if (request.entitySlug) {
65
+ slug = (0, utils_1.normalizeSlug)(request.entitySlug);
66
+ if (!(0, utils_1.validateSlug)(slug)) {
67
+ throw new Error('Invalid entity slug format');
68
+ }
69
+ // Check availability
70
+ if (!(await this.isSlugAvailable(slug))) {
71
+ throw new Error('Entity slug is already taken');
72
+ }
73
+ }
74
+ else {
75
+ slug = await this.generateUniqueSlug();
76
+ }
77
+ const [entity] = await this.config.db
78
+ .insert(this.config.entitiesTable)
79
+ .values({
80
+ entity_slug: slug,
81
+ entity_type: types_1.EntityType.ORGANIZATION,
82
+ display_name: request.displayName,
83
+ description: request.description ?? null,
84
+ owner_user_id: userId,
85
+ })
86
+ .returning();
87
+ // Add creator as admin member
88
+ await this.config.db.insert(this.config.membersTable).values({
89
+ entity_id: entity.id,
90
+ user_id: userId,
91
+ role: types_1.EntityRole.ADMIN,
92
+ });
93
+ return this.mapRecordToEntity(entity);
94
+ }
95
+ /**
96
+ * Get entity by ID.
97
+ */
98
+ async getEntity(entityId) {
99
+ const results = await this.config.db
100
+ .select()
101
+ .from(this.config.entitiesTable)
102
+ .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.id, entityId))
103
+ .limit(1);
104
+ if (results.length === 0) {
105
+ return null;
106
+ }
107
+ return this.mapRecordToEntity(results[0]);
108
+ }
109
+ /**
110
+ * Get entity by slug.
111
+ */
112
+ async getEntityBySlug(slug) {
113
+ const results = await this.config.db
114
+ .select()
115
+ .from(this.config.entitiesTable)
116
+ .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.entity_slug, slug))
117
+ .limit(1);
118
+ if (results.length === 0) {
119
+ return null;
120
+ }
121
+ return this.mapRecordToEntity(results[0]);
122
+ }
123
+ /**
124
+ * Get all entities a user is a member of.
125
+ */
126
+ async getUserEntities(userId) {
127
+ const results = await this.config.db
128
+ .select({
129
+ entity: this.config.entitiesTable,
130
+ role: this.config.membersTable.role,
131
+ })
132
+ .from(this.config.membersTable)
133
+ .innerJoin(this.config.entitiesTable, (0, drizzle_orm_1.eq)(this.config.membersTable.entity_id, this.config.entitiesTable.id))
134
+ .where((0, drizzle_orm_1.eq)(this.config.membersTable.user_id, userId));
135
+ return results.map(({ entity, role }) => ({
136
+ ...this.mapRecordToEntity(entity),
137
+ userRole: role,
138
+ }));
139
+ }
140
+ /**
141
+ * Update entity details.
142
+ */
143
+ async updateEntity(entityId, request) {
144
+ const updates = {
145
+ updated_at: new Date(),
146
+ };
147
+ if (request.displayName !== undefined) {
148
+ updates.display_name = request.displayName;
149
+ }
150
+ if (request.description !== undefined) {
151
+ updates.description = request.description;
152
+ }
153
+ if (request.avatarUrl !== undefined) {
154
+ updates.avatar_url = request.avatarUrl;
155
+ }
156
+ if (request.entitySlug !== undefined) {
157
+ const slug = (0, utils_1.normalizeSlug)(request.entitySlug);
158
+ if (!(0, utils_1.validateSlug)(slug)) {
159
+ throw new Error('Invalid entity slug format');
160
+ }
161
+ // Check if changing slug
162
+ const existing = await this.getEntity(entityId);
163
+ if (existing && existing.entitySlug !== slug) {
164
+ if (!(await this.isSlugAvailable(slug))) {
165
+ throw new Error('Entity slug is already taken');
166
+ }
167
+ updates.entity_slug = slug;
168
+ }
169
+ }
170
+ const [updated] = await this.config.db
171
+ .update(this.config.entitiesTable)
172
+ .set(updates)
173
+ .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.id, entityId))
174
+ .returning();
175
+ return this.mapRecordToEntity(updated);
176
+ }
177
+ /**
178
+ * Delete an entity.
179
+ * Only organizations can be deleted; personal entities cannot.
180
+ */
181
+ async deleteEntity(entityId) {
182
+ const entity = await this.getEntity(entityId);
183
+ if (!entity) {
184
+ throw new Error('Entity not found');
185
+ }
186
+ if (entity.entityType === types_1.EntityType.PERSONAL) {
187
+ throw new Error('Personal entities cannot be deleted');
188
+ }
189
+ await this.config.db
190
+ .delete(this.config.entitiesTable)
191
+ .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.id, entityId));
192
+ }
193
+ /**
194
+ * Check if a slug is available.
195
+ */
196
+ async isSlugAvailable(slug) {
197
+ const results = await this.config.db
198
+ .select({ id: this.config.entitiesTable.id })
199
+ .from(this.config.entitiesTable)
200
+ .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.entity_slug, slug))
201
+ .limit(1);
202
+ return results.length === 0;
203
+ }
204
+ /**
205
+ * Generate a unique slug.
206
+ */
207
+ async generateUniqueSlug() {
208
+ for (let attempts = 0; attempts < 10; attempts++) {
209
+ const slug = (0, utils_1.generateEntitySlug)();
210
+ if (await this.isSlugAvailable(slug)) {
211
+ return slug;
212
+ }
213
+ }
214
+ throw new Error('Failed to generate unique slug');
215
+ }
216
+ /**
217
+ * Map database record to Entity type.
218
+ */
219
+ mapRecordToEntity(record) {
220
+ return {
221
+ id: record.id,
222
+ entitySlug: record.entity_slug,
223
+ entityType: record.entity_type,
224
+ displayName: record.display_name,
225
+ description: record.description,
226
+ avatarUrl: record.avatar_url,
227
+ ownerUserId: record.owner_user_id,
228
+ createdAt: record.created_at?.toISOString() ?? new Date().toISOString(),
229
+ updatedAt: record.updated_at?.toISOString() ?? new Date().toISOString(),
230
+ };
231
+ }
232
+ }
233
+ exports.EntityHelper = EntityHelper;
234
+ //# sourceMappingURL=EntityHelper.js.map
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @fileoverview Entity Helper Class
3
+ * @description CRUD operations for entities (personal and organization workspaces)
4
+ */
5
+ import { type Entity, type EntityWithRole, type CreateEntityRequest, type UpdateEntityRequest, type EntityHelperConfig } from '../types';
6
+ /**
7
+ * Helper class for entity CRUD operations.
8
+ */
9
+ export declare class EntityHelper {
10
+ private readonly config;
11
+ constructor(config: EntityHelperConfig);
12
+ /**
13
+ * Create a personal entity for a user.
14
+ * Called automatically when a user first logs in.
15
+ */
16
+ createPersonalEntity(userId: string, email?: string): Promise<Entity>;
17
+ /**
18
+ * Get or create a personal entity for a user.
19
+ * Ensures exactly one personal entity exists per user.
20
+ */
21
+ getOrCreatePersonalEntity(userId: string, email?: string): Promise<Entity>;
22
+ /**
23
+ * Create an organization entity.
24
+ */
25
+ createOrganizationEntity(userId: string, request: CreateEntityRequest): Promise<Entity>;
26
+ /**
27
+ * Get entity by ID.
28
+ */
29
+ getEntity(entityId: string): Promise<Entity | null>;
30
+ /**
31
+ * Get entity by slug.
32
+ */
33
+ getEntityBySlug(slug: string): Promise<Entity | null>;
34
+ /**
35
+ * Get all entities a user is a member of.
36
+ */
37
+ getUserEntities(userId: string): Promise<EntityWithRole[]>;
38
+ /**
39
+ * Update entity details.
40
+ */
41
+ updateEntity(entityId: string, request: UpdateEntityRequest): Promise<Entity>;
42
+ /**
43
+ * Delete an entity.
44
+ * Only organizations can be deleted; personal entities cannot.
45
+ */
46
+ deleteEntity(entityId: string): Promise<void>;
47
+ /**
48
+ * Check if a slug is available.
49
+ */
50
+ isSlugAvailable(slug: string): Promise<boolean>;
51
+ /**
52
+ * Generate a unique slug.
53
+ */
54
+ private generateUniqueSlug;
55
+ /**
56
+ * Map database record to Entity type.
57
+ */
58
+ private mapRecordToEntity;
59
+ }
60
+ //# sourceMappingURL=EntityHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityHelper.d.ts","sourceRoot":"","sources":["../../src/helpers/EntityHelper.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAGL,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,UAAU,CAAC;AAGlB;;GAEG;AACH,qBAAa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,kBAAkB;IAEvD;;;OAGG;IACG,oBAAoB,CACxB,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC;IAwBlB;;;OAGG;IACG,yBAAyB,CAC7B,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC;IAoBlB;;OAEG;IACG,wBAAwB,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC;IAqClB;;OAEG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAczD;;OAEG;IACG,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAc3D;;OAEG;IACG,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IAmBhE;;OAEG;IACG,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC;IAyClB;;;OAGG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAenD;;OAEG;IACG,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUrD;;OAEG;YACW,kBAAkB;IAUhC;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAa1B"}
@@ -0,0 +1,234 @@
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
+ */
22
+ async createPersonalEntity(userId, email) {
23
+ const slug = (0, utils_1.generateEntitySlug)();
24
+ const displayName = email?.split('@')[0] ?? 'Personal';
25
+ const [entity] = await this.config.db
26
+ .insert(this.config.entitiesTable)
27
+ .values({
28
+ entity_slug: slug,
29
+ entity_type: types_1.EntityType.PERSONAL,
30
+ display_name: displayName,
31
+ owner_user_id: userId,
32
+ })
33
+ .returning();
34
+ // Add user as admin member
35
+ await this.config.db.insert(this.config.membersTable).values({
36
+ entity_id: entity.id,
37
+ user_id: userId,
38
+ role: types_1.EntityRole.ADMIN,
39
+ });
40
+ return this.mapRecordToEntity(entity);
41
+ }
42
+ /**
43
+ * Get or create a personal entity for a user.
44
+ * Ensures exactly one personal entity exists per user.
45
+ */
46
+ async getOrCreatePersonalEntity(userId, email) {
47
+ // Check for existing personal entity
48
+ const existing = await this.config.db
49
+ .select()
50
+ .from(this.config.entitiesTable)
51
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(this.config.entitiesTable.owner_user_id, userId), (0, drizzle_orm_1.eq)(this.config.entitiesTable.entity_type, types_1.EntityType.PERSONAL)))
52
+ .limit(1);
53
+ if (existing.length > 0) {
54
+ return this.mapRecordToEntity(existing[0]);
55
+ }
56
+ return this.createPersonalEntity(userId, email);
57
+ }
58
+ /**
59
+ * Create an organization entity.
60
+ */
61
+ async createOrganizationEntity(userId, request) {
62
+ // Determine slug
63
+ let slug;
64
+ if (request.entitySlug) {
65
+ slug = (0, utils_1.normalizeSlug)(request.entitySlug);
66
+ if (!(0, utils_1.validateSlug)(slug)) {
67
+ throw new Error('Invalid entity slug format');
68
+ }
69
+ // Check availability
70
+ if (!(await this.isSlugAvailable(slug))) {
71
+ throw new Error('Entity slug is already taken');
72
+ }
73
+ }
74
+ else {
75
+ slug = await this.generateUniqueSlug();
76
+ }
77
+ const [entity] = await this.config.db
78
+ .insert(this.config.entitiesTable)
79
+ .values({
80
+ entity_slug: slug,
81
+ entity_type: types_1.EntityType.ORGANIZATION,
82
+ display_name: request.displayName,
83
+ description: request.description ?? null,
84
+ owner_user_id: userId,
85
+ })
86
+ .returning();
87
+ // Add creator as admin member
88
+ await this.config.db.insert(this.config.membersTable).values({
89
+ entity_id: entity.id,
90
+ user_id: userId,
91
+ role: types_1.EntityRole.ADMIN,
92
+ });
93
+ return this.mapRecordToEntity(entity);
94
+ }
95
+ /**
96
+ * Get entity by ID.
97
+ */
98
+ async getEntity(entityId) {
99
+ const results = await this.config.db
100
+ .select()
101
+ .from(this.config.entitiesTable)
102
+ .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.id, entityId))
103
+ .limit(1);
104
+ if (results.length === 0) {
105
+ return null;
106
+ }
107
+ return this.mapRecordToEntity(results[0]);
108
+ }
109
+ /**
110
+ * Get entity by slug.
111
+ */
112
+ async getEntityBySlug(slug) {
113
+ const results = await this.config.db
114
+ .select()
115
+ .from(this.config.entitiesTable)
116
+ .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.entity_slug, slug))
117
+ .limit(1);
118
+ if (results.length === 0) {
119
+ return null;
120
+ }
121
+ return this.mapRecordToEntity(results[0]);
122
+ }
123
+ /**
124
+ * Get all entities a user is a member of.
125
+ */
126
+ async getUserEntities(userId) {
127
+ const results = await this.config.db
128
+ .select({
129
+ entity: this.config.entitiesTable,
130
+ role: this.config.membersTable.role,
131
+ })
132
+ .from(this.config.membersTable)
133
+ .innerJoin(this.config.entitiesTable, (0, drizzle_orm_1.eq)(this.config.membersTable.entity_id, this.config.entitiesTable.id))
134
+ .where((0, drizzle_orm_1.eq)(this.config.membersTable.user_id, userId));
135
+ return results.map(({ entity, role }) => ({
136
+ ...this.mapRecordToEntity(entity),
137
+ userRole: role,
138
+ }));
139
+ }
140
+ /**
141
+ * Update entity details.
142
+ */
143
+ async updateEntity(entityId, request) {
144
+ const updates = {
145
+ updated_at: new Date(),
146
+ };
147
+ if (request.displayName !== undefined) {
148
+ updates.display_name = request.displayName;
149
+ }
150
+ if (request.description !== undefined) {
151
+ updates.description = request.description;
152
+ }
153
+ if (request.avatarUrl !== undefined) {
154
+ updates.avatar_url = request.avatarUrl;
155
+ }
156
+ if (request.entitySlug !== undefined) {
157
+ const slug = (0, utils_1.normalizeSlug)(request.entitySlug);
158
+ if (!(0, utils_1.validateSlug)(slug)) {
159
+ throw new Error('Invalid entity slug format');
160
+ }
161
+ // Check if changing slug
162
+ const existing = await this.getEntity(entityId);
163
+ if (existing && existing.entitySlug !== slug) {
164
+ if (!(await this.isSlugAvailable(slug))) {
165
+ throw new Error('Entity slug is already taken');
166
+ }
167
+ updates.entity_slug = slug;
168
+ }
169
+ }
170
+ const [updated] = await this.config.db
171
+ .update(this.config.entitiesTable)
172
+ .set(updates)
173
+ .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.id, entityId))
174
+ .returning();
175
+ return this.mapRecordToEntity(updated);
176
+ }
177
+ /**
178
+ * Delete an entity.
179
+ * Only organizations can be deleted; personal entities cannot.
180
+ */
181
+ async deleteEntity(entityId) {
182
+ const entity = await this.getEntity(entityId);
183
+ if (!entity) {
184
+ throw new Error('Entity not found');
185
+ }
186
+ if (entity.entityType === types_1.EntityType.PERSONAL) {
187
+ throw new Error('Personal entities cannot be deleted');
188
+ }
189
+ await this.config.db
190
+ .delete(this.config.entitiesTable)
191
+ .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.id, entityId));
192
+ }
193
+ /**
194
+ * Check if a slug is available.
195
+ */
196
+ async isSlugAvailable(slug) {
197
+ const results = await this.config.db
198
+ .select({ id: this.config.entitiesTable.id })
199
+ .from(this.config.entitiesTable)
200
+ .where((0, drizzle_orm_1.eq)(this.config.entitiesTable.entity_slug, slug))
201
+ .limit(1);
202
+ return results.length === 0;
203
+ }
204
+ /**
205
+ * Generate a unique slug.
206
+ */
207
+ async generateUniqueSlug() {
208
+ for (let attempts = 0; attempts < 10; attempts++) {
209
+ const slug = (0, utils_1.generateEntitySlug)();
210
+ if (await this.isSlugAvailable(slug)) {
211
+ return slug;
212
+ }
213
+ }
214
+ throw new Error('Failed to generate unique slug');
215
+ }
216
+ /**
217
+ * Map database record to Entity type.
218
+ */
219
+ mapRecordToEntity(record) {
220
+ return {
221
+ id: record.id,
222
+ entitySlug: record.entity_slug,
223
+ entityType: record.entity_type,
224
+ displayName: record.display_name,
225
+ description: record.description,
226
+ avatarUrl: record.avatar_url,
227
+ ownerUserId: record.owner_user_id,
228
+ createdAt: record.created_at?.toISOString() ?? new Date().toISOString(),
229
+ updatedAt: record.updated_at?.toISOString() ?? new Date().toISOString(),
230
+ };
231
+ }
232
+ }
233
+ exports.EntityHelper = EntityHelper;
234
+ //# sourceMappingURL=EntityHelper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityHelper.js","sourceRoot":"","sources":["../../src/helpers/EntityHelper.ts"],"names":[],"mappings":"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"}