IO-3092 WIP on img proxy thumbnail generation.

This commit is contained in:
Patrick Fic
2025-02-05 11:03:29 -08:00
parent e8ee2a9416
commit 47fe1959b1
6 changed files with 489 additions and 41 deletions

View File

@@ -3,13 +3,16 @@ require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const logger = require("../utils/logger");
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
const { S3Client, PutObjectCommand, GetObjectCommand } = require("@aws-sdk/client-s3");
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
const crypto = require("crypto");
const { InstanceRegion } = require("../utils/instanceMgr");
const { GET_DOCUMENTS_BY_JOB } = require("../graphql-client/queries");
//TODO: Remove hardcoded values.
const imgproxyBaseUrl = process.env.IMGPROXY_BASE_URL || `https://d3ictiiutovkvi.cloudfront.net`;
const imgproxyBaseUrl =
process.env.IMGPROXY_BASE_URL ||
// `https://k2car6fha7w5cbgry3j2td56ra0kdmwn.lambda-url.ca-central-1.on.aws` ||
`https://d3ictiiutovkvi.cloudfront.net`;
const imgproxyKey = process.env.IMGPROXY_KEY || `secret`;
const imgproxySalt = process.env.IMGPROXY_SALT || `salt`;
const imgproxyDestinationBucket = process.env.IMGPROXY_DESTINATION_BUCKET || `imex-shop-media`;
@@ -36,7 +39,7 @@ exports.generateSignedUploadUrls = async (req, res) => {
const client = new S3Client({ region: InstanceRegion() });
const command = new PutObjectCommand({ Bucket: imgproxyDestinationBucket, Key: key });
const presignedUrl = await getSignedUrl(client, command, { expiresIn: 360 });
signedUrls.push({ filename, presignedUrl });
signedUrls.push({ filename, presignedUrl, key });
}
logger.log("imgproxy-upload-success", "DEBUG", req.user?.email, jobid, { signedUrls });
@@ -58,30 +61,25 @@ exports.generateSignedUploadUrls = async (req, res) => {
};
exports.getThumbnailUrls = async (req, res) => {
const { jobid } = req.body;
const { jobid, billid } = req.body;
try {
//TODO: Query for all documents related to the job.
//Delayed as the key structure may change slightly from what it is currently and will require evaluating mobile components.
// const { data } = await client.query({
// query: queries.GET_DOCUMENTS_BY_JOBID,
// variables: { jobid }
// });
//Mocked Keys.
const keys = [
"shopid/jobid/test2.jpg-1737502469411",
"shopid/jobid/test2.jpg-1737502469411",
"shopid/jobid/movie.mov-1737504997897",
"shopid/jobid/pdf.pdf-1737504944260"
];
const client = req.userGraphQLClient;
const data = await client.request(GET_DOCUMENTS_BY_JOB, { jobId: jobid });
const thumbResizeParams = `rs:fill:250:250:1/g:ce`;
const proxiedUrls = keys.map((key) => {
const s3client = new S3Client({ region: InstanceRegion() });
const proxiedUrls = [];
for (const document of data.documents) {
//Format to follow:
//<Cloudfront_to_lambdal>/<hmac with SHA of entire request URI path (with base64 encoded URL if needed), beginning with unencoded/unhashed Salt>/<remainder of url - resize params >/< base 64 URL encoded to image path>
// Build the S3 path to the object.
const fullS3Path = `s3://${imgproxyDestinationBucket}/${key}`;
const fullS3Path = `s3://${imgproxyDestinationBucket}/${document.key}`;
const base64UrlEncodedKeyString = base64UrlEncode(fullS3Path);
//Thumbnail Generation Block
const thumbProxyPath = `${thumbResizeParams}/${base64UrlEncodedKeyString}`;
@@ -92,15 +90,30 @@ exports.getThumbnailUrls = async (req, res) => {
const fullSizeProxyPath = `${base64UrlEncodedKeyString}`;
const fullSizeHmacSalt = createHmacSha256(`${imgproxySalt}/${fullSizeProxyPath}`);
//If not a picture, we need to get a signed download link to the file using S3 (or cloudfront preferably)
const s3Props = {};
if (!document.type.startsWith("image")) {
//If not a picture, we need to get a signed download link to the file using S3 (or cloudfront preferably)
const command = new GetObjectCommand({ Bucket: imgproxyDestinationBucket, Key: document.key });
const presignedGetUrl = await getSignedUrl(s3client, command, { expiresIn: 360 });
s3Props.presignedGetUrl = presignedGetUrl;
return {
const originalProxyPath = `raw:1/${base64UrlEncodedKeyString}`;
const originalHmacSalt = createHmacSha256(`${imgproxySalt}/${originalProxyPath}`);
s3Props.originalUrlViaProxyPath = `${imgproxyBaseUrl}/${originalHmacSalt}/${originalProxyPath}`;
}
proxiedUrls.push({
originalUrl: `${imgproxyBaseUrl}/${fullSizeHmacSalt}/${fullSizeProxyPath}`,
thumbnailUrl: `${imgproxyBaseUrl}/${thumbHmacSalt}/${thumbProxyPath}`
};
});
thumbnailUrl: `${imgproxyBaseUrl}/${thumbHmacSalt}/${thumbProxyPath}`,
fullS3Path,
base64UrlEncodedKeyString,
thumbProxyPath,
...s3Props,
...document
});
}
res.json({ proxiedUrls });
res.json(proxiedUrls);
//Iterate over them, build the link based on the media type, and return the array.
} catch (error) {
logger.log("imgproxy-get-proxied-urls-error", "ERROR", req.user?.email, jobid, {
@@ -124,8 +137,12 @@ exports.deleteFiles = async (req, res) => {
//Mark as deleted from the documents section of the database.
};
//Gerneate a key for the s3 bucket by popping off the extension, add a timestamp, and add back the extension.
//This is to prevent any collisions/duplicates in the bucket.
function GenerateKey({ bodyshopid, jobid, filename }) {
return `${bodyshopid}/${jobid}/${filename}-${Date.now()}`;
let nameArray = filename.split(".");
let extension = nameArray.pop();
return `${bodyshopid}/${jobid}/${nameArray.join(".")}-${Date.now()}.${extension}`;
}
function base64UrlEncode(str) {