214 lines
4.7 KiB
TypeScript
214 lines
4.7 KiB
TypeScript
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;
|
|
}
|