fix bug of save imageUrls
This commit is contained in:
@@ -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', () => {
|
it('does not merge color images into general list when only imageUrlsJson is set', () => {
|
||||||
const entries = parseColorEntries([{ name: 'Red', imageUrl: 'https://cdn/r.jpg' }]);
|
const entries = parseColorEntries([{ name: 'Red', imageUrl: 'https://cdn/r.jpg' }]);
|
||||||
expect(parseGeneralImageUrls(['https://cdn/g.jpg'], entries)).toEqual([
|
expect(parseGeneralImageUrls(['https://cdn/g.jpg'], entries)).toEqual([
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ export function parseColorEntries(
|
|||||||
return name ? [{ name, imageUrls: [] }] : [];
|
return name ? [{ name, imageUrls: [] }] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return colorsJson
|
const parsed = colorsJson
|
||||||
.map((item): ProductColorEntry | null => {
|
.map((item, index): ProductColorEntry | null => {
|
||||||
if (typeof item === 'string') {
|
if (typeof item === 'string') {
|
||||||
const name = item.trim();
|
const name = item.trim();
|
||||||
return name ? { name, imageUrls: [] } : null;
|
return name ? { name, imageUrls: [] } : null;
|
||||||
@@ -40,8 +40,7 @@ export function parseColorEntries(
|
|||||||
imageUrl?: unknown;
|
imageUrl?: unknown;
|
||||||
imageUrls?: unknown;
|
imageUrls?: unknown;
|
||||||
};
|
};
|
||||||
const name = String(raw.name ?? '').trim();
|
let name = String(raw.name ?? '').trim();
|
||||||
if (!name) return null;
|
|
||||||
const fromArray = normalizeUrlList(raw.imageUrls);
|
const fromArray = normalizeUrlList(raw.imageUrls);
|
||||||
const legacy =
|
const legacy =
|
||||||
typeof raw.imageUrl === 'string' && raw.imageUrl.trim()
|
typeof raw.imageUrl === 'string' && raw.imageUrl.trim()
|
||||||
@@ -55,12 +54,21 @@ export function parseColorEntries(
|
|||||||
: legacy
|
: legacy
|
||||||
? [legacy]
|
? [legacy]
|
||||||
: [];
|
: [];
|
||||||
|
if (!name && imageUrls.length > 0) {
|
||||||
|
name = fallbackColor?.trim() || `رنگ ${index + 1}`;
|
||||||
|
}
|
||||||
|
if (!name) return null;
|
||||||
const imageUrl = imageUrls[0];
|
const imageUrl = imageUrls[0];
|
||||||
return { name, imageUrl, imageUrls };
|
return { name, imageUrl, imageUrls };
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
.filter((entry): entry is ProductColorEntry => Boolean(entry));
|
.filter((entry): entry is ProductColorEntry => Boolean(entry));
|
||||||
|
|
||||||
|
if (parsed.length > 0) return parsed;
|
||||||
|
|
||||||
|
const fallbackName = fallbackColor?.trim();
|
||||||
|
return fallbackName ? [{ name: fallbackName, imageUrls: [] }] : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** تصاویر عمومی محصول — فقط از imageUrlsJson، بدون merge با رنگها */
|
/** تصاویر عمومی محصول — فقط از imageUrlsJson، بدون merge با رنگها */
|
||||||
|
|||||||
@@ -50,22 +50,65 @@ export function transformColorsJson(value: unknown): ProductColorJsonItemDto[] |
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ذخیره در Prisma — imageUrl + imageUrls برای هر رنگ */
|
function mergeColorImageUrls(
|
||||||
export function serializeColorsJsonForDb(raw: unknown): unknown[] | undefined {
|
imageUrls?: string[],
|
||||||
const entries = transformColorsJson(raw);
|
imageUrl?: string,
|
||||||
if (entries === undefined) return undefined;
|
): string[] {
|
||||||
return entries.map((entry) => {
|
|
||||||
const merged: string[] = [];
|
const merged: string[] = [];
|
||||||
const push = (u?: string) => {
|
const push = (u?: string) => {
|
||||||
const t = u?.trim();
|
const t = u?.trim();
|
||||||
if (t && !merged.includes(t)) merged.push(t);
|
if (t && !merged.includes(t)) merged.push(t);
|
||||||
};
|
};
|
||||||
for (const u of entry.imageUrls ?? []) push(u);
|
for (const u of imageUrls ?? []) push(u);
|
||||||
push(entry.imageUrl);
|
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];
|
const first = merged[0];
|
||||||
return {
|
out.push({
|
||||||
name: entry.name,
|
name,
|
||||||
...(first ? { imageUrl: first, imageUrls: merged } : {}),
|
...(first ? { imageUrl: first, imageUrls: merged } : {}),
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,13 +76,41 @@ export class ProductsService {
|
|||||||
return false;
|
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(
|
private colorsJsonForWrite(
|
||||||
preserved: unknown | undefined,
|
preserved: unknown | undefined,
|
||||||
fromDto: unknown | undefined,
|
fromDto: unknown | undefined,
|
||||||
|
fallbackColor?: string,
|
||||||
): Prisma.InputJsonValue | undefined {
|
): Prisma.InputJsonValue | undefined {
|
||||||
const source = preserved !== undefined ? preserved : fromDto;
|
const source = this.colorsJsonSourceForWrite(preserved, fromDto);
|
||||||
if (source === undefined) return undefined;
|
if (source === undefined) return undefined;
|
||||||
const serialized = serializeColorsJsonForDb(source);
|
const serialized = serializeColorsJsonForDb(source, fallbackColor);
|
||||||
if (serialized === undefined) return undefined;
|
if (serialized === undefined) return undefined;
|
||||||
return serialized as Prisma.InputJsonValue;
|
return serialized as Prisma.InputJsonValue;
|
||||||
}
|
}
|
||||||
@@ -118,6 +146,7 @@ export class ProductsService {
|
|||||||
colorsJson: this.colorsJsonForWrite(
|
colorsJson: this.colorsJsonForWrite(
|
||||||
preservedColorsJson,
|
preservedColorsJson,
|
||||||
dto.colorsJson,
|
dto.colorsJson,
|
||||||
|
dto.color,
|
||||||
),
|
),
|
||||||
imageUrlsJson: (dto.imageUrlsJson ?? undefined) as Prisma.InputJsonValue | undefined,
|
imageUrlsJson: (dto.imageUrlsJson ?? undefined) as Prisma.InputJsonValue | undefined,
|
||||||
tagsJson: dto.tagsJson ?? undefined,
|
tagsJson: dto.tagsJson ?? undefined,
|
||||||
@@ -204,7 +233,11 @@ export class ProductsService {
|
|||||||
colorsJson:
|
colorsJson:
|
||||||
dto.colorsJson === undefined && preservedColorsJson === undefined
|
dto.colorsJson === undefined && preservedColorsJson === undefined
|
||||||
? undefined
|
? undefined
|
||||||
: this.colorsJsonForWrite(preservedColorsJson, dto.colorsJson),
|
: this.colorsJsonForWrite(
|
||||||
|
preservedColorsJson,
|
||||||
|
dto.colorsJson,
|
||||||
|
dto.color,
|
||||||
|
),
|
||||||
imageUrlsJson:
|
imageUrlsJson:
|
||||||
dto.imageUrlsJson === undefined
|
dto.imageUrlsJson === undefined
|
||||||
? undefined
|
? undefined
|
||||||
|
|||||||
Reference in New Issue
Block a user