integratedhub/services/userService.ts
2026-02-14 06:10:15 +00:00

490 lines
13 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";
// 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<User | null> {
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,
};
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<User> {
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<User>,
): Promise<User | null> {
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 | null;
updated_on: number | null;
}
export async function getAllStateKeys(): Promise<StateKey[]> {
// Query from PostgreSQL database
const { sqlGetAllStateKeys } = await import("../db/sqlUserRepository.ts");
return await sqlGetAllStateKeys();
}
export async function getStateKeys(
stateId: string,
): Promise<StateKey[]> {
// 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<StateKey> {
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<void> {
// Delete from PostgreSQL database
const { sqlDeleteStateKey } = await import("../db/sqlUserRepository.ts");
await sqlDeleteStateKey(sealKey);
}
// ============================================================================
// USER TOKENS OPERATIONS
// ============================================================================
export async function getUserTokens(
userId: string,
): Promise<UserTokens[]> {
// 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<UserTokens | null> {
// 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<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,
};
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<void> {
const { sqlDeleteUserTokens } = await import("../db/sqlUserRepository.ts");
await sqlDeleteUserTokens(userId);
}
export async function deleteUserTokenByStateId(
userId: string,
stateId: string,
): Promise<void> {
// 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<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> {
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<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,
};
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<void> {
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]);
}