Files
photobooth/create-backend.js
T
HayemR 79b959aac7
Deploy photobooth / deploy (push) Has been cancelled
database
2026-05-26 10:14:31 +07:00

563 lines
14 KiB
JavaScript

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');