mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-11-12 23:05:39 +01:00
add image list api
This commit is contained in:
@@ -49,6 +49,7 @@ export class ImageDBService {
|
|||||||
public async findMany(
|
public async findMany(
|
||||||
count: number,
|
count: number,
|
||||||
page: number,
|
page: number,
|
||||||
|
userid: string | false,
|
||||||
): AsyncFailable<EImageBackend[]> {
|
): AsyncFailable<EImageBackend[]> {
|
||||||
if (count < 1 || page < 0) return Fail('Invalid page');
|
if (count < 1 || page < 0) return Fail('Invalid page');
|
||||||
if (count > 100) return Fail('Too many results');
|
if (count > 100) return Fail('Too many results');
|
||||||
@@ -57,6 +58,9 @@ export class ImageDBService {
|
|||||||
const found = await this.imageRepo.find({
|
const found = await this.imageRepo.find({
|
||||||
skip: count * page,
|
skip: count * page,
|
||||||
take: count,
|
take: count,
|
||||||
|
where: {
|
||||||
|
user_id: userid === false ? undefined : userid,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (found === undefined) return Fail('Images not found');
|
if (found === undefined) return Fail('Images not found');
|
||||||
|
|||||||
@@ -33,10 +33,18 @@ export class ImageManagerService {
|
|||||||
private readonly sysPref: SysPreferenceService,
|
private readonly sysPref: SysPreferenceService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async retrieveInfo(id: string): AsyncFailable<EImageBackend> {
|
public async findOne(id: string): AsyncFailable<EImageBackend> {
|
||||||
return await this.imagesService.findOne(id);
|
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(
|
public async upload(
|
||||||
image: Buffer,
|
image: Buffer,
|
||||||
userid: string,
|
userid: string,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Logger,
|
Logger,
|
||||||
Post
|
Post
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { PagedRequest } from 'picsur-shared/dist/dto/api/common.dto';
|
||||||
import {
|
import {
|
||||||
GetSpecialUsersResponse,
|
GetSpecialUsersResponse,
|
||||||
UserCreateRequest,
|
UserCreateRequest,
|
||||||
@@ -14,7 +15,6 @@ import {
|
|||||||
UserDeleteResponse,
|
UserDeleteResponse,
|
||||||
UserInfoRequest,
|
UserInfoRequest,
|
||||||
UserInfoResponse,
|
UserInfoResponse,
|
||||||
UserListRequest,
|
|
||||||
UserListResponse,
|
UserListResponse,
|
||||||
UserUpdateRequest,
|
UserUpdateRequest,
|
||||||
UserUpdateResponse
|
UserUpdateResponse
|
||||||
@@ -38,20 +38,9 @@ export class UserManageController {
|
|||||||
|
|
||||||
constructor(private usersService: UsersService) {}
|
constructor(private usersService: UsersService) {}
|
||||||
|
|
||||||
@Get('list')
|
|
||||||
@Returns(UserListResponse)
|
|
||||||
async listUsers(): Promise<UserListResponse> {
|
|
||||||
return this.listUsersPaged({
|
|
||||||
count: 20,
|
|
||||||
page: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('list')
|
@Post('list')
|
||||||
@Returns(UserListResponse)
|
@Returns(UserListResponse)
|
||||||
async listUsersPaged(
|
async listUsersPaged(@Body() body: PagedRequest): Promise<UserListResponse> {
|
||||||
@Body() body: UserListRequest,
|
|
||||||
): Promise<UserListResponse> {
|
|
||||||
const users = await this.usersService.findMany(body.count, body.page);
|
const users = await this.usersService.findMany(body.count, body.page);
|
||||||
if (HasFailed(users)) {
|
if (HasFailed(users)) {
|
||||||
this.logger.warn(users.getReason());
|
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,
|
Head,
|
||||||
InternalServerErrorException,
|
InternalServerErrorException,
|
||||||
Logger,
|
Logger,
|
||||||
NotFoundException,
|
NotFoundException, Query,
|
||||||
Post,
|
Res
|
||||||
Query,
|
|
||||||
Res,
|
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FastifyReply } from 'fastify';
|
import { FastifyReply } from 'fastify';
|
||||||
import {
|
import {
|
||||||
ImageMetaResponse,
|
ImageMetaResponse,
|
||||||
ImageRequestParams,
|
ImageRequestParams
|
||||||
ImageUploadResponse,
|
|
||||||
} from 'picsur-shared/dist/dto/api/image.dto';
|
} from 'picsur-shared/dist/dto/api/image.dto';
|
||||||
import { HasFailed } from 'picsur-shared/dist/types';
|
import { HasFailed } from 'picsur-shared/dist/types';
|
||||||
import { UsersService } from '../../collections/user-db/user-db.service';
|
import { UsersService } from '../../collections/user-db/user-db.service';
|
||||||
import { ImageFullIdParam } from '../../decorators/image-id/image-full-id.decorator';
|
import { ImageFullIdParam } from '../../decorators/image-id/image-full-id.decorator';
|
||||||
import { ImageIdParam } from '../../decorators/image-id/image-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 { RequiredPermissions } from '../../decorators/permissions.decorator';
|
||||||
import { ReqUserID } from '../../decorators/request-user.decorator';
|
|
||||||
import { Returns } from '../../decorators/returns.decorator';
|
import { Returns } from '../../decorators/returns.decorator';
|
||||||
import { ImageManagerService } from '../../managers/image/image.service';
|
import { ImageManagerService } from '../../managers/image/image.service';
|
||||||
import { ImageFullId } from '../../models/constants/image-full-id.const';
|
import { ImageFullId } from '../../models/constants/image-full-id.const';
|
||||||
import { Permission } from '../../models/constants/permissions.const';
|
import { Permission } from '../../models/constants/permissions.const';
|
||||||
import { ImageUploadDto } from '../../models/dto/image-upload.dto';
|
|
||||||
import { EUserBackend2EUser } from '../../models/transformers/user.transformer';
|
import { EUserBackend2EUser } from '../../models/transformers/user.transformer';
|
||||||
|
|
||||||
// This is the only controller with CORS enabled
|
// This is the only controller with CORS enabled
|
||||||
@@ -95,7 +89,7 @@ export class ImageController {
|
|||||||
@Get('meta/:id')
|
@Get('meta/:id')
|
||||||
@Returns(ImageMetaResponse)
|
@Returns(ImageMetaResponse)
|
||||||
async getImageMeta(@ImageIdParam() id: string): Promise<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)) {
|
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');
|
||||||
@@ -116,23 +110,4 @@ export class ImageController {
|
|||||||
|
|
||||||
return { image, user: EUserBackend2EUser(imageUser), fileMimes };
|
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 { UsersModule } from '../../collections/user-db/user-db.module';
|
||||||
import { DecoratorsModule } from '../../decorators/decorators.module';
|
import { DecoratorsModule } from '../../decorators/decorators.module';
|
||||||
import { ImageManagerModule } from '../../managers/image/image.module';
|
import { ImageManagerModule } from '../../managers/image/image.module';
|
||||||
|
import { ImageManageController } from './image-manage.controller';
|
||||||
import { ImageController } from './image.controller';
|
import { ImageController } from './image.controller';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ImageManagerModule, UsersModule, DecoratorsModule],
|
imports: [ImageManagerModule, UsersModule, DecoratorsModule],
|
||||||
controllers: [ImageController],
|
controllers: [ImageController, ImageManageController],
|
||||||
})
|
})
|
||||||
export class ImageModule {}
|
export class ImageModule {}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export const UIFriendlyPermissions: {
|
|||||||
|
|
||||||
[Permission.Settings]: 'View settings',
|
[Permission.Settings]: 'View settings',
|
||||||
|
|
||||||
|
[Permission.ImageManage]: 'Manage All Images',
|
||||||
[Permission.UserManage]: 'Manage users',
|
[Permission.UserManage]: 'Manage users',
|
||||||
[Permission.RoleManage]: 'Manage roles',
|
[Permission.RoleManage]: 'Manage roles',
|
||||||
[Permission.SysPrefManage]: 'Manage system',
|
[Permission.SysPrefManage]: 'Manage system',
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export class ViewComponent implements OnInit {
|
|||||||
public imageUser: EUser | null = null;
|
public imageUser: EUser | null = null;
|
||||||
|
|
||||||
public timeAgo = rxjs_poll(
|
public timeAgo = rxjs_poll(
|
||||||
1000,
|
10000,
|
||||||
(() => moment(this.image?.created).fromNow()).bind(this)
|
(() => moment(this.image?.created).fromNow()).bind(this)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export class ImageService {
|
|||||||
public async UploadImage(image: File): AsyncFailable<string> {
|
public async UploadImage(image: File): AsyncFailable<string> {
|
||||||
const result = await this.api.postForm(
|
const result = await this.api.postForm(
|
||||||
ImageUploadResponse,
|
ImageUploadResponse,
|
||||||
'/i',
|
'/api/image/upload',
|
||||||
new ImageUploadRequest(image)
|
new ImageUploadRequest(image)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ export class UtilService {
|
|||||||
} else {
|
} else {
|
||||||
this.logger.error(e);
|
this.logger.error(e);
|
||||||
this.showSnackBar(
|
this.showSnackBar(
|
||||||
'An error occured while sharing the image',
|
'Could not share',
|
||||||
SnackBarType.Error
|
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 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';
|
import { EntityIDObjectSchema } from '../id-object.dto';
|
||||||
|
|
||||||
// UserList
|
// UserList
|
||||||
export const UserListRequestSchema = z.object({
|
|
||||||
count: IsPosInt(),
|
|
||||||
page: IsPosInt(),
|
|
||||||
});
|
|
||||||
export class UserListRequest extends createZodDto(UserListRequestSchema) {}
|
|
||||||
|
|
||||||
export const UserListResponseSchema = z.object({
|
export const UserListResponseSchema = z.object({
|
||||||
users: z.array(EUserSchema),
|
users: z.array(EUserSchema),
|
||||||
count: IsPosInt(),
|
count: IsPosInt(),
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// Config
|
// Config
|
||||||
export enum ImageMime {
|
export enum ImageMime {
|
||||||
|
QOI = 'image/x-qoi',
|
||||||
JPEG = 'image/jpeg',
|
JPEG = 'image/jpeg',
|
||||||
PNG = 'image/png',
|
PNG = 'image/png',
|
||||||
WEBP = 'image/webp',
|
WEBP = 'image/webp',
|
||||||
TIFF = 'image/tiff',
|
TIFF = 'image/tiff',
|
||||||
BMP = 'image/bmp',
|
BMP = 'image/bmp',
|
||||||
// ICO = 'image/x-icon',
|
// ICO = 'image/x-icon',
|
||||||
QOI = 'image/x-qoi',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AnimMime {
|
export enum AnimMime {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// -> the frontend and backend can be somewhat out of sync
|
// -> the frontend and backend can be somewhat out of sync
|
||||||
export enum Permission {
|
export enum Permission {
|
||||||
ImageView = 'image-view',
|
ImageView = 'image-view',
|
||||||
ImageUpload = 'image-upload',
|
ImageUpload = 'image-upload', // Ability to upload and manage own images
|
||||||
|
|
||||||
UserLogin = 'user-login', // Ability to log in
|
UserLogin = 'user-login', // Ability to log in
|
||||||
UserKeepLogin = 'user-keep-login', // Ability to view own user details and refresh token
|
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
|
Settings = 'settings', // Ability to view (personal) settings
|
||||||
|
|
||||||
|
ImageManage = 'image-manage', // Ability to manage everyones manage images
|
||||||
UserManage = 'user-manage', // Allow modification of users
|
UserManage = 'user-manage', // Allow modification of users
|
||||||
RoleManage = 'role-manage', // Allow modification of roles
|
RoleManage = 'role-manage', // Allow modification of roles
|
||||||
SysPrefManage = 'syspref-manage',
|
SysPrefManage = 'syspref-manage',
|
||||||
|
|||||||
Reference in New Issue
Block a user