101 lines
3.2 KiB
TypeScript
101 lines
3.2 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import {
|
|
PREMIUM_MODULE_IDS,
|
|
updatePremiumModuleRuntimeStatus,
|
|
type PremiumModuleRuntimeStatus,
|
|
type PremiumModuleRuntimeStatusValue,
|
|
} from '@/lib/license/premium-module-service';
|
|
|
|
const validModuleIds = new Set<string>(Object.values(PREMIUM_MODULE_IDS));
|
|
const validStatuses = new Set<PremiumModuleRuntimeStatusValue>([
|
|
'missing',
|
|
'pulling',
|
|
'running',
|
|
'unhealthy',
|
|
'expired',
|
|
'revoked',
|
|
]);
|
|
|
|
function getRuntimeStatusToken(): string {
|
|
return (
|
|
process.env.MODULE_RUNTIME_STATUS_TOKEN ||
|
|
process.env.FLNXI_MODULE_STATUS_CALLBACK_TOKEN ||
|
|
''
|
|
).trim();
|
|
}
|
|
|
|
function readBearerToken(request: NextRequest): string {
|
|
const authorization = request.headers.get('authorization') || '';
|
|
if (authorization.toLowerCase().startsWith('bearer ')) {
|
|
return authorization.slice('bearer '.length).trim();
|
|
}
|
|
return (request.headers.get('x-module-status-token') || '').trim();
|
|
}
|
|
|
|
function normalizeRuntimeStatus(input: unknown): PremiumModuleRuntimeStatus | null {
|
|
if (!input || typeof input !== 'object') return null;
|
|
|
|
const raw = input as Record<string, unknown>;
|
|
const moduleId = typeof raw.moduleId === 'string' ? raw.moduleId.trim() : '';
|
|
const image = typeof raw.image === 'string' ? raw.image.trim() : '';
|
|
const digest = typeof raw.digest === 'string' && raw.digest.trim() ? raw.digest.trim() : null;
|
|
const status = typeof raw.status === 'string' ? raw.status.trim() : '';
|
|
const lastSyncAt = typeof raw.lastSyncAt === 'string' ? raw.lastSyncAt.trim() : '';
|
|
const lastError = typeof raw.lastError === 'string' && raw.lastError.trim()
|
|
? raw.lastError.trim()
|
|
: undefined;
|
|
|
|
if (!validModuleIds.has(moduleId)) return null;
|
|
if (!image) return null;
|
|
if (!validStatuses.has(status as PremiumModuleRuntimeStatusValue)) return null;
|
|
|
|
const lastSyncAtTime = Date.parse(lastSyncAt);
|
|
if (!lastSyncAt || Number.isNaN(lastSyncAtTime)) return null;
|
|
|
|
return {
|
|
moduleId,
|
|
image,
|
|
digest,
|
|
status: status as PremiumModuleRuntimeStatusValue,
|
|
lastSyncAt: new Date(lastSyncAtTime).toISOString(),
|
|
lastError,
|
|
};
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
const configuredToken = getRuntimeStatusToken();
|
|
if (!configuredToken || configuredToken.length < 32) {
|
|
return NextResponse.json(
|
|
{ success: false, error: '模块运行状态回写 token 未配置' },
|
|
{ status: 503 }
|
|
);
|
|
}
|
|
|
|
if (readBearerToken(request) !== configuredToken) {
|
|
return NextResponse.json(
|
|
{ success: false, error: '模块运行状态回写未授权' },
|
|
{ status: 401 }
|
|
);
|
|
}
|
|
|
|
const body = await request.json().catch(() => null);
|
|
const rawStatuses: unknown[] = Array.isArray(body?.statuses) ? body.statuses : [body];
|
|
const statuses = rawStatuses.map(normalizeRuntimeStatus);
|
|
|
|
if (statuses.length === 0 || statuses.some((status: PremiumModuleRuntimeStatus | null) => !status)) {
|
|
return NextResponse.json(
|
|
{ success: false, error: '模块运行状态参数无效' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
for (const status of statuses as PremiumModuleRuntimeStatus[]) {
|
|
await updatePremiumModuleRuntimeStatus(status);
|
|
}
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
updated: statuses.length,
|
|
});
|
|
}
|