learning_ai_invt_trdg/backend/src/services/userRepository.ts

103 lines
3.8 KiB
TypeScript

import { getContainer } from '@bytelyst/cosmos';
import { config } from '../config/index.js';
import logger from '../utils/logger.js';
import type { UserConfig, supabaseService } from './SupabaseService.js';
type LegacySupabaseService = typeof supabaseService;
const USER_PROFILE_CONTAINER = 'trading_users';
interface TradingUserDocument extends UserConfig {
id: string;
productId: string;
type: 'trading_user';
}
function isCosmosConfigured(): boolean {
return Boolean(config.COSMOS_ENDPOINT && config.COSMOS_KEY);
}
function normalizeUser(row: Partial<UserConfig> | null | undefined): UserConfig | null {
const userId = String(row?.user_id || '').trim();
if (!userId) {
return null;
}
return {
user_id: userId,
first_name: String(row?.first_name || ''),
last_name: String(row?.last_name || ''),
email: String(row?.email || ''),
ALPACA_API_KEY: String(row?.ALPACA_API_KEY || ''),
ALPACA_SECRET_KEY: String(row?.ALPACA_SECRET_KEY || ''),
REAL_ALPACA_API_KEY: String(row?.REAL_ALPACA_API_KEY || ''),
REAL_ALPACA_SECRET_KEY: String(row?.REAL_ALPACA_SECRET_KEY || ''),
role: String(row?.role || 'member'),
trade_enable: Boolean(row?.trade_enable),
drop_threshold_for_buy: Number(row?.drop_threshold_for_buy || 0),
gain_threshold_for_sell: Number(row?.gain_threshold_for_sell || 0),
market_poll_interval_in_seconds: Number(row?.market_poll_interval_in_seconds || 0),
};
}
export async function listActiveTradingUsers(legacyService?: LegacySupabaseService): Promise<UserConfig[]> {
if (isCosmosConfigured()) {
try {
const container = getContainer(USER_PROFILE_CONTAINER);
const { resources } = await container.items.query<TradingUserDocument>({
query: 'SELECT * FROM c WHERE c.productId = @productId AND c.type = @type AND c.trade_enable = true',
parameters: [
{ name: '@productId', value: config.PRODUCT_ID },
{ name: '@type', value: 'trading_user' },
],
}).fetchAll();
const normalized = resources
.map((row) => normalizeUser(row as UserConfig))
.filter((user): user is UserConfig => Boolean(user));
if (normalized.length > 0) {
return normalized;
}
} catch (error) {
logger.warn(`[Users] Cosmos active trading user lookup failed: ${error instanceof Error ? error.message : 'unknown error'}`);
return [];
}
}
const client = legacyService?.getClient?.();
if (!client) {
return [];
}
try {
const { data, error } = await client
.from('users')
.select('*')
.eq('trade_enable', true);
if (error || !Array.isArray(data)) {
return [];
}
const normalized = data
.map((row) => normalizeUser(row as UserConfig))
.filter((user): user is UserConfig => Boolean(user));
if (isCosmosConfigured() && normalized.length > 0) {
try {
const container = getContainer(USER_PROFILE_CONTAINER);
await Promise.all(normalized.map((user) => container.items.upsert({
...user,
id: user.user_id,
productId: config.PRODUCT_ID,
type: 'trading_user',
} as TradingUserDocument)));
} catch (error) {
logger.warn(`[Users] Cosmos user seed failed: ${error instanceof Error ? error.message : 'unknown error'}`);
}
}
return normalized;
} catch (error) {
logger.warn(`[Users] Active trading user lookup failed: ${error instanceof Error ? error.message : 'unknown error'}`);
return [];
}
}