diff --git a/packages/core/src/car.ts b/packages/core/src/car.ts new file mode 100644 index 0000000..9c609e8 --- /dev/null +++ b/packages/core/src/car.ts @@ -0,0 +1,58 @@ +export * as Car from "./car"; +import fetch from "node-fetch"; +import fs from "fs"; +import AdmZip from "adm-zip"; +import * as d3 from "d3"; +import { Car } from "./types"; +import { FUEL_TYPE } from "./config"; +import { sortByMake } from "./lib/sortByMake"; + +export const list = async () => { + const tempDir: string = "/tmp"; + const zipFileName: string = `Monthly New Registration of Cars by Make.zip`; + const zipFilePath: string = `${tempDir}/${zipFileName}`; + const csvFileName: string = `M03-Car_Regn_by_make.csv`; + const csvFilePath: string = `${tempDir}/${csvFileName}`; + const zipUrl: string = `https://datamall.lta.gov.sg/content/dam/datamall/datasets/Facts_Figures/Vehicle Registration/${zipFileName}`; + + const response = await fetch(zipUrl); + if (!response.ok) { + throw new Error(`Failed to download the ZIP file: ${response.statusText}`); + } + const data = await response.buffer(); + fs.writeFileSync(zipFilePath, data); + + const zip = new AdmZip(zipFilePath); + zip.extractAllTo(`${tempDir}`, true); + + const csvData = fs.readFileSync(csvFilePath, "utf-8"); + const parsedData = d3.csvParse(csvData); + + const electricCars: Car[] = parsedData + .filter( + ({ fuel_type, number }) => + fuel_type === FUEL_TYPE.ELECTRIC && +number !== 0, + ) + .reduce((result: Car[], { month, make, fuel_type, number }) => { + const existingCar = result.find( + (car) => car.month === month && car.make === make, + ); + + if (existingCar) { + existingCar.number += Number(number); + } else { + result.push({ + month, + make, + fuel_type, + number: Number(number), + }); + } + + return result; + }, []) + .map((car) => ({ ...car, number: +car.number })) + .sort(sortByMake); + + return electricCars; +}; diff --git a/packages/functions/src/config/index.ts b/packages/core/src/config/index.ts similarity index 100% rename from packages/functions/src/config/index.ts rename to packages/core/src/config/index.ts diff --git a/packages/functions/src/lib/sortByMake.ts b/packages/core/src/lib/sortByMake.ts similarity index 100% rename from packages/functions/src/lib/sortByMake.ts rename to packages/core/src/lib/sortByMake.ts diff --git a/packages/functions/src/types/index.ts b/packages/core/src/types/index.ts similarity index 100% rename from packages/functions/src/types/index.ts rename to packages/core/src/types/index.ts diff --git a/packages/functions/src/car.ts b/packages/functions/src/car.ts new file mode 100644 index 0000000..1b66a58 --- /dev/null +++ b/packages/functions/src/car.ts @@ -0,0 +1,7 @@ +import { ApiHandler } from "sst/node/api"; +import { Car } from "@lta-datasets-updater/core/car"; + +export const list = ApiHandler(async (_evt) => ({ + statusCode: 200, + body: JSON.stringify(await Car.list()), +})); diff --git a/packages/functions/src/lambda.ts b/packages/functions/src/lambda.ts deleted file mode 100644 index 3988765..0000000 --- a/packages/functions/src/lambda.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { ApiHandler } from "sst/node/api"; -import fs from "fs"; -import fetch from "node-fetch"; -import * as d3 from "d3"; -import AdmZip from "adm-zip"; -import { FUEL_TYPE } from "./config"; -import { sortByMake } from "./lib/sortByMake"; -import type { Car } from "./types"; - -export const handler = ApiHandler(async (_evt) => { - const tempDir: string = "/tmp"; - const zipFileName: string = `Monthly New Registration of Cars by Make.zip`; - const zipFilePath: string = `${tempDir}/${zipFileName}`; - const csvFileName: string = `M03-Car_Regn_by_make.csv`; - const csvFilePath: string = `${tempDir}/${csvFileName}`; - const zipUrl: string = `https://datamall.lta.gov.sg/content/dam/datamall/datasets/Facts_Figures/Vehicle Registration/${zipFileName}`; - - try { - const response = await fetch(zipUrl); - if (!response.ok) { - throw new Error( - `Failed to download the ZIP file: ${response.statusText}`, - ); - } - const data = await response.buffer(); - fs.writeFileSync(zipFilePath, data); - - const zip = new AdmZip(zipFilePath); - zip.extractAllTo(`${tempDir}`, true); - - const csvData = fs.readFileSync(csvFilePath, "utf-8"); - const parsedData = d3.csvParse(csvData); - - const electricCars: Car[] = parsedData - .filter( - ({ fuel_type, number }) => - fuel_type === FUEL_TYPE.ELECTRIC && +number !== 0, - ) - .reduce((result: Car[], { month, make, fuel_type, number }) => { - const existingCar = result.find( - (car) => car.month === month && car.make === make, - ); - - if (existingCar) { - existingCar.number += Number(number); - } else { - result.push({ - month, - make, - fuel_type, - number: Number(number), - }); - } - - return result; - }, []) - .map((car) => ({ ...car, number: +car.number })) - .sort(sortByMake); - - console.table(electricCars); - - return { - statusCode: 200, - body: JSON.stringify(electricCars), - }; - } catch (error) { - console.error(error); - return { - statusCode: 500, - body: "Error occurred while processing the ZIP file.", - }; - } -}); diff --git a/stacks/MyStack.ts b/stacks/MyStack.ts index 349b3f0..9e3e978 100644 --- a/stacks/MyStack.ts +++ b/stacks/MyStack.ts @@ -1,5 +1,16 @@ import { StackContext, Api, EventBus } from "sst/constructs"; +const CUSTOM_DOMAINS: Record = { + dev: { + domainName: "dev.api.singapore-ev-trends.ruchern.xyz", + hostedZone: "ruchern.xyz", + }, + prod: { + domainName: "api.singapore-ev-trends.ruchern.xyz", + hostedZone: "ruchern.xyz", + }, +}; + export const api = ({ stack }: StackContext) => { const bus = new EventBus(stack, "bus", { defaults: { @@ -14,15 +25,12 @@ export const api = ({ stack }: StackContext) => { bind: [bus], }, }, - customDomain: { - domainName: "api.singapore-ev-trends.ruchern.xyz", - hostedZone: "ruchern.xyz", - }, + customDomain: CUSTOM_DOMAINS[stack.stage], cors: { allowOrigins: ["https://singapore-ev-trends.ruchern.xyz"], }, routes: { - "GET /": "packages/functions/src/lambda.handler", + "GET /": "packages/functions/src/car.list", "GET /todo": "packages/functions/src/todo.list", "POST /todo": "packages/functions/src/todo.create", },