Files
api-worker/apps/worker/src/services/settings.ts
T
lsy 83a06ee59e add(checkin): 新增签到功能
- 一键签到写回结果并跳过今日已签站点
- 管理台展示今日签到状态并区分未签到/失败
- 系统设置增加定时签到开关与时间
2026-02-22 17:45:04 +08:00

148 lines
3.9 KiB
TypeScript

import type { D1Database } from "@cloudflare/workers-types";
import { nowIso } from "../utils/time";
const DEFAULT_LOG_RETENTION_DAYS = 30;
const DEFAULT_SESSION_TTL_HOURS = 12;
const DEFAULT_CHECKIN_SCHEDULE_TIME = "00:10";
const RETENTION_KEY = "log_retention_days";
const SESSION_TTL_KEY = "session_ttl_hours";
const ADMIN_PASSWORD_HASH_KEY = "admin_password_hash";
const CHECKIN_SCHEDULE_ENABLED_KEY = "checkin_schedule_enabled";
const CHECKIN_SCHEDULE_TIME_KEY = "checkin_schedule_time";
async function readSetting(
db: D1Database,
key: string,
): Promise<string | null> {
const setting = await db
.prepare("SELECT value FROM settings WHERE key = ?")
.bind(key)
.first<{ value?: string }>();
return setting?.value ? String(setting.value) : null;
}
async function upsertSetting(
db: D1Database,
key: string,
value: string,
): Promise<void> {
await db
.prepare(
"INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at",
)
.bind(key, value, nowIso())
.run();
}
function parsePositiveNumber(value: string | null, fallback: number): number {
if (!value) {
return fallback;
}
const parsed = Number(value);
if (!Number.isNaN(parsed) && parsed > 0) {
return parsed;
}
return fallback;
}
/**
* Returns the log retention days from settings or default fallback.
*/
export async function getRetentionDays(db: D1Database): Promise<number> {
const value = await readSetting(db, RETENTION_KEY);
return parsePositiveNumber(value, DEFAULT_LOG_RETENTION_DAYS);
}
/**
* Updates the log retention days setting.
*/
export async function setRetentionDays(
db: D1Database,
days: number,
): Promise<void> {
const value = Math.max(1, Math.floor(days)).toString();
await upsertSetting(db, RETENTION_KEY, value);
}
/**
* Returns the session TTL hours from settings or default fallback.
*/
export async function getSessionTtlHours(db: D1Database): Promise<number> {
const value = await readSetting(db, SESSION_TTL_KEY);
return parsePositiveNumber(value, DEFAULT_SESSION_TTL_HOURS);
}
/**
* Updates the session TTL hours setting.
*/
export async function setSessionTtlHours(
db: D1Database,
hours: number,
): Promise<void> {
const value = Math.max(1, Math.floor(hours)).toString();
await upsertSetting(db, SESSION_TTL_KEY, value);
}
/**
* Returns the admin password hash.
*/
export async function getAdminPasswordHash(
db: D1Database,
): Promise<string | null> {
return readSetting(db, ADMIN_PASSWORD_HASH_KEY);
}
/**
* Updates the admin password hash.
*/
export async function setAdminPasswordHash(
db: D1Database,
hash: string,
): Promise<void> {
if (!hash) {
return;
}
await upsertSetting(db, ADMIN_PASSWORD_HASH_KEY, hash);
}
/**
* Returns whether the admin password is set.
*/
export async function isAdminPasswordSet(db: D1Database): Promise<boolean> {
const hash = await getAdminPasswordHash(db);
return Boolean(hash);
}
export async function getCheckinSchedule(db: D1Database): Promise<{
enabled: boolean;
time: string;
}> {
const enabledRaw = await readSetting(db, CHECKIN_SCHEDULE_ENABLED_KEY);
const timeRaw = await readSetting(db, CHECKIN_SCHEDULE_TIME_KEY);
const enabled = enabledRaw ? enabledRaw === "true" : false;
const time = timeRaw && timeRaw.length > 0 ? timeRaw : DEFAULT_CHECKIN_SCHEDULE_TIME;
return { enabled, time };
}
export async function setCheckinSchedule(
db: D1Database,
input: { enabled: boolean; time: string },
): Promise<void> {
await upsertSetting(db, CHECKIN_SCHEDULE_ENABLED_KEY, String(input.enabled));
await upsertSetting(db, CHECKIN_SCHEDULE_TIME_KEY, input.time);
}
/**
* Loads generic settings as a key/value map.
*/
export async function listSettings(
db: D1Database,
): Promise<Record<string, string>> {
const result = await db.prepare("SELECT key, value FROM settings").all();
const map: Record<string, string> = {};
for (const row of result.results ?? []) {
map[String(row.key)] = String(row.value);
}
return map;
}