mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-26 06:23:35 -04:00
141 lines
4.2 KiB
TypeScript
141 lines
4.2 KiB
TypeScript
import { Router } from 'express';
|
|
import { ulid } from 'ulid';
|
|
import multer from 'multer';
|
|
import { loadImage, createCanvas } from 'canvas';
|
|
import type { Canvas } from 'canvas';
|
|
import { handleErrorAsync } from '../../src/helpers.ts';
|
|
import sharp from 'sharp';
|
|
import fs from 'fs';
|
|
import SQL from 'sql-template-strings';
|
|
import path from 'path';
|
|
import auditLog from '../audit.ts';
|
|
|
|
import { awsConfig, awsParams } from '../aws.ts';
|
|
import { S3 } from '@aws-sdk/client-s3';
|
|
|
|
const sizes = {
|
|
big: [1200, false],
|
|
flag: [256, false],
|
|
thumb: [192, true],
|
|
avatar: [144, true],
|
|
} as const;
|
|
|
|
const resizeImage = (
|
|
image: Canvas,
|
|
width: number,
|
|
height: number,
|
|
sx: number | null = null,
|
|
sy: number | null = null,
|
|
): Canvas => {
|
|
const canvas = createCanvas(width, height);
|
|
if (sx === null || sy === null) {
|
|
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
|
|
} else {
|
|
canvas.getContext('2d').drawImage(image, sx, sy, width, height, 0, 0, width, height);
|
|
}
|
|
|
|
return canvas;
|
|
};
|
|
|
|
const cropToSquare = (image: Canvas): Canvas => {
|
|
return image.width > image.height
|
|
? resizeImage(image, image.height, image.height, (image.width - image.height) / 2, 0)
|
|
: resizeImage(image, image.width, image.width, 0, (image.height - image.width) / 2);
|
|
};
|
|
|
|
const scaleDownTo = (image: Canvas, size: number): Canvas => {
|
|
if (image.width > image.height) {
|
|
return image.width > size
|
|
? resizeImage(image, size, image.height * size / image.width)
|
|
: image;
|
|
}
|
|
|
|
return image.height > size
|
|
? resizeImage(image, image.width * size / image.height, size)
|
|
: image;
|
|
};
|
|
|
|
const router = Router();
|
|
|
|
router.post('/images/upload', multer({ limits: { fileSize: 10 * 1024 * 1024 } }).any(), handleErrorAsync(async (req, res) => {
|
|
const s3 = new S3(awsConfig);
|
|
|
|
const requestedSizes = req.query.sizes === undefined || req.query.sizes === 'all'
|
|
? null
|
|
: (req.query.sizes as string).split(',');
|
|
|
|
const ids = [];
|
|
for (const file of req.files as Express.Multer.File[]) {
|
|
const id = ulid();
|
|
let image;
|
|
try {
|
|
image = await loadImage(await sharp(file.buffer).png()
|
|
.toBuffer());
|
|
} catch {
|
|
return res.status(400).json({ error: 'error.invalidImage' });
|
|
}
|
|
|
|
for (const [name, [size, square]] of Object.entries(sizes)) {
|
|
if (requestedSizes !== null && !requestedSizes.includes(name)) {
|
|
continue;
|
|
}
|
|
|
|
let canvas = createCanvas(image.width, image.height);
|
|
canvas.getContext('2d').drawImage(image, 0, 0);
|
|
|
|
if (square) {
|
|
canvas = cropToSquare(canvas);
|
|
}
|
|
|
|
canvas = scaleDownTo(canvas, size);
|
|
|
|
await s3.putObject({
|
|
Key: `images/${id}-${name}.png`,
|
|
Body: canvas.toBuffer('image/png'),
|
|
ContentType: 'image/png',
|
|
ACL: 'public-read',
|
|
...awsParams,
|
|
});
|
|
}
|
|
|
|
ids.push(id);
|
|
}
|
|
|
|
await auditLog(req, 'images/uploaded', { ids });
|
|
|
|
return res.json(ids);
|
|
}));
|
|
|
|
router.get('/download/:filename*', handleErrorAsync(async (req, res) => {
|
|
const filename = req.params.filename + req.params[0];
|
|
|
|
const filepath = `${__dirname}/../../locale/${global.config.locale}/files/${filename}`;
|
|
|
|
if (!fs.existsSync(filepath)) {
|
|
return res.status(404).json({ error: 'Not found' });
|
|
}
|
|
|
|
await req.db.get(SQL`INSERT INTO downloads (id, locale, filename) VALUES (${ulid()}, ${global.config.locale}, ${filename});`);
|
|
|
|
return res.download(path.resolve(filepath));
|
|
}));
|
|
|
|
router.get('/downloads', handleErrorAsync(async (req, res) => {
|
|
if (!req.isGranted('users') && !req.isGranted('community')) {
|
|
return res.status(401).json({ error: 'Unauthorised' });
|
|
}
|
|
|
|
const stats: Record<string, number> = {};
|
|
|
|
const downloads = await req.db.all<{ filename: string, c: number }>(SQL`
|
|
SELECT filename, count(*) as c FROM downloads WHERE locale = ${global.config.locale} GROUP BY filename;
|
|
`);
|
|
for (const { filename, c } of downloads) {
|
|
stats[filename] = c;
|
|
}
|
|
|
|
return res.json(stats);
|
|
}));
|
|
|
|
export default router;
|