Compare commits

..

5 Commits

Author SHA1 Message Date
0aaf858f5b chore: sync versions to v1.8.20
All checks were successful
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧪 Test (push) Successful in 1m3s
Monorepo Pipeline / 🧹 Lint (push) Successful in 2m57s
Monorepo Pipeline / 🏗️ Build (push) Successful in 4m38s
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Successful in 1m12s
Monorepo Pipeline / 🐳 Build Build-Base (push) Successful in 1m42s
Monorepo Pipeline / 🐳 Build Production Runtime (push) Successful in 1m31s
Monorepo Pipeline / 🐳 Build Image Processor (push) Successful in 2m50s
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Successful in 8m14s
Monorepo Pipeline / 🚀 Release (push) Successful in 9m13s
2026-02-23 14:03:27 +01:00
ec562c1b2c fix: imgproxy issues 2026-02-23 14:03:17 +01:00
02e15c3f4a chore: sync versions to v1.8.19
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧪 Test (push) Successful in 3m54s
Monorepo Pipeline / 🧹 Lint (push) Successful in 4m12s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m42s
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Successful in 1m7s
Monorepo Pipeline / 🐳 Build Build-Base (push) Successful in 1m43s
Monorepo Pipeline / 🐳 Build Production Runtime (push) Successful in 1m37s
Monorepo Pipeline / 🐳 Build Image Processor (push) Successful in 2m47s
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Successful in 6m39s
Monorepo Pipeline / 🚀 Release (push) Successful in 7m18s
🏥 Server Maintenance / 🧹 Prune & Clean (push) Failing after 8s
2026-02-23 00:52:35 +01:00
cd4c2193ce feat: implement legacy imgproxy compatibility and URL mapping
All checks were successful
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧪 Test (push) Successful in 1m1s
Monorepo Pipeline / 🧹 Lint (push) Successful in 2m56s
Monorepo Pipeline / 🏗️ Build (push) Successful in 4m32s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Image Processor (push) Has been skipped
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
2026-02-23 00:14:13 +01:00
df7a464e03 fix(ci): sync lockfile and remove deleted model scripts
All checks were successful
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 1s
Monorepo Pipeline / 🧪 Test (push) Successful in 5m26s
Monorepo Pipeline / 🏗️ Build (push) Successful in 7m18s
Monorepo Pipeline / 🧹 Lint (push) Successful in 2m5s
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Successful in 1m8s
Monorepo Pipeline / 🐳 Build Build-Base (push) Successful in 1m43s
Monorepo Pipeline / 🐳 Build Production Runtime (push) Successful in 1m27s
Monorepo Pipeline / 🐳 Build Image Processor (push) Successful in 2m38s
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Successful in 5m49s
Monorepo Pipeline / 🚀 Release (push) Successful in 6m24s
2026-02-22 23:40:30 +01:00
37 changed files with 277 additions and 655 deletions

2
.env
View File

@@ -1,5 +1,5 @@
# Project
IMAGE_TAG=v1.8.12
IMAGE_TAG=v1.8.19
PROJECT_NAME=at-mintel
PROJECT_COLOR=#82ed20
GITEA_TOKEN=ccce002e30fe16a31a6c9d5a414740af2f72a582

View File

@@ -1,5 +1,5 @@
# Project
IMAGE_TAG=v1.8.16
IMAGE_TAG=v1.8.20
PROJECT_NAME=sample-website
PROJECT_COLOR=#82ed20

View File

@@ -1,6 +1,6 @@
{
"name": "image-service",
"version": "1.8.16",
"version": "1.8.20",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,37 +1,45 @@
import Fastify from "fastify";
import { processImageWithSmartCrop } from "@mintel/image-processor";
import {
processImageWithSmartCrop,
parseImgproxyOptions,
mapUrl,
} from "@mintel/image-processor";
const fastify = Fastify({
logger: true,
});
fastify.get("/unsafe/:options/:urlSafeB64", async (request, reply) => {
// Compatibility endpoint for old imgproxy calls (optional, but requested by some systems sometimes)
// For now, replacing logic in clients is preferred. So we just redirect or error.
return reply
.status(400)
.send({ error: "Legacy imgproxy API not supported. Use /process" });
});
fastify.get("/process", async (request, reply) => {
const query = request.query as {
url?: string;
w?: string;
h?: string;
q?: string;
format?: string;
const { options, urlSafeB64 } = request.params as {
options: string;
urlSafeB64: string;
};
const { url } = query;
const width = parseInt(query.w || "800", 10);
const height = parseInt(query.h || "600", 10);
const quality = parseInt(query.q || "80", 10);
const format = (query.format || "webp") as "webp" | "jpeg" | "png" | "avif";
if (!url) {
return reply.status(400).send({ error: 'Parameter "url" is required' });
// urlSafeB64 might be "plain/http://..." or a Base64 string
let url = "";
if (urlSafeB64.startsWith("plain/")) {
url = urlSafeB64.substring(6);
} else {
try {
url = Buffer.from(urlSafeB64, "base64").toString("utf-8");
} catch (e) {
return reply.status(400).send({ error: "Invalid Base64 URL" });
}
}
const parsedOptions = parseImgproxyOptions(options);
const mappedUrl = mapUrl(url, process.env.IMGPROXY_URL_MAPPING);
return handleProcessing(mappedUrl, parsedOptions, reply);
});
// Helper to avoid duplication
async function handleProcessing(url: string, options: any, reply: any) {
const width = options.width || 800;
const height = options.height || 600;
const quality = options.quality || 80;
const format = options.format || "webp";
try {
const response = await fetch(url);
if (!response.ok) {
@@ -60,6 +68,29 @@ fastify.get("/process", async (request, reply) => {
.status(500)
.send({ error: "Internal Server Error processing image" });
}
}
fastify.get("/process", async (request, reply) => {
const query = request.query as {
url?: string;
w?: string;
h?: string;
q?: string;
format?: string;
};
const { url } = query;
const width = parseInt(query.w || "800", 10);
const height = parseInt(query.h || "600", 10);
const quality = parseInt(query.q || "80", 10);
const format = (query.format || "webp") as any;
if (!url) {
return reply.status(400).send({ error: 'Parameter "url" is required' });
}
const mappedUrl = mapUrl(url, process.env.IMGPROXY_URL_MAPPING);
return handleProcessing(mappedUrl, { width, height, quality, format }, reply);
});
fastify.get("/health", async () => {

View File

@@ -1,6 +1,6 @@
{
"name": "sample-website",
"version": "1.8.16",
"version": "1.8.20",
"private": true,
"type": "module",
"scripts": {

View File

@@ -57,7 +57,7 @@
"pino-pretty": "^13.1.3",
"require-in-the-middle": "^8.0.1"
},
"version": "1.8.16",
"version": "1.8.20",
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",

View File

@@ -2,7 +2,7 @@
"name": "acquisition-manager",
"description": "Custom High-Fidelity Management for Directus",
"icon": "extension",
"version": "1.8.16",
"version": "1.8.20",
"type": "module",
"keywords": [
"directus",

View File

@@ -1,6 +1,6 @@
{
"name": "acquisition",
"version": "1.8.16",
"version": "1.8.20",
"type": "module",
"directus:extension": {
"type": "endpoint",

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/cli",
"version": "1.8.16",
"version": "1.8.20",
"publishConfig": {
"access": "public",
"registry": "https://npm.infra.mintel.me"

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/cloner",
"version": "1.8.16",
"version": "1.8.20",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/cms-infra",
"version": "1.8.16",
"version": "1.8.20",
"private": true,
"type": "module",
"scripts": {

View File

@@ -2,7 +2,7 @@
"name": "company-manager",
"description": "Custom High-Fidelity Management for Directus",
"icon": "extension",
"version": "1.8.16",
"version": "1.8.20",
"type": "module",
"keywords": [
"directus",

View File

@@ -1,7 +1,7 @@
{
"name": "@mintel/content-engine",
"version": "1.8.16",
"private": true,
"version": "1.8.20",
"private": false,
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

@@ -2,7 +2,7 @@
"name": "customer-manager",
"description": "Custom High-Fidelity Management for Directus",
"icon": "extension",
"version": "1.8.16",
"version": "1.8.20",
"type": "module",
"keywords": [
"directus",

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/directus-extension-toolkit",
"version": "1.8.16",
"version": "1.8.20",
"description": "Shared toolkit for Directus extensions in the Mintel ecosystem",
"type": "module",
"main": "./dist/index.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/eslint-config",
"version": "1.8.16",
"version": "1.8.20",
"publishConfig": {
"access": "public",
"registry": "https://npm.infra.mintel.me"

View File

@@ -2,7 +2,7 @@
"name": "feedback-commander",
"description": "Custom High-Fidelity Management for Directus",
"icon": "extension",
"version": "1.8.16",
"version": "1.8.20",
"type": "module",
"keywords": [
"directus",

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/gatekeeper",
"version": "1.8.16",
"version": "1.8.20",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/husky-config",
"version": "1.8.16",
"version": "1.8.20",
"publishConfig": {
"access": "public",
"registry": "https://npm.infra.mintel.me"

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/image-processor",
"version": "1.8.16",
"version": "1.8.20",
"private": true,
"type": "module",
"main": "./dist/index.js",

View File

@@ -1,55 +0,0 @@
import * as fs from "node:fs";
import * as path from "node:path";
import * as https from "node:https";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const MODELS_DIR = path.join(__dirname, "..", "models");
const BASE_URL =
"https://raw.githubusercontent.com/vladmandic/face-api/master/model/";
const models = [
"tiny_face_detector_model-weights_manifest.json",
"tiny_face_detector_model-shard1",
];
async function downloadModel(filename: string) {
const destPath = path.join(MODELS_DIR, filename);
if (fs.existsSync(destPath)) {
console.log(`Model ${filename} already exists.`);
return;
}
return new Promise((resolve, reject) => {
console.log(`Downloading ${filename}...`);
const file = fs.createWriteStream(destPath);
https
.get(BASE_URL + filename, (response) => {
response.pipe(file);
file.on("finish", () => {
file.close();
resolve(true);
});
})
.on("error", (err) => {
fs.unlinkSync(destPath);
reject(err);
});
});
}
async function main() {
if (!fs.existsSync(MODELS_DIR)) {
fs.mkdirSync(MODELS_DIR, { recursive: true });
}
for (const model of models) {
await downloadModel(model);
}
console.log("All models downloaded successfully!");
}
main().catch(console.error);

View File

@@ -8,6 +8,78 @@ export interface ProcessImageOptions {
openRouterApiKey?: string;
}
/**
* Maps a URL based on the IMGPROXY_URL_MAPPING environment variable.
* Format: "match1:replace1,match2:replace2"
*/
export function mapUrl(url: string, mappingString?: string): string {
if (!mappingString) return url;
const mappings = mappingString.split(",").map((m) => {
if (m.includes("|")) {
return m.split("|");
}
// Legacy support for simple "host:target" or cases where one side might have a protocol
// We try to find the split point that isn't part of a protocol "://"
const colonIndices = [];
for (let i = 0; i < m.length; i++) {
if (m[i] === ":") {
// Check if this colon is part of "://"
if (!(m[i + 1] === "/" && m[i + 2] === "/")) {
colonIndices.push(i);
}
}
}
if (colonIndices.length === 0) return [m];
// In legacy mode with colons, we take the LAST non-protocol colon as the separator
// This handles "http://host:port" or "host:http://target" better
const lastColon = colonIndices[colonIndices.length - 1];
return [m.substring(0, lastColon), m.substring(lastColon + 1)];
});
let mappedUrl = url;
for (const [match, replace] of mappings) {
if (match && replace && url.includes(match)) {
mappedUrl = url.replace(match, replace);
}
}
return mappedUrl;
}
/**
* Parses legacy imgproxy options string.
* Example: rs:fill:300:400/q:80
*/
export function parseImgproxyOptions(
optionsStr: string,
): Partial<ProcessImageOptions> {
const parts = optionsStr.split("/");
const options: Partial<ProcessImageOptions> = {};
for (const part of parts) {
if (part.startsWith("rs:")) {
const [, , w, h] = part.split(":");
if (w) options.width = parseInt(w, 10);
if (h) options.height = parseInt(h, 10);
} else if (part.startsWith("q:")) {
const q = part.split(":")[1];
if (q) options.quality = parseInt(q, 10);
} else if (part.startsWith("ext:")) {
const ext = part.split(":")[1] as any;
if (["webp", "jpeg", "png", "avif"].includes(ext)) {
options.format = ext;
}
}
}
return options;
}
interface FaceDetection {
x: number;
y: number;

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/infra",
"version": "1.8.16",
"version": "1.8.20",
"publishConfig": {
"access": "public",
"registry": "https://npm.infra.mintel.me"

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/journaling",
"version": "1.8.16",
"version": "1.8.20",
"private": true,
"type": "module",
"main": "./dist/index.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/mail",
"version": "1.8.16",
"version": "1.8.20",
"private": false,
"publishConfig": {
"access": "public",

View File

@@ -1,7 +1,7 @@
{
"name": "@mintel/meme-generator",
"version": "1.8.16",
"private": true,
"version": "1.8.20",
"private": false,
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/next-config",
"version": "1.8.16",
"version": "1.8.20",
"publishConfig": {
"access": "public",
"registry": "https://npm.infra.mintel.me"

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/next-feedback",
"version": "1.8.16",
"version": "1.8.20",
"publishConfig": {
"access": "public",
"registry": "https://npm.infra.mintel.me"

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/next-observability",
"version": "1.8.16",
"version": "1.8.20",
"publishConfig": {
"access": "public",
"registry": "https://npm.infra.mintel.me"

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/next-utils",
"version": "1.8.16",
"version": "1.8.20",
"publishConfig": {
"access": "public",
"registry": "https://npm.infra.mintel.me"

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/observability",
"version": "1.8.16",
"version": "1.8.20",
"publishConfig": {
"access": "public",
"registry": "https://npm.infra.mintel.me"

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/pdf",
"version": "1.8.16",
"version": "1.8.20",
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",

View File

@@ -2,7 +2,7 @@
"name": "people-manager",
"description": "Custom High-Fidelity Management for Directus",
"icon": "extension",
"version": "1.8.16",
"version": "1.8.20",
"type": "module",
"keywords": [
"directus",

View File

@@ -1,7 +1,7 @@
{
"name": "@mintel/thumbnail-generator",
"version": "1.8.16",
"private": true,
"version": "1.8.20",
"private": false,
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@mintel/tsconfig",
"version": "1.8.16",
"version": "1.8.20",
"publishConfig": {
"access": "public",
"registry": "https://npm.infra.mintel.me"

View File

@@ -2,7 +2,7 @@
"name": "unified-dashboard",
"description": "Custom High-Fidelity Management for Directus",
"icon": "extension",
"version": "1.8.16",
"version": "1.8.20",
"type": "module",
"keywords": [
"directus",

656
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff