feat: initial commit web-cad-neu with docker-compose, frontend and backend
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* SQLite Adapter – implements DatabaseInterface with better-sqlite3
|
||||
*/
|
||||
import Database from 'better-sqlite3';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import type {
|
||||
DatabaseInterface, DBProject, DBDrawing, DBLayer,
|
||||
DBElement, DBBlock, DBSetting, DBUser,
|
||||
} from './DatabaseInterface.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export class SqliteAdapter implements DatabaseInterface {
|
||||
private db: Database.Database;
|
||||
|
||||
constructor(dbPath: string = ':memory:') {
|
||||
this.db = new Database(dbPath);
|
||||
this.db.pragma('journal_mode = WAL');
|
||||
this.db.pragma('foreign_keys = ON');
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
const schemaPath = join(__dirname, 'schema.sql');
|
||||
const schema = readFileSync(schemaPath, 'utf-8');
|
||||
this.db.exec(schema);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.db.close();
|
||||
}
|
||||
|
||||
// ─── Users ──────────────────────────────────────────
|
||||
getUser(id: string): DBUser | null {
|
||||
return (this.db.prepare('SELECT * FROM users WHERE id = ?').get(id) as DBUser) ?? null;
|
||||
}
|
||||
|
||||
getUserByEmail(email: string): DBUser | null {
|
||||
return (this.db.prepare('SELECT * FROM users WHERE email = ?').get(email) as DBUser) ?? null;
|
||||
}
|
||||
|
||||
createUser(data: Partial<DBUser>): DBUser {
|
||||
const id = data.id ?? `user-${Date.now()}`;
|
||||
this.db.prepare(
|
||||
'INSERT INTO users (id, email, password_hash, name, role) VALUES (?, ?, ?, ?, ?)',
|
||||
).run(id, data.email!, data.password_hash!, data.name!, data.role ?? 'planer');
|
||||
return this.getUser(id)!;
|
||||
}
|
||||
|
||||
updateUser(id: string, data: Partial<DBUser>): DBUser | null {
|
||||
const sets: string[] = [];
|
||||
const vals: any[] = [];
|
||||
if (data.email !== undefined) { sets.push('email = ?'); vals.push(data.email); }
|
||||
if (data.password_hash !== undefined) { sets.push('password_hash = ?'); vals.push(data.password_hash); }
|
||||
if (data.name !== undefined) { sets.push('name = ?'); vals.push(data.name); }
|
||||
if (data.role !== undefined) { sets.push('role = ?'); vals.push(data.role); }
|
||||
sets.push("updated_at = datetime('now')");
|
||||
if (sets.length > 1) {
|
||||
this.db.prepare(`UPDATE users SET ${sets.join(', ')} WHERE id = ?`).run(...vals, id);
|
||||
}
|
||||
return this.getUser(id);
|
||||
}
|
||||
|
||||
deleteUser(id: string): boolean {
|
||||
return this.db.prepare('DELETE FROM users WHERE id = ?').run(id).changes > 0;
|
||||
}
|
||||
|
||||
listUsers(): DBUser[] {
|
||||
return this.db.prepare('SELECT * FROM users ORDER BY created_at DESC').all() as DBUser[];
|
||||
}
|
||||
|
||||
// ─── Projects ──────────────────────────────────────
|
||||
listProjects(): DBProject[] {
|
||||
return this.db.prepare('SELECT * FROM projects ORDER BY updated_at DESC').all() as DBProject[];
|
||||
}
|
||||
|
||||
getProject(id: string): DBProject | null {
|
||||
return (this.db.prepare('SELECT * FROM projects WHERE id = ?').get(id) as DBProject) ?? null;
|
||||
}
|
||||
|
||||
createProject(data: Partial<DBProject>): DBProject {
|
||||
const id = data.id ?? `proj-${Date.now()}`;
|
||||
this.db.prepare(
|
||||
'INSERT INTO projects (id, name, description, owner_id) VALUES (?, ?, ?, ?)',
|
||||
).run(id, data.name ?? 'Unbenannt', data.description ?? null, data.owner_id ?? 'user-default');
|
||||
return this.getProject(id)!;
|
||||
}
|
||||
|
||||
updateProject(id: string, data: Partial<DBProject>): DBProject | null {
|
||||
const sets: string[] = [];
|
||||
const vals: any[] = [];
|
||||
if (data.name !== undefined) { sets.push('name = ?'); vals.push(data.name); }
|
||||
if (data.description !== undefined) { sets.push('description = ?'); vals.push(data.description); }
|
||||
sets.push("updated_at = datetime('now')");
|
||||
if (sets.length > 1) {
|
||||
this.db.prepare(`UPDATE projects SET ${sets.join(', ')} WHERE id = ?`).run(...vals, id);
|
||||
}
|
||||
return this.getProject(id);
|
||||
}
|
||||
|
||||
deleteProject(id: string): boolean {
|
||||
return this.db.prepare('DELETE FROM projects WHERE id = ?').run(id).changes > 0;
|
||||
}
|
||||
|
||||
// ─── Drawings ──────────────────────────────────────
|
||||
listDrawings(projectId: string): DBDrawing[] {
|
||||
return this.db.prepare('SELECT * FROM drawings WHERE project_id = ? ORDER BY updated_at DESC').all(projectId) as DBDrawing[];
|
||||
}
|
||||
|
||||
getDrawing(id: string): DBDrawing | null {
|
||||
return (this.db.prepare('SELECT * FROM drawings WHERE id = ?').get(id) as DBDrawing) ?? null;
|
||||
}
|
||||
|
||||
createDrawing(data: Partial<DBDrawing>): DBDrawing {
|
||||
const id = data.id ?? `draw-${Date.now()}`;
|
||||
this.db.prepare(
|
||||
'INSERT INTO drawings (id, project_id, name, data_json) VALUES (?, ?, ?, ?)',
|
||||
).run(id, data.project_id!, data.name ?? 'Unbenannt', data.data_json ?? '{}');
|
||||
return this.getDrawing(id)!;
|
||||
}
|
||||
|
||||
updateDrawing(id: string, data: Partial<DBDrawing>): DBDrawing | null {
|
||||
const sets: string[] = [];
|
||||
const vals: any[] = [];
|
||||
if (data.name !== undefined) { sets.push('name = ?'); vals.push(data.name); }
|
||||
if (data.data_json !== undefined) { sets.push('data_json = ?'); vals.push(data.data_json); }
|
||||
sets.push("updated_at = datetime('now')");
|
||||
if (sets.length > 1) {
|
||||
this.db.prepare(`UPDATE drawings SET ${sets.join(', ')} WHERE id = ?`).run(...vals, id);
|
||||
}
|
||||
return this.getDrawing(id);
|
||||
}
|
||||
|
||||
deleteDrawing(id: string): boolean {
|
||||
return this.db.prepare('DELETE FROM drawings WHERE id = ?').run(id).changes > 0;
|
||||
}
|
||||
|
||||
// ─── Layers ────────────────────────────────────────
|
||||
listLayers(drawingId: string): DBLayer[] {
|
||||
return this.db.prepare('SELECT * FROM layers WHERE drawing_id = ? ORDER BY sort_order').all(drawingId) as DBLayer[];
|
||||
}
|
||||
|
||||
createLayer(data: Partial<DBLayer>): DBLayer {
|
||||
const id = data.id ?? `layer-${Date.now()}`;
|
||||
this.db.prepare(
|
||||
'INSERT INTO layers (id, drawing_id, name, visible, locked, color, line_type, transparency, sort_order, parent_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
).run(id, data.drawing_id!, data.name ?? 'Layer', data.visible ?? 1, data.locked ?? 0,
|
||||
data.color ?? '#ffffff', data.line_type ?? 'solid', data.transparency ?? 0,
|
||||
data.sort_order ?? 0, data.parent_id ?? null);
|
||||
return this.db.prepare('SELECT * FROM layers WHERE id = ?').get(id) as DBLayer;
|
||||
}
|
||||
|
||||
updateLayer(id: string, data: Partial<DBLayer>): DBLayer | null {
|
||||
const sets: string[] = [];
|
||||
const vals: any[] = [];
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
if (['name','visible','locked','color','line_type','transparency','sort_order','parent_id'].includes(k)) {
|
||||
sets.push(`${k} = ?`); vals.push(v);
|
||||
}
|
||||
}
|
||||
if (sets.length > 0) {
|
||||
this.db.prepare(`UPDATE layers SET ${sets.join(', ')} WHERE id = ?`).run(...vals, id);
|
||||
}
|
||||
return this.db.prepare('SELECT * FROM layers WHERE id = ?').get(id) as DBLayer ?? null;
|
||||
}
|
||||
|
||||
deleteLayer(id: string): boolean {
|
||||
return this.db.prepare('DELETE FROM layers WHERE id = ?').run(id).changes > 0;
|
||||
}
|
||||
|
||||
// ─── Elements ──────────────────────────────────────
|
||||
listElements(drawingId: string): DBElement[] {
|
||||
return this.db.prepare('SELECT * FROM elements WHERE drawing_id = ?').all(drawingId) as DBElement[];
|
||||
}
|
||||
|
||||
createElement(data: Partial<DBElement>): DBElement {
|
||||
const id = data.id ?? `elem-${Date.now()}`;
|
||||
this.db.prepare(
|
||||
'INSERT INTO elements (id, drawing_id, layer_id, type, x, y, width, height, properties_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
).run(id, data.drawing_id!, data.layer_id!, data.type!, data.x ?? 0, data.y ?? 0,
|
||||
data.width ?? 0, data.height ?? 0, data.properties_json ?? '{}');
|
||||
return this.db.prepare('SELECT * FROM elements WHERE id = ?').get(id) as DBElement;
|
||||
}
|
||||
|
||||
updateElement(id: string, data: Partial<DBElement>): DBElement | null {
|
||||
const sets: string[] = [];
|
||||
const vals: any[] = [];
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
if (['layer_id','type','x','y','width','height','properties_json'].includes(k)) {
|
||||
sets.push(`${k} = ?`); vals.push(v);
|
||||
}
|
||||
}
|
||||
if (sets.length > 0) {
|
||||
this.db.prepare(`UPDATE elements SET ${sets.join(', ')} WHERE id = ?`).run(...vals, id);
|
||||
}
|
||||
return this.db.prepare('SELECT * FROM elements WHERE id = ?').get(id) as DBElement ?? null;
|
||||
}
|
||||
|
||||
deleteElement(id: string): boolean {
|
||||
return this.db.prepare('DELETE FROM elements WHERE id = ?').run(id).changes > 0;
|
||||
}
|
||||
|
||||
// ─── Blocks ────────────────────────────────────────
|
||||
listBlocks(drawingId: string): DBBlock[] {
|
||||
return this.db.prepare('SELECT * FROM blocks WHERE drawing_id = ?').all(drawingId) as DBBlock[];
|
||||
}
|
||||
|
||||
createBlock(data: Partial<DBBlock>): DBBlock {
|
||||
const id = data.id ?? `block-${Date.now()}`;
|
||||
this.db.prepare(
|
||||
'INSERT INTO blocks (id, drawing_id, name, description, category, elements_json, thumbnail) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
).run(id, data.drawing_id!, data.name ?? 'Block', data.description ?? null,
|
||||
data.category ?? 'Allgemein', data.elements_json ?? '[]', data.thumbnail ?? null);
|
||||
return this.db.prepare('SELECT * FROM blocks WHERE id = ?').get(id) as DBBlock;
|
||||
}
|
||||
|
||||
updateBlock(id: string, data: Partial<DBBlock>): DBBlock | null {
|
||||
const sets: string[] = [];
|
||||
const vals: any[] = [];
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
if (['name','description','category','elements_json','thumbnail'].includes(k)) {
|
||||
sets.push(`${k} = ?`); vals.push(v);
|
||||
}
|
||||
}
|
||||
if (sets.length > 0) {
|
||||
this.db.prepare(`UPDATE blocks SET ${sets.join(', ')} WHERE id = ?`).run(...vals, id);
|
||||
}
|
||||
return this.db.prepare('SELECT * FROM blocks WHERE id = ?').get(id) as DBBlock ?? null;
|
||||
}
|
||||
|
||||
deleteBlock(id: string): boolean {
|
||||
return this.db.prepare('DELETE FROM blocks WHERE id = ?').run(id).changes > 0;
|
||||
}
|
||||
|
||||
// ─── Settings ──────────────────────────────────────
|
||||
getSetting(key: string): DBSetting | null {
|
||||
return (this.db.prepare('SELECT * FROM settings WHERE key = ?').get(key) as DBSetting) ?? null;
|
||||
}
|
||||
|
||||
setSetting(key: string, value: string): void {
|
||||
this.db.prepare(
|
||||
"INSERT INTO settings (key, value, updated_at) VALUES (?, ?, datetime('now')) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = datetime('now')",
|
||||
).run(key, value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user