eggi-ai-db-schema-2 0.1.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/CHANGELOG.md +750 -0
- package/README.md +660 -0
- package/dist/config/database.d.ts +28 -0
- package/dist/config/database.d.ts.map +1 -0
- package/dist/config/database.js +72 -0
- package/dist/config/database.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +199 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/database-service.d.ts +714 -0
- package/dist/lib/database-service.d.ts.map +1 -0
- package/dist/lib/database-service.js +1394 -0
- package/dist/lib/database-service.js.map +1 -0
- package/dist/lib/db-types.d.ts +167 -0
- package/dist/lib/db-types.d.ts.map +1 -0
- package/dist/lib/db-types.js +28 -0
- package/dist/lib/db-types.js.map +1 -0
- package/dist/lib/db.d.ts +58 -0
- package/dist/lib/db.d.ts.map +1 -0
- package/dist/lib/db.js +292 -0
- package/dist/lib/db.js.map +1 -0
- package/dist/lib/index.d.ts +11 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +26 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/pg-client.d.ts +50 -0
- package/dist/lib/pg-client.d.ts.map +1 -0
- package/dist/lib/pg-client.js +106 -0
- package/dist/lib/pg-client.js.map +1 -0
- package/dist/lib/schema.d.ts +298 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +12 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/migration-manager.d.ts +49 -0
- package/dist/migration-manager.d.ts.map +1 -0
- package/dist/migration-manager.js +282 -0
- package/dist/migration-manager.js.map +1 -0
- package/dist/queries/minimal-connections.d.ts +31 -0
- package/dist/queries/minimal-connections.d.ts.map +1 -0
- package/dist/queries/minimal-connections.js +143 -0
- package/dist/queries/minimal-connections.js.map +1 -0
- package/dist/schema.ts +340 -0
- package/dist/seed.d.ts +8 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +40 -0
- package/dist/seed.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +23 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/types.d.ts +77 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/types.js +3 -0
- package/dist/types/types.js.map +1 -0
- package/dist/utils/authenticated-user-operations.d.ts +110 -0
- package/dist/utils/authenticated-user-operations.d.ts.map +1 -0
- package/dist/utils/authenticated-user-operations.js +292 -0
- package/dist/utils/authenticated-user-operations.js.map +1 -0
- package/dist/utils/authentication-operations.d.ts +48 -0
- package/dist/utils/authentication-operations.d.ts.map +1 -0
- package/dist/utils/authentication-operations.js +172 -0
- package/dist/utils/authentication-operations.js.map +1 -0
- package/dist/utils/company-mapping-job-operations.d.ts +103 -0
- package/dist/utils/company-mapping-job-operations.d.ts.map +1 -0
- package/dist/utils/company-mapping-job-operations.js +413 -0
- package/dist/utils/company-mapping-job-operations.js.map +1 -0
- package/dist/utils/company-sheet-upload-operations.d.ts +53 -0
- package/dist/utils/company-sheet-upload-operations.d.ts.map +1 -0
- package/dist/utils/company-sheet-upload-operations.js +135 -0
- package/dist/utils/company-sheet-upload-operations.js.map +1 -0
- package/dist/utils/contact-operations.d.ts +70 -0
- package/dist/utils/contact-operations.d.ts.map +1 -0
- package/dist/utils/contact-operations.js +294 -0
- package/dist/utils/contact-operations.js.map +1 -0
- package/dist/utils/forager-linkedin-operations.d.ts +74 -0
- package/dist/utils/forager-linkedin-operations.d.ts.map +1 -0
- package/dist/utils/forager-linkedin-operations.js +778 -0
- package/dist/utils/forager-linkedin-operations.js.map +1 -0
- package/dist/utils/ghost-genius-linkedin-operations.d.ts +23 -0
- package/dist/utils/ghost-genius-linkedin-operations.d.ts.map +1 -0
- package/dist/utils/ghost-genius-linkedin-operations.js +282 -0
- package/dist/utils/ghost-genius-linkedin-operations.js.map +1 -0
- package/dist/utils/index.d.ts +29 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +77 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/introduction-request-operations.d.ts +160 -0
- package/dist/utils/introduction-request-operations.d.ts.map +1 -0
- package/dist/utils/introduction-request-operations.js +492 -0
- package/dist/utils/introduction-request-operations.js.map +1 -0
- package/dist/utils/invitation-operations.d.ts +141 -0
- package/dist/utils/invitation-operations.d.ts.map +1 -0
- package/dist/utils/invitation-operations.js +749 -0
- package/dist/utils/invitation-operations.js.map +1 -0
- package/dist/utils/linkedin-account-operations.d.ts +45 -0
- package/dist/utils/linkedin-account-operations.d.ts.map +1 -0
- package/dist/utils/linkedin-account-operations.js +279 -0
- package/dist/utils/linkedin-account-operations.js.map +1 -0
- package/dist/utils/linkedin-account-relationship-operations.d.ts +77 -0
- package/dist/utils/linkedin-account-relationship-operations.d.ts.map +1 -0
- package/dist/utils/linkedin-account-relationship-operations.js +274 -0
- package/dist/utils/linkedin-account-relationship-operations.js.map +1 -0
- package/dist/utils/linkedin-data-operations.d.ts +102 -0
- package/dist/utils/linkedin-data-operations.d.ts.map +1 -0
- package/dist/utils/linkedin-data-operations.js +613 -0
- package/dist/utils/linkedin-data-operations.js.map +1 -0
- package/dist/utils/linkedin-identifier-utils.d.ts +31 -0
- package/dist/utils/linkedin-identifier-utils.d.ts.map +1 -0
- package/dist/utils/linkedin-identifier-utils.js +63 -0
- package/dist/utils/linkedin-identifier-utils.js.map +1 -0
- package/dist/utils/linkedin-profile-cache.d.ts +131 -0
- package/dist/utils/linkedin-profile-cache.d.ts.map +1 -0
- package/dist/utils/linkedin-profile-cache.js +418 -0
- package/dist/utils/linkedin-profile-cache.js.map +1 -0
- package/dist/utils/llm-inference-job-operations.d.ts +116 -0
- package/dist/utils/llm-inference-job-operations.d.ts.map +1 -0
- package/dist/utils/llm-inference-job-operations.js +267 -0
- package/dist/utils/llm-inference-job-operations.js.map +1 -0
- package/dist/utils/mapping-job-operations.d.ts +272 -0
- package/dist/utils/mapping-job-operations.d.ts.map +1 -0
- package/dist/utils/mapping-job-operations.js +833 -0
- package/dist/utils/mapping-job-operations.js.map +1 -0
- package/dist/utils/mapping-operations.d.ts +80 -0
- package/dist/utils/mapping-operations.d.ts.map +1 -0
- package/dist/utils/mapping-operations.js +318 -0
- package/dist/utils/mapping-operations.js.map +1 -0
- package/dist/utils/on-demand-mapping-operations.d.ts +199 -0
- package/dist/utils/on-demand-mapping-operations.d.ts.map +1 -0
- package/dist/utils/on-demand-mapping-operations.js +728 -0
- package/dist/utils/on-demand-mapping-operations.js.map +1 -0
- package/dist/utils/onboarding-operations.d.ts +53 -0
- package/dist/utils/onboarding-operations.d.ts.map +1 -0
- package/dist/utils/onboarding-operations.js +223 -0
- package/dist/utils/onboarding-operations.js.map +1 -0
- package/dist/utils/organization-assignment-job-operations.d.ts +258 -0
- package/dist/utils/organization-assignment-job-operations.d.ts.map +1 -0
- package/dist/utils/organization-assignment-job-operations.js +881 -0
- package/dist/utils/organization-assignment-job-operations.js.map +1 -0
- package/dist/utils/organization-assignment-operations.d.ts +59 -0
- package/dist/utils/organization-assignment-operations.d.ts.map +1 -0
- package/dist/utils/organization-assignment-operations.js +130 -0
- package/dist/utils/organization-assignment-operations.js.map +1 -0
- package/dist/utils/organization-operations.d.ts +284 -0
- package/dist/utils/organization-operations.d.ts.map +1 -0
- package/dist/utils/organization-operations.js +1030 -0
- package/dist/utils/organization-operations.js.map +1 -0
- package/dist/utils/organization-relationship-operations.d.ts +79 -0
- package/dist/utils/organization-relationship-operations.d.ts.map +1 -0
- package/dist/utils/organization-relationship-operations.js +294 -0
- package/dist/utils/organization-relationship-operations.js.map +1 -0
- package/dist/utils/quota-operations.d.ts +107 -0
- package/dist/utils/quota-operations.d.ts.map +1 -0
- package/dist/utils/quota-operations.js +692 -0
- package/dist/utils/quota-operations.js.map +1 -0
- package/dist/utils/recursive-mapping-job-operations.d.ts +42 -0
- package/dist/utils/recursive-mapping-job-operations.d.ts.map +1 -0
- package/dist/utils/recursive-mapping-job-operations.js +169 -0
- package/dist/utils/recursive-mapping-job-operations.js.map +1 -0
- package/dist/utils/relationship-operations.d.ts +130 -0
- package/dist/utils/relationship-operations.d.ts.map +1 -0
- package/dist/utils/relationship-operations.js +329 -0
- package/dist/utils/relationship-operations.js.map +1 -0
- package/dist/utils/sales-pipeline-operations.d.ts +163 -0
- package/dist/utils/sales-pipeline-operations.d.ts.map +1 -0
- package/dist/utils/sales-pipeline-operations.js +725 -0
- package/dist/utils/sales-pipeline-operations.js.map +1 -0
- package/dist/utils/skills-operations.d.ts +117 -0
- package/dist/utils/skills-operations.d.ts.map +1 -0
- package/dist/utils/skills-operations.js +487 -0
- package/dist/utils/skills-operations.js.map +1 -0
- package/dist/utils/subscription-operations.d.ts +123 -0
- package/dist/utils/subscription-operations.d.ts.map +1 -0
- package/dist/utils/subscription-operations.js +391 -0
- package/dist/utils/subscription-operations.js.map +1 -0
- package/dist/utils/unipile-account-operations.d.ts +96 -0
- package/dist/utils/unipile-account-operations.d.ts.map +1 -0
- package/dist/utils/unipile-account-operations.js +255 -0
- package/dist/utils/unipile-account-operations.js.map +1 -0
- package/dist/utils/user-industry-operations.d.ts +80 -0
- package/dist/utils/user-industry-operations.d.ts.map +1 -0
- package/dist/utils/user-industry-operations.js +237 -0
- package/dist/utils/user-industry-operations.js.map +1 -0
- package/dist/utils/user-operations.d.ts +87 -0
- package/dist/utils/user-operations.d.ts.map +1 -0
- package/dist/utils/user-operations.js +212 -0
- package/dist/utils/user-operations.js.map +1 -0
- package/package.json +98 -0
|
@@ -0,0 +1,1030 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Organization Operations
|
|
4
|
+
*
|
|
5
|
+
* Database operations for organizations and organization memberships.
|
|
6
|
+
* Provides functions for querying organization data and user memberships.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getOrganizationMembershipsForUser = getOrganizationMembershipsForUser;
|
|
10
|
+
exports.getOrganizationById = getOrganizationById;
|
|
11
|
+
exports.findOrganizationByLinkedInCompanyId = findOrganizationByLinkedInCompanyId;
|
|
12
|
+
exports.findOrganizationByLinkedInPublicIdentifier = findOrganizationByLinkedInPublicIdentifier;
|
|
13
|
+
exports.findOrganizationByDedupKey = findOrganizationByDedupKey;
|
|
14
|
+
exports.getOrganizationMembers = getOrganizationMembers;
|
|
15
|
+
exports.getOrganizationMembersCount = getOrganizationMembersCount;
|
|
16
|
+
exports.getOrganizationContributorsCount = getOrganizationContributorsCount;
|
|
17
|
+
exports.getOrganizationContributors = getOrganizationContributors;
|
|
18
|
+
exports.getOrganizationMembershipForUser = getOrganizationMembershipForUser;
|
|
19
|
+
exports.getOrganizationContributorByUserId = getOrganizationContributorByUserId;
|
|
20
|
+
exports.findContributorPage = findContributorPage;
|
|
21
|
+
exports.updateOrganizationMemberRole = updateOrganizationMemberRole;
|
|
22
|
+
exports.getOrganizationMemberRelationships = getOrganizationMemberRelationships;
|
|
23
|
+
exports.getOrganizationMemberRelationshipsCount = getOrganizationMemberRelationshipsCount;
|
|
24
|
+
exports.createOrganization = createOrganization;
|
|
25
|
+
exports.updateOrganizationLinkedInPublicIdentifier = updateOrganizationLinkedInPublicIdentifier;
|
|
26
|
+
exports.findOrCreateOrganizationMember = findOrCreateOrganizationMember;
|
|
27
|
+
exports.findPersonalWorkspaceForUser = findPersonalWorkspaceForUser;
|
|
28
|
+
exports.getOrganizationMemberById = getOrganizationMemberById;
|
|
29
|
+
exports.updateOrganizationSubscriptionTier = updateOrganizationSubscriptionTier;
|
|
30
|
+
const pg_client_1 = require("../lib/pg-client");
|
|
31
|
+
const db_1 = require("../lib/db");
|
|
32
|
+
/**
|
|
33
|
+
* Get all organization memberships for a user with full organization details
|
|
34
|
+
*
|
|
35
|
+
* @param db - Database instance
|
|
36
|
+
* @param userId - User ID to get memberships for
|
|
37
|
+
* @returns Array of organization memberships with organization details
|
|
38
|
+
*/
|
|
39
|
+
async function getOrganizationMembershipsForUser(db, userId) {
|
|
40
|
+
const sql = `
|
|
41
|
+
SELECT
|
|
42
|
+
om.id,
|
|
43
|
+
om.user_id,
|
|
44
|
+
om.organization_id,
|
|
45
|
+
om.contribution_role,
|
|
46
|
+
om.permissions_role,
|
|
47
|
+
om.created_at,
|
|
48
|
+
o.id as org_id,
|
|
49
|
+
o.name as org_name,
|
|
50
|
+
o.workspace_type as org_workspace_type,
|
|
51
|
+
o.subscription_tier as org_subscription_tier,
|
|
52
|
+
o.image_url as org_image_url,
|
|
53
|
+
o.created_at as org_created_at,
|
|
54
|
+
o.created_by_user_id as org_created_by_user_id
|
|
55
|
+
FROM public.organization_members om
|
|
56
|
+
INNER JOIN public.organizations o ON om.organization_id = o.id
|
|
57
|
+
WHERE om.user_id = $1
|
|
58
|
+
ORDER BY om.created_at
|
|
59
|
+
`;
|
|
60
|
+
const results = await (0, pg_client_1.query)(db, sql, [userId]);
|
|
61
|
+
return results.map(r => ({
|
|
62
|
+
id: r.id,
|
|
63
|
+
userId: r.user_id,
|
|
64
|
+
organizationId: r.organization_id,
|
|
65
|
+
contributionRole: r.contribution_role,
|
|
66
|
+
permissionsRole: r.permissions_role,
|
|
67
|
+
createdAt: r.created_at,
|
|
68
|
+
organization: {
|
|
69
|
+
id: r.org_id,
|
|
70
|
+
name: r.org_name,
|
|
71
|
+
workspaceType: r.org_workspace_type,
|
|
72
|
+
subscriptionTier: r.org_subscription_tier,
|
|
73
|
+
imageUrl: r.org_image_url,
|
|
74
|
+
createdAt: r.org_created_at,
|
|
75
|
+
createdByUserId: r.org_created_by_user_id,
|
|
76
|
+
},
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get organization by ID
|
|
81
|
+
*
|
|
82
|
+
* @param db - Database instance
|
|
83
|
+
* @param organizationId - Organization ID
|
|
84
|
+
* @returns Organization or null if not found
|
|
85
|
+
*/
|
|
86
|
+
async function getOrganizationById(db, organizationId) {
|
|
87
|
+
const sql = `
|
|
88
|
+
SELECT id, name, workspace_type, subscription_tier, image_url, created_at, created_by_user_id
|
|
89
|
+
FROM public.organizations
|
|
90
|
+
WHERE id = $1
|
|
91
|
+
LIMIT 1
|
|
92
|
+
`;
|
|
93
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, [organizationId]);
|
|
94
|
+
if (!result)
|
|
95
|
+
return null;
|
|
96
|
+
return {
|
|
97
|
+
id: result.id,
|
|
98
|
+
name: result.name,
|
|
99
|
+
workspaceType: result.workspace_type,
|
|
100
|
+
subscriptionTier: result.subscription_tier,
|
|
101
|
+
imageUrl: result.image_url,
|
|
102
|
+
createdAt: result.created_at,
|
|
103
|
+
createdByUserId: result.created_by_user_id,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Find organization by LinkedIn company ID (Forager organization ID)
|
|
108
|
+
*
|
|
109
|
+
* @param db - Database instance
|
|
110
|
+
* @param linkedinCompanyId - Forager organization ID
|
|
111
|
+
* @returns Organization or null if not found
|
|
112
|
+
*/
|
|
113
|
+
async function findOrganizationByLinkedInCompanyId(db, linkedinCompanyId) {
|
|
114
|
+
const sql = `
|
|
115
|
+
SELECT id, name, workspace_type, subscription_tier, image_url, created_at, created_by_user_id
|
|
116
|
+
FROM public.organizations
|
|
117
|
+
WHERE linkedin_company_id = $1
|
|
118
|
+
LIMIT 1
|
|
119
|
+
`;
|
|
120
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, [linkedinCompanyId]);
|
|
121
|
+
if (!result)
|
|
122
|
+
return null;
|
|
123
|
+
return {
|
|
124
|
+
id: result.id,
|
|
125
|
+
name: result.name,
|
|
126
|
+
workspaceType: result.workspace_type,
|
|
127
|
+
subscriptionTier: result.subscription_tier,
|
|
128
|
+
imageUrl: result.image_url,
|
|
129
|
+
createdAt: result.created_at,
|
|
130
|
+
createdByUserId: result.created_by_user_id,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Find organization by LinkedIn public identifier
|
|
135
|
+
*
|
|
136
|
+
* @param db - Database instance
|
|
137
|
+
* @param linkedinPublicIdentifier - LinkedIn public identifier (e.g., "eggi")
|
|
138
|
+
* @returns Organization or null if not found
|
|
139
|
+
*/
|
|
140
|
+
async function findOrganizationByLinkedInPublicIdentifier(db, linkedinPublicIdentifier) {
|
|
141
|
+
(0, db_1.debugLogDbOperation)("select", "organizations", { linkedinPublicIdentifier });
|
|
142
|
+
const sql = `
|
|
143
|
+
SELECT
|
|
144
|
+
id,
|
|
145
|
+
name,
|
|
146
|
+
workspace_type,
|
|
147
|
+
subscription_tier,
|
|
148
|
+
image_url,
|
|
149
|
+
created_at,
|
|
150
|
+
created_by_user_id,
|
|
151
|
+
linkedin_public_identifier,
|
|
152
|
+
linkedin_company_id
|
|
153
|
+
FROM public.organizations
|
|
154
|
+
WHERE linkedin_public_identifier = $1
|
|
155
|
+
LIMIT 1
|
|
156
|
+
`;
|
|
157
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, [linkedinPublicIdentifier]);
|
|
158
|
+
if (!result)
|
|
159
|
+
return null;
|
|
160
|
+
return {
|
|
161
|
+
id: result.id,
|
|
162
|
+
name: result.name,
|
|
163
|
+
workspaceType: result.workspace_type,
|
|
164
|
+
subscriptionTier: result.subscription_tier,
|
|
165
|
+
imageUrl: result.image_url,
|
|
166
|
+
createdAt: result.created_at,
|
|
167
|
+
createdByUserId: result.created_by_user_id,
|
|
168
|
+
linkedinPublicIdentifier: result.linkedin_public_identifier,
|
|
169
|
+
linkedinCompanyId: result.linkedin_company_id,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Find organization by dedup_key (for manual organizations)
|
|
174
|
+
*
|
|
175
|
+
* @param db - Database instance
|
|
176
|
+
* @param dedupKey - Deduplication key
|
|
177
|
+
* @returns Organization or null if not found
|
|
178
|
+
*/
|
|
179
|
+
async function findOrganizationByDedupKey(db, dedupKey) {
|
|
180
|
+
const sql = `
|
|
181
|
+
SELECT id, name, workspace_type, subscription_tier, image_url, created_at, created_by_user_id
|
|
182
|
+
FROM public.organizations
|
|
183
|
+
WHERE dedup_key = $1
|
|
184
|
+
LIMIT 1
|
|
185
|
+
`;
|
|
186
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, [dedupKey]);
|
|
187
|
+
if (!result)
|
|
188
|
+
return null;
|
|
189
|
+
return {
|
|
190
|
+
id: result.id,
|
|
191
|
+
name: result.name,
|
|
192
|
+
workspaceType: result.workspace_type,
|
|
193
|
+
subscriptionTier: result.subscription_tier,
|
|
194
|
+
imageUrl: result.image_url,
|
|
195
|
+
createdAt: result.created_at,
|
|
196
|
+
createdByUserId: result.created_by_user_id,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Get all members of an organization
|
|
201
|
+
*
|
|
202
|
+
* @param db - Database instance
|
|
203
|
+
* @param organizationId - Organization ID
|
|
204
|
+
* @returns Array of organization memberships
|
|
205
|
+
*/
|
|
206
|
+
async function getOrganizationMembers(db, organizationId) {
|
|
207
|
+
const sql = `
|
|
208
|
+
SELECT id, user_id, organization_id, contribution_role, permissions_role, created_at
|
|
209
|
+
FROM public.organization_members
|
|
210
|
+
WHERE organization_id = $1
|
|
211
|
+
ORDER BY created_at
|
|
212
|
+
`;
|
|
213
|
+
const results = await (0, pg_client_1.query)(db, sql, [organizationId]);
|
|
214
|
+
return results.map(r => ({
|
|
215
|
+
id: r.id,
|
|
216
|
+
userId: r.user_id,
|
|
217
|
+
organizationId: r.organization_id,
|
|
218
|
+
contributionRole: r.contribution_role,
|
|
219
|
+
permissionsRole: r.permissions_role,
|
|
220
|
+
createdAt: r.created_at,
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Get total count of organization members by contribution role
|
|
225
|
+
*
|
|
226
|
+
* @param db - Database instance
|
|
227
|
+
* @param organizationId - Organization ID
|
|
228
|
+
* @param contributionRole - Contribution role to filter by (optional)
|
|
229
|
+
* @returns Total count of members
|
|
230
|
+
*/
|
|
231
|
+
async function getOrganizationMembersCount(db, organizationId, contributionRole) {
|
|
232
|
+
const sql = contributionRole
|
|
233
|
+
? `
|
|
234
|
+
SELECT COUNT(*) as count
|
|
235
|
+
FROM public.organization_members
|
|
236
|
+
WHERE organization_id = $1 AND contribution_role = $2
|
|
237
|
+
`
|
|
238
|
+
: `
|
|
239
|
+
SELECT COUNT(*) as count
|
|
240
|
+
FROM public.organization_members
|
|
241
|
+
WHERE organization_id = $1
|
|
242
|
+
`;
|
|
243
|
+
const params = contributionRole ? [organizationId, contributionRole] : [organizationId];
|
|
244
|
+
const results = await (0, pg_client_1.query)(db, sql, params);
|
|
245
|
+
return parseInt(results[0]?.count || "0", 10);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get total count of contributors for an organization
|
|
249
|
+
*
|
|
250
|
+
* CRITICAL: This must use the same filtering logic as getOrganizationContributors
|
|
251
|
+
* to ensure accurate pagination. Only counts contributors with relationships_count > 0.
|
|
252
|
+
*
|
|
253
|
+
* @param db - Database instance
|
|
254
|
+
* @param organizationId - Organization ID
|
|
255
|
+
* @returns Total count of contributors with relationships
|
|
256
|
+
*/
|
|
257
|
+
async function getOrganizationContributorsCount(db, organizationId) {
|
|
258
|
+
const sql = `
|
|
259
|
+
WITH relationship_counts AS (
|
|
260
|
+
-- Count distinct relationships for each contributor using organization_member_id
|
|
261
|
+
-- This ensures we only count relationships assigned to the specific contributor,
|
|
262
|
+
-- not relationships where they appear as the "other person" in someone else's mapping job
|
|
263
|
+
SELECT
|
|
264
|
+
om.user_id,
|
|
265
|
+
COUNT(DISTINCT
|
|
266
|
+
CASE
|
|
267
|
+
WHEN r.linkedin_account_id_a = la.id THEN r.linkedin_account_id_b
|
|
268
|
+
ELSE r.linkedin_account_id_a
|
|
269
|
+
END
|
|
270
|
+
) as relationships_count
|
|
271
|
+
FROM public.organization_members om
|
|
272
|
+
INNER JOIN linkedin.accounts la ON la.user_id = om.user_id
|
|
273
|
+
INNER JOIN public.organization_user_relationships_assignments oura
|
|
274
|
+
ON oura.organization_member_id = om.id -- Use organization_member_id for faster lookup
|
|
275
|
+
INNER JOIN linkedin.relationships r
|
|
276
|
+
ON r.id = oura.linkedin_relationship_id
|
|
277
|
+
AND (r.linkedin_account_id_a = la.id OR r.linkedin_account_id_b = la.id)
|
|
278
|
+
INNER JOIN monitoring.mapping_jobs mj
|
|
279
|
+
ON mj.id = r.mapping_job_id
|
|
280
|
+
AND mj.linkedin_account_id = la.id -- CRITICAL: Only count relationships from mapping jobs that belong to this contributor
|
|
281
|
+
WHERE oura.organization_id = $1
|
|
282
|
+
AND om.organization_id = $1
|
|
283
|
+
AND om.contribution_role = 'CONTRIBUTOR'
|
|
284
|
+
GROUP BY om.user_id
|
|
285
|
+
)
|
|
286
|
+
SELECT COUNT(*) as count
|
|
287
|
+
FROM relationship_counts rc
|
|
288
|
+
WHERE rc.relationships_count > 0
|
|
289
|
+
`;
|
|
290
|
+
const results = await (0, pg_client_1.query)(db, sql, [organizationId]);
|
|
291
|
+
return parseInt(results[0]?.count || "0", 10);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Get contributors for an organization with pagination
|
|
295
|
+
* Sorted by:
|
|
296
|
+
* 1. LLM inference job status (descending) - completed jobs first, then pending/in-progress
|
|
297
|
+
* 2. Relationship count (descending) - most relationships first within each status group
|
|
298
|
+
* 3. Created date (ascending) - oldest first when status and relationships are equal
|
|
299
|
+
*
|
|
300
|
+
* @param db - Database instance
|
|
301
|
+
* @param organizationId - Organization ID
|
|
302
|
+
* @param limit - Maximum number of contributors to return (default: 50)
|
|
303
|
+
* @param offset - Number of contributors to skip (default: 0)
|
|
304
|
+
* @returns Array of contributor memberships
|
|
305
|
+
*/
|
|
306
|
+
async function getOrganizationContributors(db, organizationId, limit = 50, offset = 0) {
|
|
307
|
+
const sql = `
|
|
308
|
+
WITH relationship_counts AS (
|
|
309
|
+
-- Count distinct relationships for each contributor using organization_member_id
|
|
310
|
+
-- This ensures we only count relationships assigned to the specific contributor,
|
|
311
|
+
-- not relationships where they appear as the "other person" in someone else's mapping job
|
|
312
|
+
SELECT
|
|
313
|
+
om.user_id,
|
|
314
|
+
COUNT(DISTINCT
|
|
315
|
+
CASE
|
|
316
|
+
WHEN r.linkedin_account_id_a = la.id THEN r.linkedin_account_id_b
|
|
317
|
+
ELSE r.linkedin_account_id_a
|
|
318
|
+
END
|
|
319
|
+
) as relationships_count
|
|
320
|
+
FROM public.organization_members om
|
|
321
|
+
INNER JOIN linkedin.accounts la ON la.user_id = om.user_id
|
|
322
|
+
INNER JOIN public.organization_user_relationships_assignments oura
|
|
323
|
+
ON oura.organization_member_id = om.id -- Use organization_member_id for faster lookup
|
|
324
|
+
INNER JOIN linkedin.relationships r
|
|
325
|
+
ON r.id = oura.linkedin_relationship_id
|
|
326
|
+
AND (r.linkedin_account_id_a = la.id OR r.linkedin_account_id_b = la.id)
|
|
327
|
+
INNER JOIN monitoring.mapping_jobs mj
|
|
328
|
+
ON mj.id = r.mapping_job_id
|
|
329
|
+
AND mj.linkedin_account_id = la.id -- CRITICAL: Only count relationships from mapping jobs that belong to this contributor
|
|
330
|
+
WHERE oura.organization_id = $1
|
|
331
|
+
AND om.organization_id = $1
|
|
332
|
+
AND om.contribution_role = 'CONTRIBUTOR'
|
|
333
|
+
GROUP BY om.user_id
|
|
334
|
+
),
|
|
335
|
+
llm_job_status AS (
|
|
336
|
+
-- Get LLM inference job completion status for each contributor
|
|
337
|
+
-- Only includes users who have organization_assignment_jobs
|
|
338
|
+
-- Users without assignment jobs will have NULL (treated as false)
|
|
339
|
+
SELECT DISTINCT ON (om.user_id)
|
|
340
|
+
om.user_id,
|
|
341
|
+
CASE
|
|
342
|
+
WHEN lij.completed_at IS NOT NULL THEN true
|
|
343
|
+
ELSE false
|
|
344
|
+
END as llm_job_completed
|
|
345
|
+
FROM public.organization_members om
|
|
346
|
+
INNER JOIN monitoring.organization_assignment_jobs oaj
|
|
347
|
+
ON oaj.organization_id = om.organization_id
|
|
348
|
+
INNER JOIN monitoring.mapping_jobs mj
|
|
349
|
+
ON mj.id = oaj.mapping_job_id
|
|
350
|
+
INNER JOIN linkedin.accounts la
|
|
351
|
+
ON la.id = mj.linkedin_account_id
|
|
352
|
+
LEFT JOIN monitoring.llm_inference_jobs lij
|
|
353
|
+
ON lij.mapping_job_id = oaj.mapping_job_id
|
|
354
|
+
WHERE om.organization_id = $1
|
|
355
|
+
AND om.contribution_role = 'CONTRIBUTOR'
|
|
356
|
+
AND la.user_id = om.user_id
|
|
357
|
+
ORDER BY om.user_id, oaj.created_at DESC, lij.created_at DESC NULLS LAST
|
|
358
|
+
)
|
|
359
|
+
SELECT
|
|
360
|
+
om.id,
|
|
361
|
+
om.user_id,
|
|
362
|
+
om.organization_id,
|
|
363
|
+
om.contribution_role,
|
|
364
|
+
om.permissions_role,
|
|
365
|
+
om.created_at,
|
|
366
|
+
COALESCE(rc.relationships_count, 0) as relationships_count,
|
|
367
|
+
COALESCE(ljs.llm_job_completed, false) as llm_job_completed
|
|
368
|
+
FROM public.organization_members om
|
|
369
|
+
INNER JOIN relationship_counts rc ON rc.user_id = om.user_id
|
|
370
|
+
LEFT JOIN llm_job_status ljs ON ljs.user_id = om.user_id
|
|
371
|
+
WHERE om.organization_id = $1
|
|
372
|
+
AND om.contribution_role = 'CONTRIBUTOR'
|
|
373
|
+
AND rc.relationships_count > 0
|
|
374
|
+
ORDER BY
|
|
375
|
+
-- Primary sort: LLM job completed status (descending) - completed jobs first, then pending/in-progress
|
|
376
|
+
-- true (completed) comes before false (pending/not started)
|
|
377
|
+
COALESCE(ljs.llm_job_completed, false) DESC,
|
|
378
|
+
-- Secondary sort: relationship count (descending) - most relationships first within each status group
|
|
379
|
+
COALESCE(rc.relationships_count, 0) DESC,
|
|
380
|
+
-- Tertiary sort: created_at (ascending) - oldest first when status and relationship count are equal
|
|
381
|
+
om.created_at ASC
|
|
382
|
+
LIMIT $2 OFFSET $3
|
|
383
|
+
`;
|
|
384
|
+
const results = await (0, pg_client_1.query)(db, sql, [organizationId, limit, offset]);
|
|
385
|
+
return results.map(r => ({
|
|
386
|
+
id: r.id,
|
|
387
|
+
userId: r.user_id,
|
|
388
|
+
organizationId: r.organization_id,
|
|
389
|
+
contributionRole: r.contribution_role,
|
|
390
|
+
permissionsRole: r.permissions_role,
|
|
391
|
+
createdAt: r.created_at,
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Get a specific organization membership for a user
|
|
396
|
+
*
|
|
397
|
+
* @param db - Database instance
|
|
398
|
+
* @param userId - User ID
|
|
399
|
+
* @param organizationId - Organization ID
|
|
400
|
+
* @returns Organization membership or null if not found
|
|
401
|
+
*/
|
|
402
|
+
async function getOrganizationMembershipForUser(db, userId, organizationId) {
|
|
403
|
+
const sql = `
|
|
404
|
+
SELECT id, user_id, organization_id, contribution_role, permissions_role, created_at
|
|
405
|
+
FROM public.organization_members
|
|
406
|
+
WHERE user_id = $1 AND organization_id = $2
|
|
407
|
+
LIMIT 1
|
|
408
|
+
`;
|
|
409
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, [userId, organizationId]);
|
|
410
|
+
if (!result)
|
|
411
|
+
return null;
|
|
412
|
+
return {
|
|
413
|
+
id: result.id,
|
|
414
|
+
userId: result.user_id,
|
|
415
|
+
organizationId: result.organization_id,
|
|
416
|
+
contributionRole: result.contribution_role,
|
|
417
|
+
permissionsRole: result.permissions_role,
|
|
418
|
+
createdAt: result.created_at,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Get a specific contributor by user_id with full details
|
|
423
|
+
* Returns the same data structure as getOrganizationContributors but for a single user
|
|
424
|
+
* This is useful for direct lookups without pagination
|
|
425
|
+
*
|
|
426
|
+
* @param db - Database instance
|
|
427
|
+
* @param organizationId - Organization ID
|
|
428
|
+
* @param userId - User ID to fetch
|
|
429
|
+
* @returns Contributor data or null if not found or not a contributor with relationships
|
|
430
|
+
*/
|
|
431
|
+
async function getOrganizationContributorByUserId(db, organizationId, userId) {
|
|
432
|
+
// Use the same query logic as getOrganizationContributors but filter by user_id
|
|
433
|
+
// Updated to use organization_member_id for faster, more accurate lookups
|
|
434
|
+
const sql = `
|
|
435
|
+
WITH relationship_counts AS (
|
|
436
|
+
-- Count distinct relationships for each contributor using organization_member_id
|
|
437
|
+
-- This ensures we only count relationships assigned to the specific contributor,
|
|
438
|
+
-- not relationships where they appear as the "other person" in someone else's mapping job
|
|
439
|
+
SELECT
|
|
440
|
+
om.user_id,
|
|
441
|
+
COUNT(DISTINCT
|
|
442
|
+
CASE
|
|
443
|
+
WHEN r.linkedin_account_id_a = la.id THEN r.linkedin_account_id_b
|
|
444
|
+
ELSE r.linkedin_account_id_a
|
|
445
|
+
END
|
|
446
|
+
) as relationships_count
|
|
447
|
+
FROM public.organization_members om
|
|
448
|
+
INNER JOIN linkedin.accounts la ON la.user_id = om.user_id
|
|
449
|
+
INNER JOIN public.organization_user_relationships_assignments oura
|
|
450
|
+
ON oura.organization_member_id = om.id -- Use organization_member_id for faster lookup
|
|
451
|
+
INNER JOIN linkedin.relationships r
|
|
452
|
+
ON r.id = oura.linkedin_relationship_id
|
|
453
|
+
AND (r.linkedin_account_id_a = la.id OR r.linkedin_account_id_b = la.id)
|
|
454
|
+
INNER JOIN monitoring.mapping_jobs mj
|
|
455
|
+
ON mj.id = r.mapping_job_id
|
|
456
|
+
AND mj.linkedin_account_id = la.id -- CRITICAL: Only count relationships from mapping jobs that belong to this contributor
|
|
457
|
+
WHERE oura.organization_id = $1
|
|
458
|
+
AND om.organization_id = $1
|
|
459
|
+
AND om.contribution_role = 'CONTRIBUTOR'
|
|
460
|
+
GROUP BY om.user_id
|
|
461
|
+
),
|
|
462
|
+
llm_job_status AS (
|
|
463
|
+
-- Get LLM inference job completion status for each contributor
|
|
464
|
+
SELECT DISTINCT ON (om.user_id)
|
|
465
|
+
om.user_id,
|
|
466
|
+
CASE
|
|
467
|
+
WHEN lij.completed_at IS NOT NULL THEN true
|
|
468
|
+
ELSE false
|
|
469
|
+
END as llm_job_completed
|
|
470
|
+
FROM public.organization_members om
|
|
471
|
+
INNER JOIN monitoring.organization_assignment_jobs oaj
|
|
472
|
+
ON oaj.organization_id = om.organization_id
|
|
473
|
+
INNER JOIN monitoring.mapping_jobs mj
|
|
474
|
+
ON mj.id = oaj.mapping_job_id
|
|
475
|
+
INNER JOIN linkedin.accounts la
|
|
476
|
+
ON la.id = mj.linkedin_account_id
|
|
477
|
+
LEFT JOIN monitoring.llm_inference_jobs lij
|
|
478
|
+
ON lij.mapping_job_id = oaj.mapping_job_id
|
|
479
|
+
WHERE om.organization_id = $1
|
|
480
|
+
AND om.contribution_role = 'CONTRIBUTOR'
|
|
481
|
+
AND la.user_id = om.user_id
|
|
482
|
+
ORDER BY om.user_id, oaj.created_at DESC, lij.created_at DESC NULLS LAST
|
|
483
|
+
)
|
|
484
|
+
SELECT
|
|
485
|
+
om.id,
|
|
486
|
+
om.user_id,
|
|
487
|
+
om.organization_id,
|
|
488
|
+
om.contribution_role,
|
|
489
|
+
om.permissions_role,
|
|
490
|
+
om.created_at,
|
|
491
|
+
COALESCE(rc.relationships_count, 0) as relationships_count,
|
|
492
|
+
COALESCE(ljs.llm_job_completed, false) as llm_job_completed
|
|
493
|
+
FROM public.organization_members om
|
|
494
|
+
INNER JOIN relationship_counts rc ON rc.user_id = om.user_id
|
|
495
|
+
LEFT JOIN llm_job_status ljs ON ljs.user_id = om.user_id
|
|
496
|
+
WHERE om.organization_id = $1
|
|
497
|
+
AND om.contribution_role = 'CONTRIBUTOR'
|
|
498
|
+
AND om.user_id = $2
|
|
499
|
+
AND rc.relationships_count > 0
|
|
500
|
+
LIMIT 1
|
|
501
|
+
`;
|
|
502
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, [organizationId, userId]);
|
|
503
|
+
if (!result)
|
|
504
|
+
return null;
|
|
505
|
+
return {
|
|
506
|
+
id: result.id,
|
|
507
|
+
userId: result.user_id,
|
|
508
|
+
organizationId: result.organization_id,
|
|
509
|
+
contributionRole: result.contribution_role,
|
|
510
|
+
permissionsRole: result.permissions_role,
|
|
511
|
+
createdAt: result.created_at,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Find which page a contributor appears on in the paginated contributors list
|
|
516
|
+
* Uses the same sorting logic as getOrganizationContributors
|
|
517
|
+
*
|
|
518
|
+
* @param db - Database instance
|
|
519
|
+
* @param organizationId - Organization ID
|
|
520
|
+
* @param userId - User ID to find
|
|
521
|
+
* @param limit - Page size (default: 50)
|
|
522
|
+
* @returns Page number (1-indexed) or null if user is not a contributor with relationships
|
|
523
|
+
*/
|
|
524
|
+
async function findContributorPage(db, organizationId, userId, limit = 50) {
|
|
525
|
+
const sql = `
|
|
526
|
+
WITH relationship_counts AS (
|
|
527
|
+
-- Count distinct relationships for each contributor's LinkedIn account
|
|
528
|
+
SELECT
|
|
529
|
+
la.user_id,
|
|
530
|
+
COUNT(DISTINCT
|
|
531
|
+
CASE
|
|
532
|
+
WHEN r.linkedin_account_id_a = la.id THEN r.linkedin_account_id_b
|
|
533
|
+
ELSE r.linkedin_account_id_a
|
|
534
|
+
END
|
|
535
|
+
) as relationships_count
|
|
536
|
+
FROM linkedin.accounts la
|
|
537
|
+
INNER JOIN linkedin.relationships r
|
|
538
|
+
ON (r.linkedin_account_id_a = la.id OR r.linkedin_account_id_b = la.id)
|
|
539
|
+
INNER JOIN public.organization_user_relationships_assignments oura
|
|
540
|
+
ON oura.linkedin_relationship_id = r.id
|
|
541
|
+
WHERE oura.organization_id = $1
|
|
542
|
+
GROUP BY la.user_id
|
|
543
|
+
),
|
|
544
|
+
llm_job_status AS (
|
|
545
|
+
-- Get LLM inference job completion status for each contributor
|
|
546
|
+
SELECT DISTINCT ON (om.user_id)
|
|
547
|
+
om.user_id,
|
|
548
|
+
CASE
|
|
549
|
+
WHEN lij.completed_at IS NOT NULL THEN true
|
|
550
|
+
ELSE false
|
|
551
|
+
END as llm_job_completed
|
|
552
|
+
FROM public.organization_members om
|
|
553
|
+
INNER JOIN monitoring.organization_assignment_jobs oaj
|
|
554
|
+
ON oaj.organization_id = om.organization_id
|
|
555
|
+
INNER JOIN monitoring.mapping_jobs mj
|
|
556
|
+
ON mj.id = oaj.mapping_job_id
|
|
557
|
+
INNER JOIN linkedin.accounts la
|
|
558
|
+
ON la.id = mj.linkedin_account_id
|
|
559
|
+
LEFT JOIN monitoring.llm_inference_jobs lij
|
|
560
|
+
ON lij.mapping_job_id = oaj.mapping_job_id
|
|
561
|
+
WHERE om.organization_id = $1
|
|
562
|
+
AND om.contribution_role = 'CONTRIBUTOR'
|
|
563
|
+
AND la.user_id = om.user_id
|
|
564
|
+
ORDER BY om.user_id, oaj.created_at DESC, lij.created_at DESC NULLS LAST
|
|
565
|
+
),
|
|
566
|
+
ranked_contributors AS (
|
|
567
|
+
SELECT
|
|
568
|
+
om.user_id,
|
|
569
|
+
ROW_NUMBER() OVER (
|
|
570
|
+
ORDER BY
|
|
571
|
+
-- Primary sort: LLM job completed status (descending)
|
|
572
|
+
COALESCE(ljs.llm_job_completed, false) DESC,
|
|
573
|
+
-- Secondary sort: relationship count (descending)
|
|
574
|
+
COALESCE(rc.relationships_count, 0) DESC,
|
|
575
|
+
-- Tertiary sort: created_at (ascending)
|
|
576
|
+
om.created_at ASC
|
|
577
|
+
) as rank
|
|
578
|
+
FROM public.organization_members om
|
|
579
|
+
INNER JOIN relationship_counts rc ON rc.user_id = om.user_id
|
|
580
|
+
LEFT JOIN llm_job_status ljs ON ljs.user_id = om.user_id
|
|
581
|
+
WHERE om.organization_id = $1
|
|
582
|
+
AND om.contribution_role = 'CONTRIBUTOR'
|
|
583
|
+
AND rc.relationships_count > 0
|
|
584
|
+
)
|
|
585
|
+
SELECT rank
|
|
586
|
+
FROM ranked_contributors
|
|
587
|
+
WHERE user_id = $2
|
|
588
|
+
`;
|
|
589
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, [
|
|
590
|
+
organizationId,
|
|
591
|
+
userId,
|
|
592
|
+
]);
|
|
593
|
+
if (!result)
|
|
594
|
+
return null;
|
|
595
|
+
// Calculate page number (1-indexed)
|
|
596
|
+
const page = Math.ceil(result.rank / limit);
|
|
597
|
+
return page;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Update organization member role
|
|
601
|
+
*
|
|
602
|
+
* Updates the contribution role and/or permissions role for an existing organization member.
|
|
603
|
+
* This is useful for promoting VISITOR to CONTRIBUTOR or changing admin permissions.
|
|
604
|
+
*
|
|
605
|
+
* @param db - Database instance
|
|
606
|
+
* @param userId - User ID
|
|
607
|
+
* @param organizationId - Organization ID
|
|
608
|
+
* @param contributionRole - New contribution role (optional)
|
|
609
|
+
* @param permissionsRole - New permissions role (optional)
|
|
610
|
+
* @returns Updated organization membership or null if not found
|
|
611
|
+
*/
|
|
612
|
+
async function updateOrganizationMemberRole(db, userId, organizationId, params) {
|
|
613
|
+
(0, db_1.debugLogDbOperation)("update", "organization_members", { userId, organizationId, ...params }, undefined, {
|
|
614
|
+
operation: "updateOrganizationMemberRole",
|
|
615
|
+
});
|
|
616
|
+
if (!params.contributionRole && !params.permissionsRole) {
|
|
617
|
+
// Nothing to update, return existing
|
|
618
|
+
return getOrganizationMembershipForUser(db, userId, organizationId);
|
|
619
|
+
}
|
|
620
|
+
const updates = [];
|
|
621
|
+
const queryParams = [userId, organizationId];
|
|
622
|
+
let paramIndex = 3;
|
|
623
|
+
if (params.contributionRole !== undefined) {
|
|
624
|
+
updates.push(`contribution_role = $${paramIndex}`);
|
|
625
|
+
queryParams.push(params.contributionRole);
|
|
626
|
+
paramIndex++;
|
|
627
|
+
}
|
|
628
|
+
if (params.permissionsRole !== undefined) {
|
|
629
|
+
updates.push(`permissions_role = $${paramIndex}`);
|
|
630
|
+
queryParams.push(params.permissionsRole);
|
|
631
|
+
paramIndex++;
|
|
632
|
+
}
|
|
633
|
+
const sql = `
|
|
634
|
+
UPDATE public.organization_members
|
|
635
|
+
SET ${updates.join(", ")}
|
|
636
|
+
WHERE user_id = $1 AND organization_id = $2
|
|
637
|
+
RETURNING id, user_id, organization_id, contribution_role, permissions_role, created_at
|
|
638
|
+
`;
|
|
639
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, queryParams);
|
|
640
|
+
if (!result)
|
|
641
|
+
return null;
|
|
642
|
+
return {
|
|
643
|
+
id: result.id,
|
|
644
|
+
userId: result.user_id,
|
|
645
|
+
organizationId: result.organization_id,
|
|
646
|
+
contributionRole: result.contribution_role,
|
|
647
|
+
permissionsRole: result.permissions_role,
|
|
648
|
+
createdAt: result.created_at,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Get assigned relationships for an organization member
|
|
653
|
+
*
|
|
654
|
+
* Returns relationships from organization_user_relationships_assignments
|
|
655
|
+
* where one of the LinkedIn accounts matches the target user's LinkedIn account.
|
|
656
|
+
* Deduplicates by connection person, keeping only the highest-scoring relationship
|
|
657
|
+
* for each unique connection.
|
|
658
|
+
*
|
|
659
|
+
* @param db - Database instance
|
|
660
|
+
* @param organizationId - Organization ID
|
|
661
|
+
* @param linkedinAccountId - LinkedIn account ID of the target member
|
|
662
|
+
* @param limit - Maximum number of relationships to return (default 50)
|
|
663
|
+
* @returns Array of relationships with scores, deduplicated by connection person
|
|
664
|
+
*/
|
|
665
|
+
async function getOrganizationMemberRelationships(db, organizationId, linkedinAccountId, limit = 50) {
|
|
666
|
+
// Updated to use organization_member_id for faster, more accurate lookups
|
|
667
|
+
// First get the organization_member_id for this user
|
|
668
|
+
const memberSql = `
|
|
669
|
+
SELECT om.id as organization_member_id
|
|
670
|
+
FROM public.organization_members om
|
|
671
|
+
INNER JOIN linkedin.accounts la ON la.user_id = om.user_id
|
|
672
|
+
WHERE om.organization_id = $1 AND la.id = $2
|
|
673
|
+
LIMIT 1
|
|
674
|
+
`;
|
|
675
|
+
const member = await (0, pg_client_1.queryOne)(db, memberSql, [organizationId, linkedinAccountId]);
|
|
676
|
+
if (!member) {
|
|
677
|
+
return [];
|
|
678
|
+
}
|
|
679
|
+
const sql = `
|
|
680
|
+
SELECT DISTINCT ON (
|
|
681
|
+
CASE
|
|
682
|
+
WHEN r.linkedin_account_id_a = $1 THEN r.linkedin_account_id_b
|
|
683
|
+
ELSE r.linkedin_account_id_a
|
|
684
|
+
END
|
|
685
|
+
)
|
|
686
|
+
r.id,
|
|
687
|
+
r.linkedin_account_id_a,
|
|
688
|
+
r.linkedin_account_id_b,
|
|
689
|
+
r.score,
|
|
690
|
+
r.model_version,
|
|
691
|
+
r.analysis_type,
|
|
692
|
+
r.mapping_job_id,
|
|
693
|
+
r.metadata,
|
|
694
|
+
r.created_at,
|
|
695
|
+
oura.id AS organization_user_relationship_assignment_id
|
|
696
|
+
FROM linkedin.relationships r
|
|
697
|
+
INNER JOIN public.organization_user_relationships_assignments oura
|
|
698
|
+
ON oura.linkedin_relationship_id = r.id
|
|
699
|
+
WHERE
|
|
700
|
+
oura.organization_member_id = $2
|
|
701
|
+
AND (r.linkedin_account_id_a = $1 OR r.linkedin_account_id_b = $1)
|
|
702
|
+
ORDER BY
|
|
703
|
+
CASE
|
|
704
|
+
WHEN r.linkedin_account_id_a = $1 THEN r.linkedin_account_id_b
|
|
705
|
+
ELSE r.linkedin_account_id_a
|
|
706
|
+
END,
|
|
707
|
+
r.score DESC,
|
|
708
|
+
r.created_at DESC
|
|
709
|
+
LIMIT $3
|
|
710
|
+
`;
|
|
711
|
+
const results = await (0, pg_client_1.query)(db, sql, [linkedinAccountId, member.organization_member_id, limit]);
|
|
712
|
+
return results.map(r => ({
|
|
713
|
+
id: r.id,
|
|
714
|
+
linkedinAccountIdA: r.linkedin_account_id_a,
|
|
715
|
+
linkedinAccountIdB: r.linkedin_account_id_b,
|
|
716
|
+
score: r.score,
|
|
717
|
+
modelVersion: r.model_version,
|
|
718
|
+
analysisType: r.analysis_type,
|
|
719
|
+
mappingJobId: r.mapping_job_id,
|
|
720
|
+
metadata: r.metadata,
|
|
721
|
+
createdAt: r.created_at,
|
|
722
|
+
organizationUserRelationshipAssignmentId: r.organization_user_relationship_assignment_id,
|
|
723
|
+
}));
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Get the count of assigned relationships for an organization member.
|
|
727
|
+
* This counts relationships from the organization_user_relationships_assignments table,
|
|
728
|
+
* which represents the actual connections assigned to the organization for this member.
|
|
729
|
+
*
|
|
730
|
+
* Counts only the top 50 relationships by score (highest first) to match
|
|
731
|
+
* the limit enforced in getOrganizationMemberRelationships.
|
|
732
|
+
*
|
|
733
|
+
* @param db - Database instance
|
|
734
|
+
* @param organizationId - Organization ID
|
|
735
|
+
* @param linkedinAccountId - LinkedIn account ID of the target member
|
|
736
|
+
* @returns Count of assigned relationships for this member in this organization (capped at 50)
|
|
737
|
+
*/
|
|
738
|
+
async function getOrganizationMemberRelationshipsCount(db, organizationId, linkedinAccountId) {
|
|
739
|
+
// Updated to use organization_member_id for faster, more accurate lookups
|
|
740
|
+
// First get the organization_member_id for this user
|
|
741
|
+
const memberSql = `
|
|
742
|
+
SELECT om.id as organization_member_id
|
|
743
|
+
FROM public.organization_members om
|
|
744
|
+
INNER JOIN linkedin.accounts la ON la.user_id = om.user_id
|
|
745
|
+
WHERE om.organization_id = $1 AND la.id = $2
|
|
746
|
+
LIMIT 1
|
|
747
|
+
`;
|
|
748
|
+
const member = await (0, pg_client_1.queryOne)(db, memberSql, [organizationId, linkedinAccountId]);
|
|
749
|
+
if (!member) {
|
|
750
|
+
return 0;
|
|
751
|
+
}
|
|
752
|
+
const sql = `
|
|
753
|
+
SELECT COUNT(*) as count
|
|
754
|
+
FROM (
|
|
755
|
+
SELECT DISTINCT ON (
|
|
756
|
+
CASE
|
|
757
|
+
WHEN r.linkedin_account_id_a = $1 THEN r.linkedin_account_id_b
|
|
758
|
+
ELSE r.linkedin_account_id_a
|
|
759
|
+
END
|
|
760
|
+
)
|
|
761
|
+
r.id
|
|
762
|
+
FROM linkedin.relationships r
|
|
763
|
+
INNER JOIN public.organization_user_relationships_assignments oura
|
|
764
|
+
ON oura.linkedin_relationship_id = r.id
|
|
765
|
+
WHERE
|
|
766
|
+
oura.organization_member_id = $2
|
|
767
|
+
AND (r.linkedin_account_id_a = $1 OR r.linkedin_account_id_b = $1)
|
|
768
|
+
ORDER BY
|
|
769
|
+
CASE
|
|
770
|
+
WHEN r.linkedin_account_id_a = $1 THEN r.linkedin_account_id_b
|
|
771
|
+
ELSE r.linkedin_account_id_a
|
|
772
|
+
END,
|
|
773
|
+
r.score DESC,
|
|
774
|
+
r.created_at DESC
|
|
775
|
+
LIMIT 50
|
|
776
|
+
) AS top_relationships
|
|
777
|
+
`;
|
|
778
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, [
|
|
779
|
+
linkedinAccountId,
|
|
780
|
+
member.organization_member_id,
|
|
781
|
+
]);
|
|
782
|
+
return result ? parseInt(result.count, 10) : 0;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Create a new organization
|
|
786
|
+
*
|
|
787
|
+
* @param db - Database instance
|
|
788
|
+
* @param organizationData - Organization data to create
|
|
789
|
+
* @returns Created organization
|
|
790
|
+
*/
|
|
791
|
+
async function createOrganization(db, organizationData) {
|
|
792
|
+
(0, db_1.debugLogDbOperation)("insert", "organizations", { organizationData });
|
|
793
|
+
// Generate deterministic dedup_key if not provided
|
|
794
|
+
// This ensures idempotency - same input = same dedup_key (no duplicates on retries)
|
|
795
|
+
// For manual orgs: dedup_key MUST be provided (used for deduplication)
|
|
796
|
+
// For LinkedIn orgs: Generate deterministic key from linkedin_company_id (not used for dedup, linkedin_company_id is)
|
|
797
|
+
// For personal workspaces: Generate deterministic key from created_by_user_id
|
|
798
|
+
let dedupKey;
|
|
799
|
+
if (organizationData.dedupKey) {
|
|
800
|
+
// Manual orgs: use provided dedup_key
|
|
801
|
+
dedupKey = organizationData.dedupKey;
|
|
802
|
+
}
|
|
803
|
+
else if (organizationData.linkedinCompanyId) {
|
|
804
|
+
// LinkedIn orgs: generate deterministic key from company_id
|
|
805
|
+
dedupKey = `linkedin-${organizationData.linkedinCompanyId}`;
|
|
806
|
+
}
|
|
807
|
+
else if (organizationData.createdByUserId) {
|
|
808
|
+
// Personal workspaces: generate deterministic key from user_id
|
|
809
|
+
dedupKey = `personal-${organizationData.createdByUserId}`;
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
// This should never be reached:
|
|
813
|
+
// - Manual orgs: dedup_key is required
|
|
814
|
+
// - LinkedIn orgs: linkedin_company_id is always available (from Forager or provided)
|
|
815
|
+
// - Personal workspaces: created_by_user_id is always provided
|
|
816
|
+
throw new Error("Cannot generate dedup_key: missing required data. " +
|
|
817
|
+
"Manual orgs require dedup_key, LinkedIn orgs require linkedin_company_id, " +
|
|
818
|
+
"and personal workspaces require created_by_user_id.");
|
|
819
|
+
}
|
|
820
|
+
const sql = `
|
|
821
|
+
INSERT INTO public.organizations (
|
|
822
|
+
name,
|
|
823
|
+
workspace_type,
|
|
824
|
+
subscription_tier,
|
|
825
|
+
created_by_user_id,
|
|
826
|
+
image_url,
|
|
827
|
+
linkedin_public_identifier,
|
|
828
|
+
linkedin_company_id,
|
|
829
|
+
dedup_key,
|
|
830
|
+
created_at
|
|
831
|
+
)
|
|
832
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())
|
|
833
|
+
RETURNING id, name, workspace_type, subscription_tier, image_url, created_at, created_by_user_id
|
|
834
|
+
`;
|
|
835
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, [
|
|
836
|
+
organizationData.name,
|
|
837
|
+
organizationData.workspaceType,
|
|
838
|
+
organizationData.subscriptionTier,
|
|
839
|
+
organizationData.createdByUserId ?? null,
|
|
840
|
+
organizationData.imageUrl || null,
|
|
841
|
+
organizationData.linkedinPublicIdentifier || null,
|
|
842
|
+
organizationData.linkedinCompanyId || null,
|
|
843
|
+
dedupKey,
|
|
844
|
+
]);
|
|
845
|
+
if (!result) {
|
|
846
|
+
throw new Error("Failed to create organization");
|
|
847
|
+
}
|
|
848
|
+
return {
|
|
849
|
+
id: result.id,
|
|
850
|
+
name: result.name,
|
|
851
|
+
workspaceType: result.workspace_type,
|
|
852
|
+
subscriptionTier: result.subscription_tier,
|
|
853
|
+
imageUrl: result.image_url,
|
|
854
|
+
createdAt: result.created_at,
|
|
855
|
+
createdByUserId: result.created_by_user_id,
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Update linkedin_public_identifier for an existing organization
|
|
860
|
+
*
|
|
861
|
+
* @param db - Database instance
|
|
862
|
+
* @param organizationId - Organization ID
|
|
863
|
+
* @param linkedinPublicIdentifier - LinkedIn public identifier to set
|
|
864
|
+
* @returns true if update was successful, false if no rows were updated
|
|
865
|
+
*/
|
|
866
|
+
async function updateOrganizationLinkedInPublicIdentifier(db, organizationId, linkedinPublicIdentifier) {
|
|
867
|
+
(0, db_1.debugLogDbOperation)("update", "organizations", { organizationId, linkedinPublicIdentifier });
|
|
868
|
+
const sql = `
|
|
869
|
+
UPDATE public.organizations
|
|
870
|
+
SET linkedin_public_identifier = $1
|
|
871
|
+
WHERE id = $2 AND (linkedin_public_identifier IS NULL OR linkedin_public_identifier != $1)
|
|
872
|
+
RETURNING id
|
|
873
|
+
`;
|
|
874
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, [
|
|
875
|
+
linkedinPublicIdentifier,
|
|
876
|
+
organizationId,
|
|
877
|
+
]);
|
|
878
|
+
return result !== null;
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* Find or create an organization member
|
|
882
|
+
*
|
|
883
|
+
* @param db - Database instance
|
|
884
|
+
* @param membershipData - Membership data
|
|
885
|
+
* @returns Created or existing organization member
|
|
886
|
+
*/
|
|
887
|
+
async function findOrCreateOrganizationMember(db, membershipData) {
|
|
888
|
+
(0, db_1.debugLogDbOperation)("select", "organization_members", { membershipData });
|
|
889
|
+
// Check if membership already exists
|
|
890
|
+
const existingSql = `
|
|
891
|
+
SELECT id, user_id, organization_id, contribution_role, permissions_role, created_at
|
|
892
|
+
FROM public.organization_members
|
|
893
|
+
WHERE organization_id = $1 AND user_id = $2
|
|
894
|
+
LIMIT 1
|
|
895
|
+
`;
|
|
896
|
+
const existing = await (0, pg_client_1.queryOne)(db, existingSql, [membershipData.organizationId, membershipData.userId]);
|
|
897
|
+
if (existing) {
|
|
898
|
+
return {
|
|
899
|
+
id: existing.id,
|
|
900
|
+
userId: existing.user_id,
|
|
901
|
+
organizationId: existing.organization_id,
|
|
902
|
+
contributionRole: existing.contribution_role,
|
|
903
|
+
permissionsRole: existing.permissions_role,
|
|
904
|
+
createdAt: existing.created_at,
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
// Create new membership
|
|
908
|
+
const insertSql = `
|
|
909
|
+
INSERT INTO public.organization_members (organization_id, user_id, contribution_role, permissions_role, created_at)
|
|
910
|
+
VALUES ($1, $2, $3, $4, NOW())
|
|
911
|
+
RETURNING id, user_id, organization_id, contribution_role, permissions_role, created_at
|
|
912
|
+
`;
|
|
913
|
+
const member = await (0, pg_client_1.queryOne)(db, insertSql, [
|
|
914
|
+
membershipData.organizationId,
|
|
915
|
+
membershipData.userId,
|
|
916
|
+
membershipData.contributionRole,
|
|
917
|
+
membershipData.permissionsRole,
|
|
918
|
+
]);
|
|
919
|
+
if (!member) {
|
|
920
|
+
throw new Error("Failed to create organization member");
|
|
921
|
+
}
|
|
922
|
+
return {
|
|
923
|
+
id: member.id,
|
|
924
|
+
userId: member.user_id,
|
|
925
|
+
organizationId: member.organization_id,
|
|
926
|
+
contributionRole: member.contribution_role,
|
|
927
|
+
permissionsRole: member.permissions_role,
|
|
928
|
+
createdAt: member.created_at,
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Find existing personal workspace for a user
|
|
933
|
+
* Each user can only have one personal workspace
|
|
934
|
+
*
|
|
935
|
+
* @param db - Database instance
|
|
936
|
+
* @param userId - User ID to find personal workspace for
|
|
937
|
+
* @returns Existing personal workspace or null
|
|
938
|
+
*/
|
|
939
|
+
async function findPersonalWorkspaceForUser(db, userId) {
|
|
940
|
+
(0, db_1.debugLogDbOperation)("select", "organizations", { userId });
|
|
941
|
+
const sql = `
|
|
942
|
+
SELECT o.id, o.name, o.workspace_type, o.subscription_tier, o.image_url, o.created_at, o.created_by_user_id
|
|
943
|
+
FROM public.organizations o
|
|
944
|
+
INNER JOIN public.organization_members om ON o.id = om.organization_id
|
|
945
|
+
WHERE o.workspace_type = 'INDIVIDUALS'
|
|
946
|
+
AND om.user_id = $1
|
|
947
|
+
AND om.contribution_role = 'CONTRIBUTOR'
|
|
948
|
+
LIMIT 1
|
|
949
|
+
`;
|
|
950
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, [userId]);
|
|
951
|
+
if (!result)
|
|
952
|
+
return null;
|
|
953
|
+
return {
|
|
954
|
+
id: result.id,
|
|
955
|
+
name: result.name,
|
|
956
|
+
workspaceType: result.workspace_type,
|
|
957
|
+
subscriptionTier: result.subscription_tier,
|
|
958
|
+
imageUrl: result.image_url,
|
|
959
|
+
createdAt: result.created_at,
|
|
960
|
+
createdByUserId: result.created_by_user_id,
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Get organization member by ID
|
|
965
|
+
*
|
|
966
|
+
* @param db - Database instance
|
|
967
|
+
* @param memberId - Organization member ID
|
|
968
|
+
* @returns Organization member or null if not found
|
|
969
|
+
*/
|
|
970
|
+
async function getOrganizationMemberById(db, memberId) {
|
|
971
|
+
(0, db_1.debugLogDbOperation)("select", "organization_members", { memberId });
|
|
972
|
+
const sql = `
|
|
973
|
+
SELECT
|
|
974
|
+
id,
|
|
975
|
+
user_id,
|
|
976
|
+
organization_id,
|
|
977
|
+
contribution_role,
|
|
978
|
+
permissions_role,
|
|
979
|
+
created_at
|
|
980
|
+
FROM public.organization_members
|
|
981
|
+
WHERE id = $1
|
|
982
|
+
LIMIT 1
|
|
983
|
+
`;
|
|
984
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, [memberId]);
|
|
985
|
+
if (!result) {
|
|
986
|
+
return null;
|
|
987
|
+
}
|
|
988
|
+
return {
|
|
989
|
+
id: result.id,
|
|
990
|
+
userId: result.user_id,
|
|
991
|
+
organizationId: result.organization_id,
|
|
992
|
+
contributionRole: result.contribution_role,
|
|
993
|
+
permissionsRole: result.permissions_role,
|
|
994
|
+
createdAt: result.created_at,
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Update organization subscription tier
|
|
999
|
+
*
|
|
1000
|
+
* @param db - Database instance
|
|
1001
|
+
* @param organizationId - Organization ID
|
|
1002
|
+
* @param subscriptionTier - New subscription tier
|
|
1003
|
+
* @returns Updated organization or null if not found
|
|
1004
|
+
*/
|
|
1005
|
+
async function updateOrganizationSubscriptionTier(db, organizationId, subscriptionTier) {
|
|
1006
|
+
(0, db_1.debugLogDbOperation)("update", "organizations", {
|
|
1007
|
+
organizationId,
|
|
1008
|
+
subscriptionTier,
|
|
1009
|
+
});
|
|
1010
|
+
const sql = `
|
|
1011
|
+
UPDATE public.organizations
|
|
1012
|
+
SET subscription_tier = $1
|
|
1013
|
+
WHERE id = $2
|
|
1014
|
+
RETURNING id, name, workspace_type, subscription_tier, image_url, created_at, created_by_user_id
|
|
1015
|
+
`;
|
|
1016
|
+
const result = await (0, pg_client_1.queryOne)(db, sql, [subscriptionTier, organizationId]);
|
|
1017
|
+
if (!result) {
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
return {
|
|
1021
|
+
id: result.id,
|
|
1022
|
+
name: result.name,
|
|
1023
|
+
workspaceType: result.workspace_type,
|
|
1024
|
+
subscriptionTier: result.subscription_tier,
|
|
1025
|
+
imageUrl: result.image_url,
|
|
1026
|
+
createdAt: result.created_at,
|
|
1027
|
+
createdByUserId: result.created_by_user_id,
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
//# sourceMappingURL=organization-operations.js.map
|