import type { HttpContext } from '@adonisjs/core/http' import app from '@adonisjs/core/services/app' import logger from '@adonisjs/core/services/logger' import { cuid } from '@adonisjs/core/helpers' import { createReadStream } from 'node:fs' import { promises as fs } from 'node:fs' import { extname } from 'node:path' const allowedExtnames = ['jpg', 'jpeg', 'png', 'webp', 'gif'] const imageMimeByExt: Record = { '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.webp': 'image/webp', '.gif': 'image/gif', } export default class ImagesController { async upload({ request, response }: HttpContext) { const image = request.file('image', { size: '10mb', extnames: allowedExtnames, }) if (!image) { return response.badRequest({ message: 'Image file is required' }) } if (!image.isValid) { const message = image.errors.map((error) => error.message).join(', ') return response.badRequest({ message: message || 'Invalid image file' }) } const uploadsDir = app.makePath('storage', 'uploads') await fs.mkdir(uploadsDir, { recursive: true }) const extension = image.extname || 'bin' const filename = `${cuid()}.${extension}` await image.move(uploadsDir, { name: filename, overwrite: false }) if (image.state !== 'moved') { logger.error('Image upload failed: %s', image.errors) return response.internalServerError({ message: 'Image upload failed' }) } return response.ok({ filename, url: `/api/images/${filename}`, }) } async show({ params, response }: HttpContext) { const filename = String(params.filename || '') if (!filename || filename.includes('/') || filename.includes('\\')) { return response.badRequest({ message: 'Invalid image name' }) } const filePath = app.makePath('storage', 'uploads', filename) try { await fs.access(filePath) } catch { return response.notFound({ message: 'Image not found' }) } const mimeType = imageMimeByExt[extname(filename).toLowerCase()] || 'application/octet-stream' response.header('Content-Type', mimeType) response.header('Cache-Control', 'public, max-age=86400') return response.stream(createReadStream(filePath)) } }