import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { getBlogImagePublicUrl, resolveBlogImagePath } from '../../../config/uploadPaths'; import type { PaginatedResponse } from '../../../types/PaginatedResponse'; import { removeFileFromDisk } from '../../../utils/removeFileFromDisk'; import type { ListBlogPostsQueryDto } from '../dto/ListBlogPostsQueryDto'; import { UpdateBlogPostDto } from '../dto/UpdateBlogPostDto'; import { BlogPost } from '../entities/BlogPost'; import type { BlogPostExtended } from '../types/BlogPostExtended'; @Injectable() export class BlogPostsService { constructor( @InjectRepository(BlogPost) private readonly blogPostRepo: Repository ) {} async findAll({ page = 1, limit = 20 }: ListBlogPostsQueryDto): Promise> { const [items, total] = await this.blogPostRepo.findAndCount({ order: { createdAt: 'DESC' }, skip: (page - 1) * limit, take: limit }); const itemsExtended = items.map(post => this.toPostExtended(post)); return { items: itemsExtended, total, page, limit }; } async createDraft(): Promise { const entity = this.blogPostRepo.create({ title: '', contentHtml: '', isDraft: true }); await this.blogPostRepo.insert(entity); return this.findOne(entity.id); } async findOne(id: string): Promise { const entity = await this.findEntity(id); return this.toPostExtended(entity); } async update(id: string, { title, contentHtml, isDraft }: UpdateBlogPostDto): Promise { const exists = await this.blogPostRepo.exists({ where: { id } }); if (!exists) { throw new NotFoundException(); } await this.blogPostRepo.update(id, { title: title.trim(), contentHtml, isDraft }); return this.findOne(id); } async uploadThumbnail(id: string, filename: string): Promise { const post = await this.findEntity(id); const previousStorageKey = post.thumbnailStorageKey; await this.blogPostRepo.update(id, { thumbnailStorageKey: filename }); await this.removeBlogImageFile(previousStorageKey); return this.findOne(id); } async uploadBanner(id: string, filename: string): Promise { const post = await this.findEntity(id); const previousStorageKey = post.bannerStorageKey; await this.blogPostRepo.update(id, { bannerStorageKey: filename }); await this.removeBlogImageFile(previousStorageKey); return this.findOne(id); } async remove(id: string): Promise { const post = await this.findEntity(id); await this.blogPostRepo.delete(id); await Promise.all([ this.removeBlogImageFile(post.thumbnailStorageKey), this.removeBlogImageFile(post.bannerStorageKey) ]); } private async findEntity(id: string): Promise { const entity = await this.blogPostRepo.findOne({ where: { id } }); if (!entity) { throw new NotFoundException(); } return entity; } private toPostExtended(post: BlogPost): BlogPostExtended { return { ...post, thumbnailUrl: post.thumbnailStorageKey ? getBlogImagePublicUrl(post.thumbnailStorageKey) : null, bannerUrl: post.bannerStorageKey ? getBlogImagePublicUrl(post.bannerStorageKey) : null }; } private async removeBlogImageFile(storageKey: string | null): Promise { if (!storageKey) { return; } const path = resolveBlogImagePath(storageKey); await removeFileFromDisk(path, BlogPostsService.name); } }