Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
454 changes: 13 additions & 441 deletions src/index.js

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions src/middleware/upload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const multer = require('multer')
const path = require('node:path')
const fs = require('node:fs')

// Configure multer for file uploads
const storage = multer.diskStorage({
destination: (_req, file, cb) => {
let uploadDir
if (file.fieldname === 'circuitImage') {
uploadDir = path.join(__dirname, '../../public/uploads/circuits')
} else {
uploadDir = path.join(__dirname, '../../public/uploads')
}

// Ensure upload directory exists
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true })
}
cb(null, uploadDir)
},
filename: (_req, file, cb) => {
// Generate unique filename
const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1e9)}`
cb(null, `${file.fieldname}-${uniqueSuffix}${path.extname(file.originalname)}`)
},
})

const fileFilter = (_req, file, cb) => {
// Accept only image files
if (file.mimetype.startsWith('image/')) {
cb(null, true)
} else {
cb(new Error('Only image files are allowed!'), false)
}
}

const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB limit
},
})

module.exports = { upload }
43 changes: 43 additions & 0 deletions src/routes/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const express = require('express')
const { passport } = require('../config/auth')

const router = express.Router()

// Authentication routes
router.get('/google', passport.authenticate('google', { scope: ['profile', 'email'] }))

router.get(
'/google/callback',
passport.authenticate('google', { failureRedirect: '/' }),
(_req, res) => {
// Successful authentication
res.redirect('/')
}
)

router.post('/logout', (req, res) => {
req.logout((err) => {
if (err) {
return res.status(500).json({ error: 'Error logging out' })
}
res.json({ message: 'Logged out successfully' })
})
})

// Get current user info
router.get('/user', (req, res) => {
if (req.isAuthenticated()) {
const authorizedEmails = process.env.AUTHORIZED_EMAILS
? process.env.AUTHORIZED_EMAILS.split(',').map((email) => email.trim())
: []

res.json({
user: req.user,
isAuthorized: authorizedEmails.includes(req.user.email),
})
} else {
res.json({ user: null, isAuthorized: false })
}
})

module.exports = router
144 changes: 144 additions & 0 deletions src/routes/leaderboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
const express = require('express')
const { isAuthorized } = require('../config/auth')
const { upload } = require('../middleware/upload')
const {
deleteFileIfExists,
cleanupUploadedFile,
getPublicFilePath,
} = require('../utils/fileManager')
const Leaderboard = require('../models/leaderboard')

const router = express.Router()

// Public leaderboard route (no auth required)
router.get('/', async (_req, res) => {
try {
const leaderboards = await Leaderboard.findAll({
order: [['points', 'DESC']],
})
res.json(leaderboards)
} catch (_error) {
res.status(500).json({ error: 'Error fetching leaderboards' })
}
})

// Protected routes (require authorization)
router.post('/', isAuthorized, async (req, res) => {
try {
const { driverName, points } = req.body
const newEntry = await Leaderboard.create({ driverName, points })
res.status(201).json(newEntry)
} catch (_error) {
res.status(500).json({ error: 'Error creating leaderboard entry' })
}
})

// Upload profile picture endpoint
router.post(
'/:id/profile-picture',
isAuthorized,
upload.single('profilePicture'),
async (req, res) => {
try {
const { id } = req.params

if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' })
}

const entry = await Leaderboard.findByPk(id)
if (!entry) {
// Clean up uploaded file if driver not found
cleanupUploadedFile(req)
return res.status(404).json({ error: 'Leaderboard entry not found' })
}

// Delete old profile picture if exists
if (entry.profilePicture) {
const oldPicturePath = getPublicFilePath(entry.profilePicture)
deleteFileIfExists(oldPicturePath)
}

// Update with new profile picture path
const profilePicturePath = `/uploads/${req.file.filename}`
await entry.update({ profilePicture: profilePicturePath })

res.json({
message: 'Profile picture uploaded successfully',
profilePicture: profilePicturePath,
})
} catch (_error) {
// Clean up uploaded file on error
cleanupUploadedFile(req)
res.status(500).json({ error: 'Error uploading profile picture' })
}
}
)

// Delete profile picture endpoint
router.delete('/:id/profile-picture', isAuthorized, async (req, res) => {
try {
const { id } = req.params
const entry = await Leaderboard.findByPk(id)

if (!entry) {
return res.status(404).json({ error: 'Leaderboard entry not found' })
}

// Delete profile picture file if exists
if (entry.profilePicture) {
const picturePath = getPublicFilePath(entry.profilePicture)
deleteFileIfExists(picturePath)
}

// Update database to remove profile picture
await entry.update({ profilePicture: null })

res.json({ message: 'Profile picture deleted successfully' })
} catch (_error) {
res.status(500).json({ error: 'Error deleting profile picture' })
}
})

// Update leaderboard entry
router.put('/:id', isAuthorized, async (req, res) => {
try {
const { id } = req.params
const { driverName, points } = req.body

const entry = await Leaderboard.findByPk(id)
if (!entry) {
return res.status(404).json({ error: 'Leaderboard entry not found' })
}

await entry.update({ driverName, points })
res.json(entry)
} catch (_error) {
res.status(500).json({ error: 'Error updating leaderboard entry' })
}
})

// Delete leaderboard entry
router.delete('/:id', isAuthorized, async (req, res) => {
try {
const { id } = req.params
const entry = await Leaderboard.findByPk(id)

if (!entry) {
return res.status(404).json({ error: 'Leaderboard entry not found' })
}

// Delete profile picture file if exists
if (entry.profilePicture) {
const picturePath = getPublicFilePath(entry.profilePicture)
deleteFileIfExists(picturePath)
}

await entry.destroy()
res.json({ message: 'Leaderboard entry deleted successfully' })
} catch (_error) {
res.status(500).json({ error: 'Error deleting leaderboard entry' })
}
})

module.exports = router
Loading
Loading