75 lines
2.2 KiB
TypeScript
75 lines
2.2 KiB
TypeScript
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<string, string> = {
|
|
'.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))
|
|
}
|
|
}
|