feat: JLR hotspot support & image dimension handling
## PL24 JLR Improvements - Extend IMAGE_ID_PATTERN to support JLR alphanumeric IDs (e.g., "ll0047c") - Add extractJlrImageData() method for parsing JLR BOM response - Extract schema image width/height from JLR images[] array - Parse hotspots with key and areas from JLR response structure - Return schemaWidth and schemaHeight in PL24PartsResponse ## Assets - Add 4 new JLR schema images (eafxpa1a, lf0032a, lf0038a, ls0017) - Update UI screenshots Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
BIN
apps/api/public/images/schemas/eafxpa1a.png
Normal file
BIN
apps/api/public/images/schemas/eafxpa1a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
BIN
apps/api/public/images/schemas/lf0032a.png
Normal file
BIN
apps/api/public/images/schemas/lf0032a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
BIN
apps/api/public/images/schemas/lf0038a.png
Normal file
BIN
apps/api/public/images/schemas/lf0038a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 147 KiB |
BIN
apps/api/public/images/schemas/ls0017.png
Normal file
BIN
apps/api/public/images/schemas/ls0017.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
@@ -26,7 +26,9 @@ import { PL24AuthService } from './pl24-auth.service';
|
||||
import { PL24LegacyScraperService } from './pl24-legacy-scraper.service';
|
||||
|
||||
// Security: Strict validation patterns for image handling
|
||||
const IMAGE_ID_PATTERN = /^\d{9,12}$/;
|
||||
// Standard PL24: 9-12 digit numeric IDs
|
||||
// JLR: alphanumeric IDs like "ll0047c" (2-20 chars, letters/numbers only)
|
||||
const IMAGE_ID_PATTERN = /^(\d{9,12}|[a-zA-Z0-9]{2,20})$/;
|
||||
const ALLOWED_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'];
|
||||
import {
|
||||
PL24_ENDPOINTS,
|
||||
@@ -629,16 +631,18 @@ export class PL24Service {
|
||||
const parts = this.parsePartsResponse(bomData);
|
||||
const schemaImageUrl = this.extractIllustrationUrl(bomData);
|
||||
|
||||
// Extract hotspots from BOM response
|
||||
const hotspots = this.extractHotspotsFromBom(bomData);
|
||||
// Extract hotspots and dimensions from BOM response
|
||||
const { hotspots, schemaWidth, schemaHeight } = this.extractJlrImageData(bomData);
|
||||
|
||||
this.logger.log(`JLR: Fetched ${parts.length} parts from BOM, schema: ${schemaImageUrl ? 'yes' : 'no'}`);
|
||||
this.logger.log(`JLR: Fetched ${parts.length} parts from BOM, schema: ${schemaImageUrl ? 'yes' : 'no'}, hotspots: ${hotspots.length}, dims: ${schemaWidth}x${schemaHeight}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
groupId: String(firstIllus.btnr),
|
||||
groupName,
|
||||
schemaImageUrl: schemaImageUrl || undefined,
|
||||
schemaWidth,
|
||||
schemaHeight,
|
||||
parts,
|
||||
hotspots,
|
||||
};
|
||||
@@ -712,6 +716,58 @@ export class PL24Service {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract image data (hotspots, dimensions) from JLR BOM response
|
||||
* JLR stores image info in data.images[] with width, height, and hotspots
|
||||
*/
|
||||
private extractJlrImageData(response: unknown): {
|
||||
hotspots: PL24Hotspot[];
|
||||
schemaWidth?: number;
|
||||
schemaHeight?: number;
|
||||
} {
|
||||
const responseData = response as Record<string, unknown>;
|
||||
const data = responseData.data as Record<string, unknown> || responseData;
|
||||
const images = data.images as Array<{
|
||||
id?: string;
|
||||
uri?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
hotspots?: PL24Hotspot[];
|
||||
}> || [];
|
||||
|
||||
// Get the default image or first image
|
||||
const defaultImage = images.find(img => img.id === '_DFLT_') || images[0];
|
||||
|
||||
if (!defaultImage) {
|
||||
return { hotspots: [] };
|
||||
}
|
||||
|
||||
// Extract hotspots
|
||||
const hotspots: PL24Hotspot[] = [];
|
||||
if (defaultImage.hotspots && Array.isArray(defaultImage.hotspots)) {
|
||||
for (const hs of defaultImage.hotspots) {
|
||||
if (hs.key && hs.areas) {
|
||||
hotspots.push({
|
||||
key: hs.key,
|
||||
areas: hs.areas,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract dimensions
|
||||
const schemaWidth = defaultImage.width || undefined;
|
||||
const schemaHeight = defaultImage.height || undefined;
|
||||
|
||||
this.logger.debug(`JLR image data: ${images.length} images, dimensions=${schemaWidth}x${schemaHeight}, hotspots=${hotspots.length}`);
|
||||
|
||||
return {
|
||||
hotspots,
|
||||
schemaWidth,
|
||||
schemaHeight,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch sub-groups for a main group
|
||||
*/
|
||||
@@ -1474,12 +1530,21 @@ export class PL24Service {
|
||||
|
||||
/**
|
||||
* Extract PL24 image ID from URL for deduplication
|
||||
* URL format: https://www.partslink24.com/imageserver/ext/api/images/194500200?...
|
||||
* Standard URL: https://www.partslink24.com/imageserver/ext/api/images/194500200?...
|
||||
* JLR URL: https://www.partslink24.com/imageserver/ext/api/images/tiffimages/jlr/ll00/ll0047c.png?...
|
||||
*/
|
||||
extractImageIdFromUrl(imageUrl: string): string | null {
|
||||
if (!imageUrl) return null;
|
||||
const match = imageUrl.match(/\/images\/(\d+)\?/);
|
||||
return match ? match[1] : null;
|
||||
|
||||
// Standard PL24 image URL pattern: /images/194500200?
|
||||
const standardMatch = imageUrl.match(/\/images\/(\d+)\?/);
|
||||
if (standardMatch) return standardMatch[1];
|
||||
|
||||
// JLR tiffimages URL pattern: /tiffimages/jlr/.../ll0047c.png?
|
||||
const jlrMatch = imageUrl.match(/\/tiffimages\/jlr\/[^/]+\/([a-zA-Z0-9]+)\.\w+\?/);
|
||||
if (jlrMatch) return jlrMatch[1];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user