Add support for deletekeys
This commit is contained in:
parent
8ffb06c059
commit
92e44aea66
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
) {}
|
||||
|
|
Loading…
Reference in a new issue