learning_ai_invt_trdg/backend/src/services/dynamicConfigRepository.ts

99 lines
3.2 KiB
TypeScript

import { getContainer } from '@bytelyst/cosmos';
import { config } from '../config/index.js';
import logger from '../utils/logger.js';
export interface DynamicConfigEntry {
key: string;
value: string;
description?: string;
updated_at?: string;
}
interface DynamicConfigDocument extends DynamicConfigEntry {
id: string;
productId: string;
updatedAt: string;
}
const CONTAINER_NAME = 'dynamic_config';
function isCosmosConfigured(): boolean {
return Boolean(config.COSMOS_ENDPOINT && config.COSMOS_KEY);
}
function normalizeEntry(entry: Partial<DynamicConfigEntry> | null | undefined): DynamicConfigEntry | null {
const key = String(entry?.key || '').trim();
if (!key) return null;
return {
key,
value: String(entry?.value ?? ''),
description: entry?.description ? String(entry.description) : '',
updated_at: entry?.updated_at ? String(entry.updated_at) : undefined,
};
}
async function listFromCosmos(): Promise<DynamicConfigEntry[]> {
if (!isCosmosConfigured()) return [];
const container = getContainer(CONTAINER_NAME);
const { resources } = await container.items
.query<DynamicConfigDocument>({
query: 'SELECT * FROM c WHERE c.productId = @productId ORDER BY c.key',
parameters: [{ name: '@productId', value: config.PRODUCT_ID }],
})
.fetchAll();
return resources
.map((resource) => normalizeEntry({
key: resource.key,
value: resource.value,
description: resource.description,
updated_at: resource.updatedAt,
}))
.filter((entry): entry is DynamicConfigEntry => Boolean(entry));
}
export async function listDynamicConfigEntries(): Promise<DynamicConfigEntry[]> {
if (!isCosmosConfigured()) {
logger.warn('[DynamicConfig] Cosmos is not configured; dynamic config overrides are unavailable in this environment.');
return [];
}
try {
return await listFromCosmos();
} catch (error) {
logger.warn(`[DynamicConfig] Cosmos read failed: ${error instanceof Error ? error.message : 'unknown error'}`);
return [];
}
}
export async function upsertDynamicConfigEntries(entries: DynamicConfigEntry[]): Promise<void> {
const normalized = entries
.map((entry) => normalizeEntry(entry))
.filter((entry): entry is DynamicConfigEntry => Boolean(entry));
if (normalized.length === 0) {
return;
}
const now = new Date().toISOString();
if (!isCosmosConfigured()) {
logger.warn('[DynamicConfig] Cosmos is not configured; skipping dynamic config upsert.');
return;
}
try {
const container = getContainer(CONTAINER_NAME);
await Promise.all(normalized.map((entry) => container.items.upsert<DynamicConfigDocument>({
id: entry.key,
productId: config.PRODUCT_ID,
key: entry.key,
value: entry.value,
description: entry.description || '',
updated_at: entry.updated_at || now,
updatedAt: entry.updated_at || now,
})));
} catch (error) {
logger.warn(`[DynamicConfig] Cosmos upsert failed: ${error instanceof Error ? error.message : 'unknown error'}`);
}
}