refactor: optimize drawing data handling and cache management
- Improve cache key generation using JSON.stringify for consistent formatting - Add promise deduplication in DrawingCard to prevent redundant API calls for full drawing data - Clear full data state when drawing ID changes to ensure fresh data loading - Fix async cache invalidation in drawing update and collection delete endpoints - Move cache invalidation after database operations in SQLite import endpoint - Add HydratedDrawingData type for better type safety in drawing data management
This commit is contained in:
@@ -137,9 +137,11 @@ const buildDrawingsCacheKey = (keyParts: {
|
|||||||
collectionFilter: string;
|
collectionFilter: string;
|
||||||
includeData: boolean;
|
includeData: boolean;
|
||||||
}) =>
|
}) =>
|
||||||
`${keyParts.searchTerm}|${keyParts.collectionFilter}|${
|
JSON.stringify([
|
||||||
keyParts.includeData ? "full" : "summary"
|
keyParts.searchTerm,
|
||||||
}`;
|
keyParts.collectionFilter,
|
||||||
|
keyParts.includeData ? "full" : "summary",
|
||||||
|
]);
|
||||||
|
|
||||||
const getCachedDrawingsBody = (key: string): Buffer | null => {
|
const getCachedDrawingsBody = (key: string): Buffer | null => {
|
||||||
const entry = drawingsCache.get(key);
|
const entry = drawingsCache.get(key);
|
||||||
@@ -796,7 +798,7 @@ app.put("/drawings/:id", async (req, res) => {
|
|||||||
where: { id },
|
where: { id },
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
invalidateDrawingsCache();
|
await invalidateDrawingsCache();
|
||||||
|
|
||||||
console.log("[API] Update complete", {
|
console.log("[API] Update complete", {
|
||||||
id,
|
id,
|
||||||
@@ -925,7 +927,7 @@ app.delete("/collections/:id", async (req, res) => {
|
|||||||
where: { id },
|
where: { id },
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
invalidateDrawingsCache();
|
await invalidateDrawingsCache();
|
||||||
|
|
||||||
res.json({ success: true });
|
res.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1191,9 +1193,9 @@ app.post("/import/sqlite", upload.single("db"), async (req, res) => {
|
|||||||
|
|
||||||
// Reinitialize Prisma client
|
// Reinitialize Prisma client
|
||||||
await prisma.$disconnect();
|
await prisma.$disconnect();
|
||||||
|
invalidateDrawingsCache();
|
||||||
|
|
||||||
res.json({ success: true, message: "Database imported successfully" });
|
res.json({ success: true, message: "Database imported successfully" });
|
||||||
invalidateDrawingsCache();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
if (req.file) {
|
if (req.file) {
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ import { exportDrawingToFile } from '../utils/exportUtils';
|
|||||||
|
|
||||||
import * as api from '../api';
|
import * as api from '../api';
|
||||||
|
|
||||||
|
type HydratedDrawingData = {
|
||||||
|
elements: any[];
|
||||||
|
appState: any;
|
||||||
|
files: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
interface DrawingCardProps {
|
interface DrawingCardProps {
|
||||||
drawing: DrawingSummary;
|
drawing: DrawingSummary;
|
||||||
collections: Collection[];
|
collections: Collection[];
|
||||||
@@ -52,27 +58,39 @@ export const DrawingCard: React.FC<DrawingCardProps> = ({
|
|||||||
const [previewSvg, setPreviewSvg] = useState<string | null>(drawing.preview ?? null);
|
const [previewSvg, setPreviewSvg] = useState<string | null>(drawing.preview ?? null);
|
||||||
const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null);
|
const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null);
|
||||||
const [isExporting, setIsExporting] = useState(false);
|
const [isExporting, setIsExporting] = useState(false);
|
||||||
const [fullData, setFullData] = useState<{
|
const [fullData, setFullData] = useState<HydratedDrawingData | null>(null);
|
||||||
elements: any[];
|
|
||||||
appState: any;
|
|
||||||
files: Record<string, any> | null;
|
|
||||||
} | null>(null);
|
|
||||||
|
|
||||||
const fullDataRef = React.useRef(fullData);
|
const fullDataRef = React.useRef(fullData);
|
||||||
fullDataRef.current = fullData;
|
fullDataRef.current = fullData;
|
||||||
|
const fullDataPromiseRef = React.useRef<Promise<HydratedDrawingData> | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFullData(null);
|
||||||
|
fullDataPromiseRef.current = null;
|
||||||
|
}, [drawing.id]);
|
||||||
|
|
||||||
const ensureFullData = useCallback(async () => {
|
const ensureFullData = useCallback(async () => {
|
||||||
if (fullDataRef.current) {
|
if (fullDataRef.current) {
|
||||||
return fullDataRef.current;
|
return fullDataRef.current;
|
||||||
}
|
}
|
||||||
const fullDrawing = await api.getDrawing(drawing.id);
|
if (fullDataPromiseRef.current) {
|
||||||
const payload = {
|
return fullDataPromiseRef.current;
|
||||||
|
}
|
||||||
|
const promise = api.getDrawing(drawing.id).then((fullDrawing) => {
|
||||||
|
const payload: HydratedDrawingData = {
|
||||||
elements: fullDrawing.elements || [],
|
elements: fullDrawing.elements || [],
|
||||||
appState: fullDrawing.appState || {},
|
appState: fullDrawing.appState || {},
|
||||||
files: fullDrawing.files || {},
|
files: fullDrawing.files || {},
|
||||||
};
|
};
|
||||||
setFullData(payload);
|
setFullData(payload);
|
||||||
|
fullDataPromiseRef.current = null;
|
||||||
return payload;
|
return payload;
|
||||||
|
}).catch((error) => {
|
||||||
|
fullDataPromiseRef.current = null;
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
fullDataPromiseRef.current = promise;
|
||||||
|
return promise;
|
||||||
}, [drawing.id]);
|
}, [drawing.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user