fix: realign sqlite path and stabilize client data
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -144,6 +144,10 @@ const config = {
|
|||||||
"fromEnvVar": null,
|
"fromEnvVar": null,
|
||||||
"value": "darwin-arm64",
|
"value": "darwin-arm64",
|
||||||
"native": true
|
"native": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fromEnvVar": null,
|
||||||
|
"value": "linux-musl-arm64-openssl-3.0.x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"previewFeatures": [],
|
"previewFeatures": [],
|
||||||
@@ -169,8 +173,8 @@ const config = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"../src/generated/client\"\n}\n\ndatasource db {\n provider = \"sqlite\"\n url = env(\"DATABASE_URL\")\n}\n\nmodel Collection {\n id String @id @default(uuid())\n name String\n drawings Drawing[]\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Drawing {\n id String @id @default(uuid())\n name String\n elements String // Stored as JSON string\n appState String // Stored as JSON string\n files String @default(\"{}\") // Stored as JSON string\n preview String? // SVG string for thumbnail\n version Int @default(1)\n collectionId String?\n collection Collection? @relation(fields: [collectionId], references: [id])\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n",
|
"inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"../src/generated/client\"\n binaryTargets = [\"native\", \"linux-musl-arm64-openssl-3.0.x\"]\n}\n\ndatasource db {\n provider = \"sqlite\"\n url = env(\"DATABASE_URL\")\n}\n\nmodel Collection {\n id String @id @default(uuid())\n name String\n drawings Drawing[]\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Drawing {\n id String @id @default(uuid())\n name String\n elements String // Stored as JSON string\n appState String // Stored as JSON string\n files String @default(\"{}\") // Stored as JSON string\n preview String? // SVG string for thumbnail\n version Int @default(1)\n collectionId String?\n collection Collection? @relation(fields: [collectionId], references: [id])\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n",
|
||||||
"inlineSchemaHash": "e43b17bb99e7f864b5a0f2c54951015f0a3d998329d0577afda58e7c0dee4919",
|
"inlineSchemaHash": "9864a039193c73ddda01fd51751788fa5729bb0a603a9379a3fa314a4aced64f",
|
||||||
"copyEngine": true
|
"copyEngine": true
|
||||||
}
|
}
|
||||||
config.dirname = '/'
|
config.dirname = '/'
|
||||||
|
|||||||
@@ -145,6 +145,10 @@ const config = {
|
|||||||
"fromEnvVar": null,
|
"fromEnvVar": null,
|
||||||
"value": "darwin-arm64",
|
"value": "darwin-arm64",
|
||||||
"native": true
|
"native": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fromEnvVar": null,
|
||||||
|
"value": "linux-musl-arm64-openssl-3.0.x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"previewFeatures": [],
|
"previewFeatures": [],
|
||||||
@@ -170,8 +174,8 @@ const config = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"../src/generated/client\"\n}\n\ndatasource db {\n provider = \"sqlite\"\n url = env(\"DATABASE_URL\")\n}\n\nmodel Collection {\n id String @id @default(uuid())\n name String\n drawings Drawing[]\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Drawing {\n id String @id @default(uuid())\n name String\n elements String // Stored as JSON string\n appState String // Stored as JSON string\n files String @default(\"{}\") // Stored as JSON string\n preview String? // SVG string for thumbnail\n version Int @default(1)\n collectionId String?\n collection Collection? @relation(fields: [collectionId], references: [id])\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n",
|
"inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator client {\n provider = \"prisma-client-js\"\n output = \"../src/generated/client\"\n binaryTargets = [\"native\", \"linux-musl-arm64-openssl-3.0.x\"]\n}\n\ndatasource db {\n provider = \"sqlite\"\n url = env(\"DATABASE_URL\")\n}\n\nmodel Collection {\n id String @id @default(uuid())\n name String\n drawings Drawing[]\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Drawing {\n id String @id @default(uuid())\n name String\n elements String // Stored as JSON string\n appState String // Stored as JSON string\n files String @default(\"{}\") // Stored as JSON string\n preview String? // SVG string for thumbnail\n version Int @default(1)\n collectionId String?\n collection Collection? @relation(fields: [collectionId], references: [id])\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n",
|
||||||
"inlineSchemaHash": "e43b17bb99e7f864b5a0f2c54951015f0a3d998329d0577afda58e7c0dee4919",
|
"inlineSchemaHash": "9864a039193c73ddda01fd51751788fa5729bb0a603a9379a3fa314a4aced64f",
|
||||||
"copyEngine": true
|
"copyEngine": true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +215,10 @@ Object.assign(exports, Prisma)
|
|||||||
// file annotations for bundling tools to include these files
|
// file annotations for bundling tools to include these files
|
||||||
path.join(__dirname, "libquery_engine-darwin-arm64.dylib.node");
|
path.join(__dirname, "libquery_engine-darwin-arm64.dylib.node");
|
||||||
path.join(process.cwd(), "src/generated/client/libquery_engine-darwin-arm64.dylib.node")
|
path.join(process.cwd(), "src/generated/client/libquery_engine-darwin-arm64.dylib.node")
|
||||||
|
|
||||||
|
// file annotations for bundling tools to include these files
|
||||||
|
path.join(__dirname, "libquery_engine-linux-musl-arm64-openssl-3.0.x.so.node");
|
||||||
|
path.join(process.cwd(), "src/generated/client/libquery_engine-linux-musl-arm64-openssl-3.0.x.so.node")
|
||||||
// file annotations for bundling tools to include these files
|
// file annotations for bundling tools to include these files
|
||||||
path.join(__dirname, "schema.prisma");
|
path.join(__dirname, "schema.prisma");
|
||||||
path.join(process.cwd(), "src/generated/client/schema.prisma")
|
path.join(process.cwd(), "src/generated/client/schema.prisma")
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "prisma-client-0eff54f2002515791cbb1f1a5b1632637c7e46544d107e5ff89b7092d9540808",
|
"name": "prisma-client-04007c5051869a2f5298bd562ab2fb60a423747e0d5699dd1a73a4757b2657b6",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"browser": "index-browser.js",
|
"browser": "index-browser.js",
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
output = "../src/generated/client"
|
output = "../src/generated/client"
|
||||||
|
binaryTargets = ["native", "linux-musl-arm64-openssl-3.0.x"]
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
|
|||||||
+22
-4
@@ -12,10 +12,28 @@ import { PrismaClient } from "./generated/client";
|
|||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
// Ensure DATABASE_URL is absolute to avoid relative path issues with generated client
|
// Ensure DATABASE_URL always points to an absolute path when using SQLite.
|
||||||
// Point to the same DB file as Prisma CLI (relative to schema.prisma, usually in prisma/ folder)
|
// Respect externally provided values and only fall back to the dev database when unset.
|
||||||
const dbPath = path.resolve(__dirname, "../prisma/dev.db");
|
const backendRoot = path.resolve(__dirname, "../");
|
||||||
process.env.DATABASE_URL = `file:${dbPath}`;
|
const defaultDbPath = path.resolve(backendRoot, "prisma/dev.db");
|
||||||
|
const resolveDatabaseUrl = (rawUrl?: string) => {
|
||||||
|
if (!rawUrl || rawUrl.trim().length === 0) {
|
||||||
|
return `file:${defaultDbPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rawUrl.startsWith("file:")) {
|
||||||
|
return rawUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = rawUrl.replace(/^file:/, "");
|
||||||
|
const absolutePath = path.isAbsolute(filePath)
|
||||||
|
? filePath
|
||||||
|
: path.resolve(backendRoot, filePath);
|
||||||
|
|
||||||
|
return `file:${absolutePath}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
process.env.DATABASE_URL = resolveDatabaseUrl(process.env.DATABASE_URL);
|
||||||
console.log("Resolved DATABASE_URL:", process.env.DATABASE_URL);
|
console.log("Resolved DATABASE_URL:", process.env.DATABASE_URL);
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|||||||
+40
-15
@@ -1,27 +1,50 @@
|
|||||||
import axios from 'axios';
|
import axios from "axios";
|
||||||
import type { Drawing, Collection } from '../types';
|
import type { Drawing, Collection } from "../types";
|
||||||
|
|
||||||
export const API_URL = import.meta.env.VITE_API_URL || '/api';
|
export const API_URL = import.meta.env.VITE_API_URL || "/api";
|
||||||
|
|
||||||
export const api = axios.create({
|
export const api = axios.create({
|
||||||
baseURL: API_URL,
|
baseURL: API_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getDrawings = async (search?: string, collectionId?: string | null) => {
|
const coerceTimestamp = (value: string | number | Date): number => {
|
||||||
|
if (typeof value === "number") return value;
|
||||||
|
if (value instanceof Date) return value.getTime();
|
||||||
|
const parsed = Date.parse(value);
|
||||||
|
return Number.isNaN(parsed) ? Date.now() : parsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deserializeDrawing = (drawing: any): Drawing => ({
|
||||||
|
...drawing,
|
||||||
|
createdAt: coerceTimestamp(drawing.createdAt),
|
||||||
|
updatedAt: coerceTimestamp(drawing.updatedAt),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getDrawings = async (
|
||||||
|
search?: string,
|
||||||
|
collectionId?: string | null
|
||||||
|
) => {
|
||||||
const params: any = {};
|
const params: any = {};
|
||||||
if (search) params.search = search;
|
if (search) params.search = search;
|
||||||
if (collectionId !== undefined) params.collectionId = collectionId === null ? 'null' : collectionId;
|
if (collectionId !== undefined)
|
||||||
const response = await api.get<Drawing[]>('/drawings', { params });
|
params.collectionId = collectionId === null ? "null" : collectionId;
|
||||||
return response.data;
|
const response = await api.get<Drawing[]>("/drawings", { params });
|
||||||
|
return response.data.map(deserializeDrawing);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDrawing = async (id: string) => {
|
export const getDrawing = async (id: string) => {
|
||||||
const response = await api.get<Drawing>(`/drawings/${id}`);
|
const response = await api.get<Drawing>(`/drawings/${id}`);
|
||||||
return response.data;
|
return deserializeDrawing(response.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createDrawing = async (name?: string, collectionId?: string | null) => {
|
export const createDrawing = async (
|
||||||
const response = await api.post<{ id: string }>('/drawings', { name, collectionId });
|
name?: string,
|
||||||
|
collectionId?: string | null
|
||||||
|
) => {
|
||||||
|
const response = await api.post<{ id: string }>("/drawings", {
|
||||||
|
name,
|
||||||
|
collectionId,
|
||||||
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,22 +59,24 @@ export const deleteDrawing = async (id: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const duplicateDrawing = async (id: string) => {
|
export const duplicateDrawing = async (id: string) => {
|
||||||
const response = await api.post<{ id: string }>(`/drawings/${id}/duplicate`);
|
const response = await api.post<Drawing>(`/drawings/${id}/duplicate`);
|
||||||
return response.data;
|
return deserializeDrawing(response.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getCollections = async () => {
|
export const getCollections = async () => {
|
||||||
const response = await api.get<Collection[]>('/collections');
|
const response = await api.get<Collection[]>("/collections");
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createCollection = async (name: string) => {
|
export const createCollection = async (name: string) => {
|
||||||
const response = await api.post<Collection>('/collections', { name });
|
const response = await api.post<Collection>("/collections", { name });
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateCollection = async (id: string, name: string) => {
|
export const updateCollection = async (id: string, name: string) => {
|
||||||
const response = await api.put<{ success: true }>(`/collections/${id}`, { name });
|
const response = await api.put<{ success: true }>(`/collections/${id}`, {
|
||||||
|
name,
|
||||||
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export const DrawingCard: React.FC<DrawingCardProps> = ({
|
|||||||
exportBackground: true,
|
exportBackground: true,
|
||||||
viewBackgroundColor: drawing.appState.viewBackgroundColor || "#ffffff"
|
viewBackgroundColor: drawing.appState.viewBackgroundColor || "#ffffff"
|
||||||
},
|
},
|
||||||
files: null,
|
files: drawing.files || {},
|
||||||
exportPadding: 10
|
exportPadding: 10
|
||||||
});
|
});
|
||||||
const previewHtml = svg.outerHTML;
|
const previewHtml = svg.outerHTML;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type { Drawing, Collection } from '../types';
|
|||||||
import { useDebounce } from '../hooks/useDebounce';
|
import { useDebounce } from '../hooks/useDebounce';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { ConfirmModal } from '../components/ConfirmModal';
|
import { ConfirmModal } from '../components/ConfirmModal';
|
||||||
import { importDrawings, importLibrary } from '../utils/importUtils';
|
import { importDrawings } from '../utils/importUtils';
|
||||||
|
|
||||||
type Point = { x: number; y: number };
|
type Point = { x: number; y: number };
|
||||||
|
|
||||||
@@ -542,17 +542,14 @@ export const Dashboard: React.FC = () => {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const libFiles = files.filter(f => f.name.endsWith('.excalidrawlib'));
|
const libFiles = files.filter(f => f.name.endsWith('.excalidrawlib'));
|
||||||
const drawingFiles = files.filter(f => !f.name.endsWith('.excalidrawlib'));
|
|
||||||
|
|
||||||
if (libFiles.length > 0) {
|
if (libFiles.length > 0) {
|
||||||
for (const file of libFiles) {
|
setShowImportError({
|
||||||
const res = await importLibrary(file);
|
isOpen: true,
|
||||||
if (!res.success) {
|
message: 'Library (.excalidrawlib) imports are not supported in this build. Please import drawings (.excalidraw/.json) instead.'
|
||||||
alert(`Failed to import library ${file.name}: ${res.error}`);
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const drawingFiles = files.filter(f => !f.name.endsWith('.excalidrawlib'));
|
||||||
if (drawingFiles.length > 0) {
|
if (drawingFiles.length > 0) {
|
||||||
const result = await importDrawings(drawingFiles, targetCollectionId, refreshData);
|
const result = await importDrawings(drawingFiles, targetCollectionId, refreshData);
|
||||||
if (result.failed > 0) {
|
if (result.failed > 0) {
|
||||||
|
|||||||
@@ -76,43 +76,3 @@ export const importDrawings = async (
|
|||||||
|
|
||||||
return { success: successCount, failed: failCount, errors };
|
return { success: successCount, failed: failCount, errors };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const importLibrary = async (file: File) => {
|
|
||||||
try {
|
|
||||||
const text = await file.text();
|
|
||||||
const data = JSON.parse(text);
|
|
||||||
|
|
||||||
let newItems = [];
|
|
||||||
if (data.libraryItems) {
|
|
||||||
newItems = data.libraryItems;
|
|
||||||
} else if (Array.isArray(data)) {
|
|
||||||
newItems = data;
|
|
||||||
} else {
|
|
||||||
throw new Error("Invalid library file format");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch existing
|
|
||||||
const existingRes = await fetch(`${API_URL}/library`);
|
|
||||||
let existingItems = [];
|
|
||||||
if (existingRes.ok) {
|
|
||||||
const existingData = await existingRes.json();
|
|
||||||
existingItems = existingData.libraryItems || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge (simple concat)
|
|
||||||
const mergedItems = [...existingItems, ...newItems];
|
|
||||||
|
|
||||||
// Save
|
|
||||||
const saveRes = await fetch(`${API_URL}/library`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ libraryItems: mergedItems }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!saveRes.ok) throw new Error("Failed to save library");
|
|
||||||
return { success: true, count: newItems.length };
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error("Library import failed:", err);
|
|
||||||
return { success: false, error: err.message };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user