diff --git a/backend/prisma/dev.db.backup b/backend/prisma/dev.db.backup new file mode 100644 index 0000000..f654238 Binary files /dev/null and b/backend/prisma/dev.db.backup differ diff --git a/backend/src/index.ts b/backend/src/index.ts index 169ebe4..cea22bf 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -99,9 +99,14 @@ const upload = multer({ files: 1, // Only one file per upload }, fileFilter: (req, file, cb) => { - // Only allow .db files for SQLite imports - if (file.fieldname === "db" && !file.originalname.endsWith(".db")) { - return cb(new Error("Only .db files are allowed")); + // Only allow SQLite database extensions for database imports + if (file.fieldname === "db") { + const isSqliteDb = + file.originalname.endsWith(".db") || + file.originalname.endsWith(".sqlite"); + if (!isSqliteDb) { + return cb(new Error("Only .db or .sqlite files are allowed")); + } } cb(null, true); }, @@ -775,9 +780,14 @@ app.delete("/collections/:id", async (req, res) => { // --- Export/Import Endpoints --- -// GET /export - Export SQLite database +// GET /export - Export SQLite database (supports .sqlite and .db extensions) app.get("/export", async (req, res) => { try { + const formatParam = + typeof req.query.format === "string" + ? req.query.format.toLowerCase() + : undefined; + const extension = formatParam === "db" ? "db" : "sqlite"; const dbPath = path.resolve(__dirname, "../prisma/dev.db"); try { @@ -791,7 +801,7 @@ app.get("/export", async (req, res) => { "Content-Disposition", `attachment; filename="excalidash-db-${ new Date().toISOString().split("T")[0] - }.sqlite"` + }.${extension}"` ); const fileStream = fs.createReadStream(dbPath); diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 7b62582..314d0c3 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -3,7 +3,7 @@ import { Layout } from '../components/Layout'; import { useNavigate } from 'react-router-dom'; import * as api from '../api'; import type { Collection } from '../types'; -import { Database, FileJson, Upload, Moon, Sun, Info } from 'lucide-react'; +import { Database, FileJson, Upload, Moon, Sun, Info, HardDrive } from 'lucide-react'; import { ConfirmModal } from '../components/ConfirmModal'; import { importDrawings } from '../utils/importUtils'; import { useTheme } from '../context/ThemeContext'; @@ -94,7 +94,7 @@ export const Settings: React.FC = () => { - {/* Export SQLite */} + {/* Export SQLite (.sqlite) */} window.location.href = `${api.API_URL}/export`} className="flex flex-col items-center justify-center gap-4 p-8 bg-white dark:bg-neutral-900 border-2 border-black dark:border-neutral-700 rounded-2xl shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] dark:shadow-[4px_4px_0px_0px_rgba(255,255,255,0.2)] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] dark:hover:shadow-[6px_6px_0px_0px_rgba(255,255,255,0.2)] hover:-translate-y-1 transition-all duration-200 group" @@ -103,11 +103,25 @@ export const Settings: React.FC = () => { - Export Data (SQLite) + Export Data (.sqlite) Download full database backup + {/* Export SQLite (.db) */} + window.location.href = `${api.API_URL}/export?format=db`} + className="flex flex-col items-center justify-center gap-4 p-8 bg-white dark:bg-neutral-900 border-2 border-black dark:border-neutral-700 rounded-2xl shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] dark:shadow-[4px_4px_0px_0px_rgba(255,255,255,0.2)] hover:shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] dark:hover:shadow-[6px_6px_0px_0px_rgba(255,255,255,0.2)] hover:-translate-y-1 transition-all duration-200 group" + > + + + + + Export Data (.db) + Download Prisma .db format + + + {/* Export JSON */} window.location.href = `${api.API_URL}/export/json`} @@ -127,16 +141,16 @@ export const Settings: React.FC = () => { { const files = Array.from(e.target.files || []); if (files.length === 0) return; - // Handle SQLite Import - const sqliteFile = files.find(f => f.name.endsWith('.sqlite')); - if (sqliteFile) { + // Handle Database Import (.sqlite or .db) + const databaseFile = files.find(f => f.name.endsWith('.sqlite') || f.name.endsWith('.db')); + if (databaseFile) { if (files.length > 1) { setImportError({ isOpen: true, message: 'Please import database files separately from other files.' }); e.target.value = ''; @@ -144,7 +158,7 @@ export const Settings: React.FC = () => { } const formData = new FormData(); - formData.append('db', sqliteFile); + formData.append('db', databaseFile); try { const res = await fetch(`${api.API_URL}/import/sqlite/verify`, { @@ -159,7 +173,7 @@ export const Settings: React.FC = () => { return; } - setImportConfirmation({ isOpen: true, file: sqliteFile }); + setImportConfirmation({ isOpen: true, file: databaseFile }); } catch (err) { console.error('Verification failed:', err); setImportError({ isOpen: true, message: 'Failed to verify database file.' }); @@ -200,7 +214,7 @@ export const Settings: React.FC = () => { Import Data - Import SQLite or Drawings + Import Database or Drawings
Download full database backup
Download Prisma .db format
Import SQLite or Drawings
Import Database or Drawings