diff --git a/containers/ecr-viewer/src/app/api/conditions/service.ts b/containers/ecr-viewer/src/app/api/conditions/service.ts index 0aaa2311df..842143a5e3 100644 --- a/containers/ecr-viewer/src/app/api/conditions/service.ts +++ b/containers/ecr-viewer/src/app/api/conditions/service.ts @@ -1,8 +1,7 @@ import { NextResponse } from "next/server"; -import pgPromise from "pg-promise"; -import { database } from "../services/postgres_db"; -import { get_pool } from "../services/sqlserver_db"; import sql from "mssql"; +import { get_pool } from "../services/sqlserver_db"; +import { getDB } from "../services/postgres_db"; /** * Retrieves all unique conditions from the ecr_rr_conditions table in the PostgreSQL database. @@ -10,6 +9,7 @@ import sql from "mssql"; * @throws An error if the connection to the PostgreSQL database fails. */ export const get_conditions_postgres = async () => { + const { database, pgPromise } = getDB(); const { ParameterizedQuery: PQ } = pgPromise; try { @@ -19,8 +19,9 @@ export const get_conditions_postgres = async () => { }); const conditions = await t.any(getConditions); + return NextResponse.json( - conditions.map((c) => c.condition), + conditions.map((c: any) => c.condition), { status: 200 }, ); }); diff --git a/containers/ecr-viewer/src/app/api/fhir-data/fhir-data-service.ts b/containers/ecr-viewer/src/app/api/fhir-data/fhir-data-service.ts index d1b47767ce..264b2a1fab 100644 --- a/containers/ecr-viewer/src/app/api/fhir-data/fhir-data-service.ts +++ b/containers/ecr-viewer/src/app/api/fhir-data/fhir-data-service.ts @@ -1,5 +1,4 @@ import { NextRequest, NextResponse } from "next/server"; -import pgPromise from "pg-promise"; import { GetObjectCommand } from "@aws-sdk/client-s3"; import { BlobClient, @@ -7,9 +6,11 @@ import { BlobServiceClient, } from "@azure/storage-blob"; import { loadYamlConfig, streamToJson } from "../utils"; -import { database } from "../services/postgres_db"; +import { getDB } from "../services/postgres_db"; import { s3Client } from "../services/s3Client"; +const { database, pgPromise } = getDB(); + /** * Retrieves FHIR data from PostgreSQL database based on eCR ID. * @param request - The NextRequest object containing the request information. diff --git a/containers/ecr-viewer/src/app/api/save-fhir-data/save-fhir-data-service.ts b/containers/ecr-viewer/src/app/api/save-fhir-data/save-fhir-data-service.ts index 9d1f8d6655..45d8eef5ed 100644 --- a/containers/ecr-viewer/src/app/api/save-fhir-data/save-fhir-data-service.ts +++ b/containers/ecr-viewer/src/app/api/save-fhir-data/save-fhir-data-service.ts @@ -1,7 +1,6 @@ import { BlobServiceClient } from "@azure/storage-blob"; import { NextResponse } from "next/server"; -import pgPromise from "pg-promise"; -import { database, db_url } from "../services/postgres_db"; +import { getDB } from "../services/postgres_db"; import { PutObjectCommand, PutObjectCommandOutput } from "@aws-sdk/client-s3"; import { Bundle } from "fhir/r4"; import { S3_SOURCE, AZURE_SOURCE, POSTGRES_SOURCE } from "@/app/api/utils"; @@ -11,6 +10,8 @@ import { BundleExtendedMetadata, BundleMetadata } from "./types"; import { s3Client } from "../services/s3Client"; import { get_pool } from "../services/sqlserver_db"; +const { database, pgPromise } = getDB(); + /** * Saves a FHIR bundle to a postgres database. * @async @@ -38,7 +39,7 @@ export const saveToPostgres = async (fhirBundle: Bundle, ecrId: string) => { console.error("Error inserting data to database:", error); return NextResponse.json( { - message: `Failed to insert data to database. ${error.message} url: ${db_url}`, + message: `Failed to insert data to database. ${error.message}`, }, { status: 500 }, ); diff --git a/containers/ecr-viewer/src/app/api/services/postgres_db.ts b/containers/ecr-viewer/src/app/api/services/postgres_db.ts index ef5455bc82..8455f528e7 100644 --- a/containers/ecr-viewer/src/app/api/services/postgres_db.ts +++ b/containers/ecr-viewer/src/app/api/services/postgres_db.ts @@ -1,4 +1,41 @@ import pgp from "pg-promise"; -export const db_url = process.env.DATABASE_URL || ""; -export const database = pgp()(db_url); +/** + * Global scope singleton creator for pgPromise + * @async + * @function createSingleton + * @param name A name for your singleton + * @param create Anonymous function containing what you want singleton-ized + * @returns A singleton of the provided object + */ +const createSingleton = (name: string, create: () => T): T => { + const s = Symbol.for(name); + let scope = (global as any)[s]; + if (!scope) { + scope = { ...create() }; + (global as any)[s] = scope; + } + return scope; +}; + +const pgPromise = pgp(); +const db_url = process.env.DATABASE_URL || ""; + +interface IDatabaseScope { + database: pgp.IDatabase; + pgPromise: pgp.IMain; +} + +/** + * Provides access to pgPromise DB singleton + * @function getDB + * @returns A singleton of the pgPromise DB connection + */ +export const getDB = (): IDatabaseScope => { + return createSingleton("my-app-database-space", () => { + return { + database: pgPromise(db_url), + pgPromise, + }; + }); +}; diff --git a/containers/ecr-viewer/src/app/api/tests/conditions.service.test.ts b/containers/ecr-viewer/src/app/api/tests/conditions.service.test.ts index e04b7a0049..0ef9c91614 100644 --- a/containers/ecr-viewer/src/app/api/tests/conditions.service.test.ts +++ b/containers/ecr-viewer/src/app/api/tests/conditions.service.test.ts @@ -2,7 +2,7 @@ import { get_conditions_postgres, get_conditions_sqlserver, } from "@/app/api/conditions/service"; -import { database } from "@/app/api/services/postgres_db"; +import { getDB } from "../services/postgres_db"; import { NextResponse } from "next/server"; // Mock dependencies @@ -12,10 +12,9 @@ jest.mock("mssql", () => ({ Request: jest.fn(), })); +// Mock getDB and NextResponse jest.mock("../services/postgres_db", () => ({ - database: { - tx: jest.fn(), - }, + getDB: jest.fn(), })); jest.mock("next/server", () => ({ @@ -25,6 +24,22 @@ jest.mock("next/server", () => ({ })); describe("get_conditions_postgres", () => { + const mockDatabase = { + tx: jest.fn(), + }; + + beforeEach(() => { + // Mock getDB to return the mock database + (getDB as jest.Mock).mockReturnValue({ + database: mockDatabase, + pgPromise: { + ParameterizedQuery: jest.fn().mockImplementation(({ text }: any) => ({ + text, + })), + }, + }); + }); + afterEach(() => { jest.clearAllMocks(); }); @@ -35,7 +50,7 @@ describe("get_conditions_postgres", () => { { condition: "condition2" }, ]; - (database.tx as jest.Mock).mockImplementation(async (callback: any) => { + mockDatabase.tx.mockImplementation(async (callback: any) => { const t = { any: jest.fn().mockResolvedValue(mockConditions), }; @@ -43,9 +58,7 @@ describe("get_conditions_postgres", () => { }); (NextResponse.json as jest.Mock).mockImplementation( - (data: any, options: any) => { - return { data, options }; - }, + (data: any, options: any) => ({ data, options }), ); const response = await get_conditions_postgres(); @@ -54,23 +67,24 @@ describe("get_conditions_postgres", () => { data: ["condition1", "condition2"], options: { status: 200 }, }); + expect(NextResponse.json).toHaveBeenCalledWith( ["condition1", "condition2"], { status: 200 }, ); + expect(mockDatabase.tx).toHaveBeenCalledTimes(1); }); - it("should handle error when database query fails", async () => { + it("should return an error response when database query fails", async () => { const errorMessage = "Database error"; - (database.tx as jest.Mock).mockImplementation(async () => { + // Mock database.tx to throw an error + mockDatabase.tx.mockImplementation(async () => { throw new Error(errorMessage); }); (NextResponse.json as jest.Mock).mockImplementation( - (data: any, options: any) => { - return { data, options }; - }, + (data: any, options: any) => ({ data, options }), ); const response = await get_conditions_postgres(); @@ -79,10 +93,12 @@ describe("get_conditions_postgres", () => { data: { message: errorMessage }, options: { status: 500 }, }); + expect(NextResponse.json).toHaveBeenCalledWith( { message: errorMessage }, { status: 500 }, ); + expect(mockDatabase.tx).toHaveBeenCalledTimes(1); }); }); diff --git a/containers/ecr-viewer/src/app/services/listEcrDataService.ts b/containers/ecr-viewer/src/app/services/listEcrDataService.ts index d1b3bc1335..55956241a6 100644 --- a/containers/ecr-viewer/src/app/services/listEcrDataService.ts +++ b/containers/ecr-viewer/src/app/services/listEcrDataService.ts @@ -1,11 +1,12 @@ -import { database } from "@/app/api/services/postgres_db"; import { convertUTCToLocalString, formatDate, formatDateTime, } from "@/app/services/formatService"; -import pgPromise from "pg-promise"; import { get_pool } from "../api/services/sqlserver_db"; +import { getDB } from "../api/services/postgres_db"; + +const { database, pgPromise } = getDB(); export interface CoreMetadataModel { eicr_id: string; diff --git a/containers/ecr-viewer/src/app/tests/listEcrDataService.test.tsx b/containers/ecr-viewer/src/app/tests/listEcrDataService.test.tsx index f835dc971a..cd3200ede9 100644 --- a/containers/ecr-viewer/src/app/tests/listEcrDataService.test.tsx +++ b/containers/ecr-viewer/src/app/tests/listEcrDataService.test.tsx @@ -8,21 +8,23 @@ import { generateSearchStatement, generateWhereStatementPostgres, getTotalEcrCount, - listEcrData, processCoreMetadata, + listEcrData, } from "../services/listEcrDataService"; +import { getDB } from "../api/services/postgres_db"; import { convertUTCToLocalString, formatDate, formatDateTime, } from "../services/formatService"; -import { database } from "../api/services/postgres_db"; import { get_pool } from "../api/services/sqlserver_db"; jest.mock("../api/services/sqlserver_db", () => ({ get_pool: jest.fn(), })); +const { database } = getDB(); + describe("listEcrDataService", () => { describe("process Metadata", () => { it("should return an empty array when responseBody is empty", () => { diff --git a/containers/ingestion/app/base_service.py b/containers/ingestion/app/base_service.py index bf25d81318..69a8b0dc03 100644 --- a/containers/ingestion/app/base_service.py +++ b/containers/ingestion/app/base_service.py @@ -68,7 +68,7 @@ def __init__( self.include_health_check_endpoint = include_health_check_endpoint self.app = FastAPI( title=service_name, - version="1.7.3", + version="1.7.4", contact=DIBBS_CONTACT, license_info=LICENSES[license_info], description=description, diff --git a/containers/message-parser/app/base_service.py b/containers/message-parser/app/base_service.py index bf25d81318..69a8b0dc03 100644 --- a/containers/message-parser/app/base_service.py +++ b/containers/message-parser/app/base_service.py @@ -68,7 +68,7 @@ def __init__( self.include_health_check_endpoint = include_health_check_endpoint self.app = FastAPI( title=service_name, - version="1.7.3", + version="1.7.4", contact=DIBBS_CONTACT, license_info=LICENSES[license_info], description=description, diff --git a/containers/message-refiner/app/base_service.py b/containers/message-refiner/app/base_service.py index bf25d81318..69a8b0dc03 100644 --- a/containers/message-refiner/app/base_service.py +++ b/containers/message-refiner/app/base_service.py @@ -68,7 +68,7 @@ def __init__( self.include_health_check_endpoint = include_health_check_endpoint self.app = FastAPI( title=service_name, - version="1.7.3", + version="1.7.4", contact=DIBBS_CONTACT, license_info=LICENSES[license_info], description=description, diff --git a/containers/orchestration/app/base_service.py b/containers/orchestration/app/base_service.py index bf25d81318..69a8b0dc03 100644 --- a/containers/orchestration/app/base_service.py +++ b/containers/orchestration/app/base_service.py @@ -68,7 +68,7 @@ def __init__( self.include_health_check_endpoint = include_health_check_endpoint self.app = FastAPI( title=service_name, - version="1.7.3", + version="1.7.4", contact=DIBBS_CONTACT, license_info=LICENSES[license_info], description=description, diff --git a/containers/record-linkage/app/base_service.py b/containers/record-linkage/app/base_service.py index bf25d81318..69a8b0dc03 100644 --- a/containers/record-linkage/app/base_service.py +++ b/containers/record-linkage/app/base_service.py @@ -68,7 +68,7 @@ def __init__( self.include_health_check_endpoint = include_health_check_endpoint self.app = FastAPI( title=service_name, - version="1.7.3", + version="1.7.4", contact=DIBBS_CONTACT, license_info=LICENSES[license_info], description=description, diff --git a/containers/trigger-code-reference/app/base_service.py b/containers/trigger-code-reference/app/base_service.py index bf25d81318..69a8b0dc03 100644 --- a/containers/trigger-code-reference/app/base_service.py +++ b/containers/trigger-code-reference/app/base_service.py @@ -68,7 +68,7 @@ def __init__( self.include_health_check_endpoint = include_health_check_endpoint self.app = FastAPI( title=service_name, - version="1.7.3", + version="1.7.4", contact=DIBBS_CONTACT, license_info=LICENSES[license_info], description=description, diff --git a/containers/validation/app/base_service.py b/containers/validation/app/base_service.py index bf25d81318..69a8b0dc03 100644 --- a/containers/validation/app/base_service.py +++ b/containers/validation/app/base_service.py @@ -68,7 +68,7 @@ def __init__( self.include_health_check_endpoint = include_health_check_endpoint self.app = FastAPI( title=service_name, - version="1.7.3", + version="1.7.4", contact=DIBBS_CONTACT, license_info=LICENSES[license_info], description=description, diff --git a/phdi/__init__.py b/phdi/__init__.py index 008aaa974a..acbbc1cea6 100644 --- a/phdi/__init__.py +++ b/phdi/__init__.py @@ -1 +1 @@ -__version__ = "1.7.3" +__version__ = "1.7.4" diff --git a/phdi/containers/base_service.py b/phdi/containers/base_service.py index aabd6e1ccd..b77a1272ef 100644 --- a/phdi/containers/base_service.py +++ b/phdi/containers/base_service.py @@ -59,7 +59,7 @@ def __init__( self.include_health_check_endpoint = include_health_check_endpoint self.app = FastAPI( title=service_name, - version="1.7.3", + version="1.7.4", contact=DIBBS_CONTACT, license_info=LICENSES[license_info], description=description, diff --git a/pyproject.toml b/pyproject.toml index 584fd66701..d0bbb0f2c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "phdi" -version = "v1.7.3" +version = "v1.7.4" description = "Public health data infrastructure Building Blocks is a library to help public health departments work with their data" authors = [ "Kenneth Chow ",