🎿 Complete SnowWorld Narrowcasting System - MBO Challenge 18

 Full-stack narrowcasting platform implementation
 Real-time WebSocket communication for instant updates
 Zone-specific content distribution (reception, restaurant, skislope, lockers, shop)
 Professional admin dashboard with content management interface
 Beautiful client display with winter/snow theme matching SnowWorld branding
 Comprehensive technical documentation and test suite
 Docker deployment support with CI/CD pipeline
 All system tests passing successfully

🏗️ Technical Implementation:
- Backend: Node.js/Express with SQLite database
- Frontend: Vanilla HTML/CSS/JavaScript (no frameworks)
- Real-time: Socket.io WebSocket communication
- Database: Complete schema with content, schedule, zones, logs tables
- Security: File validation, input sanitization, CORS protection
- Performance: Optimized for fast loading and real-time updates

🚀 Features Delivered:
- Content upload (images, videos) with drag-and-drop interface
- Content scheduling and planning system
- Weather widget with real-time snow information
- Responsive design for all screen sizes
- Comprehensive error handling and fallback mechanisms
- Professional winter theme with snow animations
- Keyboard shortcuts and accessibility features

📁 Project Structure:
- /backend: Complete Node.js server with API and WebSocket
- /admin: Professional admin dashboard interface
- /client: Beautiful client display application
- /deployment: Docker and deployment configurations
- /docs: Comprehensive technical documentation
- /test_system.js: Complete test suite (all tests passing)

🧪 Testing Results:
- Server health:  Online and responsive
- API endpoints:  All endpoints functional
- Database operations:  All operations successful
- WebSocket communication:  Real-time updates working
- Zone distribution:  6 zones correctly loaded
- Weather integration:  Weather data available

Ready for production deployment at SnowWorld! 🎿❄️
This commit is contained in:
Alvin-Zilverstand
2026-01-19 10:02:11 +01:00
commit 8e446a1339
35 changed files with 15110 additions and 0 deletions

140
admin/js/api.js Normal file
View File

@@ -0,0 +1,140 @@
// API Service for SnowWorld Admin Dashboard
class APIService {
constructor() {
this.baseURL = 'http://localhost:3000/api';
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers
}
};
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
// Content Management
async getContent(zone = null, type = null) {
const params = new URLSearchParams();
if (zone) params.append('zone', zone);
if (type) params.append('type', type);
return this.request(`/content?${params.toString()}`);
}
async uploadContent(formData) {
return fetch(`${this.baseURL}/content/upload`, {
method: 'POST',
body: formData
}).then(response => {
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
return response.json();
});
}
async deleteContent(contentId) {
return this.request(`/content/${contentId}`, {
method: 'DELETE'
});
}
// Schedule Management
async getSchedule(zone) {
return this.request(`/schedule/${zone}`);
}
async createSchedule(scheduleData) {
return this.request('/schedule', {
method: 'POST',
body: JSON.stringify(scheduleData)
});
}
// Zones
async getZones() {
return this.request('/zones');
}
// Weather Data
async getWeatherData() {
return this.request('/weather');
}
// Analytics
async getContentStats() {
try {
const content = await this.getContent();
const stats = {
total: content.length,
byType: {},
byZone: {}
};
content.forEach(item => {
// Count by type
stats.byType[item.type] = (stats.byType[item.type] || 0) + 1;
// Count by zone
stats.byZone[item.zone] = (stats.byZone[item.zone] || 0) + 1;
});
return stats;
} catch (error) {
console.error('Error getting content stats:', error);
throw error;
}
}
async getScheduleStats() {
try {
// This would typically be a dedicated endpoint
// For now, we'll calculate based on available data
const zones = await this.getZones();
let totalSchedules = 0;
let activeSchedules = 0;
for (const zone of zones) {
const schedule = await this.getSchedule(zone.id);
totalSchedules += schedule.length;
const now = new Date();
const active = schedule.filter(item => {
const start = new Date(item.startTime);
const end = new Date(item.endTime);
return now >= start && now <= end;
});
activeSchedules += active.length;
}
return {
total: totalSchedules,
active: activeSchedules,
upcoming: totalSchedules - activeSchedules
};
} catch (error) {
console.error('Error getting schedule stats:', error);
throw error;
}
}
}
// Create global API instance
window.api = new APIService();