spotify/server/dist/index.js
2025-10-17 09:09:04 +02:00

146 lines
5.3 KiB
JavaScript

import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import fs from 'fs';
import path from 'path';
import https from 'https';
import { db } from './lib/db.js';
import { authRouter } from './routes/auth.js';
import { usersRouter } from './routes/users.js';
import { partnersRouter } from './routes/partners.js';
import { playlistsRouter } from './routes/playlists.js';
import { syncUserData, captureNowPlaying } from './lib/sync.js';
const app = express();
app.use(helmet());
app.use(cors({ origin: [
'http://localhost:4000',
'https://159.195.9.107:3443',
'http://159.195.9.107:3443'
], credentials: true }));
// Add CORS headers to allow mixed content for development
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
next();
});
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ extended: false }));
app.use(morgan('dev'));
// Serve static files from public directory
app.use(express.static('public'));
// Initialize DB
db.initialize();
app.get('/health', (_req, res) => {
res.json({ ok: true });
});
// Placeholder playlist cover endpoint
app.get('/api/placeholder-playlist-cover', (_req, res) => {
// Return a simple SVG placeholder
const svg = `
<svg width="300" height="300" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#8B5CF6;stop-opacity:1" />
<stop offset="100%" style="stop-color:#EC4899;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="300" height="300" fill="url(#grad1)"/>
<text x="150" y="150" font-family="Arial, sans-serif" font-size="24" fill="white" text-anchor="middle" dominant-baseline="middle">🎵</text>
</svg>
`;
res.setHeader('Content-Type', 'image/svg+xml');
res.send(svg);
});
// Serve playlist cover images as base64 data URLs to avoid mixed content issues
app.get('/api/playlist-covers/:filename', (req, res) => {
try {
const filename = decodeURIComponent(req.params.filename);
const filePath = path.join(process.cwd(), 'public', 'playlist-covers', filename);
// Check if file exists
if (!fs.existsSync(filePath)) {
return res.status(404).json({ error: 'Image not found' });
}
// Read file as base64
const fileBuffer = fs.readFileSync(filePath);
const base64Data = fileBuffer.toString('base64');
// Set appropriate content type based on file extension
const ext = path.extname(filename).toLowerCase();
let contentType = 'image/jpeg'; // default
switch (ext) {
case '.png':
contentType = 'image/png';
break;
case '.gif':
contentType = 'image/gif';
break;
case '.webp':
contentType = 'image/webp';
break;
case '.svg':
contentType = 'image/svg+xml';
break;
case '.jpg':
case '.jpeg':
contentType = 'image/jpeg';
break;
}
// Return base64 data URL
const dataUrl = `data:${contentType};base64,${base64Data}`;
res.setHeader('Content-Type', 'application/json');
res.setHeader('Cache-Control', 'public, max-age=31536000'); // Cache for 1 year
res.json({ dataUrl });
}
catch (error) {
console.error('Error serving playlist cover:', error);
res.status(500).json({ error: 'Failed to serve image' });
}
});
app.use('/auth', authRouter);
app.use('/users', usersRouter);
app.use('/partners', partnersRouter);
app.use('/playlists', playlistsRouter);
const PORT = process.env.PORT ? Number(process.env.PORT) : 8081;
const HTTPS_PORT = process.env.HTTPS_PORT ? Number(process.env.HTTPS_PORT) : 8082;
// Start HTTP server
app.listen(PORT, () => {
console.log(`API server (HTTP) listening on http://0.0.0.0:${PORT}`);
});
// Start HTTPS server
try {
const httpsOptions = {
key: fs.readFileSync(path.join(process.cwd(), 'key.pem')),
cert: fs.readFileSync(path.join(process.cwd(), 'cert.pem'))
};
https.createServer(httpsOptions, app).listen(HTTPS_PORT, () => {
console.log(`API server (HTTPS) listening on https://0.0.0.0:${HTTPS_PORT}`);
});
}
catch (error) {
console.log('HTTPS server not started - SSL certificates not found');
}
// Periodic refreshes
const REFRESH_INTERVAL_MS = 2 * 60 * 1000; // full sync
const NOWPLAYING_INTERVAL_MS = 20 * 1000; // capture now playing
setInterval(() => {
try {
const users = db.db.prepare('SELECT id FROM users').all();
for (const u of users) {
syncUserData(u.id).catch(() => void 0);
}
}
catch (e) {
// ignore timer errors
}
}, REFRESH_INTERVAL_MS);
setInterval(() => {
try {
const users = db.db.prepare('SELECT id FROM users').all();
for (const u of users)
captureNowPlaying(u.id).catch(() => void 0);
}
catch { }
}, NOWPLAYING_INTERVAL_MS);