index.ts

import express from "express";
import { listAuctions, addAuction, bidInAuction } from './routes';


// Configure and start the HTTP server.
const port = 8088;
const app = express();
app.get("/api/list", listAuctions);
app.post("/api/add", addAuction);
app.post("/api/bid", bidInAuction);
app.listen(port, () => console.log(`Server listening on ${port}`));

routes.ts

import { Request, Response } from "express";


// Description of an individual auction
// RI: minBid, maxBid >= 0
type Auction = {
  name: string,
  description: string,
  seller: string,
  minBid: number,
  endTime: number,  // ms since epoch
  maxBid: number,
  maxBidder: string
};


// Map from item name to details of the auction.
const auctions: Map<string, Auction> = new Map();


// Returns a list of all the auctions, sorted so that the ongoing auctions come
// first, with the ones about to end listed first, and the completed ones after,
// with the ones completed more recently
export function listAuctions(_: Request, res: Response) {
  const now: number = Date.now();
  const vals = Array.from(auctions.values());
  vals.sort((a: Auction, b: Auction): number => {
    const endA = a.endTime + (a.endTime < now ? 1e15 - a.endTime : a.endTime);
    const endB = b.endTime + (b.endTime < now ? 1e15 - b.endTime : b.endTime);
    return endA - endB;
  })
  res.send({auctions: vals});
}


// Bids iin an auction.
export function bidInAuction(req: Request, res: Response) {
  const name = req.query.name;
  if (name === undefined || typeof name !== 'string') {
    res.status(400).send("missing 'name' parameter");
    return;
  }

  const auction = auctions.get(name);
  if (auction === undefined) {
    res.status(400).send(`no auction for an item called ${name}`);
    return;
  }

  const now = Date.now();
  if (now >= auction.endTime) {
    res.status(400).send(`auction for "${name}" has already ended`);
    return;
  }

  const bidder = req.query.bidder;
  if (bidder === undefined || typeof bidder !== 'string') {
    res.status(400).send("missing 'bidder' parameter");
    return;
  }

  if (req.query.amount === undefined || typeof req.query.amount !== 'string') {
    res.status(400).send("missing or too many 'amount' parameters");
    return;
  }

  const amount = parseNatural("amount", req.query.amount);
  if (typeof amount === "string") {
    res.status(400).send(amount);
    return;
  } else if (amount <= auction.maxBid) {
    res.status(400).send(
        `amount is less than max bid: ${amount} <= ${auction.maxBid}`);
    return;
  }

  auction.maxBid = amount;
  auction.maxBidder = bidder;
  res.send(auction);
}


// Add the item to the list.
export function addAuction(req: Request, res: Response) {
  const name = req.query.name;
  if (name === undefined || typeof name !== 'string') {
    res.status(400).send("missing 'name' parameter");
    return;
  }

  if (auctions.has(name)) {
    res.status(400).send(`auction for an item called ${name} already exists`);
    return;
  }

  const seller = req.query.seller;
  if (seller === undefined || typeof seller !== 'string') {
    res.status(400).send("missing 'seller' parameter");
    return;
  }

  const minBid = parseNatural("minBid", req.query.minBid);
  if (typeof minBid == 'string') {
    res.status(400).send(minBid);
    return;
  }

  const description = req.query.description;
  if (description === undefined || typeof description !== 'string') {
    res.status(400).send("missing 'description' parameter");
    return;
  }

  const minutes = parseNatural("minutes", req.query.minutes);
  if (typeof minutes == 'string') {
    res.status(400).send(minutes);
    return;
  }

  const auction: Auction = {
    name: name,
    description: description,
    seller: seller,
    minBid: minBid,
    endTime: Date.now() + minutes * 60 * 1000,  // convert to ms
    maxBid: minBid,
    maxBidder: seller
  };
  auctions.set(auction.name, auction);
  res.send(auction);
}


// Returns the given string parsed into an integer or an error message 
function parseNatural(name: string, val: unknown): number | string {
  if (val === undefined || typeof val !== 'string') {
    return `missing (or too many) '${name}' parameter`;
  }

  const amount = parseInt(val);
  if (isNaN(amount)) {
    return `invalid '${name}': ${val}`;
  }

  return amount;
}

Full Code