impress-2020/pages/api/assetImageRedirect.js
Matchu a1844f76e0 Add /api/assetImageRedirect
Okay, this is gonna be a drop-in new backend for impress-asset-images.openneo.net, to enable Classic DTI to use the same images as DTI 2020!

This will enable us to stop generating images and uploading them to S3 just for Classic's sake, so we can turn those background processes off! And the new modeling script skips that anyway, so this is an important compatibility step for the new data that went out today!
2022-10-11 12:21:14 -07:00

105 lines
3.5 KiB
JavaScript

/**
* /api/assetImageRedirect takes an asset type, Neopets ID, and image size,
* and redirects to a corresponding image URL.
*
* Parameters:
* - type: "biology" or "object"
* - remoteId: The Neopets ID of the asset
* - idealSize: "600x600", "300x300", or "150x150"
*
* This is designed to be a new backend for impress-asset-images.openneo.net,
* which has URLs like: http://impress-asset-images.openneo.net/biology/000/000/000/596/600x600.png?1326426317
* (That said, we still need some of the AWS images, which will still be
* accessible at aws.impress-asset-images.openneo.net.)
*
* Note that this endpoint doesn't always respect the `idealSize` parameter very
* closely; when our best canonical image is on images.neopets.com, it's
* usually 600x600, and I don't think it's worth the negligible network savings
* on Classic DTI to do resizing work here (and add another cache layer vs just
* serving from the original CDN that's much more likely to be a cache hit!).
*/
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",
});
import { gql, loadGraphqlQuery } from "../../src/server/ssr-graphql";
async function handle(req, res) {
if (!["biology", "object"].includes(req.query.type)) {
res.setHeader("Content-Type", "text/plain");
res.status(400).end(`type must be "biology" or "object"`);
return;
}
if (!["600x600", "300x300", "150x150"].includes(req.query.idealSize)) {
res.setHeader("Content-Type", "text/plain");
res.status(400).end(`idealSize must be 600x600, 300x300, or 150x150`);
return;
}
const { data, errors } = await loadGraphqlQuery({
query: gql`
query ApiAssetImageRedirect_GetImageUrl(
$type: LayerType!
$remoteId: ID!
$idealSize: LayerImageSize!
) {
appearanceLayerByRemoteId(type: $type, remoteId: $remoteId) {
imageUrlV2(idealSize: $idealSize)
}
}
`,
variables: {
type: req.query.type === "biology" ? "PET_LAYER" : "ITEM_LAYER",
remoteId: req.query.remoteId,
idealSize: "SIZE_" + parseInt(req.query.idealSize),
},
});
if (errors) {
console.error("Error loading image URL from GraphQL:");
for (const error of errors) {
console.error(error);
}
res.setHeader("Content-Type", "text/plain");
res.status(500).end(`Error loading image URL from GraphQL`);
return;
}
const layer = data.appearanceLayerByRemoteId;
if (layer == null) {
res.setHeader("Content-Type", "text/plain");
res.status(404).end(`appearance layer not found`);
return;
}
const imageUrl = layer.imageUrlV2;
if (imageUrl == null) {
res.setHeader("Content-Type", "text/plain");
res.status(404).end(`appearance layer has no image available`);
return;
}
// Cache for 5 minutes, and immediately serve stale data for an hour.
// I don't expect asset image URLs to change often, but when they do, it'll
// probably be important! And this is a pretty fast operation tbh.
res.setHeader(
"Cache-Control",
"public, max-age=300, stale-while-revalidate=3600"
);
res.setHeader("Content-Type", "image/png");
return res.redirect(imageUrl);
}
async function handleWithBeeline(req, res) {
beeline.withTrace(
{
name: "api/assetImageRedirect",
operation_name: "api/assetImageRedirect",
},
() => handle(req, res)
);
}
export default handleWithBeeline;