Map storage keys to public paths in BlogPostsService responses so the CMS can resolve uploads consistently, and wrap blog card thumbnails in a fixed aspect-ratio container.
126 lines
3.9 KiB
TypeScript
126 lines
3.9 KiB
TypeScript
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<BlogPost>
|
|
) {}
|
|
|
|
async findAll({ page = 1, limit = 20 }: ListBlogPostsQueryDto): Promise<PaginatedResponse<BlogPostExtended>> {
|
|
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<BlogPostExtended> {
|
|
const entity = this.blogPostRepo.create({
|
|
title: '',
|
|
contentHtml: '',
|
|
isDraft: true
|
|
});
|
|
|
|
await this.blogPostRepo.insert(entity);
|
|
|
|
return this.findOne(entity.id);
|
|
}
|
|
|
|
async findOne(id: string): Promise<BlogPostExtended> {
|
|
const entity = await this.findEntity(id);
|
|
|
|
return this.toPostExtended(entity);
|
|
}
|
|
|
|
async update(id: string, { title, contentHtml, isDraft }: UpdateBlogPostDto): Promise<BlogPostExtended> {
|
|
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<BlogPostExtended> {
|
|
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<BlogPostExtended> {
|
|
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<void> {
|
|
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<BlogPost> {
|
|
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<void> {
|
|
if (!storageKey) {
|
|
return;
|
|
}
|
|
|
|
const path = resolveBlogImagePath(storageKey);
|
|
|
|
await removeFileFromDisk(path, BlogPostsService.name);
|
|
}
|
|
}
|