fix database import, allow sqlite and db format

This commit is contained in:
Zimeng Xiong
2025-11-23 09:22:01 -08:00
parent 4728ef151c
commit d581eb3e88
3 changed files with 39 additions and 15 deletions
Binary file not shown.
+15 -5
View File
@@ -99,9 +99,14 @@ const upload = multer({
files: 1, // Only one file per upload files: 1, // Only one file per upload
}, },
fileFilter: (req, file, cb) => { fileFilter: (req, file, cb) => {
// Only allow .db files for SQLite imports // Only allow SQLite database extensions for database imports
if (file.fieldname === "db" && !file.originalname.endsWith(".db")) { if (file.fieldname === "db") {
return cb(new Error("Only .db files are allowed")); 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); cb(null, true);
}, },
@@ -775,9 +780,14 @@ app.delete("/collections/:id", async (req, res) => {
// --- Export/Import Endpoints --- // --- Export/Import Endpoints ---
// GET /export - Export SQLite database // GET /export - Export SQLite database (supports .sqlite and .db extensions)
app.get("/export", async (req, res) => { app.get("/export", async (req, res) => {
try { 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"); const dbPath = path.resolve(__dirname, "../prisma/dev.db");
try { try {
@@ -791,7 +801,7 @@ app.get("/export", async (req, res) => {
"Content-Disposition", "Content-Disposition",
`attachment; filename="excalidash-db-${ `attachment; filename="excalidash-db-${
new Date().toISOString().split("T")[0] new Date().toISOString().split("T")[0]
}.sqlite"` }.${extension}"`
); );
const fileStream = fs.createReadStream(dbPath); const fileStream = fs.createReadStream(dbPath);
+24 -10
View File
@@ -3,7 +3,7 @@ import { Layout } from '../components/Layout';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import * as api from '../api'; import * as api from '../api';
import type { Collection } from '../types'; 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 { ConfirmModal } from '../components/ConfirmModal';
import { importDrawings } from '../utils/importUtils'; import { importDrawings } from '../utils/importUtils';
import { useTheme } from '../context/ThemeContext'; import { useTheme } from '../context/ThemeContext';
@@ -94,7 +94,7 @@ export const Settings: React.FC = () => {
</div> </div>
</button> </button>
{/* Export SQLite */} {/* Export SQLite (.sqlite) */}
<button <button
onClick={() => window.location.href = `${api.API_URL}/export`} onClick={() => 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" 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 = () => {
<Database size={32} className="text-indigo-600 dark:text-indigo-400" /> <Database size={32} className="text-indigo-600 dark:text-indigo-400" />
</div> </div>
<div className="text-center"> <div className="text-center">
<h3 className="text-xl font-bold text-slate-900 dark:text-white mb-1">Export Data (SQLite)</h3> <h3 className="text-xl font-bold text-slate-900 dark:text-white mb-1">Export Data (.sqlite)</h3>
<p className="text-sm text-slate-500 dark:text-neutral-400 font-medium">Download full database backup</p> <p className="text-sm text-slate-500 dark:text-neutral-400 font-medium">Download full database backup</p>
</div> </div>
</button> </button>
{/* Export SQLite (.db) */}
<button
onClick={() => 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"
>
<div className="w-16 h-16 bg-blue-50 dark:bg-neutral-800 rounded-2xl flex items-center justify-center border-2 border-blue-100 dark:border-neutral-700 group-hover:border-blue-200 dark:group-hover:border-neutral-600 transition-colors">
<HardDrive size={32} className="text-blue-600 dark:text-blue-400" />
</div>
<div className="text-center">
<h3 className="text-xl font-bold text-slate-900 dark:text-white mb-1">Export Data (.db)</h3>
<p className="text-sm text-slate-500 dark:text-neutral-400 font-medium">Download Prisma .db format</p>
</div>
</button>
{/* Export JSON */} {/* Export JSON */}
<button <button
onClick={() => window.location.href = `${api.API_URL}/export/json`} onClick={() => window.location.href = `${api.API_URL}/export/json`}
@@ -127,16 +141,16 @@ export const Settings: React.FC = () => {
<input <input
type="file" type="file"
multiple multiple
accept=".sqlite,.json,.excalidraw" accept=".sqlite,.db,.json,.excalidraw"
className="hidden" className="hidden"
id="settings-import-db" id="settings-import-db"
onChange={async (e) => { onChange={async (e) => {
const files = Array.from(e.target.files || []); const files = Array.from(e.target.files || []);
if (files.length === 0) return; if (files.length === 0) return;
// Handle SQLite Import // Handle Database Import (.sqlite or .db)
const sqliteFile = files.find(f => f.name.endsWith('.sqlite')); const databaseFile = files.find(f => f.name.endsWith('.sqlite') || f.name.endsWith('.db'));
if (sqliteFile) { if (databaseFile) {
if (files.length > 1) { if (files.length > 1) {
setImportError({ isOpen: true, message: 'Please import database files separately from other files.' }); setImportError({ isOpen: true, message: 'Please import database files separately from other files.' });
e.target.value = ''; e.target.value = '';
@@ -144,7 +158,7 @@ export const Settings: React.FC = () => {
} }
const formData = new FormData(); const formData = new FormData();
formData.append('db', sqliteFile); formData.append('db', databaseFile);
try { try {
const res = await fetch(`${api.API_URL}/import/sqlite/verify`, { const res = await fetch(`${api.API_URL}/import/sqlite/verify`, {
@@ -159,7 +173,7 @@ export const Settings: React.FC = () => {
return; return;
} }
setImportConfirmation({ isOpen: true, file: sqliteFile }); setImportConfirmation({ isOpen: true, file: databaseFile });
} catch (err) { } catch (err) {
console.error('Verification failed:', err); console.error('Verification failed:', err);
setImportError({ isOpen: true, message: 'Failed to verify database file.' }); setImportError({ isOpen: true, message: 'Failed to verify database file.' });
@@ -200,7 +214,7 @@ export const Settings: React.FC = () => {
</div> </div>
<div className="text-center"> <div className="text-center">
<h3 className="text-xl font-bold text-slate-900 dark:text-white mb-1">Import Data</h3> <h3 className="text-xl font-bold text-slate-900 dark:text-white mb-1">Import Data</h3>
<p className="text-sm text-slate-500 dark:text-neutral-400 font-medium">Import SQLite or Drawings</p> <p className="text-sm text-slate-500 dark:text-neutral-400 font-medium">Import Database or Drawings</p>
</div> </div>
</button> </button>