fhirsmith 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/FHIRsmith.png +0 -0
- package/README.md +277 -0
- package/config-template.json +144 -0
- package/library/folder-setup.js +58 -0
- package/library/html-server.js +166 -0
- package/library/html.js +835 -0
- package/library/i18nsupport.js +259 -0
- package/library/languages.js +779 -0
- package/library/logger-telnet.js +205 -0
- package/library/logger.js +279 -0
- package/library/package-manager.js +876 -0
- package/library/utilities.js +196 -0
- package/library/version-utilities.js +1056 -0
- package/npmprojector/config-example.json +13 -0
- package/npmprojector/indexer.js +394 -0
- package/npmprojector/npmprojector.js +395 -0
- package/npmprojector/readme.md +174 -0
- package/npmprojector/watcher.js +335 -0
- package/package.json +119 -0
- package/packages/package-crawler.js +846 -0
- package/packages/packages-template.html +126 -0
- package/packages/packages.js +2838 -0
- package/passwords.ini +2 -0
- package/publisher/publisher-template.html +208 -0
- package/publisher/publisher.js +2167 -0
- package/publisher/task-draft.js +458 -0
- package/registry/api.js +735 -0
- package/registry/crawler.js +637 -0
- package/registry/model.js +513 -0
- package/registry/readme.md +243 -0
- package/registry/registry-data.json +121015 -0
- package/registry/registry-template.html +126 -0
- package/registry/registry.js +1395 -0
- package/registry/test-runner.js +237 -0
- package/root-template.html +124 -0
- package/server.js +524 -0
- package/shl/private-key.pem +5 -0
- package/shl/public-key.pem +18 -0
- package/shl/shl.js +1125 -0
- package/shl/vhl.js +69 -0
- package/static/FHIRsmith128.png +0 -0
- package/static/FHIRsmith16.png +0 -0
- package/static/FHIRsmith32.png +0 -0
- package/static/FHIRsmith64.png +0 -0
- package/static/assets/css/bootstrap-fhir.css +5302 -0
- package/static/assets/css/bootstrap-glyphicons.css +2 -0
- package/static/assets/css/bootstrap.css +4097 -0
- package/static/assets/css/jquery-ui.css +523 -0
- package/static/assets/css/jquery-ui.structure.css +863 -0
- package/static/assets/css/jquery-ui.structure.min.css +5 -0
- package/static/assets/css/jquery-ui.theme.css +439 -0
- package/static/assets/css/jquery-ui.theme.min.css +5 -0
- package/static/assets/css/jquery.ui.all.css +7 -0
- package/static/assets/css/modules.css +18 -0
- package/static/assets/css/project.css +367 -0
- package/static/assets/css/pygments-manni.css +66 -0
- package/static/assets/css/tags.css +74 -0
- package/static/assets/css/xml.css +2 -0
- package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
- package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
- package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
- package/static/assets/ico/favicon.ico +0 -0
- package/static/assets/ico/favicon.png +0 -0
- package/static/assets/images/fhir-logo-www.png +0 -0
- package/static/assets/images/fhir-logo.png +0 -0
- package/static/assets/images/hl7-logo.png +0 -0
- package/static/assets/images/logo_ansinew.jpg +0 -0
- package/static/assets/images/search.png +0 -0
- package/static/assets/images/stripe.png +0 -0
- package/static/assets/images/target.png +0 -0
- package/static/assets/images/tx-registry-root.gif +0 -0
- package/static/assets/images/tx-registry.png +0 -0
- package/static/assets/images/tx-server.png +0 -0
- package/static/assets/images/tx-version.png +0 -0
- package/static/assets/js/bootstrap.min.js +6 -0
- package/static/assets/js/fhir-gw.js +259 -0
- package/static/assets/js/fhir.js +2 -0
- package/static/assets/js/html5shiv.js +8 -0
- package/static/assets/js/jcookie.js +96 -0
- package/static/assets/js/jquery-ui.min.js +6 -0
- package/static/assets/js/jquery.js +10716 -0
- package/static/assets/js/jquery.min.js +2 -0
- package/static/assets/js/jquery.ui.core.js +314 -0
- package/static/assets/js/jquery.ui.draggable.js +825 -0
- package/static/assets/js/jquery.ui.mouse.js +162 -0
- package/static/assets/js/jquery.ui.resizable.js +842 -0
- package/static/assets/js/jquery.ui.widget.js +268 -0
- package/static/assets/js/json2.js +487 -0
- package/static/assets/js/jtip.js +97 -0
- package/static/assets/js/respond.min.js +6 -0
- package/static/assets/js/statuspage.js +70 -0
- package/static/assets/js/xml.js +2 -0
- package/static/dist/js/bootstrap.js +1964 -0
- package/static/favicon.png +0 -0
- package/static/fhir.css +626 -0
- package/static/icon-fhir-16.png +0 -0
- package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
- package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- package/static/images/ui-icons_222222_256x240.png +0 -0
- package/static/images/ui-icons_228ef1_256x240.png +0 -0
- package/static/images/ui-icons_ef8c08_256x240.png +0 -0
- package/static/images/ui-icons_ffd27a_256x240.png +0 -0
- package/static/images/ui-icons_ffffff_256x240.png +0 -0
- package/static/js/jquery.effects.blind.js +49 -0
- package/static/js/jquery.effects.bounce.js +78 -0
- package/static/js/jquery.effects.clip.js +54 -0
- package/static/js/jquery.effects.core.js +763 -0
- package/static/js/jquery.effects.drop.js +50 -0
- package/static/js/jquery.effects.explode.js +79 -0
- package/static/js/jquery.effects.fade.js +32 -0
- package/static/js/jquery.effects.fold.js +56 -0
- package/static/js/jquery.effects.highlight.js +50 -0
- package/static/js/jquery.effects.pulsate.js +51 -0
- package/static/js/jquery.effects.scale.js +178 -0
- package/static/js/jquery.effects.shake.js +57 -0
- package/static/js/jquery.effects.slide.js +50 -0
- package/static/js/jquery.effects.transfer.js +45 -0
- package/static/js/jquery.ui.accordion.js +611 -0
- package/static/js/jquery.ui.autocomplete.js +612 -0
- package/static/js/jquery.ui.button.js +416 -0
- package/static/js/jquery.ui.datepicker.js +1823 -0
- package/static/js/jquery.ui.dialog.js +878 -0
- package/static/js/jquery.ui.droppable.js +296 -0
- package/static/js/jquery.ui.position.js +252 -0
- package/static/js/jquery.ui.progressbar.js +109 -0
- package/static/js/jquery.ui.selectable.js +266 -0
- package/static/js/jquery.ui.slider.js +666 -0
- package/static/js/jquery.ui.sortable.js +1077 -0
- package/static/js/jquery.ui.tabs.js +758 -0
- package/stats.js +80 -0
- package/test-cache/vsac/vsac-valuesets.db +0 -0
- package/token/nginx_passport_setup.md +383 -0
- package/token/security_guide.md +294 -0
- package/token/token-template.html +330 -0
- package/token/token.js +1300 -0
- package/translations/Messages.properties +1510 -0
- package/translations/Messages_ar.properties +1399 -0
- package/translations/Messages_de.properties +836 -0
- package/translations/Messages_es.properties +737 -0
- package/translations/Messages_fr.properties +1 -0
- package/translations/Messages_ja.properties +893 -0
- package/translations/Messages_nl.properties +1357 -0
- package/translations/Messages_pt.properties +1302 -0
- package/translations/Messages_ru.properties +1 -0
- package/translations/Messages_uz.properties +1 -0
- package/translations/Messages_zh.properties +1 -0
- package/translations/rendering-phrases.properties +1128 -0
- package/translations/rendering-phrases_ar.properties +1091 -0
- package/translations/rendering-phrases_de.properties +6 -0
- package/translations/rendering-phrases_es.properties +6 -0
- package/translations/rendering-phrases_fr.properties +624 -0
- package/translations/rendering-phrases_ja.properties +21 -0
- package/translations/rendering-phrases_nl.properties +970 -0
- package/translations/rendering-phrases_pt.properties +1020 -0
- package/translations/rendering-phrases_ru.properties +1094 -0
- package/translations/rendering-phrases_uz.properties +1 -0
- package/translations/rendering-phrases_zh.properties +1 -0
- package/tx/README.md +418 -0
- package/tx/cm/cm-api.js +110 -0
- package/tx/cm/cm-database.js +735 -0
- package/tx/cm/cm-package.js +325 -0
- package/tx/cs/cs-api.js +789 -0
- package/tx/cs/cs-areacode.js +615 -0
- package/tx/cs/cs-country.js +1110 -0
- package/tx/cs/cs-cpt.js +785 -0
- package/tx/cs/cs-cs.js +1579 -0
- package/tx/cs/cs-currency.js +539 -0
- package/tx/cs/cs-db.js +1321 -0
- package/tx/cs/cs-hgvs.js +329 -0
- package/tx/cs/cs-lang.js +465 -0
- package/tx/cs/cs-loinc.js +1485 -0
- package/tx/cs/cs-mimetypes.js +238 -0
- package/tx/cs/cs-ndc.js +704 -0
- package/tx/cs/cs-omop.js +1025 -0
- package/tx/cs/cs-provider-api.js +43 -0
- package/tx/cs/cs-provider-list.js +37 -0
- package/tx/cs/cs-rxnorm.js +808 -0
- package/tx/cs/cs-snomed.js +1102 -0
- package/tx/cs/cs-ucum.js +514 -0
- package/tx/cs/cs-unii.js +271 -0
- package/tx/cs/cs-uri.js +218 -0
- package/tx/cs/cs-usstates.js +305 -0
- package/tx/dev.fhir.org.yml +14 -0
- package/tx/fixtures/test-cases-setup.json +18 -0
- package/tx/fixtures/test-cases.yml +16 -0
- package/tx/html/codesystem-operations.liquid +25 -0
- package/tx/html/home-metrics.liquid +247 -0
- package/tx/html/operations-form.liquid +148 -0
- package/tx/html/search-form.liquid +62 -0
- package/tx/html/tx-template.html +133 -0
- package/tx/html/valueset-operations.liquid +54 -0
- package/tx/importers/atc-to-fhir.js +316 -0
- package/tx/importers/import-loinc.module.js +1536 -0
- package/tx/importers/import-ndc.module.js +1088 -0
- package/tx/importers/import-rxnorm.module.js +898 -0
- package/tx/importers/import-sct.module.js +2457 -0
- package/tx/importers/import-unii.module.js +601 -0
- package/tx/importers/readme.md +453 -0
- package/tx/importers/subset-loinc.module.js +1081 -0
- package/tx/importers/subset-rxnorm.module.js +938 -0
- package/tx/importers/tx-import-base.js +351 -0
- package/tx/importers/tx-import-settings.js +310 -0
- package/tx/importers/tx-import.js +357 -0
- package/tx/library/canonical-resource.js +88 -0
- package/tx/library/capabilitystatement.js +292 -0
- package/tx/library/codesystem.js +774 -0
- package/tx/library/conceptmap.js +568 -0
- package/tx/library/designations.js +932 -0
- package/tx/library/errors.js +77 -0
- package/tx/library/extensions.js +117 -0
- package/tx/library/namingsystem.js +322 -0
- package/tx/library/operation-outcome.js +127 -0
- package/tx/library/parameters.js +105 -0
- package/tx/library/renderer.js +1559 -0
- package/tx/library/terminologycapabilities.js +418 -0
- package/tx/library/ucum-parsers.js +1029 -0
- package/tx/library/ucum-service.js +370 -0
- package/tx/library/ucum-types.js +1099 -0
- package/tx/library/valueset.js +543 -0
- package/tx/library.js +676 -0
- package/tx/ocl/cm-ocl.js +106 -0
- package/tx/ocl/cs-ocl.js +39 -0
- package/tx/ocl/vs-ocl.js +105 -0
- package/tx/operation-context.js +568 -0
- package/tx/params.js +613 -0
- package/tx/provider.js +403 -0
- package/tx/sct/ecl.js +1560 -0
- package/tx/sct/expressions.js +2077 -0
- package/tx/sct/structures.js +1396 -0
- package/tx/tx-html.js +1063 -0
- package/tx/tx.fhir.org.yml +39 -0
- package/tx/tx.js +927 -0
- package/tx/vs/vs-api.js +112 -0
- package/tx/vs/vs-database.js +786 -0
- package/tx/vs/vs-package.js +358 -0
- package/tx/vs/vs-vsac.js +366 -0
- package/tx/workers/batch-validate.js +129 -0
- package/tx/workers/batch.js +361 -0
- package/tx/workers/closure.js +32 -0
- package/tx/workers/expand.js +1845 -0
- package/tx/workers/lookup.js +407 -0
- package/tx/workers/metadata.js +467 -0
- package/tx/workers/operations.js +34 -0
- package/tx/workers/read.js +164 -0
- package/tx/workers/search.js +384 -0
- package/tx/workers/subsumes.js +334 -0
- package/tx/workers/translate.js +492 -0
- package/tx/workers/validate.js +2504 -0
- package/tx/workers/worker.js +904 -0
- package/tx/xml/capabilitystatement-xml.js +63 -0
- package/tx/xml/codesystem-xml.js +62 -0
- package/tx/xml/conceptmap-xml.js +65 -0
- package/tx/xml/namingsystem-xml.js +65 -0
- package/tx/xml/operationoutcome-xml.js +127 -0
- package/tx/xml/parameters-xml.js +312 -0
- package/tx/xml/terminologycapabilities-xml.js +64 -0
- package/tx/xml/valueset-xml.js +64 -0
- package/tx/xml/xml-base.js +603 -0
- package/vcl/vcl-parser.js +1098 -0
- package/vcl/vcl.js +253 -0
- package/windows-install.js +19 -0
- package/xig/xig-template.html +124 -0
- package/xig/xig.js +3049 -0
package/server.js
ADDED
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
//
|
|
4
|
+
// Copyright 2025, Health Intersections Pty Ltd (http://www.healthintersections.com.au)
|
|
5
|
+
//
|
|
6
|
+
// Licensed under BSD-3: https://opensource.org/license/bsd-3-clause
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
const express = require('express');
|
|
10
|
+
// const cors = require('cors');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const folders = require('./library/folder-setup'); // <-- ADD: load early
|
|
14
|
+
|
|
15
|
+
// Load configuration BEFORE logger
|
|
16
|
+
let config;
|
|
17
|
+
try {
|
|
18
|
+
const configPath = folders.filePath('config.json'); // <-- CHANGE: config now in data dir
|
|
19
|
+
const configData = fs.readFileSync(configPath, 'utf8');
|
|
20
|
+
config = JSON.parse(configData);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Failed to load configuration:', error.message);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const Logger = require('./library/logger');
|
|
27
|
+
const serverLog = Logger.getInstance().child({ module: 'server' });
|
|
28
|
+
|
|
29
|
+
const activeModules = config.modules ? Object.keys(config.modules)
|
|
30
|
+
.filter(mod => config.modules[mod].enabled)
|
|
31
|
+
.join(', ') : [];
|
|
32
|
+
serverLog.info(`Loaded Configuration. Active modules = ${activeModules}`);
|
|
33
|
+
|
|
34
|
+
// Import modules
|
|
35
|
+
const SHLModule = require('./shl/shl.js');
|
|
36
|
+
const VCLModule = require('./vcl/vcl.js');
|
|
37
|
+
const xigModule = require('./xig/xig.js');
|
|
38
|
+
const PackagesModule = require('./packages/packages.js');
|
|
39
|
+
const RegistryModule = require('./registry/registry.js');
|
|
40
|
+
const PublisherModule = require('./publisher/publisher.js');
|
|
41
|
+
const TokenModule = require('./token/token.js');
|
|
42
|
+
const NpmProjectorModule = require('./npmprojector/npmprojector.js');
|
|
43
|
+
const TXModule = require('./tx/tx.js');
|
|
44
|
+
const packageJson = require('./package.json');
|
|
45
|
+
|
|
46
|
+
const htmlServer = require('./library/html-server');
|
|
47
|
+
const ServerStats = require("./stats");
|
|
48
|
+
const {Liquid} = require("liquidjs");
|
|
49
|
+
const {escapeHtml} = require("./library/utilities");
|
|
50
|
+
htmlServer.useLog(serverLog);
|
|
51
|
+
|
|
52
|
+
const app = express();
|
|
53
|
+
|
|
54
|
+
const PORT = process.env.PORT || config.server.port || 3000;
|
|
55
|
+
|
|
56
|
+
// Middleware
|
|
57
|
+
app.use(express.raw({ type: 'application/fhir+json', limit: '50mb' }));
|
|
58
|
+
app.use(express.raw({ type: 'application/fhir+xml', limit: '50mb' }));
|
|
59
|
+
app.use(express.json({ limit: '50mb' }));
|
|
60
|
+
app.use((req, res, next) => {
|
|
61
|
+
const requestId = req.headers['x-request-id'];
|
|
62
|
+
if (requestId) {
|
|
63
|
+
res.setHeader('X-Request-Id', requestId);
|
|
64
|
+
}
|
|
65
|
+
next();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// app.use(cors(config.server.cors));
|
|
69
|
+
|
|
70
|
+
// Module instances
|
|
71
|
+
const modules = {};
|
|
72
|
+
|
|
73
|
+
let stats = null;
|
|
74
|
+
|
|
75
|
+
// Initialize modules based on configuration
|
|
76
|
+
async function initializeModules() {
|
|
77
|
+
stats = new ServerStats();
|
|
78
|
+
|
|
79
|
+
// Initialize SHL module
|
|
80
|
+
if (config.modules?.shl?.enabled) {
|
|
81
|
+
try {
|
|
82
|
+
modules.shl = new SHLModule(stats);
|
|
83
|
+
await modules.shl.initialize(config.modules.shl);
|
|
84
|
+
app.use('/shl', modules.shl.router);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
serverLog.error('Failed to initialize SHL module:', error);
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Initialize VCL module
|
|
92
|
+
if (config.modules?.vcl?.enabled) {
|
|
93
|
+
try {
|
|
94
|
+
modules.vcl = new VCLModule(stats);
|
|
95
|
+
await modules.vcl.initialize(config.modules.vcl);
|
|
96
|
+
app.use('/VCL', modules.vcl.router);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
serverLog.error('Failed to initialize VCL module:', error);
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Initialize XIG module
|
|
104
|
+
if (config.modules?.xig?.enabled) {
|
|
105
|
+
try {
|
|
106
|
+
await xigModule.initializeXigModule(stats);
|
|
107
|
+
app.use('/xig', xigModule.router);
|
|
108
|
+
modules.xig = xigModule;
|
|
109
|
+
} catch (error) {
|
|
110
|
+
serverLog.error('Failed to initialize XIG module:', error);
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Initialize Packages module
|
|
116
|
+
if (config.modules?.packages?.enabled) {
|
|
117
|
+
try {
|
|
118
|
+
modules.packages = new PackagesModule(stats);
|
|
119
|
+
await modules.packages.initialize(config.modules.packages);
|
|
120
|
+
app.use('/packages', modules.packages.router);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error('Failed to initialize Server:', error);
|
|
123
|
+
serverLog.error('Failed to initialize Packages module:', error);
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Initialize Registry module
|
|
129
|
+
if (config.modules?.registry?.enabled) {
|
|
130
|
+
try {
|
|
131
|
+
modules.registry = new RegistryModule(stats);
|
|
132
|
+
await modules.registry.initialize(config.modules.registry);
|
|
133
|
+
app.use('/tx-reg', modules.registry.router);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
serverLog.error('Failed to initialize Registry module:', error);
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Initialize Publisher module
|
|
141
|
+
if (config.modules?.publisher?.enabled) {
|
|
142
|
+
try {
|
|
143
|
+
modules.publisher = new PublisherModule(stats);
|
|
144
|
+
await modules.publisher.initialize(config.modules.publisher);
|
|
145
|
+
app.use('/publisher', modules.publisher.router);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
serverLog.error('Failed to initialize Publisher module:', error);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Initialize Token module
|
|
153
|
+
if (config.modules?.token?.enabled) {
|
|
154
|
+
try {
|
|
155
|
+
modules.token = new TokenModule(stats);
|
|
156
|
+
await modules.token.initialize(config.modules.token);
|
|
157
|
+
app.use('/token', modules.token.router);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
serverLog.error('Failed to initialize Token module:', error);
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Initialize NpmProjector module
|
|
165
|
+
if (config.modules?.npmprojector?.enabled) {
|
|
166
|
+
try {
|
|
167
|
+
modules.npmprojector = new NpmProjectorModule(stats);
|
|
168
|
+
await modules.npmprojector.initialize(config.modules.npmprojector);
|
|
169
|
+
const basePath = NpmProjectorModule.getBasePath(config.modules.npmprojector);
|
|
170
|
+
app.use(basePath, modules.npmprojector.router);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
serverLog.error('Failed to initialize NpmProjector module:', error);
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Initialize TX module
|
|
178
|
+
// Note: TX module registers its own endpoints directly on the app
|
|
179
|
+
// because it supports multiple endpoints at different paths
|
|
180
|
+
if (config.modules?.tx?.enabled) {
|
|
181
|
+
try {
|
|
182
|
+
modules.tx = new TXModule(stats);
|
|
183
|
+
await modules.tx.initialize(config.modules.tx, app);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.log(error);
|
|
186
|
+
serverLog.error('Failed to initialize TX module:', error);
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function loadTemplates() {
|
|
193
|
+
htmlServer.useLog(serverLog);
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
// Load Root template
|
|
197
|
+
const rootTemplatePath = path.join(__dirname, 'root-template.html');
|
|
198
|
+
htmlServer.loadTemplate('root', rootTemplatePath);
|
|
199
|
+
|
|
200
|
+
// Load XIG template
|
|
201
|
+
const xigTemplatePath = path.join(__dirname, 'xig', 'xig-template.html');
|
|
202
|
+
htmlServer.loadTemplate('xig', xigTemplatePath);
|
|
203
|
+
|
|
204
|
+
// Load Packages template
|
|
205
|
+
const packagesTemplatePath = path.join(__dirname, 'packages', 'packages-template.html');
|
|
206
|
+
htmlServer.loadTemplate('packages', packagesTemplatePath);
|
|
207
|
+
|
|
208
|
+
const registryTemplatePath = path.join(__dirname, 'registry', 'registry-template.html');
|
|
209
|
+
htmlServer.loadTemplate('registry', registryTemplatePath);
|
|
210
|
+
|
|
211
|
+
const publisherTemplatePath = path.join(__dirname, 'publisher', 'publisher-template.html');
|
|
212
|
+
htmlServer.loadTemplate('publisher', publisherTemplatePath);
|
|
213
|
+
|
|
214
|
+
// Load Token template
|
|
215
|
+
const tokenTemplatePath = path.join(__dirname, 'token', 'token-template.html');
|
|
216
|
+
htmlServer.loadTemplate('token', tokenTemplatePath);
|
|
217
|
+
|
|
218
|
+
} catch (error) {
|
|
219
|
+
serverLog.error('Failed to load templates:', error);
|
|
220
|
+
// Don't fail initialization if templates fail to load
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function buildRootPageContent() {
|
|
225
|
+
stats.requestCount++;
|
|
226
|
+
let mc = 0;
|
|
227
|
+
let content = '<div class="row mb-4">';
|
|
228
|
+
content += '<div class="col-12">';
|
|
229
|
+
|
|
230
|
+
content += '<h3>Available Modules</h3>';
|
|
231
|
+
content += '<ul class="list-group">';
|
|
232
|
+
|
|
233
|
+
// Check which modules are enabled and add them to the list
|
|
234
|
+
if (config.modules.packages.enabled) {
|
|
235
|
+
mc++;
|
|
236
|
+
content += '<li class="list-group-item">';
|
|
237
|
+
content += '<a href="/packages" class="text-decoration-none">Package Server</a>: Browse and download FHIR Implementation Guide packages';
|
|
238
|
+
content += '</li>';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (config.modules.xig.enabled) {
|
|
242
|
+
mc++;
|
|
243
|
+
content += '<li class="list-group-item">';
|
|
244
|
+
content += '<a href="/xig" class="text-decoration-none">FHIR IG Statistics</a>: Statistics and analysis of FHIR Implementation Guides';
|
|
245
|
+
content += '</li>';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (config.modules.shl.enabled) {
|
|
249
|
+
mc++;
|
|
250
|
+
content += '<li class="list-group-item">';
|
|
251
|
+
content += '<a href="/shl" class="text-decoration-none">SHL Server</a>: SMART Health Links management and validation';
|
|
252
|
+
content += '</li>';
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (config.modules.vcl.enabled) {
|
|
256
|
+
mc++;
|
|
257
|
+
content += '<li class="list-group-item">';
|
|
258
|
+
content += '<a href="/VCL" class="text-decoration-none">VCL Server</a>: ValueSet Compose Language expression parsing';
|
|
259
|
+
content += '</li>';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (config.modules.registry && config.modules.registry.enabled) {
|
|
263
|
+
mc++;
|
|
264
|
+
content += '<li class="list-group-item">';
|
|
265
|
+
content += '<a href="/tx-reg" class="text-decoration-none">Terminology Server Registry</a>: ';
|
|
266
|
+
content += 'Discover and query FHIR terminology servers for code system and value set support';
|
|
267
|
+
content += '</li>';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (config.modules.publisher && config.modules.publisher.enabled) {
|
|
271
|
+
mc++;
|
|
272
|
+
content += '<li class="list-group-item">';
|
|
273
|
+
content += '<a href="/publisher" class="text-decoration-none">FHIR Publisher</a>: ';
|
|
274
|
+
content += 'Manage FHIR Implementation Guide publication tasks and approvals';
|
|
275
|
+
content += '</li>';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (config.modules.token && config.modules.token.enabled) {
|
|
279
|
+
mc++;
|
|
280
|
+
content += '<li class="list-group-item">';
|
|
281
|
+
content += '<a href="/token" class="text-decoration-none">Token Server</a>: ';
|
|
282
|
+
content += 'OAuth authentication and API key management for FHIR services';
|
|
283
|
+
content += '</li>';
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (config.modules.npmprojector && config.modules.npmprojector.enabled) {
|
|
287
|
+
mc++;
|
|
288
|
+
content += '<li class="list-group-item">';
|
|
289
|
+
content += '<a href="/npmprojector" class="text-decoration-none">NpmProjector</a>: ';
|
|
290
|
+
content += 'Hot-reloading FHIR server with FHIRPath-based search indexes';
|
|
291
|
+
content += '</li>';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (config.modules.tx && config.modules.tx.enabled) {
|
|
295
|
+
content += '<li class="list-group-item">';
|
|
296
|
+
content += '<strong>TX Terminology Server</strong>: ';
|
|
297
|
+
content += 'FHIR terminology services (CodeSystem, ValueSet, ConceptMap)';
|
|
298
|
+
if (config.modules.tx.endpoints && config.modules.tx.endpoints.length > 0) {
|
|
299
|
+
content += '<ul class="mt-2 mb-0">';
|
|
300
|
+
for (const endpoint of config.modules.tx.endpoints) {
|
|
301
|
+
mc++;
|
|
302
|
+
content += `<li><a href="${endpoint.path}" class="text-decoration-none">${endpoint.path}</a> (FHIR v${endpoint.fhirVersion}${endpoint.context ? ', context: ' + endpoint.context : ''})</li>`;
|
|
303
|
+
}
|
|
304
|
+
content += '</ul>';
|
|
305
|
+
}
|
|
306
|
+
content += '</li>';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
content += '</ul>';
|
|
310
|
+
|
|
311
|
+
content += '<hr/>';
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
// Calculate uptime
|
|
315
|
+
const uptimeMs = Date.now() - stats.startTime;
|
|
316
|
+
const uptimeSeconds = Math.floor(uptimeMs / 1000);
|
|
317
|
+
const uptimeDays = Math.floor(uptimeSeconds / 86400);
|
|
318
|
+
const uptimeHours = Math.floor((uptimeSeconds % 86400) / 3600);
|
|
319
|
+
const uptimeMinutes = Math.floor((uptimeSeconds % 3600) / 60);
|
|
320
|
+
const uptimeSecs = uptimeSeconds % 60;
|
|
321
|
+
let uptimeStr = '';
|
|
322
|
+
if (uptimeDays > 0) uptimeStr += `${uptimeDays}d `;
|
|
323
|
+
if (uptimeHours > 0 || uptimeDays > 0) uptimeStr += `${uptimeHours}h `;
|
|
324
|
+
if (uptimeMinutes > 0 || uptimeHours > 0 || uptimeDays > 0) uptimeStr += `${uptimeMinutes}m `;
|
|
325
|
+
uptimeStr += `${uptimeSecs}s`;
|
|
326
|
+
|
|
327
|
+
// Memory usage
|
|
328
|
+
const memUsage = process.memoryUsage();
|
|
329
|
+
const heapUsedMB = (memUsage.heapUsed / 1024 / 1024).toFixed(2);
|
|
330
|
+
const heapTotalMB = (memUsage.heapTotal / 1024 / 1024).toFixed(2);
|
|
331
|
+
const rssMB = (memUsage.rss / 1024 / 1024).toFixed(2);
|
|
332
|
+
|
|
333
|
+
content += '<table class="grid">';
|
|
334
|
+
content += '<tr>';
|
|
335
|
+
content += `<td><strong>Module Count:</strong> ${mc}</td>`;
|
|
336
|
+
content += `<td><strong>Uptime:</strong> ${escapeHtml(uptimeStr)}</td>`;
|
|
337
|
+
content += `<td><strong>Request Count:</strong> ${stats.requestCount}</td>`;
|
|
338
|
+
content += '</tr>';
|
|
339
|
+
content += '<tr>';
|
|
340
|
+
content += `<td><strong>Heap Used:</strong> ${heapUsedMB} MB</td>`;
|
|
341
|
+
content += `<td><strong>Heap Total:</strong> ${heapTotalMB} MB</td>`;
|
|
342
|
+
content += `<td><strong>Process Memory:</strong> ${rssMB} MB</td>`;
|
|
343
|
+
content += '</tr>';
|
|
344
|
+
|
|
345
|
+
content += '</table>';
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
// ===== Metrics Graphs =====
|
|
349
|
+
|
|
350
|
+
const liquid = new Liquid({
|
|
351
|
+
root: path.join(__dirname, 'tx', 'html'),
|
|
352
|
+
extname: '.liquid'
|
|
353
|
+
});
|
|
354
|
+
content += await liquid.renderFile('home-metrics', {
|
|
355
|
+
historyJson: JSON.stringify(stats.history),
|
|
356
|
+
startTime: stats.startTime
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
content += '</div>';
|
|
360
|
+
return content;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// eslint-disable-next-line no-unused-vars
|
|
364
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
365
|
+
console.error('Unhandled Rejection:', reason);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
app.get('/', async (req, res) => {
|
|
369
|
+
// Check if client wants HTML response
|
|
370
|
+
const acceptsHtml = req.headers.accept && req.headers.accept.includes('text/html');
|
|
371
|
+
|
|
372
|
+
if (acceptsHtml) {
|
|
373
|
+
try {
|
|
374
|
+
const startTime = Date.now();
|
|
375
|
+
|
|
376
|
+
// Load template if not already loaded
|
|
377
|
+
if (!htmlServer.hasTemplate('root')) {
|
|
378
|
+
const templatePath = path.join(__dirname, 'root-template.html');
|
|
379
|
+
htmlServer.loadTemplate('root', templatePath);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const content = await buildRootPageContent();
|
|
383
|
+
|
|
384
|
+
// Build basic stats for root page
|
|
385
|
+
const stats = {
|
|
386
|
+
version: packageJson.version,
|
|
387
|
+
enabledModules: Object.keys(config.modules).filter(m => config.modules[m].enabled).length,
|
|
388
|
+
processingTime: Date.now() - startTime
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const html = htmlServer.renderPage('root', config.hostName || 'FHIRsmith Server', content, stats);
|
|
392
|
+
res.setHeader('Content-Type', 'text/html');
|
|
393
|
+
res.send(html);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
serverLog.error('Error rendering root page:', error);
|
|
396
|
+
htmlServer.sendErrorResponse(res, 'root', error);
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
// Return JSON response for API clients
|
|
400
|
+
const enabledModules = {};
|
|
401
|
+
Object.keys(config.modules).forEach(moduleName => {
|
|
402
|
+
if (config.modules[moduleName].enabled) {
|
|
403
|
+
if (moduleName === 'tx') {
|
|
404
|
+
// TX module has multiple endpoints
|
|
405
|
+
enabledModules[moduleName] = {
|
|
406
|
+
enabled: true,
|
|
407
|
+
endpoints: config.modules.tx.endpoints.map(e => ({
|
|
408
|
+
path: e.path,
|
|
409
|
+
fhirVersion: e.fhirVersion,
|
|
410
|
+
context: e.context || null
|
|
411
|
+
}))
|
|
412
|
+
};
|
|
413
|
+
} else {
|
|
414
|
+
enabledModules[moduleName] = {
|
|
415
|
+
enabled: true,
|
|
416
|
+
endpoint: moduleName === 'vcl' ? '/VCL' : `/${moduleName}`
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
res.json({
|
|
423
|
+
message: 'FHIR Development Server',
|
|
424
|
+
version: '1.0.0',
|
|
425
|
+
modules: enabledModules,
|
|
426
|
+
endpoints: {
|
|
427
|
+
health: '/health',
|
|
428
|
+
...Object.fromEntries(
|
|
429
|
+
Object.keys(enabledModules)
|
|
430
|
+
.filter(m => m !== 'tx')
|
|
431
|
+
.map(m => [
|
|
432
|
+
m,
|
|
433
|
+
m === 'vcl' ? '/VCL' : `/${m}`
|
|
434
|
+
])
|
|
435
|
+
),
|
|
436
|
+
// Add TX endpoints separately
|
|
437
|
+
...(enabledModules.tx ? {
|
|
438
|
+
tx: config.modules.tx.endpoints.map(e => e.path)
|
|
439
|
+
} : {})
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
// Serve static files
|
|
447
|
+
app.use(express.static(path.join(__dirname, 'static')));
|
|
448
|
+
|
|
449
|
+
// Health check endpoint
|
|
450
|
+
app.get('/health', async (req, res) => {
|
|
451
|
+
const healthStatus = {
|
|
452
|
+
status: 'OK',
|
|
453
|
+
timestamp: new Date().toISOString(),
|
|
454
|
+
modules: {}
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
// Get status from each enabled module
|
|
458
|
+
Object.keys(modules).forEach(moduleName => {
|
|
459
|
+
if (modules[moduleName] && typeof modules[moduleName].getStatus === 'function') {
|
|
460
|
+
healthStatus.modules[moduleName] = modules[moduleName].getStatus();
|
|
461
|
+
} else if (moduleName === 'xig') {
|
|
462
|
+
// XIG has different status check
|
|
463
|
+
let xigStatus = 'Enabled';
|
|
464
|
+
if (modules.xig && modules.xig.isCacheLoaded && modules.xig.isCacheLoaded()) {
|
|
465
|
+
xigStatus = 'Running';
|
|
466
|
+
} else {
|
|
467
|
+
xigStatus = 'Enabled but not loaded';
|
|
468
|
+
}
|
|
469
|
+
healthStatus.modules.xig = { enabled: true, status: xigStatus };
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
res.json(healthStatus);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Initialize everything
|
|
477
|
+
async function startServer() {
|
|
478
|
+
try {
|
|
479
|
+
// Load HTML templates
|
|
480
|
+
await loadTemplates();
|
|
481
|
+
|
|
482
|
+
// Initialize modules
|
|
483
|
+
await initializeModules().catch(error => {
|
|
484
|
+
serverLog.error('Failed to initialize modules:', error);
|
|
485
|
+
throw error;
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// Start server
|
|
489
|
+
app.listen(PORT, () => {
|
|
490
|
+
stats.markStarted();
|
|
491
|
+
serverLog.info(`=== Server running on http://localhost:${PORT} ===`);
|
|
492
|
+
});
|
|
493
|
+
if (modules.packages && config.modules.packages.enabled) {
|
|
494
|
+
modules.packages.startInitialCrawler();
|
|
495
|
+
}
|
|
496
|
+
} catch (error) {
|
|
497
|
+
serverLog.error('Failed to start server:', error);
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Graceful shutdown
|
|
503
|
+
process.on('SIGINT', async () => {
|
|
504
|
+
serverLog.info('\nShutting down server...');
|
|
505
|
+
|
|
506
|
+
// Shutdown all modules
|
|
507
|
+
for (const [moduleName, moduleInstance] of Object.entries(modules)) {
|
|
508
|
+
try {
|
|
509
|
+
if (moduleInstance && typeof moduleInstance.shutdown === 'function') {
|
|
510
|
+
serverLog.info(`Shutting down ${moduleName} module...`);
|
|
511
|
+
await moduleInstance.shutdown();
|
|
512
|
+
serverLog.info(`${moduleName} module shut down`);
|
|
513
|
+
}
|
|
514
|
+
} catch (error) {
|
|
515
|
+
serverLog.error(`Error shutting down ${moduleName} module:`, error);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
stats.finishStats();
|
|
519
|
+
serverLog.info('Server shutdown complete');
|
|
520
|
+
process.exit(0);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// Start the server
|
|
524
|
+
startServer();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIICzTCCAnOgAwIBAgIUCDt7I+MKUnXKEP+V6o2GDtpy5QcwCgYIKoZIzj0EAwIw
|
|
3
|
+
gYIxCzAJBgNVBAYTAlhYMREwDwYDVQQIDAhWaWN0b3JpYTETMBEGA1UEBwwKV2Fy
|
|
4
|
+
cmFuZHl0ZTEcMBoGA1UECgwTSGVhbHRoSW50ZXJzZWN0aW9uczEMMAoGA1UECwwD
|
|
5
|
+
RGV2MR8wHQYDVQQDDBZIZWFsdGhJbnRlcnNlY3Rpb25zRGV2MB4XDTI1MDYxOTE0
|
|
6
|
+
MTU0NVoXDTI3MDYxOTE0MTU0NVowgYIxCzAJBgNVBAYTAlhYMREwDwYDVQQIDAhW
|
|
7
|
+
aWN0b3JpYTETMBEGA1UEBwwKV2FycmFuZHl0ZTEcMBoGA1UECgwTSGVhbHRoSW50
|
|
8
|
+
ZXJzZWN0aW9uczEMMAoGA1UECwwDRGV2MR8wHQYDVQQDDBZIZWFsdGhJbnRlcnNl
|
|
9
|
+
Y3Rpb25zRGV2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJts+Wzq5vlTS93GW
|
|
10
|
+
gxrZ+au4ep7/0zRm4KCjAEwOB8fDjyF0OR5RoD3RXmfOND8T9TplnyDw7Zrlbpco
|
|
11
|
+
Ok+pzaOBxDCBwTAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFJqZws5+2DAvsso2
|
|
12
|
+
IDwJY1dDTwafMB8GA1UdIwQYMBaAFHeGcWpLiEul/3p0jQvVlTzo8Nf3MD0GA1Ud
|
|
13
|
+
HwQ2MDQwMqAwoC6GLGh0dHA6Ly9jcmwuZXhhbXBsZWRvbWFpbi5leGFtcGxlL0NS
|
|
14
|
+
TC9TQ0EuY3JsMDAGA1UdJQQpMCcGCysGAQQBjjePZQEBBgsrBgEEAY43j2UBAgYL
|
|
15
|
+
KwYBBAGON49lAQMwCgYIKoZIzj0EAwIDSAAwRQIgXhherpbA37Ut3LwmM+1vR+UK
|
|
16
|
+
Jkk3d9tmq2yHurjNNwwCIQC0TTkFLoO0BsLeJ2WldcVnVsPNh4tbAJoU/vNAdcv1
|
|
17
|
+
bw==
|
|
18
|
+
-----END CERTIFICATE-----
|