node-ejs-renderer/server.js

293 lines
11 KiB
JavaScript
Raw Permalink Normal View History

2024-06-09 13:55:01 -04:00
const http = require('http');
const fs = require('fs');
const path = require('path');
const ejs = require('ejs');
const os = require('os');
const QRCode = require('qrcode');
const { handleApiRequest } = require('./routes/api');
const { validateSession, authorize, acl } = require('./acl');
const WebSocket = require('ws');
const cookie = require('cookie');
const Session = require('./models/session');
const { v4: uuidv4 } = require('uuid');
const nms = require('./media-server');
const httpProxy = require('http-proxy');
const port = process.env.PORT || 3000;
// Proxy setup
const proxy = httpProxy.createServer({
target: 'http://localhost:8000/',
changeOrigin: true
});
const connections = {}; // Store connections with confirmation codes
// Function to get the network address
const getNetworkAddress = () => {
if(process.env['DEVELOPMENT']) {
const interfaces = os.networkInterfaces();
for (const name of Object.keys(interfaces)) {
for (const iface of interfaces[name]) {
if (iface.family === 'IPv4' && !iface.internal) {
return iface.address;
}
}
}
return 'localhost';
} else {
return 'the.mk';
}
};
// Logging function
const logRequest = (req, res, startTime) => {
const duration = new Date() - startTime;
const logMessage = `${req.method} ${req.url} ${res.statusCode} - ${duration}ms`;
console.log(logMessage);
};
const renderPage = (res, template, data) => {
const filePath = path.join(__dirname, 'views', 'layouts', 'master.ejs');
ejs.renderFile(filePath, { ...data, template }, (err, str) => {
if (err) {
console.error(err); // Log the error for debugging
if (!res.headersSent) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
}
res.end('Internal Server Error');
} else {
if (!res.headersSent) {
res.writeHead(200, { 'Content-Type': 'text/html' });
}
res.end(str);
}
});
};
const serveStaticFile = (req, res) => {
const filePath = path.join(__dirname, 'public', req.url);
fs.readFile(filePath, (err, data) => {
if (err) {
if (!res.headersSent) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
}
res.end('Not Found');
} else {
const ext = path.extname(filePath);
const contentType = ext === '.css' ? 'text/css' : 'application/javascript';
if (!res.headersSent) {
res.writeHead(200, { 'Content-Type': contentType });
}
res.end(data);
}
});
};
const parseCookies = (request) => {
const list = {};
const cookieHeader = request.headers.cookie;
if (cookieHeader) {
cookieHeader.split(';').forEach((cookie) => {
let [name, ...rest] = cookie.split('=');
name = name.trim();
if (!name) return;
const value = rest.join('=').trim();
if (!value) return;
list[name] = decodeURIComponent(value);
});
}
return list;
};
const server = http.createServer(async (req, res) => {
const startTime = new Date();
const cookies = parseCookies(req);
const token = cookies.token;
res.on('finish', () => logRequest(req, res, startTime));
const session = await validateSession(token);
const user = session ? session.data.user : null;
console.log('Session:', session); // Debug: Print session data
console.log('User:', user); // Debug: Print user data
if (req.url.startsWith('/media')) {
// Modify req.url to remove /media before proxying
req.url = req.url.replace(/^\/media/, '');
// Proxy media requests to Node-Media-Server
proxy.web(req, res);
} else if (req.method === 'GET') {
if (req.url.startsWith('/css/') || req.url.startsWith('/js/') || req.url.startsWith('/videos/') || req.url.startsWith('/audio/')) {
serveStaticFile(req, res);
} else {
const permissions = acl[req.url];
const template = permissions ? permissions.template : null;
const authorized = permissions ? authorize(req.url, 'GET', user ? user.role : null) : false;
console.log('Permissions:', permissions); // Debug: Print permissions
console.log('Authorized:', authorized); // Debug: Print authorization status
if (req.url === '/') {
const networkAddress = getNetworkAddress();
const url = `https://${networkAddress}`;
const confirmationCode = uuidv4().split('-')[0]; // Simple confirmation code
connections[confirmationCode] = { desktop: null, mobile: null };
let qrCodeDataUrl = '';
try {
qrCodeDataUrl = await QRCode.toDataURL(`${url}/mobile?code=${confirmationCode}`);
} catch (error) {
console.error('Error generating QR code:', error);
}
renderPage(res, 'index.ejs', { title: 'Welcome to The Learning App', session: session || {}, qrCodeDataUrl, url, confirmationCode });
} else if (req.url.startsWith('/mobile')) {
const code = new URL(req.url, `http://${req.headers.host}`).searchParams.get('code');
if (code && connections[code]) {
renderPage(res, 'mobile.ejs', { title: 'Mobile View', session: session || {}, confirmationCode: code });
} else {
renderPage(res, 'not_found.ejs', { title: 'Not Found', session: session || {} });
}
} else if (template && authorized) {
let sessions = [];
if (req.url === '/sessions') {
sessions = await Session.findAll();
}
renderPage(res, template, { title: req.url.slice(1).toUpperCase(), session: session || {}, sessions });
} else if (authorized === false) {
renderPage(res, 'unauthorized.ejs', { title: 'Unauthorized', session: session || {} });
} else {
renderPage(res, 'not_found.ejs', { title: 'Not Found', session: session || {} });
}
}
} else if (req.method === 'POST') {
if (req.url.startsWith('/api/')) {
handleApiRequest(req, res);
} else {
if (!res.headersSent) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
}
res.end('Not Found');
}
}
});
const wss = new WebSocket.Server({ noServer: true });
server.on('upgrade', async (request, socket, head) => {
const cookies = parseCookies(request);
const token = cookies.token;
const session = await validateSession(token);
if (!session) {
socket.destroy();
} else {
wss.handleUpgrade(request, socket, head, ws => {
wss.emit('connection', ws, request, session);
});
}
});
wss.on('connection', (ws, request, session) => {
ws.on('message', message => {
const msg = JSON.parse(message);
if (msg.type === 'confirm') {
const code = msg.code;
if (connections[code]) {
connections[code].mobile = ws;
ws.send(JSON.stringify({ type: 'confirmation', token: code, content: getCurrentLesson() }));
if (connections[code].desktop) {
connections[code].desktop.send(JSON.stringify({ type: 'confirm', content: getCurrentLesson() }));
connections[code].desktop.send(JSON.stringify({ type: 'welcome' }));
connections[code].mobile.send(JSON.stringify({ type: 'welcome' }));
}
}
} else if (msg.type === 'quizAnswer' || msg.type === 'choice') {
const token = msg.token;
if (connections[token]) {
connections[token].desktop.send(JSON.stringify(msg));
// Automatically proceed to the next lesson
const nextLessonContent = nextLesson();
connections[token].desktop.send(JSON.stringify({ type: 'nextLesson', content: nextLessonContent }));
connections[token].mobile.send(JSON.stringify({ type: 'nextLesson', content: nextLessonContent }));
}
} else if (msg.type === 'videoEnded') {
const token = msg.token;
if (connections[token]) {
connections[token].mobile.send(JSON.stringify({ type: 'showControls' }));
}
} else if (msg.type === 'nextLesson') {
const token = msg.token;
if (connections[token]) {
const nextLessonContent = nextLesson();
connections[token].desktop.send(JSON.stringify({ type: 'nextLesson', content: nextLessonContent }));
connections[token].mobile.send(JSON.stringify({ type: 'nextLesson', content: nextLessonContent }));
}
}
});
// This assumes desktop connections come first
for (const code in connections) {
if (connections[code].desktop === null) {
connections[code].desktop = ws;
break;
}
}
if (session && session.data.user && session.data.user.role === 'admin') {
const confirmationCode = uuidv4().split('-')[0]; // Simple confirmation code
connections[confirmationCode] = { desktop: ws, mobile: null };
ws.send(JSON.stringify({ type: 'confirmationCode', code: confirmationCode }));
}
});
const lessonData = [
{
title: 'Lesson 1',
text: 'This is the content for lesson 1.',
video: 'lesson1.mp4',
quiz: { question: 'What is 1 + 1?', answer: '2', choices: ['A', 'B', 'C', 'D'] }
},
{
title: 'Lesson 2',
text: 'This is the content for lesson 2.',
video: 'lesson2.mp4',
quiz: { question: 'What is 2 + 2?', answer: '4', choices: ['A', 'B', 'C', 'D'] }
},
{
title: 'Lesson 3',
text: 'This is the content for lesson 3.',
video: 'lesson3.mp4',
quiz: { question: 'What is 3 + 3?', answer: '6', choices: ['A', 'B', 'C', 'D'] }
},
{
title: 'Lesson 4',
text: 'This is the content for lesson 4.',
video: 'lesson4.mp4',
quiz: { question: 'What is 4 + 4?', answer: '8', choices: ['A', 'B', 'C', 'D'] }
},
{
title: 'Lesson 5',
text: 'This is the content for lesson 5.',
video: 'lesson5.mp4',
quiz: { question: 'What is 5 + 5?', answer: '10', choices: ['A', 'B', 'C', 'D'] }
}
];
let currentLesson = 0;
function getCurrentLesson() {
return lessonData[currentLesson];
}
function nextLesson() {
currentLesson = (currentLesson + 1) % lessonData.length;
return getCurrentLesson();
}
server.listen(port, () => {
console.log(`Server is running on port ${port}`);
nms.run(); // Start the Node-Media-Server
});