first commit

This commit is contained in:
2026-10-03 13:40:26 +02:00
parent 732a5b5a16
commit 566f415fa7
555 changed files with 62704 additions and 0 deletions

View File

@@ -0,0 +1,239 @@
import { Field, ID, InputType, Int } from '@nestjs/graphql';
import {
IssueArea,
IssueLinkType,
IssuePriority,
IssueStatus,
IssueType,
ProjectRole,
} from '@task-stream/api/tracker/domain';
import {
ArrayMaxSize,
IsBoolean,
IsEnum,
IsInt,
IsISO8601,
IsOptional,
IsString,
IsUrl,
IsUUID,
Matches,
Max,
MaxLength,
Min,
MinLength,
} from 'class-validator';
@InputType()
export class CreateProjectInput {
@Field({ description: '2-10 chars, e.g. "TS" → tickets TS-1, TS-2...' })
@Matches(/^[A-Za-z][A-Za-z0-9]{1,9}$/)
key!: string;
@Field() @IsString() @MinLength(1) @MaxLength(120) name!: string;
@Field(() => String, { nullable: true })
@IsOptional()
@IsString()
@MaxLength(5000)
description?: string | null;
}
@InputType()
export class UpdateProjectInput {
@Field(() => ID) @IsUUID() projectId!: string;
@Field({ nullable: true })
@IsOptional()
@IsString()
@MinLength(1)
@MaxLength(120)
name?: string;
@Field(() => String, { nullable: true })
@IsOptional()
@IsString()
@MaxLength(5000)
description?: string | null;
}
@InputType()
export class ProjectMemberInput {
@Field(() => ID) @IsUUID() projectId!: string;
@Field(() => ID, { description: 'Pireon user id' })
@IsString()
userId!: string;
@Field(() => ProjectRole, { defaultValue: ProjectRole.MEMBER })
@IsEnum(ProjectRole)
role!: ProjectRole;
}
@InputType({ isAbstract: true })
abstract class IssueFieldsInput {
@Field(() => String, { nullable: true })
@IsOptional()
@IsString()
@MaxLength(50000)
description?: string | null;
@Field(() => IssueArea, { nullable: true })
@IsOptional()
@IsEnum(IssueArea)
area?: IssueArea | null;
@Field(() => IssuePriority, { nullable: true })
@IsOptional()
@IsEnum(IssuePriority)
priority?: IssuePriority;
@Field(() => [String], { nullable: true })
@IsOptional()
@ArrayMaxSize(20)
@IsString({ each: true })
@MaxLength(40, { each: true })
labels?: string[];
@Field(() => String, {
nullable: true,
description: 'Figma frame for UI changes',
})
@IsOptional()
@IsUrl({ protocols: ['https'] })
figmaUrl?: string | null;
@Field(() => Int, { nullable: true })
@IsOptional()
@IsInt()
@Min(0)
@Max(100)
storyPoints?: number | null;
@Field(() => String, { nullable: true }) @IsOptional() @IsISO8601() dueDate?:
| string
| null;
}
@InputType()
export class CreateIssueInput extends IssueFieldsInput {
@Field(() => ID) @IsUUID() projectId!: string;
@Field(() => IssueType) @IsEnum(IssueType) type!: IssueType;
@Field() @IsString() @MinLength(1) @MaxLength(255) title!: string;
@Field(() => ID, { nullable: true }) @IsOptional() @IsString() assigneeId?:
| string
| null;
@Field(() => ID, { nullable: true }) @IsOptional() @IsUUID() releaseId?:
| string
| null;
}
@InputType()
export class UpdateIssueInput extends IssueFieldsInput {
@Field(() => ID) @IsUUID() issueId!: string;
@Field(() => IssueType, { nullable: true })
@IsOptional()
@IsEnum(IssueType)
type?: IssueType;
@Field({ nullable: true })
@IsOptional()
@IsString()
@MinLength(1)
@MaxLength(255)
title?: string;
}
@InputType()
export class ChangeIssueStatusInput {
@Field(() => ID) @IsUUID() issueId!: string;
@Field(() => IssueStatus) @IsEnum(IssueStatus) status!: IssueStatus;
@Field(() => String, {
nullable: true,
description: 'Required for QA_FAILED and CANCELLED',
})
@IsOptional()
@IsString()
@MaxLength(5000)
reason?: string | null;
}
@InputType()
export class LinkIssuesInput {
@Field(() => ID) @IsUUID() issueId!: string;
@Field(() => IssueLinkType) @IsEnum(IssueLinkType) type!: IssueLinkType;
@Field(() => ID) @IsUUID() targetIssueId!: string;
}
@InputType()
export class IssueFilterInput {
@Field(() => ID) @IsUUID() projectId!: string;
@Field(() => [IssueStatus], { nullable: true })
@IsOptional()
@IsEnum(IssueStatus, { each: true })
statuses?: IssueStatus[];
@Field(() => [IssueType], { nullable: true })
@IsOptional()
@IsEnum(IssueType, { each: true })
types?: IssueType[];
@Field(() => [IssueArea], { nullable: true })
@IsOptional()
@IsEnum(IssueArea, { each: true })
areas?: IssueArea[];
@Field(() => [IssuePriority], { nullable: true })
@IsOptional()
@IsEnum(IssuePriority, { each: true })
priorities?: IssuePriority[];
@Field(() => ID, { nullable: true })
@IsOptional()
@IsString()
assigneeId?: string;
@Field({ nullable: true }) @IsOptional() @IsBoolean() unassigned?: boolean;
@Field(() => ID, { nullable: true })
@IsOptional()
@IsUUID()
releaseId?: string;
@Field(() => [String], { nullable: true })
@IsOptional()
@IsString({ each: true })
labels?: string[];
@Field({ nullable: true, description: 'Matches key, title or description' })
@IsOptional()
@IsString()
@MaxLength(200)
search?: string;
}
@InputType()
export class CreateReleaseInput {
@Field(() => ID) @IsUUID() projectId!: string;
@Field({ description: 'e.g. v1.4.0' })
@IsString()
@MinLength(1)
@MaxLength(60)
name!: string;
@Field(() => String, { nullable: true })
@IsOptional()
@IsString()
@MaxLength(5000)
description?: string | null;
@Field(() => String, { nullable: true })
@IsOptional()
@IsISO8601()
targetDate?: string | null;
}
@InputType()
export class UpdateReleaseInput {
@Field(() => ID) @IsUUID() releaseId!: string;
@Field({ nullable: true })
@IsOptional()
@IsString()
@MinLength(1)
@MaxLength(60)
name?: string;
@Field(() => String, { nullable: true })
@IsOptional()
@IsString()
@MaxLength(5000)
description?: string | null;
@Field(() => String, { nullable: true })
@IsOptional()
@IsISO8601()
targetDate?: string | null;
}