mirror of
https://github.com/blackboxprogramming/BlackStream.git
synced 2026-03-17 06:57:11 -05:00
117 lines
5.0 KiB
JavaScript
117 lines
5.0 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
const app = express();
|
|
const port = process.env.PORT || 4002;
|
|
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
|
|
// Content catalog (shared with gateway)
|
|
const catalog = [
|
|
{ id: 1, title: 'The Office', genre: 'Comedy', platform: 'Peacock', year: 2005, rating: 9.0, tags: ['workplace', 'mockumentary'] },
|
|
{ id: 2, title: 'Stranger Things', genre: 'Sci-Fi', platform: 'Netflix', year: 2016, rating: 8.7, tags: ['horror', '80s', 'kids'] },
|
|
{ id: 3, title: 'The Crown', genre: 'Drama', platform: 'Netflix', year: 2016, rating: 8.6, tags: ['historical', 'royalty'] },
|
|
{ id: 4, title: 'Breaking Bad', genre: 'Drama', platform: 'Netflix', year: 2008, rating: 9.5, tags: ['crime', 'antihero'] },
|
|
{ id: 5, title: 'Game of Thrones', genre: 'Fantasy', platform: 'HBO Max', year: 2011, rating: 9.2, tags: ['medieval', 'war'] },
|
|
{ id: 6, title: 'The Mandalorian', genre: 'Sci-Fi', platform: 'Disney+', year: 2019, rating: 8.7, tags: ['starwars', 'western'] },
|
|
{ id: 7, title: 'Ted Lasso', genre: 'Comedy', platform: 'Apple TV+', year: 2020, rating: 8.8, tags: ['sports', 'wholesome'] },
|
|
{ id: 8, title: 'Severance', genre: 'Thriller', platform: 'Apple TV+', year: 2022, rating: 8.7, tags: ['mystery', 'corporate'] },
|
|
{ id: 9, title: 'Succession', genre: 'Drama', platform: 'HBO Max', year: 2018, rating: 8.9, tags: ['family', 'corporate'] },
|
|
{ id: 10, title: 'Squid Game', genre: 'Thriller', platform: 'Netflix', year: 2021, rating: 8.0, tags: ['survival', 'korean'] },
|
|
{ id: 11, title: 'The Boys', genre: 'Action', platform: 'Prime Video', year: 2019, rating: 8.7, tags: ['superhero', 'satire'] },
|
|
{ id: 12, title: 'Rings of Power', genre: 'Fantasy', platform: 'Prime Video', year: 2022, rating: 6.9, tags: ['tolkien', 'epic'] },
|
|
{ id: 13, title: 'Andor', genre: 'Sci-Fi', platform: 'Disney+', year: 2022, rating: 8.4, tags: ['starwars', 'spy'] },
|
|
{ id: 14, title: 'House of the Dragon', genre: 'Fantasy', platform: 'HBO Max', year: 2022, rating: 8.5, tags: ['medieval', 'war'] },
|
|
{ id: 15, title: 'Loki', genre: 'Sci-Fi', platform: 'Disney+', year: 2021, rating: 8.2, tags: ['marvel', 'time-travel'] },
|
|
];
|
|
|
|
// In-memory user preferences (would be Redis/DB in production)
|
|
const userPrefs = new Map();
|
|
|
|
// Cosine similarity between two items based on genre, tags, rating
|
|
function similarity(a, b) {
|
|
let score = 0;
|
|
if (a.genre === b.genre) score += 3;
|
|
const sharedTags = a.tags.filter(t => b.tags.includes(t)).length;
|
|
score += sharedTags * 2;
|
|
score += (1 - Math.abs(a.rating - b.rating) / 10) * 2;
|
|
if (Math.abs(a.year - b.year) <= 3) score += 1;
|
|
return score;
|
|
}
|
|
|
|
// Get recommendations based on liked items
|
|
function recommend(likedIds, limit = 5) {
|
|
const liked = catalog.filter(c => likedIds.includes(c.id));
|
|
if (liked.length === 0) {
|
|
// Cold start: return top-rated
|
|
return catalog
|
|
.filter(c => !likedIds.includes(c.id))
|
|
.sort((a, b) => b.rating - a.rating)
|
|
.slice(0, limit);
|
|
}
|
|
|
|
const candidates = catalog.filter(c => !likedIds.includes(c.id));
|
|
const scored = candidates.map(c => {
|
|
const avgSim = liked.reduce((sum, l) => sum + similarity(l, c), 0) / liked.length;
|
|
return { ...c, score: Math.round(avgSim * 100) / 100 };
|
|
});
|
|
|
|
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
}
|
|
|
|
// Health
|
|
app.get('/', (req, res) => {
|
|
res.json({ service: 'BlackStream Recommendation Engine', status: 'ok', version: '1.0.0', catalog: catalog.length });
|
|
});
|
|
|
|
// Get recommendations for a user
|
|
app.get('/recommendations', (req, res) => {
|
|
const userId = req.query.user || 'anonymous';
|
|
const limit = parseInt(req.query.limit) || 5;
|
|
const liked = userPrefs.get(userId) || [];
|
|
const recs = recommend(liked, limit);
|
|
res.json({ user: userId, liked: liked.length, recommendations: recs });
|
|
});
|
|
|
|
// Get recommendations based on a specific show
|
|
app.get('/similar/:id', (req, res) => {
|
|
const id = parseInt(req.params.id);
|
|
const item = catalog.find(c => c.id === id);
|
|
if (!item) return res.status(404).json({ error: 'Not found' });
|
|
|
|
const limit = parseInt(req.query.limit) || 5;
|
|
const similar = catalog
|
|
.filter(c => c.id !== id)
|
|
.map(c => ({ ...c, score: Math.round(similarity(item, c) * 100) / 100 }))
|
|
.sort((a, b) => b.score - a.score)
|
|
.slice(0, limit);
|
|
|
|
res.json({ source: item, similar });
|
|
});
|
|
|
|
// Record user preference
|
|
app.post('/like', (req, res) => {
|
|
const { user, itemId } = req.body;
|
|
if (!user || !itemId) return res.status(400).json({ error: 'user and itemId required' });
|
|
if (!catalog.find(c => c.id === itemId)) return res.status(404).json({ error: 'Item not found' });
|
|
|
|
const liked = userPrefs.get(user) || [];
|
|
if (!liked.includes(itemId)) liked.push(itemId);
|
|
userPrefs.set(user, liked);
|
|
|
|
res.json({ user, liked });
|
|
});
|
|
|
|
// Get genre distribution
|
|
app.get('/genres', (req, res) => {
|
|
const genres = {};
|
|
catalog.forEach(c => { genres[c.genre] = (genres[c.genre] || 0) + 1; });
|
|
res.json({ genres });
|
|
});
|
|
|
|
if (require.main === module) {
|
|
app.listen(port, () => console.log(`Recommendation engine on port ${port}`));
|
|
}
|
|
|
|
module.exports = app;
|