Skip to content

Commit

Permalink
Added /capital-projects/ endpoint
Browse files Browse the repository at this point in the history
closes #404

Co-authored-by: tangoyankee <[email protected]>
  • Loading branch information
dhochbaum-dcp and TangoYankee committed Jan 31, 2025
1 parent 7511292 commit 9bd287e
Show file tree
Hide file tree
Showing 14 changed files with 315 additions and 0 deletions.
2 changes: 2 additions & 0 deletions openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ paths:
paths/boroughs_{boroughId}_community-districts_{communityDistrictId}_capital-projects_{z}_{x}_{y}.pbf.yaml
/capital-commitment-types:
$ref: paths/capital-commitment-types.yaml
/capital-projects:
$ref: paths/capital-projects.yaml
/capital-projects/{managingCode}/{capitalProjectId}/capital-commitments:
$ref: >-
paths/capital-projects_{managingCode}_{capitalProjectId}_capital-commitments.yaml
Expand Down
21 changes: 21 additions & 0 deletions openapi/paths/capital-projects.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
get:
summary: Find paginated capital projects.
operationId: findCapitalProjects
tags:
- Capital Projects
parameters:
- $ref: ../components/parameters/limitParam.yaml
- $ref: ../components/parameters/offsetParam.yaml
responses:
'200':
description: >-
An object containing pagination metadata and an array of capital
projects
content:
application/json:
schema:
$ref: ../components/schemas/CapitalProjectPage.yaml
'400':
$ref: ../components/responses/BadRequest.yaml
'500':
$ref: ../components/responses/InternalServerError.yaml
11 changes: 11 additions & 0 deletions src/capital-project/capital-project.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Controller,
Get,
Param,
Query,
Res,
UseFilters,
UsePipes,
Expand All @@ -12,6 +13,7 @@ import {
FindCapitalProjectByManagingCodeCapitalProjectIdPathParams,
FindCapitalProjectGeoJsonByManagingCodeCapitalProjectIdPathParams,
FindCapitalProjectTilesPathParams,
FindCapitalProjectsQueryParams,
findCapitalCommitmentsByManagingCodeCapitalProjectIdPathParamsSchema,
findCapitalProjectByManagingCodeCapitalProjectIdPathParamsSchema,
findCapitalProjectGeoJsonByManagingCodeCapitalProjectIdPathParamsSchema,
Expand All @@ -24,6 +26,7 @@ import {
NotFoundExceptionFilter,
} from "src/filter";
import { ZodTransformPipe } from "src/pipes/zod-transform-pipe";
import { findCapitalProjectsQueryParamsSchema } from "src/gen/zod/findCapitalProjectsSchema";
@UseFilters(
BadRequestExceptionFilter,
InternalServerErrorExceptionFilter,
Expand All @@ -33,6 +36,14 @@ import { ZodTransformPipe } from "src/pipes/zod-transform-pipe";
export class CapitalProjectController {
constructor(private readonly capitalProjectService: CapitalProjectService) {}

@Get("/")
async findMany(
@Query(new ZodTransformPipe(findCapitalProjectsQueryParamsSchema))
queryParams: FindCapitalProjectsQueryParams,
) {
return await this.capitalProjectService.findMany(queryParams);
}

@UsePipes(
new ZodTransformPipe(
findCapitalProjectByManagingCodeCapitalProjectIdPathParamsSchema,
Expand Down
8 changes: 8 additions & 0 deletions src/capital-project/capital-project.repository.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import {
import { mvtEntitySchema } from "src/schema/mvt";
import { z } from "zod";

export const findCapitalProjectsRepoSchema = z.array(
capitalProjectEntitySchema,
);

export type FindCapitalProjectsRepo = z.infer<
typeof findCapitalProjectsRepoSchema
>;

export const checkByManagingCodeCapitalProjectIdRepoSchema =
capitalProjectEntitySchema.pick({
id: true,
Expand Down
28 changes: 28 additions & 0 deletions src/capital-project/capital-project.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
FindByManagingCodeCapitalProjectIdRepo,
FindCapitalCommitmentsByManagingCodeCapitalProjectIdRepo,
FindGeoJsonByManagingCodeCapitalProjectIdRepo,
FindCapitalProjectsRepo,
FindTilesRepo,
} from "./capital-project.repository.schema";

Expand All @@ -30,6 +31,33 @@ export class CapitalProjectRepository {
private readonly db: DbType,
) {}

async findMany({
limit,
offset,
}: {
limit: number;
offset: number;
}): Promise<FindCapitalProjectsRepo> {
try {
return await this.db
.select({
id: capitalProject.id,
description: capitalProject.description,
managingCode: capitalProject.managingCode,
managingAgency: capitalProject.managingAgency,
maxDate: capitalProject.maxDate,
minDate: capitalProject.minDate,
category: sql<CapitalProjectCategory>`${capitalProject.category}`,
})
.from(capitalProject)
.limit(limit)
.offset(offset)
.orderBy(capitalProject.managingCode, capitalProject.id);
} catch {
throw new DataRetrievalException();
}
}

#checkByManagingCodeCapitalProjectId = this.db.query.capitalProject
.findFirst({
columns: {
Expand Down
18 changes: 18 additions & 0 deletions src/capital-project/capital-project.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
findCapitalProjectByManagingCodeCapitalProjectIdQueryResponseSchema,
findCapitalProjectGeoJsonByManagingCodeCapitalProjectIdQueryResponseSchema,
findCapitalProjectTilesQueryResponseSchema,
findCapitalProjectsQueryResponseSchema,
} from "src/gen";
import { ResourceNotFoundException } from "src/exception";

Expand All @@ -28,6 +29,23 @@ describe("CapitalProjectService", () => {
);
});

describe("findMany", () => {
it("should return a list of capital projects", async () => {
const capitalProjectsResponse = await capitalProjectService.findMany({});
expect(() =>
findCapitalProjectsQueryResponseSchema.parse(capitalProjectsResponse),
).not.toThrow();

const parsedBody = findCapitalProjectsQueryResponseSchema.parse(
capitalProjectsResponse,
);
expect(parsedBody.limit).toBe(20);
expect(parsedBody.offset).toBe(0);
expect(parsedBody.total).toBe(parsedBody.capitalProjects.length);
expect(parsedBody.order).toBe("managingCode, capitalProjectId");
});
});

describe("findByManagingCodeCapitalProjectId", () => {
it("should return a capital project with budget details", async () => {
const capitalProjectMock =
Expand Down
16 changes: 16 additions & 0 deletions src/capital-project/capital-project.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
FindCapitalProjectByManagingCodeCapitalProjectIdPathParams,
FindCapitalProjectGeoJsonByManagingCodeCapitalProjectIdPathParams,
FindCapitalProjectTilesPathParams,
FindCapitalProjectsQueryParams,
} from "src/gen";
import { CapitalProjectRepository } from "./capital-project.repository";
import { Inject } from "@nestjs/common";
Expand All @@ -19,6 +20,21 @@ export class CapitalProjectService {
private readonly capitalProjectRepository: CapitalProjectRepository,
) {}

async findMany({ limit = 20, offset = 0 }: FindCapitalProjectsQueryParams) {
const capitalProjects = await this.capitalProjectRepository.findMany({
limit,
offset,
});

return {
capitalProjects,
limit,
offset,
total: capitalProjects.length,
order: "managingCode, capitalProjectId",
};
}

async findByManagingCodeCapitalProjectId(
params: FindCapitalProjectByManagingCodeCapitalProjectIdPathParams,
) {
Expand Down
36 changes: 36 additions & 0 deletions src/gen/types/FindCapitalProjects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { CapitalProjectPage } from "./CapitalProjectPage";
import type { Error } from "./Error";

export type FindCapitalProjectsQueryParams = {
/**
* @description The maximum number of results to be returned in each response. The default value is 20. It must be between 1 and 100, inclusive.
* @type integer | undefined
*/
limit?: number;
/**
* @description The position in the full list to begin returning results. Default offset is 0. If the offset is beyond the end of the list, no results will be returned.
* @type integer | undefined
*/
offset?: number;
};
/**
* @description An object containing pagination metadata and an array of capital projects
*/
export type FindCapitalProjects200 = CapitalProjectPage;
/**
* @description Invalid client request
*/
export type FindCapitalProjects400 = Error;
/**
* @description Server side error
*/
export type FindCapitalProjects500 = Error;
/**
* @description An object containing pagination metadata and an array of capital projects
*/
export type FindCapitalProjectsQueryResponse = CapitalProjectPage;
export type FindCapitalProjectsQuery = {
Response: FindCapitalProjectsQueryResponse;
QueryParams: FindCapitalProjectsQueryParams;
Errors: FindCapitalProjects400 | FindCapitalProjects500;
};
1 change: 1 addition & 0 deletions src/gen/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export * from "./FindCapitalProjectGeoJsonByManagingCodeCapitalProjectId";
export * from "./FindCapitalProjectTiles";
export * from "./FindCapitalProjectTilesByBoroughIdCommunityDistrictId";
export * from "./FindCapitalProjectTilesByCityCouncilDistrictId";
export * from "./FindCapitalProjects";
export * from "./FindCapitalProjectsByBoroughIdCommunityDistrictId";
export * from "./FindCapitalProjectsByCityCouncilId";
export * from "./FindCityCouncilDistrictGeoJsonByCityCouncilDistrictId";
Expand Down
45 changes: 45 additions & 0 deletions src/gen/zod/findCapitalProjectsSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { z } from "zod";
import { capitalProjectPageSchema } from "./capitalProjectPageSchema";
import { errorSchema } from "./errorSchema";

export const findCapitalProjectsQueryParamsSchema = z
.object({
limit: z.coerce
.number()
.int()
.min(1)
.max(100)
.describe(
"The maximum number of results to be returned in each response. The default value is 20. It must be between 1 and 100, inclusive.",
)
.optional(),
offset: z.coerce
.number()
.int()
.min(0)
.describe(
"The position in the full list to begin returning results. Default offset is 0. If the offset is beyond the end of the list, no results will be returned.",
)
.optional(),
})
.optional();
/**
* @description An object containing pagination metadata and an array of capital projects
*/
export const findCapitalProjects200Schema = z.lazy(
() => capitalProjectPageSchema,
);
/**
* @description Invalid client request
*/
export const findCapitalProjects400Schema = z.lazy(() => errorSchema);
/**
* @description Server side error
*/
export const findCapitalProjects500Schema = z.lazy(() => errorSchema);
/**
* @description An object containing pagination metadata and an array of capital projects
*/
export const findCapitalProjectsQueryResponseSchema = z.lazy(
() => capitalProjectPageSchema,
);
1 change: 1 addition & 0 deletions src/gen/zod/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export * from "./findCapitalProjectTilesByCityCouncilDistrictIdSchema";
export * from "./findCapitalProjectTilesSchema";
export * from "./findCapitalProjectsByBoroughIdCommunityDistrictIdSchema";
export * from "./findCapitalProjectsByCityCouncilIdSchema";
export * from "./findCapitalProjectsSchema";
export * from "./findCityCouncilDistrictGeoJsonByCityCouncilDistrictIdSchema";
export * from "./findCityCouncilDistrictTilesSchema";
export * from "./findCityCouncilDistrictsSchema";
Expand Down
27 changes: 27 additions & 0 deletions src/gen/zod/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ import {
findCapitalCommitmentTypes400Schema,
findCapitalCommitmentTypes500Schema,
} from "./findCapitalCommitmentTypesSchema";
import {
findCapitalProjectsQueryResponseSchema,
findCapitalProjects400Schema,
findCapitalProjects500Schema,
findCapitalProjectsQueryParamsSchema,
} from "./findCapitalProjectsSchema";
import {
findCapitalCommitmentsByManagingCodeCapitalProjectIdQueryResponseSchema,
findCapitalCommitmentsByManagingCodeCapitalProjectId400Schema,
Expand Down Expand Up @@ -336,6 +342,24 @@ export const operations = {
500: findCapitalCommitmentTypes500Schema,
},
},
findCapitalProjects: {
request: undefined,
parameters: {
path: undefined,
query: findCapitalProjectsQueryParamsSchema,
header: undefined,
},
responses: {
200: findCapitalProjectsQueryResponseSchema,
400: findCapitalProjects400Schema,
500: findCapitalProjects500Schema,
default: findCapitalProjectsQueryResponseSchema,
},
errors: {
400: findCapitalProjects400Schema,
500: findCapitalProjects500Schema,
},
},
findCapitalCommitmentsByManagingCodeCapitalProjectId: {
request: undefined,
parameters: {
Expand Down Expand Up @@ -774,6 +798,9 @@ export const paths = {
"/capital-commitment-types": {
get: operations["findCapitalCommitmentTypes"],
},
"/capital-projects": {
get: operations["findCapitalProjects"],
},
"/capital-projects/{managingCode}/{capitalProjectId}/capital-commitments": {
get: operations["findCapitalCommitmentsByManagingCodeCapitalProjectId"],
},
Expand Down
Loading

0 comments on commit 9bd287e

Please sign in to comment.