Skip to content

Commit 888875d

Browse files
committed
FLS-1452 - Create and register new PreAwardApiService
This new service facilitates calls to endpoints in the Pre-Award API - the GET /forms/{name}/hash endpoint, and the GET /forms/{name}/published endpoint.
1 parent 940503e commit 888875d

File tree

5 files changed

+119
-1
lines changed

5 files changed

+119
-1
lines changed

runner/config/custom-environment-variables.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,6 @@
6969
"sentryDsn": "SENTRY_DSN",
7070
"sentryTracesSampleRate": "SENTRY_TRACES_SAMPLE_RATE",
7171
"copilotEnv": "COPILOT_ENV",
72-
"enableVirusScan": "ENABLE_VIRUS_SCAN"
72+
"enableVirusScan": "ENABLE_VIRUS_SCAN",
73+
"preAwardApiUrl": "PRE_AWARD_API_URL"
7374
}

runner/config/default.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,6 @@ module.exports = {
114114
copilotEnv: "",
115115

116116
enableVirusScan: false,
117+
118+
preAwardApiUrl: "https://api.communities.gov.localhost:4004/forms"
117119
};

runner/src/server/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import LanguagePlugin from "./plugins/LanguagePlugin";
4040
import {TranslationLoaderService} from "./services/TranslationLoaderService";
4141
import {WebhookService} from "./services/WebhookService";
4242
import {pluginLog} from "./plugins/logging";
43+
import { PreAwardApiService } from "./services/PreAwardApiService";
4344

4445
const Sentry = require('@sentry/node');
4546

@@ -133,6 +134,7 @@ async function createServer(routeConfig: RouteConfig) {
133134
await server.register(pluginAuth);
134135
await server.register(LanguagePlugin);
135136

137+
server.registerService([PreAwardApiService]);
136138
server.registerService([AdapterCacheService, NotifyService, PayService, WebhookService, AddressService, TranslationLoaderService]);
137139
if (config.isE2EModeEnabled && config.isE2EModeEnabled == "true") {
138140
console.log("E2E Mode enabled")
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { HapiServer } from "../types";
2+
import { config } from "../plugins/utils/AdapterConfigurationSchema";
3+
import Boom from "boom";
4+
import wreck from "@hapi/wreck";
5+
6+
export interface FormHashResponse {
7+
hash: string;
8+
}
9+
10+
export interface PublishedFormResponse {
11+
configuration: any;
12+
hash: string;
13+
}
14+
15+
const LOGGER_DATA = {
16+
class: "PreAwardApiService",
17+
}
18+
19+
export class PreAwardApiService {
20+
private logger: any;
21+
private apiBaseUrl: string;
22+
private wreck: typeof wreck;
23+
24+
constructor(server: HapiServer) {
25+
this.logger = server.logger;
26+
this.apiBaseUrl = config.preAwardApiUrl;
27+
this.wreck = wreck.defaults({
28+
timeout: 10000,
29+
headers: {
30+
'accept': 'application/json',
31+
'content-type': 'application/json'
32+
}
33+
});
34+
this.logger.info({
35+
...LOGGER_DATA,
36+
message: `Service initialized with base URL: ${this.apiBaseUrl}`
37+
});
38+
}
39+
40+
/**
41+
* Fetches the published form data including hash for a specific form.
42+
* This is the primary method used by the cache service when a form
43+
* is requested by a user. It only returns forms that have been
44+
* explicitly published in the Pre-Award system.
45+
*/
46+
async getPublishedForm(name: string): Promise<PublishedFormResponse | null> {
47+
const url = `${this.apiBaseUrl}/${name}/published`;
48+
this.logger.info({
49+
...LOGGER_DATA,
50+
message: `Fetching published form: ${name}`,
51+
url: url
52+
});
53+
try {
54+
const { payload } = await this.wreck.get(url, {json: true});
55+
this.logger.info({
56+
...LOGGER_DATA,
57+
message: `Successfully fetched published form: ${name}`
58+
});
59+
return payload as PublishedFormResponse;
60+
} catch (error: any) {
61+
// Handle 404 - form doesn't exist or isn't published
62+
if (error.output?.statusCode === 404) {
63+
this.logger.info({
64+
...LOGGER_DATA,
65+
message: `Form ${name} not found or not published in Pre-Award API`
66+
});
67+
return null;
68+
}
69+
// Handle other errors (network, timeout, server errors)
70+
this.logger.error({
71+
...LOGGER_DATA,
72+
message: `Failed to fetch published form ${name}`,
73+
error: error.message
74+
});
75+
// Don't expose internal error details to the client
76+
throw Boom.serverUnavailable('Pre-Award API is temporarily unavailable');
77+
}
78+
}
79+
80+
/**
81+
* Fetches just the hash of a published form.
82+
* This lightweight endpoint allows us to validate our cache without
83+
* downloading the entire form definition. We use this for periodic
84+
* cache freshness checks.
85+
*/
86+
async getFormHash(name: string): Promise<string | null> {
87+
const url = `${this.apiBaseUrl}/${name}/hash`;
88+
try {
89+
const { payload } = await this.wreck.get(url, {json: true});
90+
const data = payload as FormHashResponse;
91+
this.logger.debug({
92+
...LOGGER_DATA,
93+
message: `Retrieved hash for form ${name}`
94+
});
95+
return data.hash;
96+
} catch (error: any) {
97+
if (error.output?.statusCode === 404) {
98+
// Form doesn't exist or isn't published - this is normal
99+
return null;
100+
}
101+
// For hash validation failures, we don't want to fail the entire request.
102+
// We'll continue using the cached version and try again later.
103+
this.logger.warn({
104+
...LOGGER_DATA,
105+
message: `Could not fetch hash for form ${name}, will use cached version`,
106+
error: error.message
107+
});
108+
return null;
109+
}
110+
}
111+
}

runner/src/server/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {AdapterCacheService, S3UploadService} from "./services";
1717
import {AdapterStatusService} from "./services";
1818
import {WebhookService} from "./services/WebhookService";
1919
import {TranslationLoaderService} from "./services/TranslationLoaderService";
20+
import { PreAwardApiService } from "./services/PreAwardApiService";
2021

2122

2223
export type ChangeRequest = {
@@ -32,6 +33,7 @@ export type Services = (services: string[]) => {
3233
webhookService: WebhookService;
3334
adapterStatusService: AdapterStatusService;
3435
translationLoaderService: TranslationLoaderService;
36+
preAwardApiService: PreAwardApiService;
3537
};
3638

3739
export type RouteConfig = {

0 commit comments

Comments
 (0)