commit
7b999a670c
243 files changed
+24818
No files matched your search
@@ -0,0 +1,85 @@
|
||||
import { ProjectName } from '../value-objects/project-name.vo.js';
|
||||
|
||||
interface ProjectProps {
|
||||
id: string | null;
|
||||
key: string;
|
||||
name: ProjectName;
|
||||
ownerId: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Доменная сущность. Никогда не импортирует ничего из infrastructure/
|
||||
* presentation — только чистая бизнес-логика и инварианты.
|
||||
*
|
||||
* `id` — UUID, генерируется на уровне Prisma при сохранении, поэтому
|
||||
* у нового проекта он равен null до сохранения в репозитории.
|
||||
*/
|
||||
export class Project {
|
||||
private constructor(private readonly props: ProjectProps) {}
|
||||
|
||||
/** Фабричный метод для создания НОВОГО проекта */
|
||||
static create(params: { key: string; name: string; ownerId: string }): Project {
|
||||
return new Project({
|
||||
id: null,
|
||||
key: params.key,
|
||||
name: ProjectName.create(params.name),
|
||||
ownerId: params.ownerId,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
/** Восстановление сущности из персистентного слоя */
|
||||
static reconstitute(props: {
|
||||
id: string;
|
||||
key: string;
|
||||
name: string;
|
||||
ownerId: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}): Project {
|
||||
return new Project({
|
||||
id: props.id,
|
||||
key: props.key,
|
||||
name: ProjectName.create(props.name),
|
||||
ownerId: props.ownerId,
|
||||
createdAt: props.createdAt,
|
||||
updatedAt: props.updatedAt,
|
||||
});
|
||||
}
|
||||
|
||||
get id(): string | null {
|
||||
return this.props.id;
|
||||
}
|
||||
|
||||
get key(): string {
|
||||
return this.props.key;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.props.name.value;
|
||||
}
|
||||
|
||||
get ownerId(): string {
|
||||
return this.props.ownerId;
|
||||
}
|
||||
|
||||
get createdAt(): Date {
|
||||
return this.props.createdAt;
|
||||
}
|
||||
|
||||
get updatedAt(): Date {
|
||||
return this.props.updatedAt;
|
||||
}
|
||||
|
||||
updateName(name: string): void {
|
||||
this.props.name = ProjectName.create(name);
|
||||
this.touch();
|
||||
}
|
||||
|
||||
private touch(): void {
|
||||
this.props.updatedAt = new Date();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export class ProjectAccessDeniedException extends Error {
|
||||
constructor(id: string) {
|
||||
super(`Недостаточно прав для изменения проекта с id=${id}`);
|
||||
this.name = 'ProjectAccessDeniedException';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export class ProjectKeyAlreadyExistsException extends Error {
|
||||
constructor(key: string) {
|
||||
super(`Проект с ключом "${key}" уже существует`);
|
||||
this.name = 'ProjectKeyAlreadyExistsException';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export class ProjectNotFoundException extends Error {
|
||||
constructor(id: string) {
|
||||
super(`Проект с id=${id} не найден`);
|
||||
this.name = 'ProjectNotFoundException';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Project } from '../entities/project.entity.js';
|
||||
import { PaginationMeta } from '../../../../common/interfaces/pagination-meta.interface.js';
|
||||
|
||||
/** DI-токен, т.к. интерфейсы стираются при компиляции TS */
|
||||
export const PROJECT_REPOSITORY = Symbol('PROJECT_REPOSITORY');
|
||||
|
||||
export interface IProjectRepository {
|
||||
/** Проект виден владельцу и участникам проекта (ProjectMember) */
|
||||
findById(id: string, userId: string): Promise<Project | null>;
|
||||
findAll(params: {
|
||||
limit: number;
|
||||
page: number;
|
||||
userId: string;
|
||||
}): Promise<{ list: Project[]; meta: PaginationMeta }>;
|
||||
create(project: Project): Promise<Project>;
|
||||
update(project: Project): Promise<void>;
|
||||
delete(id: string): Promise<void>;
|
||||
/** Добавляет пользователя в участники проекта, если он ещё не владелец/участник */
|
||||
addMember(projectId: string, userId: string): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
export class ProjectName {
|
||||
private static readonly MAX_LENGTH = 100;
|
||||
|
||||
private constructor(private readonly _value: string) {}
|
||||
|
||||
static create(value: string): ProjectName {
|
||||
const trimmed = value?.trim();
|
||||
|
||||
if (!trimmed) {
|
||||
throw new Error('Название проекта не может быть пустым');
|
||||
}
|
||||
|
||||
if (trimmed.length > ProjectName.MAX_LENGTH) {
|
||||
throw new Error(`Название проекта не может превышать ${ProjectName.MAX_LENGTH} символов`);
|
||||
}
|
||||
|
||||
return new ProjectName(trimmed);
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
equals(other: ProjectName): boolean {
|
||||
return this._value === other.value;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user