chamesu
Version:
This package contains all packages of the chat application.
183 lines (159 loc) • 5.15 kB
text/typescript
/*
* Copyright (c) 2022.
* Author Peter Placzek (tada5hi)
* For the full copyright and license information,
* view the LICENSE file that was distributed with this source code.
*/
import {AuthUserImageType} from "@chamesu/common/domains/auth/user/type";
import {Request} from "express";
import fs from "fs";
import path from "path";
import {v4} from "uuid";
import {
createQueueMessageTemplate,
publishMessageQueue
} from "../../../../modules/message-queue";
import {ImageCropCoordinates, ImageCropResizeConfig} from "@chamesu/common/domains/image/crop";
export async function uploadUserImageWithRequest(request: Request, destPath: string, userId: number) : Promise<{
type: string,
path: string,
original: string,
coordinates: ImageCropCoordinates
}>{
const { coordinatesWidth, coordinatesHeight, coordinatesLeft, coordinatesTop } = <Record<string,any>> request.body;
const width : number = parseInt(coordinatesWidth);
const height : number = parseInt(coordinatesHeight);
const left : number = parseInt(coordinatesLeft);
const top : number = parseInt(coordinatesTop);
if(Number.isNaN(width) || Number.isNaN(height) || Number.isNaN(left) || Number.isNaN(top)) {
throw new Error('Coordinaten des Bildes sind nicht gültig.');
}
const coordinates : ImageCropCoordinates = {width, height, left, top}
// @ts-ignore
const type : string | undefined = !!request.files.cover ? 'cover' : (!!request.files.avatar ? 'avatar' : undefined);
if(typeof type === 'undefined') {
throw new Error('Es wurde weder ein Avatar noch ein Cover zum Upload angegeben...');
}
// @ts-ignore
const buffer : Buffer = request.files[type][0].buffer;
const mimeTypesAllowed : string[] = [
'image/jpeg',
'image/png',
'image/jpg',
'image/svg+xml',
'image/tiff',
'image/gif'
];
// @ts-ignore
const mimeType : string = request.files[type][0].mimetype;
if(mimeTypesAllowed.indexOf(mimeType) === -1) {
throw new Error('Der mimetype '+mimeType+' ist nicht als Bildformat erlaubt.');
}
const upload = await uploadUserImage(destPath, userId, <AuthUserImageType> type, buffer, coordinates);
let queueMessage = createQueueMessageTemplate();
queueMessage.type = 'resize';
queueMessage.data = {
srcFilePath: upload.filePath,
cropCoordinates: coordinates,
cropResizeConfig: upload.cropResizeConfig,
type: 'user-'+type,
id: userId
};
publishMessageQueue('image.command', queueMessage).then(r => r);
return {
type,
path: upload.urlPath,
original: upload.fileName,
coordinates
}
}
export async function uploadUserImage(
destPath: string,
userId: number,
type: AuthUserImageType,
image: Buffer,
coordinates : ImageCropCoordinates
) : Promise<{
fileName: string,
filePath: string,
directoryPath: string,
urlPath: string,
cropResizeConfig: ImageCropResizeConfig
}> {
const directoryPath : string = path.resolve(destPath, 'user', type, userId.toString());
// clear existing avatar and sizes
if(fs.existsSync(directoryPath)) {
fs.rmdirSync(directoryPath, {recursive: true});
}
try {
await fs.promises.stat(directoryPath);
await fs.promises.chmod(directoryPath, 0o775);
} catch (e) {
await fs.promises.mkdir(directoryPath, {
mode: 0o775
});
}
let cropResizeConfig : ImageCropResizeConfig | undefined;
switch (type) {
case "avatar":
cropResizeConfig = UserTypeConfigs.avatar;
break;
case "cover":
cropResizeConfig = UserTypeConfigs.cover;
break;
}
const ratio : string = (coordinates.width / coordinates.height).toFixed(1);
if(ratio !== cropResizeConfig.ratio) {
throw new Error('Das Verhältnis des auszuschneidenen Teils ist nicht gültig.');
}
const fileName : string = v4()+'.png';
const filePath : string = path.resolve(directoryPath, fileName);
fs.writeFileSync(filePath, image);
return {
fileName,
filePath,
directoryPath,
urlPath: 'static/user/'+type+'/'+userId+'/',
cropResizeConfig
};
}
export const UserTypeConfigs : Record<AuthUserImageType, ImageCropResizeConfig> = {
avatar: {
ratio: '1.0',
sizes: [
{
name: 'large',
width: 400
},
{
name: 'medium',
width: 200
},
{
name: 'small',
width: 128
},
{
name: 'tiny',
width: 64
}
]
},
cover: {
ratio: '3.6',
sizes: [
{
name: 'large',
width: 3600
},
{
name: 'small',
width: 1800
},
{
name: 'tiny',
width: 900
}
]
}
}