import { exists, mkdir, readTextFile, writeTextFile, readDir, remove, copyFile, } from "@tauri-apps/plugin-fs"; import { appDataDir, join } from "@tauri-apps/api/path"; import { boardSchema, appSettingsSchema } from "./schemas"; import type { Board, BoardMeta } from "@/types/board"; import type { AppSettings } from "@/types/settings"; // --------------------------------------------------------------------------- // Path helpers // --------------------------------------------------------------------------- async function getBaseDir(): Promise { const base = await appDataDir(); return join(base, "openpylon"); } async function getBoardsDir(): Promise { const base = await getBaseDir(); return join(base, "boards"); } async function getAttachmentsDir(): Promise { const base = await getBaseDir(); return join(base, "attachments"); } async function getSettingsPath(): Promise { const base = await getBaseDir(); return join(base, "settings.json"); } function boardFilePath(boardsDir: string, boardId: string): Promise { return join(boardsDir, `${boardId}.json`); } function boardBackupPath(boardsDir: string, boardId: string): Promise { return join(boardsDir, `${boardId}.backup.json`); } // --------------------------------------------------------------------------- // Directory setup // --------------------------------------------------------------------------- export async function ensureDataDirs(): Promise { const base = await getBaseDir(); if (!(await exists(base))) { await mkdir(base, { recursive: true }); } const boardsDir = await getBoardsDir(); if (!(await exists(boardsDir))) { await mkdir(boardsDir, { recursive: true }); } const attachmentsDir = await getAttachmentsDir(); if (!(await exists(attachmentsDir))) { await mkdir(attachmentsDir, { recursive: true }); } } // --------------------------------------------------------------------------- // Settings // --------------------------------------------------------------------------- export async function loadSettings(): Promise { const settingsPath = await getSettingsPath(); if (!(await exists(settingsPath))) { const defaults = appSettingsSchema.parse({}); return defaults; } try { const raw = await readTextFile(settingsPath); const data = JSON.parse(raw); return appSettingsSchema.parse(data); } catch { // If the file is corrupted, return defaults return appSettingsSchema.parse({}); } } export async function saveSettings(settings: AppSettings): Promise { const settingsPath = await getSettingsPath(); await writeTextFile(settingsPath, JSON.stringify(settings, null, 2)); } // --------------------------------------------------------------------------- // Board listing // --------------------------------------------------------------------------- export async function listBoards(): Promise { const boardsDir = await getBoardsDir(); if (!(await exists(boardsDir))) { return []; } const entries = await readDir(boardsDir); const metas: BoardMeta[] = []; for (const entry of entries) { // DirEntry has `name` property in Tauri v2 if (!entry.name || !entry.name.endsWith(".json") || entry.name.endsWith(".backup.json")) { continue; } try { const filePath = await join(boardsDir, entry.name); const raw = await readTextFile(filePath); const data = JSON.parse(raw); const board = boardSchema.parse(data); metas.push({ id: board.id, title: board.title, color: board.color, cardCount: Object.keys(board.cards).length, columnCount: board.columns.length, updatedAt: board.updatedAt, }); } catch { // Skip corrupted board files continue; } } // Sort by updatedAt descending (most recent first) metas.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)); return metas; } // --------------------------------------------------------------------------- // Board CRUD // --------------------------------------------------------------------------- export async function loadBoard(boardId: string): Promise { const boardsDir = await getBoardsDir(); const filePath = await boardFilePath(boardsDir, boardId); const raw = await readTextFile(filePath); const data = JSON.parse(raw); return boardSchema.parse(data) as Board; } export async function saveBoard(board: Board): Promise { const boardsDir = await getBoardsDir(); const filePath = await boardFilePath(boardsDir, board.id); const backupPath = await boardBackupPath(boardsDir, board.id); // Rotate previous version to backup if (await exists(filePath)) { try { const previous = await readTextFile(filePath); await writeTextFile(backupPath, previous); } catch { // If we can't create a backup, continue saving anyway } } await writeTextFile(filePath, JSON.stringify(board, null, 2)); } export async function deleteBoard(boardId: string): Promise { const boardsDir = await getBoardsDir(); const filePath = await boardFilePath(boardsDir, boardId); const backupPath = await boardBackupPath(boardsDir, boardId); // Remove board file if (await exists(filePath)) { await remove(filePath); } // Remove backup file if (await exists(backupPath)) { await remove(backupPath); } // Remove board attachments directory const attachmentsDir = await getAttachmentsDir(); const boardAttachmentsDir = await join(attachmentsDir, boardId); if (await exists(boardAttachmentsDir)) { await remove(boardAttachmentsDir, { recursive: true }); } } // --------------------------------------------------------------------------- // Backup restoration // --------------------------------------------------------------------------- export async function restoreFromBackup(boardId: string): Promise { const boardsDir = await getBoardsDir(); const backupPath = await boardBackupPath(boardsDir, boardId); const raw = await readTextFile(backupPath); const data = JSON.parse(raw); const board = boardSchema.parse(data) as Board; // Save the restored board as the current version await saveBoard(board); return board; } // --------------------------------------------------------------------------- // Search // --------------------------------------------------------------------------- export interface SearchResult { boardId: string; boardTitle: string; cardId: string; cardTitle: string; matchField: "title" | "description"; } export async function searchAllBoards(query: string): Promise { const boardsDir = await getBoardsDir(); if (!(await exists(boardsDir))) { return []; } const entries = await readDir(boardsDir); const results: SearchResult[] = []; const lowerQuery = query.toLowerCase(); for (const entry of entries) { if (!entry.name || !entry.name.endsWith(".json") || entry.name.endsWith(".backup.json")) { continue; } try { const filePath = await join(boardsDir, entry.name); const raw = await readTextFile(filePath); const data = JSON.parse(raw); const board = boardSchema.parse(data); for (const card of Object.values(board.cards)) { if (card.title.toLowerCase().includes(lowerQuery)) { results.push({ boardId: board.id, boardTitle: board.title, cardId: card.id, cardTitle: card.title, matchField: "title", }); } else if (card.description.toLowerCase().includes(lowerQuery)) { results.push({ boardId: board.id, boardTitle: board.title, cardId: card.id, cardTitle: card.title, matchField: "description", }); } } } catch { continue; } } return results; } // --------------------------------------------------------------------------- // Attachment helpers // --------------------------------------------------------------------------- export async function copyAttachment( boardId: string, sourcePath: string, fileName: string, ): Promise { const attachmentsDir = await getAttachmentsDir(); const boardAttDir = await join(attachmentsDir, boardId); if (!(await exists(boardAttDir))) { await mkdir(boardAttDir, { recursive: true }); } const destPath = await join(boardAttDir, fileName); await copyFile(sourcePath, destPath); return destPath; }