// server.js - Backend API for DrumMaster Pro // This handles Stripe payments, user management, and session tracking const express = require('express'); const cors = require('cors'); const bodyParser = require('body-parser'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const { MongoClient } = require('mongodb'); const app = express(); const PORT = process.env.PORT || 3000; // Middleware app.use(cors()); app.use(bodyParser.json()); app.use(express.static('public')); // MongoDB connection const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/drummaster'; let db; MongoClient.connect(mongoUri, { useUnifiedTopology: true }) .then(client => { console.log('Connected to MongoDB'); db = client.db('drummaster'); }) .catch(error => console.error(error)); // Stripe webhook endpoint (must be before bodyParser for raw body) app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => { const sig = req.headers['stripe-signature']; const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET; let event; try { event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret); } catch (err) { console.error('Webhook signature verification failed:', err); return res.status(400).send(`Webhook Error: ${err.message}`); } // Handle the event switch (event.type) { case 'checkout.session.completed': const session = event.data.object; await handleCheckoutSession(session); break; case 'customer.subscription.updated': case 'customer.subscription.deleted': const subscription = event.data.object; await updateSubscriptionStatus(subscription); break; default: console.log(`Unhandled event type ${event.type}`); } res.json({ received: true }); }); // User Registration app.post('/api/register', async (req, res) => { try { const { email, password, name } = req.body; // Check if user exists const existingUser = await db.collection('users').findOne({ email }); if (existingUser) { return res.status(400).json({ error: 'User already exists' }); } // Hash password const hashedPassword = [credential removed] bcrypt.hash(password, 10); // Create Stripe customer const customer = await stripe.customers.create({ email, name, metadata: { source: 'drummaster_pro' } }); // Create user in database const user = { email, password: [credential removed], name, stripeCustomerId: customer.id, plan: 'free', createdAt: new Date(), stats: { totalSessions: 0, totalPracticeTime: 0, highestScore: 0, achievements: [], level: 1 } }; await db.collection('users').insertOne(user); // Generate JWT const token = jwt.sign( { userId: user._id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '7d' } ); res.json({ success: true, token, user: { email: user.email, name: user.name, plan: user.plan } }); } catch (error) { console.error('Registration error:', error); res.status(500).json({ error: 'Registration failed' }); } }); // User Login app.post('/api/login', async (req, res) => { try { const { email, password } = req.body; const user = await db.collection('users').findOne({ email }); if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); } const isValidPassword = [credential removed] bcrypt.compare(password, user.password); if (!isValidPassword) { return res.status(401).json({ error: 'Invalid credentials' }); } const token = jwt.sign( { userId: user._id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '7d' } ); res.json({ success: true, token, user: { email: user.email, name: user.name, plan: user.plan, stats: user.stats } }); } catch (error) { console.error('Login error:', error); res.status(500).json({ error: 'Login failed' }); } }); // Create Checkout Session app.post('/api/create-checkout-session', async (req, res) => { try { const { priceId, email, plan } = req.body; // Get or create customer let customer; const existingCustomers = await stripe.customers.list({ email, limit: 1 }); if (existingCustomers.data.length > 0) { customer = existingCustomers.data[0]; } else { customer = await stripe.customers.create({ email }); } // Create checkout session const session = await stripe.checkout.sessions.create({ customer: customer.id, payment_method_types: ['card'], line_items: [{ price: priceId, quantity: 1 }], mode: plan === 'lifetime' ? 'payment' : 'subscription', success_url: `${process.env.DOMAIN}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${process.env.DOMAIN}/cancel`, metadata: { plan, email } }); res.json({ sessionId: session.id }); } catch (error) { console.error('Checkout session error:', error); res.status(500).json({ error: 'Failed to create checkout session' }); } }); // Get User Stats app.get('/api/stats/:userId', authenticateToken, async (req, res) => { try { const user = await db.collection('users').findOne({ _id: req.params.userId }); if (!user) { return res.status(404).json({ error: 'User not found' }); } const sessions = await db.collection('sessions') .find({ userId: req.params.userId }) .sort({ createdAt: -1 }) .limit(10) .toArray(); res.json({ stats: user.stats, recentSessions: sessions }); } catch (error) { console.error('Stats error:', error); res.status(500).json({ error: 'Failed to fetch stats' }); } }); // Save Practice Session app.post('/api/sessions', authenticateToken, async (req, res) => { try { const session = { userId: req.user.userId, ...req.body, createdAt: new Date() }; await db.collection('sessions').insertOne(session); // Update user stats await db.collection('users').updateOne( { _id: req.user.userId }, { $inc: { 'stats.totalSessions': 1, 'stats.totalPracticeTime': session.duration }, $max: { 'stats.highestScore': session.score } } ); res.json({ success: true, sessionId: session._id }); } catch (error) { console.error('Session save error:', error); res.status(500).json({ error: 'Failed to save session' }); } }); // Get Leaderboard app.get('/api/leaderboard', async (req, res) => { try { const leaderboard = await db.collection('users') .find({}, { projection: { name: 1, 'stats.highestScore': 1, 'stats.level': 1 } }) .sort({ 'stats.highestScore': -1 }) .limit(100) .toArray(); res.json(leaderboard); } catch (error) { console.error('Leaderboard error:', error); res.status(500).json({ error: 'Failed to fetch leaderboard' }); } }); // Update Achievements app.post('/api/achievements', authenticateToken, async (req, res) => { try { const { achievements } = req.body; await db.collection('users').updateOne( { _id: req.user.userId }, { $addToSet: { 'stats.achievements': { $each: achievements } } } ); res.json({ success: true }); } catch (error) { console.error('Achievement update error:', error); res.status(500).json({ error: 'Failed to update achievements' }); } }); // Cancel Subscription app.post('/api/cancel-subscription', authenticateToken, async (req, res) => { try { const user = await db.collection('users').findOne({ _id: req.user.userId }); if (!user.stripeSubscriptionId) { return res.status(400).json({ error: 'No active subscription' }); } const subscription = await stripe.subscriptions.update( user.stripeSubscriptionId, { cancel_at_period_end: true } ); res.json({ success: true, cancelAt: subscription.cancel_at }); } catch (error) { console.error('Cancel subscription error:', error); res.status(500).json({ error: 'Failed to cancel subscription' }); } }); // Helper Functions async function handleCheckoutSession(session) { const { customer, metadata } = session; const { plan, email } = metadata; // Update user subscription in database await db.collection('users').updateOne( { email }, { $set: { plan, stripeCustomerId: customer, stripeSubscriptionId: session.subscription, subscriptionUpdatedAt: new Date() } } ); // Send welcome email (implement email service) // await sendWelcomeEmail(email, plan); } async function updateSubscriptionStatus(subscription) { const { customer, status, id } = subscription; const user = await db.collection('users').findOne({ stripeCustomerId: customer }); if (!user) return; const plan = status === 'active' ? 'pro' : 'free'; await db.collection('users').updateOne( { stripeCustomerId: customer }, { $set: { plan, stripeSubscriptionId: id, subscriptionStatus: status, subscriptionUpdatedAt: new Date() } } ); } function authenticateToken(req, res, next) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'Access token required' }); } jwt.verify(token, process.env.JWT_SECRET, (err, user) => { if (err) { return res.status(403).json({ error: 'Invalid token' }); } req.user = user; next(); }); } // ═══════════════════════════════════════════════════════════════════════════════ // SEFERIM AGI G16 v3.1.1 API ENDPOINTS // 16 Families × 5000 Substrates = 80,000 Learning Sites // ═══════════════════════════════════════════════════════════════════════════════ // Save SEFERIM state to user account app.post('/api/seferim/save', authenticateToken, async (req, res) => { try { const { state, tick, PhiGlobal, K } = req.body; await db.collection('seferim_states').updateOne( { userId: req.user.userId }, { $set: { state, // Base64 encoded binary state tick, PhiGlobal, K, updatedAt: new Date() } }, { upsert: true } ); res.json({ success: true, tick }); } catch (error) { console.error('SEFERIM save error:', error); res.status(500).json({ error: 'Failed to save SEFERIM state' }); } }); // Load SEFERIM state for user app.get('/api/seferim/load', authenticateToken, async (req, res) => { try { const seferimState = await db.collection('seferim_states').findOne({ userId: req.user.userId }); if (!seferimState) { return res.json({ found: false }); } res.json({ found: true, state: seferimState.state, tick: seferimState.tick, PhiGlobal: seferimState.PhiGlobal, K: seferimState.K, updatedAt: seferimState.updatedAt }); } catch (error) { console.error('SEFERIM load error:', error); res.status(500).json({ error: 'Failed to load SEFERIM state' }); } }); // Get SEFERIM learning statistics app.get('/api/seferim/stats', authenticateToken, async (req, res) => { try { const seferimState = await db.collection('seferim_states').findOne({ userId: req.user.userId }); if (!seferimState) { return res.json({ tick: 0, PhiGlobal: 0, K: 0, level: 1, familyProgress: Array(16).fill(0) }); } res.json({ tick: seferimState.tick, PhiGlobal: seferimState.PhiGlobal, K: seferimState.K, updatedAt: seferimState.updatedAt }); } catch (error) { console.error('SEFERIM stats error:', error); res.status(500).json({ error: 'Failed to fetch SEFERIM stats' }); } }); // Record SEFERIM learning event app.post('/api/seferim/event', authenticateToken, async (req, res) => { try { const event = { userId: req.user.userId, ...req.body, timestamp: new Date() }; await db.collection('seferim_events').insertOne(event); // Update user's learning streak await db.collection('users').updateOne( { _id: req.user.userId }, { $inc: { 'stats.seferimTicks': 1 }, $set: { 'stats.lastSeferimActivity': new Date() } } ); res.json({ success: true }); } catch (error) { console.error('SEFERIM event error:', error); res.status(500).json({ error: 'Failed to record SEFERIM event' }); } }); // Get SEFERIM leaderboard (by knowledge bits K) app.get('/api/seferim/leaderboard', async (req, res) => { try { const leaderboard = await db.collection('seferim_states') .aggregate([ { $lookup: { from: 'users', localField: 'userId', foreignField: '_id', as: 'user' } }, { $unwind: '$user' }, { $project: { name: '$user.name', K: 1, PhiGlobal: 1, tick: 1 } }, { $sort: { K: -1 } }, { $limit: 100 } ]) .toArray(); res.json(leaderboard); } catch (error) { console.error('SEFERIM leaderboard error:', error); res.status(500).json({ error: 'Failed to fetch SEFERIM leaderboard' }); } }); // Get family-specific progress app.get('/api/seferim/families/:userId', authenticateToken, async (req, res) => { try { const familyNames = [ 'Lashon', 'Yad', 'Athena', 'Hera', 'Mnemosyne', 'Logos', 'Sophia', 'Prometheus', 'Hermes', 'Apollo', 'Hephaestus', 'Demeter', 'Poseidon', 'Ares', 'Dionysus', 'Chronos' ]; const familyDomains = [ 'Rhythm Vocabulary', 'Technique Execution', 'Practice Strategy', 'Session Governance', 'Pattern Memory', 'Beat Logic', 'Genre Wisdom', 'Creative Innovation', 'Dynamic Expression', 'Musical Artistry', 'Technical Precision', 'Skill Growth', 'Timing Flow', 'Power Control', 'Learning Joy', 'Tempo Mastery' ]; // Return family metadata (actual coherence is computed client-side) res.json({ families: familyNames.map((name, i) => ({ index: i, name, domain: familyDomains[i] })) }); } catch (error) { console.error('SEFERIM families error:', error); res.status(500).json({ error: 'Failed to fetch family info' }); } }); app.listen(PORT, () => { console.log(` ╔══════════════════════════════════════════════════════════════════════════════╗ ║ DrumMaster Pro API - SEFERIM AGI G16 v3.1.1 Enabled ║ ║ 16 Families × 5000 Substrates = 80,000 Learning Sites ║ ║ Port: ${PORT} ║ ╚══════════════════════════════════════════════════════════════════════════════╝ `); }); // Export for testing module.exports = app;