change images to use ids instead of hashes

This commit is contained in:
rubikscraft 2022-04-16 17:22:50 +02:00
parent bd20c99f84
commit 84aabbd49a
No known key found for this signature in database
GPG key ID: 1463EBE9200A5CD4
10 changed files with 37 additions and 51 deletions

View file

@ -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');
}
}

View file

@ -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(

View file

@ -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;
}

View file

@ -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');
}
}

View file

@ -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');

View file

@ -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 });
}
}

View file

@ -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() {

View file

@ -7,7 +7,7 @@ import { ViewComponent } from './view.component';
const routes: PRoutes = [
{
path: ':hash',
path: ':id',
component: ViewComponent,
canActivate: [PermissionGuard],
data: { permissions: [Permission.ImageView] },

View file

@ -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> {

View file

@ -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(),
});