Automated deployment
parent
8bf19c5518
commit
e11c95b891
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"database": {
|
||||
"type": "sqlite",
|
||||
"storage": "epyks.db"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"database": {
|
||||
"type": "sqlite",
|
||||
"storage": "epyks.db"
|
||||
}
|
||||
}
|
||||
{
|
||||
"database": {
|
||||
"type": "mysql",
|
||||
"host": "localhost",
|
||||
"database": "epyks",
|
||||
"username": "your_username",
|
||||
"password": "your_password"
|
||||
}
|
||||
}
|
||||
{
|
||||
"database": {
|
||||
"type": "postgres",
|
||||
"host": "localhost",
|
||||
"database": "epyks",
|
||||
"username": "your_username",
|
||||
"password": "your_password"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Set variables
|
||||
REPO_URL="https://github.com/Cybernomad/epkys.git"
|
||||
REMOTE_SERVER_IP="82.197.95.253"
|
||||
USERNAME="jeremy"
|
||||
PASSWORD="Chickenkil13r!"
|
||||
|
||||
# Update and push to the remote repository
|
||||
git add .
|
||||
git commit -m "Automated deployment"
|
||||
git push origin main
|
||||
|
||||
# Clone or pull from the remote server
|
||||
if [ ! -d "$REMOTE_SERVER_IP" ]; then
|
||||
git clone $REPO_URL
|
||||
else
|
||||
cd /home/$USERNAME/
|
||||
git pull origin main
|
||||
fi
|
||||
|
||||
# Navigate to the cloned project directory
|
||||
cd /home/$USERNAME/your_project_name
|
||||
|
||||
# Run the development server
|
||||
npm run dev
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "epyks",
|
||||
"version": "1.0.0",
|
||||
"description": "a server for epyks",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node server.js"
|
||||
},
|
||||
"author": "Jeremy Hayes",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^5.1.0",
|
||||
"fido2-lib": "^3.5.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"multer": "^1.4.5-lts.2",
|
||||
"mysql2": "^3.14.0",
|
||||
"pg": "^8.15.5",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"sequelize": "^6.37.7",
|
||||
"socket.io": "^4.8.1",
|
||||
"sqlite3": "^5.1.7"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,798 @@
|
|||
const express = require('express');
|
||||
const app = express();
|
||||
const http = require('http').createServer(app);
|
||||
const io = require('socket.io')(http);
|
||||
const { Sequelize, DataTypes } = require('sequelize');
|
||||
const fs = require('fs');
|
||||
const bcrypt = require('bcrypt');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
require('dotenv').config();
|
||||
|
||||
// Load configuration
|
||||
const config = JSON.parse(fs.readFileSync('config.json'));
|
||||
|
||||
// Initialize Sequelize
|
||||
let sequelize;
|
||||
if (config.database.type === 'sqlite') {
|
||||
sequelize = new Sequelize({
|
||||
dialect: 'sqlite',
|
||||
storage: config.database.storage,
|
||||
});
|
||||
} else if (config.database.type === 'mysql') {
|
||||
sequelize = new Sequelize(
|
||||
config.database.database,
|
||||
config.database.username,
|
||||
config.database.password,
|
||||
{
|
||||
host: config.database.host,
|
||||
dialect: 'mysql',
|
||||
}
|
||||
);
|
||||
} else if (config.database.type === 'postgres') {
|
||||
sequelize = new Sequelize(
|
||||
config.database.database,
|
||||
config.database.username,
|
||||
config.database.password,
|
||||
{
|
||||
host: config.database.host,
|
||||
dialect: 'postgres',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Define models
|
||||
const User = sequelize.define('User', {
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true,
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
unique: true,
|
||||
allowNull: false,
|
||||
},
|
||||
nickname: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: 'permanent',
|
||||
},
|
||||
expiresAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
avatarUrl: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
});
|
||||
|
||||
const Contact = sequelize.define('Contact', {
|
||||
userId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
contactId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
contactEmail: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
contactNickname: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
});
|
||||
|
||||
const Room = sequelize.define('Room', {
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
creatorId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
});
|
||||
|
||||
const Message = sequelize.define('Message', {
|
||||
senderId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
receiverId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
roomId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
senderNickname: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
message: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
timestamp: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
avatarUrl: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
fileUrl: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
fileName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
reactions: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
defaultValue: {},
|
||||
},
|
||||
});
|
||||
|
||||
const Meeting = sequelize.define('Meeting', {
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true,
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
scheduledTime: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
},
|
||||
duration: {
|
||||
type: DataTypes.INTEGER, // Duration in minutes
|
||||
allowNull: false,
|
||||
},
|
||||
hostId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
participants: {
|
||||
type: DataTypes.JSON, // List of user IDs in the meeting
|
||||
allowNull: true,
|
||||
defaultValue: [],
|
||||
},
|
||||
waitingRoom: {
|
||||
type: DataTypes.JSON, // List of user IDs in the waiting room
|
||||
allowNull: true,
|
||||
defaultValue: [],
|
||||
},
|
||||
breakoutRooms: {
|
||||
type: DataTypes.JSON, // Map of breakout room IDs to user IDs
|
||||
allowNull: true,
|
||||
defaultValue: {},
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: 'scheduled', // scheduled, active, ended
|
||||
},
|
||||
});
|
||||
|
||||
// Sync database
|
||||
sequelize.sync({ alter: true }).then(() => {
|
||||
console.log('Database synced');
|
||||
});
|
||||
|
||||
// Set up file upload
|
||||
const upload = multer({
|
||||
dest: 'uploads/',
|
||||
fileFilter: (req, file, cb) => {
|
||||
console.log('Received file with MIME type:', file.mimetype);
|
||||
|
||||
const allowedMimeTypes = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/bmp',
|
||||
'image/webp',
|
||||
'application/pdf',
|
||||
'text/plain',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
];
|
||||
|
||||
if (allowedMimeTypes.includes(file.mimetype)) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
const extname = path.extname(file.originalname).toLowerCase();
|
||||
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.pdf', '.txt', '.doc', '.docx'];
|
||||
if (allowedExtensions.includes(extname)) {
|
||||
console.log('MIME type not recognized, but file extension is valid:', extname);
|
||||
cb(null, true);
|
||||
} else {
|
||||
console.log('File rejected: Not an allowed file type');
|
||||
cb(new Error('Only images, PDFs, text, and Word documents are allowed'), false);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Serve uploaded files statically
|
||||
app.use('/uploads', express.static('uploads'));
|
||||
|
||||
// JWT secret from environment variable
|
||||
const JWT_SECRET = process.env.JWT_SECRET;
|
||||
if (!JWT_SECRET) {
|
||||
console.error('JWT_SECRET is not defined in .env file');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Using JWT_SECRET:', JWT_SECRET);
|
||||
|
||||
// Load BASE_URL from environment variable
|
||||
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
|
||||
console.log('Using BASE_URL:', BASE_URL);
|
||||
|
||||
// JWT middleware
|
||||
const authenticateJWT = async (req, res, next) => {
|
||||
const token = req.headers.authorization?.split(' ')[1];
|
||||
if (!token) {
|
||||
console.log('No token provided');
|
||||
return res.status(401).json({ error: 'Unauthorized: No token provided' });
|
||||
}
|
||||
try {
|
||||
console.log('Verifying token with JWT_SECRET:', JWT_SECRET);
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
console.log('JWT decoded successfully:', decoded);
|
||||
req.userId = decoded.userId;
|
||||
next();
|
||||
} catch (e) {
|
||||
console.log('Invalid token:', e.message);
|
||||
res.status(401).json({ error: `Invalid token: ${e.message}` });
|
||||
}
|
||||
};
|
||||
|
||||
// Middleware
|
||||
app.use(express.json());
|
||||
|
||||
// API endpoints
|
||||
app.post('/auth/login', async (req, res) => {
|
||||
const { username, password } = req.body; // Username is the email
|
||||
console.log('Login attempt:', { username });
|
||||
const user = await User.findOne({ where: { email: username } });
|
||||
if (!user || !user.password) {
|
||||
console.log('User not found or no password');
|
||||
return res.status(401).json({ error: 'Invalid email or password' });
|
||||
}
|
||||
const isValid = await bcrypt.compare(password, user.password);
|
||||
if (!isValid) {
|
||||
console.log('Invalid password');
|
||||
return res.status(401).json({ error: 'Invalid email or password' });
|
||||
}
|
||||
console.log('Generating token with userId:', user.id);
|
||||
console.log('Signing token with JWT_SECRET:', JWT_SECRET);
|
||||
const token = jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '7d' });
|
||||
console.log('Generated JWT for login:', token);
|
||||
res.json({ token });
|
||||
});
|
||||
|
||||
app.post('/auth/register', async (req, res) => {
|
||||
const { username, nickname, password } = req.body; // Username is the email
|
||||
console.log('Register attempt:', { username, nickname });
|
||||
try {
|
||||
const id = uuidv4();
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
const user = await User.create({
|
||||
id,
|
||||
email: username,
|
||||
nickname,
|
||||
password: hashedPassword,
|
||||
type: 'permanent',
|
||||
});
|
||||
console.log('Generating token with userId:', id);
|
||||
console.log('Signing token with JWT_SECRET:', JWT_SECRET);
|
||||
const token = jwt.sign({ userId: id }, JWT_SECRET, { expiresIn: '7d' });
|
||||
console.log('Generated JWT for register:', token);
|
||||
res.status(201).json({ token });
|
||||
} catch (e) {
|
||||
console.log('Registration error:', e.message);
|
||||
res.status(400).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/auth/burner', async (req, res) => {
|
||||
try {
|
||||
const id = uuidv4();
|
||||
const email = `guest_${id.substring(0, 8)}@epyks.com`;
|
||||
const user = await User.create({
|
||||
id,
|
||||
email,
|
||||
nickname: `Guest_${id.substring(0, 8)}`,
|
||||
type: 'burner',
|
||||
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
||||
});
|
||||
console.log('Generating token with userId:', id);
|
||||
console.log('Signing token with JWT_SECRET:', JWT_SECRET);
|
||||
const token = jwt.sign({ userId: id }, JWT_SECRET, { expiresIn: '24h' });
|
||||
console.log('Generated JWT for burner:', token);
|
||||
res.status(201).json({ token });
|
||||
} catch (e) {
|
||||
console.log('Burner account error:', e.message);
|
||||
res.status(400).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/users/me', authenticateJWT, async (req, res) => {
|
||||
const user = await User.findByPk(req.userId);
|
||||
if (user) {
|
||||
res.json(user);
|
||||
} else {
|
||||
console.log('User not found for ID:', req.userId);
|
||||
res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/users/:id', authenticateJWT, async (req, res) => {
|
||||
const user = await User.findByPk(req.params.id);
|
||||
if (user) {
|
||||
res.json(user);
|
||||
} else {
|
||||
res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/users', authenticateJWT, async (req, res) => {
|
||||
const { email } = req.query;
|
||||
const user = await User.findOne({ where: { email } });
|
||||
if (user) {
|
||||
res.json(user);
|
||||
} else {
|
||||
res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/contacts', authenticateJWT, async (req, res) => {
|
||||
const { userId } = req.query;
|
||||
const contacts = await Contact.findAll({ where: { userId } });
|
||||
res.json(contacts);
|
||||
});
|
||||
|
||||
app.post('/contacts', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.create(req.body);
|
||||
res.status(201).json(contact);
|
||||
} catch (e) {
|
||||
res.status(400).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/rooms', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const rooms = await Room.findAll();
|
||||
res.status(200).json(rooms);
|
||||
} catch (e) {
|
||||
console.log('Error fetching rooms:', e.message);
|
||||
res.status(500).json({ error: 'Server error fetching rooms' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/rooms', authenticateJWT, async (req, res) => {
|
||||
const { name } = req.body;
|
||||
if (!name) {
|
||||
return res.status(400).json({ error: 'Room name is required' });
|
||||
}
|
||||
try {
|
||||
const id = uuidv4();
|
||||
const room = await Room.create({
|
||||
id,
|
||||
name,
|
||||
creatorId: req.userId,
|
||||
});
|
||||
res.status(201).json(room);
|
||||
} catch (e) {
|
||||
console.log('Error creating room:', e.message);
|
||||
res.status(400).json({ error: 'Error creating room' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/messages', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const message = await Message.create(req.body);
|
||||
res.status(201).json(message);
|
||||
} catch (e) {
|
||||
console.log('Error creating message:', e.message);
|
||||
res.status(400).json({ error: 'Error creating message' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/messages', authenticateJWT, async (req, res) => {
|
||||
const { roomId } = req.query;
|
||||
if (!roomId) {
|
||||
return res.status(400).json({ error: 'roomId is required' });
|
||||
}
|
||||
try {
|
||||
const messages = await Message.findAll({ where: { roomId } });
|
||||
res.status(200).json(messages);
|
||||
} catch (e) {
|
||||
console.log('Error fetching messages:', e.message);
|
||||
res.status(500).json({ error: 'Server error fetching messages' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/users/update', authenticateJWT, upload.single('avatar'), async (req, res) => {
|
||||
try {
|
||||
const user = await User.findByPk(req.userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
if (req.file) {
|
||||
const avatarPath = `/uploads/${req.file.filename}`;
|
||||
const avatarUrl = `${BASE_URL}${avatarPath}`;
|
||||
console.log('Avatar uploaded successfully:', avatarUrl);
|
||||
await user.update({ avatarUrl });
|
||||
res.json({ avatarUrl });
|
||||
} else {
|
||||
console.log('No file uploaded');
|
||||
res.status(400).json({ error: 'No file uploaded' });
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error in /users/update:', e.message);
|
||||
res.status(400).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/messages/upload', authenticateJWT, upload.single('file'), async (req, res) => {
|
||||
try {
|
||||
const { roomId, senderId, senderNickname, timestamp } = req.body;
|
||||
if (!roomId || !senderId || !senderNickname || !timestamp) {
|
||||
return res.status(400).json({ error: 'Missing required fields' });
|
||||
}
|
||||
if (req.file) {
|
||||
const filePath = `/uploads/${req.file.filename}`;
|
||||
const fileUrl = `${BASE_URL}${filePath}`;
|
||||
const fileName = req.file.originalname;
|
||||
console.log('File uploaded successfully:', fileUrl);
|
||||
|
||||
const message = await Message.create({
|
||||
senderId,
|
||||
receiverId: senderId,
|
||||
roomId,
|
||||
senderNickname,
|
||||
timestamp,
|
||||
fileUrl,
|
||||
fileName,
|
||||
});
|
||||
|
||||
io.to(roomId).emit('chat-message', {
|
||||
id: message.id,
|
||||
senderId,
|
||||
senderNickname,
|
||||
timestamp,
|
||||
roomId,
|
||||
fileUrl,
|
||||
fileName,
|
||||
reactions: message.reactions || {},
|
||||
});
|
||||
|
||||
res.json({ fileUrl, fileName });
|
||||
} else {
|
||||
console.log('No file uploaded');
|
||||
res.status(400).json({ error: 'No file uploaded' });
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error in /messages/upload:', e.message);
|
||||
res.status(400).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/messages/:id/react', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { emoji } = req.body;
|
||||
const userId = req.userId;
|
||||
|
||||
if (!emoji) {
|
||||
return res.status(400).json({ error: 'Emoji is required' });
|
||||
}
|
||||
|
||||
const message = await Message.findByPk(id);
|
||||
if (!message) {
|
||||
return res.status(404).json({ error: 'Message not found' });
|
||||
}
|
||||
|
||||
let reactions = message.reactions || {};
|
||||
|
||||
if (reactions[emoji]) {
|
||||
const userIndex = reactions[emoji].indexOf(userId);
|
||||
if (userIndex !== -1) {
|
||||
reactions[emoji].splice(userIndex, 1);
|
||||
if (reactions[emoji].length === 0) {
|
||||
delete reactions[emoji];
|
||||
}
|
||||
} else {
|
||||
reactions[emoji].push(userId);
|
||||
}
|
||||
} else {
|
||||
reactions[emoji] = [userId];
|
||||
}
|
||||
|
||||
await message.update({ reactions });
|
||||
|
||||
io.to(message.roomId).emit('reaction-update', {
|
||||
messageId: id,
|
||||
reactions: message.reactions,
|
||||
});
|
||||
|
||||
res.status(200).json({ reactions: message.reactions });
|
||||
} catch (e) {
|
||||
console.log('Error in /messages/:id/react:', e.message);
|
||||
res.status(400).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Meeting Management Endpoints
|
||||
app.post('/meetings/schedule', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { title, scheduledTime, duration } = req.body;
|
||||
if (!title || !scheduledTime || !duration) {
|
||||
return res.status(400).json({ error: 'Title, scheduledTime, and duration are required' });
|
||||
}
|
||||
|
||||
const id = uuidv4();
|
||||
const meeting = await Meeting.create({
|
||||
id,
|
||||
title,
|
||||
scheduledTime,
|
||||
duration,
|
||||
hostId: req.userId,
|
||||
status: 'scheduled',
|
||||
});
|
||||
|
||||
res.status(201).json(meeting);
|
||||
} catch (e) {
|
||||
console.log('Error scheduling meeting:', e.message);
|
||||
res.status(400).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/meetings', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const meetings = await Meeting.findAll({
|
||||
where: {
|
||||
[Sequelize.Op.or]: [
|
||||
{ hostId: req.userId },
|
||||
{ participants: { [Sequelize.Op.contains]: [req.userId] } },
|
||||
],
|
||||
},
|
||||
});
|
||||
res.status(200).json(meetings);
|
||||
} catch (e) {
|
||||
console.log('Error fetching meetings:', e.message);
|
||||
res.status(500).json({ error: 'Server error fetching meetings' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/meetings/:id/join', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = req.userId;
|
||||
|
||||
const meeting = await Meeting.findByPk(id);
|
||||
if (!meeting) {
|
||||
return res.status(404).json({ error: 'Meeting not found' });
|
||||
}
|
||||
|
||||
if (meeting.hostId === userId) {
|
||||
// Host joins directly
|
||||
let participants = meeting.participants || [];
|
||||
if (!participants.includes(userId)) {
|
||||
participants.push(userId);
|
||||
await meeting.update({ participants, status: 'active' });
|
||||
}
|
||||
io.to(id).emit('participant-admitted', { userId, meetingId: id });
|
||||
res.status(200).json(meeting);
|
||||
} else {
|
||||
// Non-host joins the waiting room
|
||||
let waitingRoom = meeting.waitingRoom || [];
|
||||
if (!waitingRoom.includes(userId) && !meeting.participants.includes(userId)) {
|
||||
waitingRoom.push(userId);
|
||||
await meeting.update({ waitingRoom });
|
||||
io.to(id).emit('waiting-room-update', { meetingId: id, waitingRoom });
|
||||
}
|
||||
res.status(200).json({ status: 'waiting', meeting });
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error joining meeting:', e.message);
|
||||
res.status(400).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/meetings/:id/admit', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { userId } = req.body;
|
||||
const hostId = req.userId;
|
||||
|
||||
const meeting = await Meeting.findByPk(id);
|
||||
if (!meeting) {
|
||||
return res.status(404).json({ error: 'Meeting not found' });
|
||||
}
|
||||
|
||||
if (meeting.hostId !== hostId) {
|
||||
return res.status(403).json({ error: 'Only the host can admit participants' });
|
||||
}
|
||||
|
||||
let waitingRoom = meeting.waitingRoom || [];
|
||||
let participants = meeting.participants || [];
|
||||
|
||||
const userIndex = waitingRoom.indexOf(userId);
|
||||
if (userIndex !== -1) {
|
||||
waitingRoom.splice(userIndex, 1);
|
||||
participants.push(userId);
|
||||
await meeting.update({ waitingRoom, participants });
|
||||
io.to(id).emit('waiting-room-update', { meetingId: id, waitingRoom });
|
||||
io.to(id).emit('participant-admitted', { userId, meetingId: id });
|
||||
}
|
||||
|
||||
res.status(200).json(meeting);
|
||||
} catch (e) {
|
||||
console.log('Error admitting participant:', e.message);
|
||||
res.status(400).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/meetings/:id/breakout', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { rooms } = req.body; // { room1: [userId1, userId2], room2: [userId3] }
|
||||
const hostId = req.userId;
|
||||
|
||||
const meeting = await Meeting.findByPk(id);
|
||||
if (!meeting) {
|
||||
return res.status(404).json({ error: 'Meeting not found' });
|
||||
}
|
||||
|
||||
if (meeting.hostId !== hostId) {
|
||||
return res.status(403).json({ error: 'Only the host can create breakout rooms' });
|
||||
}
|
||||
|
||||
await meeting.update({ breakoutRooms: rooms });
|
||||
io.to(id).emit('breakout-rooms-update', { meetingId: id, breakoutRooms: rooms });
|
||||
|
||||
res.status(200).json(meeting);
|
||||
} catch (e) {
|
||||
console.log('Error creating breakout rooms:', e.message);
|
||||
res.status(400).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup expired burner accounts
|
||||
setInterval(async () => {
|
||||
await User.destroy({
|
||||
where: {
|
||||
type: 'burner',
|
||||
expiresAt: { [Sequelize.Op.lt]: new Date() },
|
||||
},
|
||||
});
|
||||
}, 60 * 60 * 1000);
|
||||
|
||||
// Socket.io with JWT
|
||||
io.use((socket, next) => {
|
||||
const token = socket.handshake.auth.token;
|
||||
if (!token) {
|
||||
return next(new Error('Authentication error'));
|
||||
}
|
||||
try {
|
||||
console.log('Verifying Socket.io token with JWT_SECRET:', JWT_SECRET);
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
socket.userId = decoded.userId;
|
||||
next();
|
||||
} catch (e) {
|
||||
console.log('Socket.io auth error:', e.message);
|
||||
next(new Error('Authentication error'));
|
||||
}
|
||||
});
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
console.log('User connected:', socket.userId);
|
||||
|
||||
socket.on('join', (data) => {
|
||||
socket.join(data.userId);
|
||||
socket.broadcast.emit('user-joined', { userId: data.userId });
|
||||
});
|
||||
|
||||
socket.on('join-room', (data) => {
|
||||
const { roomId } = data;
|
||||
socket.join(roomId);
|
||||
console.log(`User ${socket.userId} joined room ${roomId}`);
|
||||
});
|
||||
|
||||
socket.on('join-meeting', (data) => {
|
||||
const { meetingId } = data;
|
||||
socket.join(meetingId);
|
||||
console.log(`User ${socket.userId} joined meeting ${meetingId}`);
|
||||
});
|
||||
|
||||
socket.on('leave-room', (data) => {
|
||||
const { roomId } = data;
|
||||
socket.leave(roomId);
|
||||
console.log(`User ${socket.userId} left room ${roomId}`);
|
||||
});
|
||||
|
||||
socket.on('leave-meeting', (data) => {
|
||||
const { meetingId } = data;
|
||||
socket.leave(meetingId);
|
||||
console.log(`User ${socket.userId} left meeting ${meetingId}`);
|
||||
});
|
||||
|
||||
socket.on('offer', (data) => {
|
||||
socket.to(data.to).emit('offer', {
|
||||
sdp: data.sdp,
|
||||
type: data.type,
|
||||
from: data.from,
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('answer', (data) => {
|
||||
socket.to(data.to).emit('answer', { sdp: data.sdp, type: data.type });
|
||||
});
|
||||
|
||||
socket.on('ice-candidate', (data) => {
|
||||
socket.to(data.to).emit('ice-candidate', { candidate: data.candidate });
|
||||
});
|
||||
|
||||
socket.on('chat-message', async (data) => {
|
||||
const { roomId } = data;
|
||||
const message = await Message.create({
|
||||
senderId: data.senderId,
|
||||
receiverId: data.senderId,
|
||||
roomId: data.roomId,
|
||||
senderNickname: data.senderNickname,
|
||||
message: data.message,
|
||||
timestamp: data.timestamp,
|
||||
avatarUrl: data.avatarUrl,
|
||||
fileUrl: data.fileUrl,
|
||||
fileName: data.fileName,
|
||||
});
|
||||
|
||||
io.to(roomId).emit('chat-message', {
|
||||
id: message.id,
|
||||
senderId: data.senderId,
|
||||
senderNickname: data.senderNickname,
|
||||
message: data.message,
|
||||
timestamp: data.timestamp,
|
||||
roomId: data.roomId,
|
||||
avatarUrl: data.avatarUrl,
|
||||
fileUrl: data.fileUrl,
|
||||
fileName: data.fileName,
|
||||
reactions: message.reactions || {},
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('User disconnected:', socket.userId);
|
||||
});
|
||||
});
|
||||
|
||||
http.listen(3000, () => {
|
||||
console.log('Server running on port 3000');
|
||||
});
|
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 185 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"id": "1745805594032",
|
||||
"email": "Jeremy@c4b3r.studio",
|
||||
"password": "$2b$10$KXAZ2AQ/bbCtIfqHTKIur.oq6wqoBJQuCz3DX5P2Aie8NPgNeZs3m",
|
||||
"nickname": "Jeremy"
|
||||
}
|
||||
]
|
Loading…
Reference in New Issue