feat: Display a toast notification after library migration, powered by new migration status in the API response.
This commit is contained in:
parent
f11c015f7e
commit
741126c0b4
3 changed files with 33 additions and 9 deletions
|
|
@ -114,8 +114,8 @@ export async function POST(request: NextRequest) {
|
|||
}
|
||||
}
|
||||
|
||||
await ensureDocumentsV1Ready();
|
||||
await ensureAudiobooksV1Ready();
|
||||
const documentsMigrated = await ensureDocumentsV1Ready();
|
||||
const audiobooksMigrated = await ensureAudiobooksV1Ready();
|
||||
const rekey = await rekeyAudiobooksV1(mappings);
|
||||
|
||||
const documentsReady = await isDocumentsV1Ready();
|
||||
|
|
@ -125,6 +125,8 @@ export async function POST(request: NextRequest) {
|
|||
success: true,
|
||||
documentsReady,
|
||||
audiobooksReady,
|
||||
documentsMigrated,
|
||||
audiobooksMigrated,
|
||||
rekey,
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -103,11 +103,31 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
|
|||
|
||||
// Run server-side v1 migrations proactively, since the client may now
|
||||
// reference SHA-based IDs immediately after the Dexie migration.
|
||||
await fetch('/api/migrations/v1', {
|
||||
const response = await fetch('/api/migrations/v1', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ mappings }),
|
||||
}).catch(() => undefined);
|
||||
}).catch(() => null);
|
||||
|
||||
if (response?.ok) {
|
||||
const data = await response.json();
|
||||
const didMigrate =
|
||||
data.documentsMigrated ||
|
||||
data.audiobooksMigrated ||
|
||||
(data.rekey?.renamed ?? 0) > 0 ||
|
||||
(data.rekey?.merged ?? 0) > 0;
|
||||
|
||||
if (didMigrate) {
|
||||
toast.success('Library migration complete', {
|
||||
duration: 5000,
|
||||
icon: '📦',
|
||||
style: {
|
||||
background: 'var(--offbase)',
|
||||
color: 'var(--foreground)',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Startup migrations failed:', error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,18 +101,18 @@ function safeDocumentName(rawName: string, fallback: string): string {
|
|||
return baseName.replaceAll('\u0000', '').slice(0, 240) || fallback;
|
||||
}
|
||||
|
||||
export async function ensureDocumentsV1Ready(): Promise<void> {
|
||||
export async function ensureDocumentsV1Ready(): Promise<boolean> {
|
||||
await mkdir(DOCSTORE_DIR, { recursive: true });
|
||||
await mkdir(DOCUMENTS_V1_DIR, { recursive: true });
|
||||
|
||||
const state = await loadMigrationState();
|
||||
if (state.documentsV1Migrated && !(await hasLegacyDocumentFiles())) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(await hasLegacyDocumentFiles())) {
|
||||
await saveMigrationState({ documentsV1Migrated: true });
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let entries: Array<import('fs').Dirent> = [];
|
||||
|
|
@ -165,6 +165,7 @@ export async function ensureDocumentsV1Ready(): Promise<void> {
|
|||
}
|
||||
|
||||
await saveMigrationState({ documentsV1Migrated: !(await hasLegacyDocumentFiles()) });
|
||||
return true;
|
||||
}
|
||||
|
||||
async function hasLegacyAudiobookDirs(): Promise<boolean> {
|
||||
|
|
@ -269,7 +270,7 @@ async function mergeDirectoryContents(sourceDir: string, targetDir: string): Pro
|
|||
return { moved, skipped };
|
||||
}
|
||||
|
||||
export async function ensureAudiobooksV1Ready(): Promise<void> {
|
||||
export async function ensureAudiobooksV1Ready(): Promise<boolean> {
|
||||
await mkdir(DOCSTORE_DIR, { recursive: true });
|
||||
await mkdir(AUDIOBOOKS_V1_DIR, { recursive: true });
|
||||
|
||||
|
|
@ -284,7 +285,7 @@ export async function ensureAudiobooksV1Ready(): Promise<void> {
|
|||
if (hasExtraKeys) {
|
||||
await saveMigrationState({ audiobooksV1Migrated: true });
|
||||
}
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let entries: Array<import('fs').Dirent> = [];
|
||||
|
|
@ -333,6 +334,7 @@ export async function ensureAudiobooksV1Ready(): Promise<void> {
|
|||
const finalLegacyRemaining = await hasLegacyAudiobookDirs();
|
||||
const finalLegacyChaptersRemaining = await hasLegacyAudiobookChapterLayout();
|
||||
await saveMigrationState({ audiobooksV1Migrated: !finalLegacyRemaining && !finalLegacyChaptersRemaining });
|
||||
return true;
|
||||
}
|
||||
|
||||
type LegacyChapterMeta = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue