fix: HIGH issues #12,#15,#16,#17,#21 — backend persistence for import/layers/blocks + owner filtering
This commit is contained in:
@@ -112,7 +112,7 @@ export interface DatabaseInterface {
|
||||
listUsers(): DBUser[];
|
||||
|
||||
// Projects
|
||||
listProjects(): DBProject[];
|
||||
listProjects(ownerId?: string): DBProject[];
|
||||
getProject(id: string): DBProject | null;
|
||||
createProject(data: Partial<DBProject>): DBProject;
|
||||
updateProject(id: string, data: Partial<DBProject>): DBProject | null;
|
||||
|
||||
@@ -73,7 +73,10 @@ export class SqliteAdapter implements DatabaseInterface {
|
||||
}
|
||||
|
||||
// ─── Projects ──────────────────────────────────────
|
||||
listProjects(): DBProject[] {
|
||||
listProjects(ownerId?: string): DBProject[] {
|
||||
if (ownerId) {
|
||||
return this.db.prepare('SELECT * FROM projects WHERE owner_id = ? ORDER BY updated_at DESC').all(ownerId) as DBProject[];
|
||||
}
|
||||
return this.db.prepare('SELECT * FROM projects ORDER BY updated_at DESC').all() as DBProject[];
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@ import { requireAuth } from '../auth/authMiddleware.js';
|
||||
import type { AuthService } from '../auth/AuthService.js';
|
||||
|
||||
export function registerProjectRoutes(fastify: FastifyInstance, db: DatabaseInterface, authService: AuthService) {
|
||||
// List all projects
|
||||
// List projects owned by the authenticated user
|
||||
fastify.get('/api/projects', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
return db.listProjects();
|
||||
const user = requireAuth(request, reply, authService);
|
||||
if (!user) return;
|
||||
return db.listProjects(user.id);
|
||||
});
|
||||
|
||||
// Get single project
|
||||
@@ -24,10 +25,11 @@ export function registerProjectRoutes(fastify: FastifyInstance, db: DatabaseInte
|
||||
|
||||
// Create project
|
||||
fastify.post('/api/projects', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
const user = requireAuth(request, reply, authService);
|
||||
if (!user) return;
|
||||
const body = request.body as Partial<DBProject>;
|
||||
if (!body.name) return reply.code(400).send({ error: 'Name is required' });
|
||||
return reply.code(201).send(db.createProject(body));
|
||||
return reply.code(201).send(db.createProject({ ...body, owner_id: user.id }));
|
||||
});
|
||||
|
||||
// Update project
|
||||
|
||||
+138
-21
@@ -534,7 +534,13 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
const handleRenameBlock = useCallback((id: string, name: string) => {
|
||||
setBlocks((prev) => prev.map((b) => b.id === id ? { ...b, name } : b));
|
||||
setCommandHistory((prev) => [...prev, { prefix: '·', text: `Block umbenannt: ${name}`, type: 'info' }]);
|
||||
}, []);
|
||||
// Persist rename to backend
|
||||
if (token) {
|
||||
updateBlock(token, id, { name }).catch((err) => {
|
||||
console.error('Failed to save block rename:', err);
|
||||
});
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const handleDuplicateBlock = useCallback((id: string) => {
|
||||
setBlocks((prev) => {
|
||||
@@ -547,10 +553,16 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
name: `${block.name} (Kopie)`,
|
||||
elements: block.elements.map((el) => ({ ...el, id: `${el.id}_copy_${Date.now()}` })),
|
||||
};
|
||||
// Persist duplicate to backend
|
||||
if (drawingId && token) {
|
||||
createBlockTyped(token, drawingId, copy).catch((err) => {
|
||||
console.error('Failed to save duplicated block:', err);
|
||||
});
|
||||
}
|
||||
return [...prev, copy];
|
||||
});
|
||||
setCommandHistory((prev) => [...prev, { prefix: '·', text: 'Block dupliziert', type: 'info' }]);
|
||||
}, []);
|
||||
}, [drawingId, token]);
|
||||
|
||||
const handleDeleteBlock = useCallback((id: string) => {
|
||||
setBlocks((prev) => prev.filter((b) => b.id !== id));
|
||||
@@ -643,15 +655,42 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
const result = await importFile(file);
|
||||
if (result.success) {
|
||||
setElements((prev) => [...prev, ...result.elements]);
|
||||
if (result.layers && result.layers.length > 0) {
|
||||
setLayers((prev) => {
|
||||
const existingIds = new Set(prev.map(l => l.id));
|
||||
const newLayers = result.layers!.filter(l => !existingIds.has(l.id));
|
||||
return [...prev, ...newLayers];
|
||||
// Push imported elements to Yjs and backend
|
||||
for (const el of result.elements) {
|
||||
collab.setElement(el);
|
||||
}
|
||||
if (drawingId && token) {
|
||||
setSavedStatus('Speichert…');
|
||||
Promise.all(result.elements.map(el => createElementTyped(token, drawingId, el)))
|
||||
.then(() => setSavedStatus('gespeichert'))
|
||||
.catch((err) => {
|
||||
console.error('Failed to save imported elements:', err);
|
||||
setSavedStatus('Fehler beim Speichern');
|
||||
});
|
||||
}
|
||||
if (result.layers && result.layers.length > 0) {
|
||||
const existingIds = new Set(layers.map(l => l.id));
|
||||
const newLayers = result.layers.filter(l => !existingIds.has(l.id));
|
||||
setLayers((prev) => [...prev, ...newLayers]);
|
||||
// Push imported layers to Yjs and backend
|
||||
for (const layer of newLayers) {
|
||||
collab.setLayer(layer);
|
||||
}
|
||||
if (drawingId && token) {
|
||||
Promise.all(newLayers.map(l => createLayerTyped(token, drawingId, l)))
|
||||
.catch((err) => console.error('Failed to save imported layers:', err));
|
||||
}
|
||||
}
|
||||
if (result.blocks && result.blocks.length > 0) {
|
||||
setBlocks((prev) => [...prev, ...result.blocks!]);
|
||||
// Push imported blocks to Yjs and backend
|
||||
for (const block of result.blocks) {
|
||||
collab.setBlock(block);
|
||||
}
|
||||
if (drawingId && token) {
|
||||
Promise.all(result.blocks.map(b => createBlockTyped(token, drawingId, b)))
|
||||
.catch((err) => console.error('Failed to save imported blocks:', err));
|
||||
}
|
||||
}
|
||||
setCommandHistory((prev) => [...prev, { prefix: '·', text: `Importiert: ${result.elements.length} Elemente aus ${file.name}`, type: 'info' }]);
|
||||
if (result.warnings.length > 0) {
|
||||
@@ -660,7 +699,7 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
} else {
|
||||
setCommandHistory((prev) => [...prev, { prefix: '·', text: `Import fehlgeschlagen: ${result.error || 'Unbekannter Fehler'}`, type: 'info' }]);
|
||||
}
|
||||
}, []);
|
||||
}, [drawingId, token, collab, layers]);
|
||||
|
||||
const handleExport = useCallback(async (format: ExportFormat) => {
|
||||
const projectData: ProjectData = {
|
||||
@@ -866,8 +905,20 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
});
|
||||
}, [drawingId, token]);
|
||||
const handleToggleLayer = useCallback((id: string) => {
|
||||
setLayers((prev) => prev.map((l) => l.id === id ? { ...l, visible: !l.visible } : l));
|
||||
}, []);
|
||||
setLayers((prev) => {
|
||||
const updated = prev.map((l) => l.id === id ? { ...l, visible: !l.visible } : l);
|
||||
const changed = updated.find((l) => l.id === id);
|
||||
if (changed) {
|
||||
collab.setLayer(changed);
|
||||
if (token) {
|
||||
updateLayer(token, id, { visible: changed.visible }).catch((err) => {
|
||||
console.error('Failed to save layer visibility:', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
}, [token, collab]);
|
||||
const handleDeleteLayer = useCallback((id: string) => {
|
||||
setLayers((prev) => prev.filter((l) => l.id !== id));
|
||||
// Delete from backend
|
||||
@@ -878,29 +929,95 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
||||
}
|
||||
}, [token]);
|
||||
const handleRenameLayer = useCallback((id: string, name: string) => {
|
||||
setLayers((prev) => prev.map((l) => l.id === id ? { ...l, name } : l));
|
||||
}, []);
|
||||
setLayers((prev) => {
|
||||
const updated = prev.map((l) => l.id === id ? { ...l, name } : l);
|
||||
const changed = updated.find((l) => l.id === id);
|
||||
if (changed) {
|
||||
collab.setLayer(changed);
|
||||
if (token) {
|
||||
updateLayer(token, id, { name }).catch((err) => {
|
||||
console.error('Failed to save layer rename:', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
}, [token, collab]);
|
||||
const handleDuplicateLayer = useCallback((id: string) => {
|
||||
setLayers((prev) => {
|
||||
const layer = prev.find((l) => l.id === id);
|
||||
if (!layer) return prev;
|
||||
const newId = `layer-${Date.now()}`;
|
||||
const copy: CADLayer = { ...layer, id: newId, name: `${layer.name} (Kopie)`, sortOrder: prev.length };
|
||||
collab.setLayer(copy);
|
||||
if (drawingId && token) {
|
||||
createLayerTyped(token, drawingId, copy).catch((err) => {
|
||||
console.error('Failed to save duplicated layer:', err);
|
||||
});
|
||||
}
|
||||
return [...prev, copy];
|
||||
});
|
||||
}, []);
|
||||
}, [drawingId, token, collab]);
|
||||
const handleToggleLock = useCallback((id: string) => {
|
||||
setLayers((prev) => prev.map((l) => l.id === id ? { ...l, locked: !l.locked } : l));
|
||||
}, []);
|
||||
setLayers((prev) => {
|
||||
const updated = prev.map((l) => l.id === id ? { ...l, locked: !l.locked } : l);
|
||||
const changed = updated.find((l) => l.id === id);
|
||||
if (changed) {
|
||||
collab.setLayer(changed);
|
||||
if (token) {
|
||||
updateLayer(token, id, { locked: changed.locked }).catch((err) => {
|
||||
console.error('Failed to save layer lock state:', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
}, [token, collab]);
|
||||
const handleUpdateLayerColor = useCallback((id: string, color: string) => {
|
||||
setLayers((prev) => prev.map((l) => l.id === id ? { ...l, color } : l));
|
||||
}, []);
|
||||
setLayers((prev) => {
|
||||
const updated = prev.map((l) => l.id === id ? { ...l, color } : l);
|
||||
const changed = updated.find((l) => l.id === id);
|
||||
if (changed) {
|
||||
collab.setLayer(changed);
|
||||
if (token) {
|
||||
updateLayer(token, id, { color }).catch((err) => {
|
||||
console.error('Failed to save layer color:', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
}, [token, collab]);
|
||||
const handleUpdateLayerLineType = useCallback((id: string, lineType: 'solid' | 'dashed' | 'dotted') => {
|
||||
setLayers((prev) => prev.map((l) => l.id === id ? { ...l, lineType } : l));
|
||||
}, []);
|
||||
setLayers((prev) => {
|
||||
const updated = prev.map((l) => l.id === id ? { ...l, lineType } : l);
|
||||
const changed = updated.find((l) => l.id === id);
|
||||
if (changed) {
|
||||
collab.setLayer(changed);
|
||||
if (token) {
|
||||
updateLayer(token, id, { lineType }).catch((err) => {
|
||||
console.error('Failed to save layer line type:', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
}, [token, collab]);
|
||||
const handleUpdateLayerTransparency = useCallback((id: string, transparency: number) => {
|
||||
setLayers((prev) => prev.map((l) => l.id === id ? { ...l, transparency } : l));
|
||||
}, []);
|
||||
setLayers((prev) => {
|
||||
const updated = prev.map((l) => l.id === id ? { ...l, transparency } : l);
|
||||
const changed = updated.find((l) => l.id === id);
|
||||
if (changed) {
|
||||
collab.setLayer(changed);
|
||||
if (token) {
|
||||
updateLayer(token, id, { transparency }).catch((err) => {
|
||||
console.error('Failed to save layer transparency:', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
});
|
||||
}, [token, collab]);
|
||||
|
||||
const handleZoomIn = useCallback(() => {
|
||||
setCommandHistory((prev) => [...prev, { prefix: '·', text: 'Zoom: vergrößert', type: 'info' }]);
|
||||
|
||||
Reference in New Issue
Block a user