UNPKG

eggi-ai-db-schema

Version:

Type-safe database schema and ORM client for Eggi.AI with direct RDS connection

278 lines 13.7 kB
"use strict"; /** * ============================================================================= * USER OPERATIONS UTILITIES * ============================================================================= * Utility functions for creating and managing authenticated users */ Object.defineProperty(exports, "__esModule", { value: true }); exports.getAuthenticatedUserByCognitoId = getAuthenticatedUserByCognitoId; exports.getUserByCognitoId = getUserByCognitoId; exports.findUserByCognitoId = findUserByCognitoId; exports.getExistingUnipileAccount = getExistingUnipileAccount; exports.upsertLinkedAccount = upsertLinkedAccount; const drizzle_orm_1 = require("drizzle-orm"); const db_1 = require("../lib/db"); const schema_1 = require("../lib/schema"); // createOrUpdateAuthenticatedUser removed - only used in example code, not production // Production authentication uses handlePostAuthentication from authentication-operations.ts // Legacy function removed - createAuthenticatedUser // Use handlePostAuthentication from authentication-operations.ts for production authentication /** * Gets authenticated user record by Cognito ID * * This function queries the authenticated_users table and returns the authenticated user record. * * @param cognitoUserId - The Cognito User ID to search for * @returns Promise resolving to authenticated user or null if not found */ async function getAuthenticatedUserByCognitoId(cognitoUserId) { const db = await (0, db_1.getDb)(); const result = await db .select() .from(schema_1.authenticatedUsers) .where((0, drizzle_orm_1.eq)(schema_1.authenticatedUsers.cognitoUserId, cognitoUserId)) .limit(1); return result.length > 0 ? result[0] : null; } /** * Gets the actual user record from users table via Cognito ID * * This function: * 1. Finds authenticated user by Cognito ID * 2. Uses authenticated_user.user_id to get the user from users table * * @param cognitoUserId - The Cognito User ID to search for * @returns Promise resolving to user record or null if not found */ async function getUserByCognitoId(cognitoUserId) { const db = await (0, db_1.getDb)(); // First get the authenticated user const authenticatedUser = await getAuthenticatedUserByCognitoId(cognitoUserId); if (!authenticatedUser) { return null; } // Then get the actual user record from users table const result = await db .select() .from(schema_1.users) .where((0, drizzle_orm_1.eq)(schema_1.users.id, authenticatedUser.userId)) .limit(1); return result.length > 0 ? result[0] : null; } /** * @deprecated Use getAuthenticatedUserByCognitoId instead */ async function findUserByCognitoId(cognitoUserId) { return getAuthenticatedUserByCognitoId(cognitoUserId); } // Legacy function removed - findUserByEmail // Email is now stored in the users table, use contact_infos table for email lookups // Legacy function removed - updateAuthenticatedUser // User information is now in the users table, use the user update functions instead /** * Checks if a user already has a Unipile account for a specific platform * @param cognitoUserId - The Cognito User ID * @param platformType - The platform to check (e.g., "linkedin") * @returns Promise resolving to the existing account or null if not found */ async function getExistingUnipileAccount(cognitoUserId, platformType) { const db = await (0, db_1.getDb)(); if (!cognitoUserId || !platformType) { throw new Error("Missing required fields: cognitoUserId and platformType are required"); } try { // Find the authenticated user first const authUserResult = await db .select({ userId: schema_1.authenticatedUsers.userId, }) .from(schema_1.authenticatedUsers) .where((0, drizzle_orm_1.eq)(schema_1.authenticatedUsers.cognitoUserId, cognitoUserId)) .limit(1); if (authUserResult.length === 0) { return null; // User not found } const userId = authUserResult[0].userId; // Check for existing Unipile account for this platform through social accounts const existingAccount = await db .select({ id: schema_1.unipileAccounts.id, socialAccountId: schema_1.unipileAccounts.socialAccountId, unipileAccountId: schema_1.unipileAccounts.unipileAccountId, accountType: schema_1.unipileAccounts.accountType, isActive: schema_1.unipileAccounts.isActive, connectedAt: schema_1.unipileAccounts.connectedAt, lastSyncedAt: schema_1.unipileAccounts.lastSyncedAt, metadata: schema_1.unipileAccounts.metadata, createdAt: schema_1.unipileAccounts.createdAt, updatedAt: schema_1.unipileAccounts.updatedAt, }) .from(schema_1.unipileAccounts) .innerJoin(schema_1.socialAccounts, (0, drizzle_orm_1.eq)(schema_1.unipileAccounts.socialAccountId, schema_1.socialAccounts.id)) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.socialAccounts.userId, userId), (0, drizzle_orm_1.eq)(schema_1.socialAccounts.platform, platformType), (0, drizzle_orm_1.eq)(schema_1.unipileAccounts.isActive, true) // Only consider active accounts )) .limit(1); return existingAccount.length > 0 ? existingAccount[0] : null; } catch (error) { throw new Error(`Failed to check existing Unipile account: ${error instanceof Error ? error.message : "Unknown error"}`); } } /** * Upserts a linked social media account for a user * Finds or creates user by Cognito ID, then upserts the Unipile account * * @param cognitoUserId - The Cognito User ID * @param accountData - Account linking data * @returns Promise resolving to the upserted account */ async function upsertLinkedAccount(cognitoUserId, accountData) { const db = await (0, db_1.getDb)(); if (!cognitoUserId || !accountData.unipileAccountId) { throw new Error("Missing required fields: cognitoUserId and unipileAccountId are required"); } try { return await db.transaction(async (tx) => { // 1. Find or create user by Cognito ID let userId; // Check if authenticated user exists const existingAuthUser = await tx .select({ id: schema_1.authenticatedUsers.id, userId: schema_1.authenticatedUsers.userId, user: { id: schema_1.users.id, givenName: schema_1.users.givenName, familyName: schema_1.users.familyName, }, }) .from(schema_1.authenticatedUsers) .leftJoin(schema_1.users, (0, drizzle_orm_1.eq)(schema_1.authenticatedUsers.userId, schema_1.users.id)) .where((0, drizzle_orm_1.eq)(schema_1.authenticatedUsers.cognitoUserId, cognitoUserId)) .limit(1); if (existingAuthUser.length > 0) { const authRecord = existingAuthUser[0]; if (authRecord.user?.id) { // Both records exist userId = authRecord.user.id; } else { // authenticated_user exists but user record is missing const newUsers = await tx .insert(schema_1.users) .values({ givenName: null, // Will be filled when user provides info familyName: null, }) .returning(); userId = newUsers[0].id; // Update the authenticated_user to point to the new user await tx .update(schema_1.authenticatedUsers) .set({ userId }) .where((0, drizzle_orm_1.eq)(schema_1.authenticatedUsers.cognitoUserId, cognitoUserId)); } } else { // No authenticated user exists - this shouldn't happen in normal flow // Create both user and authenticated_users records const newUsers = await tx .insert(schema_1.users) .values({ givenName: null, // Shadow user - no name info yet familyName: null, }) .returning(); userId = newUsers[0].id; await tx.insert(schema_1.authenticatedUsers).values({ userId, cognitoUserId, authProvider: "cognito", }); } // 2. Find or create social account let socialAccountId; const existingSocialAccount = await tx .select({ id: schema_1.socialAccounts.id }) .from(schema_1.socialAccounts) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.socialAccounts.userId, userId), (0, drizzle_orm_1.eq)(schema_1.socialAccounts.platform, accountData.providerType), accountData.internalIdentifier ? (0, drizzle_orm_1.eq)(schema_1.socialAccounts.internalIdentifier, accountData.internalIdentifier) : undefined)) .limit(1); if (existingSocialAccount.length > 0) { socialAccountId = existingSocialAccount[0].id; } else { // Create social account if it doesn't exist // For LinkedIn accounts, we need to ensure internalIdentifierRegular is provided if (accountData.providerType === "linkedin") { // LinkedIn accounts require an ACoA identifier in internalIdentifierRegular // If only internalIdentifier is provided, we need to determine where to store it if (!accountData.internalIdentifier) { throw new Error("LinkedIn accounts require an internal identifier"); } const identifier = accountData.internalIdentifier; let socialAccountValues = { userId, platform: accountData.providerType, }; if (identifier.startsWith("ACoA")) { // ACoA identifier goes to internalIdentifierRegular socialAccountValues.internalIdentifierRegular = identifier; } else if (identifier.startsWith("ACwA") || identifier.startsWith("AEMA")) { // ACwA/AEMA identifiers go to internalIdentifier socialAccountValues.internalIdentifier = identifier; // But we still need an ACoA identifier for the constraint // This is a data issue - LinkedIn accounts should have ACoA identifiers throw new Error(`LinkedIn account with ${identifier.substring(0, 4)} identifier requires an ACoA identifier as well`); } else { // Public identifier socialAccountValues.publicIdentifier = identifier; // But we still need an ACoA identifier for the constraint throw new Error(`LinkedIn account with public identifier '${identifier}' requires an ACoA identifier as well`); } const newSocialAccounts = await tx .insert(schema_1.socialAccounts) .values(socialAccountValues) .returning({ id: schema_1.socialAccounts.id }); socialAccountId = newSocialAccounts[0].id; } else { // Non-LinkedIn platforms - but since all current accounts are LinkedIn, // this branch should not be reached in practice throw new Error(`Platform ${accountData.providerType} not supported - only LinkedIn accounts are currently supported`); } } // 3. Upsert Unipile account record const upsertedAccounts = await tx .insert(schema_1.unipileAccounts) .values({ socialAccountId, // Changed from userId unipileAccountId: accountData.unipileAccountId, isActive: true, connectedAt: new Date(accountData.linkedAt), lastSyncedAt: new Date(), metadata: accountData.providerType === "linkedin" ? {} : { email: accountData.email }, }) .onConflictDoUpdate({ target: [schema_1.unipileAccounts.socialAccountId], // Changed constraint set: { unipileAccountId: accountData.unipileAccountId, isActive: true, connectedAt: new Date(accountData.linkedAt), lastSyncedAt: new Date(), metadata: accountData.providerType === "linkedin" ? {} : { email: accountData.email }, }, }) .returning(); return upsertedAccounts[0]; }); } catch (error) { throw new Error(`Failed to upsert linked account: ${error instanceof Error ? error.message : "Unknown error"}`); } } //# sourceMappingURL=user-operations.js.map