import { BadRequestException, Injectable } from "@nestjs/common"; import { Not } from "typeorm"; import { AdminMessage, 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 { BlogCommentQueryDto } from "../DTO/blog-comment-query.dto"; import { CreateBlogDto } from "../DTO/create-blog.dto"; import { CreateBlogCategoryDto } 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 { UpdateBlogCategoryDto } 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: CreateBlogCategoryDto) { 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: UpdateBlogCategoryDto) { 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, userId: string) { const isSuperAdmin = await this.usersService.isSuperAdmin(userId); if (!isSuperAdmin) throw new BadRequestException(AdminMessage.NOT_ALLOWED); const category = await this.blogCategoriesRepository.findOneBy({ id }); if (!category) throw new BadRequestException(BlogMessage.CATEGORY_NOT_FOUND); const blogs = await this.blogsRepository.find({ where: { category: { id } }, take: 2 }); if (blogs.length > 0) throw new BadRequestException(BlogMessage.CATEGORY_HAS_BLOGS); await this.blogCategoriesRepository.remove(category); return { message: CommonMessage.DELETED, }; } //*********************************** */ 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.slug); const blog = this.blogsRepository.create({ ...createBlogDto, author: { id: authorId }, category, slug, }); 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); } if (updateBlogDto.categoryId) { const category = await this.findCategoryById(updateBlogDto.categoryId); blog.category = category; } if (updateBlogDto.slug) { const existBlog = await this.blogsRepository.findOne({ where: { slug: updateBlogDto.slug, id: Not(id) } }); if (existBlog) throw new BadRequestException(BlogMessage.BLOG_ALREADY_EXISTS); } await this.blogsRepository.save({ ...blog, ...updateBlogDto, slug: this.generateSlug(updateBlogDto.slug ?? blog.slug), }); return { message: CommonMessage.UPDATE_SUCCESS, blog, }; } //*********************************** */ async deleteBlog(id: string, userId: string) { const isSuperAdmin = await this.usersService.isSuperAdmin(userId); if (!isSuperAdmin) throw new BadRequestException(AdminMessage.NOT_ALLOWED); const blog = await this.blogsRepository.findOneBy({ id }); if (!blog) throw new BadRequestException(BlogMessage.BLOG_NOT_FOUND); await this.blogsRepository.softDelete({ id }); return { message: CommonMessage.DELETED, }; } //*********************************** */ async getBlogById(id: string, isAdmin: boolean = false) { const blog = await this.findBlogById(id); if (isAdmin) return { blog }; blog.viewCount += 1; await this.blogsRepository.save(blog); const comments = await this.blogCommentsRepository.findBlogCommentsByBlogId(id); return { blog, comments }; } //*********************************** */ async getBlogBySlug(slug: string, isAdmin: boolean = false) { const blog = await this.blogsRepository.findOneBySlug(slug); if (!blog) throw new BadRequestException(BlogMessage.BLOG_NOT_FOUND); if (isAdmin) return { blog }; blog.viewCount += 1; await this.blogsRepository.save(blog); const comments = await this.blogCommentsRepository.findBlogCommentsByBlogId(blog.id); return { blog, comments }; } //*********************************** */ async getBlogsPublic(queryDto: BlogSearchQueryDto) { const [blogs, count] = await this.blogsRepository.getBlogsForUserSide(queryDto); //; return { blogs, count, paginate: true, }; } //*********************************** */ async getCombinedBlogs() { const [pinnedBlogs, mostVisitedBlogs] = await Promise.all([ this.blogsRepository.getPinnedBlogs(), this.blogsRepository.getMostVisitedBlogs(), ]); return { pinnedBlogs, mostVisitedBlogs }; } //*********************************** */ 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 getBlogCommentsForAdmin(blogId: string) { const comments = await this.blogCommentsRepository.findBlogCommentsForAdmin(blogId); return { comments }; } //*********************************** */ async getAllCommentForAdmin(queryDto: BlogCommentQueryDto) { const [comments, count] = await this.blogCommentsRepository.getAllCommentsForAdmin(queryDto); return { comments, count, paginate: true }; } //*********************************** */ private generateSlug(slug: string): string { if (!slug) return ""; // Replace spaces and special characters with hyphens, preserve Farsi characters return slug .trim() .replace(/[&\\#,+()$~%.'":*?<>{}]/g, "") // Remove special characters .replace(/\s+/g, "-") // Replace spaces with hyphens .replace(/-+/g, "-") // Replace multiple hyphens with single hyphen .trim(); } //*********************************** */ 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; } //*********************************** */ async incrementViewCount(id: string) { const blog = await this.findBlogById(id); blog.viewCount += 1; await this.blogsRepository.save(blog); return { message: "View count incremented successfully" }; } //*********************************** */ async getMostVisitedBlogs() { const blogs = await this.blogsRepository.getMostVisitedBlogs(); return { blogs }; } //*********************************** */ async getPinnedBlogs() { const blogs = await this.blogsRepository.getPinnedBlogs(); return { blogs }; } //*********************************** */ async togglePinnedStatus(id: string) { const blog = await this.findBlogById(id); blog.isPinned = !blog.isPinned; await this.blogsRepository.save(blog); return { message: CommonMessage.UPDATE_SUCCESS, blog }; } //*********************************** */ async toggleBlogStatus(id: string) { const blog = await this.findBlogById(id); blog.isActive = !blog.isActive; // await this.blogsRepository.save(blog); return { message: CommonMessage.UPDATE_SUCCESS, blog }; } //*********************************** */ private async findBlogById(id: string) { const blog = await this.blogsRepository.findOneById(id); if (!blog) throw new BadRequestException(BlogMessage.BLOG_NOT_FOUND); return blog; } }