Update language, add LICENSE and README
All checks were successful
Build & Push Docker Image / build (push) Successful in 1m9s
All checks were successful
Build & Push Docker Image / build (push) Successful in 1m9s
This commit is contained in:
@@ -45,26 +45,26 @@ export async function sendVerificationEmail(to, name, verifyUrl, appName = 'Redl
|
||||
await transporter.sendMail({
|
||||
from: `"${appName}" <${from}>`,
|
||||
to,
|
||||
subject: `${appName} – E-Mail bestätigen / Verify your email`,
|
||||
subject: `${appName} – Verify your email`,
|
||||
html: `
|
||||
<div style="font-family:Arial,sans-serif;max-width:520px;margin:0 auto;padding:32px;background:#1e1e2e;color:#cdd6f4;border-radius:12px;">
|
||||
<h2 style="color:#cba6f7;margin-top:0;">Hey ${name} 👋</h2>
|
||||
<p>Bitte bestätige deine E-Mail-Adresse, indem du auf den folgenden Button klickst:</p>
|
||||
<p>Please verify your email address by clicking the button below:</p>
|
||||
<p style="text-align:center;margin:28px 0;">
|
||||
<a href="${verifyUrl}"
|
||||
style="display:inline-block;background:#cba6f7;color:#1e1e2e;padding:12px 32px;border-radius:8px;text-decoration:none;font-weight:bold;">
|
||||
E-Mail bestätigen
|
||||
Verify Email
|
||||
</a>
|
||||
</p>
|
||||
<p style="font-size:13px;color:#7f849c;">
|
||||
Oder kopiere diesen Link in deinen Browser:<br/>
|
||||
Or copy this link in your browser:<br/>
|
||||
<a href="${verifyUrl}" style="color:#89b4fa;word-break:break-all;">${verifyUrl}</a>
|
||||
</p>
|
||||
<p style="font-size:13px;color:#7f849c;">Der Link ist 24 Stunden gültig.</p>
|
||||
<p style="font-size:13px;color:#7f849c;">This link is valid for 24 hours.</p>
|
||||
<hr style="border:none;border-top:1px solid #313244;margin:24px 0;"/>
|
||||
<p style="font-size:12px;color:#585b70;">Falls du dich nicht registriert hast, ignoriere diese E-Mail.</p>
|
||||
<p style="font-size:12px;color:#585b70;">If you didn't register, please ignore this email.</p>
|
||||
</div>
|
||||
`,
|
||||
text: `Hey ${name},\n\nBitte bestätige deine E-Mail: ${verifyUrl}\n\nDer Link ist 24 Stunden gültig.\n\n– ${appName}`,
|
||||
text: `Hey ${name},\n\nPlease verify your email: ${verifyUrl}\n\nThis link is valid for 24 hours.\n\n– ${appName}`,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export async function authenticateToken(req, res, next) {
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Authentifizierung erforderlich' });
|
||||
return res.status(401).json({ error: 'Authentication required' });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -16,18 +16,18 @@ export async function authenticateToken(req, res, next) {
|
||||
const db = getDb();
|
||||
const user = await db.get('SELECT id, name, email, role, theme, language, avatar_color, avatar_image FROM users WHERE id = ?', [decoded.userId]);
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: 'Benutzer nicht gefunden' });
|
||||
return res.status(401).json({ error: 'User not found' });
|
||||
}
|
||||
req.user = user;
|
||||
next();
|
||||
} catch (err) {
|
||||
return res.status(403).json({ error: 'Ungültiges Token' });
|
||||
return res.status(403).json({ error: 'Invalid token' });
|
||||
}
|
||||
}
|
||||
|
||||
export function requireAdmin(req, res, next) {
|
||||
if (req.user.role !== 'admin') {
|
||||
return res.status(403).json({ error: 'Administratorrechte erforderlich' });
|
||||
return res.status(403).json({ error: 'Admin rights required' });
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@ router.post('/users', authenticateToken, requireAdmin, async (req, res) => {
|
||||
const { name, email, password, role } = req.body;
|
||||
|
||||
if (!name || !email || !password) {
|
||||
return res.status(400).json({ error: 'Alle Felder sind erforderlich' });
|
||||
return res.status(400).json({ error: 'All fields are required' });
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({ error: 'Passwort muss mindestens 6 Zeichen lang sein' });
|
||||
return res.status(400).json({ error: 'Password must be at least 6 characters long' });
|
||||
}
|
||||
|
||||
const validRole = ['user', 'admin'].includes(role) ? role : 'user';
|
||||
@@ -23,7 +23,7 @@ router.post('/users', authenticateToken, requireAdmin, async (req, res) => {
|
||||
|
||||
const existing = await db.get('SELECT id FROM users WHERE email = ?', [email.toLowerCase()]);
|
||||
if (existing) {
|
||||
return res.status(409).json({ error: 'E-Mail wird bereits verwendet' });
|
||||
return res.status(409).json({ error: 'Email is already in use' });
|
||||
}
|
||||
|
||||
const hash = bcrypt.hashSync(password, 12);
|
||||
@@ -36,7 +36,7 @@ router.post('/users', authenticateToken, requireAdmin, async (req, res) => {
|
||||
res.status(201).json({ user });
|
||||
} catch (err) {
|
||||
console.error('Create user error:', err);
|
||||
res.status(500).json({ error: 'Benutzer konnte nicht erstellt werden' });
|
||||
res.status(500).json({ error: 'User could not be created' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -54,7 +54,7 @@ router.get('/users', authenticateToken, requireAdmin, async (req, res) => {
|
||||
res.json({ users });
|
||||
} catch (err) {
|
||||
console.error('List users error:', err);
|
||||
res.status(500).json({ error: 'Benutzer konnten nicht geladen werden' });
|
||||
res.status(500).json({ error: 'Users could not be loaded' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@ router.put('/users/:id/role', authenticateToken, requireAdmin, async (req, res)
|
||||
try {
|
||||
const { role } = req.body;
|
||||
if (!['user', 'admin'].includes(role)) {
|
||||
return res.status(400).json({ error: 'Ungültige Rolle' });
|
||||
return res.status(400).json({ error: 'Invalid role' });
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
@@ -73,7 +73,7 @@ router.put('/users/:id/role', authenticateToken, requireAdmin, async (req, res)
|
||||
const adminCount = await db.get('SELECT COUNT(*) as count FROM users WHERE role = ?', ['admin']);
|
||||
const currentUser = await db.get('SELECT role FROM users WHERE id = ?', [req.params.id]);
|
||||
if (currentUser?.role === 'admin' && adminCount.count <= 1) {
|
||||
return res.status(400).json({ error: 'Der letzte Admin kann nicht herabgestuft werden' });
|
||||
return res.status(400).json({ error: 'The last admin cannot be demoted' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ router.put('/users/:id/role', authenticateToken, requireAdmin, async (req, res)
|
||||
res.json({ user: updated });
|
||||
} catch (err) {
|
||||
console.error('Update role error:', err);
|
||||
res.status(500).json({ error: 'Rolle konnte nicht aktualisiert werden' });
|
||||
res.status(500).json({ error: 'Role could not be updated' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -93,27 +93,27 @@ router.delete('/users/:id', authenticateToken, requireAdmin, async (req, res) =>
|
||||
const db = getDb();
|
||||
|
||||
if (parseInt(req.params.id) === req.user.id) {
|
||||
return res.status(400).json({ error: 'Sie können sich nicht selbst löschen' });
|
||||
return res.status(400).json({ error: 'You cannot delete yourself' });
|
||||
}
|
||||
|
||||
const user = await db.get('SELECT id, role FROM users WHERE id = ?', [req.params.id]);
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'Benutzer nicht gefunden' });
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
// Check if it's the last admin
|
||||
if (user.role === 'admin') {
|
||||
const adminCount = await db.get('SELECT COUNT(*) as count FROM users WHERE role = ?', ['admin']);
|
||||
if (adminCount.count <= 1) {
|
||||
return res.status(400).json({ error: 'Der letzte Admin kann nicht gelöscht werden' });
|
||||
return res.status(400).json({ error: 'The last admin cannot be deleted' });
|
||||
}
|
||||
}
|
||||
|
||||
await db.run('DELETE FROM users WHERE id = ?', [req.params.id]);
|
||||
res.json({ message: 'Benutzer gelöscht' });
|
||||
res.json({ message: 'User deleted' });
|
||||
} catch (err) {
|
||||
console.error('Delete user error:', err);
|
||||
res.status(500).json({ error: 'Benutzer konnte nicht gelöscht werden' });
|
||||
res.status(500).json({ error: 'User could not be deleted' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -122,17 +122,17 @@ router.put('/users/:id/password', authenticateToken, requireAdmin, async (req, r
|
||||
try {
|
||||
const { newPassword } = req.body;
|
||||
if (!newPassword || newPassword.length < 6) {
|
||||
return res.status(400).json({ error: 'Passwort muss mindestens 6 Zeichen lang sein' });
|
||||
return res.status(400).json({ error: 'Password must be at least 6 characters long' });
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
const hash = bcrypt.hashSync(newPassword, 12);
|
||||
await db.run('UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?', [hash, req.params.id]);
|
||||
|
||||
res.json({ message: 'Passwort zurückgesetzt' });
|
||||
res.json({ message: 'Password reset' });
|
||||
} catch (err) {
|
||||
console.error('Reset password error:', err);
|
||||
res.status(500).json({ error: 'Passwort konnte nicht zurückgesetzt werden' });
|
||||
res.status(500).json({ error: 'Password could not be reset' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -25,17 +25,17 @@ router.post('/register', async (req, res) => {
|
||||
const { name, email, password } = req.body;
|
||||
|
||||
if (!name || !email || !password) {
|
||||
return res.status(400).json({ error: 'Alle Felder sind erforderlich' });
|
||||
return res.status(400).json({ error: 'All fields are required' });
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({ error: 'Passwort muss mindestens 6 Zeichen lang sein' });
|
||||
return res.status(400).json({ error: 'Password must be at least 6 characters long' });
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
const existing = await db.get('SELECT id FROM users WHERE email = ?', [email]);
|
||||
if (existing) {
|
||||
return res.status(409).json({ error: 'E-Mail wird bereits verwendet' });
|
||||
return res.status(409).json({ error: 'Email is already in use' });
|
||||
}
|
||||
|
||||
const hash = bcrypt.hashSync(password, 12);
|
||||
@@ -63,7 +63,7 @@ router.post('/register', async (req, res) => {
|
||||
|
||||
await sendVerificationEmail(email.toLowerCase(), name, verifyUrl, appName);
|
||||
|
||||
return res.status(201).json({ needsVerification: true, message: 'Verifizierungs-E-Mail wurde gesendet' });
|
||||
return res.status(201).json({ needsVerification: true, message: 'Verification email has been sent' });
|
||||
}
|
||||
|
||||
// No SMTP configured – register and login immediately (legacy behaviour)
|
||||
@@ -78,7 +78,7 @@ router.post('/register', async (req, res) => {
|
||||
res.status(201).json({ token, user });
|
||||
} catch (err) {
|
||||
console.error('Register error:', err);
|
||||
res.status(500).json({ error: 'Registrierung fehlgeschlagen' });
|
||||
res.status(500).json({ error: 'Registration failed' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -87,7 +87,7 @@ router.get('/verify-email', async (req, res) => {
|
||||
try {
|
||||
const { token } = req.query;
|
||||
if (!token) {
|
||||
return res.status(400).json({ error: 'Token fehlt' });
|
||||
return res.status(400).json({ error: 'Token is missing' });
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
@@ -97,11 +97,11 @@ router.get('/verify-email', async (req, res) => {
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
return res.status(400).json({ error: 'Ungültiger oder bereits verwendeter Token' });
|
||||
return res.status(400).json({ error: 'Invalid or already used token' });
|
||||
}
|
||||
|
||||
if (new Date(user.verification_token_expires) < new Date()) {
|
||||
return res.status(400).json({ error: 'Token ist abgelaufen. Bitte registriere dich erneut.' });
|
||||
return res.status(400).json({ error: 'Token has expired. Please register again.' });
|
||||
}
|
||||
|
||||
await db.run(
|
||||
@@ -109,10 +109,10 @@ router.get('/verify-email', async (req, res) => {
|
||||
[user.id]
|
||||
);
|
||||
|
||||
res.json({ verified: true, message: 'E-Mail erfolgreich verifiziert' });
|
||||
res.json({ verified: true, message: 'Email verified successfully' });
|
||||
} catch (err) {
|
||||
console.error('Verify email error:', err);
|
||||
res.status(500).json({ error: 'Verifizierung fehlgeschlagen' });
|
||||
res.status(500).json({ error: 'Verification failed' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -121,11 +121,11 @@ router.post('/resend-verification', async (req, res) => {
|
||||
try {
|
||||
const { email } = req.body;
|
||||
if (!email) {
|
||||
return res.status(400).json({ error: 'E-Mail ist erforderlich' });
|
||||
return res.status(400).json({ error: 'Email is required' });
|
||||
}
|
||||
|
||||
if (!isMailerConfigured()) {
|
||||
return res.status(400).json({ error: 'SMTP ist nicht konfiguriert' });
|
||||
return res.status(400).json({ error: 'SMTP is not configured' });
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
@@ -133,7 +133,7 @@ router.post('/resend-verification', async (req, res) => {
|
||||
|
||||
if (!user || user.email_verified) {
|
||||
// Don't reveal whether account exists
|
||||
return res.json({ message: 'Falls ein Konto existiert, wurde eine neue E-Mail gesendet.' });
|
||||
return res.json({ message: 'If an account exists, a new email has been sent.' });
|
||||
}
|
||||
|
||||
const verificationToken = uuidv4();
|
||||
@@ -155,10 +155,10 @@ router.post('/resend-verification', async (req, res) => {
|
||||
|
||||
await sendVerificationEmail(email.toLowerCase(), user.name, verifyUrl, appName);
|
||||
|
||||
res.json({ message: 'Falls ein Konto existiert, wurde eine neue E-Mail gesendet.' });
|
||||
res.json({ message: 'If an account exists, a new email has been sent.' });
|
||||
} catch (err) {
|
||||
console.error('Resend verification error:', err);
|
||||
res.status(500).json({ error: 'E-Mail konnte nicht gesendet werden' });
|
||||
res.status(500).json({ error: 'Email could not be sent' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -168,18 +168,18 @@ router.post('/login', async (req, res) => {
|
||||
const { email, password } = req.body;
|
||||
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({ error: 'E-Mail und Passwort sind erforderlich' });
|
||||
return res.status(400).json({ error: 'Email and password are required' });
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
const user = await db.get('SELECT * FROM users WHERE email = ?', [email.toLowerCase()]);
|
||||
|
||||
if (!user || !bcrypt.compareSync(password, user.password_hash)) {
|
||||
return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
if (!user.email_verified && isMailerConfigured()) {
|
||||
return res.status(403).json({ error: 'E-Mail-Adresse noch nicht verifiziert. Bitte prüfe dein Postfach.', needsVerification: true });
|
||||
return res.status(403).json({ error: 'Email address not yet verified. Please check your inbox.', needsVerification: true });
|
||||
}
|
||||
|
||||
const token = generateToken(user.id);
|
||||
@@ -188,7 +188,7 @@ router.post('/login', async (req, res) => {
|
||||
res.json({ token, user: safeUser });
|
||||
} catch (err) {
|
||||
console.error('Login error:', err);
|
||||
res.status(500).json({ error: 'Anmeldung fehlgeschlagen' });
|
||||
res.status(500).json({ error: 'Login failed' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -206,7 +206,7 @@ router.put('/profile', authenticateToken, async (req, res) => {
|
||||
if (email && email !== req.user.email) {
|
||||
const existing = await db.get('SELECT id FROM users WHERE email = ? AND id != ?', [email.toLowerCase(), req.user.id]);
|
||||
if (existing) {
|
||||
return res.status(409).json({ error: 'E-Mail wird bereits verwendet' });
|
||||
return res.status(409).json({ error: 'Email is already in use' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ router.put('/profile', authenticateToken, async (req, res) => {
|
||||
res.json({ user: updated });
|
||||
} catch (err) {
|
||||
console.error('Profile update error:', err);
|
||||
res.status(500).json({ error: 'Profil konnte nicht aktualisiert werden' });
|
||||
res.status(500).json({ error: 'Profile could not be updated' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -237,20 +237,20 @@ router.put('/password', authenticateToken, async (req, res) => {
|
||||
|
||||
const user = await db.get('SELECT password_hash FROM users WHERE id = ?', [req.user.id]);
|
||||
if (!bcrypt.compareSync(currentPassword, user.password_hash)) {
|
||||
return res.status(401).json({ error: 'Aktuelles Passwort ist falsch' });
|
||||
return res.status(401).json({ error: 'Current password is incorrect' });
|
||||
}
|
||||
|
||||
if (newPassword.length < 6) {
|
||||
return res.status(400).json({ error: 'Neues Passwort muss mindestens 6 Zeichen lang sein' });
|
||||
return res.status(400).json({ error: 'New password must be at least 6 characters long' });
|
||||
}
|
||||
|
||||
const hash = bcrypt.hashSync(newPassword, 12);
|
||||
await db.run('UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?', [hash, req.user.id]);
|
||||
|
||||
res.json({ message: 'Passwort erfolgreich geändert' });
|
||||
res.json({ message: 'Password changed successfully' });
|
||||
} catch (err) {
|
||||
console.error('Password change error:', err);
|
||||
res.status(500).json({ error: 'Passwort konnte nicht geändert werden' });
|
||||
res.status(500).json({ error: 'Password could not be changed' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -267,12 +267,12 @@ router.post('/avatar', authenticateToken, async (req, res) => {
|
||||
// Validate content type
|
||||
const contentType = req.headers['content-type'];
|
||||
if (!contentType || !contentType.startsWith('image/')) {
|
||||
return res.status(400).json({ error: 'Nur Bilddateien sind erlaubt' });
|
||||
return res.status(400).json({ error: 'Only image files are allowed' });
|
||||
}
|
||||
|
||||
// Max 2MB
|
||||
if (buffer.length > 2 * 1024 * 1024) {
|
||||
return res.status(400).json({ error: 'Bild darf maximal 2MB groß sein' });
|
||||
return res.status(400).json({ error: 'Image must not exceed 2MB' });
|
||||
}
|
||||
|
||||
const ext = contentType.includes('png') ? 'png' : contentType.includes('gif') ? 'gif' : contentType.includes('webp') ? 'webp' : 'jpg';
|
||||
@@ -295,7 +295,7 @@ router.post('/avatar', authenticateToken, async (req, res) => {
|
||||
res.json({ user: updated });
|
||||
} catch (err) {
|
||||
console.error('Avatar upload error:', err);
|
||||
res.status(500).json({ error: 'Avatar konnte nicht hochgeladen werden' });
|
||||
res.status(500).json({ error: 'Avatar could not be uploaded' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -313,7 +313,7 @@ router.delete('/avatar', authenticateToken, async (req, res) => {
|
||||
res.json({ user: updated });
|
||||
} catch (err) {
|
||||
console.error('Avatar delete error:', err);
|
||||
res.status(500).json({ error: 'Avatar konnte nicht entfernt werden' });
|
||||
res.status(500).json({ error: 'Avatar could not be removed' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -351,7 +351,7 @@ function generateColorFromName(name) {
|
||||
router.get('/avatar/:filename', (req, res) => {
|
||||
const filepath = path.join(uploadsDir, req.params.filename);
|
||||
if (!fs.existsSync(filepath)) {
|
||||
return res.status(404).json({ error: 'Avatar nicht gefunden' });
|
||||
return res.status(404).json({ error: 'Avatar not found' });
|
||||
}
|
||||
const ext = path.extname(filepath).slice(1);
|
||||
const mimeMap = { jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', gif: 'image/gif', webp: 'image/webp' };
|
||||
|
||||
@@ -26,7 +26,7 @@ router.get('/', authenticateToken, async (req, res) => {
|
||||
return {
|
||||
recordID: rec.recordID,
|
||||
meetingID: rec.meetingID,
|
||||
name: rec.name || 'Aufnahme',
|
||||
name: rec.name || 'Recording',
|
||||
state: rec.state,
|
||||
published: rec.published === 'true',
|
||||
startTime: rec.startTime,
|
||||
@@ -46,7 +46,7 @@ router.get('/', authenticateToken, async (req, res) => {
|
||||
res.json({ recordings: formatted });
|
||||
} catch (err) {
|
||||
console.error('Get recordings error:', err);
|
||||
res.status(500).json({ error: 'Aufnahmen konnten nicht geladen werden', recordings: [] });
|
||||
res.status(500).json({ error: 'Recordings could not be loaded', recordings: [] });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -57,7 +57,7 @@ router.get('/room/:uid', authenticateToken, async (req, res) => {
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]);
|
||||
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden' });
|
||||
return res.status(404).json({ error: 'Room not found' });
|
||||
}
|
||||
|
||||
const recordings = await getRecordings(room.uid);
|
||||
@@ -90,7 +90,7 @@ router.get('/room/:uid', authenticateToken, async (req, res) => {
|
||||
res.json({ recordings: formatted });
|
||||
} catch (err) {
|
||||
console.error('Get room recordings error:', err);
|
||||
res.status(500).json({ error: 'Aufnahmen konnten nicht geladen werden', recordings: [] });
|
||||
res.status(500).json({ error: 'Recordings could not be loaded', recordings: [] });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -98,10 +98,10 @@ router.get('/room/:uid', authenticateToken, async (req, res) => {
|
||||
router.delete('/:recordID', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
await deleteRecording(req.params.recordID);
|
||||
res.json({ message: 'Aufnahme gelöscht' });
|
||||
res.json({ message: 'Recording deleted' });
|
||||
} catch (err) {
|
||||
console.error('Delete recording error:', err);
|
||||
res.status(500).json({ error: 'Aufnahme konnte nicht gelöscht werden' });
|
||||
res.status(500).json({ error: 'Recording could not be deleted' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -110,10 +110,10 @@ router.put('/:recordID/publish', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { publish } = req.body;
|
||||
await publishRecording(req.params.recordID, publish);
|
||||
res.json({ message: publish ? 'Aufnahme veröffentlicht' : 'Aufnahme nicht mehr öffentlich' });
|
||||
res.json({ message: publish ? 'Recording published' : 'Recording unpublished' });
|
||||
} catch (err) {
|
||||
console.error('Publish recording error:', err);
|
||||
res.status(500).json({ error: 'Aufnahme konnte nicht aktualisiert werden' });
|
||||
res.status(500).json({ error: 'Recording could not be updated' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ router.get('/', authenticateToken, async (req, res) => {
|
||||
res.json({ rooms: [...ownRooms, ...sharedRooms] });
|
||||
} catch (err) {
|
||||
console.error('List rooms error:', err);
|
||||
res.status(500).json({ error: 'Räume konnten nicht geladen werden' });
|
||||
res.status(500).json({ error: 'Rooms could not be loaded' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ router.get('/users/search', authenticateToken, async (req, res) => {
|
||||
res.json({ users });
|
||||
} catch (err) {
|
||||
console.error('Search users error:', err);
|
||||
res.status(500).json({ error: 'Benutzersuche fehlgeschlagen' });
|
||||
res.status(500).json({ error: 'User search failed' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -84,14 +84,14 @@ router.get('/:uid', authenticateToken, async (req, res) => {
|
||||
`, [req.params.uid]);
|
||||
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden' });
|
||||
return res.status(404).json({ error: 'Room not found' });
|
||||
}
|
||||
|
||||
// Check access: owner, admin, or shared
|
||||
if (room.user_id !== req.user.id && req.user.role !== 'admin') {
|
||||
const share = await db.get('SELECT id FROM room_shares WHERE room_id = ? AND user_id = ?', [room.id, req.user.id]);
|
||||
if (!share) {
|
||||
return res.status(403).json({ error: 'Keine Berechtigung' });
|
||||
return res.status(403).json({ error: 'No permission' });
|
||||
}
|
||||
room.shared = 1;
|
||||
}
|
||||
@@ -107,7 +107,7 @@ router.get('/:uid', authenticateToken, async (req, res) => {
|
||||
res.json({ room, sharedUsers });
|
||||
} catch (err) {
|
||||
console.error('Get room error:', err);
|
||||
res.status(500).json({ error: 'Raum konnte nicht geladen werden' });
|
||||
res.status(500).json({ error: 'Room could not be loaded' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -129,7 +129,7 @@ router.post('/', authenticateToken, async (req, res) => {
|
||||
} = req.body;
|
||||
|
||||
if (!name || name.trim().length === 0) {
|
||||
return res.status(400).json({ error: 'Raumname ist erforderlich' });
|
||||
return res.status(400).json({ error: 'Room name is required' });
|
||||
}
|
||||
|
||||
const uid = crypto.randomBytes(8).toString('hex');
|
||||
@@ -158,7 +158,7 @@ router.post('/', authenticateToken, async (req, res) => {
|
||||
res.status(201).json({ room });
|
||||
} catch (err) {
|
||||
console.error('Create room error:', err);
|
||||
res.status(500).json({ error: 'Raum konnte nicht erstellt werden' });
|
||||
res.status(500).json({ error: 'Room could not be created' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -169,7 +169,7 @@ router.put('/:uid', authenticateToken, async (req, res) => {
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ? AND user_id = ?', [req.params.uid, req.user.id]);
|
||||
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden oder keine Berechtigung' });
|
||||
return res.status(404).json({ error: 'Room not found or no permission' });
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -220,7 +220,7 @@ router.put('/:uid', authenticateToken, async (req, res) => {
|
||||
res.json({ room: updated });
|
||||
} catch (err) {
|
||||
console.error('Update room error:', err);
|
||||
res.status(500).json({ error: 'Raum konnte nicht aktualisiert werden' });
|
||||
res.status(500).json({ error: 'Room could not be updated' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -231,18 +231,18 @@ router.delete('/:uid', authenticateToken, async (req, res) => {
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]);
|
||||
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden' });
|
||||
return res.status(404).json({ error: 'Room not found' });
|
||||
}
|
||||
|
||||
if (room.user_id !== req.user.id && req.user.role !== 'admin') {
|
||||
return res.status(403).json({ error: 'Keine Berechtigung' });
|
||||
return res.status(403).json({ error: 'No permission' });
|
||||
}
|
||||
|
||||
await db.run('DELETE FROM rooms WHERE uid = ?', [req.params.uid]);
|
||||
res.json({ message: 'Raum erfolgreich gelöscht' });
|
||||
res.json({ message: 'Room deleted successfully' });
|
||||
} catch (err) {
|
||||
console.error('Delete room error:', err);
|
||||
res.status(500).json({ error: 'Raum konnte nicht gelöscht werden' });
|
||||
res.status(500).json({ error: 'Room could not be deleted' });
|
||||
}
|
||||
});
|
||||
// GET /api/rooms/:uid/shares - Get shared users for a room
|
||||
@@ -251,7 +251,7 @@ router.get('/:uid/shares', authenticateToken, async (req, res) => {
|
||||
const db = getDb();
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ? AND user_id = ?', [req.params.uid, req.user.id]);
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden oder keine Berechtigung' });
|
||||
return res.status(404).json({ error: 'Room not found or no permission' });
|
||||
}
|
||||
const shares = await db.all(`
|
||||
SELECT u.id, u.name, u.email, u.avatar_color, u.avatar_image
|
||||
@@ -262,7 +262,7 @@ router.get('/:uid/shares', authenticateToken, async (req, res) => {
|
||||
res.json({ shares });
|
||||
} catch (err) {
|
||||
console.error('Get shares error:', err);
|
||||
res.status(500).json({ error: 'Fehler beim Laden der Freigaben' });
|
||||
res.status(500).json({ error: 'Error loading shares' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -271,20 +271,20 @@ router.post('/:uid/shares', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { user_id } = req.body;
|
||||
if (!user_id) {
|
||||
return res.status(400).json({ error: 'Benutzer-ID erforderlich' });
|
||||
return res.status(400).json({ error: 'User ID is required' });
|
||||
}
|
||||
const db = getDb();
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ? AND user_id = ?', [req.params.uid, req.user.id]);
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden oder keine Berechtigung' });
|
||||
return res.status(404).json({ error: 'Room not found or no permission' });
|
||||
}
|
||||
if (user_id === req.user.id) {
|
||||
return res.status(400).json({ error: 'Du kannst den Raum nicht mit dir selbst teilen' });
|
||||
return res.status(400).json({ error: 'You cannot share the room with yourself' });
|
||||
}
|
||||
// Check if already shared
|
||||
const existing = await db.get('SELECT id FROM room_shares WHERE room_id = ? AND user_id = ?', [room.id, user_id]);
|
||||
if (existing) {
|
||||
return res.status(400).json({ error: 'Raum ist bereits mit diesem Benutzer geteilt' });
|
||||
return res.status(400).json({ error: 'Room is already shared with this user' });
|
||||
}
|
||||
await db.run('INSERT INTO room_shares (room_id, user_id) VALUES (?, ?)', [room.id, user_id]);
|
||||
const shares = await db.all(`
|
||||
@@ -296,7 +296,7 @@ router.post('/:uid/shares', authenticateToken, async (req, res) => {
|
||||
res.json({ shares });
|
||||
} catch (err) {
|
||||
console.error('Share room error:', err);
|
||||
res.status(500).json({ error: 'Fehler beim Teilen des Raums' });
|
||||
res.status(500).json({ error: 'Error sharing room' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -306,7 +306,7 @@ router.delete('/:uid/shares/:userId', authenticateToken, async (req, res) => {
|
||||
const db = getDb();
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ? AND user_id = ?', [req.params.uid, req.user.id]);
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden oder keine Berechtigung' });
|
||||
return res.status(404).json({ error: 'Room not found or no permission' });
|
||||
}
|
||||
await db.run('DELETE FROM room_shares WHERE room_id = ? AND user_id = ?', [room.id, parseInt(req.params.userId)]);
|
||||
const shares = await db.all(`
|
||||
@@ -318,7 +318,7 @@ router.delete('/:uid/shares/:userId', authenticateToken, async (req, res) => {
|
||||
res.json({ shares });
|
||||
} catch (err) {
|
||||
console.error('Remove share error:', err);
|
||||
res.status(500).json({ error: 'Fehler beim Entfernen der Freigabe' });
|
||||
res.status(500).json({ error: 'Error removing share' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -329,15 +329,15 @@ router.post('/:uid/start', authenticateToken, async (req, res) => {
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]);
|
||||
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden' });
|
||||
return res.status(404).json({ error: 'Room not found' });
|
||||
}
|
||||
|
||||
// Check access: owner or shared user
|
||||
// Check access: owner or shared
|
||||
const isOwner = room.user_id === req.user.id;
|
||||
if (!isOwner) {
|
||||
const share = await db.get('SELECT id FROM room_shares WHERE room_id = ? AND user_id = ?', [room.id, req.user.id]);
|
||||
if (!share) {
|
||||
return res.status(403).json({ error: 'Keine Berechtigung' });
|
||||
return res.status(403).json({ error: 'No permission' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +347,7 @@ router.post('/:uid/start', authenticateToken, async (req, res) => {
|
||||
res.json({ joinUrl });
|
||||
} catch (err) {
|
||||
console.error('Start meeting error:', err);
|
||||
res.status(500).json({ error: 'Meeting konnte nicht gestartet werden' });
|
||||
res.status(500).json({ error: 'Meeting could not be started' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -358,18 +358,18 @@ router.post('/:uid/join', authenticateToken, async (req, res) => {
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]);
|
||||
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden' });
|
||||
return res.status(404).json({ error: 'Room not found' });
|
||||
}
|
||||
|
||||
// Check access code if set
|
||||
if (room.access_code && req.body.access_code !== room.access_code) {
|
||||
return res.status(403).json({ error: 'Falscher Zugangscode' });
|
||||
return res.status(403).json({ error: 'Wrong access code' });
|
||||
}
|
||||
|
||||
// Check if meeting is running
|
||||
const running = await isMeetingRunning(room.uid);
|
||||
if (!running) {
|
||||
return res.status(400).json({ error: 'Meeting läuft nicht. Bitte warten Sie, bis der Moderator das Meeting gestartet hat.' });
|
||||
return res.status(400).json({ error: 'Meeting is not running. Please wait for the moderator to start the meeting.' });
|
||||
}
|
||||
|
||||
// Owner and shared users join as moderator
|
||||
@@ -381,7 +381,7 @@ router.post('/:uid/join', authenticateToken, async (req, res) => {
|
||||
res.json({ joinUrl });
|
||||
} catch (err) {
|
||||
console.error('Join meeting error:', err);
|
||||
res.status(500).json({ error: 'Meeting konnte nicht beigetreten werden' });
|
||||
res.status(500).json({ error: 'Could not join meeting' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -392,7 +392,7 @@ router.post('/:uid/end', authenticateToken, async (req, res) => {
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]);
|
||||
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden' });
|
||||
return res.status(404).json({ error: 'Room not found' });
|
||||
}
|
||||
|
||||
// Check access: owner or shared user
|
||||
@@ -400,15 +400,15 @@ router.post('/:uid/end', authenticateToken, async (req, res) => {
|
||||
if (!isOwner) {
|
||||
const share = await db.get('SELECT id FROM room_shares WHERE room_id = ? AND user_id = ?', [room.id, req.user.id]);
|
||||
if (!share) {
|
||||
return res.status(403).json({ error: 'Keine Berechtigung' });
|
||||
return res.status(403).json({ error: 'No permission' });
|
||||
}
|
||||
}
|
||||
|
||||
await endMeeting(room.uid);
|
||||
res.json({ message: 'Meeting beendet' });
|
||||
res.json({ message: 'Meeting ended' });
|
||||
} catch (err) {
|
||||
console.error('End meeting error:', err);
|
||||
res.status(500).json({ error: 'Meeting konnte nicht beendet werden' });
|
||||
res.status(500).json({ error: 'Meeting could not be ended' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -425,7 +425,7 @@ router.get('/:uid/public', async (req, res) => {
|
||||
`, [req.params.uid]);
|
||||
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden' });
|
||||
return res.status(404).json({ error: 'Room not found' });
|
||||
}
|
||||
|
||||
const running = await isMeetingRunning(room.uid);
|
||||
@@ -442,7 +442,7 @@ router.get('/:uid/public', async (req, res) => {
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Public room info error:', err);
|
||||
res.status(500).json({ error: 'Rauminfos konnten nicht geladen werden' });
|
||||
res.status(500).json({ error: 'Room info could not be loaded' });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -452,25 +452,25 @@ router.post('/:uid/guest-join', async (req, res) => {
|
||||
const { name, access_code, moderator_code } = req.body;
|
||||
|
||||
if (!name || name.trim().length === 0) {
|
||||
return res.status(400).json({ error: 'Name ist erforderlich' });
|
||||
return res.status(400).json({ error: 'Name is required' });
|
||||
}
|
||||
|
||||
const db = getDb();
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]);
|
||||
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden' });
|
||||
return res.status(404).json({ error: 'Room not found' });
|
||||
}
|
||||
|
||||
// Check access code if set
|
||||
if (room.access_code && access_code !== room.access_code) {
|
||||
return res.status(403).json({ error: 'Falscher Zugangscode' });
|
||||
return res.status(403).json({ error: 'Wrong access code' });
|
||||
}
|
||||
|
||||
// Check if meeting is running (or if anyone_can_start is enabled)
|
||||
const running = await isMeetingRunning(room.uid);
|
||||
if (!running && !room.anyone_can_start) {
|
||||
return res.status(400).json({ error: 'Meeting läuft nicht. Bitte warten Sie, bis der Moderator das Meeting gestartet hat.' });
|
||||
return res.status(400).json({ error: 'Meeting is not running. Please wait for the moderator to start the meeting.' });
|
||||
}
|
||||
|
||||
// If meeting not running but anyone_can_start, create it
|
||||
@@ -490,7 +490,7 @@ router.post('/:uid/guest-join', async (req, res) => {
|
||||
res.json({ joinUrl });
|
||||
} catch (err) {
|
||||
console.error('Guest join error:', err);
|
||||
res.status(500).json({ error: 'Beitritt als Gast fehlgeschlagen' });
|
||||
res.status(500).json({ error: 'Guest join failed' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user