10 Commits

8 changed files with 47 additions and 15 deletions

View File

@@ -22,7 +22,7 @@ export class BlogController {
constructor(
private readonly blogService: BlogService,
private readonly userService: UserService,
) {}
) { }
@Get()
getBlogs() {
@@ -71,7 +71,13 @@ export class BlogController {
const blog = await this.blogService.findById(id);
if (!blog) throw new BadRequestException('文章不存在');
return await this.blogService.getComments(id);
/** @todo 对文章可读性进行更详细的判定 */
if (!blog.permissions.includes(BlogPermission.Public) && !blog.permissions.includes(BlogPermission.ByPassword)) {
throw new BadRequestException('文章不存在或未公开');
}
return await this.blogService.getComments(blog);
}
// 该接口允许匿名评论但仍需验证userId合法性
@@ -87,6 +93,10 @@ export class BlogController {
const blog = await this.blogService.findById(id);
if (!blog) throw new BadRequestException('文章不存在');
if (!blog.permissions.includes(BlogPermission.AllowComments)) {
throw new BadRequestException('作者关闭了该文章的评论功能');
}
const user = userId ? await this.userService.findById(userId) : null;
const ip = req.headers['x-forwarded-for'] || req.ip;

View File

@@ -2,4 +2,5 @@ export enum BlogPermission {
Public = 'Public',
ByPassword = 'ByPassword',
List = 'List',
AllowComments = 'AllowComments',
}

View File

@@ -13,7 +13,7 @@ export class BlogService {
private readonly blogRepository: Repository<Blog>,
@InjectRepository(BlogComment)
private readonly blogCommentRepository: Repository<BlogComment>,
) {}
) { }
async list(
option: {
@@ -95,24 +95,40 @@ export class BlogService {
await this.blogRepository.increment({ id }, 'viewCount', 1);
}
async getComments(blogId: string) {
const blog = await this.findById(blogId);
if (!blog) {
throw new Error('文章不存在');
}
return this.blogCommentRepository.find({
where: { blog },
async getComments(blog: Blog) {
const comments = await this.blogCommentRepository.find({
where: { blog: { id: blog.id } },
relations: ['user'],
order: {
createdAt: 'DESC',
},
});
return comments.map(comment => {
const { blog, user, ...rest } = comment;
return {
...rest,
user: user ? {
userId: user.userId,
username: user.username,
nickname: user.nickname,
} : null,
}
})
}
async createComment(comment: Partial<BlogComment>) {
const newComment = this.blogCommentRepository.create(comment);
return this.blogCommentRepository.save(newComment);
const savedComment = await this.blogCommentRepository.save(newComment, {});
const { blog, user, ...commentWithoutBlog } = savedComment;
return {
...commentWithoutBlog,
user: user ? {
userId: user.userId,
username: user.username,
nickname: user.nickname,
} : null,
};
}
hashPassword(password: string) {

View File

@@ -45,6 +45,6 @@ export class Blog {
password_hash: string | null;
// 关系
@OneToMany(() => BlogComment, (blog) => blog.id)
@OneToMany(() => BlogComment, (comment) => comment.blog)
comments: BlogComment[];
}

View File

@@ -45,7 +45,7 @@ export function BlogCommentTool({ blogId, onInsertComment, replayTarget, handleC
if ((error as { statusCode: number }).statusCode === 429) {
return toast.error('操作太频繁了,稍后再试吧')
}
toast.error('发布失败')
toast.error(`${(error as Error).message || '发布失败'}`)
}
}

View File

@@ -66,7 +66,7 @@ export default function Blog() {
img: ({ src }) => (
<PhotoProvider className="w-full">
<PhotoView src={src as string}>
<Image src={src as string} alt="加载失败" />
<Image src={src as string} width={0} height={0} style={{ width: '100%', height: 'auto' }} unoptimized alt="加载失败" />
</PhotoView>
</PhotoProvider>
),

View File

@@ -15,6 +15,10 @@ const blogPermissions = [
permission: BlogPermission.List,
localText: '显示在列表中',
},
{
permission: BlogPermission.AllowComments,
localText: '允许评论',
}
] as const;
interface BlogPermissionCheckBoxsProps {

View File

@@ -2,4 +2,5 @@ export enum BlogPermission {
Public = 'Public',
ByPassword = 'ByPassword',
List = 'List',
AllowComments = 'AllowComments',
}