Matchu
2c043adbe0
Oops, I forgot to grep outside `src` for this, lol! I'll keep the S3 domain support for now, because it's still fine to accept and some clients might be on old code, whatever!
76 lines
2.8 KiB
JavaScript
76 lines
2.8 KiB
JavaScript
const beeline = require("honeycomb-beeline")({
|
|
writeKey: process.env["HONEYCOMB_WRITE_KEY"],
|
|
dataset:
|
|
process.env["NODE_ENV"] === "production"
|
|
? "Dress to Impress (2020)"
|
|
: "Dress to Impress (2020, dev)",
|
|
serviceName: "impress-2020-gql-server",
|
|
});
|
|
|
|
const { renderOutfitImage } = require("../src/server/outfit-images");
|
|
|
|
const VALID_LAYER_URLS = [
|
|
/^https:\/\/(impress-asset-images\.openneo\.net|impress-asset-images\.s3\.amazonaws\.com)\/(biology|object)\/[0-9]{3}\/[0-9]{3}\/[0-9]{3}\/[0-9]+\/(150|300)x(150|300)\.png(\?[a-zA-Z0-9_-]+)?$/,
|
|
/^http:\/\/images\.neopets\.com\/cp\/(bio|object|items)\/data\/[0-9]{3}\/[0-9]{3}\/[0-9]{3}\/[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+\.(svg|png)(\?.*)?$/,
|
|
];
|
|
|
|
async function handle(req, res) {
|
|
if (!req.query.layerUrls) {
|
|
res.setHeader("Content-Type", "text/plain");
|
|
return res.status(400).send(`Missing required parameter: layerUrls`);
|
|
}
|
|
const layerUrls = req.query.layerUrls.split(",");
|
|
|
|
const size = parseInt(req.query.size);
|
|
if (size !== 150 && size !== 300) {
|
|
res.setHeader("Content-Type", "text/plain");
|
|
return res.status(400).send(`Size must be 150 or 300`);
|
|
}
|
|
|
|
for (const layerUrl of layerUrls) {
|
|
if (!VALID_LAYER_URLS.some((pattern) => layerUrl.match(pattern))) {
|
|
return res.status(400).send(`Unexpected layer URL format: ${layerUrl}`);
|
|
}
|
|
}
|
|
|
|
let imageResult;
|
|
try {
|
|
imageResult = await renderOutfitImage(layerUrls, size);
|
|
} catch (e) {
|
|
console.error(e);
|
|
res.setHeader("Content-Type", "text/plain");
|
|
return res.status(400).send(`Error rendering image: ${e.message}`);
|
|
}
|
|
|
|
const { image, status } = imageResult;
|
|
|
|
if (status === "success") {
|
|
// On success, we use very aggressive caching, on the assumption that
|
|
// layers are ~immutable too, and that our rendering algorithm will almost
|
|
// never change in a way that requires pushing changes. If it does, we
|
|
// should add a cache-buster to the URL!
|
|
res.setHeader("Cache-Control", "public, max-age=604800, immutable");
|
|
res.status(200);
|
|
} else {
|
|
// On partial failure, we still send the image, but with a 500 status. We
|
|
// send a long-lived cache header, but in such a way that the user can
|
|
// refresh the page to try again. (`private` means the CDN won't cache it,
|
|
// and we don't send `immutable`, which would save it even across reloads.)
|
|
// The 500 won't really affect the client, which will still show the image
|
|
// without feedback to the user - but it's a helpful debugging hint.
|
|
res.setHeader("Cache-Control", "private, max-age=604800");
|
|
res.status(500);
|
|
}
|
|
|
|
res.setHeader("Content-Type", "image/png");
|
|
return res.send(image);
|
|
}
|
|
|
|
async function handleWithBeeline(req, res) {
|
|
beeline.withTrace(
|
|
{ name: "api/outfitImage", operation_name: "api/outfitImage" },
|
|
() => handle(req, res)
|
|
);
|
|
}
|
|
|
|
export default handleWithBeeline;
|