mirror of
https://github.com/Alvin-Zilverstand/narrow_casting_system.git
synced 2026-03-06 11:07:14 +01:00
yes
This commit is contained in:
@@ -61,6 +61,7 @@ class DatabaseManager {
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
icon TEXT DEFAULT 'fa-map-marker-alt',
|
||||
displayOrder INTEGER DEFAULT 0,
|
||||
isActive INTEGER DEFAULT 1
|
||||
)
|
||||
@@ -82,23 +83,23 @@ class DatabaseManager {
|
||||
this.db.run(zonesTable);
|
||||
this.db.run(logsTable);
|
||||
|
||||
// Insert default zones
|
||||
// Insert default zones with icons
|
||||
const defaultZones = [
|
||||
{ id: 'reception', name: 'Receptie', description: 'Hoofdingang en receptie', displayOrder: 1 },
|
||||
{ id: 'restaurant', name: 'Restaurant', description: 'Eetgelegenheid', displayOrder: 2 },
|
||||
{ id: 'skislope', name: 'Skibaan', description: 'Hoofdskibaan', displayOrder: 3 },
|
||||
{ id: 'lockers', name: 'Kluisjes', description: 'Kleedkamers en kluisjes', displayOrder: 4 },
|
||||
{ id: 'shop', name: 'Winkel', description: 'Ski-uitrusting winkel', displayOrder: 5 },
|
||||
{ id: 'all', name: 'Alle zones', description: 'Toon op alle schermen', displayOrder: 0 }
|
||||
{ id: 'reception', name: 'Receptie', description: 'Hoofdingang en receptie', icon: 'fa-door-open', displayOrder: 1 },
|
||||
{ id: 'restaurant', name: 'Restaurant', description: 'Eetgelegenheid', icon: 'fa-utensils', displayOrder: 2 },
|
||||
{ id: 'skislope', name: 'Skibaan', description: 'Hoofdskibaan', icon: 'fa-skiing', displayOrder: 3 },
|
||||
{ id: 'lockers', name: 'Kluisjes', description: 'Kleedkamers en kluisjes', icon: 'fa-locker', displayOrder: 4 },
|
||||
{ id: 'shop', name: 'Winkel', description: 'Ski-uitrusting winkel', icon: 'fa-shopping-bag', displayOrder: 5 },
|
||||
{ id: 'all', name: 'Alle zones', description: 'Toon op alle schermen', icon: 'fa-globe', displayOrder: 0 }
|
||||
];
|
||||
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT OR IGNORE INTO zones (id, name, description, displayOrder)
|
||||
VALUES (?, ?, ?, ?)
|
||||
INSERT OR IGNORE INTO zones (id, name, description, icon, displayOrder)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
defaultZones.forEach(zone => {
|
||||
stmt.run(zone.id, zone.name, zone.description, zone.displayOrder);
|
||||
stmt.run(zone.id, zone.name, zone.description, zone.icon, zone.displayOrder);
|
||||
});
|
||||
|
||||
stmt.finalize();
|
||||
@@ -111,22 +112,57 @@ class DatabaseManager {
|
||||
async addContent(contentData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT INTO content (id, type, title, filename, originalName, mimeType, size, path, url, zone, duration, createdAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO content (id, type, title, filename, originalName, mimeType, size, path, url, zone, duration, textContent, createdAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
stmt.run(
|
||||
contentData.id,
|
||||
contentData.type,
|
||||
contentData.title,
|
||||
contentData.filename,
|
||||
contentData.originalName,
|
||||
contentData.mimeType,
|
||||
contentData.size,
|
||||
contentData.path,
|
||||
contentData.url,
|
||||
contentData.filename || '',
|
||||
contentData.originalName || '',
|
||||
contentData.mimeType || '',
|
||||
contentData.size || 0,
|
||||
contentData.path || '',
|
||||
contentData.url || '',
|
||||
contentData.zone,
|
||||
contentData.duration,
|
||||
contentData.textContent || null,
|
||||
contentData.createdAt,
|
||||
function(err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(contentData);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
stmt.finalize();
|
||||
});
|
||||
}
|
||||
|
||||
async addTextContent(contentData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT INTO content (id, type, title, filename, originalName, mimeType, size, path, url, zone, duration, textContent, createdAt)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
stmt.run(
|
||||
contentData.id,
|
||||
'text',
|
||||
contentData.title,
|
||||
'',
|
||||
'',
|
||||
'text/plain',
|
||||
0,
|
||||
'',
|
||||
'',
|
||||
contentData.zone,
|
||||
contentData.duration,
|
||||
contentData.textContent,
|
||||
contentData.createdAt,
|
||||
function(err) {
|
||||
if (err) {
|
||||
@@ -257,6 +293,33 @@ class DatabaseManager {
|
||||
});
|
||||
}
|
||||
|
||||
async addZone(zoneData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT INTO zones (id, name, description, icon, displayOrder, isActive)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
stmt.run(
|
||||
zoneData.id,
|
||||
zoneData.name,
|
||||
zoneData.description || '',
|
||||
zoneData.icon || 'fa-map-marker-alt',
|
||||
zoneData.displayOrder || 0,
|
||||
1,
|
||||
function(err) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(zoneData);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
stmt.finalize();
|
||||
});
|
||||
}
|
||||
|
||||
// Logging
|
||||
async addLog(type, message, data = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@@ -95,6 +95,39 @@ app.post('/api/content/upload', upload.single('content'), async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 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;
|
||||
@@ -172,16 +205,44 @@ app.get('/api/schedule/:zone', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/zones', (req, res) => {
|
||||
const zones = [
|
||||
{ id: 'reception', name: 'Receptie', description: 'Hoofdingang en receptie' },
|
||||
{ id: 'restaurant', name: 'Restaurant', description: 'Eetgelegenheid' },
|
||||
{ id: 'skislope', name: 'Skibaan', description: 'Hoofdskibaan' },
|
||||
{ id: 'lockers', name: 'Kluisjes', description: 'Kleedkamers en kluisjes' },
|
||||
{ id: 'shop', name: 'Winkel', description: 'Ski-uitrusting winkel' },
|
||||
{ id: 'all', name: 'Alle zones', description: 'Toon op alle schermen' }
|
||||
];
|
||||
res.json(zones);
|
||||
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
|
||||
|
||||
@@ -14,6 +14,17 @@ class ContentManager {
|
||||
}
|
||||
}
|
||||
|
||||
async addTextContent(contentData) {
|
||||
try {
|
||||
const content = await this.db.addTextContent(contentData);
|
||||
await this.db.addLog('content', 'Text content added', { contentId: content.id, type: 'text' });
|
||||
return content;
|
||||
} catch (error) {
|
||||
console.error('Error adding text content:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getContent(zone = null, type = null) {
|
||||
try {
|
||||
return await this.db.getContent(zone, type);
|
||||
@@ -92,7 +103,8 @@ class ContentManager {
|
||||
const allowedTypes = {
|
||||
'image': ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
|
||||
'video': ['video/mp4', 'video/webm', 'video/ogg'],
|
||||
'livestream': ['application/x-mpegURL', 'application/vnd.apple.mpegurl']
|
||||
'livestream': ['application/x-mpegURL', 'application/vnd.apple.mpegurl'],
|
||||
'text': ['text/plain']
|
||||
};
|
||||
|
||||
for (const [type, mimeTypes] of Object.entries(allowedTypes)) {
|
||||
@@ -109,7 +121,8 @@ class ContentManager {
|
||||
const defaultDurations = {
|
||||
'image': 10,
|
||||
'video': 30,
|
||||
'livestream': 3600 // 1 hour for livestreams
|
||||
'livestream': 3600, // 1 hour for livestreams
|
||||
'text': 15
|
||||
};
|
||||
|
||||
// For videos, estimate duration based on file size (rough approximation)
|
||||
|
||||
Reference in New Issue
Block a user