10 Commits

8 changed files with 47 additions and 15 deletions

View File

@@ -22,7 +22,7 @@ export class BlogController {
constructor( constructor(
private readonly blogService: BlogService, private readonly blogService: BlogService,
private readonly userService: UserService, private readonly userService: UserService,
) {} ) { }
@Get() @Get()
getBlogs() { getBlogs() {
@@ -71,7 +71,13 @@ export class BlogController {
const blog = await this.blogService.findById(id); const blog = await this.blogService.findById(id);
if (!blog) throw new BadRequestException('文章不存在'); 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合法性 // 该接口允许匿名评论但仍需验证userId合法性
@@ -87,6 +93,10 @@ export class BlogController {
const blog = await this.blogService.findById(id); const blog = await this.blogService.findById(id);
if (!blog) throw new BadRequestException('文章不存在'); if (!blog) throw new BadRequestException('文章不存在');
if (!blog.permissions.includes(BlogPermission.AllowComments)) {
throw new BadRequestException('作者关闭了该文章的评论功能');
}
const user = userId ? await this.userService.findById(userId) : null; const user = userId ? await this.userService.findById(userId) : null;
const ip = req.headers['x-forwarded-for'] || req.ip; const ip = req.headers['x-forwarded-for'] || req.ip;

View File

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

View File

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

View File

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

View File

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

View File

@@ -66,7 +66,7 @@ export default function Blog() {
img: ({ src }) => ( img: ({ src }) => (
<PhotoProvider className="w-full"> <PhotoProvider className="w-full">
<PhotoView src={src as string}> <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> </PhotoView>
</PhotoProvider> </PhotoProvider>
), ),

View File

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

View File

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