eggi-ai-db-schema
Version:
Type-safe database schema and ORM client for Eggi.AI with direct RDS connection
278 lines • 13.7 kB
JavaScript
"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