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