impress-2020/pages/api/assetImageRedirect.js

106 lines
3.5 KiB
JavaScript
Raw Normal View History

/**
* /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;