init
Gitea Actions Demo / Deploy (push) Failing after 18s

This commit is contained in:
vadim committed 2026-09-24 10:34:27 +03:00
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;
}
}