// 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"; import { getE2EUserById, isE2EEnabled, setE2EUser, updateE2EUser, } from "./e2eFixtures.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 { if (isE2EEnabled()) { return getE2EUserById(userId); } 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, }; if (isE2EEnabled()) { setE2EUser(user); return user; } await kvStore.set(["users", id], user); return user; } export async function upsertUser( id: string, name: string, email: string, ): Promise { if (isE2EEnabled()) { const existingUser = await getUser(id); if (existingUser) { const updatedUser: User = { ...existingUser, name, email, updated_on: Date.now(), }; setE2EUser(updatedUser); return updatedUser; } 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, }; setE2EUser(newUser); return newUser; } 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); 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); return newUser; } } export async function updateUser( id: string, fields: Partial, ): Promise { if (isE2EEnabled()) { return updateE2EUser(id, fields); } 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; updated_on: number; } export async function getAllStateKeys(): Promise { const stateKeys: StateKey[] = []; for await ( const entry of kvStore.list({ prefix: ["state_keys"] }) ) { stateKeys.push(entry.value); } return stateKeys; } export async function getStateKeys( stateId: string, ): Promise { const stateKeys: StateKey[] = []; for await ( const entry of kvStore.list({ prefix: ["state_keys"] }) ) { if (entry.value.state_id === stateId) { stateKeys.push(entry.value); } } return stateKeys; } 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(), }; await kvStore.set(["state_keys", sealKey], stateKey); return stateKey; } export async function deleteStateKey(sealKey: string): Promise { await kvStore.delete(["state_keys", sealKey]); } // ============================================================================ // USER TOKENS OPERATIONS // ============================================================================ export async function getUserTokens( userId: string, ): Promise { return await kvStore.get(["user_tokens", userId]); } 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, }; await kvStore.set(["user_tokens", userId], tokens); return tokens; } export async function deleteUserTokens(userId: string): Promise { await kvStore.delete(["user_tokens", userId]); } // ============================================================================ // 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 { 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, }; await kvStore.set(["user_pins", userId], userPin); return userPin; } export async function deleteUserPin(userId: string): Promise { await kvStore.delete(["user_pins", userId]); }