站点同步
This commit is contained in:
1 parent
a64be50b73
commit
9cb8552a99
28 files changed
+1683
-18
No files matched your search
@@ -9,6 +9,8 @@ export {
|
||||
startFullExportJob,
|
||||
startFullRestoreJob,
|
||||
startBackendSyncJob,
|
||||
startSiteSyncJob,
|
||||
resumeSiteSyncJob,
|
||||
isBackupRunnerBusy,
|
||||
cancelBackupJob
|
||||
} from './runner'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { randomBytes } from 'node:crypto'
|
||||
import { getDb, getPrimaryAdminUserId } from '../db'
|
||||
import { ensureBackupSchema } from './schema'
|
||||
|
||||
export type BackupJobType = 'full_export' | 'full_restore' | 'backend_sync'
|
||||
export type BackupJobType = 'full_export' | 'full_restore' | 'backend_sync' | 'site_sync'
|
||||
export type BackupJobStatus = 'pending' | 'running' | 'done' | 'failed'
|
||||
|
||||
export interface BackupJob {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { runBackendSyncJob } from './backend-sync'
|
||||
import { runFullExportJob } from './full-export'
|
||||
import { runFullRestoreJob } from './full-restore'
|
||||
import { registerSiteSyncToken, runSiteSyncJob, type SiteSyncOptions } from '../site-sync/runner'
|
||||
import {
|
||||
createBackupJob,
|
||||
getBackupJob,
|
||||
@@ -114,3 +115,55 @@ export function startBackendSyncJob(options: {
|
||||
scheduleBackupJob(job, () => runBackendSyncJob(job.id, options))
|
||||
return getBackupJob(job.id)!
|
||||
}
|
||||
|
||||
export function startSiteSyncJob(
|
||||
options: SiteSyncOptions,
|
||||
token: string,
|
||||
userId?: number | null
|
||||
): BackupJob {
|
||||
claimJobSlot()
|
||||
const job = createBackupJob({
|
||||
type: 'site_sync',
|
||||
userId,
|
||||
message: '等待开始…',
|
||||
payload: {
|
||||
targetBaseUrl: options.targetBaseUrl,
|
||||
conflictMode: options.conflictMode,
|
||||
direction: options.direction
|
||||
}
|
||||
})
|
||||
registerSiteSyncToken(job.id, token)
|
||||
scheduleBackupJob(job, () => runSiteSyncJob(job.id, options))
|
||||
return getBackupJob(job.id)!
|
||||
}
|
||||
|
||||
export function resumeSiteSyncJob(
|
||||
jobId: string,
|
||||
token: string,
|
||||
userId?: number | null
|
||||
): BackupJob {
|
||||
claimJobSlot()
|
||||
const job = getBackupJob(jobId, userId ?? undefined)
|
||||
if (
|
||||
!job
|
||||
|| job.type !== 'site_sync'
|
||||
|| job.status !== 'failed'
|
||||
|| job.payload.resumable !== true
|
||||
) {
|
||||
throw new Error('SITE_SYNC_JOB_NOT_RESUMABLE')
|
||||
}
|
||||
const targetBaseUrl = typeof job.payload.targetBaseUrl === 'string'
|
||||
? job.payload.targetBaseUrl
|
||||
: ''
|
||||
const conflictMode = job.payload.conflictMode === 'skip' ? 'skip' : 'overwrite'
|
||||
const direction = job.payload.direction === 'pull' ? 'pull' : 'push'
|
||||
if (!targetBaseUrl) throw new Error('SITE_SYNC_JOB_NOT_RESUMABLE')
|
||||
|
||||
registerSiteSyncToken(job.id, token)
|
||||
scheduleBackupJob(job, () => runSiteSyncJob(job.id, {
|
||||
targetBaseUrl,
|
||||
conflictMode,
|
||||
direction
|
||||
}))
|
||||
return getBackupJob(job.id)!
|
||||
}
|
||||
@@ -1,11 +1,50 @@
|
||||
import { getDb } from '../db'
|
||||
|
||||
function backupJobsTableSql(): string {
|
||||
const row = getDb().prepare(`
|
||||
SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'backup_jobs'
|
||||
`).get() as { sql?: string } | undefined
|
||||
return row?.sql ?? ''
|
||||
}
|
||||
|
||||
function migrateBackupJobTypes(): void {
|
||||
if (backupJobsTableSql().includes('\'site_sync\'')) return
|
||||
|
||||
const db = getDb()
|
||||
db.exec(`
|
||||
CREATE TABLE backup_jobs_new (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id INTEGER,
|
||||
type TEXT NOT NULL CHECK(type IN ('full_export', 'full_restore', 'backend_sync', 'site_sync')),
|
||||
status TEXT NOT NULL CHECK(status IN ('pending', 'running', 'done', 'failed')),
|
||||
progress INTEGER NOT NULL DEFAULT 0,
|
||||
total INTEGER NOT NULL DEFAULT 0,
|
||||
message TEXT NOT NULL DEFAULT '',
|
||||
payload_json TEXT NOT NULL DEFAULT '{}',
|
||||
output_path TEXT,
|
||||
error TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
finished_at TEXT
|
||||
);
|
||||
INSERT INTO backup_jobs_new
|
||||
(id, user_id, type, status, progress, total, message, payload_json, output_path, error, created_at, finished_at)
|
||||
SELECT id, user_id, type, status, progress, total, message, payload_json, output_path, error, created_at, finished_at
|
||||
FROM backup_jobs;
|
||||
DROP TABLE backup_jobs;
|
||||
ALTER TABLE backup_jobs_new RENAME TO backup_jobs;
|
||||
CREATE INDEX IF NOT EXISTS idx_backup_jobs_created_at
|
||||
ON backup_jobs(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_backup_jobs_user_created
|
||||
ON backup_jobs(user_id, created_at DESC);
|
||||
`)
|
||||
}
|
||||
|
||||
export function ensureBackupSchema(): void {
|
||||
getDb().exec(`
|
||||
CREATE TABLE IF NOT EXISTS backup_jobs (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id INTEGER,
|
||||
type TEXT NOT NULL CHECK(type IN ('full_export', 'full_restore', 'backend_sync')),
|
||||
type TEXT NOT NULL CHECK(type IN ('full_export', 'full_restore', 'backend_sync', 'site_sync')),
|
||||
status TEXT NOT NULL CHECK(status IN ('pending', 'running', 'done', 'failed')),
|
||||
progress INTEGER NOT NULL DEFAULT 0,
|
||||
total INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -36,4 +75,5 @@ export function ensureBackupSchema(): void {
|
||||
WHERE user_id IS NULL
|
||||
AND EXISTS (SELECT 1 FROM users WHERE role = 'admin');
|
||||
`)
|
||||
migrateBackupJobTypes()
|
||||
}
|
||||
Reference in new issue
Block a user