Dateien nach "backend" hochladen
This commit is contained in:
204
backend/db.js
Normal file
204
backend/db.js
Normal file
@@ -0,0 +1,204 @@
|
||||
const Database = require('better-sqlite3');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const path = require('path');
|
||||
|
||||
const db = new Database(process.env.DB_PATH || '/data/dickendock.db');
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('foreign_keys = ON');
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT 'user',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Dashboard-Widgets
|
||||
CREATE TABLE IF NOT EXISTS quick_links (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
icon TEXT NOT NULL DEFAULT '🔗',
|
||||
sort_order INTEGER DEFAULT 0
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS todos (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
text TEXT NOT NULL,
|
||||
done INTEGER NOT NULL DEFAULT 0,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS notes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE,
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Tool: 3D-Kalkulator
|
||||
CREATE TABLE IF NOT EXISTS calendar_feeds (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
color TEXT NOT NULL DEFAULT '#4ecdc4',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS folders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
parent_id INTEGER REFERENCES folders(id) ON DELETE CASCADE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS folder_shares (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
folder_id INTEGER NOT NULL REFERENCES folders(id) ON DELETE CASCADE,
|
||||
shared_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
shared_with INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(folder_id, shared_with)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS files (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
filename TEXT NOT NULL,
|
||||
originalname TEXT NOT NULL,
|
||||
mimetype TEXT NOT NULL DEFAULT 'application/octet-stream',
|
||||
size INTEGER NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS file_shares (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
|
||||
shared_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
shared_with INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(file_id, shared_with)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS user_keys (
|
||||
user_id INTEGER PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
||||
public_key TEXT NOT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS push_subscriptions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
endpoint TEXT NOT NULL UNIQUE,
|
||||
p256dh TEXT NOT NULL,
|
||||
auth TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sender_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
recipient_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
encrypted_content TEXT NOT NULL,
|
||||
iv TEXT NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_by_sender INTEGER NOT NULL DEFAULT 0,
|
||||
deleted_by_recipient INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS admin_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
bemerkung TEXT NOT NULL DEFAULT '',
|
||||
status TEXT NOT NULL DEFAULT 'warteliste',
|
||||
custom_price REAL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS order_items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
order_id INTEGER NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
|
||||
calculation_id INTEGER REFERENCES calculations(id) ON DELETE SET NULL,
|
||||
calc_name TEXT NOT NULL,
|
||||
preis_freundschaft REAL NOT NULL,
|
||||
preis_normal REAL NOT NULL,
|
||||
preis_auftrag REAL NOT NULL,
|
||||
stueckzahl INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS calculations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
gramm REAL NOT NULL,
|
||||
stunden REAL NOT NULL,
|
||||
farben INTEGER NOT NULL DEFAULT 1,
|
||||
materialpreis_pro_gramm REAL NOT NULL DEFAULT 0.01,
|
||||
stromverbrauch_kw REAL NOT NULL DEFAULT 0.15,
|
||||
strompreis_pro_kwh REAL NOT NULL DEFAULT 0.38,
|
||||
druckerpreis REAL NOT NULL DEFAULT 550,
|
||||
gesamtdruckstunden REAL NOT NULL DEFAULT 5000,
|
||||
verschleiss_pro_stunde REAL NOT NULL DEFAULT 0.06,
|
||||
preis_freundschaft REAL NOT NULL,
|
||||
preis_normal REAL NOT NULL,
|
||||
preis_auftrag REAL NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
// Standard-Admin beim ersten Start anlegen
|
||||
// Default admin settings
|
||||
const settingDefaults = [
|
||||
['file_max_size_mb', '50'],
|
||||
['file_max_count', '20'],
|
||||
['file_allowed_ext', '.pdf,.jpg,.jpeg,.png,.gif,.zip,.txt,.docx,.xlsx,.mp4,.stl,.3mf'],
|
||||
];
|
||||
for (const [key, value] of settingDefaults) {
|
||||
if (!db.prepare('SELECT key FROM admin_settings WHERE key=?').get(key))
|
||||
db.prepare('INSERT INTO admin_settings (key,value) VALUES (?,?)').run(key, value);
|
||||
}
|
||||
|
||||
if (db.prepare('SELECT COUNT(*) c FROM users').get().c === 0) {
|
||||
db.prepare('INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)')
|
||||
.run('admin', bcrypt.hashSync('admin123', 12), 'admin');
|
||||
console.log('✅ Erster Start: Admin angelegt: admin / admin123 → Bitte Passwort sofort ändern!');
|
||||
}
|
||||
|
||||
// ── Migrationen (werden bei jedem Start geprüft) ─────────────────────────────
|
||||
const cols = db.prepare("PRAGMA table_info(calculations)").all().map(r => r.name);
|
||||
if (!cols.includes('image')) db.exec("ALTER TABLE calculations ADD COLUMN image TEXT");
|
||||
if (!cols.includes('bemerkung')) db.exec("ALTER TABLE calculations ADD COLUMN bemerkung TEXT NOT NULL DEFAULT ''");
|
||||
|
||||
// Migrationen orders
|
||||
const ordCols = db.prepare("PRAGMA table_info(orders)").all().map(r => r.name);
|
||||
if (!ordCols.includes('bezahlt')) db.exec("ALTER TABLE orders ADD COLUMN bezahlt INTEGER NOT NULL DEFAULT 0");
|
||||
if (!ordCols.includes('bezahlt_am')) db.exec("ALTER TABLE orders ADD COLUMN bezahlt_am DATETIME");
|
||||
|
||||
// Migrationen order_items
|
||||
// files migrations
|
||||
const fileCols = db.prepare("PRAGMA table_info(files)").all().map(r => r.name);
|
||||
if (!fileCols.includes('folder_id')) db.exec("ALTER TABLE files ADD COLUMN folder_id INTEGER REFERENCES folders(id) ON DELETE SET NULL");
|
||||
|
||||
// folder_shares migrations (only if table exists)
|
||||
const fsTableExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='folder_shares'").get();
|
||||
if (fsTableExists) {
|
||||
const fsCols = db.prepare("PRAGMA table_info(folder_shares)").all().map(r => r.name);
|
||||
if (!fsCols.includes('created_at')) db.exec("ALTER TABLE folder_shares ADD COLUMN created_at DATETIME DEFAULT NULL");
|
||||
}
|
||||
|
||||
const oiCols = db.prepare("PRAGMA table_info(order_items)").all().map(r => r.name);
|
||||
if (!oiCols.includes('stunden')) db.exec("ALTER TABLE order_items ADD COLUMN stunden REAL NOT NULL DEFAULT 0");
|
||||
if (!oiCols.includes('custom_price')) db.exec("ALTER TABLE order_items ADD COLUMN custom_price REAL");
|
||||
if (!oiCols.includes('status')) db.exec("ALTER TABLE order_items ADD COLUMN status TEXT NOT NULL DEFAULT 'warteliste'");
|
||||
if (!oiCols.includes('qty_warteliste')) db.exec("ALTER TABLE order_items ADD COLUMN qty_warteliste INTEGER NOT NULL DEFAULT 0");
|
||||
if (!oiCols.includes('qty_in_arbeit')) db.exec("ALTER TABLE order_items ADD COLUMN qty_in_arbeit INTEGER NOT NULL DEFAULT 0");
|
||||
if (!oiCols.includes('qty_fertig')) db.exec("ALTER TABLE order_items ADD COLUMN qty_fertig INTEGER NOT NULL DEFAULT 0");
|
||||
// Bestehende Einträge: alle Stücke auf Warteliste setzen falls noch keine Qtys gesetzt
|
||||
db.exec("UPDATE order_items SET qty_warteliste=stueckzahl WHERE qty_warteliste=0 AND qty_in_arbeit=0 AND qty_fertig=0");
|
||||
module.exports = db;
|
||||
48
backend/index.js
Normal file
48
backend/index.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
require('./db');
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
// ── API ───────────────────────────────────────────────────────────────────────
|
||||
app.use('/api/auth', require('./routes/auth'));
|
||||
app.use('/api/admin', require('./routes/admin'));
|
||||
app.use('/api/calendar', require('./routes/calendar'));
|
||||
app.use('/api/nachrichten', require('./routes/nachrichten'));
|
||||
app.use('/api/dashboard', require('./routes/dashboard'));
|
||||
app.use('/api/system', require('./routes/system'));
|
||||
|
||||
// Tool-Routen automatisch laden
|
||||
const toolsDir = path.join(__dirname, 'tools');
|
||||
fs.readdirSync(toolsDir, { withFileTypes: true })
|
||||
.filter(d => d.isDirectory())
|
||||
.forEach(d => {
|
||||
const routeFile = path.join(toolsDir, d.name, 'routes.js');
|
||||
if (fs.existsSync(routeFile)) {
|
||||
app.use(`/api/tools/${d.name}`, require(routeFile));
|
||||
console.log(` 🔧 Tool geladen: ${d.name}`);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Frontend ──────────────────────────────────────────────────────────────────
|
||||
const PUBLIC = path.join(__dirname, '../public');
|
||||
|
||||
app.get('/manifest.json', (_req, res) => {
|
||||
res.setHeader('Content-Type', 'application/manifest+json');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.sendFile(path.join(PUBLIC, 'manifest.json'));
|
||||
});
|
||||
app.get('/sw.js', (_req, res) => {
|
||||
res.setHeader('Content-Type', 'application/javascript');
|
||||
res.setHeader('Service-Worker-Allowed', '/');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.sendFile(path.join(PUBLIC, 'sw.js'));
|
||||
});
|
||||
|
||||
app.use(express.static(PUBLIC));
|
||||
app.get('*', (_req, res) => res.sendFile(path.join(PUBLIC, 'index.html')));
|
||||
|
||||
app.listen(4000, () => console.log('🚀 Dicken Dock läuft auf Port 4000'));
|
||||
168
backend/nachrichten.js
Normal file
168
backend/nachrichten.js
Normal file
@@ -0,0 +1,168 @@
|
||||
const express = require('express');
|
||||
const webpush = require('web-push');
|
||||
const db = require('../db');
|
||||
const { authenticate } = require('../middleware/auth');
|
||||
const router = express.Router();
|
||||
|
||||
// ── VAPID-Setup (einmalig, in DB gespeichert) ─────────────────────────────────
|
||||
function getVapidKeys() {
|
||||
const pub = db.prepare("SELECT value FROM admin_settings WHERE key='vapid_public'").get();
|
||||
const priv = db.prepare("SELECT value FROM admin_settings WHERE key='vapid_private'").get();
|
||||
if (pub && priv) return { publicKey: pub.value, privateKey: priv.value };
|
||||
const keys = webpush.generateVAPIDKeys();
|
||||
db.prepare("INSERT OR REPLACE INTO admin_settings (key,value) VALUES ('vapid_public',?)").run(keys.publicKey);
|
||||
db.prepare("INSERT OR REPLACE INTO admin_settings (key,value) VALUES ('vapid_private',?)").run(keys.privateKey);
|
||||
return keys;
|
||||
}
|
||||
const vapid = getVapidKeys();
|
||||
webpush.setVapidDetails('mailto:admin@dickendock.local', vapid.publicKey, vapid.privateKey);
|
||||
|
||||
// GET /vapid-key – öffentlicher VAPID-Schlüssel für den Browser
|
||||
router.get('/vapid-key', authenticate, (req, res) => {
|
||||
res.json({ publicKey: vapid.publicKey });
|
||||
});
|
||||
|
||||
// ── Öffentliche Schlüssel ─────────────────────────────────────────────────────
|
||||
// POST /keys – eigenen öffentlichen Schlüssel speichern
|
||||
router.post('/keys', authenticate, (req, res) => {
|
||||
const { publicKey } = req.body;
|
||||
if (!publicKey) return res.status(400).json({ error: 'Kein Schlüssel' });
|
||||
db.prepare('INSERT OR REPLACE INTO user_keys (user_id,public_key,updated_at) VALUES (?,?,CURRENT_TIMESTAMP)').run(req.user.id, publicKey);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// GET /keys/:username – öffentlichen Schlüssel eines Users abrufen
|
||||
router.get('/keys/:username', authenticate, (req, res) => {
|
||||
const user = db.prepare('SELECT id FROM users WHERE username=?').get(req.params.username);
|
||||
if (!user) return res.status(404).json({ error: 'User nicht gefunden' });
|
||||
const key = db.prepare('SELECT public_key FROM user_keys WHERE user_id=?').get(user.id);
|
||||
if (!key) return res.status(404).json({ error: 'Noch kein Schlüssel hinterlegt' });
|
||||
res.json({ publicKey: key.public_key });
|
||||
});
|
||||
|
||||
// ── Push-Subscriptions ────────────────────────────────────────────────────────
|
||||
router.post('/subscribe', authenticate, (req, res) => {
|
||||
const { endpoint, keys } = req.body;
|
||||
if (!endpoint || !keys?.p256dh || !keys?.auth) return res.status(400).json({ error: 'Ungültige Subscription' });
|
||||
db.prepare('INSERT OR REPLACE INTO push_subscriptions (user_id,endpoint,p256dh,auth) VALUES (?,?,?,?)')
|
||||
.run(req.user.id, endpoint, keys.p256dh, keys.auth);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
router.delete('/subscribe', authenticate, (req, res) => {
|
||||
db.prepare('DELETE FROM push_subscriptions WHERE user_id=?').run(req.user.id);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// ── Nachrichten ───────────────────────────────────────────────────────────────
|
||||
// GET /conversations – alle Gesprächspartner mit ungelesener Anzahl
|
||||
router.get('/conversations', authenticate, (req, res) => {
|
||||
const uid = req.user.id;
|
||||
const convs = db.prepare(`
|
||||
SELECT
|
||||
CASE WHEN m.sender_id=? THEN m.recipient_id ELSE m.sender_id END AS partner_id,
|
||||
u.username AS partner_name,
|
||||
MAX(m.created_at) AS last_at,
|
||||
COUNT(CASE WHEN m.recipient_id=? AND m.deleted_by_recipient=0 THEN 1 END) AS unread
|
||||
FROM messages m
|
||||
JOIN users u ON u.id=(CASE WHEN m.sender_id=? THEN m.recipient_id ELSE m.sender_id END)
|
||||
WHERE (m.sender_id=? AND m.deleted_by_sender=0)
|
||||
OR (m.recipient_id=? AND m.deleted_by_recipient=0)
|
||||
GROUP BY partner_id
|
||||
ORDER BY last_at DESC
|
||||
`).all(uid, uid, uid, uid, uid);
|
||||
res.json(convs);
|
||||
});
|
||||
|
||||
// GET /unread-count – Gesamtanzahl ungelesener Nachrichten
|
||||
router.get('/unread-count', authenticate, (req, res) => {
|
||||
const count = db.prepare('SELECT COUNT(*) c FROM messages WHERE recipient_id=? AND deleted_by_recipient=0').get(req.user.id).c;
|
||||
res.json({ count });
|
||||
});
|
||||
|
||||
// GET /with/:username – Nachrichten mit einem User
|
||||
router.get('/with/:username', authenticate, (req, res) => {
|
||||
const uid = req.user.id;
|
||||
const partner = db.prepare('SELECT id FROM users WHERE username=?').get(req.params.username);
|
||||
if (!partner) return res.status(404).json({ error: 'User nicht gefunden' });
|
||||
const msgs = db.prepare(`
|
||||
SELECT m.id, m.sender_id, m.encrypted_content, m.iv, m.created_at,
|
||||
s.username AS sender_name
|
||||
FROM messages m
|
||||
JOIN users s ON s.id=m.sender_id
|
||||
WHERE ((m.sender_id=? AND m.recipient_id=? AND m.deleted_by_sender=0)
|
||||
OR (m.sender_id=? AND m.recipient_id=? AND m.deleted_by_recipient=0))
|
||||
ORDER BY m.created_at ASC
|
||||
`).all(uid, partner.id, partner.id, uid);
|
||||
res.json(msgs);
|
||||
});
|
||||
|
||||
// GET /users – alle User außer sich selbst (für neues Gespräch)
|
||||
router.get('/users', authenticate, (req, res) => {
|
||||
const users = db.prepare('SELECT id,username FROM users WHERE id!=? ORDER BY username').all(req.user.id);
|
||||
res.json(users);
|
||||
});
|
||||
|
||||
// POST /send – verschlüsselte Nachricht senden
|
||||
router.post('/send', authenticate, async (req, res) => {
|
||||
const { recipient_username, encrypted_content, iv } = req.body;
|
||||
if (!recipient_username || !encrypted_content || !iv)
|
||||
return res.status(400).json({ error: 'Fehlende Felder' });
|
||||
const recipient = db.prepare('SELECT id FROM users WHERE username=?').get(recipient_username);
|
||||
if (!recipient) return res.status(404).json({ error: 'Empfänger nicht gefunden' });
|
||||
|
||||
const r = db.prepare('INSERT INTO messages (sender_id,recipient_id,encrypted_content,iv) VALUES (?,?,?,?)')
|
||||
.run(req.user.id, recipient.id, encrypted_content, iv);
|
||||
|
||||
// Push-Benachrichtigung an Empfänger
|
||||
const subs = db.prepare('SELECT * FROM push_subscriptions WHERE user_id=?').all(recipient.id);
|
||||
const sender = db.prepare('SELECT username FROM users WHERE id=?').get(req.user.id);
|
||||
for (const sub of subs) {
|
||||
try {
|
||||
await webpush.sendNotification(
|
||||
{ endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } },
|
||||
JSON.stringify({ title: `Nachricht von ${sender.username}`, body: '🔒 Verschlüsselte Nachricht', icon: '/favicon.svg' })
|
||||
);
|
||||
} catch(e) {
|
||||
if (e.statusCode === 410) db.prepare('DELETE FROM push_subscriptions WHERE endpoint=?').run(sub.endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ id: r.lastInsertRowid, success: true });
|
||||
});
|
||||
|
||||
// DELETE /:id – Nachricht für sich löschen
|
||||
router.delete('/:id', authenticate, (req, res) => {
|
||||
const uid = req.user.id;
|
||||
const msg = db.prepare('SELECT * FROM messages WHERE id=?').get(req.params.id);
|
||||
if (!msg) return res.status(404).json({ error: 'Nicht gefunden' });
|
||||
if (msg.sender_id !== uid && msg.recipient_id !== uid)
|
||||
return res.status(403).json({ error: 'Kein Zugriff' });
|
||||
|
||||
if (msg.sender_id === uid) db.prepare('UPDATE messages SET deleted_by_sender=1 WHERE id=?').run(msg.id);
|
||||
else db.prepare('UPDATE messages SET deleted_by_recipient=1 WHERE id=?').run(msg.id);
|
||||
|
||||
// Wenn beide gelöscht → komplett entfernen
|
||||
const updated = db.prepare('SELECT * FROM messages WHERE id=?').get(msg.id);
|
||||
if (updated?.deleted_by_sender && updated?.deleted_by_recipient)
|
||||
db.prepare('DELETE FROM messages WHERE id=?').run(msg.id);
|
||||
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// DELETE /conversation/:username – ganzes Gespräch für sich löschen
|
||||
router.delete('/conversation/:username', authenticate, (req, res) => {
|
||||
const uid = req.user.id;
|
||||
const partner = db.prepare('SELECT id FROM users WHERE username=?').get(req.params.username);
|
||||
if (!partner) return res.status(404).json({ error: 'User nicht gefunden' });
|
||||
|
||||
db.prepare('UPDATE messages SET deleted_by_sender=1 WHERE sender_id=? AND recipient_id=?').run(uid, partner.id);
|
||||
db.prepare('UPDATE messages SET deleted_by_recipient=1 WHERE recipient_id=? AND sender_id=?').run(uid, partner.id);
|
||||
// Komplett löschen wenn beide weg
|
||||
db.prepare('DELETE FROM messages WHERE sender_id IN (?,?) AND recipient_id IN (?,?) AND deleted_by_sender=1 AND deleted_by_recipient=1')
|
||||
.run(uid, partner.id, uid, partner.id);
|
||||
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -7,6 +7,7 @@
|
||||
"better-sqlite3": "^9.4.3",
|
||||
"express": "^4.18.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"multer": "^1.4.5-lts.1"
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"web-push": "^3.6.7"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user