upd
This commit is contained in:
27
index.html
27
index.html
@@ -127,6 +127,17 @@
|
|||||||
.product-item {
|
.product-item {
|
||||||
padding: 0.25rem 0;
|
padding: 0.25rem 0;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-item:last-child {
|
.product-item:last-child {
|
||||||
@@ -261,7 +272,21 @@
|
|||||||
products.slice(0, 3).forEach(p => {
|
products.slice(0, 3).forEach(p => {
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
li.className = 'product-item';
|
li.className = 'product-item';
|
||||||
li.textContent = `📦 ${p.cName}`;
|
|
||||||
|
// Image
|
||||||
|
if (p.images && p.images.length > 0) {
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.className = 'product-image';
|
||||||
|
img.src = `/img/prod/${p.images[0]}.avif`;
|
||||||
|
img.alt = p.cName;
|
||||||
|
img.onerror = () => img.style.display = 'none';
|
||||||
|
li.appendChild(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.textContent = p.cName;
|
||||||
|
li.appendChild(span);
|
||||||
|
|
||||||
ul.appendChild(li);
|
ul.appendChild(li);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,37 @@ export function registerImages(app, cacheDir) {
|
|||||||
app.get('/img/cat/:id.avif', (req, res) => {
|
app.get('/img/cat/:id.avif', (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const imagePath = path.join(cacheDir, 'img', 'categories', `${id}.avif`);
|
const imagePath = path.join(cacheDir, 'img', 'categories', `${id}.avif`);
|
||||||
|
const resolvedPath = path.resolve(imagePath);
|
||||||
|
|
||||||
// Cache images for 1 year (immutable content)
|
res.sendFile(resolvedPath, {
|
||||||
res.set('Cache-Control', 'public, max-age=31536000, immutable');
|
headers: {
|
||||||
|
'Cache-Control': 'public, max-age=31536000, immutable'
|
||||||
res.sendFile(path.resolve(imagePath), (err) => {
|
}
|
||||||
|
}, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
if (!res.headersSent) {
|
||||||
res.status(404).send('Image not found');
|
res.status(404).send('Image not found');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Product images
|
||||||
|
app.get('/img/prod/:id.avif', (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const imagePath = path.join(cacheDir, 'img', 'products', `${id}.avif`);
|
||||||
|
const resolvedPath = path.resolve(imagePath);
|
||||||
|
res.sendFile(resolvedPath, {
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'public, max-age=31536000, immutable'
|
||||||
|
}
|
||||||
|
}, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(`❌ Error serving image ${resolvedPath}:`, err);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(404).send('Image not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import fs from 'fs/promises';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { createConnection } from '../utils/database.js';
|
import { createConnection } from '../utils/database.js';
|
||||||
|
import pictureSyncer from './picture-syncer.js';
|
||||||
|
|
||||||
class CategoryProductsSyncer extends EventEmitter {
|
class CategoryProductsSyncer extends EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -90,6 +91,8 @@ class CategoryProductsSyncer extends EventEmitter {
|
|||||||
|
|
||||||
async _fetchAndWriteProducts(ids, dir) {
|
async _fetchAndWriteProducts(ids, dir) {
|
||||||
let pool;
|
let pool;
|
||||||
|
const globalImageIds = new Set();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pool = await createConnection();
|
pool = await createConnection();
|
||||||
|
|
||||||
@@ -116,6 +119,31 @@ class CategoryProductsSyncer extends EventEmitter {
|
|||||||
ORDER BY a.bRowversion DESC, ab.bRowversion DESC
|
ORDER BY a.bRowversion DESC, ab.bRowversion DESC
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// Collect all kArtikel IDs to fetch images
|
||||||
|
const artikelIds = new Set();
|
||||||
|
result.recordset.forEach(r => artikelIds.add(r.kArtikel));
|
||||||
|
|
||||||
|
// Fetch images for these articles
|
||||||
|
let productImages = new Map(); // kArtikel -> kBild[]
|
||||||
|
if (artikelIds.size > 0) {
|
||||||
|
const artikelList = Array.from(artikelIds).join(',');
|
||||||
|
const imagesResult = await pool.request().query(`
|
||||||
|
SELECT kArtikel, kBild
|
||||||
|
FROM tArtikelbildPlattform
|
||||||
|
WHERE kShop = ${process.env.JTL_SHOP_ID}
|
||||||
|
AND kPlattform = ${process.env.JTL_PLATTFORM_ID}
|
||||||
|
AND kArtikel IN (${artikelList})
|
||||||
|
ORDER BY nNr ASC
|
||||||
|
`);
|
||||||
|
|
||||||
|
imagesResult.recordset.forEach(r => {
|
||||||
|
if (!productImages.has(r.kArtikel)) {
|
||||||
|
productImages.set(r.kArtikel, []);
|
||||||
|
}
|
||||||
|
productImages.get(r.kArtikel).push(r.kBild);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Group results by kKategorie
|
// Group results by kKategorie
|
||||||
const productsByCategory = {};
|
const productsByCategory = {};
|
||||||
|
|
||||||
@@ -126,9 +154,13 @@ class CategoryProductsSyncer extends EventEmitter {
|
|||||||
|
|
||||||
for (const record of result.recordset) {
|
for (const record of result.recordset) {
|
||||||
if (productsByCategory[record.kKategorie]) {
|
if (productsByCategory[record.kKategorie]) {
|
||||||
|
const images = productImages.get(record.kArtikel) || [];
|
||||||
|
images.forEach(imgId => globalImageIds.add(imgId));
|
||||||
|
|
||||||
productsByCategory[record.kKategorie].push({
|
productsByCategory[record.kKategorie].push({
|
||||||
kArtikel: record.kArtikel,
|
kArtikel: record.kArtikel,
|
||||||
cName: record.cName
|
cName: record.cName,
|
||||||
|
images: images
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,6 +192,13 @@ class CategoryProductsSyncer extends EventEmitter {
|
|||||||
console.log(`⏳ Processed products for ${processed}/${ids.length} categories...`);
|
console.log(`⏳ Processed products for ${processed}/${ids.length} categories...`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sync all collected images at once
|
||||||
|
if (globalImageIds.size > 0) {
|
||||||
|
console.log(`🖼️ Syncing ${globalImageIds.size} product images...`);
|
||||||
|
await pictureSyncer.syncImages(Array.from(globalImageIds), 'products');
|
||||||
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('❌ Error fetching products:', err);
|
console.error('❌ Error fetching products:', err);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user