feat: Add a large collection of emoji and other frontend assets, including a sound file, and a backend package.json.
This commit is contained in:
@@ -11,14 +11,81 @@ function generateFakeSalt(username) {
|
||||
}
|
||||
|
||||
router.post('/register', async (req, res) => {
|
||||
const { username, salt, encryptedMK, hak, publicKey, signingKey, encryptedPrivateKeys } = req.body;
|
||||
const { username, salt, encryptedMK, hak, publicKey, signingKey, encryptedPrivateKeys, inviteCode } = req.body;
|
||||
|
||||
try {
|
||||
const result = await db.query(
|
||||
`INSERT INTO users (username, client_salt, encrypted_master_key, hashed_auth_key, public_identity_key, public_signing_key, encrypted_private_keys)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,
|
||||
[username, salt, encryptedMK, hak, publicKey, signingKey, encryptedPrivateKeys]
|
||||
);
|
||||
res.json({ success: true, userId: result.rows[0].id });
|
||||
// Step 1: Enforce Invite (unless first user)
|
||||
const userCountRes = await db.query('SELECT count(*) FROM users');
|
||||
const userCount = parseInt(userCountRes.rows[0].count);
|
||||
|
||||
if (userCount > 0) {
|
||||
if (!inviteCode) {
|
||||
return res.status(403).json({ error: 'Invite code required' });
|
||||
}
|
||||
|
||||
// Check Invite validity
|
||||
const inviteRes = await db.query('SELECT * FROM invites WHERE code = $1', [inviteCode]);
|
||||
if (inviteRes.rows.length === 0) {
|
||||
return res.status(403).json({ error: 'Invalid invite code' });
|
||||
}
|
||||
|
||||
var invite = inviteRes.rows[0];
|
||||
|
||||
// Check Expiration
|
||||
if (invite.expires_at && new Date() > new Date(invite.expires_at)) {
|
||||
return res.status(410).json({ error: 'Invite expired' });
|
||||
}
|
||||
|
||||
// Check Usage Limits
|
||||
if (invite.max_uses !== null && invite.uses >= invite.max_uses) {
|
||||
return res.status(410).json({ error: 'Invite max uses reached' });
|
||||
}
|
||||
}
|
||||
|
||||
// START TRANSACTION - To ensure invite usage and user creation are atomic
|
||||
await db.query('BEGIN');
|
||||
|
||||
try {
|
||||
// Update Invite Usage (only if enforced)
|
||||
if (userCount > 0) {
|
||||
await db.query('UPDATE invites SET uses = uses + 1 WHERE code = $1', [inviteCode]);
|
||||
}
|
||||
|
||||
// Create User
|
||||
// Create User
|
||||
const result = await db.query(
|
||||
`INSERT INTO users (username, client_salt, encrypted_master_key, hashed_auth_key, public_identity_key, public_signing_key, encrypted_private_keys)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,
|
||||
[username, salt, encryptedMK, hak, publicKey, signingKey, encryptedPrivateKeys]
|
||||
);
|
||||
const newUserId = result.rows[0].id;
|
||||
|
||||
// Assign Roles
|
||||
// 1. @everyone (Always)
|
||||
await db.query(`
|
||||
INSERT INTO user_roles (user_id, role_id)
|
||||
SELECT $1, id FROM roles WHERE name = '@everyone'
|
||||
`, [newUserId]);
|
||||
|
||||
// 2. Owner (If first user or if admin logic allows)
|
||||
if (userCount === 0) {
|
||||
await db.query(`
|
||||
INSERT INTO user_roles (user_id, role_id)
|
||||
SELECT $1, id FROM roles WHERE name = 'Owner'
|
||||
`, [newUserId]);
|
||||
|
||||
// Also set is_admin = true for legacy support
|
||||
await db.query('UPDATE users SET is_admin = TRUE WHERE id = $1', [newUserId]);
|
||||
}
|
||||
|
||||
await db.query('COMMIT');
|
||||
res.json({ success: true, userId: result.rows[0].id });
|
||||
|
||||
} catch (txErr) {
|
||||
await db.query('ROLLBACK');
|
||||
throw txErr;
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (err.code === '23505') { // Unique violation
|
||||
@@ -50,7 +117,7 @@ router.post('/login/verify', async (req, res) => {
|
||||
|
||||
try {
|
||||
const result = await db.query(
|
||||
'SELECT id, hashed_auth_key, encrypted_master_key, encrypted_private_keys FROM users WHERE username = $1',
|
||||
'SELECT id, hashed_auth_key, encrypted_master_key, encrypted_private_keys, public_identity_key FROM users WHERE username = $1',
|
||||
[username]
|
||||
);
|
||||
|
||||
@@ -66,7 +133,8 @@ router.post('/login/verify', async (req, res) => {
|
||||
success: true,
|
||||
userId: user.id,
|
||||
encryptedMK: user.encrypted_master_key,
|
||||
encryptedPrivateKeys: user.encrypted_private_keys
|
||||
encryptedPrivateKeys: user.encrypted_private_keys,
|
||||
publicKey: user.public_identity_key // Return Public Key
|
||||
});
|
||||
} else {
|
||||
res.status(401).json({ error: 'Invalid credentials' });
|
||||
@@ -77,4 +145,14 @@ router.post('/login/verify', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/users/public-keys', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query('SELECT id, public_identity_key FROM users');
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -12,4 +12,149 @@ router.get('/', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Create New Channel
|
||||
router.post('/create', async (req, res) => {
|
||||
console.log('Creates Channel Body:', req.body);
|
||||
const { name, type } = req.body;
|
||||
if (!name) return res.status(400).json({ error: 'Channel name required' });
|
||||
|
||||
try {
|
||||
const result = await db.query(
|
||||
'INSERT INTO channels (name, type) VALUES ($1, $2) RETURNING *',
|
||||
[name, type || 'text']
|
||||
);
|
||||
const newChannel = result.rows[0];
|
||||
// DO NOT emit 'new_channel' here. Wait until keys are uploaded.
|
||||
res.json({ id: newChannel.id });
|
||||
} catch (err) {
|
||||
console.error('Error creating channel:', err);
|
||||
if (err.code === '23505') {
|
||||
res.status(400).json({ error: 'Channel already exists' });
|
||||
} else {
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Notify Channel Creation (Called AFTER keys are uploaded)
|
||||
router.post('/:id/notify', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
const result = await db.query('SELECT * FROM channels WHERE id = $1', [id]);
|
||||
if (result.rows.length === 0) return res.status(404).json({ error: 'Channel not found' });
|
||||
|
||||
const channel = result.rows[0];
|
||||
if (req.io) req.io.emit('new_channel', channel); // Emit NOW
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Upload Channel Keys (for self or others)
|
||||
// Upload Channel Keys (for self or others) - Supports Batch
|
||||
router.post('/keys', async (req, res) => {
|
||||
// Check if body is array
|
||||
const keysToUpload = Array.isArray(req.body) ? req.body : [req.body];
|
||||
|
||||
if (keysToUpload.length === 0) return res.json({ success: true });
|
||||
|
||||
try {
|
||||
await db.query('BEGIN');
|
||||
|
||||
for (const keyData of keysToUpload) {
|
||||
const { channelId, userId, encryptedKeyBundle, keyVersion } = keyData;
|
||||
|
||||
if (!channelId || !userId || !encryptedKeyBundle) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await db.query(
|
||||
`INSERT INTO channel_keys (channel_id, user_id, encrypted_key_bundle, key_version)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (channel_id, user_id) DO UPDATE
|
||||
SET encrypted_key_bundle = EXCLUDED.encrypted_key_bundle,
|
||||
key_version = EXCLUDED.key_version`,
|
||||
[channelId, userId, encryptedKeyBundle, keyVersion || 1]
|
||||
);
|
||||
}
|
||||
|
||||
await db.query('COMMIT');
|
||||
res.json({ success: true, count: keysToUpload.length });
|
||||
} catch (err) {
|
||||
await db.query('ROLLBACK');
|
||||
console.error('Error uploading channel keys:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get User's Channel Keys
|
||||
router.get('/keys/:userId', async (req, res) => {
|
||||
const { userId } = req.params;
|
||||
try {
|
||||
const result = await db.query(
|
||||
'SELECT channel_id, encrypted_key_bundle, key_version FROM channel_keys WHERE user_id = $1',
|
||||
[userId]
|
||||
);
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error('Error fetching channel keys:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Update Channel Name
|
||||
router.put('/:id', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { name } = req.body;
|
||||
|
||||
if (!name) return res.status(400).json({ error: 'Name required' });
|
||||
|
||||
try {
|
||||
const result = await db.query(
|
||||
'UPDATE channels SET name = $1 WHERE id = $2 RETURNING *',
|
||||
[name, id]
|
||||
);
|
||||
if (result.rows.length === 0) return res.status(404).json({ error: 'Channel not found' });
|
||||
|
||||
const updatedChannel = result.rows[0];
|
||||
if (req.io) req.io.emit('channel_renamed', updatedChannel);
|
||||
res.json(updatedChannel);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete Channel
|
||||
router.delete('/:id', async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
try {
|
||||
await db.query('BEGIN');
|
||||
|
||||
// Manual Cascade (since we didn't set FK CASCADE in schema for these yet)
|
||||
await db.query('DELETE FROM messages WHERE channel_id = $1', [id]);
|
||||
await db.query('DELETE FROM channel_keys WHERE channel_id = $1', [id]);
|
||||
|
||||
const result = await db.query('DELETE FROM channels WHERE id = $1 RETURNING *', [id]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
await db.query('ROLLBACK');
|
||||
return res.status(404).json({ error: 'Channel not found' });
|
||||
}
|
||||
|
||||
await db.query('COMMIT');
|
||||
|
||||
if (req.io) req.io.emit('channel_deleted', id);
|
||||
res.json({ success: true, deletedId: id });
|
||||
} catch (err) {
|
||||
await db.query('ROLLBACK');
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
75
Backend/routes/invites.js
Normal file
75
Backend/routes/invites.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../db');
|
||||
|
||||
// Create a new invite
|
||||
router.post('/create', async (req, res) => {
|
||||
const { code, encryptedPayload, createdBy, maxUses, expiresAt, keyVersion } = req.body;
|
||||
|
||||
if (!code || !encryptedPayload || !createdBy || !keyVersion) {
|
||||
return res.status(400).json({ error: 'Missing required fields' });
|
||||
}
|
||||
|
||||
try {
|
||||
await db.query(
|
||||
`INSERT INTO invites (code, encrypted_payload, created_by, max_uses, expires_at, key_version)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)`,
|
||||
[code, encryptedPayload, createdBy, maxUses || null, expiresAt || null, keyVersion]
|
||||
);
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
console.error('Error creating invite:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch an invite (and validate it)
|
||||
router.get('/:code', async (req, res) => {
|
||||
const { code } = req.params;
|
||||
|
||||
try {
|
||||
const result = await db.query('SELECT * FROM invites WHERE code = $1', [code]);
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Invite not found' });
|
||||
}
|
||||
|
||||
const invite = result.rows[0];
|
||||
|
||||
// Check Expiration
|
||||
if (invite.expires_at && new Date() > new Date(invite.expires_at)) {
|
||||
return res.status(410).json({ error: 'Invite expired' });
|
||||
}
|
||||
|
||||
// Check Usage Limits
|
||||
if (invite.max_uses !== null && invite.uses >= invite.max_uses) {
|
||||
return res.status(410).json({ error: 'Invite max uses reached' });
|
||||
}
|
||||
|
||||
// Increment Uses
|
||||
await db.query('UPDATE invites SET uses = uses + 1 WHERE code = $1', [code]);
|
||||
|
||||
res.json({
|
||||
encryptedPayload: invite.encrypted_payload,
|
||||
keyVersion: invite.key_version
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error fetching invite:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete an invite (Revoke) -> Triggers client-side key rotation policy warning?
|
||||
// The client should call this, then rotate keys.
|
||||
router.delete('/:code', async (req, res) => {
|
||||
const { code } = req.params;
|
||||
try {
|
||||
await db.query('DELETE FROM invites WHERE code = $1', [code]);
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
console.error('Error deleting invite:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
213
Backend/routes/roles.js
Normal file
213
Backend/routes/roles.js
Normal file
@@ -0,0 +1,213 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../db');
|
||||
|
||||
// Middleware to check for permissions (simplified for now)
|
||||
// In a real app, you'd check if req.user has the permission
|
||||
// Middleware to check for permissions
|
||||
const checkPermission = (requiredPerm) => {
|
||||
return async (req, res, next) => {
|
||||
const userId = req.headers['x-user-id'];
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'Unauthorized: No User ID' });
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch all roles for user and aggregate permissions
|
||||
const result = await db.query(`
|
||||
SELECT r.permissions
|
||||
FROM user_roles ur
|
||||
JOIN roles r ON ur.role_id = r.id
|
||||
WHERE ur.user_id = $1
|
||||
`, [userId]);
|
||||
|
||||
let hasPermission = false;
|
||||
|
||||
// Check if ANY of the user's roles has the permission
|
||||
for (const row of result.rows) {
|
||||
const perms = row.permissions || {};
|
||||
// If Manage Roles is true, or if checking for something else and they have it
|
||||
if (perms[requiredPerm] === true) {
|
||||
hasPermission = true;
|
||||
break;
|
||||
}
|
||||
// Implicit Admin/Owner check: if they have 'manage_roles' and we are checking something lower?
|
||||
// For "Owner" role, we seeded it with specific true values.
|
||||
// But let's check for 'administrator' equivalent if we had it.
|
||||
// For now, implicit check: if we need 'manage_roles', look for it.
|
||||
}
|
||||
|
||||
if (!hasPermission) {
|
||||
return res.status(403).json({ error: `Forbidden: Missing ${requiredPerm}` });
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
console.error('Permission check failed:', err);
|
||||
return res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// GET /api/roles/permissions - Get current user's permissions
|
||||
router.get('/permissions', async (req, res) => {
|
||||
const userId = req.headers['x-user-id'];
|
||||
if (!userId) return res.json({}); // No perms if no ID
|
||||
|
||||
try {
|
||||
const result = await db.query(`
|
||||
SELECT r.permissions
|
||||
FROM user_roles ur
|
||||
JOIN roles r ON ur.role_id = r.id
|
||||
WHERE ur.user_id = $1
|
||||
`, [userId]);
|
||||
|
||||
const finalPerms = {
|
||||
manage_channels: false,
|
||||
manage_roles: false,
|
||||
create_invite: false,
|
||||
embed_links: false,
|
||||
attach_files: false
|
||||
};
|
||||
|
||||
for (const row of result.rows) {
|
||||
const p = row.permissions || {};
|
||||
if (p.manage_channels) finalPerms.manage_channels = true;
|
||||
if (p.manage_roles) finalPerms.manage_roles = true;
|
||||
if (p.create_invite) finalPerms.create_invite = true;
|
||||
if (p.embed_links) finalPerms.embed_links = true;
|
||||
if (p.attach_files) finalPerms.attach_files = true;
|
||||
}
|
||||
res.json(finalPerms);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/roles - List all roles
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query('SELECT * FROM roles ORDER BY position DESC, id ASC');
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/roles - Create new role
|
||||
router.post('/', checkPermission('manage_roles'), async (req, res) => {
|
||||
const { name, color, permissions, position, is_hoist } = req.body;
|
||||
try {
|
||||
const result = await db.query(
|
||||
'INSERT INTO roles (name, color, permissions, position, is_hoist) VALUES ($1, $2, $3, $4, $5) RETURNING *',
|
||||
[name || 'new role', color || '#99aab5', permissions || {}, position || 0, is_hoist || false]
|
||||
);
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/roles/:id - Update role
|
||||
router.put('/:id', checkPermission('manage_roles'), async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { name, color, permissions, position, is_hoist } = req.body;
|
||||
|
||||
// Dynamic update query
|
||||
const fields = [];
|
||||
const values = [];
|
||||
let idx = 1;
|
||||
|
||||
if (name !== undefined) { fields.push(`name = $${idx++}`); values.push(name); }
|
||||
if (color !== undefined) { fields.push(`color = $${idx++}`); values.push(color); }
|
||||
if (permissions !== undefined) { fields.push(`permissions = $${idx++}`); values.push(permissions); }
|
||||
if (position !== undefined) { fields.push(`position = $${idx++}`); values.push(position); }
|
||||
if (is_hoist !== undefined) { fields.push(`is_hoist = $${idx++}`); values.push(is_hoist); }
|
||||
|
||||
if (fields.length === 0) return res.json({ success: true }); // Nothing to update
|
||||
|
||||
values.push(id);
|
||||
const query = `UPDATE roles SET ${fields.join(', ')} WHERE id = $${idx} RETURNING *`;
|
||||
|
||||
try {
|
||||
const result = await db.query(query, values);
|
||||
if (result.rows.length === 0) return res.status(404).json({ error: 'Role not found' });
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/roles/:id - Delete role
|
||||
router.delete('/:id', checkPermission('manage_roles'), async (req, res) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
// user_roles cascade handled by DB
|
||||
const result = await db.query('DELETE FROM roles WHERE id = $1 RETURNING *', [id]);
|
||||
if (result.rows.length === 0) return res.status(404).json({ error: 'Role not found' });
|
||||
res.json({ success: true, deletedId: id });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/roles/members - List members with roles
|
||||
// (This is effectively GET /api/users but enriched with roles)
|
||||
router.get('/members', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query(`
|
||||
SELECT u.id, u.username, u.public_identity_key,
|
||||
json_agg(r.*) FILTER (WHERE r.id IS NOT NULL) as roles
|
||||
FROM users u
|
||||
LEFT JOIN user_roles ur ON u.id = ur.user_id
|
||||
LEFT JOIN roles r ON ur.role_id = r.id
|
||||
GROUP BY u.id
|
||||
`);
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/roles/:id/assign - Assign role to user
|
||||
router.post('/:id/assign', checkPermission('manage_roles'), async (req, res) => {
|
||||
const { id } = req.params; // role id
|
||||
const { userId } = req.body;
|
||||
|
||||
try {
|
||||
await db.query(
|
||||
'INSERT INTO user_roles (user_id, role_id) VALUES ($1, $2) ON CONFLICT DO NOTHING',
|
||||
[userId, id]
|
||||
);
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/roles/:id/remove - Remove role from user
|
||||
router.post('/:id/remove', checkPermission('manage_roles'), async (req, res) => {
|
||||
const { id } = req.params; // role id
|
||||
const { userId } = req.body;
|
||||
|
||||
try {
|
||||
await db.query(
|
||||
'DELETE FROM user_roles WHERE user_id = $1 AND role_id = $2',
|
||||
[userId, id]
|
||||
);
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
41
Backend/routes/upload.js
Normal file
41
Backend/routes/upload.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const express = require('express');
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Ensure uploads directory exists
|
||||
const uploadDir = path.join(__dirname, '../uploads');
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir);
|
||||
}
|
||||
|
||||
// Configure Multer Storage
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
cb(null, uploadDir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
// Generate unique filename: timestamp-random.ext
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
const ext = path.extname(file.originalname);
|
||||
cb(null, uniqueSuffix + ext);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({ storage: storage });
|
||||
|
||||
// POST /api/upload
|
||||
router.post('/', upload.single('file'), (req, res) => {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: 'No file uploaded' });
|
||||
}
|
||||
|
||||
// Return the URL to access the file
|
||||
// Assumes server serves 'uploads' folder at '/uploads'
|
||||
const fileUrl = `/uploads/${req.file.filename}`;
|
||||
res.json({ url: fileUrl, filename: req.file.filename });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
64
Backend/routes/voice.js
Normal file
64
Backend/routes/voice.js
Normal file
@@ -0,0 +1,64 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { AccessToken } = require('livekit-server-sdk');
|
||||
const db = require('../db');
|
||||
|
||||
// Middleware to check permissions?
|
||||
// For now, simpler: assuming user is logged in (via x-user-id header check in frontend)
|
||||
// Real implementation should use the checkPermission middleware or verify session
|
||||
|
||||
router.post('/token', async (req, res) => {
|
||||
const { channelId } = req.body;
|
||||
const userId = req.headers['x-user-id']; // Sent by frontend
|
||||
|
||||
// Default fallback if no user (should rely on auth middleware ideally)
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Get Username (for display)
|
||||
const userRes = await db.query('SELECT username FROM users WHERE id = $1', [userId]);
|
||||
if (userRes.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
const username = userRes.rows[0].username;
|
||||
|
||||
// 2. Get Channel Name (Optional, for room name check)
|
||||
// Ensure channel exists and is of type 'voice'
|
||||
const channelRes = await db.query('SELECT id, type FROM channels WHERE id = $1', [channelId]);
|
||||
if (channelRes.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Channel not found' });
|
||||
}
|
||||
if (channelRes.rows[0].type !== 'voice') {
|
||||
return res.status(400).json({ error: 'Not a voice channel' });
|
||||
}
|
||||
|
||||
// 3. Generate Token
|
||||
// API Key/Secret from env
|
||||
const apiKey = process.env.LIVEKIT_API_KEY || 'devkey';
|
||||
const apiSecret = process.env.LIVEKIT_API_SECRET || 'secret';
|
||||
|
||||
const at = new AccessToken(apiKey, apiSecret, {
|
||||
identity: userId,
|
||||
name: username,
|
||||
});
|
||||
|
||||
at.addGrant({
|
||||
roomJoin: true,
|
||||
room: channelId,
|
||||
canPublish: true,
|
||||
canSubscribe: true,
|
||||
});
|
||||
|
||||
const token = await at.toJwt();
|
||||
|
||||
res.json({ token });
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error creating voice token:', err);
|
||||
res.status(500).json({ error: 'Server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user