Feature/add tag management in admin control panel (#2389)
* Add tag management * Update locales * Update changelog
This commit is contained in:
@@ -39,6 +39,7 @@ import { RedisCacheModule } from './redis-cache/redis-cache.module';
|
||||
import { SitemapModule } from './sitemap/sitemap.module';
|
||||
import { SubscriptionModule } from './subscription/subscription.module';
|
||||
import { SymbolModule } from './symbol/symbol.module';
|
||||
import { TagModule } from './tag/tag.module';
|
||||
import { UserModule } from './user/user.module';
|
||||
|
||||
@Module({
|
||||
@@ -101,6 +102,7 @@ import { UserModule } from './user/user.module';
|
||||
SitemapModule,
|
||||
SubscriptionModule,
|
||||
SymbolModule,
|
||||
TagModule,
|
||||
TwitterBotModule,
|
||||
UserModule
|
||||
],
|
||||
|
6
apps/api/src/app/tag/create-tag.dto.ts
Normal file
6
apps/api/src/app/tag/create-tag.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { IsString } from 'class-validator';
|
||||
|
||||
export class CreateTagDto {
|
||||
@IsString()
|
||||
name: string;
|
||||
}
|
104
apps/api/src/app/tag/tag.controller.ts
Normal file
104
apps/api/src/app/tag/tag.controller.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
import type { RequestWithUser } from '@ghostfolio/common/types';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpException,
|
||||
Inject,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
UseGuards
|
||||
} from '@nestjs/common';
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Tag } from '@prisma/client';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
|
||||
import { CreateTagDto } from './create-tag.dto';
|
||||
import { TagService } from './tag.service';
|
||||
import { UpdateTagDto } from './update-tag.dto';
|
||||
|
||||
@Controller('tag')
|
||||
export class TagController {
|
||||
public constructor(
|
||||
@Inject(REQUEST) private readonly request: RequestWithUser,
|
||||
private readonly tagService: TagService
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
public async getTags() {
|
||||
return this.tagService.getTagsWithActivityCount();
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
public async createTag(@Body() data: CreateTagDto): Promise<Tag> {
|
||||
if (!hasPermission(this.request.user.permissions, permissions.createTag)) {
|
||||
throw new HttpException(
|
||||
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||
StatusCodes.FORBIDDEN
|
||||
);
|
||||
}
|
||||
|
||||
return this.tagService.createTag(data);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
public async updateTag(@Param('id') id: string, @Body() data: UpdateTagDto) {
|
||||
if (!hasPermission(this.request.user.permissions, permissions.updateTag)) {
|
||||
throw new HttpException(
|
||||
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||
StatusCodes.FORBIDDEN
|
||||
);
|
||||
}
|
||||
|
||||
const originalTag = await this.tagService.getTag({
|
||||
id
|
||||
});
|
||||
|
||||
if (!originalTag) {
|
||||
throw new HttpException(
|
||||
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||
StatusCodes.FORBIDDEN
|
||||
);
|
||||
}
|
||||
|
||||
return this.tagService.updateTag({
|
||||
data: {
|
||||
...data
|
||||
},
|
||||
where: {
|
||||
id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
public async deleteTag(@Param('id') id: string) {
|
||||
if (!hasPermission(this.request.user.permissions, permissions.deleteTag)) {
|
||||
throw new HttpException(
|
||||
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||
StatusCodes.FORBIDDEN
|
||||
);
|
||||
}
|
||||
|
||||
const originalTag = await this.tagService.getTag({
|
||||
id
|
||||
});
|
||||
|
||||
if (!originalTag) {
|
||||
throw new HttpException(
|
||||
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||
StatusCodes.FORBIDDEN
|
||||
);
|
||||
}
|
||||
|
||||
return this.tagService.deleteTag({ id });
|
||||
}
|
||||
}
|
13
apps/api/src/app/tag/tag.module.ts
Normal file
13
apps/api/src/app/tag/tag.module.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { TagController } from './tag.controller';
|
||||
import { TagService } from './tag.service';
|
||||
|
||||
@Module({
|
||||
controllers: [TagController],
|
||||
exports: [TagService],
|
||||
imports: [PrismaModule],
|
||||
providers: [TagService]
|
||||
})
|
||||
export class TagModule {}
|
79
apps/api/src/app/tag/tag.service.ts
Normal file
79
apps/api/src/app/tag/tag.service.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma, Tag } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class TagService {
|
||||
public constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
public async createTag(data: Prisma.TagCreateInput) {
|
||||
return this.prismaService.tag.create({
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
public async deleteTag(where: Prisma.TagWhereUniqueInput): Promise<Tag> {
|
||||
return this.prismaService.tag.delete({ where });
|
||||
}
|
||||
|
||||
public async getTag(
|
||||
tagWhereUniqueInput: Prisma.TagWhereUniqueInput
|
||||
): Promise<Tag> {
|
||||
return this.prismaService.tag.findUnique({
|
||||
where: tagWhereUniqueInput
|
||||
});
|
||||
}
|
||||
|
||||
public async getTags({
|
||||
cursor,
|
||||
orderBy,
|
||||
skip,
|
||||
take,
|
||||
where
|
||||
}: {
|
||||
cursor?: Prisma.TagWhereUniqueInput;
|
||||
orderBy?: Prisma.TagOrderByWithRelationInput;
|
||||
skip?: number;
|
||||
take?: number;
|
||||
where?: Prisma.TagWhereInput;
|
||||
} = {}) {
|
||||
return this.prismaService.tag.findMany({
|
||||
cursor,
|
||||
orderBy,
|
||||
skip,
|
||||
take,
|
||||
where
|
||||
});
|
||||
}
|
||||
|
||||
public async getTagsWithActivityCount() {
|
||||
const tagsWithOrderCount = await this.prismaService.tag.findMany({
|
||||
include: {
|
||||
_count: {
|
||||
select: { orders: true }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return tagsWithOrderCount.map(({ _count, id, name }) => {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
activityCount: _count.orders
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public async updateTag({
|
||||
data,
|
||||
where
|
||||
}: {
|
||||
data: Prisma.TagUpdateInput;
|
||||
where: Prisma.TagWhereUniqueInput;
|
||||
}): Promise<Tag> {
|
||||
return this.prismaService.tag.update({
|
||||
data,
|
||||
where
|
||||
});
|
||||
}
|
||||
}
|
9
apps/api/src/app/tag/update-tag.dto.ts
Normal file
9
apps/api/src/app/tag/update-tag.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { IsString } from 'class-validator';
|
||||
|
||||
export class UpdateTagDto {
|
||||
@IsString()
|
||||
id: string;
|
||||
|
||||
@IsString()
|
||||
name: string;
|
||||
}
|
Reference in New Issue
Block a user