chore: add blog blog comment and blog category crud
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
import { SetMetadata } from "@nestjs/common";
|
||||||
|
|
||||||
|
export const SKIP_AUTH_KEY = "skipAuth";
|
||||||
|
|
||||||
|
export const SkipAuth = () => SetMetadata(SKIP_AUTH_KEY, true);
|
||||||
@@ -616,3 +616,52 @@ export const enum UserQuickAccessMessage {
|
|||||||
export const enum SmsMessage {
|
export const enum SmsMessage {
|
||||||
SEND_SMS_ERROR = "خطا در ارسال پیامک",
|
SEND_SMS_ERROR = "خطا در ارسال پیامک",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const enum BlogMessage {
|
||||||
|
TITLE_REQUIRED = "عنوان الزامی است",
|
||||||
|
TITLE_STRING = "عنوان باید یک رشته باشد",
|
||||||
|
DESCRIPTION_REQUIRED = "توضیحات الزامی است",
|
||||||
|
DESCRIPTION_STRING = "توضیحات باید یک رشته باشد",
|
||||||
|
TITLE_MAX_LENGTH = "حداکثر طول عنوان باید ۱۰۰ کاراکتر باشد",
|
||||||
|
DESCRIPTION_MAX_LENGTH = "حداکثر طول توضیحات باید ۲۵۵ کاراکتر باشد",
|
||||||
|
ICON_URL_REQUIRED = "آدرس آیکون الزامی است",
|
||||||
|
ICON_URL_INVALID = "آدرس آیکون نامعتبر است",
|
||||||
|
ICON_URL_MAX_LENGTH = "حداکثر طول آدرس آیکون باید ۲۵۵ کاراکتر باشد",
|
||||||
|
IS_ACTIVE_REQUIRED = "وضعیت فعال بودن الزامی است",
|
||||||
|
IS_ACTIVE_SHOULD_BE_A_BOOLEAN = "وضعیت فعال بودن باید یک بولین باشد",
|
||||||
|
CATEGORY_ALREADY_EXISTS = "دسته بندی با این عنوان قبلا ثبت شده است",
|
||||||
|
CATEGORY_NOT_FOUND = "دسته بندی مورد نظر یافت نشد",
|
||||||
|
BLOG_ALREADY_EXISTS = "بلاگ با این عنوان قبلا ثبت شده است",
|
||||||
|
BLOG_NOT_FOUND = "بلاگ مورد نظر یافت نشد",
|
||||||
|
PREVIEW_CONTENT_REQUIRED = "محتوای پیشنمایش الزامی است",
|
||||||
|
PREVIEW_CONTENT_STRING = "محتوای پیشنمایش باید یک رشته باشد",
|
||||||
|
PREVIEW_CONTENT_MAX_LENGTH = "حداکثر طول محتوای پیشنمایش باید ۱۵۰ کاراکتر باشد",
|
||||||
|
CONTENT_REQUIRED = "محتوای بلاگ الزامی است",
|
||||||
|
CONTENT_STRING = "محتوای بلاگ باید یک رشته باشد",
|
||||||
|
CONTENT_MAX_LENGTH = "حداکثر طول محتوای بلاگ باید ۶۰۰ کاراکتر باشد",
|
||||||
|
IMAGE_URL_REQUIRED = "آدرس تصویر الزامی است",
|
||||||
|
IMAGE_URL_STRING = "آدرس تصویر باید یک رشته باشد",
|
||||||
|
IMAGE_URL_MAX_LENGTH = "حداکثر طول آدرس تصویر باید ۱۵۰ کاراکتر باشد",
|
||||||
|
META_TITLE_REQUIRED = "عنوان متا الزامی است",
|
||||||
|
META_TITLE_STRING = "عنوان متا باید یک رشته باشد",
|
||||||
|
META_TITLE_MAX_LENGTH = "حداکثر طول عنوان متا باید ۶۰ کاراکتر باشد",
|
||||||
|
META_DESCRIPTION_REQUIRED = "توضیحات متا الزامی است",
|
||||||
|
META_DESCRIPTION_STRING = "توضیحات متا باید یک رشته باشد",
|
||||||
|
META_DESCRIPTION_MAX_LENGTH = "حداکثر طول توضیحات متا باید ۱۶۰ کاراکتر باشد",
|
||||||
|
TAGS_REQUIRED = "تگها الزامی است",
|
||||||
|
TAGS_ARRAY = "تگها باید یک آرایه باشد",
|
||||||
|
TAGS_STRING = "تگها باید یک رشته باشد",
|
||||||
|
TAGS_MIN_SIZE = "حداقل تعداد تگها باید ۱ باشد",
|
||||||
|
CATEGORY_ID_REQUIRED = "شناسه دسته بندی الزامی است",
|
||||||
|
CATEGORY_ID_UUID = "شناسه دسته بندی باید یک UUID معتبر باشد",
|
||||||
|
COMMENT_CONTENT_REQUIRED = "محتوای دیدگاه الزامی است",
|
||||||
|
COMMENT_CONTENT_STRING = "محتوای دیدگاه باید یک رشته باشد",
|
||||||
|
COMMENT_STATUS_REQUIRED = "وضعیت دیدگاه الزامی است",
|
||||||
|
COMMENT_STATUS_INVALID = "وضعیت دیدگاه نامعتبر است",
|
||||||
|
COMMENT_NOT_FOUND = "دیدگاه مورد نظر یافت نشد",
|
||||||
|
SLUG_NOT_EMPTY = "اسلاگ بلاگ نمیتواند خالی باشد",
|
||||||
|
SLUG_STRING = "اسلاگ بلاگ باید یک رشته باشد",
|
||||||
|
INVALID_DATE = "تاریخ نامعتبر است",
|
||||||
|
IS_ACTIVE_SHOULD_BE_1_0 = "وضعیت فعال بودن باید یکی از ۱ یا ۰ باشد",
|
||||||
|
SEARCH_QUERY_STRING = "رشته جستجو باید یک رشته باشد",
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
import { ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
|
import { ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
|
||||||
|
import { Reflector } from "@nestjs/core";
|
||||||
import { AuthGuard } from "@nestjs/passport";
|
import { AuthGuard } from "@nestjs/passport";
|
||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
import { JWT_STRATEGY_NAME } from "../../../common/constants";
|
import { JWT_STRATEGY_NAME } from "../../../common/constants";
|
||||||
|
import { SKIP_AUTH_KEY } from "../../../common/decorators/skip-auth.decorator";
|
||||||
import { AuthMessage } from "../../../common/enums/message.enum";
|
import { AuthMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtAuthGuard extends AuthGuard(JWT_STRATEGY_NAME) {
|
export class JwtAuthGuard extends AuthGuard(JWT_STRATEGY_NAME) {
|
||||||
|
constructor(private reflector: Reflector) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
|
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
|
||||||
|
const skipAuth = this.reflector.getAllAndOverride<boolean>(SKIP_AUTH_KEY, [context.getHandler(), context.getClass()]);
|
||||||
|
|
||||||
|
if (skipAuth) return true;
|
||||||
|
|
||||||
return super.canActivate(context);
|
return super.canActivate(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { ArrayMinSize, IsArray, IsBoolean, IsNotEmpty, IsString, IsUUID, MaxLength } from "class-validator";
|
||||||
|
|
||||||
|
import { BlogMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class CreateBlogDto {
|
||||||
|
@IsNotEmpty({ message: BlogMessage.TITLE_REQUIRED })
|
||||||
|
@IsString({ message: BlogMessage.TITLE_STRING })
|
||||||
|
@MaxLength(150, { message: BlogMessage.TITLE_MAX_LENGTH })
|
||||||
|
@ApiProperty({ description: "Title of the blog", example: "Blog Title" })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: BlogMessage.PREVIEW_CONTENT_REQUIRED })
|
||||||
|
@IsString({ message: BlogMessage.PREVIEW_CONTENT_STRING })
|
||||||
|
@MaxLength(150, { message: BlogMessage.PREVIEW_CONTENT_MAX_LENGTH })
|
||||||
|
@ApiProperty({ description: "Preview content of the blog", example: "Preview content of the blog" })
|
||||||
|
previewContent: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: BlogMessage.CONTENT_REQUIRED })
|
||||||
|
@IsString({ message: BlogMessage.CONTENT_STRING })
|
||||||
|
@MaxLength(600, { message: BlogMessage.CONTENT_MAX_LENGTH })
|
||||||
|
@ApiProperty({ description: "Content of the blog", example: "Content of the blog" })
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: BlogMessage.IMAGE_URL_REQUIRED })
|
||||||
|
@IsString({ message: BlogMessage.IMAGE_URL_STRING })
|
||||||
|
@MaxLength(150, { message: BlogMessage.IMAGE_URL_MAX_LENGTH })
|
||||||
|
imageUrl: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: BlogMessage.META_TITLE_REQUIRED })
|
||||||
|
@IsString({ message: BlogMessage.META_TITLE_STRING })
|
||||||
|
@MaxLength(60, { message: BlogMessage.META_TITLE_MAX_LENGTH })
|
||||||
|
@ApiProperty({ description: "Meta title of the blog", example: "Meta title of the blog" })
|
||||||
|
metaTitle: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: BlogMessage.META_DESCRIPTION_REQUIRED })
|
||||||
|
@IsString({ message: BlogMessage.META_DESCRIPTION_STRING })
|
||||||
|
@MaxLength(160, { message: BlogMessage.META_DESCRIPTION_MAX_LENGTH })
|
||||||
|
@ApiProperty({ description: "Meta description of the blog", example: "Meta description of the blog" })
|
||||||
|
metaDescription: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: BlogMessage.TAGS_REQUIRED })
|
||||||
|
@IsArray({ message: BlogMessage.TAGS_ARRAY })
|
||||||
|
@IsString({ each: true, message: BlogMessage.TAGS_STRING })
|
||||||
|
@ArrayMinSize(1, { message: BlogMessage.TAGS_MIN_SIZE })
|
||||||
|
@ApiProperty({ description: "Tags of the blog", example: ["tag1", "tag2", "tag3"] })
|
||||||
|
tags: string[];
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: BlogMessage.CATEGORY_ID_REQUIRED })
|
||||||
|
@IsUUID("4", { message: BlogMessage.CATEGORY_ID_UUID })
|
||||||
|
@ApiProperty({ description: "Category ID of the blog", example: "123e4567-e89b-12d3-a456-426614174000" })
|
||||||
|
categoryId: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: BlogMessage.IS_ACTIVE_REQUIRED })
|
||||||
|
@IsBoolean({ message: BlogMessage.IS_ACTIVE_SHOULD_BE_A_BOOLEAN })
|
||||||
|
@ApiProperty({ description: "Is active of the blog", example: true })
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsBoolean, IsNotEmpty, IsString, IsUrl, MaxLength } from "class-validator";
|
||||||
|
|
||||||
|
import { BlogMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class CreateCategoryDto {
|
||||||
|
@IsNotEmpty({ message: BlogMessage.TITLE_REQUIRED })
|
||||||
|
@IsString({ message: BlogMessage.TITLE_STRING })
|
||||||
|
@MaxLength(100, { message: BlogMessage.TITLE_MAX_LENGTH })
|
||||||
|
@ApiProperty({ description: "The title of the category", example: "This is the title of the category" })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: BlogMessage.DESCRIPTION_REQUIRED })
|
||||||
|
@IsString({ message: BlogMessage.DESCRIPTION_STRING })
|
||||||
|
@MaxLength(255, { message: BlogMessage.DESCRIPTION_MAX_LENGTH })
|
||||||
|
@ApiProperty({ description: "The description of the category", example: "This is the description of the category" })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: BlogMessage.ICON_URL_REQUIRED })
|
||||||
|
@IsUrl({ protocols: ["http", "https"] }, { message: BlogMessage.ICON_URL_INVALID })
|
||||||
|
@MaxLength(255, { message: BlogMessage.ICON_URL_MAX_LENGTH })
|
||||||
|
@ApiProperty({ description: "The URL of the icon for the category", example: "https://example.com/icon.png" })
|
||||||
|
iconUrl: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: BlogMessage.IS_ACTIVE_REQUIRED })
|
||||||
|
@IsBoolean({ message: BlogMessage.IS_ACTIVE_SHOULD_BE_A_BOOLEAN })
|
||||||
|
@ApiProperty({ description: "The status of the category", example: true })
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsNotEmpty, IsString } from "class-validator";
|
||||||
|
|
||||||
|
import { BlogMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class CreateCommentDto {
|
||||||
|
@IsNotEmpty({ message: BlogMessage.TITLE_REQUIRED })
|
||||||
|
@IsString({ message: BlogMessage.TITLE_STRING })
|
||||||
|
@ApiProperty({ description: "the title of comment" })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@IsNotEmpty({ message: BlogMessage.COMMENT_CONTENT_REQUIRED })
|
||||||
|
@IsString({ message: BlogMessage.COMMENT_CONTENT_STRING })
|
||||||
|
@ApiProperty({ description: "the content of comment", example: "sd" })
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsNotEmpty, IsString } from "class-validator";
|
||||||
|
|
||||||
|
import { BlogMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class BlogSlugDto {
|
||||||
|
@IsNotEmpty({ message: BlogMessage.SLUG_NOT_EMPTY })
|
||||||
|
@IsString({ message: BlogMessage.SLUG_STRING })
|
||||||
|
@ApiProperty({ description: "The slug of the blog", example: "blog-slug" })
|
||||||
|
slug: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { ApiPropertyOptional, PickType } from "@nestjs/swagger";
|
||||||
|
import { Type } from "class-transformer";
|
||||||
|
import { IsDateString, IsIn, IsOptional, IsString, IsUUID } from "class-validator";
|
||||||
|
|
||||||
|
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||||
|
import { BlogMessage, CategoryMessage } from "../../../common/enums/message.enum";
|
||||||
|
|
||||||
|
export class BlogListSearchQueryDto extends PaginationDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString({ message: BlogMessage.SEARCH_QUERY_STRING })
|
||||||
|
@ApiPropertyOptional({ description: "Search query", example: "search query" })
|
||||||
|
q?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsUUID(4, { message: CategoryMessage.CAT_ID_SHOULD_BE_UUID })
|
||||||
|
@ApiPropertyOptional({ description: "Category id", example: "8b1e8b1e-8b1e-8b1e-8b1e-8b1e8b1e8b1e" })
|
||||||
|
categoryId?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsDateString({}, { message: BlogMessage.INVALID_DATE })
|
||||||
|
@ApiPropertyOptional({ description: "since query", example: "2025-01-27" })
|
||||||
|
since?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsIn([1, 0], { message: BlogMessage.IS_ACTIVE_SHOULD_BE_1_0 })
|
||||||
|
@ApiPropertyOptional({ description: "blog status", example: 1 })
|
||||||
|
isActive?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BlogSearchQueryDto extends PickType(BlogListSearchQueryDto, ["limit", "page", "categoryId"] as const) {}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { PartialType } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
import { CreateBlogDto } from "./create-blog.dto";
|
||||||
|
|
||||||
|
export class UpdateBlogDto extends PartialType(CreateBlogDto) {}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { PartialType } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
import { CreateCategoryDto } from "./create-category.dto";
|
||||||
|
|
||||||
|
export class UpdateCategoryDto extends PartialType(CreateCategoryDto) {}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsEnum, IsNotEmpty } from "class-validator";
|
||||||
|
|
||||||
|
import { BlogMessage } from "../../../common/enums/message.enum";
|
||||||
|
import { CommentStatus } from "../enums/comment-status.enum";
|
||||||
|
|
||||||
|
export class UpdateCommentStatusDto {
|
||||||
|
@IsNotEmpty({ message: BlogMessage.COMMENT_STATUS_REQUIRED })
|
||||||
|
@IsEnum(CommentStatus)
|
||||||
|
@ApiProperty({ description: "the status of comment", example: CommentStatus.APPROVED, enum: CommentStatus })
|
||||||
|
status: CommentStatus;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||||
|
|
||||||
|
import { BlogsController } from "./controllers/blogs.controller";
|
||||||
|
import { BlogCategory } from "./entities/blog-category.entity";
|
||||||
|
import { BlogComment } from "./entities/blog-comment.entity";
|
||||||
|
import { Blog } from "./entities/blog.entity";
|
||||||
|
import { BlogsService } from "./providers/blogs.service";
|
||||||
|
import { BlogCategoriesRepository } from "./repositories/blog-categories.repository";
|
||||||
|
import { BlogCommentsRepository } from "./repositories/blog-comments.repository";
|
||||||
|
import { BlogsRepository } from "./repositories/blogs.repository";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Blog, BlogComment, BlogCategory])],
|
||||||
|
controllers: [BlogsController],
|
||||||
|
providers: [BlogsService, BlogsRepository, BlogCommentsRepository, BlogCategoriesRepository],
|
||||||
|
exports: [BlogsService],
|
||||||
|
})
|
||||||
|
export class BlogsModule {}
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Patch, Post, Put, Query } from "@nestjs/common";
|
||||||
|
import { ApiOperation } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
import { AdminRoute } from "../../../common/decorators/admin.decorator";
|
||||||
|
import { AuthGuards } from "../../../common/decorators/auth-guard.decorator";
|
||||||
|
import { Pagination } from "../../../common/decorators/pagination.decorator";
|
||||||
|
import { PermissionsDec } from "../../../common/decorators/permission.decorator";
|
||||||
|
import { SkipAuth } from "../../../common/decorators/skip-auth.decorator";
|
||||||
|
import { UserDec } from "../../../common/decorators/user.decorator";
|
||||||
|
import { ParamDto } from "../../../common/DTO/param.dto";
|
||||||
|
import { CategoryListSearchQueryDto } from "../../danak-services/DTO/category-search-query.dto";
|
||||||
|
import { PermissionEnum } from "../../users/enums/permission.enum";
|
||||||
|
import { CreateBlogDto } from "../DTO/create-blog.dto";
|
||||||
|
import { CreateCategoryDto } from "../DTO/create-category.dto";
|
||||||
|
import { CreateCommentDto } from "../DTO/create-comment.dto";
|
||||||
|
import { BlogSlugDto } from "../DTO/get-blog-by-slug.dto";
|
||||||
|
import { BlogListSearchQueryDto, BlogSearchQueryDto } from "../DTO/search-blog-query.dto";
|
||||||
|
import { UpdateBlogDto } from "../DTO/update-blog.dto";
|
||||||
|
import { UpdateCategoryDto } from "../DTO/update-category.dto";
|
||||||
|
import { UpdateCommentStatusDto } from "../DTO/update-comment-status.dto";
|
||||||
|
import { BlogsService } from "../providers/blogs.service";
|
||||||
|
|
||||||
|
@Controller("blogs")
|
||||||
|
@AuthGuards()
|
||||||
|
export class BlogsController {
|
||||||
|
constructor(private readonly blogsService: BlogsService) {}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Get all public and active blogs with category id" })
|
||||||
|
@SkipAuth()
|
||||||
|
@Get()
|
||||||
|
getBlogsPublic(@Query() queryDto: BlogSearchQueryDto) {
|
||||||
|
return this.blogsService.getBlogsPublic(queryDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Get all blogs (admin route)" })
|
||||||
|
@AdminRoute()
|
||||||
|
@PermissionsDec(PermissionEnum.BLOGS)
|
||||||
|
@Get("list")
|
||||||
|
getBlogList(@Query() queryDto: BlogListSearchQueryDto) {
|
||||||
|
return this.blogsService.getBlogList(queryDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Get a blog by id" })
|
||||||
|
@SkipAuth()
|
||||||
|
@Get(":id")
|
||||||
|
getBlog(@Param() paramDto: ParamDto) {
|
||||||
|
return this.blogsService.getBlogById(paramDto.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Get a blog by slug" })
|
||||||
|
@SkipAuth()
|
||||||
|
@Get("slug/:slug")
|
||||||
|
getBlogBySlug(@Param() paramDto: BlogSlugDto) {
|
||||||
|
return this.blogsService.getBlogBySlug(paramDto.slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Create a new blog (admin route)" })
|
||||||
|
@Post()
|
||||||
|
@AdminRoute()
|
||||||
|
@PermissionsDec(PermissionEnum.BLOGS)
|
||||||
|
createBlog(@Body() createBlogDto: CreateBlogDto, @UserDec("id") userId: string) {
|
||||||
|
return this.blogsService.createBlog(createBlogDto, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Update a blog (admin route)" })
|
||||||
|
@Patch(":id")
|
||||||
|
@AdminRoute()
|
||||||
|
@PermissionsDec(PermissionEnum.BLOGS)
|
||||||
|
updateBlog(@Param() paramDto: ParamDto, @Body() updateBlogDto: UpdateBlogDto) {
|
||||||
|
return this.blogsService.updateBlog(paramDto.id, updateBlogDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Delete a blog (admin route) " })
|
||||||
|
@Delete(":id")
|
||||||
|
@AdminRoute()
|
||||||
|
@PermissionsDec(PermissionEnum.BLOGS)
|
||||||
|
deleteBlog(@Param() paramDto: ParamDto) {
|
||||||
|
return this.blogsService.deleteBlog(paramDto.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------- comments --------------
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Create a new comment" })
|
||||||
|
@Post(":id/comments")
|
||||||
|
createComment(@Param() paramDto: ParamDto, @Body() createCommentDto: CreateCommentDto, @UserDec("id") userId: string) {
|
||||||
|
return this.blogsService.createComment(paramDto.id, createCommentDto, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Get all comments for a blog" })
|
||||||
|
@AdminRoute()
|
||||||
|
@PermissionsDec(PermissionEnum.BLOGS)
|
||||||
|
@Get(":id/comments")
|
||||||
|
getBlogComments(@Param() paramDto: ParamDto) {
|
||||||
|
return this.blogsService.getBlogComments(paramDto.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Update the status of a comment (admin route)" })
|
||||||
|
@AdminRoute()
|
||||||
|
@PermissionsDec(PermissionEnum.BLOGS)
|
||||||
|
@Put("comments/:id/status")
|
||||||
|
updateCommentStatus(@Param() paramDto: ParamDto, @Body() updateCommentStatusDto: UpdateCommentStatusDto) {
|
||||||
|
return this.blogsService.updateCommentStatus(paramDto.id, updateCommentStatusDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------- categories --------------
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Get all active categories (admin route)" })
|
||||||
|
@Pagination()
|
||||||
|
@Get("categories/list")
|
||||||
|
getAllActiveCategories(@Query() queryDto: CategoryListSearchQueryDto) {
|
||||||
|
return this.blogsService.getCategories(queryDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Get all active categories (user side)" })
|
||||||
|
@Get("categories/public")
|
||||||
|
getCategoriesUserSide() {
|
||||||
|
return this.blogsService.getCategoriesUserSide();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Get a category by id" })
|
||||||
|
@Get("categories/:id")
|
||||||
|
getCategory(@Param() paramDto: ParamDto) {
|
||||||
|
return this.blogsService.getCategory(paramDto.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Create a new category (admin route)" })
|
||||||
|
@Post("categories")
|
||||||
|
@AdminRoute()
|
||||||
|
@PermissionsDec(PermissionEnum.BLOGS)
|
||||||
|
createCategory(@Body() createCategoryDto: CreateCategoryDto) {
|
||||||
|
return this.blogsService.createCategory(createCategoryDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Update a category (admin route)" })
|
||||||
|
@Put("categories/:id")
|
||||||
|
@AdminRoute()
|
||||||
|
@PermissionsDec(PermissionEnum.BLOGS)
|
||||||
|
updateCategory(@Param() paramDto: ParamDto, @Body() updateCategoryDto: UpdateCategoryDto) {
|
||||||
|
return this.blogsService.updateCategory(paramDto.id, updateCategoryDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "Delete a category (admin route)" })
|
||||||
|
@Delete("categories/:id")
|
||||||
|
@AdminRoute()
|
||||||
|
@PermissionsDec(PermissionEnum.BLOGS)
|
||||||
|
deleteCategory(@Param() paramDto: ParamDto) {
|
||||||
|
return this.blogsService.deleteCategory(paramDto.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "toggle status of categories (admin route)" })
|
||||||
|
@PermissionsDec(PermissionEnum.BLOGS)
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post("categories/toggle-status/:id")
|
||||||
|
toggleCategoryStatus(@Param() paramDto: ParamDto) {
|
||||||
|
return this.blogsService.toggleCategoryStatus(paramDto.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Column, DeleteDateColumn, Entity, OneToMany } from "typeorm";
|
||||||
|
|
||||||
|
import { Blog } from "./blog.entity";
|
||||||
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class BlogCategory extends BaseEntity {
|
||||||
|
@Column({ type: "varchar", length: 100, unique: true })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@Column({ type: "varchar", length: 255, nullable: true })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@Column({ type: "varchar", length: 255, nullable: true })
|
||||||
|
iconUrl: string;
|
||||||
|
|
||||||
|
@Column({ type: "boolean", default: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@OneToMany(() => Blog, (blog) => blog.category)
|
||||||
|
blogs: Blog[];
|
||||||
|
|
||||||
|
@DeleteDateColumn({ type: "timestamptz" })
|
||||||
|
deletedAt?: Date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { Column, Entity, ManyToOne } from "typeorm";
|
||||||
|
|
||||||
|
import { Blog } from "./blog.entity";
|
||||||
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
|
import { User } from "../../users/entities/user.entity";
|
||||||
|
import { CommentStatus } from "../enums/comment-status.enum";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class BlogComment extends BaseEntity {
|
||||||
|
@Column({ type: "varchar", length: 150, nullable: false })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@Column({ type: "text" })
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
@Column({ type: "enum", enum: CommentStatus, default: CommentStatus.PENDING })
|
||||||
|
status: CommentStatus;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, (user) => user.blogComments)
|
||||||
|
user: User;
|
||||||
|
|
||||||
|
@ManyToOne(() => Blog, (blog) => blog.comments)
|
||||||
|
blog: Blog;
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Column, DeleteDateColumn, Entity, ManyToOne, OneToMany } from "typeorm";
|
||||||
|
|
||||||
|
import { BlogCategory } from "./blog-category.entity";
|
||||||
|
import { BlogComment } from "./blog-comment.entity";
|
||||||
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
|
import { User } from "../../users/entities/user.entity";
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Blog extends BaseEntity {
|
||||||
|
@Column({ type: "varchar", length: 150, unique: true })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@Column({ type: "varchar", length: 150, unique: true })
|
||||||
|
slug: string;
|
||||||
|
|
||||||
|
@Column({ type: "text" })
|
||||||
|
previewContent: string;
|
||||||
|
|
||||||
|
@Column({ type: "text" })
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
@Column({ type: "varchar", length: 150, nullable: true })
|
||||||
|
imageUrl: string;
|
||||||
|
|
||||||
|
@Column({ type: "varchar", length: 100, nullable: true })
|
||||||
|
metaTitle: string;
|
||||||
|
|
||||||
|
@Column({ type: "varchar", length: 160, nullable: true })
|
||||||
|
metaDescription: string;
|
||||||
|
|
||||||
|
@Column({ type: "simple-array", nullable: true })
|
||||||
|
tags: string[];
|
||||||
|
|
||||||
|
@Column({ type: "boolean", default: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, (user) => user.blogs)
|
||||||
|
author: User;
|
||||||
|
|
||||||
|
@ManyToOne(() => BlogCategory, (category) => category.blogs)
|
||||||
|
category: BlogCategory;
|
||||||
|
|
||||||
|
@OneToMany(() => BlogComment, (comment) => comment.blog)
|
||||||
|
comments: BlogComment[];
|
||||||
|
|
||||||
|
@DeleteDateColumn({ type: "timestamptz" })
|
||||||
|
deletedAt?: Date;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export enum CommentStatus {
|
||||||
|
PENDING = "PENDING",
|
||||||
|
APPROVED = "APPROVED",
|
||||||
|
REJECTED = "REJECTED",
|
||||||
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||||
|
import slugify from "slugify";
|
||||||
|
import { Not } from "typeorm";
|
||||||
|
|
||||||
|
import { BlogMessage, CommonMessage } from "../../../common/enums/message.enum";
|
||||||
|
import { CategoryListSearchQueryDto } from "../../danak-services/DTO/category-search-query.dto";
|
||||||
|
import { UsersService } from "../../users/providers/users.service";
|
||||||
|
import { CreateBlogDto } from "../DTO/create-blog.dto";
|
||||||
|
import { CreateCategoryDto } from "../DTO/create-category.dto";
|
||||||
|
import { CreateCommentDto } from "../DTO/create-comment.dto";
|
||||||
|
import { BlogListSearchQueryDto, BlogSearchQueryDto } from "../DTO/search-blog-query.dto";
|
||||||
|
import { UpdateBlogDto } from "../DTO/update-blog.dto";
|
||||||
|
import { UpdateCategoryDto } from "../DTO/update-category.dto";
|
||||||
|
import { UpdateCommentStatusDto } from "../DTO/update-comment-status.dto";
|
||||||
|
import { CommentStatus } from "../enums/comment-status.enum";
|
||||||
|
import { BlogCategoriesRepository } from "../repositories/blog-categories.repository";
|
||||||
|
import { BlogCommentsRepository } from "../repositories/blog-comments.repository";
|
||||||
|
import { BlogsRepository } from "../repositories/blogs.repository";
|
||||||
|
@Injectable()
|
||||||
|
export class BlogsService {
|
||||||
|
constructor(
|
||||||
|
private readonly blogsRepository: BlogsRepository,
|
||||||
|
private readonly blogCommentsRepository: BlogCommentsRepository,
|
||||||
|
private readonly blogCategoriesRepository: BlogCategoriesRepository,
|
||||||
|
private readonly usersService: UsersService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
//*********************************** */
|
||||||
|
async createCategory(createCategoryDto: CreateCategoryDto) {
|
||||||
|
const existingCategory = await this.blogCategoriesRepository.findOneBy({ title: createCategoryDto.title });
|
||||||
|
if (existingCategory) throw new BadRequestException(BlogMessage.CATEGORY_ALREADY_EXISTS);
|
||||||
|
|
||||||
|
const category = await this.blogCategoriesRepository.createCategory(createCategoryDto);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: CommonMessage.CREATED,
|
||||||
|
category,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async updateCategory(id: string, updateCategoryDto: UpdateCategoryDto) {
|
||||||
|
const category = await this.findCategoryById(id);
|
||||||
|
|
||||||
|
if (updateCategoryDto.title) {
|
||||||
|
await this.checkCategoryExists(updateCategoryDto.title, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.blogCategoriesRepository.save({ ...category, ...updateCategoryDto });
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: CommonMessage.UPDATE_SUCCESS,
|
||||||
|
category,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async deleteCategory(id: string) {
|
||||||
|
await this.findCategoryById(id);
|
||||||
|
|
||||||
|
await this.blogCategoriesRepository.softDelete(id);
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async toggleCategoryStatus(id: string) {
|
||||||
|
const category = await this.findCategoryById(id);
|
||||||
|
|
||||||
|
//
|
||||||
|
category.isActive = !category.isActive;
|
||||||
|
await this.blogCategoriesRepository.save(category);
|
||||||
|
|
||||||
|
//
|
||||||
|
return { message: CommonMessage.UPDATE_SUCCESS, category };
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async getCategory(id: string) {
|
||||||
|
const category = await this.findCategoryById(id);
|
||||||
|
return { category };
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async getCategoriesUserSide() {
|
||||||
|
const categories = await this.blogCategoriesRepository.getCategoriesUserSide();
|
||||||
|
return { categories };
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async getCategories(queryDto: CategoryListSearchQueryDto) {
|
||||||
|
const [categories, count] = await this.blogCategoriesRepository.getCategories(queryDto);
|
||||||
|
return { categories, count, paginate: true };
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async createBlog(createBlogDto: CreateBlogDto, authorId: string) {
|
||||||
|
const existingBlog = await this.blogsRepository.findOne({ where: { title: createBlogDto.title } });
|
||||||
|
if (existingBlog) throw new BadRequestException(BlogMessage.BLOG_ALREADY_EXISTS);
|
||||||
|
|
||||||
|
const category = await this.findCategoryById(createBlogDto.categoryId);
|
||||||
|
|
||||||
|
const slug = this.generateSlug(createBlogDto.title);
|
||||||
|
|
||||||
|
const blog = this.blogsRepository.create({
|
||||||
|
...createBlogDto,
|
||||||
|
slug,
|
||||||
|
author: { id: authorId },
|
||||||
|
category,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.blogsRepository.save(blog);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: CommonMessage.CREATED,
|
||||||
|
blog,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async updateBlog(id: string, updateBlogDto: UpdateBlogDto) {
|
||||||
|
const blog = await this.findBlogById(id);
|
||||||
|
|
||||||
|
if (updateBlogDto.title) {
|
||||||
|
const existBlog = await this.blogsRepository.findOne({ where: { title: updateBlogDto.title, id: Not(id) } });
|
||||||
|
if (existBlog) throw new BadRequestException(BlogMessage.BLOG_ALREADY_EXISTS);
|
||||||
|
blog.slug = this.generateSlug(updateBlogDto.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateBlogDto.categoryId) {
|
||||||
|
const category = await this.findCategoryById(updateBlogDto.categoryId);
|
||||||
|
blog.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.blogsRepository.save({ ...blog, ...updateBlogDto });
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: CommonMessage.UPDATE_SUCCESS,
|
||||||
|
blog,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async deleteBlog(id: string) {
|
||||||
|
await this.findBlogById(id);
|
||||||
|
|
||||||
|
await this.blogsRepository.softDelete(id);
|
||||||
|
return { message: CommonMessage.DELETED };
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async getBlogById(id: string) {
|
||||||
|
const blog = await this.findBlogById(id);
|
||||||
|
return { blog };
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async getBlogBySlug(slug: string) {
|
||||||
|
const blog = await this.blogsRepository.findOneBySlug(slug);
|
||||||
|
if (!blog) throw new BadRequestException(BlogMessage.BLOG_NOT_FOUND);
|
||||||
|
return { blog };
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async getBlogsPublic(queryDto: BlogSearchQueryDto) {
|
||||||
|
const blogs = await this.blogsRepository.getBlogsForUserSide(queryDto);
|
||||||
|
return { blogs };
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async getBlogList(queryDto: BlogListSearchQueryDto) {
|
||||||
|
const [blogs, count] = await this.blogsRepository.getBlogListForAdmin(queryDto);
|
||||||
|
return { blogs, count, paginate: true };
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async createComment(blogId: string, createCommentDto: CreateCommentDto, userId: string) {
|
||||||
|
const blog = await this.findBlogById(blogId);
|
||||||
|
|
||||||
|
const { user } = await this.usersService.findOneById(userId);
|
||||||
|
|
||||||
|
const comment = this.blogCommentsRepository.create({
|
||||||
|
...createCommentDto,
|
||||||
|
blog,
|
||||||
|
user,
|
||||||
|
status: CommentStatus.PENDING,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.blogCommentsRepository.save(comment);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: CommonMessage.CREATED,
|
||||||
|
comment,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async updateCommentStatus(commentId: string, updateCommentStatusDto: UpdateCommentStatusDto) {
|
||||||
|
const comment = await this.blogCommentsRepository.findOneById(commentId);
|
||||||
|
if (!comment) throw new BadRequestException(BlogMessage.COMMENT_NOT_FOUND);
|
||||||
|
|
||||||
|
comment.status = updateCommentStatusDto.status;
|
||||||
|
await this.blogCommentsRepository.save(comment);
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: CommonMessage.UPDATE_SUCCESS,
|
||||||
|
comment,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async getBlogComments(blogId: string) {
|
||||||
|
const comments = await this.blogCommentsRepository.findAllByBlogId(blogId);
|
||||||
|
return { comments };
|
||||||
|
}
|
||||||
|
|
||||||
|
//*********************************** */
|
||||||
|
private generateSlug(title: string): string {
|
||||||
|
return slugify(title, { lower: true, strict: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
//*********************************** */
|
||||||
|
private async checkCategoryExists(title: string, id?: string) {
|
||||||
|
const existingCategory = await this.blogCategoriesRepository.findOneBy({ title, ...(id && { id: Not(id) }) });
|
||||||
|
if (existingCategory) throw new BadRequestException(BlogMessage.CATEGORY_ALREADY_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
//*********************************** */
|
||||||
|
private async findCategoryById(id: string) {
|
||||||
|
const category = await this.blogCategoriesRepository.findOneById(id);
|
||||||
|
if (!category) throw new BadRequestException(BlogMessage.CATEGORY_NOT_FOUND);
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
//*********************************** */
|
||||||
|
private async findBlogById(id: string) {
|
||||||
|
const blog = await this.blogsRepository.findOneById(id);
|
||||||
|
if (!blog) throw new BadRequestException(BlogMessage.BLOG_NOT_FOUND);
|
||||||
|
return blog;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
|
import { IsNull, Repository } from "typeorm";
|
||||||
|
|
||||||
|
import { CategoryListSearchQueryDto } from "../../danak-services/DTO/category-search-query.dto";
|
||||||
|
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||||
|
import { CreateCategoryDto } from "../DTO/create-category.dto";
|
||||||
|
import { BlogCategory } from "../entities/blog-category.entity";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BlogCategoriesRepository extends Repository<BlogCategory> {
|
||||||
|
constructor(@InjectRepository(BlogCategory) repository: Repository<BlogCategory>) {
|
||||||
|
super(repository.target, repository.manager, repository.queryRunner);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneById(id: string): Promise<BlogCategory | null> {
|
||||||
|
return this.findOne({
|
||||||
|
where: { id, deletedAt: IsNull() },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCategoriesUserSide() {
|
||||||
|
return this.find({
|
||||||
|
where: { isActive: true, deletedAt: IsNull() },
|
||||||
|
order: { createdAt: "DESC" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCategories(queryDto: CategoryListSearchQueryDto) {
|
||||||
|
const { limit, skip } = PaginationUtils(queryDto);
|
||||||
|
|
||||||
|
const query = this.createQueryBuilder("blogCategory").where("blogCategory.deletedAt IS NULL");
|
||||||
|
|
||||||
|
if (queryDto.q) {
|
||||||
|
query.andWhere("blogCategory.title ILIKE :q OR blogCategory.slug ILIKE :q", { q: `%${queryDto.q}%` });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryDto.isActive) {
|
||||||
|
query.andWhere("blogCategory.isActive = :isActive", { isActive: queryDto.isActive });
|
||||||
|
}
|
||||||
|
|
||||||
|
query.skip(skip).take(limit).orderBy({ createdAt: "DESC" });
|
||||||
|
|
||||||
|
return query.getManyAndCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCategory(createBlogCategoryDto: CreateCategoryDto) {
|
||||||
|
const { title, description, iconUrl, isActive } = createBlogCategoryDto;
|
||||||
|
|
||||||
|
const blogCategory = this.create({ title, description, iconUrl, isActive });
|
||||||
|
|
||||||
|
return this.save(blogCategory);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
|
import { Repository } from "typeorm";
|
||||||
|
|
||||||
|
import { BlogComment } from "../entities/blog-comment.entity";
|
||||||
|
import { CommentStatus } from "../enums/comment-status.enum";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BlogCommentsRepository extends Repository<BlogComment> {
|
||||||
|
constructor(@InjectRepository(BlogComment) blogCommentsRepository: Repository<BlogComment>) {
|
||||||
|
super(blogCommentsRepository.target, blogCommentsRepository.manager, blogCommentsRepository.queryRunner);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneById(id: string) {
|
||||||
|
return this.findOne({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllByBlogId(blogId: string) {
|
||||||
|
return this.find({
|
||||||
|
where: { blog: { id: blogId }, status: CommentStatus.APPROVED },
|
||||||
|
order: { createdAt: "DESC" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
|
import { Brackets, IsNull, Repository } from "typeorm";
|
||||||
|
|
||||||
|
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||||
|
import { BlogListSearchQueryDto, BlogSearchQueryDto } from "../DTO/search-blog-query.dto";
|
||||||
|
import { Blog } from "../entities/blog.entity";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BlogsRepository extends Repository<Blog> {
|
||||||
|
constructor(@InjectRepository(Blog) blogsRepository: Repository<Blog>) {
|
||||||
|
super(blogsRepository.target, blogsRepository.manager, blogsRepository.queryRunner);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneById(id: string): Promise<Blog | null> {
|
||||||
|
return this.findOne({
|
||||||
|
where: { id, deletedAt: IsNull() },
|
||||||
|
relations: {
|
||||||
|
author: true,
|
||||||
|
category: true,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
title: true,
|
||||||
|
previewContent: true,
|
||||||
|
metaTitle: true,
|
||||||
|
metaDescription: true,
|
||||||
|
imageUrl: true,
|
||||||
|
slug: true,
|
||||||
|
createdAt: true,
|
||||||
|
content: true,
|
||||||
|
tags: true,
|
||||||
|
category: {
|
||||||
|
id: true,
|
||||||
|
title: true,
|
||||||
|
iconUrl: true,
|
||||||
|
description: true,
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneBySlug(slug: string): Promise<Blog | null> {
|
||||||
|
return this.findOne({
|
||||||
|
where: { slug, deletedAt: IsNull(), isActive: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fineOneByTitle(title: string): Promise<Blog | null> {
|
||||||
|
return this.findOne({
|
||||||
|
where: { title, deletedAt: IsNull(), isActive: true },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//*********************************** */
|
||||||
|
|
||||||
|
async getBlogsForUserSide(queryDto: BlogSearchQueryDto) {
|
||||||
|
const { limit, skip } = PaginationUtils(queryDto);
|
||||||
|
|
||||||
|
const queryBuilder = this.createQueryBuilder("blog")
|
||||||
|
.where("blog.deletedAt IS NULL")
|
||||||
|
.andWhere("blog.isActive = :isActive", { isActive: true })
|
||||||
|
.leftJoinAndSelect("blog.category", "category")
|
||||||
|
.leftJoinAndSelect("blog.author", "author")
|
||||||
|
.select([
|
||||||
|
"blog.id",
|
||||||
|
"blog.title",
|
||||||
|
"blog.previewContent",
|
||||||
|
"blog.metaTitle",
|
||||||
|
"blog.metaDescription",
|
||||||
|
"blog.imageUrl",
|
||||||
|
"blog.slug",
|
||||||
|
"blog.createdAt",
|
||||||
|
"category.id",
|
||||||
|
"category.iconUrl",
|
||||||
|
"category.title",
|
||||||
|
"author.id",
|
||||||
|
"author.firstName",
|
||||||
|
"author.lastName",
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (queryDto.categoryId) {
|
||||||
|
queryBuilder.andWhere("blog.categoryId = :categoryId", { categoryId: queryDto.categoryId });
|
||||||
|
}
|
||||||
|
|
||||||
|
queryBuilder.skip(skip).take(limit).orderBy("blog.createdAt", "DESC");
|
||||||
|
return queryBuilder.getManyAndCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
//*********************************** */
|
||||||
|
async getBlogListForAdmin(queryDto: BlogListSearchQueryDto) {
|
||||||
|
const { limit, skip } = PaginationUtils(queryDto);
|
||||||
|
|
||||||
|
const queryBuilder = this.createQueryBuilder("blog")
|
||||||
|
.where("blog.deletedAt IS NULL")
|
||||||
|
.leftJoinAndSelect("blog.category", "category")
|
||||||
|
.leftJoinAndSelect("blog.author", "author");
|
||||||
|
|
||||||
|
if (queryDto.isActive) {
|
||||||
|
queryBuilder.andWhere("blog.isActive = :isActive", { isActive: queryDto.isActive === 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryDto.q) {
|
||||||
|
// queryBuilder.andWhere(
|
||||||
|
// "blog.title ILIKE :q OR blog.previewContent ILIKE :q OR blog.content ILIKE :q OR blog.metaTitle ILIKE :q OR blog.slug ILIKE :q",
|
||||||
|
// { q: `%${queryDto.q}%` },
|
||||||
|
// );
|
||||||
|
|
||||||
|
queryBuilder.andWhere(
|
||||||
|
new Brackets((qb) => {
|
||||||
|
qb.where("blog.title ILIKE :q", { q: `%${queryDto.q}%` })
|
||||||
|
.orWhere("blog.previewContent ILIKE :q", { q: `%${queryDto.q}%` })
|
||||||
|
.orWhere("blog.content ILIKE :q", { q: `%${queryDto.q}%` })
|
||||||
|
.orWhere("blog.metaTitle ILIKE :q", { q: `%${queryDto.q}%` })
|
||||||
|
.orWhere("blog.slug ILIKE :q", { q: `%${queryDto.q}%` });
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryDto.categoryId) {
|
||||||
|
queryBuilder.andWhere("blog.categoryId = :categoryId", { categoryId: queryDto.categoryId });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryDto.since) {
|
||||||
|
queryBuilder.andWhere("blog.createdAt >= :since", { since: queryDto.since });
|
||||||
|
}
|
||||||
|
|
||||||
|
queryBuilder.skip(skip).take(limit).orderBy("blog.createdAt", "DESC");
|
||||||
|
|
||||||
|
return queryBuilder.getManyAndCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,15 +7,15 @@ import { ReviewStatus } from "../enums/review-status.enum";
|
|||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class DanakServiceReview extends BaseEntity {
|
export class DanakServiceReview extends BaseEntity {
|
||||||
|
@Column({ type: "varchar", length: 150, nullable: false })
|
||||||
|
title: string;
|
||||||
|
|
||||||
@Column({ type: "text", nullable: false })
|
@Column({ type: "text", nullable: false })
|
||||||
comment: string;
|
comment: string;
|
||||||
|
|
||||||
@Column({ type: "int", nullable: false })
|
@Column({ type: "int", nullable: false })
|
||||||
rating: number;
|
rating: number;
|
||||||
|
|
||||||
@Column({ type: "varchar", length: 150, nullable: false })
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
@Column({ type: "enum", enum: ReviewStatus, default: ReviewStatus.PENDING })
|
@Column({ type: "enum", enum: ReviewStatus, default: ReviewStatus.PENDING })
|
||||||
status: ReviewStatus;
|
status: ReviewStatus;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { Role } from "./role.entity";
|
|||||||
import { UserGroup } from "./user-group.entity";
|
import { UserGroup } from "./user-group.entity";
|
||||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
import { UserAnnouncement } from "../../announcements/entities/user-announcement.entity";
|
import { UserAnnouncement } from "../../announcements/entities/user-announcement.entity";
|
||||||
|
import { BlogComment } from "../../blogs/entities/blog-comment.entity";
|
||||||
|
import { Blog } from "../../blogs/entities/blog.entity";
|
||||||
import { Criticism } from "../../criticisms/entities/criticism.entity";
|
import { Criticism } from "../../criticisms/entities/criticism.entity";
|
||||||
import { DanakServiceReview } from "../../danak-services/entities/danak-service-review.entity";
|
import { DanakServiceReview } from "../../danak-services/entities/danak-service-review.entity";
|
||||||
import { Discount } from "../../discounts/entities/discount.entity";
|
import { Discount } from "../../discounts/entities/discount.entity";
|
||||||
@@ -74,6 +76,12 @@ export class User extends BaseEntity {
|
|||||||
@OneToMany(() => DanakServiceReview, (danakServiceReview) => danakServiceReview.user)
|
@OneToMany(() => DanakServiceReview, (danakServiceReview) => danakServiceReview.user)
|
||||||
reviews: DanakServiceReview[];
|
reviews: DanakServiceReview[];
|
||||||
|
|
||||||
|
@OneToMany(() => Blog, (blog) => blog.author)
|
||||||
|
blogs: Blog[];
|
||||||
|
|
||||||
|
@OneToMany(() => BlogComment, (blogComment) => blogComment.user)
|
||||||
|
blogComments: BlogComment[];
|
||||||
|
|
||||||
@OneToMany(() => Ticket, (ticket) => ticket.user)
|
@OneToMany(() => Ticket, (ticket) => ticket.user)
|
||||||
tickets: Ticket[];
|
tickets: Ticket[];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user