Skip to content

Commit

Permalink
Added /capital-projects/ endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
dhochbaum-dcp committed Jan 30, 2025
1 parent 7511292 commit e038daf
Show file tree
Hide file tree
Showing 13 changed files with 241 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
23 changes: 23 additions & 0 deletions openapi/paths/capital-projects.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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
'404':
$ref: ../components/responses/NotFound.yaml
'500':
$ref: ../components/responses/InternalServerError.yaml
12 changes: 12 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,8 @@ import {
FindCapitalProjectByManagingCodeCapitalProjectIdPathParams,
FindCapitalProjectGeoJsonByManagingCodeCapitalProjectIdPathParams,
FindCapitalProjectTilesPathParams,
FindCapitalProjectsQueryParams,
findCapitalProjectsQueryParamsSchema,
findCapitalCommitmentsByManagingCodeCapitalProjectIdPathParamsSchema,
findCapitalProjectByManagingCodeCapitalProjectIdPathParamsSchema,
findCapitalProjectGeoJsonByManagingCodeCapitalProjectIdPathParamsSchema,
Expand All @@ -33,6 +36,15 @@ import { ZodTransformPipe } from "src/pipes/zod-transform-pipe";
export class CapitalProjectController {
constructor(private readonly capitalProjectService: CapitalProjectService) {}

@UsePipes(new ZodTransformPipe(findCapitalProjectsQueryParamsSchema))
@Get("/")
async findCapitalProjects(
@Query(new ZodTransformPipe(findCapitalProjectsQueryParamsSchema))
queryParams: FindCapitalProjectsQueryParams,
) {
return await this.capitalProjectService.findCapitalProjects(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 findCapitalProjects({
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
11 changes: 11 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,16 @@ describe("CapitalProjectService", () => {
);
});

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

describe("findByManagingCodeCapitalProjectId", () => {
it("should return a capital project with budget details", async () => {
const capitalProjectMock =
Expand Down
11 changes: 11 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,16 @@ export class CapitalProjectService {
private readonly capitalProjectRepository: CapitalProjectRepository,
) {}

async findCapitalProjects({
limit = 20,
offset = 0,
}: FindCapitalProjectsQueryParams) {
return await this.capitalProjectRepository.findCapitalProjects({
limit,
offset,
});
}

async findByManagingCodeCapitalProjectId(
params: FindCapitalProjectByManagingCodeCapitalProjectIdPathParams,
) {
Expand Down
43 changes: 43 additions & 0 deletions src/gen/types/FindCapitalProjects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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 Requested resource does not exist or is not available
*/
export type FindCapitalProjects404 = 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
| FindCapitalProjects404
| 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
49 changes: 49 additions & 0 deletions src/gen/zod/findCapitalProjectsSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
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 Requested resource does not exist or is not available
*/
export const findCapitalProjects404Schema = 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
30 changes: 30 additions & 0 deletions src/gen/zod/operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ import {
findCapitalCommitmentTypes400Schema,
findCapitalCommitmentTypes500Schema,
} from "./findCapitalCommitmentTypesSchema";
import {
findCapitalProjectsQueryResponseSchema,
findCapitalProjects400Schema,
findCapitalProjects404Schema,
findCapitalProjects500Schema,
findCapitalProjectsQueryParamsSchema,
} from "./findCapitalProjectsSchema";
import {
findCapitalCommitmentsByManagingCodeCapitalProjectIdQueryResponseSchema,
findCapitalCommitmentsByManagingCodeCapitalProjectId400Schema,
Expand Down Expand Up @@ -336,6 +343,26 @@ export const operations = {
500: findCapitalCommitmentTypes500Schema,
},
},
findCapitalProjects: {
request: undefined,
parameters: {
path: undefined,
query: findCapitalProjectsQueryParamsSchema,
header: undefined,
},
responses: {
200: findCapitalProjectsQueryResponseSchema,
400: findCapitalProjects400Schema,
404: findCapitalProjects404Schema,
500: findCapitalProjects500Schema,
default: findCapitalProjectsQueryResponseSchema,
},
errors: {
400: findCapitalProjects400Schema,
404: findCapitalProjects404Schema,
500: findCapitalProjects500Schema,
},
},
findCapitalCommitmentsByManagingCodeCapitalProjectId: {
request: undefined,
parameters: {
Expand Down Expand Up @@ -774,6 +801,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
22 changes: 22 additions & 0 deletions test/capital-project/capital-project.repository.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
FindGeoJsonByManagingCodeCapitalProjectIdRepo,
findGeoJsonByManagingCodeCapitalProjectIdRepoSchema,
findTilesRepoSchema,
findCapitalProjectsRepoSchema,
} from "src/capital-project/capital-project.repository.schema";
import {
FindCapitalCommitmentsByManagingCodeCapitalProjectIdPathParams,
Expand All @@ -16,6 +17,27 @@ import {
} from "src/gen";

export class CapitalProjectRepositoryMock {
findCapitalProjectsMocks = Array.from(
Array(4),
(_) =>
generateMock(findCapitalProjectsRepoSchema, {
stringMap: {
minDate: () => "2018-01-01",
maxDate: () => "2045-12-31",
},
})[0],
);

async findCapitalProjects() {
return {
limit: 20,
offset: 0,
total: 4,
order: "managingCode, capitalProjectId",
capitalProjects: this.findCapitalProjectsMocks,
};
}

checkByManagingCodeCapitalProjectIdMocks = Array.from(Array(5), (_, seed) =>
generateMock(checkByManagingCodeCapitalProjectIdRepoSchema, {
seed: seed + 1,
Expand Down

0 comments on commit e038daf

Please sign in to comment.