Skip to content

Commit

Permalink
Fix duplicate postgres database connection issue (#2961)
Browse files Browse the repository at this point in the history
* refactor pg-promise to connect as a singleton

* update import path for db functions

* fix other imports

* couple more spots that needed the change

* fix typing

* fix tests after merge

* remove logs

* iron out merge issues

* remove unneeded export
  • Loading branch information
gordonfarrell authored Dec 3, 2024
1 parent 2febfbd commit 03fb4b4
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 28 deletions.
9 changes: 5 additions & 4 deletions containers/ecr-viewer/src/app/api/conditions/service.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
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.
* @returns A promise resolving to a NextResponse object.
* @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 {
Expand All @@ -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 },
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { NextRequest, NextResponse } from "next/server";
import pgPromise from "pg-promise";
import { GetObjectCommand } from "@aws-sdk/client-s3";
import {
BlobClient,
BlobDownloadResponseParsed,
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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
Expand Down Expand Up @@ -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 },
);
Expand Down
41 changes: 39 additions & 2 deletions containers/ecr-viewer/src/app/api/services/postgres_db.ts
Original file line number Diff line number Diff line change
@@ -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 = <T>(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<any>;
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<IDatabaseScope>("my-app-database-space", () => {
return {
database: pgPromise(db_url),
pgPromise,
};
});
};
42 changes: 29 additions & 13 deletions containers/ecr-viewer/src/app/api/tests/conditions.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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", () => ({
Expand All @@ -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();
});
Expand All @@ -35,17 +50,15 @@ 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),
};
return callback(t);
});

(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();
Expand All @@ -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();
Expand All @@ -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);
});
});

Expand Down
5 changes: 3 additions & 2 deletions containers/ecr-viewer/src/app/services/listEcrDataService.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down

0 comments on commit 03fb4b4

Please sign in to comment.