fix bug of save imageUrls

This commit is contained in:
2026-10-01 11:39:04 +03:30
parent 1e6787abdd
commit e1395bc5b0
4 changed files with 129 additions and 23 deletions
+22
View File
@@ -38,6 +38,28 @@ describe('product image separation', () => {
]);
});
it('recovers color name from product.color when validation stripped nested fields', () => {
const db = serializeColorsJsonForDb(
[{ imageUrl: 'data:image/jpeg;base64,xyz' }],
'قرمز شرابی',
);
expect(db).toEqual([
{
name: 'قرمز شرابی',
imageUrl: 'data:image/jpeg;base64,xyz',
imageUrls: ['data:image/jpeg;base64,xyz'],
},
]);
const entries = parseColorEntries(db, 'قرمز شرابی');
expect(entries[0]?.imageUrls).toEqual(['data:image/jpeg;base64,xyz']);
expect(entries.map((e) => e.name)).toEqual(['قرمز شرابی']);
});
it('falls back to product.color when colorsJson rows are empty objects', () => {
const entries = parseColorEntries([{}], 'قرمز شرابی');
expect(entries).toEqual([{ name: 'قرمز شرابی', imageUrls: [] }]);
});
it('does not merge color images into general list when only imageUrlsJson is set', () => {
const entries = parseColorEntries([{ name: 'Red', imageUrl: 'https://cdn/r.jpg' }]);
expect(parseGeneralImageUrls(['https://cdn/g.jpg'], entries)).toEqual([
+12 -4
View File
@@ -28,8 +28,8 @@ export function parseColorEntries(
return name ? [{ name, imageUrls: [] }] : [];
}
return colorsJson
.map((item): ProductColorEntry | null => {
const parsed = colorsJson
.map((item, index): ProductColorEntry | null => {
if (typeof item === 'string') {
const name = item.trim();
return name ? { name, imageUrls: [] } : null;
@@ -40,8 +40,7 @@ export function parseColorEntries(
imageUrl?: unknown;
imageUrls?: unknown;
};
const name = String(raw.name ?? '').trim();
if (!name) return null;
let name = String(raw.name ?? '').trim();
const fromArray = normalizeUrlList(raw.imageUrls);
const legacy =
typeof raw.imageUrl === 'string' && raw.imageUrl.trim()
@@ -55,12 +54,21 @@ export function parseColorEntries(
: legacy
? [legacy]
: [];
if (!name && imageUrls.length > 0) {
name = fallbackColor?.trim() || `رنگ ${index + 1}`;
}
if (!name) return null;
const imageUrl = imageUrls[0];
return { name, imageUrl, imageUrls };
}
return null;
})
.filter((entry): entry is ProductColorEntry => Boolean(entry));
if (parsed.length > 0) return parsed;
const fallbackName = fallbackColor?.trim();
return fallbackName ? [{ name: fallbackName, imageUrls: [] }] : [];
}
/** تصاویر عمومی محصول — فقط از imageUrlsJson، بدون merge با رنگ‌ها */
+59 -16
View File
@@ -50,22 +50,65 @@ export function transformColorsJson(value: unknown): ProductColorJsonItemDto[] |
return out;
}
/** ذخیره در Prisma — imageUrl + imageUrls برای هر رنگ */
export function serializeColorsJsonForDb(raw: unknown): unknown[] | undefined {
const entries = transformColorsJson(raw);
if (entries === undefined) return undefined;
return entries.map((entry) => {
const merged: string[] = [];
const push = (u?: string) => {
const t = u?.trim();
if (t && !merged.includes(t)) merged.push(t);
};
for (const u of entry.imageUrls ?? []) push(u);
push(entry.imageUrl);
function mergeColorImageUrls(
imageUrls?: string[],
imageUrl?: string,
): string[] {
const merged: string[] = [];
const push = (u?: string) => {
const t = u?.trim();
if (t && !merged.includes(t)) merged.push(t);
};
for (const u of imageUrls ?? []) push(u);
push(imageUrl);
return merged;
}
/** ذخیره در Prisma — imageUrl + imageUrls برای هر رنگ (از body خام یا DTO) */
export function serializeColorsJsonForDb(
raw: unknown,
fallbackColor?: string,
): unknown[] | undefined {
if (raw === undefined || raw === null) return undefined;
if (!Array.isArray(raw)) return undefined;
const colorFallback = fallbackColor?.trim() || '';
const out: Array<{ name: string; imageUrl?: string; imageUrls?: string[] }> =
[];
for (let i = 0; i < raw.length; i++) {
const item = raw[i];
if (typeof item === 'string') {
const name = item.trim();
if (name) out.push({ name, imageUrls: [] });
continue;
}
if (!item || typeof item !== 'object') continue;
const o = item as Record<string, unknown>;
let name = String(o.name ?? '').trim();
const imageUrls = Array.isArray(o.imageUrls)
? o.imageUrls
.filter((u): u is string => typeof u === 'string' && u.trim().length > 0)
.map((u) => u.trim())
: undefined;
const imageUrl =
typeof o.imageUrl === 'string' && o.imageUrl.trim()
? o.imageUrl.trim()
: undefined;
const merged = mergeColorImageUrls(imageUrls, imageUrl);
if (!name && merged.length > 0) {
name = colorFallback || `رنگ ${i + 1}`;
}
if (!name) continue;
const first = merged[0];
return {
name: entry.name,
out.push({
name,
...(first ? { imageUrl: first, imageUrls: merged } : {}),
};
});
});
}
return out;
}
+36 -3
View File
@@ -76,13 +76,41 @@ export class ProductsService {
return false;
}
private colorsJsonHasPayload(raw: unknown): boolean {
if (!Array.isArray(raw) || raw.length === 0) return false;
return raw.some((item) => {
if (typeof item === 'string') return item.trim().length > 0;
if (!item || typeof item !== 'object') return false;
const o = item as Record<string, unknown>;
if (String(o.name ?? '').trim()) return true;
if (typeof o.imageUrl === 'string' && o.imageUrl.trim()) return true;
return (
Array.isArray(o.imageUrls) &&
o.imageUrls.some((u) => typeof u === 'string' && u.trim().length > 0)
);
});
}
private colorsJsonSourceForWrite(
preserved: unknown | undefined,
fromDto: unknown | undefined,
): unknown | undefined {
if (preserved !== undefined && this.colorsJsonHasPayload(preserved)) {
return preserved;
}
if (fromDto !== undefined) return fromDto;
if (preserved !== undefined) return preserved;
return undefined;
}
private colorsJsonForWrite(
preserved: unknown | undefined,
fromDto: unknown | undefined,
fallbackColor?: string,
): Prisma.InputJsonValue | undefined {
const source = preserved !== undefined ? preserved : fromDto;
const source = this.colorsJsonSourceForWrite(preserved, fromDto);
if (source === undefined) return undefined;
const serialized = serializeColorsJsonForDb(source);
const serialized = serializeColorsJsonForDb(source, fallbackColor);
if (serialized === undefined) return undefined;
return serialized as Prisma.InputJsonValue;
}
@@ -118,6 +146,7 @@ export class ProductsService {
colorsJson: this.colorsJsonForWrite(
preservedColorsJson,
dto.colorsJson,
dto.color,
),
imageUrlsJson: (dto.imageUrlsJson ?? undefined) as Prisma.InputJsonValue | undefined,
tagsJson: dto.tagsJson ?? undefined,
@@ -204,7 +233,11 @@ export class ProductsService {
colorsJson:
dto.colorsJson === undefined && preservedColorsJson === undefined
? undefined
: this.colorsJsonForWrite(preservedColorsJson, dto.colorsJson),
: this.colorsJsonForWrite(
preservedColorsJson,
dto.colorsJson,
dto.color,
),
imageUrlsJson:
dto.imageUrlsJson === undefined
? undefined