189 lines
5.7 KiB
TypeScript
189 lines
5.7 KiB
TypeScript
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository, IsNull } from 'typeorm';
|
|
import { ContentPiece, ContentStatus, ContentType } from '../entities/content-piece.entity';
|
|
import { CreateContentPieceDto, UpdateContentPieceDto } from '../dto';
|
|
import { TenantAwareService } from '@/shared/services/tenant-aware.service';
|
|
import { PaginationParams, PaginatedResult } from '@/shared/dto/pagination.dto';
|
|
|
|
@Injectable()
|
|
export class ContentPieceService extends TenantAwareService<ContentPiece> {
|
|
constructor(
|
|
@InjectRepository(ContentPiece)
|
|
private readonly contentPieceRepository: Repository<ContentPiece>,
|
|
) {
|
|
super(contentPieceRepository);
|
|
}
|
|
|
|
async findAllPaginated(
|
|
tenantId: string,
|
|
pagination: PaginationParams & {
|
|
projectId?: string;
|
|
type?: ContentType;
|
|
status?: ContentStatus;
|
|
search?: string;
|
|
},
|
|
): Promise<PaginatedResult<ContentPiece>> {
|
|
const {
|
|
page = 1,
|
|
limit = 20,
|
|
sortBy = 'created_at',
|
|
sortOrder = 'DESC',
|
|
projectId,
|
|
type,
|
|
status,
|
|
search,
|
|
} = pagination;
|
|
|
|
const queryBuilder = this.contentPieceRepository
|
|
.createQueryBuilder('content')
|
|
.leftJoinAndSelect('content.project', 'project')
|
|
.leftJoinAndSelect('content.assignee', 'assignee')
|
|
.where('content.tenant_id = :tenantId', { tenantId })
|
|
.andWhere('content.deleted_at IS NULL');
|
|
|
|
if (projectId) {
|
|
queryBuilder.andWhere('content.project_id = :projectId', { projectId });
|
|
}
|
|
|
|
if (type) {
|
|
queryBuilder.andWhere('content.type = :type', { type });
|
|
}
|
|
|
|
if (status) {
|
|
queryBuilder.andWhere('content.status = :status', { status });
|
|
}
|
|
|
|
if (search) {
|
|
queryBuilder.andWhere(
|
|
'(content.title ILIKE :search OR content.content ILIKE :search)',
|
|
{ search: `%${search}%` },
|
|
);
|
|
}
|
|
|
|
queryBuilder.orderBy(`content.${sortBy}`, sortOrder);
|
|
|
|
const total = await queryBuilder.getCount();
|
|
const totalPages = Math.ceil(total / limit);
|
|
|
|
queryBuilder.skip((page - 1) * limit).take(limit);
|
|
|
|
const data = await queryBuilder.getMany();
|
|
|
|
return {
|
|
data,
|
|
meta: {
|
|
total,
|
|
page,
|
|
limit,
|
|
totalPages,
|
|
hasNextPage: page < totalPages,
|
|
hasPreviousPage: page > 1,
|
|
},
|
|
};
|
|
}
|
|
|
|
async findById(tenantId: string, id: string): Promise<ContentPiece> {
|
|
const contentPiece = await this.contentPieceRepository.findOne({
|
|
where: { id, tenant_id: tenantId, deleted_at: IsNull() },
|
|
relations: ['project', 'assignee'],
|
|
});
|
|
|
|
if (!contentPiece) {
|
|
throw new NotFoundException(`Content piece with ID ${id} not found`);
|
|
}
|
|
|
|
return contentPiece;
|
|
}
|
|
|
|
async findByProject(tenantId: string, projectId: string): Promise<ContentPiece[]> {
|
|
return this.contentPieceRepository.find({
|
|
where: { tenant_id: tenantId, project_id: projectId, deleted_at: IsNull() },
|
|
relations: ['assignee'],
|
|
order: { created_at: 'DESC' },
|
|
});
|
|
}
|
|
|
|
async create(tenantId: string, dto: CreateContentPieceDto): Promise<ContentPiece> {
|
|
const contentPiece = this.contentPieceRepository.create({
|
|
...dto,
|
|
tenant_id: tenantId,
|
|
version: 1,
|
|
});
|
|
|
|
return this.contentPieceRepository.save(contentPiece);
|
|
}
|
|
|
|
async update(tenantId: string, id: string, dto: UpdateContentPieceDto): Promise<ContentPiece> {
|
|
const contentPiece = await this.findById(tenantId, id);
|
|
|
|
// Increment version on content changes
|
|
if (dto.content && dto.content !== contentPiece.content) {
|
|
contentPiece.version += 1;
|
|
}
|
|
|
|
Object.assign(contentPiece, dto);
|
|
|
|
return this.contentPieceRepository.save(contentPiece);
|
|
}
|
|
|
|
async updateStatus(tenantId: string, id: string, status: ContentStatus): Promise<ContentPiece> {
|
|
const contentPiece = await this.findById(tenantId, id);
|
|
contentPiece.status = status;
|
|
|
|
if (status === ContentStatus.PUBLISHED) {
|
|
contentPiece.published_at = new Date();
|
|
}
|
|
|
|
return this.contentPieceRepository.save(contentPiece);
|
|
}
|
|
|
|
async assignTo(tenantId: string, id: string, userId: string | null): Promise<ContentPiece> {
|
|
const contentPiece = await this.findById(tenantId, id);
|
|
contentPiece.assigned_to = userId;
|
|
return this.contentPieceRepository.save(contentPiece);
|
|
}
|
|
|
|
async schedule(tenantId: string, id: string, scheduledAt: Date): Promise<ContentPiece> {
|
|
const contentPiece = await this.findById(tenantId, id);
|
|
contentPiece.scheduled_at = scheduledAt;
|
|
contentPiece.status = ContentStatus.SCHEDULED;
|
|
return this.contentPieceRepository.save(contentPiece);
|
|
}
|
|
|
|
async publish(tenantId: string, id: string, publishedUrl?: string): Promise<ContentPiece> {
|
|
const contentPiece = await this.findById(tenantId, id);
|
|
contentPiece.status = ContentStatus.PUBLISHED;
|
|
contentPiece.published_at = new Date();
|
|
if (publishedUrl) {
|
|
contentPiece.published_url = publishedUrl;
|
|
}
|
|
return this.contentPieceRepository.save(contentPiece);
|
|
}
|
|
|
|
async softDelete(tenantId: string, id: string): Promise<void> {
|
|
const contentPiece = await this.findById(tenantId, id);
|
|
contentPiece.deleted_at = new Date();
|
|
await this.contentPieceRepository.save(contentPiece);
|
|
}
|
|
|
|
async duplicate(tenantId: string, id: string): Promise<ContentPiece> {
|
|
const original = await this.findById(tenantId, id);
|
|
|
|
const duplicate = this.contentPieceRepository.create({
|
|
...original,
|
|
id: undefined,
|
|
title: `${original.title} (Copy)`,
|
|
status: ContentStatus.IDEA,
|
|
published_at: null,
|
|
published_url: null,
|
|
scheduled_at: null,
|
|
version: 1,
|
|
created_at: undefined,
|
|
updated_at: undefined,
|
|
});
|
|
|
|
return this.contentPieceRepository.save(duplicate);
|
|
}
|
|
}
|