Add sharing rooms
All checks were successful
Build & Push Docker Image / build (push) Successful in 1m8s
All checks were successful
Build & Push Docker Image / build (push) Successful in 1m8s
This commit is contained in:
@@ -157,9 +157,19 @@ export async function initDatabase() {
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS room_shares (
|
||||
id SERIAL PRIMARY KEY,
|
||||
room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
UNIQUE(room_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_rooms_user_id ON rooms(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_rooms_uid ON rooms(uid);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_room_shares_room_id ON room_shares(room_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_room_shares_user_id ON room_shares(user_id);
|
||||
`);
|
||||
} else {
|
||||
await db.exec(`
|
||||
@@ -197,9 +207,21 @@ export async function initDatabase() {
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS room_shares (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
room_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(room_id, user_id),
|
||||
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_rooms_user_id ON rooms(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_rooms_uid ON rooms(uid);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_room_shares_room_id ON room_shares(room_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_room_shares_user_id ON room_shares(user_id);
|
||||
`);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,25 +22,56 @@ function getUserAvatarURL(req, user) {
|
||||
return `${baseUrl}/api/auth/avatar/initials/${encodeURIComponent(user.name)}${color}`;
|
||||
}
|
||||
|
||||
// GET /api/rooms - List user's rooms
|
||||
// GET /api/rooms - List user's rooms (owned + shared)
|
||||
router.get('/', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const db = getDb();
|
||||
const rooms = await db.all(`
|
||||
SELECT r.*, u.name as owner_name
|
||||
const ownRooms = await db.all(`
|
||||
SELECT r.*, u.name as owner_name, 0 as shared
|
||||
FROM rooms r
|
||||
JOIN users u ON r.user_id = u.id
|
||||
WHERE r.user_id = ?
|
||||
ORDER BY r.created_at DESC
|
||||
`, [req.user.id]);
|
||||
|
||||
res.json({ rooms });
|
||||
const sharedRooms = await db.all(`
|
||||
SELECT r.*, u.name as owner_name, 1 as shared
|
||||
FROM rooms r
|
||||
JOIN users u ON r.user_id = u.id
|
||||
JOIN room_shares rs ON rs.room_id = r.id
|
||||
WHERE rs.user_id = ?
|
||||
ORDER BY r.created_at DESC
|
||||
`, [req.user.id]);
|
||||
|
||||
res.json({ rooms: [...ownRooms, ...sharedRooms] });
|
||||
} catch (err) {
|
||||
console.error('List rooms error:', err);
|
||||
res.status(500).json({ error: 'Räume konnten nicht geladen werden' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/rooms/users/search - Search users for sharing (must be before /:uid routes)
|
||||
router.get('/users/search', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const { q } = req.query;
|
||||
if (!q || q.length < 2) {
|
||||
return res.json({ users: [] });
|
||||
}
|
||||
const db = getDb();
|
||||
const searchTerm = `%${q}%`;
|
||||
const users = await db.all(`
|
||||
SELECT id, name, email, avatar_color, avatar_image
|
||||
FROM users
|
||||
WHERE (name LIKE ? OR email LIKE ?) AND id != ?
|
||||
LIMIT 10
|
||||
`, [searchTerm, searchTerm, req.user.id]);
|
||||
res.json({ users });
|
||||
} catch (err) {
|
||||
console.error('Search users error:', err);
|
||||
res.status(500).json({ error: 'Benutzersuche fehlgeschlagen' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/rooms/:uid - Get room details
|
||||
router.get('/:uid', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
@@ -56,7 +87,24 @@ router.get('/:uid', authenticateToken, async (req, res) => {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden' });
|
||||
}
|
||||
|
||||
res.json({ room });
|
||||
// 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' });
|
||||
}
|
||||
room.shared = 1;
|
||||
}
|
||||
|
||||
// Get shared users
|
||||
const sharedUsers = await db.all(`
|
||||
SELECT u.id, u.name, u.email, u.avatar_color, u.avatar_image
|
||||
FROM room_shares rs
|
||||
JOIN users u ON rs.user_id = u.id
|
||||
WHERE rs.room_id = ?
|
||||
`, [room.id]);
|
||||
|
||||
res.json({ room, sharedUsers });
|
||||
} catch (err) {
|
||||
console.error('Get room error:', err);
|
||||
res.status(500).json({ error: 'Raum konnte nicht geladen werden' });
|
||||
@@ -197,15 +245,100 @@ router.delete('/:uid', authenticateToken, async (req, res) => {
|
||||
res.status(500).json({ error: 'Raum konnte nicht gelöscht werden' });
|
||||
}
|
||||
});
|
||||
// GET /api/rooms/:uid/shares - Get shared users for a room
|
||||
router.get('/:uid/shares', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
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' });
|
||||
}
|
||||
const shares = await db.all(`
|
||||
SELECT u.id, u.name, u.email, u.avatar_color, u.avatar_image
|
||||
FROM room_shares rs
|
||||
JOIN users u ON rs.user_id = u.id
|
||||
WHERE rs.room_id = ?
|
||||
`, [room.id]);
|
||||
res.json({ shares });
|
||||
} catch (err) {
|
||||
console.error('Get shares error:', err);
|
||||
res.status(500).json({ error: 'Fehler beim Laden der Freigaben' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/rooms/:uid/shares - Share room with a user
|
||||
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' });
|
||||
}
|
||||
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' });
|
||||
}
|
||||
if (user_id === req.user.id) {
|
||||
return res.status(400).json({ error: 'Du kannst den Raum nicht mit dir selbst teilen' });
|
||||
}
|
||||
// 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' });
|
||||
}
|
||||
await db.run('INSERT INTO room_shares (room_id, user_id) VALUES (?, ?)', [room.id, user_id]);
|
||||
const shares = await db.all(`
|
||||
SELECT u.id, u.name, u.email, u.avatar_color, u.avatar_image
|
||||
FROM room_shares rs
|
||||
JOIN users u ON rs.user_id = u.id
|
||||
WHERE rs.room_id = ?
|
||||
`, [room.id]);
|
||||
res.json({ shares });
|
||||
} catch (err) {
|
||||
console.error('Share room error:', err);
|
||||
res.status(500).json({ error: 'Fehler beim Teilen des Raums' });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/rooms/:uid/shares/:userId - Remove share
|
||||
router.delete('/:uid/shares/:userId', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
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' });
|
||||
}
|
||||
await db.run('DELETE FROM room_shares WHERE room_id = ? AND user_id = ?', [room.id, parseInt(req.params.userId)]);
|
||||
const shares = await db.all(`
|
||||
SELECT u.id, u.name, u.email, u.avatar_color, u.avatar_image
|
||||
FROM room_shares rs
|
||||
JOIN users u ON rs.user_id = u.id
|
||||
WHERE rs.room_id = ?
|
||||
`, [room.id]);
|
||||
res.json({ shares });
|
||||
} catch (err) {
|
||||
console.error('Remove share error:', err);
|
||||
res.status(500).json({ error: 'Fehler beim Entfernen der Freigabe' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/rooms/:uid/start - Start meeting
|
||||
router.post('/:uid/start', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const db = getDb();
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ? AND user_id = ?', [req.params.uid, req.user.id]);
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]);
|
||||
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden oder keine Berechtigung' });
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden' });
|
||||
}
|
||||
|
||||
// Check access: owner or shared user
|
||||
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' });
|
||||
}
|
||||
}
|
||||
|
||||
await createMeeting(room, `${req.protocol}://${req.get('host')}`);
|
||||
@@ -239,7 +372,10 @@ router.post('/:uid/join', authenticateToken, async (req, res) => {
|
||||
return res.status(400).json({ error: 'Meeting läuft nicht. Bitte warten Sie, bis der Moderator das Meeting gestartet hat.' });
|
||||
}
|
||||
|
||||
const isModerator = room.user_id === req.user.id || room.all_join_moderator;
|
||||
// Owner and shared users join as moderator
|
||||
const isOwner = room.user_id === req.user.id;
|
||||
const isShared = !isOwner && await db.get('SELECT id FROM room_shares WHERE room_id = ? AND user_id = ?', [room.id, req.user.id]);
|
||||
const isModerator = isOwner || !!isShared || room.all_join_moderator;
|
||||
const avatarURL = getUserAvatarURL(req, req.user);
|
||||
const joinUrl = await joinMeeting(room.uid, req.user.name, isModerator, avatarURL);
|
||||
res.json({ joinUrl });
|
||||
@@ -253,10 +389,19 @@ router.post('/:uid/join', authenticateToken, async (req, res) => {
|
||||
router.post('/:uid/end', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const db = getDb();
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ? AND user_id = ?', [req.params.uid, req.user.id]);
|
||||
const room = await db.get('SELECT * FROM rooms WHERE uid = ?', [req.params.uid]);
|
||||
|
||||
if (!room) {
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden oder keine Berechtigung' });
|
||||
return res.status(404).json({ error: 'Raum nicht gefunden' });
|
||||
}
|
||||
|
||||
// Check access: owner or shared user
|
||||
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' });
|
||||
}
|
||||
}
|
||||
|
||||
await endMeeting(room.uid);
|
||||
|
||||
Reference in New Issue
Block a user