Fix e-payment settings payload, cart race, and media/content helpers.

Stop returning passwordSet (forbidNonWhitelisted), upsert carts safely, and harden related media/product/workshop flows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-25 16:41:00 +03:30
co-authored by Cursor
parent ad6f9dcf1d
commit 20cc0bbd78
12 changed files with 220 additions and 42 deletions
@@ -10,7 +10,6 @@ import { BusinessSettings } from './business-settings.types';
import { import {
mergeBusinessSettings, mergeBusinessSettings,
mergeEPaymentSettings, mergeEPaymentSettings,
mellatPasswordSet,
normalizeBusinessSettings, normalizeBusinessSettings,
normalizeEnabledBusinessModules, normalizeEnabledBusinessModules,
normalizeEPaymentSettings, normalizeEPaymentSettings,
@@ -241,23 +240,7 @@ export class BusinessSettingsService {
} }
private toClientSettings(settings: BusinessSettings) { private toClientSettings(settings: BusinessSettings) {
const sanitized = sanitizeBusinessSettingsForClient(settings); return sanitizeBusinessSettingsForClient(settings);
return {
...sanitized,
store: {
...sanitized.store,
ePayment: {
...sanitized.store.ePayment,
gateways: {
...sanitized.store.ePayment.gateways,
mellat: {
...sanitized.store.ePayment.gateways.mellat,
passwordSet: mellatPasswordSet(settings),
},
},
},
},
};
} }
private async assertPermission( private async assertPermission(
@@ -54,7 +54,7 @@ export type MellatGatewaySettings = {
enabled: boolean; enabled: boolean;
terminalId: string; terminalId: string;
username: string; username: string;
/** Stored secret — redacted in API responses; use passwordSet instead. */ /** Stored secret — never returned in API responses (always empty string). */
password: string; password: string;
}; };
@@ -249,8 +249,6 @@ export function sanitizeBusinessSettingsForClient(
mellat: { mellat: {
...mellat, ...mellat,
password: '', password: '',
// Clients treat non-empty passwordSet via a parallel field — see
// get/update response shaping in BusinessSettingsService.
}, },
}, },
}, },
@@ -258,10 +256,6 @@ export function sanitizeBusinessSettingsForClient(
}; };
} }
export function mellatPasswordSet(settings: BusinessSettings): boolean {
return settings.store.ePayment.gateways.mellat.password.length > 0;
}
/** Whether a gateway is enabled and has enough credentials to initiate. */ /** Whether a gateway is enabled and has enough credentials to initiate. */
export function isGatewayReady( export function isGatewayReady(
settings: EPaymentSettings, settings: EPaymentSettings,
+5 -10
View File
@@ -279,21 +279,16 @@ export class CartService {
} }
private async getOrCreateCart(businessId: bigint, userId: bigint) { private async getOrCreateCart(businessId: bigint, userId: bigint) {
const existing = await this.prisma.cart.findUnique({ // Concurrent GETs (e.g. React Strict Mode) can race on create; upsert is safe.
const cart = await this.prisma.cart.upsert({
where: { where: {
businessId_userId: { businessId, userId }, businessId_userId: { businessId, userId },
}, },
create: { businessId, userId },
update: {},
}); });
if (existing) { return this.loadCart(cart.id);
return this.loadCart(existing.id);
}
const created = await this.prisma.cart.create({
data: { businessId, userId },
});
return this.loadCart(created.id);
} }
private async loadCart(cartId: bigint): Promise<CartWithItems> { private async loadCart(cartId: bigint): Promise<CartWithItems> {
+54 -1
View File
@@ -5,7 +5,7 @@ import {
NotFoundException, NotFoundException,
} from '@nestjs/common'; } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { MediaType } from '@prisma/client'; import { MediaEntityType, MediaType } from '@prisma/client';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import * as path from 'path'; import * as path from 'path';
import sharp from 'sharp'; import sharp from 'sharp';
@@ -209,6 +209,7 @@ export class MediaService {
blogs, blogs,
videos, videos,
instructions, instructions,
workshops,
brands, brands,
userProducts, userProducts,
sliders, sliders,
@@ -235,6 +236,10 @@ export class MediaService {
where: { business_id: businessId, featured_media_id: { in: uniqueIds } }, where: { business_id: businessId, featured_media_id: { in: uniqueIds } },
select: { featured_media_id: true }, select: { featured_media_id: true },
}), }),
this.prisma.workshops.findMany({
where: { business_id: businessId, featured_media_id: { in: uniqueIds } },
select: { featured_media_id: true },
}),
this.prisma.brand.findMany({ this.prisma.brand.findMany({
where: { businessId, imageMediaId: { in: uniqueIds } }, where: { businessId, imageMediaId: { in: uniqueIds } },
select: { imageMediaId: true }, select: { imageMediaId: true },
@@ -274,6 +279,7 @@ export class MediaService {
for (const row of blogs) addId(row.featured_media_id); for (const row of blogs) addId(row.featured_media_id);
for (const row of videos) addId(row.featured_media_id); for (const row of videos) addId(row.featured_media_id);
for (const row of instructions) addId(row.featured_media_id); for (const row of instructions) addId(row.featured_media_id);
for (const row of workshops) addId(row.featured_media_id);
for (const row of brands) addId(row.imageMediaId); for (const row of brands) addId(row.imageMediaId);
for (const row of userProducts) addId(row.featuredMediaId); for (const row of userProducts) addId(row.featuredMediaId);
for (const row of sliders) addId(row.image_media_id); for (const row of sliders) addId(row.image_media_id);
@@ -304,6 +310,53 @@ export class MediaService {
return mediaRows.length; return mediaRows.length;
} }
async galleryMediaIds(
businessId: bigint,
entityType: MediaEntityType,
entityId: bigint,
): Promise<bigint[]> {
const rows = await this.prisma.mediaAttachment.findMany({
where: { businessId, entityType, entityId, isFeatured: false },
select: { mediaId: true },
});
return rows.map((row) => row.mediaId);
}
async allLinkedMediaIds(
businessId: bigint,
entityType: MediaEntityType,
entityId: bigint,
featuredMediaId?: bigint | null,
): Promise<bigint[]> {
const rows = await this.prisma.mediaAttachment.findMany({
where: { businessId, entityType, entityId },
select: { mediaId: true },
});
const ids = rows.map((row) => row.mediaId);
if (featuredMediaId != null) ids.push(featuredMediaId);
return ids;
}
idsNotIn(
previous: bigint[],
next: Array<bigint | null | undefined>,
): bigint[] {
const keep = new Set(
next
.filter((id): id is bigint => id != null)
.map((id) => id.toString()),
);
const seen = new Set<string>();
const dropped: bigint[] = [];
for (const id of previous) {
const key = id.toString();
if (keep.has(key) || seen.has(key)) continue;
seen.add(key);
dropped.push(id);
}
return dropped;
}
private async uploadOne( private async uploadOne(
businessId: bigint, businessId: bigint,
file: Express.Multer.File, file: Express.Multer.File,
+2 -1
View File
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { AuthModule } from '../auth/auth.module'; import { AuthModule } from '../auth/auth.module';
import { BusinessSettingsModule } from '../business-settings/business-settings.module'; import { BusinessSettingsModule } from '../business-settings/business-settings.module';
import { MediaModule } from '../media/media.module';
import { SitemapModule } from '../sitemap/sitemap.module'; import { SitemapModule } from '../sitemap/sitemap.module';
import { TenantModule } from '../tenant/tenant.module'; import { TenantModule } from '../tenant/tenant.module';
import { import {
@@ -10,7 +11,7 @@ import {
import { PortfoliosService } from './portfolios.service'; import { PortfoliosService } from './portfolios.service';
@Module({ @Module({
imports: [AuthModule, BusinessSettingsModule, SitemapModule, TenantModule], imports: [AuthModule, BusinessSettingsModule, MediaModule, SitemapModule, TenantModule],
controllers: [PortfoliosController, PublicPortfoliosController], controllers: [PortfoliosController, PublicPortfoliosController],
providers: [PortfoliosService], providers: [PortfoliosService],
}) })
+33
View File
@@ -12,6 +12,7 @@ import {
import { AuthUser } from '../auth/auth.types'; import { AuthUser } from '../auth/auth.types';
import { PermissionsService } from '../auth/permissions.service'; import { PermissionsService } from '../auth/permissions.service';
import { BusinessSettingsService } from '../business-settings/business-settings.service'; import { BusinessSettingsService } from '../business-settings/business-settings.service';
import { MediaService } from '../media/media.service';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { SitemapService } from '../sitemap/sitemap.service'; import { SitemapService } from '../sitemap/sitemap.service';
import { TenantService } from '../tenant/tenant.service'; import { TenantService } from '../tenant/tenant.service';
@@ -56,6 +57,7 @@ export class PortfoliosService {
private readonly tenant: TenantService, private readonly tenant: TenantService,
private readonly businessSettings: BusinessSettingsService, private readonly businessSettings: BusinessSettingsService,
private readonly sitemap: SitemapService, private readonly sitemap: SitemapService,
private readonly mediaService: MediaService,
) {} ) {}
async list(businessIdRaw: string, query: ListPortfoliosDto, actor: AuthUser) { async list(businessIdRaw: string, query: ListPortfoliosDto, actor: AuthUser) {
@@ -285,6 +287,13 @@ export class PortfoliosService {
? await this.nextSortOrder(businessId) ? await this.nextSortOrder(businessId)
: undefined; : undefined;
const previousGalleryIds = await this.mediaService.galleryMediaIds(
businessId,
MediaEntityType.portfolio,
portfolioId,
);
let nextGalleryIds: bigint[] | undefined;
const updated = await this.prisma.$transaction(async (tx) => { const updated = await this.prisma.$transaction(async (tx) => {
const data: Prisma.portfoliosUncheckedUpdateInput = { const data: Prisma.portfoliosUncheckedUpdateInput = {
slug, slug,
@@ -353,6 +362,7 @@ export class PortfoliosService {
businessId, businessId,
dto.galleryMediaIds, dto.galleryMediaIds,
); );
nextGalleryIds = galleryMediaIds;
await this.syncGalleryAttachments( await this.syncGalleryAttachments(
tx, tx,
businessId, businessId,
@@ -366,6 +376,21 @@ export class PortfoliosService {
await this.sitemap.invalidateForBusiness(businessId); await this.sitemap.invalidateForBusiness(businessId);
const dropped: bigint[] = [];
if (nextGalleryIds) {
dropped.push(
...this.mediaService.idsNotIn(previousGalleryIds, nextGalleryIds),
);
}
if (
featuredMediaId !== undefined &&
existing.featured_media_id != null &&
existing.featured_media_id !== featuredMediaId
) {
dropped.push(existing.featured_media_id);
}
await this.mediaService.deleteUnused(businessId, dropped);
return { return {
message: 'Portfolio updated successfully', message: 'Portfolio updated successfully',
portfolio: await this.serializePortfolio(updated), portfolio: await this.serializePortfolio(updated),
@@ -389,6 +414,13 @@ export class PortfoliosService {
throw new NotFoundException('Portfolio not found'); throw new NotFoundException('Portfolio not found');
} }
const mediaIds = await this.mediaService.allLinkedMediaIds(
businessId,
MediaEntityType.portfolio,
portfolioId,
existing.featured_media_id,
);
await this.prisma.$transaction([ await this.prisma.$transaction([
this.prisma.comment.deleteMany({ this.prisma.comment.deleteMany({
where: { where: {
@@ -415,6 +447,7 @@ export class PortfoliosService {
]); ]);
await this.sitemap.invalidateForBusiness(businessId); await this.sitemap.invalidateForBusiness(businessId);
await this.mediaService.deleteUnused(businessId, mediaIds);
return { message: 'Portfolio deleted successfully' }; return { message: 'Portfolio deleted successfully' };
} }
+2 -1
View File
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { AuthModule } from '../auth/auth.module'; import { AuthModule } from '../auth/auth.module';
import { BrandsModule } from '../brands/brands.module'; import { BrandsModule } from '../brands/brands.module';
import { CategoriesModule } from '../categories/categories.module'; import { CategoriesModule } from '../categories/categories.module';
import { MediaModule } from '../media/media.module';
import { SitemapModule } from '../sitemap/sitemap.module'; import { SitemapModule } from '../sitemap/sitemap.module';
import { TenantModule } from '../tenant/tenant.module'; import { TenantModule } from '../tenant/tenant.module';
import { ProductAiService } from './product-ai.service'; import { ProductAiService } from './product-ai.service';
@@ -11,7 +12,7 @@ import { ProductsController, PublicProductsController } from './products.control
import { ProductsService } from './products.service'; import { ProductsService } from './products.service';
@Module({ @Module({
imports: [AuthModule, BrandsModule, CategoriesModule, SitemapModule, TenantModule], imports: [AuthModule, BrandsModule, CategoriesModule, MediaModule, SitemapModule, TenantModule],
controllers: [ProductsController, PublicProductsController], controllers: [ProductsController, PublicProductsController],
providers: [ providers: [
ProductsService, ProductsService,
+33
View File
@@ -8,6 +8,7 @@ import { ContentStatus, MediaEntityType, Prisma } from '@prisma/client';
import { AuthUser } from '../auth/auth.types'; import { AuthUser } from '../auth/auth.types';
import { PermissionsService } from '../auth/permissions.service'; import { PermissionsService } from '../auth/permissions.service';
import { BrandsService } from '../brands/brands.service'; import { BrandsService } from '../brands/brands.service';
import { MediaService } from '../media/media.service';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { SitemapService } from '../sitemap/sitemap.service'; import { SitemapService } from '../sitemap/sitemap.service';
import { TenantService } from '../tenant/tenant.service'; import { TenantService } from '../tenant/tenant.service';
@@ -54,6 +55,7 @@ export class ProductsService {
private readonly brands: BrandsService, private readonly brands: BrandsService,
private readonly tenant: TenantService, private readonly tenant: TenantService,
private readonly sitemap: SitemapService, private readonly sitemap: SitemapService,
private readonly mediaService: MediaService,
) {} ) {}
async list(businessIdRaw: string, query: ListProductsDto, actor: AuthUser) { async list(businessIdRaw: string, query: ListProductsDto, actor: AuthUser) {
@@ -390,6 +392,13 @@ export class ProductsService {
} }
} }
const previousGalleryIds = await this.mediaService.galleryMediaIds(
businessId,
MediaEntityType.product,
productId,
);
let nextGalleryIds: bigint[] | undefined;
const updated = await this.prisma.$transaction(async (tx) => { const updated = await this.prisma.$transaction(async (tx) => {
const product = await tx.product.update({ const product = await tx.product.update({
where: { id: productId }, where: { id: productId },
@@ -440,6 +449,7 @@ export class ProductsService {
businessId, businessId,
dto.galleryMediaIds, dto.galleryMediaIds,
); );
nextGalleryIds = galleryMediaIds;
await this.syncGalleryAttachments( await this.syncGalleryAttachments(
tx, tx,
businessId, businessId,
@@ -453,6 +463,21 @@ export class ProductsService {
await this.sitemap.invalidateForBusiness(businessId); await this.sitemap.invalidateForBusiness(businessId);
const dropped: bigint[] = [];
if (nextGalleryIds) {
dropped.push(
...this.mediaService.idsNotIn(previousGalleryIds, nextGalleryIds),
);
}
if (
featuredMediaId !== undefined &&
existing.featuredMediaId != null &&
existing.featuredMediaId !== featuredMediaId
) {
dropped.push(existing.featuredMediaId);
}
await this.mediaService.deleteUnused(businessId, dropped);
return { return {
message: 'Product updated successfully', message: 'Product updated successfully',
product: await this.serializeProduct(updated), product: await this.serializeProduct(updated),
@@ -472,6 +497,13 @@ export class ProductsService {
throw new NotFoundException('Product not found'); throw new NotFoundException('Product not found');
} }
const mediaIds = await this.mediaService.allLinkedMediaIds(
businessId,
MediaEntityType.product,
productId,
existing.featuredMediaId,
);
await this.prisma.$transaction([ await this.prisma.$transaction([
this.prisma.mediaAttachment.deleteMany({ this.prisma.mediaAttachment.deleteMany({
where: { where: {
@@ -491,6 +523,7 @@ export class ProductsService {
]); ]);
await this.sitemap.invalidateForBusiness(businessId); await this.sitemap.invalidateForBusiness(businessId);
await this.mediaService.deleteUnused(businessId, mediaIds);
return { message: 'Product deleted successfully' }; return { message: 'Product deleted successfully' };
} }
+53 -3
View File
@@ -456,7 +456,13 @@ export class UserProductsService {
const existing = await this.prisma.userProduct.findFirst({ const existing = await this.prisma.userProduct.findFirst({
where: { id: productId, businessId, userId: actor.id }, where: { id: productId, businessId, userId: actor.id },
select: { id: true, title: true, slug: true, metadata: true }, select: {
id: true,
title: true,
slug: true,
metadata: true,
featuredMediaId: true,
},
}); });
if (!existing) { if (!existing) {
@@ -475,6 +481,7 @@ export class UserProductsService {
title: string; title: string;
slug: string; slug: string;
metadata: unknown; metadata: unknown;
featuredMediaId: bigint | null;
}, },
) { ) {
const titleFa = dto.titleFa.trim(); const titleFa = dto.titleFa.trim();
@@ -525,6 +532,12 @@ export class UserProductsService {
); );
} }
const previousGalleryIds = await this.mediaService.galleryMediaIds(
businessId,
MediaEntityType.user_product,
productId,
);
const updated = await this.prisma.$transaction(async (tx) => { const updated = await this.prisma.$transaction(async (tx) => {
const product = await tx.userProduct.update({ const product = await tx.userProduct.update({
where: { id: productId }, where: { id: productId },
@@ -594,6 +607,20 @@ export class UserProductsService {
select: { id: true, name: true, nameFa: true }, select: { id: true, name: true, nameFa: true },
}); });
const dropped: bigint[] = [];
if (galleryMediaIds !== null) {
dropped.push(
...this.mediaService.idsNotIn(previousGalleryIds, galleryMediaIds),
);
}
if (
existing.featuredMediaId != null &&
existing.featuredMediaId !== featuredMediaId
) {
dropped.push(existing.featuredMediaId);
}
await this.mediaService.deleteUnused(businessId, dropped);
return { return {
message: 'User product updated successfully', message: 'User product updated successfully',
product: this.serializeAdminListItem(updated, category ?? undefined), product: this.serializeAdminListItem(updated, category ?? undefined),
@@ -619,14 +646,28 @@ export class UserProductsService {
const product = await this.prisma.userProduct.findFirst({ const product = await this.prisma.userProduct.findFirst({
where: { id: productId, businessId, userId: actor.id }, where: { id: productId, businessId, userId: actor.id },
select: { id: true }, select: { id: true, featuredMediaId: true },
}); });
if (!product) { if (!product) {
throw new NotFoundException('User product not found'); throw new NotFoundException('User product not found');
} }
const mediaIds = await this.mediaService.allLinkedMediaIds(
businessId,
MediaEntityType.user_product,
productId,
product.featuredMediaId,
);
await this.prisma.$transaction(async (tx) => { await this.prisma.$transaction(async (tx) => {
await tx.mediaAttachment.deleteMany({
where: {
businessId,
entityType: MediaEntityType.user_product,
entityId: productId,
},
});
await tx.categoryAssignment.deleteMany({ await tx.categoryAssignment.deleteMany({
where: { where: {
businessId, businessId,
@@ -637,6 +678,8 @@ export class UserProductsService {
await tx.userProduct.delete({ where: { id: productId } }); await tx.userProduct.delete({ where: { id: productId } });
}); });
await this.mediaService.deleteUnused(businessId, mediaIds);
return { message: 'User product deleted successfully' }; return { message: 'User product deleted successfully' };
} }
@@ -784,7 +827,14 @@ export class UserProductsService {
const existing = await this.prisma.userProduct.findFirst({ const existing = await this.prisma.userProduct.findFirst({
where: { id: productId, businessId }, where: { id: productId, businessId },
select: { id: true, title: true, slug: true, metadata: true, userId: true }, select: {
id: true,
title: true,
slug: true,
metadata: true,
userId: true,
featuredMediaId: true,
},
}); });
if (!existing) { if (!existing) {
+2 -1
View File
@@ -1,5 +1,6 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { AuthModule } from '../auth/auth.module'; import { AuthModule } from '../auth/auth.module';
import { MediaModule } from '../media/media.module';
import { TenantModule } from '../tenant/tenant.module'; import { TenantModule } from '../tenant/tenant.module';
import { import {
PublicWorkshopsController, PublicWorkshopsController,
@@ -8,7 +9,7 @@ import {
import { WorkshopsService } from './workshops.service'; import { WorkshopsService } from './workshops.service';
@Module({ @Module({
imports: [AuthModule, TenantModule], imports: [AuthModule, MediaModule, TenantModule],
controllers: [WorkshopsController, PublicWorkshopsController], controllers: [WorkshopsController, PublicWorkshopsController],
providers: [WorkshopsService], providers: [WorkshopsService],
}) })
+34
View File
@@ -13,6 +13,7 @@ import {
} from '@prisma/client'; } from '@prisma/client';
import { AuthUser } from '../auth/auth.types'; import { AuthUser } from '../auth/auth.types';
import { PermissionsService } from '../auth/permissions.service'; import { PermissionsService } from '../auth/permissions.service';
import { MediaService } from '../media/media.service';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { TenantService } from '../tenant/tenant.service'; import { TenantService } from '../tenant/tenant.service';
import { import {
@@ -61,6 +62,7 @@ export class WorkshopsService {
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly permissions: PermissionsService, private readonly permissions: PermissionsService,
private readonly tenant: TenantService, private readonly tenant: TenantService,
private readonly mediaService: MediaService,
) {} ) {}
async list(businessIdRaw: string, query: ListWorkshopsDto, actor: AuthUser) { async list(businessIdRaw: string, query: ListWorkshopsDto, actor: AuthUser) {
@@ -278,6 +280,13 @@ export class WorkshopsService {
} }
} }
const previousGalleryIds = await this.mediaService.galleryMediaIds(
businessId,
MediaEntityType.workshop,
workshopId,
);
let nextGalleryIds: bigint[] | undefined;
const updated = await this.prisma.$transaction(async (tx) => { const updated = await this.prisma.$transaction(async (tx) => {
const workshop = await tx.workshops.update({ const workshop = await tx.workshops.update({
where: { id: workshopId }, where: { id: workshopId },
@@ -341,6 +350,7 @@ export class WorkshopsService {
businessId, businessId,
dto.galleryMediaIds, dto.galleryMediaIds,
); );
nextGalleryIds = galleryMediaIds;
await this.syncGalleryAttachments( await this.syncGalleryAttachments(
tx, tx,
businessId, businessId,
@@ -352,6 +362,21 @@ export class WorkshopsService {
return workshop; return workshop;
}); });
const dropped: bigint[] = [];
if (nextGalleryIds) {
dropped.push(
...this.mediaService.idsNotIn(previousGalleryIds, nextGalleryIds),
);
}
if (
featuredMediaId !== undefined &&
existing.featured_media_id != null &&
existing.featured_media_id !== featuredMediaId
) {
dropped.push(existing.featured_media_id);
}
await this.mediaService.deleteUnused(businessId, dropped);
return { return {
message: 'Workshop updated successfully', message: 'Workshop updated successfully',
workshop: await this.serializeWorkshop(updated), workshop: await this.serializeWorkshop(updated),
@@ -371,6 +396,13 @@ export class WorkshopsService {
throw new NotFoundException('Workshop not found'); throw new NotFoundException('Workshop not found');
} }
const mediaIds = await this.mediaService.allLinkedMediaIds(
businessId,
MediaEntityType.workshop,
workshopId,
existing.featured_media_id,
);
await this.prisma.$transaction([ await this.prisma.$transaction([
this.prisma.workshop_registrations.deleteMany({ this.prisma.workshop_registrations.deleteMany({
where: { workshop_id: workshopId, business_id: businessId }, where: { workshop_id: workshopId, business_id: businessId },
@@ -392,6 +424,8 @@ export class WorkshopsService {
this.prisma.workshops.delete({ where: { id: workshopId } }), this.prisma.workshops.delete({ where: { id: workshopId } }),
]); ]);
await this.mediaService.deleteUnused(businessId, mediaIds);
return { message: 'Workshop deleted successfully' }; return { message: 'Workshop deleted successfully' };
} }