Spaces:
Running
Running
File size: 5,227 Bytes
b1cfe1b 11486ce | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | import { appLogger } from '@/lib/app-logger';
import {
feedbackRecordToResponse,
getFeedbackStore,
normalizeFeedbackDeletedAt,
normalizeFeedbackNote,
normalizeFeedbackTargetId,
normalizeFeedbackTargetType,
normalizeFeedbackUpdatedAt,
normalizeFeedbackValue,
type FeedbackRecord,
type FeedbackTarget
} from '@/lib/feedback-store';
import { PAGE_PASSWORD_AUTH_ERROR_CODES } from '@/lib/page-password-auth';
import { verifyAccessToken } from '@/lib/server-runtime';
import { NextRequest, NextResponse } from 'next/server';
const MAX_FEEDBACK_TARGETS = 20;
function jsonError(code: string, message: string, status: number) {
return NextResponse.json({ error: message, code }, { status });
}
function verifyFeedbackWriter(request: NextRequest) {
const appPassword = process.env.APP_PASSWORD?.trim();
if (!appPassword) return undefined;
const accessToken = request.cookies.get('gptImageAccess')?.value;
if (verifyAccessToken(accessToken, appPassword)) return undefined;
const code = accessToken ? PAGE_PASSWORD_AUTH_ERROR_CODES.invalid : PAGE_PASSWORD_AUTH_ERROR_CODES.missing;
return jsonError(code, '未授权:无效的访问令牌。', 401);
}
function readBodyObject(value: unknown): Record<string, unknown> {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
throw new TypeError('反馈请求体必须是 JSON 对象。');
}
return value as Record<string, unknown>;
}
function readTargets(value: unknown): FeedbackTarget[] {
if (!Array.isArray(value) || value.length === 0 || value.length > MAX_FEEDBACK_TARGETS) {
throw new TypeError('反馈目标数量无效。');
}
const targets = new Map<string, FeedbackTarget>();
for (const target of value) {
const object = readBodyObject(target);
const targetType = normalizeFeedbackTargetType(object.type ?? object.targetType ?? object.target_type);
if (targetType !== 'page_request') {
throw new TypeError('页面反馈只能写入 page_request 目标。');
}
const targetId = normalizeFeedbackTargetId(object.id ?? object.targetId ?? object.target_id);
targets.set(`${targetType}:${targetId}`, { targetType, targetId });
}
return Array.from(targets.values());
}
function buildRecords(body: Record<string, unknown>, now = new Date()): FeedbackRecord[] {
const value = normalizeFeedbackValue(body.value);
const note = normalizeFeedbackNote(body.note);
const updatedAt = normalizeFeedbackUpdatedAt(body.updatedAt ?? body.updated_at, now);
return readTargets(body.targets).map((target) => ({
...target,
value,
source: 'webui',
updatedAt,
...(note ? { note } : {})
}));
}
export async function PUT(request: NextRequest) {
const authError = verifyFeedbackWriter(request);
if (authError) return authError;
let records: FeedbackRecord[];
try {
records = buildRecords(readBodyObject(await request.json()));
} catch (error) {
const message = error instanceof Error ? error.message : '反馈请求格式无效。';
return jsonError('invalid_feedback_request', message, 400);
}
try {
const store = await getFeedbackStore();
await store.upsertFeedbackBatch(records);
appLogger.info('结果反馈已写入服务端状态。', {
clientRequestId: records[0]?.targetId,
feedbackTargets: records.map((record) => `${record.targetType}:${record.targetId}`),
feedbackValue: records[0]?.value
});
return NextResponse.json({ feedback: records.map(feedbackRecordToResponse) });
} catch (error) {
appLogger.error('结果反馈写入失败。', error);
return jsonError('feedback_persist_failed', '结果反馈写入失败。', 500);
}
}
export async function DELETE(request: NextRequest) {
const authError = verifyFeedbackWriter(request);
if (authError) return authError;
let targets: FeedbackTarget[];
let deletedAt: string | undefined;
try {
const body = readBodyObject(await request.json());
targets = readTargets(body.targets);
deletedAt = normalizeFeedbackDeletedAt(body.deletedAt ?? body.deleted_at);
} catch (error) {
const message = error instanceof Error ? error.message : '反馈删除请求格式无效。';
return jsonError('invalid_feedback_delete_request', message, 400);
}
try {
const store = await getFeedbackStore();
const deleted = await store.deleteFeedbackByTargets(targets, deletedAt ? { deletedAt } : undefined);
appLogger.info('结果反馈已从服务端状态删除。', {
feedbackTargets: targets.map((target) => `${target.targetType}:${target.targetId}`),
...(deletedAt ? { feedbackDeletedAt: deletedAt } : {}),
deleted
});
return NextResponse.json({ deleted, targets, ...(deletedAt ? { deleted_at: deletedAt } : {}) });
} catch (error) {
appLogger.error('结果反馈删除失败。', error);
return jsonError('feedback_delete_failed', '结果反馈删除失败。', 500);
}
}
|