refactor: drop legacy image-processor and directus from pipeline
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 12s
Monorepo Pipeline / 🧪 Test (push) Successful in 1m3s
Monorepo Pipeline / 🧹 Lint (push) Failing after 1m31s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m4s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Image Processor (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
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 12s
Monorepo Pipeline / 🧪 Test (push) Successful in 1m3s
Monorepo Pipeline / 🧹 Lint (push) Failing after 1m31s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m4s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Image Processor (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
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
FROM node:20.18-bookworm-slim AS base
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
python3 \
|
||||
libcairo2-dev \
|
||||
libpango1.0-dev \
|
||||
libjpeg-dev \
|
||||
libgif-dev \
|
||||
librsvg2-dev \
|
||||
libexpat1 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN npm install -g pnpm@10.30.1
|
||||
|
||||
FROM base AS build
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
# We only need standard pnpm install now, no C++ tools needed for basic Sharp
|
||||
RUN pnpm install --frozen-lockfile
|
||||
RUN pnpm --filter @mintel/image-processor build
|
||||
RUN pnpm --filter image-service build
|
||||
|
||||
FROM base
|
||||
WORKDIR /app
|
||||
# Instead of copying node_modules which contains native C++ bindings for canvas and tfjs-node,
|
||||
# we copy the package.json files and install natively in the final stage so the bindings are correct.
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
|
||||
COPY apps/image-service/package.json ./apps/image-service/package.json
|
||||
COPY packages/image-processor/package.json ./packages/image-processor/package.json
|
||||
|
||||
RUN pnpm install --frozen-lockfile --filter image-service...
|
||||
|
||||
COPY --from=build /app/apps/image-service/dist ./apps/image-service/dist
|
||||
COPY --from=build /app/packages/image-processor/dist ./packages/image-processor/dist
|
||||
COPY --from=build /app/packages/image-processor/models ./packages/image-processor/models
|
||||
|
||||
EXPOSE 8080
|
||||
WORKDIR /app/apps/image-service
|
||||
CMD ["npm", "run", "start"]
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"name": "image-service",
|
||||
"version": "1.9.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js",
|
||||
"lint": "eslint src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mintel/image-processor": "workspace:*",
|
||||
"fastify": "^4.26.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mintel/eslint-config": "workspace:*",
|
||||
"@mintel/tsconfig": "workspace:*",
|
||||
"@types/node": "^20.0.0",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
import Fastify from "fastify";
|
||||
import {
|
||||
processImageWithSmartCrop,
|
||||
parseImgproxyOptions,
|
||||
mapUrl,
|
||||
} from "@mintel/image-processor";
|
||||
|
||||
const fastify = Fastify({
|
||||
logger: true,
|
||||
});
|
||||
|
||||
fastify.get("/unsafe/:options/:urlSafeB64", async (request, reply) => {
|
||||
const { options, urlSafeB64 } = request.params as {
|
||||
options: string;
|
||||
urlSafeB64: string;
|
||||
};
|
||||
|
||||
// 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) {
|
||||
return reply.status(response.status).send({
|
||||
error: `Failed to fetch source image: ${response.statusText}`,
|
||||
});
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
|
||||
const processedBuffer = await processImageWithSmartCrop(buffer, {
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
quality,
|
||||
});
|
||||
|
||||
reply.header("Content-Type", `image/${format}`);
|
||||
reply.header("Cache-Control", "public, max-age=31536000, immutable");
|
||||
return reply.send(processedBuffer);
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return 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 () => {
|
||||
return { status: "ok" };
|
||||
});
|
||||
|
||||
const start = async () => {
|
||||
try {
|
||||
await fastify.listen({ port: 8080, host: "0.0.0.0" });
|
||||
console.log(`Server listening on 8080`);
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
start();
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"extends": "@mintel/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"noEmit": false
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
@@ -2,12 +2,7 @@ import mintelNextConfig from "@mintel/next-config";
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
serverExternalPackages: [
|
||||
"@mintel/image-processor",
|
||||
"@tensorflow/tfjs-node",
|
||||
"sharp",
|
||||
"canvas",
|
||||
],
|
||||
transpilePackages: ["@mintel/ui"],
|
||||
};
|
||||
|
||||
export default mintelNextConfig(nextConfig);
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
"pagespeed:test": "mintel pagespeed"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mintel/image-processor": "workspace:*",
|
||||
"@mintel/next-observability": "workspace:*",
|
||||
"@mintel/next-utils": "workspace:*",
|
||||
"@mintel/observability": "workspace:*",
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const runtime = "nodejs";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const url = searchParams.get("url");
|
||||
const width = parseInt(searchParams.get("w") || "800");
|
||||
const height = parseInt(searchParams.get("h") || "600");
|
||||
const q = parseInt(searchParams.get("q") || "80");
|
||||
|
||||
if (!url) {
|
||||
return NextResponse.json(
|
||||
{ error: "Missing url parameter" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Fetch image from original URL
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to fetch original image" },
|
||||
{ status: response.status },
|
||||
);
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
|
||||
// Dynamically import to prevent Next.js from trying to bundle tfjs-node/sharp locally at build time
|
||||
const { processImageWithSmartCrop } =
|
||||
await import("@mintel/image-processor");
|
||||
|
||||
// 2. Process image with Face-API and Sharp
|
||||
const processedBuffer = await processImageWithSmartCrop(buffer, {
|
||||
width,
|
||||
height,
|
||||
format: "webp",
|
||||
quality: q,
|
||||
});
|
||||
|
||||
// 3. Return the processed image
|
||||
return new NextResponse(new Uint8Array(processedBuffer), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "image/webp",
|
||||
"Cache-Control": "public, max-age=31536000, immutable",
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Image Processing Error:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to process image" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user