@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.
- package/dist/helpers/EntityHelper.js +25 -29
- package/dist/helpers/EntityHelper.js.map +1 -1
- package/dist/helpers/EntityMemberHelper.js +26 -30
- package/dist/helpers/EntityMemberHelper.js.map +1 -1
- package/dist/helpers/InvitationHelper.js +28 -32
- package/dist/helpers/InvitationHelper.js.map +1 -1
- package/dist/helpers/PermissionHelper.js +13 -17
- package/dist/helpers/PermissionHelper.js.map +1 -1
- package/dist/helpers/index.js +4 -11
- package/dist/helpers/index.js.map +1 -1
- package/dist/index.js +9 -38
- package/dist/index.js.map +1 -1
- package/dist/middleware/hono.js +18 -24
- package/dist/middleware/hono.js.map +1 -1
- package/dist/middleware/index.js +1 -8
- package/dist/middleware/index.js.map +1 -1
- package/dist/migrations/001_add_entities.js +2 -6
- package/dist/migrations/001_add_entities.js.map +1 -1
- package/dist/migrations/index.js +1 -6
- package/dist/migrations/index.js.map +1 -1
- package/dist/schema/entities.js +125 -135
- package/dist/schema/entities.js.map +1 -1
- package/dist/types/index.js +1 -8
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.js +1 -17
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/slug-generator.js +6 -14
- package/dist/utils/slug-generator.js.map +1 -1
- package/package.json +9 -16
- package/dist/helpers/EntityHelper.cjs +0 -253
- package/dist/helpers/EntityMemberHelper.cjs +0 -254
- package/dist/helpers/InvitationHelper.cjs +0 -256
- package/dist/helpers/PermissionHelper.cjs +0 -193
- package/dist/helpers/index.cjs +0 -15
- package/dist/index.cjs +0 -76
- package/dist/middleware/hono.cjs +0 -148
- package/dist/middleware/index.cjs +0 -12
- package/dist/migrations/001_add_entities.cjs +0 -330
- package/dist/migrations/index.cjs +0 -10
- package/dist/schema/entities.cjs +0 -311
- package/dist/types/index.cjs +0 -14
- package/dist/utils/index.cjs +0 -21
- 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
|