@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.
- package/CLAUDE.md +124 -0
- package/dist/helpers/EntityHelper.cjs +234 -0
- package/dist/helpers/EntityHelper.d.ts +60 -0
- package/dist/helpers/EntityHelper.d.ts.map +1 -0
- package/dist/helpers/EntityHelper.js +234 -0
- package/dist/helpers/EntityHelper.js.map +1 -0
- package/dist/helpers/EntityMemberHelper.cjs +215 -0
- package/dist/helpers/EntityMemberHelper.d.ts +45 -0
- package/dist/helpers/EntityMemberHelper.d.ts.map +1 -0
- package/dist/helpers/EntityMemberHelper.js +215 -0
- package/dist/helpers/EntityMemberHelper.js.map +1 -0
- package/dist/helpers/InvitationHelper.cjs +251 -0
- package/dist/helpers/InvitationHelper.d.ts +59 -0
- package/dist/helpers/InvitationHelper.d.ts.map +1 -0
- package/dist/helpers/InvitationHelper.js +251 -0
- package/dist/helpers/InvitationHelper.js.map +1 -0
- package/dist/helpers/PermissionHelper.cjs +197 -0
- package/dist/helpers/PermissionHelper.d.ts +86 -0
- package/dist/helpers/PermissionHelper.d.ts.map +1 -0
- package/dist/helpers/PermissionHelper.js +197 -0
- package/dist/helpers/PermissionHelper.js.map +1 -0
- package/dist/helpers/index.cjs +15 -0
- package/dist/helpers/index.d.ts +8 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +15 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/index.cjs +76 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/hono.cjs +148 -0
- package/dist/middleware/hono.d.ts +102 -0
- package/dist/middleware/hono.d.ts.map +1 -0
- package/dist/middleware/hono.js +148 -0
- package/dist/middleware/hono.js.map +1 -0
- package/dist/middleware/index.cjs +12 -0
- package/dist/middleware/index.d.ts +5 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +12 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/migrations/001_add_entities.cjs +269 -0
- package/dist/migrations/001_add_entities.d.ts +29 -0
- package/dist/migrations/001_add_entities.d.ts.map +1 -0
- package/dist/migrations/001_add_entities.js +269 -0
- package/dist/migrations/001_add_entities.js.map +1 -0
- package/dist/migrations/index.cjs +10 -0
- package/dist/migrations/index.d.ts +5 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +10 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/schema/entities.cjs +304 -0
- package/dist/schema/entities.d.ts +1047 -0
- package/dist/schema/entities.d.ts.map +1 -0
- package/dist/schema/entities.js +304 -0
- package/dist/schema/entities.js.map +1 -0
- package/dist/types/index.cjs +14 -0
- package/dist/types/index.d.ts +71 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +14 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.cjs +21 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +21 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/slug-generator.cjs +92 -0
- package/dist/utils/slug-generator.d.ts +41 -0
- package/dist/utils/slug-generator.d.ts.map +1 -0
- package/dist/utils/slug-generator.js +92 -0
- package/dist/utils/slug-generator.js.map +1 -0
- 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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"}
|