106 lines
3.5 KiB
JavaScript
106 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;
|