Skip to content

Commit

Permalink
249 OpenAPI endpoint implementation findCapitalCommitmentsByManagingC…
Browse files Browse the repository at this point in the history
…odeCapitalProjectId

     - Updated OpenAPI documentation to remove "under construction" emoji glag

     Implement find capital commitments by managing code / capital project id id endpoint
     - cap com by mng code cap proj id controller, module, repository and service
     - e2e and service unit tests

Co-authored-by: Horatio <[email protected]>
Co-authored-by: Tim <[email protected]>
  • Loading branch information
3 people committed Jul 10, 2024
1 parent 4ecdb0b commit 74b3f5d
Show file tree
Hide file tree
Showing 14 changed files with 274 additions and 24 deletions.
8 changes: 5 additions & 3 deletions openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ paths:
$ref: "#/components/responses/InternalServerError"
/capital-projects/{managingCode}/{capitalProjectId}/capital-commitments:
get:
summary: 🚧 Find capital commitments associated with a specific capital project
summary: Find capital commitments associated with a specific capital project
operationId: findCapitalCommitmentsByManagingCodeCapitalProjectId
tags:
- Capital Commitments
Expand Down Expand Up @@ -761,12 +761,14 @@ components:
type: string
description: A string used to refer to the budget line.
example: '0002Q'
sponsoringAgencies:
sponsoringAgency:
type: string
nullable: true
description: A string of variable length containing the initials of the sponsoring agency.
example: DOT
budgetType:
type: string
nullable: true
description: A string of variable length denoting the type of budget.
example: 'Highways'
totalValue:
Expand All @@ -779,7 +781,7 @@ components:
- plannedDate
- budgetLineCode
- budgetLineId
- sponsoringAgencies
- sponsoringAgency
- budgetType
- totalValue
CapitalProjectCategory:
Expand Down
18 changes: 17 additions & 1 deletion src/capital-project/capital-project.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import {
} from "@nestjs/common";
import { Response } from "express";
import {
FindCapitalCommitmentsByManagingCodeCapitalProjectIdPathParams,
FindCapitalProjectByManagingCodeCapitalProjectIdPathParams,
FindCapitalProjectTilesPathParams,
findCapitalCommitmentsByManagingCodeCapitalProjectIdPathParamsSchema,
findCapitalProjectByManagingCodeCapitalProjectIdPathParamsSchema,
findCapitalProjectTilesPathParamsSchema,
} from "src/gen";
Expand All @@ -20,7 +22,6 @@ import {
NotFoundExceptionFilter,
} from "src/filter";
import { ZodTransformPipe } from "src/pipes/zod-transform-pipe";

@UseFilters(
BadRequestExceptionFilter,
InternalServerErrorExceptionFilter,
Expand Down Expand Up @@ -54,4 +55,19 @@ export class CapitalProjectController {
res.set("Content-Type", "application/x-protobuf");
res.send(tile);
}

@UsePipes(
new ZodTransformPipe(
findCapitalCommitmentsByManagingCodeCapitalProjectIdPathParamsSchema,
),
)
@Get("/:managingCode/:capitalProjectId/capital-commitments")
async findCapitalCommitmentsByManagingCodeCapitalProjectId(
@Param()
params: FindCapitalCommitmentsByManagingCodeCapitalProjectIdPathParams,
) {
return await this.capitalProjectService.findCapitalCommitmentsByManagingCodeCapitalProjectId(
params,
);
}
}
32 changes: 32 additions & 0 deletions src/capital-project/capital-project.repository.schema.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import {
agencyBudgetEntitySchema,
agencyEntitySchema,
capitalCommitmentEntitySchema,
capitalCommitmentFundEntitySchema,
capitalProjectEntitySchema,
} from "src/schema";
import { mvtEntitySchema } from "src/schema/mvt";
import { z } from "zod";

export const checkByManagingCodeCapitalProjectIdRepoSchema =
capitalProjectEntitySchema.pick({
id: true,
managingCode: true,
});

export type CheckByManagingCodeCapitalProjectIdRepo = z.infer<
typeof checkByManagingCodeCapitalProjectIdRepoSchema
>;

export const findByManagingCodeCapitalProjectIdRepoSchema = z.array(
capitalProjectEntitySchema.extend({
sponsoringAgencies: z.array(agencyEntitySchema.shape.initials),
Expand All @@ -22,3 +33,24 @@ export type FindByManagingCodeCapitalProjectIdRepo = z.infer<
export const findTilesRepoSchema = mvtEntitySchema;

export type FindTilesRepo = z.infer<typeof findTilesRepoSchema>;

export const findCapitalCommitmentsByManagingCodeCapitalProjectIdRepoSchema =
z.array(
capitalCommitmentEntitySchema
.pick({
id: true,
type: true,
plannedDate: true,
budgetLineCode: true,
budgetLineId: true,
})
.extend({
sponsoringAgency: agencyBudgetEntitySchema.shape.sponsor,
budgetType: agencyBudgetEntitySchema.shape.type,
totalValue: capitalCommitmentFundEntitySchema.shape.value,
}),
);

export type FindCapitalCommitmentsByManagingCodeCapitalProjectIdRepo = z.infer<
typeof findCapitalCommitmentsByManagingCodeCapitalProjectIdRepoSchema
>;
69 changes: 69 additions & 0 deletions src/capital-project/capital-project.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ import { Inject } from "@nestjs/common";
import { isNotNull, sql, and, eq, sum } from "drizzle-orm";
import { DataRetrievalException } from "src/exception";
import {
FindCapitalCommitmentsByManagingCodeCapitalProjectIdPathParams,
FindCapitalProjectByManagingCodeCapitalProjectIdPathParams,
FindCapitalProjectTilesPathParams,
} from "src/gen";
import { DB, DbType } from "src/global/providers/db.provider";
import {
agencyBudget,
budgetLine,
capitalCommitment,
capitalCommitmentFund,
capitalProject,
} from "src/schema";
import {
CheckByManagingCodeCapitalProjectIdRepo,
FindByManagingCodeCapitalProjectIdRepo,
FindCapitalCommitmentsByManagingCodeCapitalProjectIdRepo,
FindTilesRepo,
} from "./capital-project.repository.schema";

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

#checkByManagingCodeCapitalProjectId = this.db.query.capitalProject
.findFirst({
columns: {
managingCode: true,
id: true,
},
where: (capitalProject, { and, eq, sql }) =>
and(
eq(capitalProject.managingCode, sql.placeholder("managingCode")),
eq(capitalProject.id, sql.placeholder("capitalProjectId")),
),
})
.prepare("checkByManagingCodeCapitalProjectId");

async checkByManagingCodeCapitalProjectId(
managingCode: string,
capitalProjectId: string,
): Promise<CheckByManagingCodeCapitalProjectIdRepo | undefined> {
return await this.#checkByManagingCodeCapitalProjectId.execute({
managingCode,
capitalProjectId,
});
}

async findByManagingCodeCapitalProjectId(
params: FindCapitalProjectByManagingCodeCapitalProjectIdPathParams,
): Promise<FindByManagingCodeCapitalProjectIdRepo> {
Expand Down Expand Up @@ -121,4 +149,45 @@ export class CapitalProjectRepository {
throw new DataRetrievalException();
}
}

async findCapitalCommitmentsByManagingCodeCapitalProjectId(
params: FindCapitalCommitmentsByManagingCodeCapitalProjectIdPathParams,
): Promise<FindCapitalCommitmentsByManagingCodeCapitalProjectIdRepo> {
const { managingCode, capitalProjectId } = params;
try {
return await this.db
.select({
id: capitalCommitment.id,
type: capitalCommitment.type,
plannedDate: capitalCommitment.plannedDate,
budgetLineCode: capitalCommitment.budgetLineCode,
budgetLineId: capitalCommitment.budgetLineId,
sponsoringAgency: sql<string>`${agencyBudget.sponsor}`,
budgetType: sql<string>`${agencyBudget.type}`,
totalValue: sql`${capitalCommitmentFund.value}`.mapWith(Number),
})
.from(capitalCommitment)
.leftJoin(
budgetLine,
and(
eq(budgetLine.id, capitalCommitment.budgetLineId),
eq(budgetLine.code, capitalCommitment.budgetLineCode),
),
)
.leftJoin(agencyBudget, eq(agencyBudget.code, budgetLine.code))
.leftJoin(
capitalCommitmentFund,
eq(capitalCommitmentFund.capitalCommitmentId, capitalCommitment.id),
)
.where(
and(
eq(capitalCommitmentFund.category, "total"),
eq(capitalCommitment.managingCode, managingCode),
eq(capitalCommitment.capitalProjectId, capitalProjectId),
),
);
} catch {
throw new DataRetrievalException();
}
}
}
32 changes: 32 additions & 0 deletions src/capital-project/capital-project.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { CapitalProjectService } from "./capital-project.service";
import { Test } from "@nestjs/testing";
import { CapitalProjectRepository } from "./capital-project.repository";
import {
findCapitalCommitmentsByManagingCodeCapitalProjectIdQueryResponseSchema,
findCapitalProjectByManagingCodeCapitalProjectIdQueryResponseSchema,
findCapitalProjectTilesQueryResponseSchema,
} from "src/gen";
Expand Down Expand Up @@ -69,4 +70,35 @@ describe("CapitalProjectService", () => {
).not.toThrow();
});
});

describe("findCapitalCommitmentsByManagingCodeCapitalProjectId", () => {
it("should return capital commitments for a capital project", async () => {
const { id: capitalProjectId, managingCode } =
capitalProjectRepository.checkByManagingCodeCapitalProjectIdMocks[0];
const result =
await capitalProjectService.findCapitalCommitmentsByManagingCodeCapitalProjectId(
{ capitalProjectId, managingCode },
);

expect(() =>
findCapitalCommitmentsByManagingCodeCapitalProjectIdQueryResponseSchema.parse(
result,
),
).not.toThrow();
});

it.only("should throw a resource error when requesting a missing project", async () => {
const missingManagingCode = "725";
const missingCapitalProjectId = "JIRO";

expect(
capitalProjectService.findCapitalCommitmentsByManagingCodeCapitalProjectId(
{
managingCode: missingManagingCode,
capitalProjectId: missingCapitalProjectId,
},
),
).rejects.toThrow(ResourceNotFoundException);
});
});
});
22 changes: 22 additions & 0 deletions src/capital-project/capital-project.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
FindCapitalCommitmentsByManagingCodeCapitalProjectIdPathParams,
FindCapitalProjectByManagingCodeCapitalProjectIdPathParams,
FindCapitalProjectTilesPathParams,
} from "src/gen";
Expand Down Expand Up @@ -27,4 +28,25 @@ export class CapitalProjectService {
async findTiles(params: FindCapitalProjectTilesPathParams) {
return await this.capitalProjectRepository.findTiles(params);
}

async findCapitalCommitmentsByManagingCodeCapitalProjectId(
params: FindCapitalCommitmentsByManagingCodeCapitalProjectIdPathParams,
) {
const capitalProjectCheck =
await this.capitalProjectRepository.checkByManagingCodeCapitalProjectId(
params.managingCode,
params.capitalProjectId,
);
if (capitalProjectCheck === undefined)
throw new ResourceNotFoundException();

const capitalCommitments =
await this.capitalProjectRepository.findCapitalCommitmentsByManagingCodeCapitalProjectId(
params,
);

return {
capitalCommitments,
};
}
}
10 changes: 6 additions & 4 deletions src/gen/schemas/CapitalCommitment.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@
"type": "string",
"example": "0002Q"
},
"sponsoringAgencies": {
"sponsoringAgency": {
"description": "A string of variable length containing the initials of the sponsoring agency.",
"type": "string",
"example": "DOT"
"example": "DOT",
"nullable": true
},
"budgetType": {
"description": "A string of variable length denoting the type of budget.",
"type": "string",
"example": "Highways"
"example": "Highways",
"nullable": true
},
"totalValue": {
"description": "A numeric string used to refer to the amount of total planned commitments.",
Expand All @@ -50,7 +52,7 @@
"plannedDate",
"budgetLineCode",
"budgetLineId",
"sponsoringAgencies",
"sponsoringAgency",
"budgetType",
"totalValue"
],
Expand Down
4 changes: 2 additions & 2 deletions src/gen/types/CapitalCommitment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ export type CapitalCommitment = {
* @description A string of variable length containing the initials of the sponsoring agency.
* @type string
*/
sponsoringAgencies: string;
sponsoringAgency: string | null;
/**
* @description A string of variable length denoting the type of budget.
* @type string
*/
budgetType: string;
budgetType: string | null;
/**
* @description A numeric string used to refer to the amount of total planned commitments.
* @type number
Expand Down
8 changes: 5 additions & 3 deletions src/gen/zod/capitalCommitmentSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ export const capitalCommitmentSchema = z.object({
budgetLineId: z.coerce
.string()
.describe("A string used to refer to the budget line."),
sponsoringAgencies: z.coerce
sponsoringAgency: z.coerce
.string()
.describe(
"A string of variable length containing the initials of the sponsoring agency.",
),
)
.nullable(),
budgetType: z.coerce
.string()
.describe("A string of variable length denoting the type of budget."),
.describe("A string of variable length denoting the type of budget.")
.nullable(),
totalValue: z.coerce
.number()
.describe(
Expand Down
6 changes: 4 additions & 2 deletions src/schema/agency-budget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { agency } from "./agency";

export const agencyBudget = pgTable("agency_budget", {
code: text("code").primaryKey(),
type: text("type"),
sponsor: text("sponsor").references(() => agency.initials),
type: text("type").notNull(),
sponsor: text("sponsor")
.notNull()
.references(() => agency.initials),
});

export const agencyBudgetEntitySchema = z.object({
Expand Down
2 changes: 1 addition & 1 deletion src/schema/capital-commitment-fund.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const capitalCommitmentFund = pgTable("capital_commitment_fund", {
() => capitalCommitment.id,
),
category: capitalFundCategoryEnum("capital_fund_category"),
value: numeric("value"),
value: numeric("value").notNull(),
});

export const capitalCommitmentFundEntitySchema = z.object({
Expand Down
Loading

0 comments on commit 74b3f5d

Please sign in to comment.