mirror of
https://github.com/Alvin-Zilverstand/narrow_casting_system.git
synced 2026-03-06 13:24:46 +01:00
🎿 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:
140
admin/js/api.js
Normal file
140
admin/js/api.js
Normal 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();
|
||||
Reference in New Issue
Block a user