- Add centralized Zod schemas (server/utils/schemas.js) and validateBody utility for all API endpoints - Fix critical mass assignment in member creation: raw body no longer passed to new Member(), only validated fields (email, name, circle, contributionTier) are accepted - Apply Zod validation to login, profile patch, event registration, updates, verify-payment, and admin event creation endpoints - Fix logout cookie flags to match login (httpOnly: true, secure conditional on NODE_ENV) - Delete unauthenticated test/debug endpoints (test-connection, test-subscription, test-bot) - Remove sensitive console.log statements from Helcim and member endpoints - Remove unused bcryptjs dependency - Add 10MB file size limit on image uploads - Use runtime config for JWT secret across all endpoints - Add 38 validation tests (117 total, all passing)
83 lines
No EOL
2.3 KiB
JavaScript
83 lines
No EOL
2.3 KiB
JavaScript
import { v2 as cloudinary } from 'cloudinary'
|
|
import { requireAuth } from '../../utils/auth.js'
|
|
|
|
// Configure Cloudinary
|
|
cloudinary.config({
|
|
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
|
|
api_key: process.env.CLOUDINARY_API_KEY,
|
|
api_secret: process.env.CLOUDINARY_API_SECRET
|
|
})
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
try {
|
|
await requireAuth(event)
|
|
// Parse the multipart form data
|
|
const formData = await readMultipartFormData(event)
|
|
|
|
if (!formData || formData.length === 0) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'No file provided'
|
|
})
|
|
}
|
|
|
|
// Find the file in the form data
|
|
const fileData = formData.find(item => item.name === 'file')
|
|
|
|
if (!fileData) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'No file found in upload'
|
|
})
|
|
}
|
|
|
|
// Validate file type
|
|
if (!fileData.type?.startsWith('image/')) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'Invalid file type. Only images are allowed.'
|
|
})
|
|
}
|
|
|
|
// Validate file size (10MB limit)
|
|
const maxSize = 10 * 1024 * 1024
|
|
if (fileData.data.length > maxSize) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'File too large. Maximum size is 10MB.'
|
|
})
|
|
}
|
|
|
|
// Convert buffer to base64 for Cloudinary upload
|
|
const base64File = `data:${fileData.type};base64,${fileData.data.toString('base64')}`
|
|
|
|
// Upload to Cloudinary
|
|
const result = await cloudinary.uploader.upload(base64File, {
|
|
folder: 'ghost-guild/events',
|
|
transformation: [
|
|
{ quality: 'auto', fetch_format: 'auto' },
|
|
{ width: 1200, height: 630, crop: 'fill' } // Standard social media dimensions
|
|
],
|
|
allowed_formats: ['jpg', 'png', 'webp', 'gif'],
|
|
resource_type: 'image'
|
|
})
|
|
|
|
return {
|
|
success: true,
|
|
secure_url: result.secure_url,
|
|
public_id: result.public_id,
|
|
width: result.width,
|
|
height: result.height,
|
|
format: result.format,
|
|
bytes: result.bytes
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Image upload error:', error)
|
|
|
|
throw createError({
|
|
statusCode: error.statusCode || 500,
|
|
statusMessage: error.statusMessage || 'Image upload failed'
|
|
})
|
|
}
|
|
}) |