Expose blog image URLs on admin API and fix storefront card layout.

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.
This commit is contained in:
2026-09-16 19:46:11 +02:00
parent 9c7b8470d3
commit 9cd7059928
5 changed files with 82 additions and 31 deletions
@@ -1,12 +1,13 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { resolveBlogImagePath } from '../../../config/uploadPaths';
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 {
@@ -15,17 +16,19 @@ export class BlogPostsService {
private readonly blogPostRepo: Repository<BlogPost>
) {}
async findAll({ page = 1, limit = 20 }: ListBlogPostsQueryDto): Promise<PaginatedResponse<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
});
return { items, total, page, limit };
const itemsExtended = items.map(post => this.toPostExtended(post));
return { items: itemsExtended, total, page, limit };
}
async createDraft(): Promise<BlogPost> {
async createDraft(): Promise<BlogPostExtended> {
const entity = this.blogPostRepo.create({
title: '',
contentHtml: '',
@@ -37,17 +40,13 @@ export class BlogPostsService {
return this.findOne(entity.id);
}
async findOne(id: string): Promise<BlogPost> {
const entity = await this.blogPostRepo.findOne({ where: { id } });
async findOne(id: string): Promise<BlogPostExtended> {
const entity = await this.findEntity(id);
if (!entity) {
throw new NotFoundException();
}
return entity;
return this.toPostExtended(entity);
}
async update(id: string, { title, contentHtml, isDraft }: UpdateBlogPostDto): Promise<BlogPost> {
async update(id: string, { title, contentHtml, isDraft }: UpdateBlogPostDto): Promise<BlogPostExtended> {
const exists = await this.blogPostRepo.exists({ where: { id } });
if (!exists) {
@@ -63,8 +62,8 @@ export class BlogPostsService {
return this.findOne(id);
}
async uploadThumbnail(id: string, filename: string): Promise<BlogPost> {
const post = await this.findOne(id);
async uploadThumbnail(id: string, filename: string): Promise<BlogPostExtended> {
const post = await this.findEntity(id);
const previousStorageKey = post.thumbnailStorageKey;
@@ -74,8 +73,8 @@ export class BlogPostsService {
return this.findOne(id);
}
async uploadBanner(id: string, filename: string): Promise<BlogPost> {
const post = await this.findOne(id);
async uploadBanner(id: string, filename: string): Promise<BlogPostExtended> {
const post = await this.findEntity(id);
const previousStorageKey = post.bannerStorageKey;
@@ -86,7 +85,7 @@ export class BlogPostsService {
}
async remove(id: string): Promise<void> {
const post = await this.findOne(id);
const post = await this.findEntity(id);
await this.blogPostRepo.delete(id);
@@ -96,6 +95,24 @@ export class BlogPostsService {
]);
}
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;