migrate mobile with new endpoints
This commit is contained in:
@@ -4,7 +4,9 @@ import { eq, like, or, and, desc, asc, sql, isNull } from 'drizzle-orm';
|
||||
import { files, fileTags, deletedFiles } from './schema';
|
||||
import type { Tag } from '../../types';
|
||||
|
||||
const DB_NAME = 'vaultdrop.db';
|
||||
const DB_NAME = 'vaultdrop-v3.db';
|
||||
const SCHEMA_VERSION_KEY = 'schema_version';
|
||||
const SCHEMA_VERSION = 3;
|
||||
|
||||
let _db: ReturnType<typeof drizzle> | null = null;
|
||||
let _sqliteDb: SQLite.SQLiteDatabase | null = null;
|
||||
@@ -14,9 +16,41 @@ export function initDB() {
|
||||
_sqliteDb = SQLite.openDatabaseSync(DB_NAME);
|
||||
_sqliteDb.execSync('PRAGMA journal_mode = WAL;');
|
||||
_sqliteDb.execSync('PRAGMA foreign_keys = ON;');
|
||||
_db = drizzle(_sqliteDb);
|
||||
|
||||
_sqliteDb.execSync(`
|
||||
const existingVersion = _sqliteDb.getFirstSync<{ version: number }>(
|
||||
`SELECT name as version FROM sqlite_master WHERE type='table' AND name='schema_version'`
|
||||
);
|
||||
|
||||
if (!existingVersion) {
|
||||
_sqliteDb.execSync(`CREATE TABLE schema_version (version INTEGER PRIMARY KEY);`);
|
||||
}
|
||||
|
||||
const versionRow = _sqliteDb.getFirstSync<{ version: number }>(
|
||||
`SELECT version FROM schema_version ORDER BY version DESC LIMIT 1`
|
||||
);
|
||||
const currentVersion = versionRow?.version ?? 0;
|
||||
|
||||
if (currentVersion < SCHEMA_VERSION) {
|
||||
dropAllTables(_sqliteDb);
|
||||
createSchema(_sqliteDb);
|
||||
_sqliteDb.execSync(`INSERT INTO schema_version (version) VALUES (${SCHEMA_VERSION});`);
|
||||
}
|
||||
|
||||
_db = drizzle(_sqliteDb);
|
||||
return _db;
|
||||
}
|
||||
|
||||
function dropAllTables(db: SQLite.SQLiteDatabase) {
|
||||
db.execSync(`DROP TABLE IF EXISTS file_tags;`);
|
||||
db.execSync(`DROP TABLE IF EXISTS files;`);
|
||||
db.execSync(`DROP TABLE IF EXISTS deleted_files;`);
|
||||
db.execSync(`DROP TABLE IF EXISTS device_info;`);
|
||||
db.execSync(`DROP TABLE IF EXISTS resources_fts;`);
|
||||
db.execSync(`DROP TABLE IF EXISTS schema_version;`);
|
||||
}
|
||||
|
||||
function createSchema(db: SQLite.SQLiteDatabase) {
|
||||
db.execSync(`
|
||||
CREATE TABLE IF NOT EXISTS files (
|
||||
id TEXT PRIMARY KEY,
|
||||
backend_id TEXT,
|
||||
@@ -26,33 +60,75 @@ export function initDB() {
|
||||
source TEXT NOT NULL DEFAULT 'cloud',
|
||||
local_uri TEXT,
|
||||
sync_status TEXT NOT NULL DEFAULT 'cloud',
|
||||
parent_file_id TEXT,
|
||||
parent_resource_id TEXT,
|
||||
is_folder INTEGER NOT NULL DEFAULT 0,
|
||||
ocr_text TEXT,
|
||||
thumbnail_url TEXT,
|
||||
owner_id TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
last_synced_at TEXT
|
||||
);
|
||||
`);
|
||||
db.execSync(`CREATE INDEX IF NOT EXISTS idx_files_backend_id ON files(backend_id);`);
|
||||
db.execSync(`CREATE INDEX IF NOT EXISTS idx_files_parent_resource_id ON files(parent_resource_id);`);
|
||||
db.execSync(`CREATE INDEX IF NOT EXISTS idx_files_source ON files(source);`);
|
||||
db.execSync(`CREATE INDEX IF NOT EXISTS idx_files_is_folder ON files(is_folder);`);
|
||||
db.execSync(`CREATE INDEX IF NOT EXISTS idx_files_sync_status ON files(sync_status);`);
|
||||
db.execSync(`CREATE INDEX IF NOT EXISTS idx_files_owner_id ON files(owner_id);`);
|
||||
db.execSync(`CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY);`);
|
||||
|
||||
db.execSync(`
|
||||
CREATE TABLE IF NOT EXISTS file_tags (
|
||||
id TEXT PRIMARY KEY,
|
||||
file_id TEXT NOT NULL REFERENCES files(id) ON DELETE CASCADE,
|
||||
tag_name TEXT NOT NULL,
|
||||
tag_type TEXT NOT NULL DEFAULT 'none'
|
||||
tag_name TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_files_backend_id ON files(backend_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_files_parent_id ON files(parent_file_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_files_source ON files(source);
|
||||
CREATE INDEX IF NOT EXISTS idx_files_is_folder ON files(is_folder);
|
||||
CREATE INDEX IF NOT EXISTS idx_files_sync_status ON files(sync_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_file_tags_file_id ON file_tags(file_id);
|
||||
`);
|
||||
db.execSync(`CREATE INDEX IF NOT EXISTS idx_file_tags_file_id ON file_tags(file_id);`);
|
||||
|
||||
db.execSync(`
|
||||
CREATE TABLE IF NOT EXISTS deleted_files (
|
||||
id TEXT PRIMARY KEY,
|
||||
deleted_at TEXT NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
return _db;
|
||||
db.execSync(`
|
||||
CREATE TABLE IF NOT EXISTS device_info (
|
||||
id TEXT PRIMARY KEY,
|
||||
server_id TEXT,
|
||||
device_name TEXT NOT NULL DEFAULT '',
|
||||
platform TEXT NOT NULL DEFAULT '',
|
||||
registered_at TEXT
|
||||
);
|
||||
`);
|
||||
|
||||
db.execSync(`
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS resources_fts USING fts5(
|
||||
name,
|
||||
ocr_text,
|
||||
content='files',
|
||||
content_rowid='rowid'
|
||||
);
|
||||
`);
|
||||
|
||||
db.execSync(`
|
||||
CREATE TRIGGER IF NOT EXISTS resources_fts_insert AFTER INSERT ON files BEGIN
|
||||
INSERT INTO resources_fts(rowid, name, ocr_text) VALUES (new.rowid, new.name, new.ocr_text);
|
||||
END;
|
||||
`);
|
||||
db.execSync(`
|
||||
CREATE TRIGGER IF NOT EXISTS resources_fts_delete AFTER DELETE ON files BEGIN
|
||||
INSERT INTO resources_fts(resources_fts, rowid, name, ocr_text) VALUES('delete', old.rowid, old.name, old.ocr_text);
|
||||
END;
|
||||
`);
|
||||
db.execSync(`
|
||||
CREATE TRIGGER IF NOT EXISTS resources_fts_update AFTER UPDATE ON files BEGIN
|
||||
INSERT INTO resources_fts(resources_fts, rowid, name, ocr_text) VALUES('delete', old.rowid, old.name, old.ocr_text);
|
||||
INSERT INTO resources_fts(rowid, name, ocr_text) VALUES (new.rowid, new.name, new.ocr_text);
|
||||
END;
|
||||
`);
|
||||
}
|
||||
|
||||
function getDb() {
|
||||
@@ -69,18 +145,35 @@ export type FileRecord = {
|
||||
source: string;
|
||||
localUri: string | null;
|
||||
syncStatus: string;
|
||||
parentFileId: string | null;
|
||||
parentResourceId: string | null;
|
||||
isFolder: number;
|
||||
ocrText: string | null;
|
||||
thumbnailUrl: string | null;
|
||||
ownerId: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
lastSyncedAt: string | null;
|
||||
tags?: Tag[];
|
||||
};
|
||||
|
||||
type FileRow = typeof files.$inferSelect;
|
||||
type TagRow = typeof fileTags.$inferSelect;
|
||||
type FileRow = {
|
||||
id: string;
|
||||
backendId: string | null;
|
||||
name: string;
|
||||
mimeType: string;
|
||||
size: number;
|
||||
source: string;
|
||||
localUri: string | null;
|
||||
syncStatus: string;
|
||||
parentResourceId: string | null;
|
||||
isFolder: number;
|
||||
ocrText: string | null;
|
||||
thumbnailUrl: string | null;
|
||||
ownerId: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
lastSyncedAt: string | null;
|
||||
};
|
||||
|
||||
function rowToRecord(row: FileRow, tags?: Tag[]): FileRecord {
|
||||
return {
|
||||
@@ -92,10 +185,11 @@ function rowToRecord(row: FileRow, tags?: Tag[]): FileRecord {
|
||||
source: row.source,
|
||||
localUri: row.localUri,
|
||||
syncStatus: row.syncStatus,
|
||||
parentFileId: row.parentFileId,
|
||||
parentResourceId: row.parentResourceId,
|
||||
isFolder: row.isFolder,
|
||||
ocrText: row.ocrText,
|
||||
thumbnailUrl: row.thumbnailUrl,
|
||||
ownerId: row.ownerId,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
lastSyncedAt: row.lastSyncedAt,
|
||||
@@ -106,7 +200,7 @@ function rowToRecord(row: FileRow, tags?: Tag[]): FileRecord {
|
||||
function getTagsForFile(fileId: string): Tag[] {
|
||||
const d = getDb();
|
||||
const rows = d.select().from(fileTags).where(eq(fileTags.fileId, fileId)).all();
|
||||
return rows.map((r) => ({ id: r.tagName, tag_name: r.tagName, tag_type: r.tagType }));
|
||||
return rows.map((r) => ({ id: r.tagName, tag_name: r.tagName }));
|
||||
}
|
||||
|
||||
function setTagsForFile(fileId: string, tags: Tag[]) {
|
||||
@@ -118,7 +212,6 @@ function setTagsForFile(fileId: string, tags: Tag[]) {
|
||||
id: `${fileId}_${t.id || t.tag_name}`,
|
||||
fileId,
|
||||
tagName: t.tag_name,
|
||||
tagType: t.tag_type,
|
||||
})),
|
||||
).run();
|
||||
}
|
||||
@@ -134,10 +227,11 @@ function upsertRow(file: FileRecord) {
|
||||
source: file.source,
|
||||
localUri: file.localUri,
|
||||
syncStatus: file.syncStatus,
|
||||
parentFileId: file.parentFileId,
|
||||
parentResourceId: file.parentResourceId,
|
||||
isFolder: file.isFolder,
|
||||
ocrText: file.ocrText,
|
||||
thumbnailUrl: file.thumbnailUrl,
|
||||
ownerId: file.ownerId,
|
||||
createdAt: file.createdAt,
|
||||
updatedAt: file.updatedAt,
|
||||
lastSyncedAt: file.lastSyncedAt,
|
||||
@@ -151,10 +245,11 @@ function upsertRow(file: FileRecord) {
|
||||
source: file.source,
|
||||
localUri: file.localUri,
|
||||
syncStatus: file.syncStatus,
|
||||
parentFileId: file.parentFileId,
|
||||
parentResourceId: file.parentResourceId,
|
||||
isFolder: file.isFolder,
|
||||
ocrText: file.ocrText,
|
||||
thumbnailUrl: file.thumbnailUrl,
|
||||
ownerId: file.ownerId,
|
||||
updatedAt: file.updatedAt,
|
||||
lastSyncedAt: file.lastSyncedAt,
|
||||
},
|
||||
@@ -179,14 +274,14 @@ export const fileStore = {
|
||||
|
||||
getById(id: string): FileRecord | null {
|
||||
const d = getDb();
|
||||
const row = d.select().from(files).where(eq(files.id, id)).get();
|
||||
const row = d.select().from(files).where(eq(files.id, id)).get() as FileRow | undefined;
|
||||
if (!row) return null;
|
||||
return rowToRecord(row, getTagsForFile(id));
|
||||
},
|
||||
|
||||
getByBackendId(backendId: string): FileRecord | null {
|
||||
const d = getDb();
|
||||
const row = d.select().from(files).where(eq(files.backendId, backendId)).get();
|
||||
const row = d.select().from(files).where(eq(files.backendId, backendId)).get() as FileRow | undefined;
|
||||
if (!row) return null;
|
||||
return rowToRecord(row, getTagsForFile(row.id));
|
||||
},
|
||||
@@ -194,18 +289,18 @@ export const fileStore = {
|
||||
getRootFolders(): FileRecord[] {
|
||||
const d = getDb();
|
||||
const rows = d.select().from(files)
|
||||
.where(and(eq(files.isFolder, 1), isNull(files.parentFileId)))
|
||||
.where(and(eq(files.isFolder, 1), isNull(files.parentResourceId)))
|
||||
.orderBy(asc(files.name))
|
||||
.all();
|
||||
.all() as FileRow[];
|
||||
return rows.map((r) => rowToRecord(r, getTagsForFile(r.id)));
|
||||
},
|
||||
|
||||
getChildrenByParent(parentId: string): FileRecord[] {
|
||||
const d = getDb();
|
||||
const rows = d.select().from(files)
|
||||
.where(eq(files.parentFileId, parentId))
|
||||
.where(eq(files.parentResourceId, parentId))
|
||||
.orderBy(desc(files.isFolder), desc(files.createdAt))
|
||||
.all();
|
||||
.all() as FileRow[];
|
||||
return rows.map((r) => rowToRecord(r, getTagsForFile(r.id)));
|
||||
},
|
||||
|
||||
@@ -215,16 +310,16 @@ export const fileStore = {
|
||||
|
||||
const countRow = d.select({ count: sql<number>`count(*)` })
|
||||
.from(files)
|
||||
.where(and(isNull(files.parentFileId), eq(files.isFolder, 0)))
|
||||
.where(and(isNull(files.parentResourceId), eq(files.isFolder, 0)))
|
||||
.get();
|
||||
const total = countRow?.count ?? 0;
|
||||
|
||||
const rows = d.select().from(files)
|
||||
.where(and(isNull(files.parentFileId), eq(files.isFolder, 0)))
|
||||
.where(and(isNull(files.parentResourceId), eq(files.isFolder, 0)))
|
||||
.orderBy(desc(files.createdAt))
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.all();
|
||||
.all() as FileRow[];
|
||||
|
||||
return {
|
||||
files: rows.map((r) => rowToRecord(r, getTagsForFile(r.id))),
|
||||
@@ -237,7 +332,29 @@ export const fileStore = {
|
||||
const rows = d.select().from(files)
|
||||
.where(eq(files.isFolder, 1))
|
||||
.orderBy(asc(files.name))
|
||||
.all();
|
||||
.all() as FileRow[];
|
||||
return rows.map((r) => rowToRecord(r, getTagsForFile(r.id)));
|
||||
},
|
||||
|
||||
searchFts(query: string): FileRecord[] {
|
||||
const d = getDb();
|
||||
const sanitized = query.replace(/['"]/g, '').trim();
|
||||
if (!sanitized) return [];
|
||||
const ftsPattern = sanitized.split(/\s+/).map(w => `"${w}"`).join(' OR ');
|
||||
const sqlQuery = `
|
||||
SELECT f.* FROM files f
|
||||
JOIN resources_fts r ON r.rowid = f.rowid
|
||||
WHERE resources_fts MATCH ?
|
||||
ORDER BY rank
|
||||
LIMIT 100
|
||||
`;
|
||||
const sqliteDb = _sqliteDb!;
|
||||
const stmt = sqliteDb.prepareSync(sqlQuery);
|
||||
const result = stmt.executeSync<FileRow>(ftsPattern);
|
||||
const rows: FileRow[] = [];
|
||||
for (const r of result) {
|
||||
rows.push(r as unknown as FileRow);
|
||||
}
|
||||
return rows.map((r) => rowToRecord(r, getTagsForFile(r.id)));
|
||||
},
|
||||
|
||||
@@ -248,7 +365,7 @@ export const fileStore = {
|
||||
.where(or(like(files.name, pattern), like(files.ocrText, pattern)))
|
||||
.orderBy(desc(files.createdAt))
|
||||
.limit(100)
|
||||
.all();
|
||||
.all() as FileRow[];
|
||||
return rows.map((r) => rowToRecord(r, getTagsForFile(r.id)));
|
||||
},
|
||||
|
||||
@@ -262,15 +379,16 @@ export const fileStore = {
|
||||
ocrText?: string;
|
||||
tags?: Tag[];
|
||||
isFolder: boolean;
|
||||
parentFileId?: string;
|
||||
parentResourceId?: string;
|
||||
thumbnailUrl?: string;
|
||||
ownerId?: string;
|
||||
}>) {
|
||||
const d = getDb();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
d.transaction(() => {
|
||||
for (const bf of backendFiles) {
|
||||
const existing = d.select().from(files).where(eq(files.backendId, bf.id)).get();
|
||||
const existing = d.select().from(files).where(eq(files.backendId, bf.id)).get() as FileRow | undefined;
|
||||
|
||||
const source = existing && existing.localUri ? 'synced' : 'cloud';
|
||||
const syncStatus = existing && existing.localUri
|
||||
@@ -286,10 +404,11 @@ export const fileStore = {
|
||||
source,
|
||||
localUri: existing?.localUri ?? null,
|
||||
syncStatus,
|
||||
parentFileId: bf.parentFileId ?? null,
|
||||
parentResourceId: bf.parentResourceId ?? null,
|
||||
isFolder: bf.isFolder ? 1 : 0,
|
||||
ocrText: bf.ocrText ?? null,
|
||||
thumbnailUrl: bf.thumbnailUrl ?? null,
|
||||
ownerId: bf.ownerId ?? null,
|
||||
createdAt: bf.createdAt,
|
||||
updatedAt: bf.updatedAt ?? now,
|
||||
lastSyncedAt: now,
|
||||
@@ -327,10 +446,11 @@ export const fileStore = {
|
||||
source: 'local',
|
||||
localUri: df.uri,
|
||||
syncStatus: 'local',
|
||||
parentFileId: df.folderId ?? null,
|
||||
parentResourceId: df.folderId ?? null,
|
||||
isFolder: 0,
|
||||
ocrText: null,
|
||||
thumbnailUrl: null,
|
||||
ownerId: null,
|
||||
createdAt: df.createdAt,
|
||||
updatedAt: now,
|
||||
lastSyncedAt: null,
|
||||
@@ -348,8 +468,9 @@ export const fileStore = {
|
||||
if (updates.source !== undefined) setFields.source = updates.source;
|
||||
if (updates.thumbnailUrl !== undefined) setFields.thumbnailUrl = updates.thumbnailUrl;
|
||||
if (updates.ocrText !== undefined) setFields.ocrText = updates.ocrText;
|
||||
if (updates.parentFileId !== undefined) setFields.parentFileId = updates.parentFileId;
|
||||
if (updates.parentResourceId !== undefined) setFields.parentResourceId = updates.parentResourceId;
|
||||
if (updates.name !== undefined) setFields.name = updates.name;
|
||||
if (updates.ownerId !== undefined) setFields.ownerId = updates.ownerId;
|
||||
setFields.updatedAt = new Date().toISOString();
|
||||
|
||||
d.update(files).set(setFields).where(eq(files.id, id)).run();
|
||||
@@ -390,7 +511,7 @@ export const fileStore = {
|
||||
|
||||
deleteByBackendId(backendId: string) {
|
||||
const d = getDb();
|
||||
const row = d.select().from(files).where(eq(files.backendId, backendId)).get();
|
||||
const row = d.select().from(files).where(eq(files.backendId, backendId)).get() as FileRow | undefined;
|
||||
if (row) this.markDeleted(row.id);
|
||||
d.delete(files).where(eq(files.backendId, backendId)).run();
|
||||
},
|
||||
@@ -411,7 +532,7 @@ export const fileStore = {
|
||||
const d = getDb();
|
||||
const rows = d.select().from(files)
|
||||
.where(or(eq(files.source, 'local'), eq(files.source, 'synced')))
|
||||
.all();
|
||||
.all() as FileRow[];
|
||||
return rows.map((r) => rowToRecord(r, getTagsForFile(r.id)));
|
||||
},
|
||||
|
||||
@@ -419,7 +540,7 @@ export const fileStore = {
|
||||
const d = getDb();
|
||||
const rows = d.select().from(files)
|
||||
.where(eq(files.source, 'synced'))
|
||||
.all();
|
||||
.all() as FileRow[];
|
||||
return rows.map((r) => rowToRecord(r, getTagsForFile(r.id)));
|
||||
},
|
||||
|
||||
@@ -427,7 +548,7 @@ export const fileStore = {
|
||||
const d = getDb();
|
||||
const rows = d.select().from(files)
|
||||
.where(and(eq(files.syncStatus, 'local'), sql`${files.backendId} IS NULL`))
|
||||
.all();
|
||||
.all() as FileRow[];
|
||||
return rows.map((r) => rowToRecord(r, getTagsForFile(r.id)));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,112 +1,5 @@
|
||||
import { createMMKV } from 'react-native-mmkv';
|
||||
import { fileStore, FileRecord } from './index';
|
||||
import type { Tag, SyncStatus } from '../../types';
|
||||
|
||||
const metadataStorage = createMMKV({ id: 'vaultdrop-metadata' });
|
||||
const localFilesStorage = createMMKV({ id: 'vaultdrop-local-files' });
|
||||
|
||||
interface LegacyCachedFiles {
|
||||
files: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
mimeType: string;
|
||||
size: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
ocrText?: string;
|
||||
tags?: Tag[];
|
||||
isFolder: boolean;
|
||||
parentFileId?: string;
|
||||
url?: string;
|
||||
thumbnailUrl?: string;
|
||||
}>;
|
||||
page: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
interface LegacyRegistryBlob {
|
||||
entries: Record<string, {
|
||||
id: string;
|
||||
backendFileId?: string;
|
||||
localUri: string;
|
||||
name: string;
|
||||
mimeType: string;
|
||||
size: number;
|
||||
syncStatus: SyncStatus;
|
||||
createdAt: string;
|
||||
tags?: Tag[];
|
||||
folderId?: string;
|
||||
}>;
|
||||
}
|
||||
import { initDB } from './index';
|
||||
|
||||
export function migrateFromLegacy() {
|
||||
const count = fileStore.count();
|
||||
if (count > 0) return;
|
||||
|
||||
try {
|
||||
const rawMetadata = metadataStorage.getString('backend_files_cache');
|
||||
if (rawMetadata) {
|
||||
const cached: LegacyCachedFiles = JSON.parse(rawMetadata);
|
||||
for (const f of cached.files) {
|
||||
fileStore.upsert({
|
||||
id: f.id,
|
||||
backendId: f.id,
|
||||
name: f.name,
|
||||
mimeType: f.mimeType,
|
||||
size: f.size,
|
||||
source: 'cloud',
|
||||
localUri: null,
|
||||
syncStatus: 'cloud',
|
||||
parentFileId: f.parentFileId ?? null,
|
||||
isFolder: f.isFolder ? 1 : 0,
|
||||
ocrText: f.ocrText ?? null,
|
||||
thumbnailUrl: f.thumbnailUrl ?? null,
|
||||
createdAt: f.createdAt,
|
||||
updatedAt: f.updatedAt,
|
||||
lastSyncedAt: new Date().toISOString(),
|
||||
tags: f.tags,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
const rawRegistry = localFilesStorage.getString('local_files_v2');
|
||||
if (rawRegistry) {
|
||||
const blob: LegacyRegistryBlob = JSON.parse(rawRegistry);
|
||||
for (const entry of Object.values(blob.entries)) {
|
||||
const existing = fileStore.getByBackendId(entry.backendFileId ?? '');
|
||||
const source = entry.backendFileId
|
||||
? (entry.syncStatus === 'synced' ? 'synced' : 'cloud')
|
||||
: 'local';
|
||||
|
||||
fileStore.upsert({
|
||||
id: entry.id,
|
||||
backendId: entry.backendFileId ?? null,
|
||||
name: entry.name,
|
||||
mimeType: entry.mimeType,
|
||||
size: entry.size,
|
||||
source,
|
||||
localUri: entry.localUri,
|
||||
syncStatus: entry.syncStatus,
|
||||
parentFileId: entry.folderId ?? null,
|
||||
isFolder: 0,
|
||||
ocrText: null,
|
||||
thumbnailUrl: null,
|
||||
createdAt: entry.createdAt,
|
||||
updatedAt: entry.createdAt,
|
||||
lastSyncedAt: entry.backendFileId ? new Date().toISOString() : null,
|
||||
tags: entry.tags,
|
||||
});
|
||||
|
||||
if (existing && entry.localUri) {
|
||||
fileStore.updatePartial(entry.id, {
|
||||
localUri: entry.localUri,
|
||||
source: 'synced',
|
||||
syncStatus: 'synced',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
initDB();
|
||||
}
|
||||
|
||||
@@ -11,20 +11,22 @@ export const files = sqliteTable(
|
||||
source: text('source').notNull().default('cloud'),
|
||||
localUri: text('local_uri'),
|
||||
syncStatus: text('sync_status').notNull().default('cloud'),
|
||||
parentFileId: text('parent_file_id'),
|
||||
parentResourceId: text('parent_resource_id'),
|
||||
isFolder: integer('is_folder').notNull().default(0),
|
||||
ocrText: text('ocr_text'),
|
||||
thumbnailUrl: text('thumbnail_url'),
|
||||
ownerId: text('owner_id'),
|
||||
createdAt: text('created_at').notNull(),
|
||||
updatedAt: text('updated_at').notNull(),
|
||||
lastSyncedAt: text('last_synced_at'),
|
||||
},
|
||||
(t) => [
|
||||
index('idx_files_backend_id').on(t.backendId),
|
||||
index('idx_files_parent_id').on(t.parentFileId),
|
||||
index('idx_files_parent_resource_id').on(t.parentResourceId),
|
||||
index('idx_files_source').on(t.source),
|
||||
index('idx_files_is_folder').on(t.isFolder),
|
||||
index('idx_files_sync_status').on(t.syncStatus),
|
||||
index('idx_files_owner_id').on(t.ownerId),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -36,7 +38,6 @@ export const fileTags = sqliteTable(
|
||||
.notNull()
|
||||
.references(() => files.id, { onDelete: 'cascade' }),
|
||||
tagName: text('tag_name').notNull(),
|
||||
tagType: text('tag_type').notNull().default('none'),
|
||||
},
|
||||
(t) => [index('idx_file_tags_file_id').on(t.fileId)],
|
||||
);
|
||||
@@ -45,3 +46,11 @@ export const deletedFiles = sqliteTable('deleted_files', {
|
||||
id: text('id').primaryKey(),
|
||||
deletedAt: text('deleted_at').notNull(),
|
||||
});
|
||||
|
||||
export const deviceInfo = sqliteTable('device_info', {
|
||||
id: text('id').primaryKey(),
|
||||
serverId: text('server_id'),
|
||||
deviceName: text('device_name').notNull().default(''),
|
||||
platform: text('platform').notNull().default(''),
|
||||
registeredAt: text('registered_at'),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user