feat: Admin Area Blog and Blog Page
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { BlogPostEntity } from '../../database/entities/blog-post.entity';
|
||||
import { ApiError } from '../../common/errors/api-error';
|
||||
import { AdminBlogPost, CreatePostPayload, UpdatePostPayload } from './dto/blog.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AdminBlogService {
|
||||
constructor(
|
||||
@InjectRepository(BlogPostEntity)
|
||||
private readonly posts: Repository<BlogPostEntity>,
|
||||
) {}
|
||||
|
||||
async listAll(): Promise<AdminBlogPost[]> {
|
||||
const rows = await this.posts.find({ order: { updatedAt: 'DESC' } });
|
||||
return rows.map((r) => this.toView(r));
|
||||
}
|
||||
|
||||
async getById(id: string): Promise<AdminBlogPost> {
|
||||
const row = await this.posts.findOne({ where: { id } });
|
||||
if (!row) throw ApiError.notFound('Post not found');
|
||||
return this.toView(row);
|
||||
}
|
||||
|
||||
async create(payload: CreatePostPayload): Promise<AdminBlogPost> {
|
||||
const status = payload.status ?? 'draft';
|
||||
const row = this.posts.create({
|
||||
id: uuid(),
|
||||
title: payload.title.trim(),
|
||||
bodyMd: payload.bodyMd ?? '',
|
||||
status,
|
||||
publishedAt: status === 'published' ? new Date().toISOString() : null,
|
||||
});
|
||||
await this.posts.save(row);
|
||||
return this.toView(row);
|
||||
}
|
||||
|
||||
async update(id: string, payload: UpdatePostPayload): Promise<AdminBlogPost> {
|
||||
const row = await this.posts.findOne({ where: { id } });
|
||||
if (!row) throw ApiError.notFound('Post not found');
|
||||
|
||||
if (payload.title !== undefined) row.title = payload.title.trim();
|
||||
if (payload.bodyMd !== undefined) row.bodyMd = payload.bodyMd;
|
||||
|
||||
if (payload.status !== undefined) {
|
||||
if (payload.status === 'published') {
|
||||
row.status = 'published';
|
||||
// Only stamp publishedAt if this post has never been published before;
|
||||
// preserve the original publishedAt for re-publications.
|
||||
if (!row.publishedAt) {
|
||||
row.publishedAt = new Date().toISOString();
|
||||
}
|
||||
} else if (payload.status === 'draft') {
|
||||
row.status = 'draft';
|
||||
// preserve publishedAt so a later re-publish keeps the original publication timestamp.
|
||||
}
|
||||
}
|
||||
|
||||
await this.posts.save(row);
|
||||
return this.toView(row);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
const row = await this.posts.findOne({ where: { id } });
|
||||
if (!row) throw ApiError.notFound('Post not found');
|
||||
await this.posts.delete({ id });
|
||||
}
|
||||
|
||||
private toView(r: BlogPostEntity): AdminBlogPost {
|
||||
return {
|
||||
id: r.id,
|
||||
title: r.title,
|
||||
bodyMd: r.bodyMd ?? '',
|
||||
status: r.status,
|
||||
createdAt: r.createdAt,
|
||||
updatedAt: r.updatedAt,
|
||||
publishedAt: r.publishedAt ?? null,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user