mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-11-12 14:55:39 +01:00
add image list api
This commit is contained in:
@@ -49,6 +49,7 @@ export class ImageDBService {
|
||||
public async findMany(
|
||||
count: number,
|
||||
page: number,
|
||||
userid: string | false,
|
||||
): AsyncFailable<EImageBackend[]> {
|
||||
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');
|
||||
|
||||
@@ -33,10 +33,18 @@ export class ImageManagerService {
|
||||
private readonly sysPref: SysPreferenceService,
|
||||
) {}
|
||||
|
||||
public async retrieveInfo(id: string): AsyncFailable<EImageBackend> {
|
||||
public async findOne(id: string): AsyncFailable<EImageBackend> {
|
||||
return await this.imagesService.findOne(id);
|
||||
}
|
||||
|
||||
public async findMany(
|
||||
count: number,
|
||||
page: number,
|
||||
userid: string | false,
|
||||
): AsyncFailable<EImageBackend[]> {
|
||||
return await this.imagesService.findMany(count, page, userid);
|
||||
}
|
||||
|
||||
public async upload(
|
||||
image: Buffer,
|
||||
userid: string,
|
||||
|
||||
@@ -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<UserListResponse> {
|
||||
return this.listUsersPaged({
|
||||
count: 20,
|
||||
page: 0,
|
||||
});
|
||||
}
|
||||
|
||||
@Post('list')
|
||||
@Returns(UserListResponse)
|
||||
async listUsersPaged(
|
||||
@Body() body: UserListRequest,
|
||||
): Promise<UserListResponse> {
|
||||
async listUsersPaged(@Body() body: PagedRequest): Promise<UserListResponse> {
|
||||
const users = await this.usersService.findMany(body.count, body.page);
|
||||
if (HasFailed(users)) {
|
||||
this.logger.warn(users.getReason());
|
||||
|
||||
66
backend/src/routes/image/image-manage.controller.ts
Normal file
66
backend/src/routes/image/image-manage.controller.ts
Normal file
@@ -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<ImageUploadResponse> {
|
||||
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<ImageListResponse> {
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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<ImageMetaResponse> {
|
||||
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<ImageUploadResponse> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export class ImageService {
|
||||
public async UploadImage(image: File): AsyncFailable<string> {
|
||||
const result = await this.api.postForm(
|
||||
ImageUploadResponse,
|
||||
'/i',
|
||||
'/api/image/upload',
|
||||
new ImageUploadRequest(image)
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
9
shared/src/dto/api/common.dto.ts
Normal file
9
shared/src/dto/api/common.dto.ts
Normal file
@@ -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) {}
|
||||
16
shared/src/dto/api/image-manage.dto.ts
Normal file
16
shared/src/dto/api/image-manage.dto.ts
Normal file
@@ -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) {}
|
||||
@@ -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,
|
||||
) {}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user