comments implement
This commit is contained in:
1 parent
9e0bef2527
commit
4b6ecfbf5d
41 files changed
+1350
-13
No files matched your search
@@ -0,0 +1,129 @@
|
||||
import { CommentContent } from '../value-objects/comment-content.vo.js';
|
||||
import { CommentForbiddenException } from '../exceptions/comment-forbidden.exception.js';
|
||||
|
||||
interface CommentProps {
|
||||
id: string | null;
|
||||
content: CommentContent;
|
||||
issueId: number;
|
||||
/** null — автор удалил аккаунт */
|
||||
authorId: string | null;
|
||||
editedAt: Date | null;
|
||||
deletedAt: Date | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Комментарий к задаче. Удаление мягкое: запись остаётся (deletedAt),
|
||||
* чтобы в ленте можно было показать «комментарий удалён».
|
||||
*
|
||||
* `id` (uuid v7) генерируется базой, поэтому у нового комментария
|
||||
* он равен null до сохранения в репозитории.
|
||||
*/
|
||||
export class Comment {
|
||||
private constructor(private readonly props: CommentProps) {}
|
||||
|
||||
/** Фабричный метод для создания НОВОГО комментария */
|
||||
static create(params: { issueId: number; authorId: string; content: string }): Comment {
|
||||
const now = new Date();
|
||||
|
||||
return new Comment({
|
||||
id: null,
|
||||
content: CommentContent.create(params.content),
|
||||
issueId: params.issueId,
|
||||
authorId: params.authorId,
|
||||
editedAt: null,
|
||||
deletedAt: null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
}
|
||||
|
||||
/** Восстановление сущности из персистентного слоя */
|
||||
static reconstitute(props: {
|
||||
id: string;
|
||||
content: string;
|
||||
issueId: number;
|
||||
authorId: string | null;
|
||||
editedAt: Date | null;
|
||||
deletedAt: Date | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}): Comment {
|
||||
return new Comment({
|
||||
...props,
|
||||
content: CommentContent.create(props.content),
|
||||
});
|
||||
}
|
||||
|
||||
get id(): string | null {
|
||||
return this.props.id;
|
||||
}
|
||||
|
||||
get content(): string {
|
||||
return this.props.content.value;
|
||||
}
|
||||
|
||||
get issueId(): number {
|
||||
return this.props.issueId;
|
||||
}
|
||||
|
||||
get authorId(): string | null {
|
||||
return this.props.authorId;
|
||||
}
|
||||
|
||||
get editedAt(): Date | null {
|
||||
return this.props.editedAt;
|
||||
}
|
||||
|
||||
get deletedAt(): Date | null {
|
||||
return this.props.deletedAt;
|
||||
}
|
||||
|
||||
get isDeleted(): boolean {
|
||||
return this.props.deletedAt !== null;
|
||||
}
|
||||
|
||||
get createdAt(): Date {
|
||||
return this.props.createdAt;
|
||||
}
|
||||
|
||||
get updatedAt(): Date {
|
||||
return this.props.updatedAt;
|
||||
}
|
||||
|
||||
/** Меняет текст. Возвращает false, если текст не изменился */
|
||||
edit(content: string, userId: string): boolean {
|
||||
this.assertCanModify(userId);
|
||||
|
||||
const next = CommentContent.create(content);
|
||||
if (next.equals(this.props.content)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
this.props.content = next;
|
||||
this.props.editedAt = now;
|
||||
this.props.updatedAt = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
delete(userId: string): void {
|
||||
this.assertCanModify(userId);
|
||||
|
||||
const now = new Date();
|
||||
this.props.deletedAt = now;
|
||||
this.props.updatedAt = now;
|
||||
}
|
||||
|
||||
/** Править и удалять комментарий может только его автор, и только пока он не удалён */
|
||||
private assertCanModify(userId: string): void {
|
||||
if (this.isDeleted) {
|
||||
throw new Error('Комментарий уже удалён');
|
||||
}
|
||||
|
||||
if (this.props.authorId !== userId) {
|
||||
throw new CommentForbiddenException(this.props.id ?? 'new');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user