server/db/operate/configs.ts
Rama main · réplica de lectura del repositorio original.
// 配置表
'use server'
import { db } from '~/server/lib/db'
import { revalidateConfigCache } from '~/server/lib/cache'
import { normalizeDefaultTheme } from '~/lib/utils/theme'
import type { CustomInfo, R2Info, S3Info, OpenListInfo, VariantStorageInfo } from '~/types'
// The configs table stores everything as text, so boolean values coming from
// the API layer (real `boolean`) need to be serialised back to 'true' / 'false'
// before they hit the raw SQL UPDATE statements below. `toBoolString` accepts
// both real booleans (the new API contract) and pre-serialised strings (any
// remaining legacy call sites) to keep the layer permissive during migration.
const toBoolString = (value: boolean | string | undefined | null): string => {
if (typeof value === 'boolean') return value ? 'true' : 'false'
if (value === 'true' || value === 'false') return value
return 'false'
}
/**
* 更新 S3 配置
* @param configs S3 配置(camelCase, see {@link S3Info})
*/
export async function updateS3Config(configs: S3Info) {
const forcePathStyle = toBoolString(configs.forcePathStyle)
const s3Cdn = toBoolString(configs.s3Cdn)
const s3DirectDownload = toBoolString(configs.s3DirectDownload)
const result = await db.$executeRaw`
UPDATE "public"."configs"
SET config_value = CASE
WHEN config_key = 'accesskey_id' THEN ${configs.accesskeyId}
WHEN config_key = 'accesskey_secret' THEN ${configs.accesskeySecret}
WHEN config_key = 'region' THEN ${configs.region}
WHEN config_key = 'endpoint' THEN ${configs.endpoint}
WHEN config_key = 'bucket' THEN ${configs.bucket}
WHEN config_key = 'storage_folder' THEN ${configs.storageFolder}
WHEN config_key = 'force_path_style' THEN ${forcePathStyle}
WHEN config_key = 's3_cdn' THEN ${s3Cdn}
WHEN config_key = 's3_cdn_url' THEN ${configs.s3CdnUrl}
WHEN config_key = 's3_direct_download' THEN ${s3DirectDownload}
ELSE 'N&A'
END,
updated_at = NOW()
WHERE config_key IN ('accesskey_id', 'accesskey_secret', 'region', 'endpoint', 'bucket', 'storage_folder', 'force_path_style', 's3_cdn', 's3_cdn_url', 's3_direct_download');
`
revalidateConfigCache()
return result
}
/**
* 更新 R2 配置
* @param configs R2 配置(camelCase, see {@link R2Info})
*/
export async function updateR2Config(configs: R2Info) {
const r2DirectDownload = toBoolString(configs.r2DirectDownload)
const result = await db.$executeRaw`
UPDATE "public"."configs"
SET config_value = CASE
WHEN config_key = 'r2_accesskey_id' THEN ${configs.r2AccesskeyId}
WHEN config_key = 'r2_accesskey_secret' THEN ${configs.r2AccesskeySecret}
WHEN config_key = 'r2_account_id' THEN ${configs.r2AccountId}
WHEN config_key = 'r2_bucket' THEN ${configs.r2Bucket}
WHEN config_key = 'r2_storage_folder' THEN ${configs.r2StorageFolder}
WHEN config_key = 'r2_public_domain' THEN ${configs.r2PublicDomain}
WHEN config_key = 'r2_direct_download' THEN ${r2DirectDownload}
ELSE 'N&A'
END,
updated_at = NOW()
WHERE config_key IN ('r2_accesskey_id', 'r2_accesskey_secret', 'r2_account_id', 'r2_bucket', 'r2_storage_folder', 'r2_public_domain', 'r2_direct_download');
`
revalidateConfigCache()
return result
}
/**
* 更新 Open List 配置
* @param configs Open List 配置(camelCase, see {@link OpenListInfo})
*/
export async function updateOpenListConfig(configs: OpenListInfo) {
const result = await db.$executeRaw`
UPDATE "public"."configs"
SET config_value = CASE
WHEN config_key = 'open_list_url' THEN ${configs.openListUrl}
WHEN config_key = 'open_list_token' THEN ${configs.openListToken}
ELSE 'N&A'
END,
updated_at = NOW()
WHERE config_key IN ('open_list_url', 'open_list_token');
`
revalidateConfigCache()
return result
}
/**
* 更新变体存储后端配置(预处理管线上传变体的目标:'' | 's3' | 'r2')。
* upsert 以兼容尚未重新 seed 出 `variant_storage` 行的旧实例。
*/
export async function updateVariantStorageConfig(payload: VariantStorageInfo) {
const value = payload.variantStorage === 's3' || payload.variantStorage === 'r2' ? payload.variantStorage : ''
const result = await db.configs.upsert({
where: { config_key: 'variant_storage' },
update: { config_value: value, updatedAt: new Date() },
create: { config_key: 'variant_storage', config_value: value },
})
revalidateConfigCache()
return result
}
/**
* 更新自定义信息
* @param payload 自定义信息(camelCase API shape, see {@link CustomInfo})
*/
export async function updateCustomInfo(payload: CustomInfo) {
const {
customTitle,
customFaviconUrl,
customAuthor,
rssFeedId,
rssUserId,
customIndexStyle,
customIndexDownloadEnable,
previewMaxWidthLimitSwitch,
previewMaxWidthLimit,
previewQuality,
umamiHost,
umamiAnalytics,
maxUploadFiles,
customIndexOriginEnable,
adminImagesPerPage,
defaultTheme,
} = payload
const normalizedDefaultTheme = normalizeDefaultTheme(defaultTheme)
const updates = [
db.configs.update({ where: { config_key: 'custom_title' }, data: { config_value: customTitle, updatedAt: new Date() } }),
db.configs.update({ where: { config_key: 'custom_favicon_url' }, data: { config_value: customFaviconUrl, updatedAt: new Date() } }),
db.configs.update({ where: { config_key: 'custom_author' }, data: { config_value: customAuthor, updatedAt: new Date() } }),
db.configs.update({ where: { config_key: 'rss_feed_id' }, data: { config_value: rssFeedId, updatedAt: new Date() } }),
db.configs.update({ where: { config_key: 'rss_user_id' }, data: { config_value: rssUserId, updatedAt: new Date() } }),
db.configs.update({ where: { config_key: 'umami_host' }, data: { config_value: umamiHost, updatedAt: new Date() } }),
db.configs.update({ where: { config_key: 'umami_analytics' }, data: { config_value: umamiAnalytics, updatedAt: new Date() } }),
db.configs.update({ where: { config_key: 'custom_index_style' }, data: { config_value: customIndexStyle.toString(), updatedAt: new Date() } }),
db.configs.update({ where: { config_key: 'custom_index_download_enable' }, data: { config_value: customIndexDownloadEnable ? 'true' : 'false', updatedAt: new Date() } }),
db.configs.update({ where: { config_key: 'preview_max_width_limit_switch' }, data: { config_value: previewMaxWidthLimitSwitch ? '1' : '0', updatedAt: new Date() } }),
db.configs.update({ where: { config_key: 'max_upload_files' }, data: { config_value: maxUploadFiles.toString(), updatedAt: new Date() } }),
db.configs.update({ where: { config_key: 'custom_index_origin_enable' }, data: { config_value: customIndexOriginEnable ? 'true' : 'false', updatedAt: new Date() } }),
db.configs.update({ where: { config_key: 'admin_images_per_page' }, data: { config_value: adminImagesPerPage.toString(), updatedAt: new Date() } }),
db.configs.upsert({
where: { config_key: 'default_theme' },
update: { config_value: normalizedDefaultTheme, updatedAt: new Date() },
create: {
config_key: 'default_theme',
config_value: normalizedDefaultTheme,
detail: 'Default theme for users without a saved preference.',
},
}),
]
if (previewMaxWidthLimit > 0) {
updates.push(db.configs.update({ where: { config_key: 'preview_max_width_limit' }, data: { config_value: previewMaxWidthLimit.toString(), updatedAt: new Date() } }))
}
if (previewQuality > 0) {
updates.push(db.configs.update({ where: { config_key: 'preview_quality' }, data: { config_value: previewQuality.toString(), updatedAt: new Date() } }))
}
const result = await db.$transaction(updates)
revalidateConfigCache()
return result
}