490 lines
13 KiB
TypeScript
Executable File
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]);
|
|
}
|