Ship legacy migrate APIs, portfolio/blog old-id schema, and admin migrate/purge flows.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
7244b70e90
commit
4598add88c
@@ -60,9 +60,20 @@ export class ListPublicPortfoliosDto {
|
||||
}
|
||||
|
||||
export class CreatePortfolioDto {
|
||||
/** @deprecated Prefer titleFa — kept for older clients. */
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
title!: string;
|
||||
title?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
titleFa?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
titleEn?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@@ -107,11 +118,21 @@ export class CreatePortfolioDto {
|
||||
}
|
||||
|
||||
export class UpdatePortfolioDto {
|
||||
/** @deprecated Prefer titleFa — kept for older clients. */
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
title?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
titleFa?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
titleEn?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
abstract?: string | null;
|
||||
|
||||
@@ -109,9 +109,15 @@ export class PortfoliosService {
|
||||
const businessId = BigInt(businessIdRaw);
|
||||
await this.assertPermission(businessId, actor.id, 'portfolios.create');
|
||||
|
||||
const titleFa = this.resolveTitleFa(dto.titleFa, dto.title);
|
||||
const titleEn = dto.titleEn?.trim() || null;
|
||||
if (!titleFa) {
|
||||
throw new BadRequestException('titleFa is required');
|
||||
}
|
||||
|
||||
const slug = await this.ensureUniqueSlug(
|
||||
businessId,
|
||||
dto.slug ?? slugify(dto.title),
|
||||
dto.slug ?? slugify(titleEn || titleFa),
|
||||
);
|
||||
|
||||
const status = dto.status ?? ContentStatus.draft;
|
||||
@@ -139,7 +145,9 @@ export class PortfoliosService {
|
||||
const portfolio = await tx.portfolios.create({
|
||||
data: {
|
||||
business_id: businessId,
|
||||
title: dto.title.trim(),
|
||||
title: titleFa,
|
||||
title_fa: titleFa,
|
||||
title_en: titleEn,
|
||||
slug,
|
||||
description: dto.abstract?.trim() || null,
|
||||
content: this.buildContent(dto.mainTextHtml) as Prisma.InputJsonValue,
|
||||
@@ -198,12 +206,30 @@ export class PortfoliosService {
|
||||
}
|
||||
|
||||
let slug = existing.slug;
|
||||
const nextTitleFaRaw =
|
||||
dto.titleFa !== undefined || dto.title !== undefined
|
||||
? this.resolveTitleFa(dto.titleFa, dto.title)
|
||||
: undefined;
|
||||
if (
|
||||
(dto.titleFa !== undefined || dto.title !== undefined) &&
|
||||
!nextTitleFaRaw
|
||||
) {
|
||||
throw new BadRequestException('titleFa is required');
|
||||
}
|
||||
const nextTitleFa = nextTitleFaRaw ?? undefined;
|
||||
if (dto.slug) {
|
||||
slug = await this.ensureUniqueSlug(businessId, dto.slug, portfolioId);
|
||||
} else if (dto.title && dto.title !== existing.title) {
|
||||
} else if (
|
||||
nextTitleFa &&
|
||||
nextTitleFa !== (existing.title_fa ?? existing.title)
|
||||
) {
|
||||
const slugSource =
|
||||
(dto.titleEn !== undefined
|
||||
? dto.titleEn?.trim() || null
|
||||
: existing.title_en) || nextTitleFa;
|
||||
slug = await this.ensureUniqueSlug(
|
||||
businessId,
|
||||
slugify(dto.title),
|
||||
slugify(slugSource),
|
||||
portfolioId,
|
||||
);
|
||||
}
|
||||
@@ -245,23 +271,37 @@ export class PortfoliosService {
|
||||
}
|
||||
|
||||
const updated = await this.prisma.$transaction(async (tx) => {
|
||||
const data: Prisma.portfoliosUncheckedUpdateInput = {
|
||||
slug,
|
||||
content: nextContent as Prisma.InputJsonValue,
|
||||
metadata: nextMetadata as Prisma.InputJsonValue,
|
||||
};
|
||||
if (nextTitleFa !== undefined) {
|
||||
data.title = nextTitleFa;
|
||||
data.title_fa = nextTitleFa;
|
||||
}
|
||||
if (dto.titleEn !== undefined) {
|
||||
data.title_en = dto.titleEn?.trim() || null;
|
||||
}
|
||||
if (dto.abstract !== undefined) {
|
||||
data.description = dto.abstract?.trim() || null;
|
||||
}
|
||||
if (dto.status !== undefined) {
|
||||
data.status = dto.status;
|
||||
}
|
||||
if (featuredMediaId !== undefined) {
|
||||
data.featured_media_id = featuredMediaId;
|
||||
}
|
||||
if (dto.sortOrder !== undefined) {
|
||||
data.sort_order = dto.sortOrder;
|
||||
}
|
||||
if (publishedAt !== undefined) {
|
||||
data.published_at = publishedAt;
|
||||
}
|
||||
|
||||
const portfolio = await tx.portfolios.update({
|
||||
where: { id: portfolioId },
|
||||
data: {
|
||||
...(dto.title !== undefined ? { title: dto.title.trim() } : {}),
|
||||
...(dto.abstract !== undefined
|
||||
? { description: dto.abstract?.trim() || null }
|
||||
: {}),
|
||||
...(dto.status !== undefined ? { status: dto.status } : {}),
|
||||
...(featuredMediaId !== undefined
|
||||
? { featured_media_id: featuredMediaId }
|
||||
: {}),
|
||||
...(dto.sortOrder !== undefined ? { sort_order: dto.sortOrder } : {}),
|
||||
...(publishedAt !== undefined ? { published_at: publishedAt } : {}),
|
||||
slug,
|
||||
content: nextContent as Prisma.InputJsonValue,
|
||||
metadata: nextMetadata as Prisma.InputJsonValue,
|
||||
},
|
||||
data,
|
||||
include: portfolioInclude,
|
||||
});
|
||||
|
||||
@@ -540,12 +580,36 @@ export class PortfoliosService {
|
||||
...(entityIds ? { id: { in: entityIds } } : {}),
|
||||
...(query.title?.trim()
|
||||
? {
|
||||
title: { contains: query.title.trim(), mode: 'insensitive' },
|
||||
OR: [
|
||||
{
|
||||
title: { contains: query.title.trim(), mode: 'insensitive' },
|
||||
},
|
||||
{
|
||||
title_fa: {
|
||||
contains: query.title.trim(),
|
||||
mode: 'insensitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
title_en: {
|
||||
contains: query.title.trim(),
|
||||
mode: 'insensitive',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
private resolveTitleFa(
|
||||
titleFa?: string | null,
|
||||
title?: string | null,
|
||||
): string | null {
|
||||
const value = (titleFa ?? title)?.trim() || '';
|
||||
return value.length >= 2 ? value : null;
|
||||
}
|
||||
|
||||
private async findPortfolioOrThrow(businessId: bigint, portfolioId: bigint) {
|
||||
const portfolio = await this.prisma.portfolios.findFirst({
|
||||
where: { id: portfolioId, business_id: businessId },
|
||||
@@ -619,7 +683,9 @@ export class PortfoliosService {
|
||||
return {
|
||||
id: portfolio.id.toString(),
|
||||
businessId: portfolio.business_id.toString(),
|
||||
title: portfolio.title,
|
||||
title: portfolio.title_fa?.trim() || portfolio.title,
|
||||
titleFa: portfolio.title_fa?.trim() || portfolio.title,
|
||||
titleEn: portfolio.title_en?.trim() || null,
|
||||
slug: portfolio.slug,
|
||||
abstract: portfolio.description ?? '',
|
||||
mainTextHtml: (content.html as string | undefined) ?? '',
|
||||
|
||||
Reference in New Issue
Block a user