integratedhub/services/userService.ts
2026-01-20 09:02:26 -06:00

479 lines
12 KiB
TypeScript
Executable File

// 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<User | null> {
if (isE2EEnabled()) {
return getE2EUserById(userId);
}
return await kvStore.get<User>(["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<User> {
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<User> {
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<User>,
): Promise<User | null> {
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<boolean> {
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<User | null> {
return await getUser(userId);
}
// ============================================================================
// ROLE OPERATIONS
// ============================================================================
export async function getAllRoles(): Promise<Role[]> {
const roles: Role[] = [];
for await (const entry of kvStore.list<Role>({ prefix: ["roles"] })) {
roles.push(entry.value);
}
return roles;
}
export async function getRole(roleId: string): Promise<Role | null> {
return await kvStore.get<Role>(["roles", roleId]);
}
export async function createRole(
id: string,
description: string,
): Promise<Role> {
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<string[]>(["user_roles", userId]);
if (!roleIds) {
return [];
}
return roleIds.map((roleId) => ({ roleId }));
}
export async function createUserRole(
user_id: string,
role_id: string,
): Promise<void> {
const existingRoles = await kvStore.get<string[]>(["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<void> {
// 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<void> {
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<UserWithRole[]> {
const usersWithRoles: UserWithRole[] = [];
for await (const entry of kvStore.list<User>({ prefix: ["users"] })) {
const user = entry.value;
const roleIds = await kvStore.get<string[]>(["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<StateKey[]> {
const stateKeys: StateKey[] = [];
for await (
const entry of kvStore.list<StateKey>({ prefix: ["state_keys"] })
) {
stateKeys.push(entry.value);
}
return stateKeys;
}
export async function getStateKeys(
stateId: string,
): Promise<StateKey[]> {
const stateKeys: StateKey[] = [];
for await (
const entry of kvStore.list<StateKey>({ 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<StateKey> {
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<void> {
await kvStore.delete(["state_keys", sealKey]);
}
// ============================================================================
// USER TOKENS OPERATIONS
// ============================================================================
export async function getUserTokens(
userId: string,
): Promise<UserTokens | null> {
return await kvStore.get<UserTokens>(["user_tokens", userId]);
}
export async function setUserTokens(
userId: string,
stateId: string,
accessToken: string,
refreshToken: string,
): Promise<UserTokens> {
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<void> {
await kvStore.delete(["user_tokens", userId]);
}
// ============================================================================
// FINANCIAL BALANCE OPERATIONS
// ============================================================================
export async function getFinancialBalancesByUser(
user_id: string,
): Promise<FinancialBalance | null> {
return await kvStore.get<FinancialBalance>(["financial_balances", user_id]);
}
export async function upsertFinancialBalance(
user_id: string,
balance: number,
balance_date: number,
): Promise<FinancialBalance> {
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<void> {
await kvStore.delete(["financial_balances", user_id]);
}
// ============================================================================
// USER PINS OPERATIONS
// ============================================================================
export async function getUserPin(userId: string): Promise<UserPin | null> {
return await kvStore.get<UserPin>(["user_pins", userId]);
}
export async function setUserPin(
userId: string,
pin: string,
): Promise<UserPin> {
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<void> {
await kvStore.delete(["user_pins", userId]);
}