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
+2 -1
View File
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { AuthModule } from '../auth/auth.module';
import { BrandsModule } from '../brands/brands.module';
import { CategoriesModule } from '../categories/categories.module';
import { MediaModule } from '../media/media.module';
import { SitemapModule } from '../sitemap/sitemap.module';
import { TenantModule } from '../tenant/tenant.module';
import { ProductAiService } from './product-ai.service';
@@ -11,7 +12,7 @@ import { ProductsController, PublicProductsController } from './products.control
import { ProductsService } from './products.service';
@Module({
imports: [AuthModule, BrandsModule, CategoriesModule, SitemapModule, TenantModule],
imports: [AuthModule, BrandsModule, CategoriesModule, MediaModule, SitemapModule, TenantModule],
controllers: [ProductsController, PublicProductsController],
providers: [
ProductsService,
+33
View File
@@ -8,6 +8,7 @@ import { ContentStatus, MediaEntityType, Prisma } from '@prisma/client';
import { AuthUser } from '../auth/auth.types';
import { PermissionsService } from '../auth/permissions.service';
import { BrandsService } from '../brands/brands.service';
import { MediaService } from '../media/media.service';
import { PrismaService } from '../prisma/prisma.service';
import { SitemapService } from '../sitemap/sitemap.service';
import { TenantService } from '../tenant/tenant.service';
@@ -54,6 +55,7 @@ export class ProductsService {
private readonly brands: BrandsService,
private readonly tenant: TenantService,
private readonly sitemap: SitemapService,
private readonly mediaService: MediaService,
) {}
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 product = await tx.product.update({
where: { id: productId },
@@ -440,6 +449,7 @@ export class ProductsService {
businessId,
dto.galleryMediaIds,
);
nextGalleryIds = galleryMediaIds;
await this.syncGalleryAttachments(
tx,
businessId,
@@ -453,6 +463,21 @@ export class ProductsService {
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 {
message: 'Product updated successfully',
product: await this.serializeProduct(updated),
@@ -472,6 +497,13 @@ export class ProductsService {
throw new NotFoundException('Product not found');
}
const mediaIds = await this.mediaService.allLinkedMediaIds(
businessId,
MediaEntityType.product,
productId,
existing.featuredMediaId,
);
await this.prisma.$transaction([
this.prisma.mediaAttachment.deleteMany({
where: {
@@ -491,6 +523,7 @@ export class ProductsService {
]);
await this.sitemap.invalidateForBusiness(businessId);
await this.mediaService.deleteUnused(businessId, mediaIds);
return { message: 'Product deleted successfully' };
}