import fs from 'fs'; import path from 'path'; const baseDir = './backend'; // Create directories const dirs = [ `${baseDir}/src/routes`, ]; dirs.forEach(dir => { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); console.log(`Created directory: ${dir}`); } }); // package.json fs.writeFileSync(`${baseDir}/package.json`, JSON.stringify({ "name": "photobooth-backend", "version": "1.0.0", "type": "module", "main": "src/server.js", "scripts": { "start": "node src/server.js", "dev": "node --watch src/server.js" }, "keywords": ["photobooth", "express", "mysql"], "author": "", "license": "MIT", "dependencies": { "express": "^4.18.2", "cors": "^2.8.5", "dotenv": "^16.3.1", "mysql2": "^3.6.5", "jsonwebtoken": "^9.1.0", "bcryptjs": "^2.4.3", "uuid": "^9.0.1", "express-async-errors": "^3.1.1", "multer": "^1.4.5-lts.1" }, "devDependencies": {} }, null, 2)); console.log('Created package.json'); // .env.example fs.writeFileSync(`${baseDir}/.env.example`, `# Backend API NODE_ENV=production PORT=3000 # Database DB_HOST=127.0.0.1 DB_PORT=3306 DB_NAME=photobooth DB_USER=photobooth_app DB_PASSWORD=your_app_password_here # JWT JWT_SECRET=your_jwt_secret_here_change_in_production JWT_EXPIRY=15m REFRESH_TOKEN_EXPIRY=7d # Frontend FRONTEND_URL=http://localhost:5173 BACKEND_URL=http://localhost:3000 # File storage UPLOADS_DIR=./uploads MAX_FILE_SIZE=10485760 `); console.log('Created .env.example'); // src/db.js fs.writeFileSync(`${baseDir}/src/db.js`, `import mysql from 'mysql2/promise' import dotenv from 'dotenv' dotenv.config() const pool = mysql.createPool({ host: process.env.DB_HOST, port: process.env.DB_PORT, database: process.env.DB_NAME, user: process.env.DB_USER, password: process.env.DB_PASSWORD, waitForConnections: true, connectionLimit: 10, queueLimit: 0, }) export async function query(sql, values) { const connection = await pool.getConnection() try { const [results] = await connection.execute(sql, values) return results } finally { connection.release() } } export async function getConnection() { return pool.getConnection() } export default pool `); console.log('Created src/db.js'); // src/jwt.js fs.writeFileSync(`${baseDir}/src/jwt.js`, `import jwt from 'jsonwebtoken' import dotenv from 'dotenv' dotenv.config() const JWT_SECRET = process.env.JWT_SECRET || 'change-me-in-production' const JWT_EXPIRY = process.env.JWT_EXPIRY || '15m' const REFRESH_TOKEN_EXPIRY = process.env.REFRESH_TOKEN_EXPIRY || '7d' export function createAccessToken(userId) { return jwt.sign({ userId }, JWT_SECRET, { expiresIn: JWT_EXPIRY }) } export function createRefreshToken() { return jwt.sign({ refreshToken: true }, JWT_SECRET, { expiresIn: REFRESH_TOKEN_EXPIRY }) } export function verifyToken(token) { try { return jwt.verify(token, JWT_SECRET) } catch { return null } } export function decodeToken(token) { try { return jwt.decode(token) } catch { return null } } `); console.log('Created src/jwt.js'); // src/models.js fs.writeFileSync(`${baseDir}/src/models.js`, `import { query } from './db.js' import bcryptjs from 'bcryptjs' // User model export const User = { async findById(userId) { const results = await query('SELECT id, email, username, created_at FROM users WHERE id = ?', [userId]) return results[0] || null }, async findByEmail(email) { const results = await query('SELECT * FROM users WHERE email = ?', [email]) return results[0] || null }, async findByUsername(username) { const results = await query('SELECT * FROM users WHERE username = ?', [username]) return results[0] || null }, async create(email, username, passwordHash) { const id = crypto.randomUUID() await query('INSERT INTO users (id, email, username, password_hash) VALUES (?, ?, ?, ?)', [id, email, username, passwordHash]) return this.findById(id) }, async verifyPassword(user, password) { return bcryptjs.compare(password, user.password_hash) }, async updateLastLogin(userId) { await query('UPDATE users SET last_login = NOW() WHERE id = ?', [userId]) }, } // Session model export const Session = { async create(sessionId, eventId, createdBy, name) { await query('INSERT INTO photo_sessions (id, event_id, created_by, session_name) VALUES (?, ?, ?, ?)', [sessionId, eventId, createdBy, name]) return this.findById(sessionId) }, async findById(sessionId) { const results = await query('SELECT * FROM photo_sessions WHERE id = ?', [sessionId]) return results[0] || null }, async findByEvent(eventId) { return query('SELECT * FROM photo_sessions WHERE event_id = ? ORDER BY created_at DESC', [eventId]) }, async update(sessionId, updates) { const fields = Object.keys(updates).map(k => \`\${k} = ?\`).join(', ') const values = Object.values(updates) values.push(sessionId) await query(\`UPDATE photo_sessions SET \${fields} WHERE id = ?\`, values) return this.findById(sessionId) }, async delete(sessionId) { await query('DELETE FROM photo_sessions WHERE id = ?', [sessionId]) }, } // Photo model export const Photo = { async create(photoId, sessionId, fileName, filePath, fileSize, uploadedAt) { await query('INSERT INTO photos (id, session_id, file_name, file_path, file_size, uploaded_at) VALUES (?, ?, ?, ?, ?, ?)', [photoId, sessionId, fileName, filePath, fileSize, uploadedAt]) return this.findById(photoId) }, async findById(photoId) { const results = await query('SELECT * FROM photos WHERE id = ?', [photoId]) return results[0] || null }, async findBySession(sessionId) { return query('SELECT * FROM photos WHERE session_id = ? ORDER BY uploaded_at DESC', [sessionId]) }, async delete(photoId) { await query('DELETE FROM photos WHERE id = ?', [photoId]) }, async getCount(sessionId) { const results = await query('SELECT COUNT(*) as count FROM photos WHERE session_id = ?', [sessionId]) return results[0]?.count || 0 }, } // Event model export const Event = { async create(eventId, createdBy, eventName, description) { await query('INSERT INTO events (id, created_by, event_name, description) VALUES (?, ?, ?, ?)', [eventId, createdBy, eventName, description]) return this.findById(eventId) }, async findById(eventId) { const results = await query('SELECT * FROM events WHERE id = ?', [eventId]) return results[0] || null }, async findByUser(userId) { return query('SELECT * FROM events WHERE created_by = ? ORDER BY created_at DESC', [userId]) }, async update(eventId, updates) { const fields = Object.keys(updates).map(k => \`\${k} = ?\`).join(', ') const values = Object.values(updates) values.push(eventId) await query(\`UPDATE events SET \${fields} WHERE id = ?\`, values) return this.findById(eventId) }, async delete(eventId) { await query('DELETE FROM events WHERE id = ?', [eventId]) }, } `); console.log('Created src/models.js'); // src/routes/auth.js fs.writeFileSync(`${baseDir}/src/routes/auth.js`, `import { Router } from 'express' import { User } from '../models.js' import { createAccessToken, createRefreshToken, verifyToken } from '../jwt.js' import bcryptjs from 'bcryptjs' import { v4 as uuidv4 } from 'uuid' const router = Router() // Register router.post('/register', async (req, res) => { const { email, username, password } = req.body if (!email || !username || !password) { return res.status(400).json({ error: 'Missing required fields' }) } try { const existingEmail = await User.findByEmail(email) if (existingEmail) { return res.status(400).json({ error: 'Email already registered' }) } const existingUser = await User.findByUsername(username) if (existingUser) { return res.status(400).json({ error: 'Username already taken' }) } const passwordHash = await bcryptjs.hash(password, 10) const user = await User.create(email, username, passwordHash) const accessToken = createAccessToken(user.id) const refreshToken = createRefreshToken() res.json({ user: { id: user.id, email: user.email, username: user.username, }, accessToken, refreshToken, }) } catch (error) { res.status(500).json({ error: error.message }) } }) // Login router.post('/login', async (req, res) => { const { email, password } = req.body if (!email || !password) { return res.status(400).json({ error: 'Email and password required' }) } try { const user = await User.findByEmail(email) if (!user) { return res.status(401).json({ error: 'Invalid email or password' }) } const valid = await User.verifyPassword(user, password) if (!valid) { return res.status(401).json({ error: 'Invalid email or password' }) } await User.updateLastLogin(user.id) const accessToken = createAccessToken(user.id) const refreshToken = createRefreshToken() res.json({ user: { id: user.id, email: user.email, username: user.username, }, accessToken, refreshToken, }) } catch (error) { res.status(500).json({ error: error.message }) } }) // Refresh token router.post('/refresh', (req, res) => { const { refreshToken } = req.body if (!refreshToken) { return res.status(400).json({ error: 'Refresh token required' }) } const decoded = verifyToken(refreshToken) if (!decoded || !decoded.userId) { return res.status(401).json({ error: 'Invalid refresh token' }) } const newAccessToken = createAccessToken(decoded.userId) res.json({ accessToken: newAccessToken }) }) // Logout (stateless, just return success) router.post('/logout', (req, res) => { res.json({ message: 'Logged out successfully' }) }) export default router `); console.log('Created src/routes/auth.js'); // src/routes/media.js fs.writeFileSync(`${baseDir}/src/routes/media.js`, `import { Router } from 'express' import multer from 'multer' import path from 'path' import fs from 'fs' import { v4 as uuidv4 } from 'uuid' import { Photo, Session } from '../models.js' import { authenticateToken } from '../middleware.js' const router = Router() // Configure multer for file uploads const uploadsDir = process.env.UPLOADS_DIR || './uploads' if (!fs.existsSync(uploadsDir)) { fs.mkdirSync(uploadsDir, { recursive: true }) } const storage = multer.diskStorage({ destination: (req, file, cb) => { cb(null, uploadsDir) }, filename: (req, file, cb) => { const ext = path.extname(file.originalname) const filename = \`\${uuidv4()}\${ext}\` cb(null, filename) }, }) const upload = multer({ storage, limits: { fileSize: parseInt(process.env.MAX_FILE_SIZE) || 10485760 }, fileFilter: (req, file, cb) => { const allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'] if (allowedMimes.includes(file.mimetype)) { cb(null, true) } else { cb(new Error('Invalid file type')) } }, }) // Upload photo router.post('/upload/:sessionId', authenticateToken, upload.single('photo'), async (req, res) => { try { const { sessionId } = req.params const session = await Session.findById(sessionId) if (!session) { if (req.file) fs.unlinkSync(req.file.path) return res.status(404).json({ error: 'Session not found' }) } const photoId = uuidv4() const photo = await Photo.create( photoId, sessionId, req.file.originalname, req.file.path, req.file.size, new Date() ) res.json(photo) } catch (error) { if (req.file) fs.unlinkSync(req.file.path) res.status(500).json({ error: error.message }) } }) // Get photos for session router.get('/session/:sessionId', authenticateToken, async (req, res) => { try { const { sessionId } = req.params const photos = await Photo.findBySession(sessionId) res.json(photos) } catch (error) { res.status(500).json({ error: error.message }) } }) // Delete photo router.delete('/:photoId', authenticateToken, async (req, res) => { try { const { photoId } = req.params const photo = await Photo.findById(photoId) if (!photo) { return res.status(404).json({ error: 'Photo not found' }) } if (fs.existsSync(photo.file_path)) { fs.unlinkSync(photo.file_path) } await Photo.delete(photoId) res.json({ message: 'Photo deleted' }) } catch (error) { res.status(500).json({ error: error.message }) } }) export default router `); console.log('Created src/routes/media.js'); // src/middleware.js fs.writeFileSync(`${baseDir}/src/middleware.js`, `import { verifyToken } from './jwt.js' export 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' }) } const decoded = verifyToken(token) if (!decoded) { return res.status(403).json({ error: 'Invalid or expired token' }) } req.userId = decoded.userId next() } export function errorHandler(err, req, res, next) { console.error(err) res.status(err.status || 500).json({ error: err.message || 'Internal server error' }) } `); console.log('Created src/middleware.js'); // src/server.js fs.writeFileSync(`${baseDir}/src/server.js`, `import express from 'express' import cors from 'cors' import 'express-async-errors' import dotenv from 'dotenv' import authRoutes from './routes/auth.js' import mediaRoutes from './routes/media.js' import { errorHandler } from './middleware.js' dotenv.config() const app = express() const PORT = process.env.PORT || 3000 // Middleware app.use(cors({ origin: process.env.FRONTEND_URL || 'http://localhost:5173', credentials: true, })) app.use(express.json()) app.use(express.urlencoded({ extended: true })) // Routes app.use('/api/auth', authRoutes) app.use('/api/media', mediaRoutes) // Health check app.get('/api/health', (req, res) => { res.json({ status: 'ok' }) }) // Error handling app.use(errorHandler) // 404 handler app.use((req, res) => { res.status(404).json({ error: 'Not found' }) }) // Start server app.listen(PORT, () => { console.log(\`Server running on port \${PORT}\`) }) `); console.log('Created src/server.js'); console.log('\\n✅ Backend structure created successfully!'); console.log('Next steps:'); console.log(' cd backend'); console.log(' npm install'); console.log(' cp .env.example .env'); console.log(' npm run dev');