import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsString, IsNotEmpty, IsOptional, IsArray, IsBoolean, IsEnum, IsDateString, MaxLength, MinLength, Matches, } from 'class-validator'; import { BlogPostStatus, type BlogPostCategory } from '@domain/entities/blog-post.entity'; const CATEGORIES: BlogPostCategory[] = ['industry', 'technology', 'guides', 'news']; export class CreateBlogPostDto { @ApiProperty() @IsString() @IsNotEmpty() @MaxLength(255) title: string; @ApiProperty({ description: 'URL-friendly slug, e.g. "my-article"' }) @IsString() @IsNotEmpty() @MaxLength(255) @Matches(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { message: 'Slug must be lowercase alphanumeric with hyphens', }) slug: string; @ApiProperty() @IsString() @IsNotEmpty() @MinLength(10) excerpt: string; @ApiProperty() @IsString() @IsNotEmpty() content: string; @ApiPropertyOptional() @IsOptional() @IsString() @MaxLength(500) coverImageUrl?: string; @ApiProperty({ enum: CATEGORIES }) @IsEnum(CATEGORIES) category: BlogPostCategory; @ApiPropertyOptional({ type: [String] }) @IsOptional() @IsArray() @IsString({ each: true }) tags?: string[]; @ApiProperty() @IsString() @IsNotEmpty() @MaxLength(255) authorName: string; @ApiPropertyOptional({ description: 'ISO date string for scheduled publication' }) @IsOptional() @IsDateString() scheduledAt?: string; @ApiPropertyOptional({ description: 'SEO meta title (50-60 chars recommended)' }) @IsOptional() @IsString() @MaxLength(255) metaTitle?: string; @ApiPropertyOptional({ description: 'SEO meta description (150-160 chars recommended)' }) @IsOptional() @IsString() @MaxLength(500) metaDescription?: string; @ApiPropertyOptional({ description: 'Primary SEO keyword' }) @IsOptional() @IsString() @MaxLength(255) primaryKeyword?: string; @ApiPropertyOptional({ type: [String], description: 'Secondary SEO keywords' }) @IsOptional() @IsArray() @IsString({ each: true }) secondaryKeywords?: string[]; } export class UpdateBlogPostDto { @ApiPropertyOptional() @IsOptional() @IsString() @IsNotEmpty() @MaxLength(255) title?: string; @ApiPropertyOptional() @IsOptional() @IsString() @MaxLength(255) @Matches(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, { message: 'Slug must be lowercase alphanumeric with hyphens', }) slug?: string; @ApiPropertyOptional() @IsOptional() @IsString() excerpt?: string; @ApiPropertyOptional() @IsOptional() @IsString() content?: string; @ApiPropertyOptional() @IsOptional() @IsString() @MaxLength(500) coverImageUrl?: string; @ApiPropertyOptional({ enum: CATEGORIES }) @IsOptional() @IsEnum(CATEGORIES) category?: BlogPostCategory; @ApiPropertyOptional({ type: [String] }) @IsOptional() @IsArray() @IsString({ each: true }) tags?: string[]; @ApiPropertyOptional() @IsOptional() @IsString() @MaxLength(255) authorName?: string; @ApiPropertyOptional({ enum: BlogPostStatus }) @IsOptional() @IsEnum(BlogPostStatus) status?: BlogPostStatus; @ApiPropertyOptional() @IsOptional() @IsBoolean() isFeatured?: boolean; @ApiPropertyOptional({ description: 'ISO date string for scheduled publication' }) @IsOptional() @IsDateString() scheduledAt?: string; @ApiPropertyOptional() @IsOptional() @IsString() @MaxLength(255) metaTitle?: string; @ApiPropertyOptional() @IsOptional() @IsString() @MaxLength(500) metaDescription?: string; @ApiPropertyOptional() @IsOptional() @IsString() @MaxLength(255) primaryKeyword?: string; @ApiPropertyOptional({ type: [String] }) @IsOptional() @IsArray() @IsString({ each: true }) secondaryKeywords?: string[]; } export class BlogPostResponseDto { @ApiProperty() id: string; @ApiProperty() title: string; @ApiProperty() slug: string; @ApiProperty() excerpt: string; @ApiProperty() content: string; @ApiPropertyOptional() coverImageUrl?: string; @ApiProperty() category: string; @ApiProperty({ type: [String] }) tags: string[]; @ApiProperty() authorName: string; @ApiProperty({ enum: BlogPostStatus }) status: BlogPostStatus; @ApiProperty() isFeatured: boolean; @ApiPropertyOptional() publishedAt?: Date; @ApiPropertyOptional() metaTitle?: string; @ApiPropertyOptional() metaDescription?: string; @ApiPropertyOptional() primaryKeyword?: string; @ApiProperty({ type: [String] }) secondaryKeywords: string[]; @ApiProperty() createdAt: Date; @ApiProperty() updatedAt: Date; } export class BlogPostListResponseDto { @ApiProperty({ type: [BlogPostResponseDto] }) posts: BlogPostResponseDto[]; @ApiProperty() total: number; @ApiProperty() limit: number; @ApiProperty() offset: number; }