Reshaped onto main after #1039 landed the coercion engine (typedColumnsFor / epochToIso / coerceForTargetEngine) — this PR is now only the policy delta on top of it: - validateManifest: replace the CLI-only allowEngineSwitch opt-in with a direction rule — sqlite → pg allowed (upload UI and CLI alike), pg → sqlite refused with a message naming the supported direction - importFromPicpeak: derive crossEngine from the manifest's engine (absent field = target engine, the exact pre-change behavior), log it, return it; route passes it through - scripts/migrate-sqlite-to-postgres.js: rely on the shared gate, drop the flag - restore card: direction stated in the intro, cross-engine notice after a converting restore; both strings in en.json + de.json; removed the orphaned settings.backup.picpeak locale node (unreferenced, stale copy) - picpeakCrossEngine.test.js: direction policy, epochToIso (ms, seconds, numeric strings), coerceForTargetEngine units, plus PICPEAK_PG_TEST_URL-gated real-Postgres stored-value assertions Co-authored-by: Paul Nothaft <[email protected]>
This commit is contained in:
@@ -16,6 +16,7 @@ interface RestoreResult {
|
||||
tables: number;
|
||||
filesRestored: number;
|
||||
usesExternalMedia: boolean;
|
||||
crossEngine?: boolean;
|
||||
sessionInvalidated?: boolean;
|
||||
}
|
||||
|
||||
@@ -137,7 +138,7 @@ export const PicpeakRestoreCard: React.FC = () => {
|
||||
{t('backup.picpeak.restoreTitle', 'Restore from a .picpeak')}
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-neutral-600 dark:text-neutral-400">
|
||||
{t('backup.picpeak.restoreIntro', 'Upload a .picpeak taken from this or another instance. Same database engine only.')}
|
||||
{t('backup.picpeak.restoreIntro', 'Upload a .picpeak taken from this or another instance. Restoring a SQLite backup onto a PostgreSQL instance is supported (the upgrade path); other engine combinations must match.')}
|
||||
</p>
|
||||
<input ref={fileRef} type="file" accept=".picpeak,application/zip" className="hidden" onChange={onFilePick} />
|
||||
<Button
|
||||
@@ -163,6 +164,11 @@ export const PicpeakRestoreCard: React.FC = () => {
|
||||
files: result.filesRestored,
|
||||
})}
|
||||
</p>
|
||||
{result.crossEngine && (
|
||||
<p className="mt-0.5 text-xs text-green-700 dark:text-green-300">
|
||||
{t('backup.picpeak.crossEngineNote', 'Cross-engine restore: a SQLite backup was converted onto this PostgreSQL instance.')}
|
||||
</p>
|
||||
)}
|
||||
{result.usesExternalMedia && (
|
||||
<p className="mt-2 flex items-start gap-1 text-xs text-amber-800 dark:text-amber-300">
|
||||
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0" />
|
||||
|
||||
@@ -464,6 +464,10 @@
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
"picpeak": {
|
||||
"restoreIntro": "Laden Sie eine .picpeak-Datei von dieser oder einer anderen Instanz hoch. Die Wiederherstellung eines SQLite-Backups auf einer PostgreSQL-Instanz wird unterstützt (Upgrade-Pfad); ansonsten müssen die Datenbank-Engines übereinstimmen.",
|
||||
"crossEngineNote": "Engine-übergreifende Wiederherstellung: Ein SQLite-Backup wurde auf diese PostgreSQL-Instanz übernommen."
|
||||
},
|
||||
"title": "Backup-Verwaltung",
|
||||
"subtitle": "Verwalten Sie System-Backups, konfigurieren Sie automatisierte Backups und stellen Sie vorherige Backups wieder her.",
|
||||
"tabs": {
|
||||
@@ -1951,27 +1955,7 @@
|
||||
"title": "E-Mail-Einstellungen"
|
||||
},
|
||||
"backup": {
|
||||
"title": "Backup",
|
||||
"picpeak": {
|
||||
"title": "Portables Backup (.picpeak)",
|
||||
"intro": "Laden Sie eine einzelne, in sich geschlossene Datei herunter und laden Sie sie auf einer anderen Instanz hoch, um diese zu klonen — komplett im Browser.",
|
||||
"includePhotos": "Original-Galeriefotos einschließen (größere Datei)",
|
||||
"secretsWarning": "Diese Datei enthält Geheimnisse im Klartext (E-Mail-Passwort, Admin-Zugangsdaten, API-Schlüssel). Bewahren Sie sie sicher auf und übertragen Sie sie nur über vertrauenswürdige Kanäle.",
|
||||
"download": ".picpeak herunterladen",
|
||||
"downloadFailed": "Die Backup-Datei konnte nicht erstellt werden.",
|
||||
"restoreTitle": "Aus einer .picpeak wiederherstellen",
|
||||
"restoreIntro": "Laden Sie eine .picpeak von dieser oder einer anderen Instanz hoch. Nur dieselbe Datenbank-Engine.",
|
||||
"chooseFile": ".picpeak-Datei auswählen…",
|
||||
"restoreDone": "Backup wiederhergestellt.",
|
||||
"restoreFailed": "Wiederherstellung fehlgeschlagen.",
|
||||
"restoreSummary": "{{tables}} Tabellen und {{files}} Dateien wiederhergestellt.",
|
||||
"externalMediaNote": "Dieses Backup verweist auf eine externe Medienbibliothek. Stellen Sie sicher, dass das externe Medien-Routing auf dieser Instanz konfiguriert ist.",
|
||||
"externalMediaLink": "Einrichtungsanleitung",
|
||||
"reload": "App neu laden",
|
||||
"confirmTitle": "Die Wiederherstellung löscht alle aktuellen Daten",
|
||||
"confirmBody": "Dies ersetzt ALLE Daten auf dieser Instanz dauerhaft durch das hochgeladene Backup, mit Ausnahme Ihres aktuellen Kontos. Dies kann nicht rückgängig gemacht werden.",
|
||||
"confirmRestore": "Löschen & wiederherstellen"
|
||||
}
|
||||
"title": "Backup"
|
||||
},
|
||||
"branding": {
|
||||
"title": "Branding"
|
||||
|
||||
@@ -1496,27 +1496,7 @@
|
||||
"title": "Email Settings"
|
||||
},
|
||||
"backup": {
|
||||
"title": "Backup",
|
||||
"picpeak": {
|
||||
"title": "Portable backup (.picpeak)",
|
||||
"intro": "Download a single self-contained file, then upload it on another instance to clone this one — all through the browser.",
|
||||
"includePhotos": "Include original gallery photos (larger file)",
|
||||
"secretsWarning": "This file contains secrets in plain text (email password, admin credentials, API keys). Store it securely and only transfer it over trusted channels.",
|
||||
"download": "Download .picpeak",
|
||||
"downloadFailed": "Could not create the backup file.",
|
||||
"restoreTitle": "Restore from a .picpeak",
|
||||
"restoreIntro": "Upload a .picpeak taken from this or another instance. Same database engine only.",
|
||||
"chooseFile": "Choose .picpeak file…",
|
||||
"restoreDone": "Backup restored.",
|
||||
"restoreFailed": "Restore failed.",
|
||||
"restoreSummary": "{{tables}} tables and {{files}} files restored.",
|
||||
"externalMediaNote": "This backup references an external-media library. Make sure external-media routing is configured on this instance.",
|
||||
"externalMediaLink": "Setup guide",
|
||||
"reload": "Reload app",
|
||||
"confirmTitle": "Restore will delete all current data",
|
||||
"confirmBody": "This permanently replaces ALL data on this instance with the uploaded backup, except your current account. This cannot be undone.",
|
||||
"confirmRestore": "Delete & restore"
|
||||
}
|
||||
"title": "Backup"
|
||||
},
|
||||
"branding": {
|
||||
"title": "Branding"
|
||||
@@ -3044,6 +3024,10 @@
|
||||
}
|
||||
},
|
||||
"backup": {
|
||||
"picpeak": {
|
||||
"restoreIntro": "Upload a .picpeak taken from this or another instance. Restoring a SQLite backup onto a PostgreSQL instance is supported (the upgrade path); other engine combinations must match.",
|
||||
"crossEngineNote": "Cross-engine restore: a SQLite backup was converted onto this PostgreSQL instance."
|
||||
},
|
||||
"title": "Backup Management",
|
||||
"subtitle": "Manage system backups, configure automated backups, and restore from previous backups.",
|
||||
"tabs": {
|
||||
|
||||
Reference in New Issue
Block a user