// services/userService.ts // Manage user registration, lookup, role changes, and profile updates import { kvStore } from "../modules/KvCacheStore.ts"; import type { FinancialBalance, Role, UserWithRole, } from "../db/sqlUserRepository.ts"; import type { User, UserPin } from "../db/interfaces.ts"; // Re-export User type for use in other modules export type { User } from "../db/interfaces.ts"; // ============================================================================ // KV STORE EQUIVALENT FUNCTIONS // ============================================================================ // Key structure: // - ["users", userId] -> User // - ["roles", roleId] -> Role // - ["user_roles", userId] -> string[] (array of roleIds) // - ["state_keys", stateId] -> { seal_key, state_id, cookie_hash, created_on } // - ["financial_balances", userId] -> FinancialBalance // - ["user_pins", userId] -> UserPin // ============================================================================ // USER OPERATIONS // ============================================================================ export async function getUser(userId: string): Promise { return await kvStore.get(["users", userId]); } export async function createUser( id: string, name: string, email: string, phone: string, address_1: string, address_2: string, city: string, state: string, zip: string, country: string, ): Promise { const now = Date.now(); const user: User = { id, name, email, phone, address_1, address_2, city, state, zip, country, user_type: null, onboarded_landlord: false, onboarded_tenant: false, email_verified: null, banking_setup_complete: null, onboarding_step: null, encrypted_data: null, started_on: now, updated_on: now, }; await kvStore.set(["users", id], user); // Also upsert user in SQL database for foreign key constraints const { sqlUpsertUser } = await import("../db/sqlUserRepository.ts"); await sqlUpsertUser(id, name, email); return user; } export async function upsertUser( id: string, name: string, email: string, ): Promise { const existingUser = await getUser(id); if (existingUser) { // Update existing user const updatedUser: User = { ...existingUser, name, email, updated_on: Date.now(), }; await kvStore.set(["users", id], updatedUser); const { sqlUpsertUser } = await import("../db/sqlUserRepository.ts"); await sqlUpsertUser(id, name, email); return updatedUser; } else { // Create new user const now = Date.now(); const newUser: User = { id, name, email, phone: "", address_1: "", address_2: "", city: "", state: "", zip: "", country: "", user_type: null, onboarded_landlord: false, onboarded_tenant: false, email_verified: null, banking_setup_complete: null, onboarding_step: null, encrypted_data: null, started_on: now, updated_on: now, }; await kvStore.set(["users", id], newUser); const { sqlUpsertUser } = await import("../db/sqlUserRepository.ts"); await sqlUpsertUser(id, name, email); return newUser; } } export async function updateUser( id: string, fields: Partial, ): Promise { const existingUser = await getUser(id); if (!existingUser) { return null; } const updatedUser: User = { ...existingUser, ...fields, updated_on: Date.now(), }; await kvStore.set(["users", id], updatedUser); return updatedUser; } export async function deleteUser(id: string): Promise { const user = await getUser(id); if (!user) { return false; } await kvStore.delete(["users", id]); await kvStore.delete(["user_roles", id]); return true; } export async function getUserById(userId: string): Promise { return await getUser(userId); } // ============================================================================ // ROLE OPERATIONS // ============================================================================ export async function getAllRoles(): Promise { const roles: Role[] = []; for await (const entry of kvStore.list({ prefix: ["roles"] })) { roles.push(entry.value); } return roles; } export async function getRole(roleId: string): Promise { return await kvStore.get(["roles", roleId]); } export async function createRole( id: string, description: string, ): Promise { const role: Role = { id, description }; await kvStore.set(["roles", id], role); return role; } // ============================================================================ // USER ROLE OPERATIONS // ============================================================================ export async function getUserRoles( userId: string, ): Promise<{ roleId: string }[]> { const roleIds = await kvStore.get(["user_roles", userId]); if (!roleIds) { return []; } return roleIds.map((roleId) => ({ roleId })); } export async function createUserRole( user_id: string, role_id: string, ): Promise { const existingRoles = await kvStore.get(["user_roles", user_id]) || []; if (!existingRoles.includes(role_id)) { existingRoles.push(role_id); await kvStore.set(["user_roles", user_id], existingRoles); } } export async function updateUserRole( user_id: string, role_id: string, ): Promise { // Replace all roles with the new single role await kvStore.set(["user_roles", user_id], [role_id]); } export async function deleteUserRole(user_id: string): Promise { await kvStore.delete(["user_roles", user_id]); } export async function getUserWithRoles(userId: string) { const user = await getUser(userId); if (!user) { return null; } const userRoles = await getUserRoles(userId); return { id: user.id, name: user.name, email: user.email, phone: user.phone, address_1: user.address_1, address_2: user.address_2, city: user.city, state: user.state, zip: user.zip, country: user.country, roles: userRoles, }; } export async function getAllUsersWithRoles(): Promise { const usersWithRoles: UserWithRole[] = []; for await (const entry of kvStore.list({ prefix: ["users"] })) { const user = entry.value; const roleIds = await kvStore.get(["user_roles", user.id]) || []; let roleId = ""; let roleDescription = ""; if (roleIds.length > 0) { const role = await getRole(roleIds[0]); if (role) { roleId = role.id; roleDescription = role.description; } } usersWithRoles.push({ id: user.id, name: user.name || "", email: user.email || "", role: roleId, roleDescription: roleDescription, }); } return usersWithRoles; } // ============================================================================ // STATE KEY OPERATIONS // ============================================================================ interface StateKey { seal_key: string; state_id: string; cookie_hash: string; created_on: number; } export interface UserTokens { user_id: string; state_id: string; access_token: string; refresh_token: string; created_on: number | null; updated_on: number | null; } export async function getAllStateKeys(): Promise { // Query from PostgreSQL database const { sqlGetAllStateKeys } = await import("../db/sqlUserRepository.ts"); return await sqlGetAllStateKeys(); } export async function getStateKeys( stateId: string, ): Promise { // Query from PostgreSQL database const { sqlGetStateKeys } = await import("../db/sqlUserRepository.ts"); return await sqlGetStateKeys(stateId); } export async function setStateKey( sealKey: string, stateId: string, cookieHash: string, ): Promise { const stateKey: StateKey = { seal_key: sealKey, state_id: stateId, cookie_hash: cookieHash, created_on: Date.now(), }; // Store in PostgreSQL database const { sqlSetStateKey } = await import("../db/sqlUserRepository.ts"); await sqlSetStateKey(sealKey, stateId, cookieHash); return stateKey; } export async function deleteStateKey(sealKey: string): Promise { // Delete from PostgreSQL database const { sqlDeleteStateKey } = await import("../db/sqlUserRepository.ts"); await sqlDeleteStateKey(sealKey); } // ============================================================================ // USER TOKENS OPERATIONS // ============================================================================ export async function getUserTokens( userId: string, ): Promise { // Returns all tokens for a user (supports multiple sessions) const { sqlGetUserTokens } = await import("../db/sqlUserRepository.ts"); return await sqlGetUserTokens(userId); } export async function getUserTokenByStateId( userId: string, stateId: string, ): Promise { // Get a specific session's token const { sqlGetUserTokenByStateId } = await import( "../db/sqlUserRepository.ts" ); return await sqlGetUserTokenByStateId(userId, stateId); } export async function setUserTokens( userId: string, stateId: string, accessToken: string, refreshToken: string, ): Promise { const now = Date.now(); const _tokens: UserTokens = { user_id: userId, state_id: stateId, access_token: accessToken, refresh_token: refreshToken, created_on: now, updated_on: now, }; const { sqlSetUserTokens } = await import("../db/sqlUserRepository.ts"); const result = await sqlSetUserTokens( userId, stateId, accessToken, refreshToken, ); return result[0]; } export async function deleteUserTokens(userId: string): Promise { const { sqlDeleteUserTokens } = await import("../db/sqlUserRepository.ts"); await sqlDeleteUserTokens(userId); } export async function deleteUserTokenByStateId( userId: string, stateId: string, ): Promise { // Delete a specific session's token const { sqlDeleteUserTokenByStateId } = await import( "../db/sqlUserRepository.ts" ); await sqlDeleteUserTokenByStateId(userId, stateId); } // ============================================================================ // FINANCIAL BALANCE OPERATIONS // ============================================================================ export async function getFinancialBalancesByUser( user_id: string, ): Promise { return await kvStore.get(["financial_balances", user_id]); } export async function upsertFinancialBalance( user_id: string, balance: number, balance_date: number, ): Promise { const now = Date.now(); const existing = await getFinancialBalancesByUser(user_id); console.log("Existing balance:", existing); const financialBalance: FinancialBalance = { id: existing?.id || crypto.randomUUID(), user_id, balance, balance_date, encrypted_data: existing?.encrypted_data || null, created_on: existing?.created_on || now, updated_on: now, }; await kvStore.set(["financial_balances", user_id], financialBalance); return financialBalance; } export async function deleteFinancialBalance(user_id: string): Promise { await kvStore.delete(["financial_balances", user_id]); } // ============================================================================ // USER PINS OPERATIONS // ============================================================================ export async function getUserPin(userId: string): Promise { try { const { sqlGetUserPin } = await import("../db/sqlUserRepository.ts"); const sqlPin = await sqlGetUserPin(userId); if (sqlPin) { return sqlPin as UserPin; } } catch (error) { console.error("Error reading user pin from SQL:", error); } // Backward-compatibility fallback for legacy KV-only records. return await kvStore.get(["user_pins", userId]); } export async function setUserPin( userId: string, pin: string, ): Promise { const now = Date.now(); const userPin: UserPin = { user_id: userId, pin, created_on: now, modified_on: now, }; const { sqlSetUserPin } = await import("../db/sqlUserRepository.ts"); const rows = await sqlSetUserPin(userId, pin); const sqlPin = rows[0] as UserPin | undefined; if (sqlPin) { await kvStore.set(["user_pins", userId], sqlPin); return sqlPin; } await kvStore.set(["user_pins", userId], userPin); return userPin; } export async function deleteUserPin(userId: string): Promise { try { const { sqlDeleteUserPin } = await import("../db/sqlUserRepository.ts"); await sqlDeleteUserPin(userId); } catch (error) { console.error("Error deleting user pin from SQL:", error); } await kvStore.delete(["user_pins", userId]); }