From dce72d0680fde418dfb033d29c5f465d42e61933 Mon Sep 17 00:00:00 2001 From: rubikscraft Date: Tue, 3 May 2022 21:37:20 +0200 Subject: [PATCH] add image list api --- .../collections/image-db/image-db.service.ts | 4 ++ backend/src/managers/image/image.service.ts | 10 ++- .../routes/api/user/user-manage.controller.ts | 15 +---- .../routes/image/image-manage.controller.ts | 66 +++++++++++++++++++ backend/src/routes/image/image.controller.ts | 33 ++-------- backend/src/routes/image/image.module.ts | 3 +- frontend/src/app/i18n/permissions.i18n.ts | 1 + .../src/app/routes/view/view.component.ts | 2 +- .../src/app/services/api/image.service.ts | 2 +- .../src/app/util/util-module/util.service.ts | 2 +- shared/src/dto/api/common.dto.ts | 9 +++ shared/src/dto/api/image-manage.dto.ts | 16 +++++ shared/src/dto/api/image.dto.ts | 5 -- shared/src/dto/api/user-manage.dto.ts | 6 -- shared/src/dto/mimes.dto.ts | 2 +- shared/src/dto/permissions.dto.ts | 3 +- 16 files changed, 119 insertions(+), 60 deletions(-) create mode 100644 backend/src/routes/image/image-manage.controller.ts create mode 100644 shared/src/dto/api/common.dto.ts create mode 100644 shared/src/dto/api/image-manage.dto.ts diff --git a/backend/src/collections/image-db/image-db.service.ts b/backend/src/collections/image-db/image-db.service.ts index f451974..864da50 100644 --- a/backend/src/collections/image-db/image-db.service.ts +++ b/backend/src/collections/image-db/image-db.service.ts @@ -49,6 +49,7 @@ export class ImageDBService { public async findMany( count: number, page: number, + userid: string | false, ): AsyncFailable { if (count < 1 || page < 0) return Fail('Invalid page'); if (count > 100) return Fail('Too many results'); @@ -57,6 +58,9 @@ export class ImageDBService { const found = await this.imageRepo.find({ skip: count * page, take: count, + where: { + user_id: userid === false ? undefined : userid, + }, }); if (found === undefined) return Fail('Images not found'); diff --git a/backend/src/managers/image/image.service.ts b/backend/src/managers/image/image.service.ts index 9062fec..2802609 100644 --- a/backend/src/managers/image/image.service.ts +++ b/backend/src/managers/image/image.service.ts @@ -33,10 +33,18 @@ export class ImageManagerService { private readonly sysPref: SysPreferenceService, ) {} - public async retrieveInfo(id: string): AsyncFailable { + public async findOne(id: string): AsyncFailable { return await this.imagesService.findOne(id); } + public async findMany( + count: number, + page: number, + userid: string | false, + ): AsyncFailable { + return await this.imagesService.findMany(count, page, userid); + } + public async upload( image: Buffer, userid: string, diff --git a/backend/src/routes/api/user/user-manage.controller.ts b/backend/src/routes/api/user/user-manage.controller.ts index 34363a4..91a9773 100644 --- a/backend/src/routes/api/user/user-manage.controller.ts +++ b/backend/src/routes/api/user/user-manage.controller.ts @@ -6,6 +6,7 @@ import { Logger, Post } from '@nestjs/common'; +import { PagedRequest } from 'picsur-shared/dist/dto/api/common.dto'; import { GetSpecialUsersResponse, UserCreateRequest, @@ -14,7 +15,6 @@ import { UserDeleteResponse, UserInfoRequest, UserInfoResponse, - UserListRequest, UserListResponse, UserUpdateRequest, UserUpdateResponse @@ -38,20 +38,9 @@ export class UserManageController { constructor(private usersService: UsersService) {} - @Get('list') - @Returns(UserListResponse) - async listUsers(): Promise { - return this.listUsersPaged({ - count: 20, - page: 0, - }); - } - @Post('list') @Returns(UserListResponse) - async listUsersPaged( - @Body() body: UserListRequest, - ): Promise { + async listUsersPaged(@Body() body: PagedRequest): Promise { const users = await this.usersService.findMany(body.count, body.page); if (HasFailed(users)) { this.logger.warn(users.getReason()); diff --git a/backend/src/routes/image/image-manage.controller.ts b/backend/src/routes/image/image-manage.controller.ts new file mode 100644 index 0000000..36707df --- /dev/null +++ b/backend/src/routes/image/image-manage.controller.ts @@ -0,0 +1,66 @@ +import { + Body, + Controller, + InternalServerErrorException, + Logger, + Post +} from '@nestjs/common'; +import { PagedRequest } from 'picsur-shared/dist/dto/api/common.dto'; +import { ImageListResponse, ImageUploadResponse } from 'picsur-shared/dist/dto/api/image-manage.dto'; +import { Permission } from 'picsur-shared/dist/dto/permissions.dto'; +import { HasFailed } from 'picsur-shared/dist/types'; +import { MultiPart } from '../../decorators/multipart/multipart.decorator'; +import { RequiredPermissions } from '../../decorators/permissions.decorator'; +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 { + private readonly logger = new Logger('ImageManageController'); + + constructor(private readonly imagesService: ImageManagerService) {} + + @Post('upload') + @Returns(ImageUploadResponse) + async uploadImage( + @MultiPart() multipart: ImageUploadDto, + @ReqUserID() userid: string, + ): Promise { + const image = await this.imagesService.upload( + multipart.image.buffer, + userid, + ); + if (HasFailed(image)) { + this.logger.warn(image.getReason(), image.getStack()); + throw new InternalServerErrorException('Could not upload image'); + } + + return image; + } + + @Post('my/list') + @Returns(ImageListResponse) + async listUsersPaged( + @Body() body: PagedRequest, + @ReqUserID() userid: string, + ): Promise { + const images = await this.imagesService.findMany( + body.count, + body.page, + userid, + ); + if (HasFailed(images)) { + this.logger.warn(images.getReason()); + throw new InternalServerErrorException('Could not list images'); + } + + return { + images, + count: images.length, + page: body.page, + }; + } +} diff --git a/backend/src/routes/image/image.controller.ts b/backend/src/routes/image/image.controller.ts index 80eec0d..6bc18bb 100644 --- a/backend/src/routes/image/image.controller.ts +++ b/backend/src/routes/image/image.controller.ts @@ -4,29 +4,23 @@ import { Head, InternalServerErrorException, Logger, - NotFoundException, - Post, - Query, - Res, + NotFoundException, Query, + Res } from '@nestjs/common'; import { FastifyReply } from 'fastify'; import { ImageMetaResponse, - ImageRequestParams, - ImageUploadResponse, + ImageRequestParams } from 'picsur-shared/dist/dto/api/image.dto'; import { HasFailed } from 'picsur-shared/dist/types'; import { UsersService } from '../../collections/user-db/user-db.service'; import { ImageFullIdParam } from '../../decorators/image-id/image-full-id.decorator'; import { ImageIdParam } from '../../decorators/image-id/image-id.decorator'; -import { MultiPart } from '../../decorators/multipart/multipart.decorator'; import { RequiredPermissions } from '../../decorators/permissions.decorator'; -import { ReqUserID } from '../../decorators/request-user.decorator'; import { Returns } from '../../decorators/returns.decorator'; import { ImageManagerService } from '../../managers/image/image.service'; import { ImageFullId } from '../../models/constants/image-full-id.const'; import { Permission } from '../../models/constants/permissions.const'; -import { ImageUploadDto } from '../../models/dto/image-upload.dto'; import { EUserBackend2EUser } from '../../models/transformers/user.transformer'; // This is the only controller with CORS enabled @@ -95,7 +89,7 @@ export class ImageController { @Get('meta/:id') @Returns(ImageMetaResponse) async getImageMeta(@ImageIdParam() id: string): Promise { - const image = await this.imagesService.retrieveInfo(id); + const image = await this.imagesService.findOne(id); if (HasFailed(image)) { this.logger.warn(image.getReason()); throw new NotFoundException('Could not find image'); @@ -116,23 +110,4 @@ export class ImageController { return { image, user: EUserBackend2EUser(imageUser), fileMimes }; } - - @Post() - @Returns(ImageUploadResponse) - @RequiredPermissions(Permission.ImageUpload) - async uploadImage( - @MultiPart() multipart: ImageUploadDto, - @ReqUserID() userid: string, - ): Promise { - const image = await this.imagesService.upload( - multipart.image.buffer, - userid, - ); - if (HasFailed(image)) { - this.logger.warn(image.getReason(), image.getStack()); - throw new InternalServerErrorException('Could not upload image'); - } - - return image; - } } diff --git a/backend/src/routes/image/image.module.ts b/backend/src/routes/image/image.module.ts index 2e91ddb..3648159 100644 --- a/backend/src/routes/image/image.module.ts +++ b/backend/src/routes/image/image.module.ts @@ -2,10 +2,11 @@ import { Module } from '@nestjs/common'; import { UsersModule } from '../../collections/user-db/user-db.module'; import { DecoratorsModule } from '../../decorators/decorators.module'; import { ImageManagerModule } from '../../managers/image/image.module'; +import { ImageManageController } from './image-manage.controller'; import { ImageController } from './image.controller'; @Module({ imports: [ImageManagerModule, UsersModule, DecoratorsModule], - controllers: [ImageController], + controllers: [ImageController, ImageManageController], }) export class ImageModule {} diff --git a/frontend/src/app/i18n/permissions.i18n.ts b/frontend/src/app/i18n/permissions.i18n.ts index 506eb3f..fa31384 100644 --- a/frontend/src/app/i18n/permissions.i18n.ts +++ b/frontend/src/app/i18n/permissions.i18n.ts @@ -12,6 +12,7 @@ export const UIFriendlyPermissions: { [Permission.Settings]: 'View settings', + [Permission.ImageManage]: 'Manage All Images', [Permission.UserManage]: 'Manage users', [Permission.RoleManage]: 'Manage roles', [Permission.SysPrefManage]: 'Manage system', diff --git a/frontend/src/app/routes/view/view.component.ts b/frontend/src/app/routes/view/view.component.ts index 803719b..3e9f4c7 100644 --- a/frontend/src/app/routes/view/view.component.ts +++ b/frontend/src/app/routes/view/view.component.ts @@ -53,7 +53,7 @@ export class ViewComponent implements OnInit { public imageUser: EUser | null = null; public timeAgo = rxjs_poll( - 1000, + 10000, (() => moment(this.image?.created).fromNow()).bind(this) ); diff --git a/frontend/src/app/services/api/image.service.ts b/frontend/src/app/services/api/image.service.ts index 98efa80..a13fe03 100644 --- a/frontend/src/app/services/api/image.service.ts +++ b/frontend/src/app/services/api/image.service.ts @@ -18,7 +18,7 @@ export class ImageService { public async UploadImage(image: File): AsyncFailable { const result = await this.api.postForm( ImageUploadResponse, - '/i', + '/api/image/upload', new ImageUploadRequest(image) ); diff --git a/frontend/src/app/util/util-module/util.service.ts b/frontend/src/app/util/util-module/util.service.ts index 1e0f7ed..461bd8b 100644 --- a/frontend/src/app/util/util-module/util.service.ts +++ b/frontend/src/app/util/util-module/util.service.ts @@ -200,7 +200,7 @@ export class UtilService { } else { this.logger.error(e); this.showSnackBar( - 'An error occured while sharing the image', + 'Could not share', SnackBarType.Error ); } diff --git a/shared/src/dto/api/common.dto.ts b/shared/src/dto/api/common.dto.ts new file mode 100644 index 0000000..3e20152 --- /dev/null +++ b/shared/src/dto/api/common.dto.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; +import { createZodDto } from '../../util/create-zod-dto'; +import { IsPosInt } from '../../validators/positive-int.validator'; + +export const PagedRequestSchema = z.object({ + count: IsPosInt(), + page: IsPosInt(), +}); +export class PagedRequest extends createZodDto(PagedRequestSchema) {} diff --git a/shared/src/dto/api/image-manage.dto.ts b/shared/src/dto/api/image-manage.dto.ts new file mode 100644 index 0000000..3721efb --- /dev/null +++ b/shared/src/dto/api/image-manage.dto.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; +import { EImageSchema } from '../../entities/image.entity'; +import { createZodDto } from '../../util/create-zod-dto'; +import { IsPosInt } from '../../validators/positive-int.validator'; + +export const ImageUploadResponseSchema = EImageSchema; +export class ImageUploadResponse extends createZodDto( + ImageUploadResponseSchema, +) {} + +export const ImageListResponseSchema = z.object({ + images: z.array(EImageSchema), + count: IsPosInt(), + page: IsPosInt(), +}); +export class ImageListResponse extends createZodDto(ImageListResponseSchema) {} diff --git a/shared/src/dto/api/image.dto.ts b/shared/src/dto/api/image.dto.ts index 556b787..d75b58f 100644 --- a/shared/src/dto/api/image.dto.ts +++ b/shared/src/dto/api/image.dto.ts @@ -40,8 +40,3 @@ export const ImageMetaResponseSchema = z.object({ }), }); export class ImageMetaResponse extends createZodDto(ImageMetaResponseSchema) {} - -export const ImageUploadResponseSchema = EImageSchema; -export class ImageUploadResponse extends createZodDto( - ImageUploadResponseSchema, -) {} diff --git a/shared/src/dto/api/user-manage.dto.ts b/shared/src/dto/api/user-manage.dto.ts index c8e66c8..55f32ec 100644 --- a/shared/src/dto/api/user-manage.dto.ts +++ b/shared/src/dto/api/user-manage.dto.ts @@ -8,12 +8,6 @@ import { IsStringList } from '../../validators/string-list.validator'; import { EntityIDObjectSchema } from '../id-object.dto'; // UserList -export const UserListRequestSchema = z.object({ - count: IsPosInt(), - page: IsPosInt(), -}); -export class UserListRequest extends createZodDto(UserListRequestSchema) {} - export const UserListResponseSchema = z.object({ users: z.array(EUserSchema), count: IsPosInt(), diff --git a/shared/src/dto/mimes.dto.ts b/shared/src/dto/mimes.dto.ts index e125ad6..3627746 100644 --- a/shared/src/dto/mimes.dto.ts +++ b/shared/src/dto/mimes.dto.ts @@ -1,12 +1,12 @@ // Config export enum ImageMime { + QOI = 'image/x-qoi', JPEG = 'image/jpeg', PNG = 'image/png', WEBP = 'image/webp', TIFF = 'image/tiff', BMP = 'image/bmp', // ICO = 'image/x-icon', - QOI = 'image/x-qoi', } export enum AnimMime { diff --git a/shared/src/dto/permissions.dto.ts b/shared/src/dto/permissions.dto.ts index e008239..4515127 100644 --- a/shared/src/dto/permissions.dto.ts +++ b/shared/src/dto/permissions.dto.ts @@ -4,7 +4,7 @@ // -> the frontend and backend can be somewhat out of sync export enum Permission { ImageView = 'image-view', - ImageUpload = 'image-upload', + ImageUpload = 'image-upload', // Ability to upload and manage own images UserLogin = 'user-login', // Ability to log in UserKeepLogin = 'user-keep-login', // Ability to view own user details and refresh token @@ -12,6 +12,7 @@ export enum Permission { Settings = 'settings', // Ability to view (personal) settings + ImageManage = 'image-manage', // Ability to manage everyones manage images UserManage = 'user-manage', // Allow modification of users RoleManage = 'role-manage', // Allow modification of roles SysPrefManage = 'syspref-manage',