Skip to content

Commit 327c1c4

Browse files
committed
fix: importar backup funciona correctamente + resumen al finalizar
- El modal ya no se cierra solo al importar - Muestra spinner durante el proceso y resumen al terminar (posts importados · categorías · elementos omitidos) - Los errores aparecen dentro del modal con botón Reintentar - Corregido fallo que dejaba la base de datos a medias al importar
1 parent ed023e3 commit 327c1c4

4 files changed

Lines changed: 320 additions & 131 deletions

File tree

src/lib/storage/adapter.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import type { Post, Category } from '../types'
22

3+
export interface ImportResult {
4+
posts: number
5+
categories: number
6+
errors: number
7+
}
8+
39
export interface StorageAdapter {
410
// Posts
511
getPosts(): Promise<Post[]>
@@ -13,6 +19,6 @@ export interface StorageAdapter {
1319
deleteCategory(id: string): Promise<void>
1420

1521
// Backup
16-
exportBackup(): Promise<string> // devuelve JSON string
17-
importBackup(json: string): Promise<void>
22+
exportBackup(): Promise<string> // devuelve JSON string
23+
importBackup(json: string): Promise<ImportResult> // devuelve resumen de lo importado
1824
}

src/lib/storage/dexie.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Dexie, { type Table } from 'dexie'
2-
import type { StorageAdapter } from './adapter'
2+
import type { StorageAdapter, ImportResult } from './adapter'
33
import type { Post, Category } from '../types'
44
import { normalizeBackupPayload } from './backup-normalizer'
55

@@ -88,13 +88,42 @@ export class DexieStorage implements StorageAdapter {
8888
}, null, 2)
8989
}
9090

91-
async importBackup(json: string): Promise<void> {
91+
async importBackup(json: string): Promise<ImportResult> {
9292
const data = normalizeBackupPayload(JSON.parse(json))
93+
94+
let catCount = 0
95+
let postCount = 0
96+
let errors = 0
97+
9398
await this.db.transaction('rw', [this.db.posts, this.db.categories], async () => {
9499
await this.db.posts.clear()
95100
await this.db.categories.clear()
96-
await this.db.posts.bulkAdd(data.posts)
97-
await this.db.categories.bulkAdd(data.categories)
101+
102+
for (const cat of data.categories) {
103+
try {
104+
await this.db.categories.put(cat)
105+
catCount++
106+
} catch {
107+
errors++
108+
}
109+
}
110+
111+
for (const post of data.posts) {
112+
try {
113+
const cleanPost: Post = {
114+
...post,
115+
previewImage: post.previewImage?.startsWith('data:') ? undefined : post.previewImage,
116+
previewVideo: post.previewVideo?.startsWith('data:') ? undefined : post.previewVideo,
117+
media: post.media?.filter(m => !m.url.startsWith('data:')),
118+
}
119+
await this.db.posts.put(cleanPost)
120+
postCount++
121+
} catch {
122+
errors++
123+
}
124+
}
98125
})
126+
127+
return { posts: postCount, categories: catCount, errors }
99128
}
100129
}

src/lib/storage/sqlite.ts

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Database from '@tauri-apps/plugin-sql'
2-
import type { StorageAdapter } from './adapter'
2+
import type { StorageAdapter, ImportResult } from './adapter'
33
import type { Post, Category } from '../types'
44
import { normalizeBackupPayload } from './backup-normalizer'
55

@@ -199,18 +199,55 @@ export class SqliteStorage implements StorageAdapter {
199199
}, null, 2)
200200
}
201201

202-
async importBackup(json: string): Promise<void> {
202+
async importBackup(json: string): Promise<ImportResult> {
203+
// Normalizar y validar los datos ANTES de tocar la base de datos.
204+
// Si esto lanza, la DB no ha sido modificada.
203205
const data = normalizeBackupPayload(JSON.parse(json))
204-
try {
205-
await this.db.execute('BEGIN TRANSACTION')
206-
await this.db.execute('DELETE FROM posts')
207-
await this.db.execute('DELETE FROM categories')
208-
for (const post of data.posts as Post[]) await this.savePost(post)
209-
for (const cat of data.categories as Category[]) await this.saveCategory(cat)
210-
await this.db.execute('COMMIT')
211-
} catch (error) {
212-
try { await this.db.execute('ROLLBACK') } catch { /* ignorar error de rollback */ }
213-
throw error
206+
207+
// IMPORTANTE: @tauri-apps/plugin-sql usa un pool de conexiones SQLite.
208+
// Cada execute() puede ir a una conexión diferente del pool, por lo que
209+
// BEGIN TRANSACTION / COMMIT / ROLLBACK en llamadas separadas NO garantizan
210+
// atomicidad — las operaciones pueden ejecutarse en conexiones distintas.
211+
//
212+
// Estrategia: auto-commit por statement + manejo de errores por-item.
213+
// Las categorías se insertan primero (los posts referencian categoryId).
214+
// Los posts se importan con data URLs eliminadas para evitar payloads
215+
// enormes en el IPC de Tauri (imágenes cacheadas en base64 pueden ser >5MB).
216+
217+
// 1. Borrar datos existentes
218+
await this.db.execute('DELETE FROM posts')
219+
await this.db.execute('DELETE FROM categories')
220+
221+
let catCount = 0
222+
let postCount = 0
223+
let errors = 0
224+
225+
// 2. Insertar categorías primero (pequeñas, rápidas, nunca tienen data URLs)
226+
for (const cat of data.categories) {
227+
try {
228+
await this.saveCategory(cat)
229+
catCount++
230+
} catch {
231+
errors++
232+
}
214233
}
234+
235+
// 3. Insertar posts — eliminar data URLs para evitar fallos IPC por payload grande
236+
for (const post of data.posts) {
237+
try {
238+
const cleanPost: Post = {
239+
...post,
240+
previewImage: post.previewImage?.startsWith('data:') ? undefined : post.previewImage,
241+
previewVideo: post.previewVideo?.startsWith('data:') ? undefined : post.previewVideo,
242+
media: post.media?.filter(m => !m.url.startsWith('data:')),
243+
}
244+
await this.savePost(cleanPost)
245+
postCount++
246+
} catch {
247+
errors++
248+
}
249+
}
250+
251+
return { posts: postCount, categories: catCount, errors }
215252
}
216253
}

0 commit comments

Comments
 (0)