Skip to content

Commit

Permalink
IMN-745 - API Gateway - GET Agreement Purposes (#905)
Browse files Browse the repository at this point in the history
  • Loading branch information
ecamellini authored Sep 11, 2024
1 parent 9fe58bd commit 9910d84
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 12 deletions.
2 changes: 2 additions & 0 deletions packages/api-clients/src/apiGatewayApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ export type GetAgreementsQueryParams = QueryParametersByAlias<
"getAgreements"
>;

export type GetPurposesQueryParams = QueryParametersByAlias<Api, "getPurposes">;

export * from "./generated/apiGatewayApi.js";
2 changes: 1 addition & 1 deletion packages/api-clients/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export * as authorizationApi from "./generated/authorizationApi.js";
export * as authorizationManagementApi from "./generated/authorizationManagementApi.js";
export * as bffApi from "./bffApi.js";
export * as catalogApi from "./catalogApi.js";
export * as purposeApi from "./generated/purposeApi.js";
export * as purposeApi from "./purposeApi.js";
export * as selfcareV2ClientApi from "./generated/selfcareV2ClientApi.js";
export * as tenantApi from "./generated/tenantApi.js";
export * as apiGatewayApi from "./apiGatewayApi.js";
Expand Down
8 changes: 8 additions & 0 deletions packages/api-clients/src/purposeApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as purposeApi from "./generated/purposeApi.js";
import { QueryParametersByAlias } from "./utils.js";

type Api = typeof purposeApi.purposeApi.api;

export type GetPurposesQueryParams = QueryParametersByAlias<Api, "getPurposes">;

export * from "./generated/purposeApi.js";
1 change: 1 addition & 0 deletions packages/api-gateway/.env
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ AWS_CONFIG_FILE=aws.config.local

AGREEMENT_PROCESS_URL="http://localhost:3100"
TENANT_PROCESS_URL="http://localhost:3500"
PURPOSE_PROCESS_URL="http://localhost:3400"
40 changes: 40 additions & 0 deletions packages/api-gateway/src/api/purposeApiConverter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { apiGatewayApi, purposeApi } from "pagopa-interop-api-clients";
import { assertActivePurposeVersionExists } from "../services/validators.js";

const allowedPurposeStates: apiGatewayApi.PurposeState[] = [
apiGatewayApi.PurposeState.Values.ACTIVE,
apiGatewayApi.PurposeState.Values.SUSPENDED,
];

export function toPurposeProcessGetPurposesQueryParams(
queryParams: apiGatewayApi.GetPurposesQueryParams
): Omit<purposeApi.GetPurposesQueryParams, "offset" | "limit"> {
const { eserviceId, consumerId } = queryParams;

return {
producersIds: [],
consumersIds: consumerId ? [consumerId] : [],
eservicesIds: eserviceId ? [eserviceId] : [],
states: allowedPurposeStates,
excludeDraft: false,
};
}

export function toApiGatewayPurpose(
purpose: purposeApi.Purpose
): apiGatewayApi.Purpose {
const activePurposeVersion = [...purpose.versions]
.sort(
(a, b) =>
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
)
.find((v) => allowedPurposeStates.includes(v.state));

assertActivePurposeVersionExists(activePurposeVersion, purpose.id);

return {
id: purpose.id,
throughput: activePurposeVersion.dailyCalls,
state: activePurposeVersion.state,
};
}
14 changes: 13 additions & 1 deletion packages/api-gateway/src/clients/clientsProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { agreementApi, tenantApi } from "pagopa-interop-api-clients";
import {
agreementApi,
purposeApi,
tenantApi,
} from "pagopa-interop-api-clients";
import { config } from "../config/config.js";

export type AgreementProcessClient = ReturnType<
Expand All @@ -9,9 +13,14 @@ export type TenantProcessClient = {
tenant: ReturnType<typeof tenantApi.createTenantApiClient>;
};

export type PurposeProcessClient = ReturnType<
typeof purposeApi.createPurposeApiClient
>;

export type PagoPAInteropBeClients = {
agreementProcessClient: AgreementProcessClient;
tenantProcessClient: TenantProcessClient;
purposeProcessClient: PurposeProcessClient;
};

export function getInteropBeClients(): PagoPAInteropBeClients {
Expand All @@ -22,5 +31,8 @@ export function getInteropBeClients(): PagoPAInteropBeClients {
tenantProcessClient: {
tenant: tenantApi.createTenantApiClient(config.tenantProcessUrl),
},
purposeProcessClient: purposeApi.createPurposeApiClient(
config.purposeProcessUrl
),
};
}
12 changes: 11 additions & 1 deletion packages/api-gateway/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,19 @@ export type TenantProcessServerConfig = z.infer<
typeof TenantProcessServerConfig
>;

export const PurposeProcessServerConfig = z
.object({
PURPOSE_PROCESS_URL: APIEndpoint,
})
.transform((c) => ({
purposeProcessUrl: c.PURPOSE_PROCESS_URL,
}));

const ApiGatewayConfig = CommonHTTPServiceConfig.and(
AgreementProcessServerConfig
).and(TenantProcessServerConfig);
)
.and(TenantProcessServerConfig)
.and(PurposeProcessServerConfig);
export type ApiGatewayConfig = z.infer<typeof ApiGatewayConfig>;

export const config: ApiGatewayConfig = ApiGatewayConfig.parse(process.env);
13 changes: 12 additions & 1 deletion packages/api-gateway/src/models/errors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { agreementApi } from "pagopa-interop-api-clients";
import { agreementApi, purposeApi } from "pagopa-interop-api-clients";
import { ApiError, makeApiProblemBuilder } from "pagopa-interop-models";

export const errorCodes = {
invalidAgreementState: "0001",
producerAndConsumerParamMissing: "0002",
missingActivePurposeVersion: "0003",
};

export type ErrorCodes = keyof typeof errorCodes;
Expand All @@ -28,3 +29,13 @@ export function producerAndConsumerParamMissing(): ApiError<ErrorCodes> {
title: "Producer and Consumer param missing",
});
}

export function missingActivePurposeVersion(
purposeId: purposeApi.Purpose["id"]
): ApiError<ErrorCodes> {
return new ApiError({
detail: `There is no active version for purpose ${purposeId}`,
code: "missingActivePurposeVersion",
title: "Missing active purpose version",
});
}
25 changes: 22 additions & 3 deletions packages/api-gateway/src/routers/apiGatewayRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import {

const apiGatewayRouter = (
ctx: ZodiosContext,
{ agreementProcessClient, tenantProcessClient }: PagoPAInteropBeClients
{
agreementProcessClient,
tenantProcessClient,
purposeProcessClient,
}: PagoPAInteropBeClients
): ZodiosRouter<ZodiosEndpointDefinitions, ExpressContext> => {
const { M2M_ROLE } = userRoles;
const apiGatewayRouter = ctx.router(apiGatewayApi.gatewayApi.api, {
Expand All @@ -29,7 +33,8 @@ const apiGatewayRouter = (

const agreementService = agreementServiceBuilder(
agreementProcessClient,
tenantProcessClient
tenantProcessClient,
purposeProcessClient
);

apiGatewayRouter
Expand Down Expand Up @@ -101,7 +106,21 @@ const apiGatewayRouter = (
.get(
"/agreements/:agreementId/purposes",
authorizationMiddleware([M2M_ROLE]),
async (_req, res) => res.status(501).send()
async (req, res) => {
const ctx = fromApiGatewayAppContext(req.ctx, req.headers);

try {
const purposes = await agreementService.getAgreementPurposes(
ctx,
req.params.agreementId
);

return res.status(200).json(purposes).send();
} catch (error) {
const errorRes = makeApiProblem(error, emptyErrorMapper, ctx.logger);
return res.status(errorRes.status).json(errorRes).end();
}
}
)
.post(
"/attributes",
Expand Down
47 changes: 45 additions & 2 deletions packages/api-gateway/src/services/agreementService.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
import { agreementApi, apiGatewayApi } from "pagopa-interop-api-clients";
import { getAllFromPaginated, WithLogger } from "pagopa-interop-commons";
import {
agreementApi,
apiGatewayApi,
purposeApi,
} from "pagopa-interop-api-clients";
import {
AgreementProcessClient,
PurposeProcessClient,
TenantProcessClient,
} from "../clients/clientsProvider.js";
import { ApiGatewayAppContext } from "../utilities/context.js";
import { toApiGatewayAgreementIfNotDraft } from "../api/agreementApiConverter.js";
import { producerAndConsumerParamMissing } from "../models/errors.js";
import { toAgreementProcessGetAgreementsQueryParams } from "../api/agreementApiConverter.js";
import { toApiGatewayAgreementAttributes } from "../api/attributesApiConverter.js";
import {
toApiGatewayPurpose,
toPurposeProcessGetPurposesQueryParams,
} from "../api/purposeApiConverter.js";

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function agreementServiceBuilder(
agreementProcessClient: AgreementProcessClient,
tenantProcessClient: TenantProcessClient
tenantProcessClient: TenantProcessClient,
purposeProcessClient: PurposeProcessClient
) {
return {
getAgreements: async (
Expand Down Expand Up @@ -85,5 +95,38 @@ export function agreementServiceBuilder(

return toApiGatewayAgreementAttributes(agreement, tenant);
},

getAgreementPurposes: async (
{ logger, headers }: WithLogger<ApiGatewayAppContext>,
agreementId: agreementApi.Agreement["id"]
): Promise<apiGatewayApi.Purposes> => {
logger.info(`Retrieving Purposes for Agreement ${agreementId}`);

const agreement = await agreementProcessClient.getAgreementById({
headers,
params: {
agreementId,
},
});

const getPurposesQueryParams = toPurposeProcessGetPurposesQueryParams({
eserviceId: agreement.eserviceId,
consumerId: agreement.consumerId,
});

const purposes = await getAllFromPaginated<purposeApi.Purpose>(
async (offset, limit) =>
await purposeProcessClient.getPurposes({
headers,
queries: {
...getPurposesQueryParams,
offset,
limit,
},
})
);

return { purposes: purposes.map(toApiGatewayPurpose) };
},
};
}
20 changes: 18 additions & 2 deletions packages/api-gateway/src/services/validators.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { agreementApi, apiGatewayApi } from "pagopa-interop-api-clients";
import { invalidAgreementState } from "../models/errors.js";
import {
agreementApi,
apiGatewayApi,
purposeApi,
} from "pagopa-interop-api-clients";
import {
invalidAgreementState,
missingActivePurposeVersion,
} from "../models/errors.js";

export function assertAgreementStateNotDraft(
agreementState: agreementApi.AgreementState,
Expand All @@ -9,3 +16,12 @@ export function assertAgreementStateNotDraft(
throw invalidAgreementState(agreementState, agreementId);
}
}

export function assertActivePurposeVersionExists(
purposeVersion: purposeApi.PurposeVersion | undefined,
purposeId: purposeApi.Purpose["id"]
): asserts purposeVersion is NonNullable<purposeApi.PurposeVersion> {
if (!purposeVersion) {
throw missingActivePurposeVersion(purposeId);
}
}
2 changes: 1 addition & 1 deletion packages/purpose-process/.env
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ S3_SERVER_PORT=9000
RISK_ANALYSIS_DOCUMENTS_PATH="risk-analysis/docs"

WELL_KNOWN_URLS="http://127.0.0.1:4500/jwks.json"
ACCEPTED_AUDIENCES="dev.interop.pagopa.it/ui,refactor.dev.interop.pagopa.it/ui"
ACCEPTED_AUDIENCES="dev.interop.pagopa.it/ui,refactor.dev.interop.pagopa.it/ui,dev.interop.pagopa.it/m2m,refactor.dev.interop.pagopa.it/m2m"

0 comments on commit 9910d84

Please sign in to comment.