Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/Jennys-Drumste / server.js |
| Date | Not specified in source |
| Repository | Janady13/Jennys-Drumste |
| Revision | 1f946b9e18e552f80f4b2ffd7f59c01d0126aa53 |
| Branch | claude/jenny-drumsets-research-MofSb |
| Path or archive member | server.js |
| Original SHA-256 | f0683e150ee4b9b2c70da0de9500b1f1ad599d5229490cda0a49d0d4cab6ccc1 |
| Git blob SHA | 45d338752fd4eecd8f4bbfd6e1de83b41a270b0a |
| Reading-copy SHA-256 | 172d94e777cc53b4153a5198514b48bc816df1ae467d864e88710b52d013929f |
Recovered source transcript
Download complete source text ↓568 lines · 17.9 KB
Credential-bearing and unrelated personal details were removed from this reading copy. The source identity and original checksum remain recorded.
// 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;