feat(plugin): Add Elementor dynamic tags and product loop widget v1.7.0

WordPress Plugin:
- Add dynamic tags for all product payload fields (name, brand, price, THC, effects, etc.)
- Add Product Loop widget with filtering, sorting, and layout options
- Register CannaIQ widget category in Elementor
- Update build script to auto-upload to MinIO CDN
- Remove legacy dutchie references
- Bump version to 1.7.0

Backend:
- Redirect /downloads/* to CDN instead of serving from local filesystem

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kelly
2025-12-13 03:10:01 -07:00
parent 9f0d68d4c9
commit 5c08135007
8 changed files with 1941 additions and 70 deletions

View File

@@ -38,42 +38,16 @@ app.use('/images', express.static(LOCAL_IMAGES_PATH));
// Usage: /img/products/az/store/brand/product/image.webp?w=200&h=200
app.use('/img', imageProxyRoutes);
// Serve static downloads (plugin files, etc.)
// Uses ./public/downloads relative to working directory (works for both Docker and local dev)
const LOCAL_DOWNLOADS_PATH = process.env.LOCAL_DOWNLOADS_PATH || './public/downloads';
// Serve downloads from MinIO/CDN
// Files are stored in MinIO bucket at: cannaiq/downloads/
const CDN_DOWNLOADS_URL = process.env.CDN_DOWNLOADS_URL || 'https://cdn.cannabrands.app/cannaiq/downloads';
// Dynamic "latest" redirect for WordPress plugin - finds highest version automatically
app.get('/downloads/cannaiq-menus-latest.zip', (req, res) => {
const fs = require('fs');
const path = require('path');
try {
const files = fs.readdirSync(LOCAL_DOWNLOADS_PATH);
const pluginFiles = files
.filter((f: string) => f.match(/^cannaiq-menus-\d+\.\d+\.\d+\.zip$/))
.sort((a: string, b: string) => {
const vA = a.match(/(\d+)\.(\d+)\.(\d+)/);
const vB = b.match(/(\d+)\.(\d+)\.(\d+)/);
if (!vA || !vB) return 0;
for (let i = 1; i <= 3; i++) {
const diff = parseInt(vB[i]) - parseInt(vA[i]);
if (diff !== 0) return diff;
}
return 0;
});
if (pluginFiles.length > 0) {
const latestFile = pluginFiles[0];
res.redirect(302, `/downloads/${latestFile}`);
} else {
res.status(404).json({ error: 'No plugin versions found' });
}
} catch (err) {
res.status(500).json({ error: 'Failed to find latest plugin' });
}
// Redirect all download requests to CDN
app.get('/downloads/:filename', (req, res) => {
const filename = req.params.filename;
res.redirect(302, `${CDN_DOWNLOADS_URL}/${filename}`);
});
app.use('/downloads', express.static(LOCAL_DOWNLOADS_PATH));
// Simple health check for load balancers/K8s probes
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });