UNPKG

usda-food-data-api-server

Version:

USDA Food Data API server for self-hosted API access to the USDA Food Data database

124 lines (110 loc) 3.79 kB
import { dump } from "js-yaml"; import { BaseContext } from "koa"; import Router from "koa-router"; import { defaultFoodQuery, DefinedFoodQuery, getFood } from "../helpers/usda-v1.js"; import { splitNumber } from "./koa-normalize-arguments.js"; const router = new Router({ prefix: "/v1" }); export default router; /** * Given a Koa.JS query param, returns a valid format type for formatDocument * @param input koa query param * @returns yaml or json */ function sanitizeFormatType(input: string | string[] | undefined) { let str = (input instanceof Array ? input[0] : input) || "json"; return str.toLowerCase() === "yaml" ? "yaml" : "json"; } /** * Given a raw query or POST body, normalizes the arguments. * @param dirty The dirty request parameters * @returns Normalized request parameters */ function getArgs(dirty: any) { const results = { ...defaultFoodQuery, ...dirty }; results.pageSize = Math.max(1, Number(results.pageSize)); results.pageNumber = Math.max(0, Number(results.pageNumber)); results.nutrients = splitNumber(results.nutrients); results.format = sanitizeFormatType(results.format); return <DefinedFoodQuery>results; } /** * Handles a generic API request, taking a response and formatting it into the * requested format. * * @param ctx Koa ctx * @param docs The documents to be served * @param format json or yaml format */ function handleApi(ctx: BaseContext, docs: any, format: string) { if (docs) { let contentType = format === "yaml" ? "text/yaml" : "application/json"; ctx.set("Content-type", contentType); if (format === "yaml") { ctx.body = dump(docs); } else { ctx.body = JSON.stringify(docs); } } else { ctx.status = 404; ctx.body = `Not found`; // TODO Match USDA Api } } /** * For the given fdcId, returns a FoodItem */ router.get("/food/:fdcId", async function (ctx) { const fdcId = splitNumber(ctx.params.fdcId); const args = getArgs(ctx.request.query); handleApi(ctx, (await getFood({ fdcId }, args))[0], args.format); }); /** * For the given context, finds the requested FoodItem for the given fdcIds. * Handles both the POST and GET requests. * @param ctx Koa ctx */ async function handleFdcIdsApi(ctx: any) { const post = ctx.request.method === "POST"; const params = ctx.request[post ? "body" : "query"]; const fdcIds = splitNumber(params.fdcIds || ""); const args = getArgs(params); handleApi( ctx, await getFood({ fdcId: { $in: fdcIds } }, args), args.format ); } router.get("/foods", handleFdcIdsApi); router.post("/foods", handleFdcIdsApi); /** * For the given context, finds the required FoodItem's and returns them. * Covers both /foods/list and /foods/search since they offer the same * functionality, with the difference of a full text search. * @param ctx Koa context */ async function handleSearchApi(ctx: any) { const params = ctx.request.method === "GET" ? ctx.request.query : ctx.request.body; const { query } = params; const args = getArgs(params); const opts = query ? { $text: { $search: query } } : {}; handleApi(ctx, await getFood(opts, args), args.format); } router.get("/foods/list", handleSearchApi); router.post("/foods/list", handleSearchApi); router.get("/foods/search", handleSearchApi); router.post("/foods/search", handleSearchApi); // TODO - specs router.get("/json-spec", async (ctx) => { ctx.status = 404; ctx.body = "Not found"; }); router.get("/yaml-spec", async (ctx) => { ctx.status = 404; ctx.body = "Not found"; });