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