change images to use ids instead of hashes
This commit is contained in:
parent
bd20c99f84
commit
84aabbd49a
|
@ -1,9 +1,8 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import Crypto from 'crypto';
|
||||
import {
|
||||
AsyncFailable,
|
||||
Fail, HasSuccess
|
||||
Fail
|
||||
} from 'picsur-shared/dist/types';
|
||||
import { Repository } from 'typeorm';
|
||||
import { EImageBackend } from '../../models/entities/image.entity';
|
||||
|
@ -20,14 +19,9 @@ export class ImageDBService {
|
|||
image: Buffer,
|
||||
type: string,
|
||||
): AsyncFailable<EImageBackend> {
|
||||
const hash = this.hash(image);
|
||||
const find = await this.findOne(hash);
|
||||
if (HasSuccess(find)) return find;
|
||||
|
||||
let imageEntity = new EImageBackend();
|
||||
imageEntity.data = image;
|
||||
imageEntity.mime = type;
|
||||
imageEntity.hash = hash;
|
||||
|
||||
try {
|
||||
imageEntity = await this.imageRepository.save(imageEntity);
|
||||
|
@ -39,14 +33,14 @@ export class ImageDBService {
|
|||
}
|
||||
|
||||
public async findOne<B extends true | undefined = undefined>(
|
||||
hash: string,
|
||||
id: string,
|
||||
getPrivate?: B,
|
||||
): AsyncFailable<
|
||||
B extends undefined ? EImageBackend : Required<EImageBackend>
|
||||
> {
|
||||
try {
|
||||
const found = await this.imageRepository.findOne({
|
||||
where: { hash },
|
||||
where: { id },
|
||||
select: getPrivate ? GetCols(this.imageRepository) : undefined,
|
||||
});
|
||||
|
||||
|
@ -79,9 +73,9 @@ export class ImageDBService {
|
|||
}
|
||||
}
|
||||
|
||||
public async delete(hash: string): AsyncFailable<true> {
|
||||
public async delete(id: string): AsyncFailable<true> {
|
||||
try {
|
||||
const result = await this.imageRepository.delete({ hash });
|
||||
const result = await this.imageRepository.delete({ id });
|
||||
if (result.affected === 0) return Fail('Image not found');
|
||||
} catch (e: any) {
|
||||
return Fail(e?.message);
|
||||
|
@ -100,8 +94,4 @@ export class ImageDBService {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private hash(image: Buffer): string {
|
||||
return Crypto.createHash('sha256').update(image).digest('hex');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,16 +19,16 @@ export class ImageManagerService {
|
|||
private readonly processService: ImageProcessorService,
|
||||
) {}
|
||||
|
||||
public async retrieveInfo(hash: string): AsyncFailable<EImageBackend> {
|
||||
return await this.imagesService.findOne(hash);
|
||||
public async retrieveInfo(id: string): AsyncFailable<EImageBackend> {
|
||||
return await this.imagesService.findOne(id);
|
||||
}
|
||||
|
||||
// Image data buffer is not included by default, this also returns that buffer
|
||||
// Dont send to client, keep in backend
|
||||
public async retrieveComplete(
|
||||
hash: string,
|
||||
id: string,
|
||||
): AsyncFailable<Required<EImageBackend>> {
|
||||
return await this.imagesService.findOne(hash, true);
|
||||
return await this.imagesService.findOne(id, true);
|
||||
}
|
||||
|
||||
public async upload(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { EImageSchema } from 'picsur-shared/dist/entities/image.entity';
|
||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { z } from 'zod';
|
||||
|
||||
const OverriddenEImageSchema = EImageSchema.omit({ data: true }).merge(
|
||||
|
@ -12,11 +12,7 @@ type OverriddenEImage = z.infer<typeof OverriddenEImageSchema>;
|
|||
@Entity()
|
||||
export class EImageBackend implements OverriddenEImage {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id?: string;
|
||||
|
||||
@Index()
|
||||
@Column({ unique: true, nullable: false })
|
||||
hash: string;
|
||||
id: string;
|
||||
|
||||
@Column({ nullable: false })
|
||||
mime: string;
|
||||
|
@ -24,4 +20,7 @@ export class EImageBackend implements OverriddenEImage {
|
|||
// Binary data
|
||||
@Column({ type: 'bytea', nullable: false, select: false })
|
||||
data?: Buffer;
|
||||
|
||||
@Column({ type: 'bytea', nullable: true, select: false })
|
||||
originaldata?: Buffer;
|
||||
}
|
||||
|
|
|
@ -4,13 +4,12 @@ import {
|
|||
Injectable,
|
||||
PipeTransform
|
||||
} from '@nestjs/common';
|
||||
import { SHA256Regex } from 'picsur-shared/dist/util/common-regex';
|
||||
import { UUIDRegex } from 'picsur-shared/dist/util/common-regex';
|
||||
|
||||
@Injectable()
|
||||
export class ImageIdValidator implements PipeTransform<string, string> {
|
||||
transform(value: string, metadata: ArgumentMetadata): string {
|
||||
// Check regex for sha256
|
||||
if (SHA256Regex.test(value)) return value;
|
||||
if (UUIDRegex.test(value)) return value;
|
||||
throw new BadRequestException('Invalid image id');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,14 +30,14 @@ export class ImageController {
|
|||
|
||||
constructor(private readonly imagesService: ImageManagerService) {}
|
||||
|
||||
@Get(':hash')
|
||||
@Get(':id')
|
||||
async getImage(
|
||||
// Usually passthrough is for manually sending the response,
|
||||
// But we need it here to set the mime type
|
||||
@Res({ passthrough: true }) res: FastifyReply,
|
||||
@Param('hash', ImageIdValidator) hash: string,
|
||||
@Param('id', ImageIdValidator) id: string,
|
||||
): Promise<Buffer> {
|
||||
const image = await this.imagesService.retrieveComplete(hash);
|
||||
const image = await this.imagesService.retrieveComplete(id);
|
||||
if (HasFailed(image)) {
|
||||
this.logger.warn(image.getReason());
|
||||
throw new NotFoundException('Could not find image');
|
||||
|
@ -47,12 +47,12 @@ export class ImageController {
|
|||
return image.data;
|
||||
}
|
||||
|
||||
@Head(':hash')
|
||||
@Head(':id')
|
||||
async headImage(
|
||||
@Res({ passthrough: true }) res: FastifyReply,
|
||||
@Param('hash', ImageIdValidator) hash: string,
|
||||
@Param('id', ImageIdValidator) id: string,
|
||||
) {
|
||||
const image = await this.imagesService.retrieveInfo(hash);
|
||||
const image = await this.imagesService.retrieveInfo(id);
|
||||
if (HasFailed(image)) {
|
||||
this.logger.warn(image.getReason());
|
||||
throw new NotFoundException('Could not find image');
|
||||
|
@ -61,12 +61,12 @@ export class ImageController {
|
|||
res.type(image.mime);
|
||||
}
|
||||
|
||||
@Get('meta/:hash')
|
||||
@Get('meta/:id')
|
||||
@Returns(ImageMetaResponse)
|
||||
async getImageMeta(
|
||||
@Param('hash', ImageIdValidator) hash: string,
|
||||
@Param('id', ImageIdValidator) id: string,
|
||||
): Promise<ImageMetaResponse> {
|
||||
const image = await this.imagesService.retrieveInfo(hash);
|
||||
const image = await this.imagesService.retrieveInfo(id);
|
||||
if (HasFailed(image)) {
|
||||
this.logger.warn(image.getReason());
|
||||
throw new NotFoundException('Could not find image');
|
||||
|
|
|
@ -23,11 +23,11 @@ export class ProcessingComponent implements OnInit {
|
|||
|
||||
history.replaceState(null, '');
|
||||
|
||||
const hash = await this.imageService.UploadImage(state.imageFile);
|
||||
if (HasFailed(hash)) {
|
||||
return this.utilService.quitError(hash.getReason());
|
||||
const id = await this.imageService.UploadImage(state.imageFile);
|
||||
if (HasFailed(id)) {
|
||||
return this.utilService.quitError(id.getReason());
|
||||
}
|
||||
|
||||
this.router.navigate([`/view/`, hash], { replaceUrl: true });
|
||||
this.router.navigate([`/view/`, id], { replaceUrl: true });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ImageLinks } from 'picsur-shared/dist/dto/imagelinks.dto';
|
||||
import { HasFailed } from 'picsur-shared/dist/types';
|
||||
import { SHA256Regex } from 'picsur-shared/dist/util/common-regex';
|
||||
import { UUIDRegex } from 'picsur-shared/dist/util/common-regex';
|
||||
import { ImageService } from 'src/app/services/api/image.service';
|
||||
import { UtilService } from 'src/app/util/util.service';
|
||||
|
||||
|
@ -22,17 +22,17 @@ export class ViewComponent implements OnInit {
|
|||
|
||||
async ngOnInit() {
|
||||
const params = this.route.snapshot.paramMap;
|
||||
const hash = params.get('hash') ?? '';
|
||||
if (!SHA256Regex.test(hash)) {
|
||||
const id = params.get('id') ?? '';
|
||||
if (!UUIDRegex.test(id)) {
|
||||
return this.utilService.quitError('Invalid image link');
|
||||
}
|
||||
|
||||
const metadata = await this.imageService.GetImageMeta(hash);
|
||||
const metadata = await this.imageService.GetImageMeta(id);
|
||||
if (HasFailed(metadata)) {
|
||||
return this.utilService.quitError(metadata.getReason());
|
||||
}
|
||||
|
||||
this.imageLinks = this.imageService.CreateImageLinksFromID(hash);
|
||||
this.imageLinks = this.imageService.CreateImageLinksFromID(id);
|
||||
}
|
||||
|
||||
downloadImage() {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { ViewComponent } from './view.component';
|
|||
|
||||
const routes: PRoutes = [
|
||||
{
|
||||
path: ':hash',
|
||||
path: ':id',
|
||||
component: ViewComponent,
|
||||
canActivate: [PermissionGuard],
|
||||
data: { permissions: [Permission.ImageView] },
|
||||
|
|
|
@ -19,7 +19,7 @@ export class ImageService {
|
|||
new ImageUploadRequest(image)
|
||||
);
|
||||
|
||||
return Open(result, 'hash');
|
||||
return Open(result, 'id');
|
||||
}
|
||||
|
||||
public async GetImageMeta(image: string): AsyncFailable<EImage> {
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { z } from 'zod';
|
||||
import { SHA256Regex } from '../util/common-regex';
|
||||
import { IsEntityID } from '../validators/entity-id.validator';
|
||||
|
||||
export const EImageSchema = z.object({
|
||||
id: IsEntityID().optional(),
|
||||
hash: z.string().regex(SHA256Regex),
|
||||
id: IsEntityID(),
|
||||
data: z.undefined(),
|
||||
mime: z.string(),
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue