Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions plugins/fhir_functions/fhir-init/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"workspace": [],
"nodeModulesDir": "auto",
"compilerOptions": {
"lib": [
"deno.window"
],
"strict": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"imports": {
"axios": "npm:axios@1.7.7",
"./src/seed": "./src/seed.ts",
"./src/env": "./src/env.ts",
"./src/api/DbCredentialsAPI": "./src/api/DbCredentialsAPI.ts",
"./src/utils/credentialProcessor": "./src/utils/credentialProcessor.ts",
"./src/utils/type": "./src/utils/type.ts",
"./src/utils/request-util": "./src/utils/request-util.ts"
}
}
141 changes: 141 additions & 0 deletions plugins/fhir_functions/fhir-init/deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions plugins/fhir_functions/fhir-init/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { seed } from "./src/seed"

try {
await seed();
} catch (error) {
console.error("FHIR init failed:", error);
throw error;
}
Comment thread
csafreen marked this conversation as resolved.
86 changes: 86 additions & 0 deletions plugins/fhir_functions/fhir-init/src/api/DbCredentialsAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { env, services } from "../env.ts";
import { get, post } from "../utils/request-util";
import type { IDbCreateDto, IDbDto } from "../utils/type";
import http from "node:http";

export class DbCredentialsAPI {
protected readonly logger = console;
private readonly baseURL: string;
private agent: any;
private accessToken: string;
private readonly oauthUrl: string;

constructor() {
this.accessToken = "";
this.oauthUrl = env.ALP_GATEWAY_OAUTH__URL;
this.agent = new http.Agent({ keepAlive: true });
if (services.trex) {
this.baseURL = services.trex;
} else {
this.logger.error("No url is set for DbCredentialsApi");
throw new Error("No url is set for DbCredentialsApi");
}
}

async getClientCredentialsToken() {
try {
const params = {
grant_type: "client_credentials",
client_id: env.IDP__ALP_DATA__CLIENT_ID,
client_secret: env.IDP__ALP_DATA__CLIENT_SECRET,
};

const options = {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
};
const data = Object.keys(params)
.map(
(key) =>
`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`
)
.join("&");
const result = await post(this.oauthUrl, data, options);
this.accessToken = `Bearer ${result.data.access_token}`;
return this.accessToken;
} catch (error: any) {
console.error(`Error obtaining client credentials token: ${error.response?.data || error.message}`);
throw error;
}
}

private async getRequestConfig() {
return {
headers: {
Authorization: this.accessToken,
},
};
}

async getDbList(): Promise<IDbDto[]> {
try {
this.logger.info("Get database list");
const options = await this.getRequestConfig();
const url = `${this.baseURL}/trex/db/`;
const result = await get(url, options);
return result.data;
} catch (error) {
console.error(`Error while getting database list: ${error}`);
throw error;
}
}

async createDb(dto: IDbCreateDto) {
try {
this.logger.info("Create database");
const options = await this.getRequestConfig();
const url = `${this.baseURL}/trex/db/`;
const result = await post(url, dto, options);
return result.data;
} catch (error) {
console.error(`Error while creating database: ${error}`);
throw error;
}
}
}
22 changes: 22 additions & 0 deletions plugins/fhir_functions/fhir-init/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const _env = Deno.env.toObject();

const certEscapeNewLine = (str: string) => {
return str.replace(/-----BEGIN PUBLIC KEY-----(.*?)-----END PUBLIC KEY-----/gs, (match) => {
return match.replace(/\n/g, "\\n");
});
}
export const env = {
NODE_ENV: _env.NODE_ENV,
FHIR_DATABASE_CODE: _env.FHIR_DATABASE_CODE,
SERVICE_ROUTES: _env.SERVICE_ROUTES || "{}",
TREX_SQL_HOST: _env.TREX__SQL__HOST,
TREX_SQL_PORT: _env.TREX__SQL__PORT,
TREX_SQL_USER: _env.TREX__SQL__USER,
TREX_SQL_PASSWORD: _env.TREX__SQL__PASSWORD,
FHIR_DB_NAME: _env.FHIR__DB_NAME,
ALP_GATEWAY_OAUTH__URL: _env.ALP_GATEWAY_OAUTH__URL,
IDP__ALP_DATA__CLIENT_ID: _env.IDP__ALP_DATA_CLIENT_ID,
IDP__ALP_DATA__CLIENT_SECRET: _env.IDP__ALP_DATA__CLIENT_SECRET,
DB_CREDENTIALS_PUBLIC_KEYS: certEscapeNewLine(_env.DB_CREDENTIALS__PUBLIC_KEYS || "").replace('}\\n', '}'),
}
export const services = JSON.parse(env.SERVICE_ROUTES);
69 changes: 69 additions & 0 deletions plugins/fhir_functions/fhir-init/src/seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { DbCredentialsAPI } from "./api/DbCredentialsAPI";
import { env } from "./env";
import { DbCredentialProcessor } from "./utils/credentialProcessor";
import { AUTHENTICATION_MODES, IDbCreateDto, IDbCredential } from "./utils/type"

export async function seed(): Promise<void> {
try {
let logger = console;
logger.info("Adding FHIR database (trex pgwire)");

const dbCredentialsAPI = new DbCredentialsAPI();
await dbCredentialsAPI.getClientCredentialsToken()

const dbList = await dbCredentialsAPI.getDbList();
const exist = dbList.find((db) => db.code === env.FHIR_DATABASE_CODE);
if (exist && exist.dialect === "trex") {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a dialect that is trex?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FHIR db entry is created with dialect trex cause of the way its being connected in DBReader nodes.

logger.info(`Database exist: ${JSON.stringify(exist)}`);
return;
}
if (exist) {
// Stale entry from the pre-trex FHIR setup (dialect "postgres");
// POST upserts via ON CONFLICT, replacing it with the trex pgwire entry.
logger.info(`Database ${env.FHIR_DATABASE_CODE} exists with dialect "${exist.dialect}" — updating to trex`);
}
const dbCredentialProcessor = new DbCredentialProcessor();
let credentials: IDbCredential;
let encryptedPasswords: IDbCredential[] = [];
credentials = {
username: env.TREX_SQL_USER,
password: env.TREX_SQL_PASSWORD,
serviceScope: "Internal",
salt: "",
userScope: "Admin"
}
encryptedPasswords.push(await dbCredentialProcessor.encryptDbCredential(credentials))

credentials = {
username: env.TREX_SQL_USER,
password: env.TREX_SQL_PASSWORD,
serviceScope: "Internal",
salt: "",
userScope: "Read",
}
encryptedPasswords.push(await dbCredentialProcessor.encryptDbCredential(credentials))

const db: IDbCreateDto = {
name: env.FHIR_DB_NAME,
code: env.FHIR_DATABASE_CODE,
vocabSchemas: [],
credentials: encryptedPasswords,
host: env.TREX_SQL_HOST,
port: Number(env.TREX_SQL_PORT),
dialect: "trex",
authenticationMode: AUTHENTICATION_MODES.PASSWORD,
extra: {
Internal: {
"queryTimeout": 60000,
"statementTimeout": 60000,
}
}
};
const result = await dbCredentialsAPI.createDb(db);
logger.info(`Database added: ${JSON.stringify(result)}`);
return result;
} catch (error: any) {
console.error("Error seeding database:", error.response?.data || error.message);
throw error;
}
}
Loading
Loading