CourseInsights / src / rest / Server.ts
Server.ts
Raw
import express, { Application, Request, Response } from "express";
import { StatusCodes } from "http-status-codes";
//import { Log } from "@ubccpsc310/project-support";
import * as http from "http";
import cors from "cors";
import InsightFacade from "../controller/InsightFacade";
import { InsightDatasetKind, NotFoundError } from "../controller/IInsightFacade";

export default class Server {
	private readonly port: number;
	private express: Application;
	private server: http.Server | undefined;
	private insightFacade: InsightFacade;

	constructor(port: number) {
		//Log.info(`Server::<init>( ${port} )`);
		//console.log(`Server::<init>( ${port} )`);
		this.port = port;
		this.express = express();
		this.insightFacade = new InsightFacade();

		this.registerMiddleware();
		this.registerRoutes();

		// NOTE: you can serve static frontend files in from your express server
		// by uncommenting the line below. This makes files in ./frontend/public
		// accessible at http://localhost:<port>/
		this.express.use(express.static("./frontend/public"));
	}

	/**
	 * Starts the server. Returns a promise that resolves if success. Promises are used
	 * here because starting the server takes some time and we want to know when it
	 * is done (and if it worked).
	 *
	 * @returns {Promise<void>}
	 */
	public async start(): Promise<void> {
		return new Promise((resolve, reject) => {
			//Log.info("Server::start() - start");
			//console.log("Server::start() - start");
			if (this.server !== undefined) {
				//Log.error("Server::start() - server already listening");
				//console.error("Server::start() - server already listening");
				reject();
			} else {
				this.server = this.express
					.listen(this.port, () => {
						//Log.info(`Server::start() - server listening on port: ${this.port}`);
						//console.log(`Server::start() - server listening on port: ${this.port}`);
						resolve();
					})
					.on("error", (err: Error) => {
						// catches errors in server start
						//Log.error(`Server::start() - server ERROR: ${err.message}`);
						//console.error(`Server::start() - server ERROR: ${err.message}`);
						reject(err);
					});
			}
		});
	}

	/**
	 * Stops the server. Again returns a promise so we know when the connections have
	 * actually been fully closed and the port has been released.
	 *
	 * @returns {Promise<void>}
	 */
	public async stop(): Promise<void> {
		//Log.info("Server::stop()");
		//console.log("Server::stop()");
		return new Promise((resolve, reject) => {
			if (this.server === undefined) {
				//Log.error("Server::stop() - ERROR: server not started");
				//console.error("Server::stop() - ERROR: server not started");
				reject();
			} else {
				this.server.close(() => {
					//Log.info("Server::stop() - server closed");
					//console.log("Server::stop() - server closed");
					resolve();
				});
			}
		});
	}

	// Registers middleware to parse request before passing them to request handlers
	private registerMiddleware(): void {
		// JSON parser must be place before raw parser because of wildcard matching done by raw parser below
		this.express.use(express.json());
		this.express.use(express.raw({ type: "application/*", limit: "10mb" }));

		// enable cors in request headers to allow cross-origin HTTP requests
		this.express.use(cors({ origin: "*" }));
	}

	// Registers all request handlers to routes
	private registerRoutes(): void {
		// This is an example endpoint this you can invoke by accessing this URL in your browser:
		// http://localhost:4321/echo/hello
		this.express.get("/echo/:msg", Server.echo);

		// TODO: your other endpoints should go here
		this.express.put("/dataset/:id/:kind", this.addDataset);
		this.express.delete("/dataset/:id", this.removeDataset);
		this.express.post("/query", this.performQuery);
		this.express.get("/datasets", this.listDatasets);
	}

	private addDataset = async (req: Request, res: Response): Promise<void> => {
		try {
			//Log.info(`Server::addDataset(..) - params: ${JSON.stringify(req.params)}`);
			//console.log(`Server::addDataset(..) - params: ${JSON.stringify(req.params)}`);
			const base64Content = Buffer.isBuffer(req.body) ? req.body.toString("base64") : req.body;
			const response = await this.insightFacade.addDataset(
				req.params.id,
				base64Content,
				req.params.kind as InsightDatasetKind
			);
			res.status(StatusCodes.OK).json({ result: response });
		} catch (err: any) {
			res.status(StatusCodes.BAD_REQUEST).json({ error: err.message });
		}
	};

	private removeDataset = async (req: Request, res: Response): Promise<void> => {
		try {
			//Log.info(`Server::removeDataset(..) - params: ${JSON.stringify(req.params)}`);
			//console.log(`Server::removeDataset(..) - params: ${JSON.stringify(req.params)}`);
			const response = await this.insightFacade.removeDataset(req.params.id);
			res.status(StatusCodes.OK).json({ result: response });
		} catch (err: any) {
			if (err instanceof NotFoundError) {
				res.status(StatusCodes.NOT_FOUND).json({ error: err.message });
			} else {
				//if (err instanceof InsightError)
				res.status(StatusCodes.BAD_REQUEST).json({ error: err.message });
			}
		}
	};

	private performQuery = async (req: Request, res: Response): Promise<void> => {
		try {
			//Log.info(`Server::performQuery(..) - params: ${JSON.stringify(req.body, null, 2)}`);
			//console.log(`Server::performQuery(..) - params: ${JSON.stringify(req.body, null, 2)}`);
			const response = await this.insightFacade.performQuery(req.body);
			res.status(StatusCodes.OK).json({ result: response });
		} catch (err: any) {
			res.status(StatusCodes.BAD_REQUEST).json({ error: err.message });
		}
	};

	private listDatasets = async (req: Request, res: Response): Promise<void> => {
		try {
			//Log.info(`Server::listDatasets(..) - params: ${JSON.stringify(req.params)}`);
			//console.log(`Server::listDatasets(..) - params: ${JSON.stringify(req.params)}`);
			const response = await this.insightFacade.listDatasets();
			res.status(StatusCodes.OK).json({ result: response });
		} catch (err: any) {
			res.status(StatusCodes.BAD_REQUEST).json({ error: err.message });
		}
	};

	// The next two methods handle the echo service.
	// These are almost certainly not the best place to put these, but are here for your reference.
	// By updating the Server.echo function pointer above, these methods can be easily moved.
	private static echo(req: Request, res: Response): void {
		try {
			//Log.info(`Server::echo(..) - params: ${JSON.stringify(req.params)}`);
			//console.log(`Server::echo(..) - params: ${JSON.stringify(req.params)}`);
			const response = Server.performEcho(req.params.msg);
			res.status(StatusCodes.OK).json({ result: response });
		} catch (err) {
			res.status(StatusCodes.BAD_REQUEST).json({ error: err });
		}
	}

	private static performEcho(msg: string): string {
		if (typeof msg !== "undefined" && msg !== null) {
			return `${msg}...${msg}`;
		} else {
			return "Message not provided";
		}
	}
}