forked from boos/BoosAPI
Refactor logging timestamps to use formatUtcSqlDateTime function across proxy routes and services; add storedTimestampRepairService for fixing createdAt values in database
This commit is contained in:
@@ -17,6 +17,8 @@ describe('accounts health refresh runtime state', () => {
|
||||
let db: DbModule['db'];
|
||||
let schema: DbModule['schema'];
|
||||
let dataDir = '';
|
||||
let resetBackgroundTasks: (() => void) | null = null;
|
||||
let getBackgroundTask: ((taskId: string) => { status: string; result: unknown; finishedAt: string | null } | null) | null = null;
|
||||
|
||||
beforeAll(async () => {
|
||||
dataDir = mkdtempSync(join(tmpdir(), 'metapi-accounts-health-refresh-'));
|
||||
@@ -25,8 +27,11 @@ describe('accounts health refresh runtime state', () => {
|
||||
await import('../../db/migrate.js');
|
||||
const dbModule = await import('../../db/index.js');
|
||||
const routesModule = await import('./accounts.js');
|
||||
const backgroundTaskModule = await import('../../services/backgroundTaskService.js');
|
||||
db = dbModule.db;
|
||||
schema = dbModule.schema;
|
||||
resetBackgroundTasks = backgroundTaskModule.__resetBackgroundTasksForTests;
|
||||
getBackgroundTask = backgroundTaskModule.getBackgroundTask;
|
||||
|
||||
app = Fastify();
|
||||
await app.register(routesModule.accountsRoutes);
|
||||
@@ -34,6 +39,7 @@ describe('accounts health refresh runtime state', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
refreshBalanceMock.mockReset();
|
||||
resetBackgroundTasks?.();
|
||||
await db.delete(schema.proxyLogs).run();
|
||||
await db.delete(schema.checkinLogs).run();
|
||||
await db.delete(schema.routeChannels).run();
|
||||
@@ -41,6 +47,7 @@ describe('accounts health refresh runtime state', () => {
|
||||
await db.delete(schema.tokenModelAvailability).run();
|
||||
await db.delete(schema.modelAvailability).run();
|
||||
await db.delete(schema.accountTokens).run();
|
||||
await db.delete(schema.events).run();
|
||||
await db.delete(schema.accounts).run();
|
||||
await db.delete(schema.sites).run();
|
||||
});
|
||||
@@ -101,4 +108,149 @@ describe('accounts health refresh runtime state', () => {
|
||||
message: '站点不支持签到接口',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns readable task messages when starting a background refresh for all accounts', async () => {
|
||||
const site = await db.insert(schema.sites).values({
|
||||
name: 'Wind Hub',
|
||||
url: 'https://windhub.cc',
|
||||
platform: 'done-hub',
|
||||
}).returning().get();
|
||||
|
||||
await db.insert(schema.accounts).values({
|
||||
siteId: site.id,
|
||||
username: 'tester',
|
||||
accessToken: 'token',
|
||||
status: 'active',
|
||||
}).returning().get();
|
||||
|
||||
refreshBalanceMock.mockImplementation(() => new Promise(() => {}));
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/accounts/health/refresh',
|
||||
payload: {},
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(202);
|
||||
expect(response.json()).toMatchObject({
|
||||
success: true,
|
||||
message: '已开始刷新账号运行健康状态,请稍后查看账号列表',
|
||||
});
|
||||
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const events = await db.select().from(schema.events).all();
|
||||
if (events.length > 0) {
|
||||
expect(events[0]).toMatchObject({
|
||||
title: '刷新全部账号运行健康状态已开始',
|
||||
message: '刷新全部账号运行健康状态 已开始执行',
|
||||
level: 'info',
|
||||
type: 'status',
|
||||
});
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
}
|
||||
|
||||
throw new Error('expected a background task event to be recorded');
|
||||
});
|
||||
|
||||
it('fails a single-account health refresh after 10 seconds when the site never responds', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const site = await db.insert(schema.sites).values({
|
||||
name: 'Slow Site',
|
||||
url: 'https://slow.example.com',
|
||||
platform: 'done-hub',
|
||||
}).returning().get();
|
||||
|
||||
const account = await db.insert(schema.accounts).values({
|
||||
siteId: site.id,
|
||||
username: 'slow-user',
|
||||
accessToken: 'token',
|
||||
status: 'active',
|
||||
}).returning().get();
|
||||
|
||||
refreshBalanceMock.mockImplementation(() => new Promise(() => {}));
|
||||
|
||||
const responsePromise = app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/accounts/health/refresh',
|
||||
payload: { accountId: account.id, wait: true },
|
||||
});
|
||||
|
||||
await vi.advanceTimersByTimeAsync(10_001);
|
||||
|
||||
const response = await responsePromise;
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
||||
const body = response.json() as {
|
||||
success: boolean;
|
||||
summary: {
|
||||
total: number;
|
||||
failed: number;
|
||||
};
|
||||
results: Array<{ status: string; state: string; message: string }>;
|
||||
};
|
||||
|
||||
expect(body.success).toBe(true);
|
||||
expect(body.summary).toMatchObject({
|
||||
total: 1,
|
||||
failed: 1,
|
||||
});
|
||||
expect(body.results[0]).toMatchObject({
|
||||
status: 'failed',
|
||||
state: 'unhealthy',
|
||||
message: '站点健康检查超时(10s)',
|
||||
});
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
it('finishes the background refresh-all task after the 10 second site timeout instead of staying in progress', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
const site = await db.insert(schema.sites).values({
|
||||
name: 'Slow Site',
|
||||
url: 'https://slow.example.com',
|
||||
platform: 'done-hub',
|
||||
}).returning().get();
|
||||
|
||||
await db.insert(schema.accounts).values({
|
||||
siteId: site.id,
|
||||
username: 'slow-user',
|
||||
accessToken: 'token',
|
||||
status: 'active',
|
||||
}).returning().get();
|
||||
|
||||
refreshBalanceMock.mockImplementation(() => new Promise(() => {}));
|
||||
|
||||
const responsePromise = app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/accounts/health/refresh',
|
||||
payload: {},
|
||||
});
|
||||
await vi.advanceTimersByTimeAsync(0);
|
||||
const response = await responsePromise;
|
||||
|
||||
expect(response.statusCode).toBe(202);
|
||||
const body = response.json() as { jobId: string };
|
||||
|
||||
await vi.advanceTimersByTimeAsync(10_001);
|
||||
|
||||
const task = getBackgroundTask?.(body.jobId);
|
||||
expect(task).toMatchObject({
|
||||
status: 'succeeded',
|
||||
result: {
|
||||
summary: {
|
||||
total: 1,
|
||||
failed: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(task?.finishedAt).toBeTruthy();
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
@@ -11,11 +11,13 @@ import {
|
||||
} from '../../services/localTimeService.js';
|
||||
|
||||
type DbModule = typeof import('../../db/index.js');
|
||||
type RepairModule = typeof import('../../services/storedTimestampRepairService.js');
|
||||
|
||||
describe('accounts api today reward fallback', () => {
|
||||
let app: FastifyInstance;
|
||||
let db: DbModule['db'];
|
||||
let schema: DbModule['schema'];
|
||||
let repairStoredCreatedAtValues: RepairModule['repairStoredCreatedAtValues'];
|
||||
let dataDir = '';
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -25,8 +27,10 @@ describe('accounts api today reward fallback', () => {
|
||||
await import('../../db/migrate.js');
|
||||
const dbModule = await import('../../db/index.js');
|
||||
const routesModule = await import('./accounts.js');
|
||||
const repairModule = await import('../../services/storedTimestampRepairService.js');
|
||||
db = dbModule.db;
|
||||
schema = dbModule.schema;
|
||||
repairStoredCreatedAtValues = repairModule.repairStoredCreatedAtValues;
|
||||
|
||||
app = Fastify();
|
||||
await app.register(routesModule.accountsRoutes);
|
||||
@@ -90,6 +94,39 @@ describe('accounts api today reward fallback', () => {
|
||||
expect(target?.todayReward).toBe(12.5);
|
||||
});
|
||||
|
||||
it('repairs ISO timestamps for today reward filtering', async () => {
|
||||
const site = await db.insert(schema.sites).values({
|
||||
name: 'iso-reward-site',
|
||||
url: 'https://iso-reward-site.example.com',
|
||||
platform: 'new-api',
|
||||
}).returning().get();
|
||||
const account = await db.insert(schema.accounts).values({
|
||||
siteId: site.id,
|
||||
username: 'iso-reward-user',
|
||||
accessToken: 'token',
|
||||
status: 'active',
|
||||
}).returning().get();
|
||||
|
||||
await db.insert(schema.checkinLogs).values({
|
||||
accountId: account.id,
|
||||
status: 'success',
|
||||
message: 'checkin success',
|
||||
reward: '1.8',
|
||||
createdAt: new Date().toISOString(),
|
||||
}).run();
|
||||
await repairStoredCreatedAtValues();
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/accounts',
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
const rows = response.json() as Array<{ id: number; todayReward: number }>;
|
||||
const target = rows.find((row) => row.id === account.id);
|
||||
expect(target?.todayReward).toBe(1.8);
|
||||
});
|
||||
|
||||
it('prefers parsed checkin reward when available', async () => {
|
||||
const today = formatLocalDate(new Date());
|
||||
const site = await db.insert(schema.sites).values({
|
||||
@@ -182,4 +219,40 @@ describe('accounts api today reward fallback', () => {
|
||||
const target = rows.find((row) => row.id === account.id);
|
||||
expect(target?.todaySpend).toBe(2);
|
||||
});
|
||||
|
||||
it('repairs ISO timestamps for today spend filtering', async () => {
|
||||
const site = await db.insert(schema.sites).values({
|
||||
name: 'iso-spend-site',
|
||||
url: 'https://iso-spend-site.example.com',
|
||||
platform: 'new-api',
|
||||
}).returning().get();
|
||||
const account = await db.insert(schema.accounts).values({
|
||||
siteId: site.id,
|
||||
username: 'iso-spend-user',
|
||||
accessToken: 'token',
|
||||
status: 'active',
|
||||
}).returning().get();
|
||||
|
||||
const { startUtc } = getLocalDayRangeUtc();
|
||||
const startDate = parseStoredUtcDateTime(startUtc)!;
|
||||
const inRangeIso = new Date(startDate.getTime() + 60_000).toISOString();
|
||||
|
||||
await db.insert(schema.proxyLogs).values({
|
||||
accountId: account.id,
|
||||
status: 'success',
|
||||
estimatedCost: 2.25,
|
||||
createdAt: inRangeIso,
|
||||
}).run();
|
||||
await repairStoredCreatedAtValues();
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/accounts',
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
const rows = response.json() as Array<{ id: number; todaySpend: number }>;
|
||||
const target = rows.find((row) => row.id === account.id);
|
||||
expect(target?.todaySpend).toBe(2.25);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,6 +125,8 @@ type LoginFailureInfo = {
|
||||
shieldBlocked: boolean;
|
||||
};
|
||||
|
||||
const ACCOUNT_HEALTH_REFRESH_TIMEOUT_MS = 10_000;
|
||||
|
||||
function normalizeLoginFailure(message: string | null | undefined): LoginFailureInfo {
|
||||
const raw = (message || '').trim();
|
||||
const lowered = raw.toLowerCase();
|
||||
@@ -154,8 +156,8 @@ function normalizeLoginFailure(message: string | null | undefined): LoginFailure
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeAccountHealthRefresh(results: AccountHealthRefreshResult[]) {
|
||||
return {
|
||||
function summarizeAccountHealthRefresh(results: AccountHealthRefreshResult[]) {
|
||||
return {
|
||||
total: results.length,
|
||||
healthy: results.filter((item) => item.state === 'healthy').length,
|
||||
unhealthy: results.filter((item) => item.state === 'unhealthy').length,
|
||||
@@ -165,10 +167,24 @@ function summarizeAccountHealthRefresh(results: AccountHealthRefreshResult[]) {
|
||||
success: results.filter((item) => item.status === 'success').length,
|
||||
failed: results.filter((item) => item.status === 'failed').length,
|
||||
skipped: results.filter((item) => item.status === 'skipped').length,
|
||||
};
|
||||
}
|
||||
|
||||
async function refreshRuntimeHealthForRow(row: AccountWithSiteRow): Promise<AccountHealthRefreshResult> {
|
||||
};
|
||||
}
|
||||
|
||||
async function withTimeout<T>(fn: () => Promise<T>, timeoutMs: number, timeoutMessage: string): Promise<T> {
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
try {
|
||||
return await Promise.race([
|
||||
fn(),
|
||||
new Promise<T>((_, reject) => {
|
||||
timer = setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs);
|
||||
}),
|
||||
]);
|
||||
} finally {
|
||||
if (timer) clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshRuntimeHealthForRow(row: AccountWithSiteRow): Promise<AccountHealthRefreshResult> {
|
||||
const accountId = row.accounts.id;
|
||||
const username = row.accounts.username;
|
||||
const siteName = row.sites.name;
|
||||
@@ -187,10 +203,14 @@ async function refreshRuntimeHealthForRow(row: AccountWithSiteRow): Promise<Acco
|
||||
state: 'disabled',
|
||||
message: '账号或站点已禁用',
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
await refreshBalance(accountId);
|
||||
await withTimeout(
|
||||
() => refreshBalance(accountId),
|
||||
ACCOUNT_HEALTH_REFRESH_TIMEOUT_MS,
|
||||
`站点健康检查超时(${Math.max(1, Math.round(ACCOUNT_HEALTH_REFRESH_TIMEOUT_MS / 1000))}s)`,
|
||||
);
|
||||
const refreshedAccount = await db.select().from(schema.accounts)
|
||||
.where(eq(schema.accounts.id, accountId))
|
||||
.get();
|
||||
|
||||
@@ -2,6 +2,7 @@ import { FastifyInstance } from 'fastify';
|
||||
import { db, schema } from '../../db/index.js';
|
||||
import { config } from '../../config.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { formatUtcSqlDateTime } from '../../services/localTimeService.js';
|
||||
|
||||
export async function authRoutes(app: FastifyInstance) {
|
||||
// Change admin auth token (requires old token verification)
|
||||
@@ -32,13 +33,14 @@ export async function authRoutes(app: FastifyInstance) {
|
||||
config.authToken = newToken;
|
||||
|
||||
try {
|
||||
const createdAt = formatUtcSqlDateTime(new Date());
|
||||
await db.insert(schema.events).values({
|
||||
type: 'token',
|
||||
title: '管理员登录令牌已更新',
|
||||
message: '管理员登录 Token 已被修改,请使用新 Token 登录。',
|
||||
level: 'warning',
|
||||
relatedType: 'settings',
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt,
|
||||
}).run();
|
||||
} catch {}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
testDatabaseConnection,
|
||||
type MigrationDialect,
|
||||
} from '../../services/databaseMigrationService.js';
|
||||
import { formatUtcSqlDateTime } from '../../services/localTimeService.js';
|
||||
import { extractClientIp, isIpAllowed } from '../../middleware/auth.js';
|
||||
|
||||
type RoutingWeights = typeof config.routingWeights;
|
||||
@@ -87,13 +88,14 @@ async function appendSettingsEvent(input: {
|
||||
level?: 'info' | 'warning' | 'error';
|
||||
}) {
|
||||
try {
|
||||
const createdAt = formatUtcSqlDateTime(new Date());
|
||||
await db.insert(schema.events).values({
|
||||
type: input.type,
|
||||
title: input.title,
|
||||
message: input.message,
|
||||
level: input.level || 'info',
|
||||
relatedType: 'settings',
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt,
|
||||
}).run();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { db, schema } from '../../db/index.js';
|
||||
import { and, eq, sql } from 'drizzle-orm';
|
||||
import { detectSite } from '../../services/siteDetector.js';
|
||||
import { invalidateSiteProxyCache, parseSiteProxyUrlInput } from '../../services/siteProxy.js';
|
||||
import { formatUtcSqlDateTime } from '../../services/localTimeService.js';
|
||||
|
||||
function normalizeSiteStatus(input: unknown): 'active' | 'disabled' | null {
|
||||
if (input === undefined || input === null) return null;
|
||||
@@ -237,6 +238,7 @@ export async function sitesRoutes(app: FastifyInstance) {
|
||||
.run();
|
||||
|
||||
try {
|
||||
const createdAt = formatUtcSqlDateTime(new Date());
|
||||
await db.insert(schema.events).values({
|
||||
type: 'status',
|
||||
title: '站点已禁用',
|
||||
@@ -244,7 +246,7 @@ export async function sitesRoutes(app: FastifyInstance) {
|
||||
level: 'warning',
|
||||
relatedId: id,
|
||||
relatedType: 'site',
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt,
|
||||
}).run();
|
||||
} catch {}
|
||||
} else {
|
||||
@@ -254,6 +256,7 @@ export async function sitesRoutes(app: FastifyInstance) {
|
||||
.run();
|
||||
|
||||
try {
|
||||
const createdAt = formatUtcSqlDateTime(new Date());
|
||||
await db.insert(schema.events).values({
|
||||
type: 'status',
|
||||
title: '站点已启用',
|
||||
@@ -261,7 +264,7 @@ export async function sitesRoutes(app: FastifyInstance) {
|
||||
level: 'info',
|
||||
relatedId: id,
|
||||
relatedType: 'site',
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt,
|
||||
}).run();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,13 @@ import {
|
||||
} from '../../services/localTimeService.js';
|
||||
|
||||
type DbModule = typeof import('../../db/index.js');
|
||||
type RepairModule = typeof import('../../services/storedTimestampRepairService.js');
|
||||
|
||||
describe('stats dashboard today reward fallback', () => {
|
||||
let app: FastifyInstance;
|
||||
let db: DbModule['db'];
|
||||
let schema: DbModule['schema'];
|
||||
let repairStoredCreatedAtValues: RepairModule['repairStoredCreatedAtValues'];
|
||||
let dataDir = '';
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -25,8 +27,10 @@ describe('stats dashboard today reward fallback', () => {
|
||||
await import('../../db/migrate.js');
|
||||
const dbModule = await import('../../db/index.js');
|
||||
const routesModule = await import('./stats.js');
|
||||
const repairModule = await import('../../services/storedTimestampRepairService.js');
|
||||
db = dbModule.db;
|
||||
schema = dbModule.schema;
|
||||
repairStoredCreatedAtValues = repairModule.repairStoredCreatedAtValues;
|
||||
|
||||
app = Fastify();
|
||||
await app.register(routesModule.statsRoutes);
|
||||
@@ -89,6 +93,38 @@ describe('stats dashboard today reward fallback', () => {
|
||||
expect(body.todayReward).toBe(4.8);
|
||||
});
|
||||
|
||||
it('repairs ISO timestamps for dashboard today reward filtering', async () => {
|
||||
const site = await db.insert(schema.sites).values({
|
||||
name: 'stats-iso-site',
|
||||
url: 'https://stats-iso.example.com',
|
||||
platform: 'new-api',
|
||||
}).returning().get();
|
||||
const account = await db.insert(schema.accounts).values({
|
||||
siteId: site.id,
|
||||
username: 'stats-iso-user',
|
||||
accessToken: 'token',
|
||||
status: 'active',
|
||||
}).returning().get();
|
||||
|
||||
await db.insert(schema.checkinLogs).values({
|
||||
accountId: account.id,
|
||||
status: 'success',
|
||||
message: 'checked in',
|
||||
reward: '3.6',
|
||||
createdAt: new Date().toISOString(),
|
||||
}).run();
|
||||
await repairStoredCreatedAtValues();
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/stats/dashboard',
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
const body = response.json() as { todayReward: number };
|
||||
expect(body.todayReward).toBe(3.6);
|
||||
});
|
||||
|
||||
it('counts dashboard today spend only inside local-day range', async () => {
|
||||
const site = await db.insert(schema.sites).values({
|
||||
name: 'stats-spend-site',
|
||||
@@ -139,4 +175,39 @@ describe('stats dashboard today reward fallback', () => {
|
||||
const body = response.json() as { todaySpend: number };
|
||||
expect(body.todaySpend).toBe(3);
|
||||
});
|
||||
|
||||
it('repairs ISO timestamps for dashboard today spend filtering', async () => {
|
||||
const site = await db.insert(schema.sites).values({
|
||||
name: 'stats-iso-spend-site',
|
||||
url: 'https://stats-iso-spend.example.com',
|
||||
platform: 'new-api',
|
||||
}).returning().get();
|
||||
const account = await db.insert(schema.accounts).values({
|
||||
siteId: site.id,
|
||||
username: 'stats-iso-spend-user',
|
||||
accessToken: 'token',
|
||||
status: 'active',
|
||||
}).returning().get();
|
||||
|
||||
const { startUtc } = getLocalDayRangeUtc();
|
||||
const startDate = parseStoredUtcDateTime(startUtc)!;
|
||||
const inRangeIso = new Date(startDate.getTime() + 60_000).toISOString();
|
||||
|
||||
await db.insert(schema.proxyLogs).values({
|
||||
accountId: account.id,
|
||||
status: 'success',
|
||||
estimatedCost: 6.4,
|
||||
createdAt: inRangeIso,
|
||||
}).run();
|
||||
await repairStoredCreatedAtValues();
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/stats/dashboard',
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
const body = response.json() as { todaySpend: number };
|
||||
expect(body.todaySpend).toBe(6.4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FastifyInstance } from 'fastify';
|
||||
import { db, schema } from '../../db/index.js';
|
||||
import { and, desc, gte, eq, lt, sql } from 'drizzle-orm';
|
||||
import { and, desc, eq, gte, lt, sql } from 'drizzle-orm';
|
||||
import {
|
||||
refreshModelsForAccount,
|
||||
refreshModelsAndRebuildRoutes,
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
} from './downstreamPolicy.js';
|
||||
import { composeProxyLogMessage } from './logPathMeta.js';
|
||||
import { executeEndpointFlow, withUpstreamPath } from './endpointFlow.js';
|
||||
import { formatUtcSqlDateTime } from '../../services/localTimeService.js';
|
||||
|
||||
const MAX_RETRIES = 2;
|
||||
const CLAUDE_SSE_EVENT_NAMES = new Set([
|
||||
@@ -719,6 +720,7 @@ async function logProxy(
|
||||
upstreamPath: string | null = null,
|
||||
) {
|
||||
try {
|
||||
const createdAt = formatUtcSqlDateTime(new Date());
|
||||
const normalizedErrorMessage = composeProxyLogMessage({
|
||||
downstreamPath,
|
||||
upstreamPath,
|
||||
@@ -739,7 +741,7 @@ async function logProxy(
|
||||
estimatedCost,
|
||||
errorMessage: normalizedErrorMessage,
|
||||
retryCount,
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt,
|
||||
}).run();
|
||||
} catch (error) {
|
||||
console.warn('[proxy/chat] failed to write proxy log', error);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { mergeProxyUsage, parseProxyUsage, pullSseDataEvents } from '../../servi
|
||||
import { ensureModelAllowedForDownstreamKey, getDownstreamRoutingPolicy, recordDownstreamCostUsage } from './downstreamPolicy.js';
|
||||
import { withExplicitProxyRequestInit } from '../../services/siteProxy.js';
|
||||
import { composeProxyLogMessage } from './logPathMeta.js';
|
||||
import { formatUtcSqlDateTime } from '../../services/localTimeService.js';
|
||||
|
||||
const MAX_RETRIES = 2;
|
||||
|
||||
@@ -242,6 +243,7 @@ async function logProxy(
|
||||
estimatedCost = 0,
|
||||
) {
|
||||
try {
|
||||
const createdAt = formatUtcSqlDateTime(new Date());
|
||||
const normalizedErrorMessage = composeProxyLogMessage({
|
||||
downstreamPath: '/v1/completions',
|
||||
errorMessage,
|
||||
@@ -261,7 +263,7 @@ async function logProxy(
|
||||
estimatedCost,
|
||||
errorMessage: normalizedErrorMessage,
|
||||
retryCount,
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt,
|
||||
}).run();
|
||||
} catch (error) {
|
||||
console.warn('[proxy/completions] failed to write proxy log', error);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { parseProxyUsage } from '../../services/proxyUsageParser.js';
|
||||
import { ensureModelAllowedForDownstreamKey, getDownstreamRoutingPolicy, recordDownstreamCostUsage } from './downstreamPolicy.js';
|
||||
import { withExplicitProxyRequestInit } from '../../services/siteProxy.js';
|
||||
import { composeProxyLogMessage } from './logPathMeta.js';
|
||||
import { formatUtcSqlDateTime } from '../../services/localTimeService.js';
|
||||
|
||||
const MAX_RETRIES = 2;
|
||||
|
||||
@@ -157,6 +158,7 @@ async function logProxy(
|
||||
estimatedCost = 0,
|
||||
) {
|
||||
try {
|
||||
const createdAt = formatUtcSqlDateTime(new Date());
|
||||
const normalizedErrorMessage = composeProxyLogMessage({
|
||||
downstreamPath: '/v1/embeddings',
|
||||
errorMessage,
|
||||
@@ -176,7 +178,7 @@ async function logProxy(
|
||||
estimatedCost,
|
||||
errorMessage: normalizedErrorMessage,
|
||||
retryCount,
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt,
|
||||
}).run();
|
||||
} catch (error) {
|
||||
console.warn('[proxy/embeddings] failed to write proxy log', error);
|
||||
|
||||
@@ -10,6 +10,7 @@ import { shouldRetryProxyRequest } from '../../services/proxyRetryPolicy.js';
|
||||
import { ensureModelAllowedForDownstreamKey, getDownstreamRoutingPolicy, recordDownstreamCostUsage } from './downstreamPolicy.js';
|
||||
import { withExplicitProxyRequestInit } from '../../services/siteProxy.js';
|
||||
import { composeProxyLogMessage } from './logPathMeta.js';
|
||||
import { formatUtcSqlDateTime } from '../../services/localTimeService.js';
|
||||
|
||||
const MAX_RETRIES = 2;
|
||||
|
||||
@@ -127,6 +128,7 @@ async function logProxy(
|
||||
estimatedCost = 0,
|
||||
) {
|
||||
try {
|
||||
const createdAt = formatUtcSqlDateTime(new Date());
|
||||
const normalizedErrorMessage = composeProxyLogMessage({
|
||||
downstreamPath: '/v1/images/generations',
|
||||
errorMessage,
|
||||
@@ -146,7 +148,7 @@ async function logProxy(
|
||||
estimatedCost,
|
||||
errorMessage: normalizedErrorMessage,
|
||||
retryCount,
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt,
|
||||
}).run();
|
||||
} catch (error) {
|
||||
console.warn('[proxy/images] failed to write proxy log', error);
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
import { ensureModelAllowedForDownstreamKey, getDownstreamRoutingPolicy, recordDownstreamCostUsage } from './downstreamPolicy.js';
|
||||
import { composeProxyLogMessage } from './logPathMeta.js';
|
||||
import { executeEndpointFlow, withUpstreamPath } from './endpointFlow.js';
|
||||
import { formatUtcSqlDateTime } from '../../services/localTimeService.js';
|
||||
|
||||
const MAX_RETRIES = 2;
|
||||
|
||||
@@ -1577,6 +1578,7 @@ async function logProxy(
|
||||
upstreamPath: string | null = null,
|
||||
) {
|
||||
try {
|
||||
const createdAt = formatUtcSqlDateTime(new Date());
|
||||
const normalizedErrorMessage = composeProxyLogMessage({
|
||||
downstreamPath,
|
||||
upstreamPath,
|
||||
@@ -1597,7 +1599,7 @@ async function logProxy(
|
||||
estimatedCost,
|
||||
errorMessage: normalizedErrorMessage,
|
||||
retryCount,
|
||||
createdAt: new Date().toISOString(),
|
||||
createdAt,
|
||||
}).run();
|
||||
} catch (error) {
|
||||
console.warn('[proxy/responses] failed to write proxy log', error);
|
||||
|
||||
Reference in New Issue
Block a user