Add support for deletekeys

This commit is contained in:
rubikscraft 2022-09-03 20:03:28 +02:00
parent 8ffb06c059
commit 92e44aea66
No known key found for this signature in database
GPG key ID: 1463EBE9200A5CD4
9 changed files with 134 additions and 27 deletions

View file

@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { AsyncFailable, Fail, FT } from 'picsur-shared/dist/types';
import { FindResult } from 'picsur-shared/dist/types/find-result';
import { generateRandomString } from 'picsur-shared/dist/util/random';
import { In, Repository } from 'typeorm';
import { EImageDerivativeBackend } from '../../database/entities/image-derivative.entity';
import { EImageFileBackend } from '../../database/entities/image-file.entity';
@ -23,11 +24,13 @@ export class ImageDBService {
public async create(
userid: string,
filename: string,
withDeleteKey: boolean,
): AsyncFailable<EImageBackend> {
let imageEntity = new EImageBackend();
imageEntity.user_id = userid;
imageEntity.created = new Date();
imageEntity.file_name = filename;
if (withDeleteKey) imageEntity.delete_key = generateRandomString(32);
try {
imageEntity = await this.imageRepo.save(imageEntity, { reload: true });
@ -122,6 +125,34 @@ export class ImageDBService {
}
}
public async deleteWithKey(
id: string,
key: string,
): AsyncFailable<EImageBackend> {
try {
const found = await this.imageRepo.findOne({
where: { id, delete_key: key },
});
if (!found) return Fail(FT.NotFound, 'Image not found');
await Promise.all([
this.imageDerivativeRepo.delete({
image_id: found.id,
}),
this.imageFileRepo.delete({
image_id: found.id,
}),
this.imageRepo.delete({ id: found.id }),
]);
return found;
} catch (e) {
return Fail(FT.Database, e);
}
}
public async deleteAll(IAmSure: boolean): AsyncFailable<true> {
if (!IAmSure)
return Fail(

View file

@ -18,11 +18,16 @@ export class EImageBackend implements EImage {
@Column({
nullable: false,
default: "image",
default: 'image',
})
file_name: string;
// @Column({
// nullable: false,
// })
@Column({
nullable: true,
transformer: {
from: (value: string | null) => (value === null ? undefined : value),
to: (value: string | undefined) => (value === undefined ? null : value),
},
})
delete_key?: string;
}

View file

@ -1,14 +1,14 @@
import {
createParamDecorator,
ExecutionContext,
SetMetadata,
UseGuards
createParamDecorator,
ExecutionContext,
SetMetadata,
UseGuards
} from '@nestjs/common';
import { Fail, FT } from 'picsur-shared/dist/types';
import { CombineFCDecorators } from 'picsur-shared/dist/util/decorator';
import { LocalAuthGuard } from '../managers/auth/guards/local-auth.guard';
import { Permission, Permissions } from '../models/constants/permissions.const';
import AuthFasityRequest from '../models/interfaces/authrequest.dto';
import AuthFastifyRequest from '../models/interfaces/authrequest.dto';
export const RequiredPermissions = (...permissions: Permissions) => {
return SetMetadata('permissions', permissions);
@ -26,7 +26,7 @@ export const UseLocalAuth = (...permissions: Permissions) =>
export const HasPermission = createParamDecorator(
(data: Permission, ctx: ExecutionContext) => {
const req: AuthFasityRequest = ctx.switchToHttp().getRequest();
const req: AuthFastifyRequest = ctx.switchToHttp().getRequest();
const permissions = req.userPermissions;
if (!permissions) {
throw Fail(FT.Internal, undefined, 'Permissions are missing from request');
@ -38,7 +38,7 @@ export const HasPermission = createParamDecorator(
export const GetPermissions = createParamDecorator(
(data: Permission, ctx: ExecutionContext) => {
const req: AuthFasityRequest = ctx.switchToHttp().getRequest();
const req: AuthFastifyRequest = ctx.switchToHttp().getRequest();
const permissions = req.userPermissions;
if (!permissions) {
throw Fail(FT.Internal, undefined, 'Permissions are missing from request');

View file

@ -1,17 +1,17 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { Fail, FT } from 'picsur-shared/dist/types';
import AuthFasityRequest from '../models/interfaces/authrequest.dto';
import AuthFastifyRequest from '../models/interfaces/authrequest.dto';
export const ReqUser = createParamDecorator(
(input: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest() as AuthFasityRequest;
const request = ctx.switchToHttp().getRequest() as AuthFastifyRequest;
return request.user;
},
);
export const ReqUserID = createParamDecorator(
(input: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest() as AuthFasityRequest;
const request = ctx.switchToHttp().getRequest() as AuthFastifyRequest;
const id = request.user.id;
if (!id) throw Fail(FT.Internal, undefined, 'User ID is not set');
return id;

View file

@ -59,10 +59,18 @@ export class ImageManagerService {
return await this.imagesService.delete(ids, userid);
}
public async deleteWithKey(
imageId: string,
key: string,
): AsyncFailable<EImageBackend> {
return await this.imagesService.deleteWithKey(imageId, key);
}
public async upload(
userid: string,
filename: string,
image: Buffer,
withDeleteKey: boolean,
): AsyncFailable<EImageBackend> {
const fileType = await this.getFileTypeFromBuffer(image);
if (HasFailed(fileType)) return fileType;
@ -86,7 +94,11 @@ export class ImageManagerService {
})();
// Save processed to db
const imageEntity = await this.imagesService.create(userid, name);
const imageEntity = await this.imagesService.create(
userid,
name,
withDeleteKey,
);
if (HasFailed(imageEntity)) return imageEntity;
const imageFileEntity = await this.imageFilesService.setFile(

View file

@ -3,7 +3,7 @@ import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { Permissions } from '../constants/permissions.const';
// Add typing to FastifyRequest to make using the user object easier
export default interface AuthFasityRequest extends FastifyRequest {
export default interface AuthFastifyRequest extends FastifyRequest {
user: EUser;
userPermissions?: Permissions;
}

View file

@ -1,20 +1,21 @@
import { Controller, Get, Request } from '@nestjs/common';
import { Controller, Get, Request, Response } from '@nestjs/common';
import type { FastifyReply } from 'fastify';
import { UserInfoResponse } from 'picsur-shared/dist/dto/api/user-manage.dto';
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
import { RequiredPermissions } from '../../../decorators/permissions.decorator';
import { NoPermissions } from '../../../decorators/permissions.decorator';
import { Returns } from '../../../decorators/returns.decorator';
import type AuthFasityRequest from '../../../models/interfaces/authrequest.dto';
import type AuthFastifyRequest from '../../../models/interfaces/authrequest.dto';
@Controller('api/experiment')
@RequiredPermissions(Permission.SysPrefAdmin)
@NoPermissions()
export class ExperimentController {
constructor() {}
@Get()
@Returns(UserInfoResponse)
async testRoute(
@Request() req: AuthFasityRequest,
@Request() req: AuthFastifyRequest,
@Response() res: FastifyReply,
): Promise<UserInfoResponse> {
res.header('Location', '/penis');
return req.user;
}
}

View file

@ -1,13 +1,24 @@
import { Body, Controller, Logger, Post } from '@nestjs/common';
import {
Body,
Controller,
Get,
Logger,
Param,
Post,
Res
} from '@nestjs/common';
import type { FastifyReply } from 'fastify';
import {
ImageDeleteRequest,
ImageDeleteResponse,
ImageDeleteWithKeyRequest,
ImageDeleteWithKeyResponse,
ImageListRequest,
ImageListResponse,
ImageUploadResponse
} from 'picsur-shared/dist/dto/api/image-manage.dto';
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
import { ThrowIfFailed } from 'picsur-shared/dist/types';
import { HasFailed, ThrowIfFailed } from 'picsur-shared/dist/types';
import { MultiPart } from '../../decorators/multipart/multipart.decorator';
import {
HasPermission,
@ -17,7 +28,6 @@ import { ReqUserID } from '../../decorators/request-user.decorator';
import { Returns } from '../../decorators/returns.decorator';
import { ImageManagerService } from '../../managers/image/image.service';
import { ImageUploadDto } from '../../models/dto/image-upload.dto';
@Controller('api/image')
@RequiredPermissions(Permission.ImageUpload)
export class ImageManageController {
@ -30,12 +40,14 @@ export class ImageManageController {
async uploadImage(
@MultiPart() multipart: ImageUploadDto,
@ReqUserID() userid: string,
@HasPermission(Permission.ImageDeleteKey) withDeleteKey: boolean,
): Promise<ImageUploadResponse> {
const image = ThrowIfFailed(
await this.imagesService.upload(
userid,
multipart.image.filename,
multipart.image.buffer,
withDeleteKey,
),
);
@ -80,4 +92,32 @@ export class ImageManageController {
images: deletedImages,
};
}
@Post('delete/key')
@RequiredPermissions(Permission.ImageDeleteKey)
@Returns(ImageDeleteWithKeyResponse)
async deleteImageWithKeyGet(
@Body() body: ImageDeleteWithKeyRequest,
): Promise<ImageDeleteWithKeyResponse> {
return ThrowIfFailed(
await this.imagesService.deleteWithKey(body.id, body.key),
);
}
@Get('delete/:id/:key')
@RequiredPermissions(Permission.ImageDeleteKey)
async deleteImageWithKeyPost(
@Param() params: ImageDeleteWithKeyRequest,
@Res({ passthrough: true }) res: FastifyReply,
): Promise<string> {
const image = await this.imagesService.deleteWithKey(params.id, params.key);
if (HasFailed(image)) {
res.code(image.getCode());
res.type('text/html');
return `<html><body><h1>Error</h1><p>${image.getReason()}</p></body></html>`;
}
res.type('text/html');
return '<html><body><h1>Image deleted</h1></body></html>';
}
}

View file

@ -1,10 +1,14 @@
import { z } from 'zod';
import { EImageSchema } from '../../entities/image.entity';
import { createZodDto } from '../../util/create-zod-dto';
import { IsApiKey } from '../../validators/api-key.validator';
import { IsEntityID } from '../../validators/entity-id.validator';
import { IsPosInt } from '../../validators/positive-int.validator';
// Image upload
export const ImageUploadResponseSchema = EImageSchema;
export const ImageUploadResponseSchema = EImageSchema.extend({
delete_key: IsApiKey().optional(),
});
export class ImageUploadResponse extends createZodDto(
ImageUploadResponseSchema,
) {}
@ -41,3 +45,17 @@ export const ImageDeleteResponseSchema = z.object({
export class ImageDeleteResponse extends createZodDto(
ImageDeleteResponseSchema,
) {}
// Image Delete with Key
export const ImageDeleteWithKeyRequestSchema = z.object({
id: IsEntityID(),
key: IsApiKey(),
});
export class ImageDeleteWithKeyRequest extends createZodDto(
ImageDeleteWithKeyRequestSchema,
) {}
export const ImageDeleteWithKeyResponseSchema = EImageSchema;
export class ImageDeleteWithKeyResponse extends createZodDto(
ImageDeleteWithKeyResponseSchema,
) {}