import { BadRequestException, Injectable } from "@nestjs/common"; import { Not } from "typeorm"; import { AdminMessage, BlogMessage, CommonMessage } from "../../../common/enums/message.enum"; import { UserType } from "../../access-logs/enums/user-type.enum"; import { AccessLogService } from "../../access-logs/providers/access-log.service"; 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 { CreateBlogCategoryDto } from "../DTO/create-category.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 { 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, private readonly accessLogService: AccessLogService, ) {} //*********************************** */ 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 existSlug = await this.blogsRepository.findOne({ where: { slug: createBlogDto.slug } }); if (existSlug) throw new BadRequestException(BlogMessage.BLOG_ALREADY_EXISTS_WITH_THIS_SLUG); const slug = this.generateSlug(createBlogDto.slug); const blog = this.blogsRepository.create({ ...createBlogDto, author: { id: authorId }, category, slug, }); await this.blogsRepository.save(blog); await this.accessLogService.logCreate( "Blog", blog.id, { blog: createBlogDto.title }, { user: { id: authorId }, userType: UserType.ADMIN, actionDescription: `Admin created blog: ${createBlogDto.title}`, metadata: { categoryId: createBlogDto.categoryId, slug: slug, authorId: authorId, }, }, ); return { message: CommonMessage.CREATED, blog, }; } //*********************************** */ async updateBlog(id: string, updateBlogDto: UpdateBlogDto, userId: string) { 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_WITH_THIS_SLUG); } await this.blogsRepository.save({ ...blog, ...updateBlogDto, slug: this.generateSlug(updateBlogDto.slug ?? blog.slug), }); await this.accessLogService.logUpdate( "Blog", blog.id, { blog: blog.title }, { blog: updateBlogDto.title }, { user: { id: userId }, userType: UserType.ADMIN, actionDescription: `Admin updated blog: ${blog.title}`, metadata: { categoryId: updateBlogDto.categoryId, }, }, ); 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 }); await this.accessLogService.logDelete( "Blog", blog.id, { blog: blog.title }, { user: { id: userId }, userType: UserType.ADMIN, actionDescription: `Admin deleted blog: ${blog.title}`, metadata: { categoryId: blog.category.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 }; } //*********************************** */ 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; } }