You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
4.9 KiB
170 lines
4.9 KiB
// 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,
|
|
});
|
|
}
|
|
}
|
|
}
|