Files
narrow_casting_system/backend/server.js
Alvin-Zilverstand 10b9ba4e61 yes
2026-02-09 10:59:59 +01:00

298 lines
7.9 KiB
JavaScript

const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const cors = require('cors');
const path = require('path');
const multer = require('multer');
const { v4: uuidv4 } = require('uuid');
const fs = require('fs-extra');
const DatabaseManager = require('./database/DatabaseManager');
const ContentManager = require('./services/ContentManager');
const ScheduleManager = require('./services/ScheduleManager');
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.static(path.join(__dirname, '../public')));
// File upload configuration
const storage = multer.diskStorage({
destination: (req, file, cb) => {
let uploadPath;
if (file.mimetype.startsWith('image/')) {
uploadPath = path.join(__dirname, '../public/uploads/images');
} else if (file.mimetype.startsWith('video/')) {
uploadPath = path.join(__dirname, '../public/uploads/videos');
} else {
return cb(new Error('Unsupported file type'));
}
cb(null, uploadPath);
},
filename: (req, file, cb) => {
const uniqueName = `${uuidv4()}-${file.originalname}`;
cb(null, uniqueName);
}
});
const upload = multer({ storage: storage });
// Initialize managers
const dbManager = new DatabaseManager();
const contentManager = new ContentManager(dbManager);
const scheduleManager = new ScheduleManager(dbManager, io);
// Initialize database
dbManager.initialize();
// API Routes
// Content Management
app.post('/api/content/upload', upload.single('content'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
const contentData = {
id: uuidv4(),
type: req.body.type,
title: req.body.title || req.file.originalname,
filename: req.file.filename,
originalName: req.file.originalname,
mimeType: req.file.mimetype,
size: req.file.size,
path: req.file.path,
url: `/uploads/${req.file.mimetype.startsWith('image/') ? 'images' : 'videos'}/${req.file.filename}`,
zone: req.body.zone || 'all',
duration: parseInt(req.body.duration) || 10,
createdAt: new Date().toISOString()
};
const content = await contentManager.addContent(contentData);
// Emit real-time update
io.emit('contentUpdated', {
type: 'content_added',
content: content
});
res.json({ success: true, content });
} catch (error) {
console.error('Upload error:', error);
res.status(500).json({ error: 'Upload failed' });
}
});
// Text Content Management
app.post('/api/content/text', async (req, res) => {
try {
const { title, textContent, zone, duration } = req.body;
if (!title || !textContent) {
return res.status(400).json({ error: 'Title and text content are required' });
}
const contentData = {
id: uuidv4(),
title: title,
textContent: textContent,
zone: zone || 'all',
duration: parseInt(duration) || 15,
createdAt: new Date().toISOString()
};
const content = await contentManager.addTextContent(contentData);
// Emit real-time update
io.emit('contentUpdated', {
type: 'content_added',
content: content
});
res.json({ success: true, content });
} catch (error) {
console.error('Text content creation error:', error);
res.status(500).json({ error: 'Failed to create text content' });
}
});
app.get('/api/content', async (req, res) => {
try {
const { zone, type } = req.query;
const content = await contentManager.getContent(zone, type);
res.json(content);
} catch (error) {
console.error('Get content error:', error);
res.status(500).json({ error: 'Failed to retrieve content' });
}
});
app.delete('/api/content/:id', async (req, res) => {
try {
const { id } = req.params;
const content = await contentManager.getContentById(id);
if (!content) {
return res.status(404).json({ error: 'Content not found' });
}
// Delete physical file
await fs.remove(content.path);
// Delete from database
await contentManager.deleteContent(id);
// Emit real-time update
io.emit('contentUpdated', {
type: 'content_deleted',
contentId: id
});
res.json({ success: true });
} catch (error) {
console.error('Delete content error:', error);
res.status(500).json({ error: 'Failed to delete content' });
}
});
// Schedule Management
app.post('/api/schedule', async (req, res) => {
try {
const scheduleData = {
id: uuidv4(),
contentId: req.body.contentId,
zone: req.body.zone,
startTime: req.body.startTime,
endTime: req.body.endTime,
priority: req.body.priority || 1,
createdAt: new Date().toISOString()
};
const schedule = await scheduleManager.addSchedule(scheduleData);
io.emit('scheduleUpdated', {
type: 'schedule_added',
schedule: schedule
});
res.json({ success: true, schedule });
} catch (error) {
console.error('Schedule creation error:', error);
res.status(500).json({ error: 'Failed to create schedule' });
}
});
app.get('/api/schedule/:zone', async (req, res) => {
try {
const { zone } = req.params;
const schedule = await scheduleManager.getActiveSchedule(zone);
res.json(schedule);
} catch (error) {
console.error('Get schedule error:', error);
res.status(500).json({ error: 'Failed to retrieve schedule' });
}
});
app.get('/api/zones', async (req, res) => {
try {
const zones = await dbManager.getZones();
res.json(zones);
} catch (error) {
console.error('Get zones error:', error);
res.status(500).json({ error: 'Failed to retrieve zones' });
}
});
app.post('/api/zones', async (req, res) => {
try {
const { id, name, description, icon, displayOrder } = req.body;
if (!id || !name) {
return res.status(400).json({ error: 'Zone ID and name are required' });
}
const zoneData = {
id: id.toLowerCase().replace(/\s+/g, '-'),
name: name,
description: description || '',
icon: icon || 'fa-map-marker-alt',
displayOrder: parseInt(displayOrder) || 0
};
const zone = await dbManager.addZone(zoneData);
io.emit('zonesUpdated', {
type: 'zone_added',
zone: zone
});
res.json({ success: true, zone });
} catch (error) {
console.error('Create zone error:', error);
res.status(500).json({ error: 'Failed to create zone' });
}
});
// Weather widget data
app.get('/api/weather', (req, res) => {
// Mock weather data - in real implementation, integrate with weather API
const weatherData = {
temperature: -5,
snowCondition: 'Frisse sneeuw',
slopeCondition: 'Perfect',
humidity: 65,
windSpeed: 8,
lastUpdated: new Date().toISOString()
};
res.json(weatherData);
});
// Socket.io connection handling
io.on('connection', (socket) => {
console.log('Client connected:', socket.id);
socket.on('joinZone', (zone) => {
socket.join(zone);
console.log(`Client ${socket.id} joined zone: ${zone}`);
});
socket.on('leaveZone', (zone) => {
socket.leave(zone);
console.log(`Client ${socket.id} left zone: ${zone}`);
});
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
});
});
// Error handling middleware
app.use((error, req, res, next) => {
console.error('Server error:', error);
res.status(500).json({
error: 'Internal server error',
message: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong'
});
});
// 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
server.listen(PORT, () => {
console.log(`SnowWorld Narrowcasting Server running on port ${PORT}`);
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
});