fix database import, allow sqlite and db format
This commit is contained in:
Binary file not shown.
+15
-5
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user