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 | c601bf1648fe71058835589331c7f7e669940b8b |
| Branch | main |
| Path or archive member | server.js |
| Original SHA-256 | 33777fe4aa27fcddde2b7b3986793c106756c30573ef374d4330a1a99fb94753 |
| Git blob SHA | 1de09dd85e3b3951049afcb639b99e88def6004d |
| Reading-copy SHA-256 | 466d75564f317deaef8ca4a6b024f7b0086a67a770eeeac4d64cd3aa2b0edbce |
Every recorded occurrence
These 2 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/Jennys-Drumste / server.js | main | c601bf1648fe71058835589331c7f7e669940b8b | Open original |
| Janady13/Jennys-Drumste / server.js | claude/complete-integration-feature-011CUkTzyyk8bKmXaRXJAR61 | 01c36ac1a180916e11864b4573b8c1999e2c5db3 | Open original |
Recovered source transcript
Download complete source text ↓385 lines · 11.3 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();
});
}
app.listen(PORT, () => {
console.log(`DrumMaster Pro API running on port ${PORT}`);
});
// Export for testing
module.exports = app;