diff --git a/db/migration/0005_luxuriant_microbe.sql b/db/migration/0005_luxuriant_microbe.sql new file mode 100644 index 00000000..1782b7b4 --- /dev/null +++ b/db/migration/0005_luxuriant_microbe.sql @@ -0,0 +1 @@ +ALTER TABLE "agency" ALTER COLUMN "name" SET NOT NULL; \ No newline at end of file diff --git a/db/migration/meta/0005_snapshot.json b/db/migration/meta/0005_snapshot.json new file mode 100644 index 00000000..62c2898e --- /dev/null +++ b/db/migration/meta/0005_snapshot.json @@ -0,0 +1,934 @@ +{ + "id": "7b7118ec-5859-43b8-9fc8-edb891250255", + "prevId": "bd003b5a-e4cc-46e1-a5dc-c23e206f9519", + "version": "5", + "dialect": "pg", + "tables": { + "agency_budget": { + "name": "agency_budget", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sponsor": { + "name": "sponsor", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "agency_budget_sponsor_agency_initials_fk": { + "name": "agency_budget_sponsor_agency_initials_fk", + "tableFrom": "agency_budget", + "tableTo": "agency", + "columnsFrom": [ + "sponsor" + ], + "columnsTo": [ + "initials" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "agency": { + "name": "agency", + "schema": "", + "columns": { + "initials": { + "name": "initials", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "borough": { + "name": "borough", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(1)", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "abbr": { + "name": "abbr", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "budget_line": { + "name": "budget_line", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "budget_line_code_agency_budget_code_fk": { + "name": "budget_line_code_agency_budget_code_fk", + "tableFrom": "budget_line", + "tableTo": "agency_budget", + "columnsFrom": [ + "code" + ], + "columnsTo": [ + "code" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "budget_line_code_id_pk": { + "name": "budget_line_code_id_pk", + "columns": [ + "code", + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "capital_commitment_fund": { + "name": "capital_commitment_fund", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "capital_commitment_id": { + "name": "capital_commitment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "capital_fund_category": { + "name": "capital_fund_category", + "type": "capital_fund_category", + "primaryKey": false, + "notNull": false + }, + "value": { + "name": "value", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "capital_commitment_fund_capital_commitment_id_captial_commitment_id_fk": { + "name": "capital_commitment_fund_capital_commitment_id_captial_commitment_id_fk", + "tableFrom": "capital_commitment_fund", + "tableTo": "captial_commitment", + "columnsFrom": [ + "capital_commitment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "capital_commitment_type": { + "name": "capital_commitment_type", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "char(4)", + "primaryKey": true, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "captial_commitment": { + "name": "captial_commitment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "char(4)", + "primaryKey": false, + "notNull": false + }, + "planned_date": { + "name": "planned_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "managing_code": { + "name": "managing_code", + "type": "char(3)", + "primaryKey": false, + "notNull": false + }, + "capital_project_id": { + "name": "capital_project_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "budget_line_code": { + "name": "budget_line_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "budget_line_id": { + "name": "budget_line_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "captial_commitment_type_capital_commitment_type_code_fk": { + "name": "captial_commitment_type_capital_commitment_type_code_fk", + "tableFrom": "captial_commitment", + "tableTo": "capital_commitment_type", + "columnsFrom": [ + "type" + ], + "columnsTo": [ + "code" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "captial_commitment_managing_code_capital_project_id_capital_project_managing_code_id_fk": { + "name": "captial_commitment_managing_code_capital_project_id_capital_project_managing_code_id_fk", + "tableFrom": "captial_commitment", + "tableTo": "capital_project", + "columnsFrom": [ + "managing_code", + "capital_project_id" + ], + "columnsTo": [ + "managing_code", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "captial_commitment_budget_line_code_budget_line_id_budget_line_code_id_fk": { + "name": "captial_commitment_budget_line_code_budget_line_id_budget_line_code_id_fk", + "tableFrom": "captial_commitment", + "tableTo": "budget_line", + "columnsFrom": [ + "budget_line_code", + "budget_line_id" + ], + "columnsTo": [ + "code", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "capital_project_checkbook": { + "name": "capital_project_checkbook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "managing_code": { + "name": "managing_code", + "type": "char(3)", + "primaryKey": false, + "notNull": false + }, + "capital_project_id": { + "name": "capital_project_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "value": { + "name": "value", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "custom_fk": { + "name": "custom_fk", + "tableFrom": "capital_project_checkbook", + "tableTo": "capital_project", + "columnsFrom": [ + "managing_code", + "capital_project_id" + ], + "columnsTo": [ + "managing_code", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "capital_project_fund": { + "name": "capital_project_fund", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "managing_code": { + "name": "managing_code", + "type": "char(3)", + "primaryKey": false, + "notNull": false + }, + "capital_project_id": { + "name": "capital_project_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "capital_fund_category": { + "name": "capital_fund_category", + "type": "capital_fund_category", + "primaryKey": false, + "notNull": false + }, + "stage": { + "name": "stage", + "type": "capital_project_fund_stage", + "primaryKey": false, + "notNull": false + }, + "value": { + "name": "value", + "type": "numeric", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "custom_fk": { + "name": "custom_fk", + "tableFrom": "capital_project_fund", + "tableTo": "capital_project", + "columnsFrom": [ + "managing_code", + "capital_project_id" + ], + "columnsTo": [ + "managing_code", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "capital_project": { + "name": "capital_project", + "schema": "", + "columns": { + "managing_code": { + "name": "managing_code", + "type": "char(3)", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "managing_agency": { + "name": "managing_agency", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "min_date": { + "name": "min_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "max_date": { + "name": "max_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "capital_project_category", + "primaryKey": false, + "notNull": false + }, + "li_ft_m_pnt": { + "name": "li_ft_m_pnt", + "type": "geometry(multiPoint,2263)", + "primaryKey": false, + "notNull": false + }, + "li_ft_m_poly": { + "name": "li_ft_m_poly", + "type": "geometry(multiPolygon,2263)", + "primaryKey": false, + "notNull": false + }, + "mercator_label": { + "name": "mercator_label", + "type": "geometry(point,3857)", + "primaryKey": false, + "notNull": false + }, + "mercator_fill_m_pnt": { + "name": "mercator_fill_m_pnt", + "type": "geometry(multiPoint,3857)", + "primaryKey": false, + "notNull": false + }, + "mercator_fill_m_poly": { + "name": "mercator_fill_m_poly", + "type": "geometry(multiPolygon,3857)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "capital_project_managing_code_managing_code_id_fk": { + "name": "capital_project_managing_code_managing_code_id_fk", + "tableFrom": "capital_project", + "tableTo": "managing_code", + "columnsFrom": [ + "managing_code" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "capital_project_managing_agency_agency_initials_fk": { + "name": "capital_project_managing_agency_agency_initials_fk", + "tableFrom": "capital_project", + "tableTo": "agency", + "columnsFrom": [ + "managing_agency" + ], + "columnsTo": [ + "initials" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "capital_project_managing_code_id_pk": { + "name": "capital_project_managing_code_id_pk", + "columns": [ + "managing_code", + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "city_council_district": { + "name": "city_council_district", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "li_ft": { + "name": "li_ft", + "type": "geometry(multiPolygon,2263)", + "primaryKey": false, + "notNull": false + }, + "mercator_fill": { + "name": "mercator_fill", + "type": "geometry(multiPolygon,3857)", + "primaryKey": false, + "notNull": false + }, + "mercator_label": { + "name": "mercator_label", + "type": "geometry(point,3857)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "community_district": { + "name": "community_district", + "schema": "", + "columns": { + "borough_id": { + "name": "borough_id", + "type": "char(1)", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "char(2)", + "primaryKey": false, + "notNull": false + }, + "li_ft": { + "name": "li_ft", + "type": "geometry(multiPoint,2263)", + "primaryKey": false, + "notNull": false + }, + "mercator_fill": { + "name": "mercator_fill", + "type": "geometry(multiPolygon,3857)", + "primaryKey": false, + "notNull": false + }, + "mercator_label": { + "name": "mercator_label", + "type": "geometry(point,3857)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "community_district_borough_id_borough_id_fk": { + "name": "community_district_borough_id_borough_id_fk", + "tableFrom": "community_district", + "tableTo": "borough", + "columnsFrom": [ + "borough_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "community_district_borough_id_id_pk": { + "name": "community_district_borough_id_id_pk", + "columns": [ + "borough_id", + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "tax_lot": { + "name": "tax_lot", + "schema": "", + "columns": { + "bbl": { + "name": "bbl", + "type": "char(10)", + "primaryKey": true, + "notNull": true + }, + "borough_id": { + "name": "borough_id", + "type": "char(1)", + "primaryKey": false, + "notNull": true + }, + "block": { + "name": "block", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lot": { + "name": "lot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "land_use_id": { + "name": "land_use_id", + "type": "char(2)", + "primaryKey": false, + "notNull": false + }, + "wgs84": { + "name": "wgs84", + "type": "geography(multiPolygon, 4326)", + "primaryKey": false, + "notNull": true + }, + "li_ft": { + "name": "li_ft", + "type": "geometry(multiPolygon,2263)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "tax_lot_borough_id_borough_id_fk": { + "name": "tax_lot_borough_id_borough_id_fk", + "tableFrom": "tax_lot", + "tableTo": "borough", + "columnsFrom": [ + "borough_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tax_lot_land_use_id_land_use_id_fk": { + "name": "tax_lot_land_use_id_land_use_id_fk", + "tableFrom": "tax_lot", + "tableTo": "land_use", + "columnsFrom": [ + "land_use_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "land_use": { + "name": "land_use", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(2)", + "primaryKey": true, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "char(9)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "managing_code": { + "name": "managing_code", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "char(3)", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "zoning_district": { + "name": "zoning_district", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "wgs84": { + "name": "wgs84", + "type": "geography(multiPolygon, 4326)", + "primaryKey": false, + "notNull": true + }, + "li_ft": { + "name": "li_ft", + "type": "geometry(multiPolygon,2263)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "zoning_district_class": { + "name": "zoning_district_class", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "category": { + "name": "category", + "type": "category", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "char(9)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "zoning_district_zoning_district_class": { + "name": "zoning_district_zoning_district_class", + "schema": "", + "columns": { + "zoning_district_id": { + "name": "zoning_district_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "zoning_district_class_id": { + "name": "zoning_district_class_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "zoning_district_zoning_district_class_zoning_district_id_zoning_district_id_fk": { + "name": "zoning_district_zoning_district_class_zoning_district_id_zoning_district_id_fk", + "tableFrom": "zoning_district_zoning_district_class", + "tableTo": "zoning_district", + "columnsFrom": [ + "zoning_district_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "zoning_district_zoning_district_class_zoning_district_class_id_zoning_district_class_id_fk": { + "name": "zoning_district_zoning_district_class_zoning_district_class_id_zoning_district_class_id_fk", + "tableFrom": "zoning_district_zoning_district_class", + "tableTo": "zoning_district_class", + "columnsFrom": [ + "zoning_district_class_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "capital_fund_category": { + "name": "capital_fund_category", + "values": { + "city-non-exempt": "city-non-exempt", + "city-exempt": "city-exempt", + "city-cost": "city-cost", + "non-city-state": "non-city-state", + "non-city-federal": "non-city-federal", + "non-city-other": "non-city-other", + "non-city-cost": "non-city-cost", + "total": "total" + } + }, + "capital_project_fund_stage": { + "name": "capital_project_fund_stage", + "values": { + "adopt": "adopt", + "allocate": "allocate", + "commit": "commit", + "spent": "spent" + } + }, + "capital_project_category": { + "name": "capital_project_category", + "values": { + "Fixed Asset": "Fixed Asset", + "Lump Sum": "Lump Sum", + "ITT, Vehicles and Equipment": "ITT, Vehicles and Equipment" + } + }, + "category": { + "name": "category", + "values": { + "Residential": "Residential", + "Commercial": "Commercial", + "Manufacturing": "Manufacturing" + } + } + }, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/db/migration/meta/_journal.json b/db/migration/meta/_journal.json index c3283e33..784a2a16 100644 --- a/db/migration/meta/_journal.json +++ b/db/migration/meta/_journal.json @@ -36,6 +36,13 @@ "when": 1715888872392, "tag": "0004_tidy_sauron", "breakpoints": true + }, + { + "idx": 5, + "version": "5", + "when": 1716492728623, + "tag": "0005_luxuriant_microbe", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/agency/agency.controller.ts b/src/agency/agency.controller.ts new file mode 100644 index 00000000..422374d7 --- /dev/null +++ b/src/agency/agency.controller.ts @@ -0,0 +1,14 @@ +import { Controller, Get, UseFilters } from "@nestjs/common"; +import { AgencyService } from "./agency.service"; +import { InternalServerErrorExceptionFilter } from "src/filter"; + +@UseFilters(InternalServerErrorExceptionFilter) +@Controller("agencies") +export class AgencyController { + constructor(private readonly agencyService: AgencyService) {} + + @Get() + async findMany() { + return this.agencyService.findMany(); + } +} diff --git a/src/agency/agency.module.ts b/src/agency/agency.module.ts new file mode 100644 index 00000000..2ef4bda9 --- /dev/null +++ b/src/agency/agency.module.ts @@ -0,0 +1,11 @@ +import { Module } from "@nestjs/common"; +import { AgencyService } from "./agency.service"; +import { AgencyController } from "./agency.controller"; +import { AgencyRepository } from "./agency.repository"; + +@Module({ + exports: [AgencyService], + providers: [AgencyService, AgencyRepository], + controllers: [AgencyController], +}) +export class AgencyModule {} diff --git a/src/agency/agency.repository.schema.ts b/src/agency/agency.repository.schema.ts new file mode 100644 index 00000000..aa884f6d --- /dev/null +++ b/src/agency/agency.repository.schema.ts @@ -0,0 +1,6 @@ +import { agencyEntitySchema } from "src/schema/agency"; +import { z } from "zod"; + +export const findManyRepoSchema = z.array(agencyEntitySchema); + +export type FindManyRepo = z.infer; diff --git a/src/agency/agency.repository.ts b/src/agency/agency.repository.ts new file mode 100644 index 00000000..c1314662 --- /dev/null +++ b/src/agency/agency.repository.ts @@ -0,0 +1,19 @@ +import { Inject } from "@nestjs/common"; +import { DB, DbType } from "src/global/providers/db.provider"; +import { DataRetrievalException } from "src/exception"; +import { FindManyRepo } from "./agency.repository.schema"; + +export class AgencyRepository { + constructor( + @Inject(DB) + private readonly db: DbType, + ) {} + + async findMany(): Promise { + try { + return await this.db.query.agency.findMany(); + } catch { + throw new DataRetrievalException(); + } + } +} diff --git a/src/agency/agency.service.spec.ts b/src/agency/agency.service.spec.ts new file mode 100644 index 00000000..021a3c7d --- /dev/null +++ b/src/agency/agency.service.spec.ts @@ -0,0 +1,27 @@ +import { AgencyRepositoryMock } from "test/agency/agency.repository.mock"; +import { Test } from "@nestjs/testing"; +import { AgencyRepository } from "src/agency/agency.repository"; +import { findAgenciesQueryResponseSchema } from "src/gen"; +import { AgencyService } from "./agency.service"; + +describe("Agency service unit", () => { + let agencyService: AgencyService; + + beforeEach(async () => { + const agencyRepositoryMock = new AgencyRepositoryMock(); + + const moduleRef = await Test.createTestingModule({ + providers: [AgencyService, AgencyRepository], + }) + .overrideProvider(AgencyRepository) + .useValue(agencyRepositoryMock) + .compile(); + + agencyService = moduleRef.get(AgencyService); + }); + + it("service should return a findAgenciesQueryResponseSchema compliant object", async () => { + const agencies = await agencyService.findMany(); + expect(() => findAgenciesQueryResponseSchema.parse(agencies)).not.toThrow(); + }); +}); diff --git a/src/agency/agency.service.ts b/src/agency/agency.service.ts new file mode 100644 index 00000000..c339812d --- /dev/null +++ b/src/agency/agency.service.ts @@ -0,0 +1,17 @@ +import { Inject, Injectable } from "@nestjs/common"; +import { AgencyRepository } from "./agency.repository"; + +@Injectable() +export class AgencyService { + constructor( + @Inject(AgencyRepository) + private readonly agencyRepository: AgencyRepository, + ) {} + + async findMany() { + const agencies = await this.agencyRepository.findMany(); + return { + agencies, + }; + } +} diff --git a/src/app.module.ts b/src/app.module.ts index 3baf6851..7dd334e8 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -12,6 +12,7 @@ import { ZoningDistrictModule } from "./zoning-district/zoning-district.module"; import { DbConfig, FeatureFlagConfig, StorageConfig } from "./config"; import { GlobalModule } from "./global/global.module"; import { ZoningDistrictClassModule } from "./zoning-district-class/zoning-district-class.module"; +import { AgencyModule } from "./agency/agency.module"; @Module({ imports: [ @@ -36,6 +37,7 @@ import { ZoningDistrictClassModule } from "./zoning-district-class/zoning-distri exclude: ["/api/(.*)"], }), GlobalModule, + AgencyModule, BoroughModule, LandUseModule, TaxLotModule, diff --git a/src/gen/types/Agency.ts b/src/gen/types/Agency.ts new file mode 100644 index 00000000..36f80c62 --- /dev/null +++ b/src/gen/types/Agency.ts @@ -0,0 +1,14 @@ +export type Agency = { + /** + * @description A string of variable length containing the initials of the agency. + * @type string + * @example DOT + */ + initials: string; + /** + * @description The full name of the agency. + * @type string + * @example Department of Transportation + */ + name: string; +}; diff --git a/src/gen/types/CapitalCommitment.ts b/src/gen/types/CapitalCommitment.ts new file mode 100644 index 00000000..1e1058d5 --- /dev/null +++ b/src/gen/types/CapitalCommitment.ts @@ -0,0 +1,50 @@ +export type CapitalCommitment = { + /** + * @description A uuid used to refer to the capital commitment. + * @type string | undefined uuid + */ + id?: string; + /** + * @description A four character string used to refer to the commitment type. + * @type string | undefined + * @example DSGN + */ + type?: string; + /** + * @description A string used to refer to the date when the commitment is projected to be committed. + * @type string | undefined date + * @example 2012-04-23 + */ + plannedDate?: string; + /** + * @description A string used to refer to the budget line. + * @type string | undefined + * @example HW + */ + budgetLineCode?: string; + /** + * @description A string used to refer to the budget line. + * @type string | undefined + * @example 0002Q + */ + budgetLineId?: string; + /** + * @description A string of variable length containing the initials of the sponsoring agency. + * @type string | undefined + * @example DOT + */ + sponsoringAgencyInitials?: string; + /** + * @description A string of variable length denoting the type of budget. + * @type string | undefined + * @example Highways + */ + budgetType?: string; + /** + * @description A numeric string used to refer to the amount of total planned commitments. + * @type number | undefined + * @example 1600000 + */ + totalValue?: number; + required?: any; +}; diff --git a/src/gen/types/CapitalProject.ts b/src/gen/types/CapitalProject.ts new file mode 100644 index 00000000..acb3eeb4 --- /dev/null +++ b/src/gen/types/CapitalProject.ts @@ -0,0 +1,41 @@ +import type { CapitalProjectCategory } from "./CapitalProjectCategory"; + +export type CapitalProject = { + /** + * @description The id for the project, which combines with the managing code to make a unique id + * @type string + * @example HWPEDSF5 + */ + id: string; + /** + * @description The capital project title. + * @type string + * @example Multi-Site Pedestrian Safety Phase 5 + */ + description: string; + /** + * @description Three character string of numbers representing managing agency + * @type string + * @example 850 + */ + managingCode: string; + /** + * @description The managing agency name abbreviation or acronym + * @type string + * @example DOT + */ + managingAgencyInitials: string; + /** + * @description The starting date of the capital project + * @type date + * @example 2024-05-15T14:20:03.842Z + */ + minDate: any; + /** + * @description The ending date of the capital project + * @type date + * @example 2024-05-15T14:20:03.842Z + */ + maxDate: any; + category?: CapitalProjectCategory; +}; diff --git a/src/gen/types/CapitalProjectBudgeted.ts b/src/gen/types/CapitalProjectBudgeted.ts new file mode 100644 index 00000000..81d7011b --- /dev/null +++ b/src/gen/types/CapitalProjectBudgeted.ts @@ -0,0 +1,22 @@ +import type { CapitalProject } from "./CapitalProject"; + +export type CapitalProjectBudgeted = CapitalProject & { + /** + * @description The sum total of commitments for the capital project + * @type number + * @example 200000 + */ + commitmentsTotal: number; + /** + * @description An array containing string values representing the sponsoring agencies initials. + * @type array + * @example DOT + */ + sponsoringAgencyInitials: string[]; + /** + * @description An array containing string values representing the budget types. + * @type array + * @example Highways,Highway Bridges + */ + budgetType: string[]; +}; diff --git a/src/gen/types/CapitalProjectCategory.ts b/src/gen/types/CapitalProjectCategory.ts new file mode 100644 index 00000000..c02be798 --- /dev/null +++ b/src/gen/types/CapitalProjectCategory.ts @@ -0,0 +1,7 @@ +export const capitalProjectCategory = { + "Fixed Asset": "Fixed Asset", + "Lump Sum": "Lump Sum", + "ITT, Vehicles and Equipment": "ITT, Vehicles and Equipment", +} as const; +export type CapitalProjectCategory = + (typeof capitalProjectCategory)[keyof typeof capitalProjectCategory]; diff --git a/src/gen/types/CityCouncilDistrict.ts b/src/gen/types/CityCouncilDistrict.ts new file mode 100644 index 00000000..da7a65fc --- /dev/null +++ b/src/gen/types/CityCouncilDistrict.ts @@ -0,0 +1,8 @@ +export type CityCouncilDistrict = { + /** + * @description One or two character code to represent city council districts. + * @type string + * @example 25 + */ + id: string; +}; diff --git a/src/gen/types/CommunityDistrict.ts b/src/gen/types/CommunityDistrict.ts new file mode 100644 index 00000000..21a222bc --- /dev/null +++ b/src/gen/types/CommunityDistrict.ts @@ -0,0 +1,15 @@ +export type CommunityDistrict = { + /** + * @description The two character numeric string containing the number used to refer to the community district. + * @type string | undefined + * @example 1 + */ + id?: string; + /** + * @description A single character numeric string containing the common number used to refer to the borough. Possible values are 1-5. + * @type string | undefined + * @example 1 + */ + boroughId?: string; + required?: any; +}; diff --git a/src/gen/types/FindAgencies.ts b/src/gen/types/FindAgencies.ts new file mode 100644 index 00000000..b54035f0 --- /dev/null +++ b/src/gen/types/FindAgencies.ts @@ -0,0 +1,16 @@ +import type { Error } from "./Error"; +import type { Agency } from "./Agency"; + +export type FindAgencies400 = Error; + +export type FindAgencies500 = Error; + +/** + * @description An object containing all agencies. + */ +export type FindAgenciesQueryResponse = { + /** + * @type array + */ + agencies: Agency[]; +}; diff --git a/src/gen/types/FindCommunityDistrictsByBoroughId.ts b/src/gen/types/FindCommunityDistrictsByBoroughId.ts new file mode 100644 index 00000000..21ac78c7 --- /dev/null +++ b/src/gen/types/FindCommunityDistrictsByBoroughId.ts @@ -0,0 +1,27 @@ +import type { Error } from "./Error"; +import type { CommunityDistrict } from "./CommunityDistrict"; + +export type FindCommunityDistrictsByBoroughIdPathParams = { + /** + * @description A single character numeric string containing the common number used to refer to the borough. Possible values are 1-5. + * @type string + * @example 1 + */ + boroughId: string; +}; + +export type FindCommunityDistrictsByBoroughId400 = Error; + +export type FindCommunityDistrictsByBoroughId404 = Error; + +export type FindCommunityDistrictsByBoroughId500 = Error; + +/** + * @description An object of community district schemas for the borough + */ +export type FindCommunityDistrictsByBoroughIdQueryResponse = { + /** + * @type array + */ + communityDistricts: CommunityDistrict[]; +}; diff --git a/src/gen/types/FindTaxLots.ts b/src/gen/types/FindTaxLots.ts index d96ce563..9db1f479 100644 --- a/src/gen/types/FindTaxLots.ts +++ b/src/gen/types/FindTaxLots.ts @@ -28,13 +28,13 @@ export type FindTaxLotsQueryParams = { */ geometry?: FindTaxLotsQueryParamsGeometry; /** - * @description The longitude portion of coordinates. It must be provided when applying a spatial filter and have the same length as the latitudes. + * @description The longitude portion of coordinates. It must be provided when applying a spatial filter and have the same length as the latitudes. (If using a tool like axios, serializing the array with brackets is also supported. ex; lons[]=-74.010776&lons[]=-74.010776) * @type array | undefined * @example -74.010776,-74.010776,-74.010139,-74.010139,-74.010776 */ lons?: number[]; /** - * @description The latitude portion of coordinates. It must be provided when applying a spatial filter and have the same length as the longitudes. + * @description The latitude portion of coordinates. It must be provided when applying a spatial filter and have the same length as the longitudes. (If using a tool like axios, serializing the array with brackets is also supported. ex; lats[]=40.708649&lats[]=40.707800) * @type array | undefined */ lats?: number[]; diff --git a/src/gen/types/index.ts b/src/gen/types/index.ts index cb8cc02c..6e85f09b 100644 --- a/src/gen/types/index.ts +++ b/src/gen/types/index.ts @@ -1,7 +1,16 @@ +export * from "./Agency"; export * from "./BadRequest"; export * from "./Borough"; +export * from "./CapitalCommitment"; +export * from "./CapitalProject"; +export * from "./CapitalProjectBudgeted"; +export * from "./CapitalProjectCategory"; +export * from "./CityCouncilDistrict"; +export * from "./CommunityDistrict"; export * from "./Error"; +export * from "./FindAgencies"; export * from "./FindBoroughs"; +export * from "./FindCommunityDistrictsByBoroughId"; export * from "./FindLandUses"; export * from "./FindTaxLotByBbl"; export * from "./FindTaxLotGeoJsonByBbl"; diff --git a/src/gen/zod/agencySchema.ts b/src/gen/zod/agencySchema.ts new file mode 100644 index 00000000..af563e6a --- /dev/null +++ b/src/gen/zod/agencySchema.ts @@ -0,0 +1,10 @@ +import { z } from "zod"; + +export const agencySchema = z.object({ + initials: z + .string() + .describe( + `A string of variable length containing the initials of the agency.`, + ), + name: z.string().describe(`The full name of the agency.`), +}); diff --git a/src/gen/zod/capitalCommitmentSchema.ts b/src/gen/zod/capitalCommitmentSchema.ts new file mode 100644 index 00000000..735f42f7 --- /dev/null +++ b/src/gen/zod/capitalCommitmentSchema.ts @@ -0,0 +1,45 @@ +import { z } from "zod"; + +export const capitalCommitmentSchema = z.object({ + id: z + .string() + .describe(`A uuid used to refer to the capital commitment.`) + .uuid() + .optional(), + type: z + .string() + .describe(`A four character string used to refer to the commitment type.`) + .regex(new RegExp("^([A-z]{4})$")) + .optional(), + plannedDate: z + .string() + .describe( + `A string used to refer to the date when the commitment is projected to be committed.`, + ) + .optional(), + budgetLineCode: z + .string() + .describe(`A string used to refer to the budget line.`) + .optional(), + budgetLineId: z + .string() + .describe(`A string used to refer to the budget line.`) + .optional(), + sponsoringAgencyInitials: z + .string() + .describe( + `A string of variable length containing the initials of the sponsoring agency.`, + ) + .optional(), + budgetType: z + .string() + .describe(`A string of variable length denoting the type of budget.`) + .optional(), + totalValue: z + .number() + .describe( + `A numeric string used to refer to the amount of total planned commitments.`, + ) + .optional(), + required: z.any().optional(), +}); diff --git a/src/gen/zod/capitalProjectBudgetedSchema.ts b/src/gen/zod/capitalProjectBudgetedSchema.ts new file mode 100644 index 00000000..292a9252 --- /dev/null +++ b/src/gen/zod/capitalProjectBudgetedSchema.ts @@ -0,0 +1,23 @@ +import { z } from "zod"; + +import { capitalProjectSchema } from "./capitalProjectSchema"; + +export const capitalProjectBudgetedSchema = z + .lazy(() => capitalProjectSchema) + .schema.and( + z.object({ + commitmentsTotal: z + .number() + .describe(`The sum total of commitments for the capital project`), + sponsoringAgencyInitials: z + .array(z.string()) + .describe( + `An array containing string values representing the sponsoring agencies initials.`, + ), + budgetType: z + .array(z.string()) + .describe( + `An array containing string values representing the budget types.`, + ), + }), + ); diff --git a/src/gen/zod/capitalProjectCategorySchema.ts b/src/gen/zod/capitalProjectCategorySchema.ts new file mode 100644 index 00000000..e3e58cb4 --- /dev/null +++ b/src/gen/zod/capitalProjectCategorySchema.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const capitalProjectCategorySchema = z.enum([ + `Fixed Asset`, + `Lump Sum`, + `ITT, Vehicles and Equipment`, +]); diff --git a/src/gen/zod/capitalProjectSchema.ts b/src/gen/zod/capitalProjectSchema.ts new file mode 100644 index 00000000..d4eb07b1 --- /dev/null +++ b/src/gen/zod/capitalProjectSchema.ts @@ -0,0 +1,22 @@ +import { z } from "zod"; + +import { capitalProjectCategorySchema } from "./capitalProjectCategorySchema"; + +export const capitalProjectSchema = z.object({ + id: z + .string() + .describe( + `The id for the project, which combines with the managing code to make a unique id`, + ), + description: z.string().describe(`The capital project title.`), + managingCode: z + .string() + .describe(`Three character string of numbers representing managing agency`) + .regex(new RegExp("^([0-9]{3})$")), + managingAgencyInitials: z + .string() + .describe(`The managing agency name abbreviation or acronym`), + minDate: z.any().describe(`The starting date of the capital project`), + maxDate: z.any().describe(`The ending date of the capital project`), + category: z.lazy(() => capitalProjectCategorySchema).schema.optional(), +}); diff --git a/src/gen/zod/cityCouncilDistrictSchema.ts b/src/gen/zod/cityCouncilDistrictSchema.ts new file mode 100644 index 00000000..fd310de2 --- /dev/null +++ b/src/gen/zod/cityCouncilDistrictSchema.ts @@ -0,0 +1,8 @@ +import { z } from "zod"; + +export const cityCouncilDistrictSchema = z.object({ + id: z + .string() + .describe(`One or two character code to represent city council districts.`) + .regex(new RegExp("^([0-9]{1,2})$")), +}); diff --git a/src/gen/zod/communityDistrictSchema.ts b/src/gen/zod/communityDistrictSchema.ts new file mode 100644 index 00000000..8011815e --- /dev/null +++ b/src/gen/zod/communityDistrictSchema.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; + +export const communityDistrictSchema = z.object({ + id: z + .string() + .describe( + `The two character numeric string containing the number used to refer to the community district.`, + ) + .regex(new RegExp("^([0-9]{2})$")) + .optional(), + boroughId: z + .string() + .describe( + `A single character numeric string containing the common number used to refer to the borough. Possible values are 1-5.`, + ) + .regex(new RegExp("\\b[1-9]\\b")) + .optional(), + required: z.any().optional(), +}); diff --git a/src/gen/zod/findAgenciesSchema.ts b/src/gen/zod/findAgenciesSchema.ts new file mode 100644 index 00000000..572bfe26 --- /dev/null +++ b/src/gen/zod/findAgenciesSchema.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; + +import { errorSchema } from "./errorSchema"; +import { agencySchema } from "./agencySchema"; + +export const findAgencies400Schema = z.lazy(() => errorSchema).schema; +export const findAgencies500Schema = z.lazy(() => errorSchema).schema; + +/** + * @description An object containing all agencies. + */ +export const findAgenciesQueryResponseSchema = z.object({ + agencies: z.array(z.lazy(() => agencySchema).schema), +}); diff --git a/src/gen/zod/findCommunityDistrictsByBoroughIdSchema.ts b/src/gen/zod/findCommunityDistrictsByBoroughIdSchema.ts new file mode 100644 index 00000000..7e1fc210 --- /dev/null +++ b/src/gen/zod/findCommunityDistrictsByBoroughIdSchema.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; + +import { errorSchema } from "./errorSchema"; +import { communityDistrictSchema } from "./communityDistrictSchema"; + +export const findCommunityDistrictsByBoroughIdPathParamsSchema = z.object({ + boroughId: z + .string() + .describe( + `A single character numeric string containing the common number used to refer to the borough. Possible values are 1-5.`, + ) + .regex(new RegExp("^([0-9]{1})$")), +}); +export const findCommunityDistrictsByBoroughId400Schema = z.lazy( + () => errorSchema, +).schema; +export const findCommunityDistrictsByBoroughId404Schema = z.lazy( + () => errorSchema, +).schema; +export const findCommunityDistrictsByBoroughId500Schema = z.lazy( + () => errorSchema, +).schema; + +/** + * @description An object of community district schemas for the borough + */ +export const findCommunityDistrictsByBoroughIdQueryResponseSchema = z.object({ + communityDistricts: z.array(z.lazy(() => communityDistrictSchema).schema), +}); diff --git a/src/gen/zod/findTaxLotsSchema.ts b/src/gen/zod/findTaxLotsSchema.ts index 884ecdb4..a3625171 100644 --- a/src/gen/zod/findTaxLotsSchema.ts +++ b/src/gen/zod/findTaxLotsSchema.ts @@ -28,7 +28,7 @@ export const findTaxLotsQueryParamsSchema = z.object({ lons: z .array(z.number()) .describe( - `The longitude portion of coordinates. It must be provided when applying a spatial filter and have the same length as the latitudes.`, + `The longitude portion of coordinates. It must be provided when applying a spatial filter and have the same length as the latitudes. (If using a tool like axios, serializing the array with brackets is also supported. ex; lons[]=-74.010776&lons[]=-74.010776)`, ) .min(1) .max(5) @@ -36,7 +36,7 @@ export const findTaxLotsQueryParamsSchema = z.object({ lats: z .array(z.number()) .describe( - `The latitude portion of coordinates. It must be provided when applying a spatial filter and have the same length as the longitudes.`, + `The latitude portion of coordinates. It must be provided when applying a spatial filter and have the same length as the longitudes. (If using a tool like axios, serializing the array with brackets is also supported. ex; lats[]=40.708649&lats[]=40.707800)`, ) .min(1) .max(5) diff --git a/src/gen/zod/index.ts b/src/gen/zod/index.ts index dd06c164..ad6e407e 100644 --- a/src/gen/zod/index.ts +++ b/src/gen/zod/index.ts @@ -1,7 +1,16 @@ +export * from "./agencySchema"; export * from "./badRequestSchema"; export * from "./boroughSchema"; +export * from "./capitalCommitmentSchema"; +export * from "./capitalProjectBudgetedSchema"; +export * from "./capitalProjectCategorySchema"; +export * from "./capitalProjectSchema"; +export * from "./cityCouncilDistrictSchema"; +export * from "./communityDistrictSchema"; export * from "./errorSchema"; +export * from "./findAgenciesSchema"; export * from "./findBoroughsSchema"; +export * from "./findCommunityDistrictsByBoroughIdSchema"; export * from "./findLandUsesSchema"; export * from "./findTaxLotByBblSchema"; export * from "./findTaxLotGeoJsonByBblSchema"; diff --git a/src/schema/agency.ts b/src/schema/agency.ts index 60be7fb7..377fc116 100644 --- a/src/schema/agency.ts +++ b/src/schema/agency.ts @@ -3,7 +3,7 @@ import { z } from "zod"; export const agency = pgTable("agency", { initials: text("initials").primaryKey(), - name: text("name"), + name: text("name").notNull(), }); export const agencyEntitySchema = z.object({ diff --git a/test/agency/agency.e2e-spec.ts b/test/agency/agency.e2e-spec.ts new file mode 100644 index 00000000..05bdf129 --- /dev/null +++ b/test/agency/agency.e2e-spec.ts @@ -0,0 +1,56 @@ +import * as request from "supertest"; +import { INestApplication } from "@nestjs/common"; +import { Test } from "@nestjs/testing"; +import { DataRetrievalException } from "src/exception"; +import { HttpName } from "src/filter"; +import { AgencyRepository } from "src/agency/agency.repository"; +import { AgencyRepositoryMock } from "./agency.repository.mock"; +import { AgencyModule } from "src/agency/agency.module"; +import { findAgenciesQueryResponseSchema } from "src/gen"; + +describe("Agency e2e", () => { + let app: INestApplication; + + const agencyRepositoryMock = new AgencyRepositoryMock(); + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [AgencyModule], + }) + .overrideProvider(AgencyRepository) + .useValue(agencyRepositoryMock) + .compile(); + app = moduleRef.createNestApplication(); + await app.init(); + }); + + describe("findAgencies", () => { + it("should 200 and return all agencies", async () => { + const response = await request(app.getHttpServer()) + .get(`/agencies`) + .expect(200); + expect(() => + findAgenciesQueryResponseSchema.parse(response.body), + ).not.toThrow(); + }); + + it("should 500 when the database errors", async () => { + const dataRetrievalException = new DataRetrievalException(); + jest + .spyOn(agencyRepositoryMock, "findMany") + .mockImplementationOnce(() => { + throw dataRetrievalException; + }); + + const response = await request(app.getHttpServer()) + .get(`/agencies`) + .expect(500); + expect(response.body.message).toBe(dataRetrievalException.message); + expect(response.body.error).toBe(HttpName.INTERNAL_SEVER_ERROR); + }); + }); + + afterAll(async () => { + await app.close(); + }); +}); diff --git a/test/agency/agency.repository.mock.ts b/test/agency/agency.repository.mock.ts new file mode 100644 index 00000000..53bc7bd5 --- /dev/null +++ b/test/agency/agency.repository.mock.ts @@ -0,0 +1,10 @@ +import { findManyRepoSchema } from "src/agency/agency.repository.schema"; +import { generateMock } from "@anatine/zod-mock"; + +export class AgencyRepositoryMock { + findManyMocks = generateMock(findManyRepoSchema); + + async findMany() { + return this.findManyMocks; + } +}