170 lines
3.6 KiB
TypeScript
170 lines
3.6 KiB
TypeScript
import { COMPANY_VISIBILITY, HIERARCHY_GRANT_ROLES } from '@mosaicstack/db';
|
|
import { IsIn, IsOptional, IsString, IsUUID, Matches, MaxLength, MinLength } from 'class-validator';
|
|
|
|
/**
|
|
* Hierarchy command DTOs (contract 1 §5, contract 2 §4/§7).
|
|
*
|
|
* The global ValidationPipe runs with whitelist + forbidNonWhitelisted, so a
|
|
* payload field absent from these classes is a 400. That closure is itself
|
|
* contract surface:
|
|
* - CreateCompanyDto declares NO visibility field — creation is always
|
|
* private (contract 1 §5.5); a visibility argument is refused by the pipe.
|
|
* - CreateGrantDto declares NO teamId field — team grant subjects are
|
|
* suspended (contract 2 §1.4/§7.5); a team subject is refused by the pipe.
|
|
* Every class here must be registered in PIPE_GUARDED_DTOS so the boot-time
|
|
* assertion proves the pipe sees the decorators.
|
|
*/
|
|
|
|
const SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
const SLUG_MESSAGE = 'slug must be lowercase alphanumeric with interior hyphens';
|
|
|
|
export class CreateCompanyDto {
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
name!: string;
|
|
|
|
@IsString()
|
|
@MaxLength(100)
|
|
@Matches(SLUG_PATTERN, { message: SLUG_MESSAGE })
|
|
slug!: string;
|
|
|
|
/** Client-supplied idempotency key (REQ-AUD-001 replay); server-generated when absent. */
|
|
@IsOptional()
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
idempotencyKey?: string;
|
|
}
|
|
|
|
export class RenameNodeDto {
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
name!: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
idempotencyKey?: string;
|
|
}
|
|
|
|
export class ChangeCompanyVisibilityDto {
|
|
@IsIn(COMPANY_VISIBILITY)
|
|
visibility!: (typeof COMPANY_VISIBILITY)[number];
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
idempotencyKey?: string;
|
|
}
|
|
|
|
export class DeleteNodeDto {
|
|
@IsOptional()
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
idempotencyKey?: string;
|
|
}
|
|
|
|
export class CreateEstateDto {
|
|
@IsUUID()
|
|
companyId!: string;
|
|
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
name!: string;
|
|
|
|
@IsString()
|
|
@MaxLength(100)
|
|
@Matches(SLUG_PATTERN, { message: SLUG_MESSAGE })
|
|
slug!: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
idempotencyKey?: string;
|
|
}
|
|
|
|
export class CreatePlatformProjectDto {
|
|
@IsUUID()
|
|
estateId!: string;
|
|
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
name!: string;
|
|
|
|
@IsString()
|
|
@MaxLength(100)
|
|
@Matches(SLUG_PATTERN, { message: SLUG_MESSAGE })
|
|
slug!: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
idempotencyKey?: string;
|
|
}
|
|
|
|
export class TransferEstateDto {
|
|
@IsUUID()
|
|
destinationCompanyId!: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
idempotencyKey?: string;
|
|
}
|
|
|
|
export class TransferPlatformProjectDto {
|
|
@IsUUID()
|
|
destinationEstateId!: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
idempotencyKey?: string;
|
|
}
|
|
|
|
export class CreateGrantDto {
|
|
/** Subject user (better-auth text id). No teamId field — see module doc. */
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
userId!: string;
|
|
|
|
@IsIn(['company', 'estate', 'platform_project'])
|
|
targetKind!: 'company' | 'estate' | 'platform_project';
|
|
|
|
@IsUUID()
|
|
targetId!: string;
|
|
|
|
/** Bare vocabulary on requests; responses and audit events are namespaced (§4.5). */
|
|
@IsIn(HIERARCHY_GRANT_ROLES)
|
|
role!: (typeof HIERARCHY_GRANT_ROLES)[number];
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
idempotencyKey?: string;
|
|
}
|
|
|
|
export class ChangeGrantDto {
|
|
@IsIn(HIERARCHY_GRANT_ROLES)
|
|
role!: (typeof HIERARCHY_GRANT_ROLES)[number];
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(255)
|
|
idempotencyKey?: string;
|
|
}
|