// page.js // Copyright (C) 2022,2023 DTP Technologies, LLC // All Rights Reserved 'use strict'; import fs from 'node:fs'; import { SiteController, SiteError } from '../../lib/site-lib.js'; import { populateImageId, } from './lib/populators.js'; export default class ImageController extends SiteController { static get name ( ) { return 'ImageController'; } static get slug ( ) { return 'image'; } constructor (dtp) { super(dtp, ImageController); } async start ( ) { const { dtp } = this; const { limiter: limiterService } = dtp.services; const limiterConfig = limiterService.config.image; const router = this.createRouter('/image'); const imageUpload = this.createMulter(ImageController.slug, { limits: { fileSize: 1024 * 1000 * 5, }, }); router.use(async (req, res, next) => { res.locals.currentView = 'image'; return next(); }); router.param('imageId', populateImageId(this)); router.post('/', limiterService.create(limiterConfig.postCreateImage), imageUpload.single('file'), this.postCreateImage.bind(this), ); router.get('/proxy', limiterService.create(limiterConfig.getProxyImage), this.getProxyImage.bind(this), ); router.get('/:imageId', limiterService.create(limiterConfig.getImage), this.getHostCacheImage.bind(this), // this.getImage.bind(this), ); router.delete('/:imageId', limiterService.create(limiterConfig.deleteImage), this.deleteImage.bind(this), ); } async postCreateImage (req, res, next) { const { image: imageService } = this.dtp.services; try { res.locals.image = await imageService.create(req.user, req.body, req.file); res.status(200).json({ success: true, imageId: res.locals.image._id.toString(), }); } catch (error) { this.log.error('failed to create image', { error }); return next(error); } } async getProxyImage (req, res) { const { hostCache: hostCacheService } = this.dtp.services; try { if (!req.query || !req.query.url) { throw new SiteError(400, 'Missing url parameter'); } const fileInfo = await hostCacheService.fetchUrl(req.query.url); const stream = fs.createReadStream(fileInfo.file.path); res.setHeader('Cache-Control', 'public, maxage=86400, s-maxage=86400, immutable'); res.setHeader('Content-Type', fileInfo.file.meta.contentType); res.setHeader('Content-Length', fileInfo.file.stats.size); res.status(200); stream.pipe(res); } catch (error) { this.log.error('failed to fetch web resource', { url: req.query.url, error }); return res.status(error.statusCode || 500).json({ success: false, message: error.message, }); } } async getHostCacheImage (req, res) { const { hostCache: hostCacheService } = this.dtp.services; const { image } = res.locals; try { const fileInfo = await hostCacheService.getFile(image.file.bucket, image.file.key); const stream = fs.createReadStream(fileInfo.file.path); res.setHeader('Cache-Control', 'public, maxage=86400, s-maxage=86400, immutable'); res.setHeader('Content-Type', image.type); res.setHeader('Content-Length', fileInfo.file.stats.size); res.status(200); stream.pipe(res); } catch (error) { this.log.error('failed to fetch image', { image, error }); return res.status(error.statusCode || 500).json({ success: false, message: error.message, }); } } async getImage (req, res) { const { minio: minioService } = this.dtp.services; try { const stream = await minioService.openDownloadStream({ bucket: res.locals.image.file.bucket, key: res.locals.image.file.key }); res.setHeader('Cache-Control', 'public, maxage=86400, s-maxage=86400, immutable'); res.setHeader('Content-Type', res.locals.image.type); res.setHeader('Content-Length', res.locals.image.size); res.status(200); stream.pipe(res); } catch (error) { return res.status(error.statusCode || 500).json({ success: false, message: error.message, }); } } async deleteImage (req, res) { const { image: imageService } = this.dtp.services; try { if (!req.user) { throw new SiteError(403, 'Must be logged in to delete images you own'); } if (!req.user._id.equals(res.locals.image.owner._id)) { throw new SiteError(403, 'You are not the owner of the requested image'); } this.log.debug('deleting image', { imageId: res.locals.image._id }); await imageService.deleteImage(res.locals.image); res.status(200).json({ success: true }); } catch (error) { this.log.error('failed to delete image', { error }); res.status(error.statusCode || 500).json({ success: false, message: error.message, }); } } }