feat: MVP-5 Sprint 1 - Backup/Restore system
- Add backup types and RecallBackup format - Create backup snapshot engine (createBackupSnapshot) - Add IndexedDB storage for local backups - Implement retention policy (max 10, 30-day cleanup) - Add backup validation and restore logic (merge/replace modes) - Add backup restore UI dialog with preview and confirmation - Add unsaved changes guard hook - Integrate backups section in Settings - Add backup endpoint to export-import API
This commit is contained in:
75
src/lib/backup-validator.ts
Normal file
75
src/lib/backup-validator.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { RecallBackup } from '@/types/backup'
|
||||
|
||||
interface ValidationResult {
|
||||
valid: boolean
|
||||
errors: string[]
|
||||
info?: {
|
||||
noteCount: number
|
||||
tagCount: number
|
||||
createdAt: string
|
||||
source: string
|
||||
}
|
||||
}
|
||||
|
||||
export function validateBackup(data: unknown): ValidationResult {
|
||||
const errors: string[] = []
|
||||
|
||||
if (typeof data !== 'object' || data === null) {
|
||||
return { valid: false, errors: ['Backup must be an object'] }
|
||||
}
|
||||
|
||||
const backup = data as Record<string, unknown>
|
||||
|
||||
if (!validateSchemaVersion(backup as unknown as RecallBackup)) {
|
||||
errors.push('Missing or invalid schemaVersion (expected "1.0")')
|
||||
}
|
||||
|
||||
if (!backup.createdAt || typeof backup.createdAt !== 'string') {
|
||||
errors.push('Missing or invalid createdAt field')
|
||||
}
|
||||
|
||||
if (!backup.source || typeof backup.source !== 'string') {
|
||||
errors.push('Missing or invalid source field')
|
||||
}
|
||||
|
||||
if (!backup.metadata || typeof backup.metadata !== 'object') {
|
||||
errors.push('Missing or invalid metadata field')
|
||||
} else {
|
||||
const metadata = backup.metadata as Record<string, unknown>
|
||||
if (typeof metadata.noteCount !== 'number') {
|
||||
errors.push('Missing or invalid metadata.noteCount')
|
||||
}
|
||||
if (typeof metadata.tagCount !== 'number') {
|
||||
errors.push('Missing or invalid metadata.tagCount')
|
||||
}
|
||||
}
|
||||
|
||||
if (!backup.data || typeof backup.data !== 'object') {
|
||||
errors.push('Missing or invalid data field')
|
||||
} else {
|
||||
const data = backup.data as Record<string, unknown>
|
||||
if (!Array.isArray(data.notes)) {
|
||||
errors.push('Missing or invalid data.notes (expected array)')
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
return { valid: false, errors }
|
||||
}
|
||||
|
||||
const metadata = backup.metadata as { noteCount: number; tagCount: number }
|
||||
return {
|
||||
valid: true,
|
||||
errors: [],
|
||||
info: {
|
||||
noteCount: metadata.noteCount,
|
||||
tagCount: metadata.tagCount,
|
||||
createdAt: backup.createdAt as string,
|
||||
source: backup.source as string,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function validateSchemaVersion(backup: RecallBackup): boolean {
|
||||
return backup.schemaVersion === '1.0'
|
||||
}
|
||||
Reference in New Issue
Block a user