pl / src /api /settings.ts
ghuser1's picture
Upload 2 files
3c49634 verified
Raw
History Blame Contribute Delete
3.76 kB
import { Hono } from 'hono';
import { eq } from 'drizzle-orm';
import { settings } from '../db/schema';
import { backupDatabaseToRclone } from '../backup';
import { hashPassword } from '../password';
import type { Env, Variables } from '../index';
export const settingsApi = new Hono<{ Bindings: Env; Variables: Variables }>();
const DEFAULT_ID = 'default';
async function ensureRow(db: any) {
const row = (await db.select().from(settings).where(eq(settings.id, DEFAULT_ID))).at(0);
if (row) return row;
await db.insert(settings).values({ id: DEFAULT_ID, updatedAt: Date.now() });
return (await db.select().from(settings).where(eq(settings.id, DEFAULT_ID))).at(0);
}
// 读取(API key 不返回明文,只返回是否已配置)
settingsApi.get('/', async (c) => {
const row = await ensureRow(c.var.db);
let customSizes: string[] = [];
try { customSizes = JSON.parse(row.customSizes || '[]'); } catch {}
return c.json({
openaiBaseUrl: row.openaiBaseUrl || '',
openaiApiKeySet: !!row.openaiApiKey,
imageModel: row.imageModel || '',
translateModel: row.translateModel || '',
customSizes,
defaultSize: row.defaultSize || '1024x1024',
accessPasswordSet: !!row.accessPassword,
jobPollInterval: row.jobPollInterval || 3000,
});
});
// 保存(只更新提交的字段;API key 留空表示不变)
settingsApi.put('/', async (c) => {
const db = c.var.db;
await ensureRow(db);
const body = await c.req.json<Record<string, any>>();
const update: Record<string, any> = { updatedAt: Date.now() };
if (body.openaiBaseUrl !== undefined)
update.openaiBaseUrl = String(body.openaiBaseUrl).trim() || 'https://api.openai.com/v1';
if (body.imageModel !== undefined)
update.imageModel = String(body.imageModel).trim() || 'gpt-image-2';
if (body.translateModel !== undefined)
update.translateModel = String(body.translateModel).trim() || 'gpt-4o-mini';
if (body.defaultSize !== undefined) {
const v = String(body.defaultSize).trim().toLowerCase().replace(/×/g, 'x');
if (/^\d{2,5}x\d{2,5}$/.test(v)) update.defaultSize = v;
}
if (body.customSizes !== undefined) {
let arr: string[] = [];
if (Array.isArray(body.customSizes)) arr = body.customSizes;
else if (typeof body.customSizes === 'string') {
arr = body.customSizes.split(/[\s,;\n]+/);
}
arr = arr
.map((s) => String(s).trim().toLowerCase().replace(/×/g, 'x'))
.filter((s) => /^\d{2,5}x\d{2,5}$/.test(s));
// 去重,过滤掉与预设重复的
const presets = new Set(['1024x1024', '1792x1024', '1024x1792']);
arr = Array.from(new Set(arr)).filter((s) => !presets.has(s));
update.customSizes = JSON.stringify(arr);
}
if (body.jobPollInterval !== undefined) {
const n = Number(body.jobPollInterval);
if (Number.isFinite(n)) {
update.jobPollInterval = Math.max(1000, Math.min(30000, Math.round(n)));
}
}
// API key:仅当传入非空字符串时才更新,空串/未传不动
if (body.openaiApiKey && String(body.openaiApiKey).trim())
update.openaiApiKey = String(body.openaiApiKey).trim();
// 访问密码:null 表示清除;空字符串/未传 = 保持不变;其他 = 更新
if (body.accessPassword === null) {
update.accessPassword = '';
} else if (typeof body.accessPassword === 'string' && body.accessPassword.trim()) {
update.accessPassword = hashPassword(body.accessPassword.trim());
}
await db.update(settings).set(update).where(eq(settings.id, DEFAULT_ID));
const backup = await backupDatabaseToRclone();
return c.json({ ok: true, backup });
});
// 内部使用:返回完整设置(含明文 key)
export async function getFullSettings(db: any) {
return await ensureRow(db);
}