@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
package/dist/middleware/hono.cjs
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview Hono Middleware for Entity Context
|
|
4
|
-
* @description Middleware to inject entity context into Hono request handlers
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.createEntityContextMiddleware = createEntityContextMiddleware;
|
|
8
|
-
exports.createRequirePermissionMiddleware = createRequirePermissionMiddleware;
|
|
9
|
-
exports.createRequireRoleMiddleware = createRequireRoleMiddleware;
|
|
10
|
-
exports.createEntityHelpers = createEntityHelpers;
|
|
11
|
-
const EntityHelper_1 = require("../helpers/EntityHelper");
|
|
12
|
-
const EntityMemberHelper_1 = require("../helpers/EntityMemberHelper");
|
|
13
|
-
const InvitationHelper_1 = require("../helpers/InvitationHelper");
|
|
14
|
-
const PermissionHelper_1 = require("../helpers/PermissionHelper");
|
|
15
|
-
const types_1 = require("../types");
|
|
16
|
-
/**
|
|
17
|
-
* Create middleware that injects entity context into the request.
|
|
18
|
-
*
|
|
19
|
-
* Usage:
|
|
20
|
-
* ```typescript
|
|
21
|
-
* const entityContext = createEntityContextMiddleware(config, {
|
|
22
|
-
* getUserId: (c) => c.get('userId'),
|
|
23
|
-
* entitySlugParam: 'entitySlug',
|
|
24
|
-
* });
|
|
25
|
-
*
|
|
26
|
-
* app.use('/api/v1/entities/:entitySlug/*', entityContext);
|
|
27
|
-
*
|
|
28
|
-
* app.get('/api/v1/entities/:entitySlug/projects', (c) => {
|
|
29
|
-
* const { entity, userRole, permissions } = c.get('entityContext');
|
|
30
|
-
* // ...
|
|
31
|
-
* });
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
function createEntityContextMiddleware(config, options) {
|
|
35
|
-
const entityHelper = new EntityHelper_1.EntityHelper(config);
|
|
36
|
-
const permissionHelper = new PermissionHelper_1.PermissionHelper(config);
|
|
37
|
-
const slugParam = options.entitySlugParam ?? 'entitySlug';
|
|
38
|
-
return async (c, next) => {
|
|
39
|
-
const entitySlug = c.req.param(slugParam);
|
|
40
|
-
if (!entitySlug) {
|
|
41
|
-
return c.json({ error: 'Entity slug is required' }, 400);
|
|
42
|
-
}
|
|
43
|
-
const userId = options.getUserId(c);
|
|
44
|
-
if (!userId && !options.allowUnauthenticated) {
|
|
45
|
-
return c.json({ error: 'Authentication required' }, 401);
|
|
46
|
-
}
|
|
47
|
-
// Get entity by slug
|
|
48
|
-
const entity = await entityHelper.getEntityBySlug(entitySlug);
|
|
49
|
-
if (!entity) {
|
|
50
|
-
return c.json({ error: 'Entity not found' }, 404);
|
|
51
|
-
}
|
|
52
|
-
// If unauthenticated access is allowed and no user, continue without role
|
|
53
|
-
if (!userId && options.allowUnauthenticated) {
|
|
54
|
-
c.set('entity', entity);
|
|
55
|
-
await next();
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
// Get user's role and permissions
|
|
59
|
-
const userRole = await permissionHelper.getUserRole(entity.id, userId);
|
|
60
|
-
if (!userRole) {
|
|
61
|
-
return c.json({ error: 'Access denied' }, 403);
|
|
62
|
-
}
|
|
63
|
-
const permissions = permissionHelper.getPermissionsForRole(userRole);
|
|
64
|
-
// Set entity context
|
|
65
|
-
const entityContext = {
|
|
66
|
-
entity,
|
|
67
|
-
userRole,
|
|
68
|
-
permissions,
|
|
69
|
-
};
|
|
70
|
-
c.set('entityContext', entityContext);
|
|
71
|
-
c.set('entity', entity);
|
|
72
|
-
c.set('userRole', userRole);
|
|
73
|
-
c.set('permissions', permissions);
|
|
74
|
-
await next();
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Create middleware that requires a specific permission.
|
|
79
|
-
*
|
|
80
|
-
* Usage:
|
|
81
|
-
* ```typescript
|
|
82
|
-
* const requireAdmin = createRequirePermissionMiddleware('canManageMembers');
|
|
83
|
-
*
|
|
84
|
-
* app.post('/api/v1/entities/:entitySlug/members', requireAdmin, (c) => {
|
|
85
|
-
* // Only admins can reach here
|
|
86
|
-
* });
|
|
87
|
-
* ```
|
|
88
|
-
*/
|
|
89
|
-
function createRequirePermissionMiddleware(permission) {
|
|
90
|
-
return async (c, next) => {
|
|
91
|
-
const permissions = c.get('permissions');
|
|
92
|
-
if (!permissions) {
|
|
93
|
-
return c.json({ error: 'Entity context not found' }, 500);
|
|
94
|
-
}
|
|
95
|
-
if (!permissions[permission]) {
|
|
96
|
-
return c.json({ error: 'Insufficient permissions' }, 403);
|
|
97
|
-
}
|
|
98
|
-
await next();
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Create middleware that requires a minimum role.
|
|
103
|
-
*
|
|
104
|
-
* Usage:
|
|
105
|
-
* ```typescript
|
|
106
|
-
* const requireAdmin = createRequireRoleMiddleware(EntityRole.ADMIN);
|
|
107
|
-
*
|
|
108
|
-
* app.post('/api/v1/entities/:entitySlug/projects', requireAdmin, (c) => {
|
|
109
|
-
* // Only admins and owners can reach here
|
|
110
|
-
* });
|
|
111
|
-
* ```
|
|
112
|
-
*/
|
|
113
|
-
function createRequireRoleMiddleware(minimumRole) {
|
|
114
|
-
const roleHierarchy = {
|
|
115
|
-
[types_1.EntityRole.MEMBER]: 0,
|
|
116
|
-
[types_1.EntityRole.ADMIN]: 1,
|
|
117
|
-
[types_1.EntityRole.OWNER]: 2,
|
|
118
|
-
};
|
|
119
|
-
return async (c, next) => {
|
|
120
|
-
const userRole = c.get('userRole');
|
|
121
|
-
if (!userRole) {
|
|
122
|
-
return c.json({ error: 'Entity context not found' }, 500);
|
|
123
|
-
}
|
|
124
|
-
if (roleHierarchy[userRole] < roleHierarchy[minimumRole]) {
|
|
125
|
-
return c.json({ error: 'Insufficient role' }, 403);
|
|
126
|
-
}
|
|
127
|
-
await next();
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Create all entity helpers with shared config.
|
|
132
|
-
*
|
|
133
|
-
* Usage:
|
|
134
|
-
* ```typescript
|
|
135
|
-
* const helpers = createEntityHelpers(config);
|
|
136
|
-
* const entity = await helpers.entity.getEntity(entityId);
|
|
137
|
-
* const members = await helpers.members.getMembers(entityId);
|
|
138
|
-
* ```
|
|
139
|
-
*/
|
|
140
|
-
function createEntityHelpers(config) {
|
|
141
|
-
return {
|
|
142
|
-
entity: new EntityHelper_1.EntityHelper(config),
|
|
143
|
-
members: new EntityMemberHelper_1.EntityMemberHelper(config),
|
|
144
|
-
invitations: new InvitationHelper_1.InvitationHelper(config),
|
|
145
|
-
permissions: new PermissionHelper_1.PermissionHelper(config),
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
//# sourceMappingURL=hono.js.map
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview Middleware Exports
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.createEntityHelpers = exports.createRequireRoleMiddleware = exports.createRequirePermissionMiddleware = exports.createEntityContextMiddleware = void 0;
|
|
7
|
-
var hono_1 = require("./hono");
|
|
8
|
-
Object.defineProperty(exports, "createEntityContextMiddleware", { enumerable: true, get: function () { return hono_1.createEntityContextMiddleware; } });
|
|
9
|
-
Object.defineProperty(exports, "createRequirePermissionMiddleware", { enumerable: true, get: function () { return hono_1.createRequirePermissionMiddleware; } });
|
|
10
|
-
Object.defineProperty(exports, "createRequireRoleMiddleware", { enumerable: true, get: function () { return hono_1.createRequireRoleMiddleware; } });
|
|
11
|
-
Object.defineProperty(exports, "createEntityHelpers", { enumerable: true, get: function () { return hono_1.createEntityHelpers; } });
|
|
12
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview Entity Tables Migration
|
|
4
|
-
* @description Creates entity tables and migrates existing data
|
|
5
|
-
*
|
|
6
|
-
* This migration:
|
|
7
|
-
* 1. Creates entities, entity_members, entity_invitations tables
|
|
8
|
-
* 2. Adds entity_id column to projects table (nullable for backward compatibility)
|
|
9
|
-
* 3. Creates personal entities for existing users
|
|
10
|
-
* 4. Populates entity_id for existing projects
|
|
11
|
-
*/
|
|
12
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.runEntityMigration = runEntityMigration;
|
|
14
|
-
exports.rollbackEntityMigration = rollbackEntityMigration;
|
|
15
|
-
/**
|
|
16
|
-
* Run the full entity migration.
|
|
17
|
-
*/
|
|
18
|
-
async function runEntityMigration(config) {
|
|
19
|
-
const { client, schemaName, indexPrefix, migrateProjects = true } = config;
|
|
20
|
-
const prefix = `${schemaName}.`;
|
|
21
|
-
console.log(`Running entity migration for schema: ${schemaName}`);
|
|
22
|
-
// Step 1: Create entity tables
|
|
23
|
-
await createEntityTables(client, prefix, indexPrefix);
|
|
24
|
-
// Step 2: Add entity_id to projects if requested
|
|
25
|
-
if (migrateProjects) {
|
|
26
|
-
await addEntityIdToProjects(client, prefix, indexPrefix);
|
|
27
|
-
}
|
|
28
|
-
// Step 3: Migrate existing users to personal entities
|
|
29
|
-
await migrateUsersToPersonalEntities(client, prefix);
|
|
30
|
-
// Step 4: Populate entity_id for existing projects
|
|
31
|
-
if (migrateProjects) {
|
|
32
|
-
await populateProjectEntityIds(client, prefix);
|
|
33
|
-
}
|
|
34
|
-
console.log('Entity migration completed successfully');
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Create the entity tables.
|
|
38
|
-
* Note: Ownership is tracked via entity_members (role = 'owner'), not entities.owner_user_id.
|
|
39
|
-
*/
|
|
40
|
-
async function createEntityTables(client, prefix, indexPrefix) {
|
|
41
|
-
console.log('Creating entity tables...');
|
|
42
|
-
// Create entities table (ownership tracked via entity_members)
|
|
43
|
-
await client.unsafe(`
|
|
44
|
-
CREATE TABLE IF NOT EXISTS ${prefix}entities (
|
|
45
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
46
|
-
entity_slug VARCHAR(12) NOT NULL UNIQUE,
|
|
47
|
-
entity_type VARCHAR(20) NOT NULL CHECK (entity_type IN ('personal', 'organization')),
|
|
48
|
-
display_name VARCHAR(255) NOT NULL,
|
|
49
|
-
description TEXT,
|
|
50
|
-
avatar_url TEXT,
|
|
51
|
-
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
52
|
-
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
53
|
-
)
|
|
54
|
-
`);
|
|
55
|
-
await client.unsafe(`
|
|
56
|
-
CREATE UNIQUE INDEX IF NOT EXISTS ${indexPrefix}_entities_slug_idx
|
|
57
|
-
ON ${prefix}entities (entity_slug)
|
|
58
|
-
`);
|
|
59
|
-
await client.unsafe(`
|
|
60
|
-
CREATE INDEX IF NOT EXISTS ${indexPrefix}_entities_type_idx
|
|
61
|
-
ON ${prefix}entities (entity_type)
|
|
62
|
-
`);
|
|
63
|
-
// Create entity_members table (tracks all user-entity relationships including ownership)
|
|
64
|
-
await client.unsafe(`
|
|
65
|
-
CREATE TABLE IF NOT EXISTS ${prefix}entity_members (
|
|
66
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
67
|
-
entity_id UUID NOT NULL REFERENCES ${prefix}entities(id) ON DELETE CASCADE,
|
|
68
|
-
user_id VARCHAR(128) NOT NULL,
|
|
69
|
-
role VARCHAR(20) NOT NULL CHECK (role IN ('owner', 'admin', 'member')),
|
|
70
|
-
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
71
|
-
joined_at TIMESTAMPTZ DEFAULT NOW(),
|
|
72
|
-
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
73
|
-
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
74
|
-
UNIQUE(entity_id, user_id)
|
|
75
|
-
)
|
|
76
|
-
`);
|
|
77
|
-
// Add is_active column if it doesn't exist (for upgrading existing tables)
|
|
78
|
-
await client.unsafe(`
|
|
79
|
-
DO $$
|
|
80
|
-
BEGIN
|
|
81
|
-
IF NOT EXISTS (
|
|
82
|
-
SELECT 1 FROM information_schema.columns
|
|
83
|
-
WHERE table_schema = '${prefix.replace('.', '')}'
|
|
84
|
-
AND table_name = 'entity_members'
|
|
85
|
-
AND column_name = 'is_active'
|
|
86
|
-
) THEN
|
|
87
|
-
ALTER TABLE ${prefix}entity_members
|
|
88
|
-
ADD COLUMN is_active BOOLEAN NOT NULL DEFAULT true;
|
|
89
|
-
END IF;
|
|
90
|
-
END $$;
|
|
91
|
-
`);
|
|
92
|
-
await client.unsafe(`
|
|
93
|
-
CREATE UNIQUE INDEX IF NOT EXISTS ${indexPrefix}_entity_members_entity_user_idx
|
|
94
|
-
ON ${prefix}entity_members (entity_id, user_id)
|
|
95
|
-
`);
|
|
96
|
-
await client.unsafe(`
|
|
97
|
-
CREATE INDEX IF NOT EXISTS ${indexPrefix}_entity_members_entity_idx
|
|
98
|
-
ON ${prefix}entity_members (entity_id)
|
|
99
|
-
`);
|
|
100
|
-
await client.unsafe(`
|
|
101
|
-
CREATE INDEX IF NOT EXISTS ${indexPrefix}_entity_members_user_idx
|
|
102
|
-
ON ${prefix}entity_members (user_id)
|
|
103
|
-
`);
|
|
104
|
-
await client.unsafe(`
|
|
105
|
-
CREATE INDEX IF NOT EXISTS ${indexPrefix}_entity_members_active_idx
|
|
106
|
-
ON ${prefix}entity_members (is_active)
|
|
107
|
-
`);
|
|
108
|
-
// Create entity_invitations table
|
|
109
|
-
await client.unsafe(`
|
|
110
|
-
CREATE TABLE IF NOT EXISTS ${prefix}entity_invitations (
|
|
111
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
112
|
-
entity_id UUID NOT NULL REFERENCES ${prefix}entities(id) ON DELETE CASCADE,
|
|
113
|
-
email VARCHAR(255) NOT NULL,
|
|
114
|
-
role VARCHAR(20) NOT NULL CHECK (role IN ('admin', 'member')),
|
|
115
|
-
status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'accepted', 'declined', 'expired')),
|
|
116
|
-
invited_by_user_id VARCHAR(128) NOT NULL,
|
|
117
|
-
token VARCHAR(64) NOT NULL UNIQUE,
|
|
118
|
-
expires_at TIMESTAMPTZ NOT NULL,
|
|
119
|
-
accepted_at TIMESTAMPTZ,
|
|
120
|
-
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
121
|
-
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
122
|
-
)
|
|
123
|
-
`);
|
|
124
|
-
await client.unsafe(`
|
|
125
|
-
CREATE UNIQUE INDEX IF NOT EXISTS ${indexPrefix}_entity_invitations_token_idx
|
|
126
|
-
ON ${prefix}entity_invitations (token)
|
|
127
|
-
`);
|
|
128
|
-
await client.unsafe(`
|
|
129
|
-
CREATE INDEX IF NOT EXISTS ${indexPrefix}_entity_invitations_entity_idx
|
|
130
|
-
ON ${prefix}entity_invitations (entity_id)
|
|
131
|
-
`);
|
|
132
|
-
await client.unsafe(`
|
|
133
|
-
CREATE INDEX IF NOT EXISTS ${indexPrefix}_entity_invitations_email_idx
|
|
134
|
-
ON ${prefix}entity_invitations (email)
|
|
135
|
-
`);
|
|
136
|
-
await client.unsafe(`
|
|
137
|
-
CREATE INDEX IF NOT EXISTS ${indexPrefix}_entity_invitations_status_idx
|
|
138
|
-
ON ${prefix}entity_invitations (status)
|
|
139
|
-
`);
|
|
140
|
-
console.log('Entity tables created');
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Add entity_id column to projects table.
|
|
144
|
-
*/
|
|
145
|
-
async function addEntityIdToProjects(client, prefix, indexPrefix) {
|
|
146
|
-
console.log('Adding entity_id to projects table...');
|
|
147
|
-
// Check if column already exists
|
|
148
|
-
const columnExists = await client.unsafe(`
|
|
149
|
-
SELECT EXISTS (
|
|
150
|
-
SELECT 1 FROM information_schema.columns
|
|
151
|
-
WHERE table_schema = '${prefix.replace('.', '')}'
|
|
152
|
-
AND table_name = 'projects'
|
|
153
|
-
AND column_name = 'entity_id'
|
|
154
|
-
)
|
|
155
|
-
`);
|
|
156
|
-
if (!columnExists[0]?.exists) {
|
|
157
|
-
// Add nullable entity_id column
|
|
158
|
-
await client.unsafe(`
|
|
159
|
-
ALTER TABLE ${prefix}projects
|
|
160
|
-
ADD COLUMN entity_id UUID REFERENCES ${prefix}entities(id) ON DELETE CASCADE
|
|
161
|
-
`);
|
|
162
|
-
// Create index on entity_id
|
|
163
|
-
await client.unsafe(`
|
|
164
|
-
CREATE INDEX IF NOT EXISTS ${indexPrefix}_projects_entity_idx
|
|
165
|
-
ON ${prefix}projects (entity_id)
|
|
166
|
-
`);
|
|
167
|
-
console.log('entity_id column added to projects');
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
console.log('entity_id column already exists in projects');
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Create personal entities for existing users.
|
|
175
|
-
* Uses firebase_uid as the user identifier.
|
|
176
|
-
* Personal entities use role = 'admin' (not 'owner' - owner role is for organizations).
|
|
177
|
-
*/
|
|
178
|
-
async function migrateUsersToPersonalEntities(client, prefix) {
|
|
179
|
-
console.log('Migrating users to personal entities...');
|
|
180
|
-
// Check if user_settings table exists and has firebase_uid column
|
|
181
|
-
const schemaName = prefix.replace('.', '');
|
|
182
|
-
const settingsColumnCheck = await client.unsafe(`
|
|
183
|
-
SELECT column_name FROM information_schema.columns
|
|
184
|
-
WHERE table_schema = '${schemaName}'
|
|
185
|
-
AND table_name = 'user_settings'
|
|
186
|
-
AND column_name IN ('firebase_uid', 'user_id')
|
|
187
|
-
`);
|
|
188
|
-
const settingsHasFirebaseUid = settingsColumnCheck.some((c) => c.column_name === 'firebase_uid');
|
|
189
|
-
const settingsHasUserId = settingsColumnCheck.some((c) => c.column_name === 'user_id');
|
|
190
|
-
const settingsJoinColumn = settingsHasFirebaseUid ? 'firebase_uid' : (settingsHasUserId ? 'user_id' : null);
|
|
191
|
-
// Build query based on whether user_settings exists and which column it has
|
|
192
|
-
let usersQuery;
|
|
193
|
-
if (settingsJoinColumn) {
|
|
194
|
-
usersQuery = `
|
|
195
|
-
SELECT u.firebase_uid, u.email, u.display_name,
|
|
196
|
-
COALESCE(s.organization_path, SUBSTRING(MD5(RANDOM()::TEXT) FROM 1 FOR 8)) as slug_source
|
|
197
|
-
FROM ${prefix}users u
|
|
198
|
-
LEFT JOIN ${prefix}user_settings s ON u.firebase_uid = s.${settingsJoinColumn}
|
|
199
|
-
WHERE u.firebase_uid IS NOT NULL
|
|
200
|
-
AND NOT EXISTS (
|
|
201
|
-
SELECT 1 FROM ${prefix}entity_members em
|
|
202
|
-
INNER JOIN ${prefix}entities e ON em.entity_id = e.id
|
|
203
|
-
WHERE em.user_id = u.firebase_uid
|
|
204
|
-
AND em.role = 'admin'
|
|
205
|
-
AND em.is_active = true
|
|
206
|
-
AND e.entity_type = 'personal'
|
|
207
|
-
)
|
|
208
|
-
`;
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
// No user_settings table or no matching column - just use users table
|
|
212
|
-
usersQuery = `
|
|
213
|
-
SELECT u.firebase_uid, u.email, u.display_name,
|
|
214
|
-
SUBSTRING(MD5(RANDOM()::TEXT) FROM 1 FOR 8) as slug_source
|
|
215
|
-
FROM ${prefix}users u
|
|
216
|
-
WHERE u.firebase_uid IS NOT NULL
|
|
217
|
-
AND NOT EXISTS (
|
|
218
|
-
SELECT 1 FROM ${prefix}entity_members em
|
|
219
|
-
INNER JOIN ${prefix}entities e ON em.entity_id = e.id
|
|
220
|
-
WHERE em.user_id = u.firebase_uid
|
|
221
|
-
AND em.role = 'admin'
|
|
222
|
-
AND em.is_active = true
|
|
223
|
-
AND e.entity_type = 'personal'
|
|
224
|
-
)
|
|
225
|
-
`;
|
|
226
|
-
}
|
|
227
|
-
// Get users without personal entities (check via entity_members with role = 'admin')
|
|
228
|
-
const usersWithoutEntities = await client.unsafe(usersQuery);
|
|
229
|
-
let migratedCount = 0;
|
|
230
|
-
for (const user of usersWithoutEntities) {
|
|
231
|
-
// Generate a unique slug (8 chars, lowercase alphanumeric)
|
|
232
|
-
const slug = generateSlug(user.slug_source);
|
|
233
|
-
const displayName = user.display_name || user.email?.split('@')[0] || 'Personal';
|
|
234
|
-
const firebaseUid = user.firebase_uid;
|
|
235
|
-
try {
|
|
236
|
-
// Create personal entity
|
|
237
|
-
const [entity] = await client.unsafe(`
|
|
238
|
-
INSERT INTO ${prefix}entities (entity_slug, entity_type, display_name)
|
|
239
|
-
VALUES ('${slug}', 'personal', '${displayName.replace(/'/g, "''")}')
|
|
240
|
-
RETURNING id
|
|
241
|
-
`);
|
|
242
|
-
// Add user as admin (personal entities use 'admin' role, not 'owner')
|
|
243
|
-
await client.unsafe(`
|
|
244
|
-
INSERT INTO ${prefix}entity_members (entity_id, user_id, role, is_active)
|
|
245
|
-
VALUES ('${entity.id}', '${firebaseUid}', 'admin', true)
|
|
246
|
-
`);
|
|
247
|
-
migratedCount++;
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
// If slug collision, generate a new one and retry
|
|
251
|
-
if (error.code === '23505') {
|
|
252
|
-
const newSlug = generateSlug();
|
|
253
|
-
const [entity] = await client.unsafe(`
|
|
254
|
-
INSERT INTO ${prefix}entities (entity_slug, entity_type, display_name)
|
|
255
|
-
VALUES ('${newSlug}', 'personal', '${displayName.replace(/'/g, "''")}')
|
|
256
|
-
RETURNING id
|
|
257
|
-
`);
|
|
258
|
-
await client.unsafe(`
|
|
259
|
-
INSERT INTO ${prefix}entity_members (entity_id, user_id, role, is_active)
|
|
260
|
-
VALUES ('${entity.id}', '${firebaseUid}', 'admin', true)
|
|
261
|
-
`);
|
|
262
|
-
migratedCount++;
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
throw error;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
console.log(`Migrated ${migratedCount} users to personal entities`);
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Populate entity_id for existing projects.
|
|
273
|
-
* Uses entity_members with role = 'admin' to find personal entity membership.
|
|
274
|
-
*/
|
|
275
|
-
async function populateProjectEntityIds(client, prefix) {
|
|
276
|
-
console.log('Populating entity_id for existing projects...');
|
|
277
|
-
// Update projects to use user's personal entity (via entity_members with admin role)
|
|
278
|
-
const result = await client.unsafe(`
|
|
279
|
-
UPDATE ${prefix}projects p
|
|
280
|
-
SET entity_id = e.id
|
|
281
|
-
FROM ${prefix}entities e
|
|
282
|
-
INNER JOIN ${prefix}entity_members em ON em.entity_id = e.id
|
|
283
|
-
WHERE p.user_id = em.user_id
|
|
284
|
-
AND em.role = 'admin'
|
|
285
|
-
AND em.is_active = true
|
|
286
|
-
AND e.entity_type = 'personal'
|
|
287
|
-
AND p.entity_id IS NULL
|
|
288
|
-
`);
|
|
289
|
-
console.log(`Updated entity_id for ${result.count || 0} projects`);
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Generate a slug from a source string or random.
|
|
293
|
-
*/
|
|
294
|
-
function generateSlug(source) {
|
|
295
|
-
if (source) {
|
|
296
|
-
// Normalize: lowercase, remove non-alphanumeric, take first 8 chars
|
|
297
|
-
return source
|
|
298
|
-
.toLowerCase()
|
|
299
|
-
.replace(/[^a-z0-9]/g, '')
|
|
300
|
-
.substring(0, 8)
|
|
301
|
-
.padEnd(8, '0');
|
|
302
|
-
}
|
|
303
|
-
// Generate random slug
|
|
304
|
-
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
305
|
-
let slug = '';
|
|
306
|
-
for (let i = 0; i < 8; i++) {
|
|
307
|
-
slug += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
308
|
-
}
|
|
309
|
-
return slug;
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Rollback the entity migration.
|
|
313
|
-
*/
|
|
314
|
-
async function rollbackEntityMigration(config) {
|
|
315
|
-
const { client, schemaName, migrateProjects = true } = config;
|
|
316
|
-
const prefix = `${schemaName}.`;
|
|
317
|
-
console.log(`Rolling back entity migration for schema: ${schemaName}`);
|
|
318
|
-
// Remove entity_id from projects first
|
|
319
|
-
if (migrateProjects) {
|
|
320
|
-
await client.unsafe(`
|
|
321
|
-
ALTER TABLE ${prefix}projects DROP COLUMN IF EXISTS entity_id
|
|
322
|
-
`);
|
|
323
|
-
}
|
|
324
|
-
// Drop tables in reverse order
|
|
325
|
-
await client.unsafe(`DROP TABLE IF EXISTS ${prefix}entity_invitations`);
|
|
326
|
-
await client.unsafe(`DROP TABLE IF EXISTS ${prefix}entity_members`);
|
|
327
|
-
await client.unsafe(`DROP TABLE IF EXISTS ${prefix}entities`);
|
|
328
|
-
console.log('Entity migration rolled back');
|
|
329
|
-
}
|
|
330
|
-
//# sourceMappingURL=001_add_entities.js.map
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview Migration Exports
|
|
4
|
-
*/
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.rollbackEntityMigration = exports.runEntityMigration = void 0;
|
|
7
|
-
var _001_add_entities_1 = require("./001_add_entities");
|
|
8
|
-
Object.defineProperty(exports, "runEntityMigration", { enumerable: true, get: function () { return _001_add_entities_1.runEntityMigration; } });
|
|
9
|
-
Object.defineProperty(exports, "rollbackEntityMigration", { enumerable: true, get: function () { return _001_add_entities_1.rollbackEntityMigration; } });
|
|
10
|
-
//# sourceMappingURL=index.js.map
|