refactor(workspace): enforce layered crate boundaries

This commit is contained in:
elky
2026-07-15 23:47:19 +08:00
parent a728c090a9
commit 8616fe6ee2
969 changed files with 40187 additions and 27240 deletions
@@ -180,20 +180,22 @@ onMounted(() => {
async function loadConfig() {
try {
const [moduleStatus, deviceKey, serverUrl, template] = await Promise.all([
const [moduleStatus, configs] = await Promise.all([
modulesApi.getStatus('bark_push'),
adminApi.getSystemConfig(CONFIG_KEYS.device_key),
adminApi.getSystemConfig(CONFIG_KEYS.server_url),
adminApi.getSystemConfig(CONFIG_KEYS.template),
adminApi.getAllSystemConfigs({ cacheTtlMs: 30_000 }),
])
const configsByKey = new Map(configs.map(config => [config.key, config]))
const deviceKey = configsByKey.get(CONFIG_KEYS.device_key)
const serverUrl = configsByKey.get(CONFIG_KEYS.server_url)
const template = configsByKey.get(CONFIG_KEYS.template)
enabled.value = moduleStatus.enabled === true
deviceKeyIsSet.value = deviceKey.is_set === true
deviceKeyIsSet.value = deviceKey?.is_set === true
deviceKeyInput.value = ''
serverUrlInput.value = typeof serverUrl.value === 'string' && serverUrl.value.trim()
serverUrlInput.value = typeof serverUrl?.value === 'string' && serverUrl.value.trim()
? serverUrl.value
: DEFAULT_SERVER_URL
templateInput.value = typeof template.value === 'string' ? template.value : ''
templateInput.value = typeof template?.value === 'string' ? template.value : ''
} catch (err) {
error(parseApiError(err, '加载 Bark 推送配置失败'))
log.error('加载 Bark 推送配置失败:', err)
@@ -532,42 +532,33 @@ onMounted(() => {
async function loadConfig() {
try {
const [
moduleStatus,
emailEnabled,
recipients,
defaultChannel,
items,
serverChanModuleStatus,
serverChanKey,
barkModuleStatus,
barkDeviceKey,
smtpHost,
smtpFromEmail,
] = await Promise.all([
modulesApi.getStatus('important_notification'),
adminApi.getSystemConfig(CONFIG_KEYS.email_enabled),
adminApi.getSystemConfig(CONFIG_KEYS.email_recipients),
adminApi.getSystemConfig(CONFIG_KEYS.default_channel),
adminApi.getSystemConfig(CONFIG_KEYS.items),
modulesApi.getStatus('server_chan_push'),
adminApi.getSystemConfig(CONFIG_KEYS.server_chan_send_key),
modulesApi.getStatus('bark_push'),
adminApi.getSystemConfig(CONFIG_KEYS.bark_device_key),
adminApi.getSystemConfig('smtp_host'),
adminApi.getSystemConfig('smtp_from_email'),
const [moduleStatuses, configs] = await Promise.all([
modulesApi.getAllStatus(),
adminApi.getAllSystemConfigs({ cacheTtlMs: 30_000 }),
])
const moduleStatus = moduleStatuses.important_notification
const serverChanModuleStatus = moduleStatuses.server_chan_push
const barkModuleStatus = moduleStatuses.bark_push
const configsByKey = new Map(configs.map(config => [config.key, config]))
const emailEnabled = configsByKey.get(CONFIG_KEYS.email_enabled)
const recipients = configsByKey.get(CONFIG_KEYS.email_recipients)
const defaultChannel = configsByKey.get(CONFIG_KEYS.default_channel)
const items = configsByKey.get(CONFIG_KEYS.items)
const serverChanKey = configsByKey.get(CONFIG_KEYS.server_chan_send_key)
const barkDeviceKey = configsByKey.get(CONFIG_KEYS.bark_device_key)
const smtpHost = configsByKey.get('smtp_host')
const smtpFromEmail = configsByKey.get('smtp_from_email')
config.value.enabled = moduleStatus.enabled === true
config.value.email_enabled = emailEnabled.value === true
config.value.email_recipients = normalizeRecipients(recipients.value)
config.value.default_channel = normalizeDefaultChannel(defaultChannel.value)
config.value.items = normalizeItems(items.value)
config.value.email_enabled = emailEnabled?.value === true
config.value.email_recipients = normalizeRecipients(recipients?.value)
config.value.default_channel = normalizeDefaultChannel(defaultChannel?.value)
config.value.items = normalizeItems(items?.value)
serverChanStatus.value = serverChanModuleStatus
serverChanKeyIsSet.value = serverChanKey.is_set === true
serverChanKeyIsSet.value = serverChanKey?.is_set === true
barkStatus.value = barkModuleStatus
barkKeyIsSet.value = barkDeviceKey.is_set === true
smtpConfigured.value = isNonEmptyString(smtpHost.value) && isNonEmptyString(smtpFromEmail.value)
barkKeyIsSet.value = barkDeviceKey?.is_set === true
smtpConfigured.value = isNonEmptyString(smtpHost?.value) && isNonEmptyString(smtpFromEmail?.value)
if (!config.value.items.some(item => item.key === testItemKey.value)) {
testItemKey.value = config.value.items[0]?.key || ''
}
@@ -154,16 +154,18 @@ onMounted(() => {
async function loadConfig() {
try {
const [moduleStatus, sendKey, template] = await Promise.all([
const [moduleStatus, configs] = await Promise.all([
modulesApi.getStatus('server_chan_push'),
adminApi.getSystemConfig(CONFIG_KEYS.send_key),
adminApi.getSystemConfig(CONFIG_KEYS.template),
adminApi.getAllSystemConfigs({ cacheTtlMs: 30_000 }),
])
const configsByKey = new Map(configs.map(config => [config.key, config]))
const sendKey = configsByKey.get(CONFIG_KEYS.send_key)
const template = configsByKey.get(CONFIG_KEYS.template)
enabled.value = moduleStatus.enabled === true
sendKeyIsSet.value = sendKey.is_set === true
sendKeyIsSet.value = sendKey?.is_set === true
sendKeyInput.value = ''
templateInput.value = typeof template.value === 'string' ? template.value : ''
templateInput.value = typeof template?.value === 'string' ? template.value : ''
} catch (err) {
error(parseApiError(err, '加载 Server 酱推送配置失败'))
log.error('加载 Server 酱推送配置失败:', err)
@@ -1,8 +1,8 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
const { getSystemConfigMock, updateSystemConfigMock, runS3BackupMock, errorMock, successMock } =
const { getAllSystemConfigsMock, updateSystemConfigMock, runS3BackupMock, errorMock, successMock } =
vi.hoisted(() => ({
getSystemConfigMock: vi.fn(),
getAllSystemConfigsMock: vi.fn(),
updateSystemConfigMock: vi.fn(),
runS3BackupMock: vi.fn(),
errorMock: vi.fn(),
@@ -11,7 +11,7 @@ const { getSystemConfigMock, updateSystemConfigMock, runS3BackupMock, errorMock,
vi.mock('@/api/admin', () => ({
adminApi: {
getSystemConfig: getSystemConfigMock,
getAllSystemConfigs: getAllSystemConfigsMock,
updateSystemConfig: updateSystemConfigMock,
runS3Backup: runS3BackupMock,
},
@@ -28,7 +28,7 @@ import { useS3BackupConfig } from '../composables/useS3BackupConfig'
describe('useS3BackupConfig', () => {
beforeEach(() => {
getSystemConfigMock.mockReset()
getAllSystemConfigsMock.mockReset()
updateSystemConfigMock.mockReset()
runS3BackupMock.mockReset()
errorMock.mockReset()
@@ -36,15 +36,10 @@ describe('useS3BackupConfig', () => {
})
it('loads write-only secret as configured without exposing the value', async () => {
getSystemConfigMock.mockImplementation(async (key: string) => {
if (key === 'backup_s3_secret_access_key') {
return { key, value: null, is_set: true }
}
if (key === 'backup_s3_scope') {
return { key, value: 'data' }
}
return { key, value: null }
})
getAllSystemConfigsMock.mockResolvedValue([
{ key: 'backup_s3_secret_access_key', value: null, is_set: true },
{ key: 'backup_s3_scope', value: 'data' },
])
const backup = useS3BackupConfig()
await backup.loadS3BackupConfig()
@@ -55,12 +50,9 @@ describe('useS3BackupConfig', () => {
})
it('keeps an existing secret when saving with an empty secret field', async () => {
getSystemConfigMock.mockImplementation(async (key: string) => {
if (key === 'backup_s3_secret_access_key') {
return { key, value: null, is_set: true }
}
return { key, value: null }
})
getAllSystemConfigsMock.mockResolvedValue([
{ key: 'backup_s3_secret_access_key', value: null, is_set: true },
])
updateSystemConfigMock.mockResolvedValue({})
const backup = useS3BackupConfig()
@@ -76,15 +68,10 @@ describe('useS3BackupConfig', () => {
it('reloads server state when saving fails before writing a new secret', async () => {
let loadRound = 0
getSystemConfigMock.mockImplementation(async (key: string) => {
if (key === 'backup_s3_secret_access_key') {
return { key, value: null, is_set: false }
}
if (key === 'backup_s3_bucket') {
return { key, value: loadRound === 0 ? 'old-bucket' : 'server-bucket' }
}
return { key, value: null }
})
getAllSystemConfigsMock.mockImplementation(async () => [
{ key: 'backup_s3_secret_access_key', value: null, is_set: false },
{ key: 'backup_s3_bucket', value: loadRound === 0 ? 'old-bucket' : 'server-bucket' },
])
updateSystemConfigMock.mockImplementation(async (key: string) => {
if (key === 'backup_s3_bucket') {
loadRound += 1
@@ -143,38 +143,37 @@ export function useS3BackupConfig() {
loading.value = true
try {
const next = defaultS3BackupConfig()
await Promise.all(CONFIG_KEYS.map(async (key) => {
try {
const response = await adminApi.getSystemConfig(key)
const field = FIELD_BY_CONFIG_KEY[key]
if (field === 'secretAccessKey') {
next.secretAccessKey = ''
next.secretAccessKeyIsSet = !!response.is_set
return
}
if (response.value === null || response.value === undefined) return
if (field === 'enabled' || field === 'pathStyle') {
next[field] = booleanValue(response.value, next[field])
} else if (
field === 'scheduleInterval' ||
field === 'scheduleMinute' ||
field === 'scheduleHour' ||
field === 'scheduleWeekday' ||
field === 'scheduleMonthDay' ||
field === 'retentionCount'
) {
next[field] = numberValue(response.value, next[field])
} else if (field === 'scope') {
next.scope = scopeValue(response.value, next.scope)
} else if (field === 'scheduleUnit') {
next.scheduleUnit = scheduleUnitValue(response.value, next.scheduleUnit)
} else {
next[field] = stringValue(response.value, next[field] as string)
}
} catch {
// 单个配置缺失时使用默认值
const configs = await adminApi.getAllSystemConfigs({ cacheTtlMs: 30_000 })
const configsByKey = new Map(configs.map((item) => [item.key, item]))
for (const key of CONFIG_KEYS) {
const response = configsByKey.get(key)
if (!response) continue
const field = FIELD_BY_CONFIG_KEY[key]
if (field === 'secretAccessKey') {
next.secretAccessKey = ''
next.secretAccessKeyIsSet = !!response.is_set
continue
}
}))
if (response.value === null || response.value === undefined) continue
if (field === 'enabled' || field === 'pathStyle') {
next[field] = booleanValue(response.value, next[field])
} else if (
field === 'scheduleInterval' ||
field === 'scheduleMinute' ||
field === 'scheduleHour' ||
field === 'scheduleWeekday' ||
field === 'scheduleMonthDay' ||
field === 'retentionCount'
) {
next[field] = numberValue(response.value, next[field])
} else if (field === 'scope') {
next.scope = scopeValue(response.value, next.scope)
} else if (field === 'scheduleUnit') {
next.scheduleUnit = scheduleUnitValue(response.value, next.scheduleUnit)
} else {
next[field] = stringValue(response.value, next[field] as string)
}
}
config.value = next
originalConfig.value = cloneConfig(next)
} catch (err) {