103 lines
3.8 KiB
TypeScript
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 [];
|
|
}
|
|
}
|