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/CHANGELOG.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the Health Intersections Node Server will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [v0.3.0] - 2026-02-05
|
|
9
|
+
### Added
|
|
10
|
+
- Add first draft of publishing engine
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Move all runtime files to a data directory, where an environment variable says. Existing configurations MUST change
|
|
14
|
+
- Finish porting the terminology server
|
|
15
|
+
- Lots of QA related changes, and consistency.
|
|
16
|
+
|
|
17
|
+
## [v0.2.0] - 2026-01-13
|
|
18
|
+
### Added
|
|
19
|
+
- port tx.fhir.org to FHIRsmith, and pass all the tests
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- rework logging, testing, etc infrastructure
|
|
23
|
+
|
|
24
|
+
## [v0.1.1] - 2025-08-21
|
|
25
|
+
### Added
|
|
26
|
+
- set up ci and release workflows with Docker
|
|
27
|
+
- Add tx-reg implementation
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- rework logging from scratch
|
|
32
|
+
|
|
33
|
+
## [v0.1.0] - 2025-08-20
|
|
34
|
+
|
|
35
|
+
First Documented Release
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
- SHL Module: Support services for SHL and VHL implementations
|
|
39
|
+
- VCL Module: Support services for ValueSet Compose Language
|
|
40
|
+
- XIG Module: The Cross-IG Resource server
|
|
41
|
+
- Packages Modules: The server for packages2.fhir.org/packages
|
|
42
|
+
- Testing Infrastructure
|
package/FHIRsmith.png
ADDED
|
Binary file
|
package/README.md
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
#  FHIRsmith - FHIR Server toolkit
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
This server provides a set of server-side services that are useful for the FHIR Community. The set of are two kinds of services:
|
|
6
|
+
|
|
7
|
+
## Modules useful to anyone in the community
|
|
8
|
+
|
|
9
|
+
* (Coming) R4/R6 interconverter
|
|
10
|
+
* [tx.fhir.org](tx/README.md) server
|
|
11
|
+
* [SHL Server](shl/readme.md) - SHL/VHL support services
|
|
12
|
+
|
|
13
|
+
## Services useful the community as a whole
|
|
14
|
+
|
|
15
|
+
* [TX Registry](registry/readme.md) - **Terminology System Registry** as [described by the terminology ecosystem specification](https://build.fhir.org/ig/HL7/fhir-tx-ecosystem-ig)(as running at http://tx.fhir.org/tx-reg)
|
|
16
|
+
* [Package server](packages/readme.md) - **NPM-style FHIR package registry** with search, versioning, and downloads, consistent with the FHIR NPM Specification (as running at http://packages2.fhir.org/packages)
|
|
17
|
+
* [XIG server](xig/readme.md) - **Comprehensive FHIR IG analytics** with resource breakdowns by version, authority, and realm (as running at http://packages2.fhir.org/packages)
|
|
18
|
+
* [Publisher](publisher/readme.md) - FHIR publishing services (coming)
|
|
19
|
+
* [VCL](vcl/readme.md) - **Parse VCL expressions** into FHIR ValueSet resources for http://fhir.org/vcl
|
|
20
|
+
* (Coming) Token services
|
|
21
|
+
|
|
22
|
+
## Build Status
|
|
23
|
+

|
|
24
|
+
[](https://github.com/HealthIntersections/fhirsmith/releases)
|
|
25
|
+
[](https://github.com/HealthIntersections/fhirsmith/pkgs/container/fhirsmith)
|
|
26
|
+
|
|
27
|
+
Note: In production, this server always runs behind an nginx reverse proxy, so there's no
|
|
28
|
+
in-build support for SSL, rate limiting etc.
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
There are 4 executable programs:
|
|
33
|
+
* the server (`node server`)
|
|
34
|
+
* the test cases (`npm test`)
|
|
35
|
+
* the terminology importer (`node --max-old-space-size=8192 tx/importers/tx-import XXX`) - see [Doco](tx/importers/readme.md)
|
|
36
|
+
* the test cases generater (`node tx/tests/testcases-generator.js`)
|
|
37
|
+
|
|
38
|
+
### Data Directory
|
|
39
|
+
|
|
40
|
+
The server separates code from runtime data. All databases, caches, logs, and downloaded
|
|
41
|
+
files are stored in a single data directory. The location is determined by:
|
|
42
|
+
|
|
43
|
+
1. The `FHIRSMITH_DATA_DIR` environment variable (if set)
|
|
44
|
+
2. Otherwise, defaults to `./data` relative to the working directory
|
|
45
|
+
|
|
46
|
+
The data directory contains (depending on which modules are in use):
|
|
47
|
+
* `config.json` — server and module configuration
|
|
48
|
+
* `logs/` — server and nginx log files
|
|
49
|
+
* `terminology-cache/` — downloaded terminology packages and FHIR packages
|
|
50
|
+
* `packages/` — package server database
|
|
51
|
+
* `xig/` — XIG database
|
|
52
|
+
* `shl/` — SHL databases and certificates
|
|
53
|
+
* `registry/` — registry crawler data
|
|
54
|
+
* `publisher/` — publisher database and build workspace
|
|
55
|
+
* `token/` — token database
|
|
56
|
+
|
|
57
|
+
During development with a cloned repository, the data directory defaults to `[root]/data`
|
|
58
|
+
(the test cases require this setup). When deployed via Docker or npm, the data directory
|
|
59
|
+
is provided by the host — see Deployment below.
|
|
60
|
+
|
|
61
|
+
### Prerequisites
|
|
62
|
+
- Node.js 16+
|
|
63
|
+
- NPM or Yarn
|
|
64
|
+
- Java 17+ (for FHIR validator, also for the test cases)
|
|
65
|
+
|
|
66
|
+
### Installation
|
|
67
|
+
|
|
68
|
+
These instructions are for Development. For deployment, see below.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Clone the repository
|
|
72
|
+
git clone https://github.com/HealthIntersections/FHIRsmith
|
|
73
|
+
cd FHIRsmith
|
|
74
|
+
|
|
75
|
+
# Install dependencies
|
|
76
|
+
npm install
|
|
77
|
+
|
|
78
|
+
# Create required directories
|
|
79
|
+
mkdir -p data data/logs
|
|
80
|
+
|
|
81
|
+
# Copy example configuration
|
|
82
|
+
cp config.example.json data/config.json
|
|
83
|
+
|
|
84
|
+
# Edit configuration as needed
|
|
85
|
+
nano data/config.json
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Each Module has it's own entry in the config, as described by the module
|
|
89
|
+
|
|
90
|
+
### Basic Configuration
|
|
91
|
+
|
|
92
|
+
Create a `config.json` file in your data directory (use `config-template.json` as a starting point):
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"hostName" : "[descriptive name for the server]",
|
|
97
|
+
"server": {
|
|
98
|
+
"port": 3000,
|
|
99
|
+
"cors": {
|
|
100
|
+
"origin": "*",
|
|
101
|
+
"credentials": true
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"modules": {
|
|
105
|
+
// per modules...
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Start the Server
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Development mode
|
|
114
|
+
npm run dev
|
|
115
|
+
|
|
116
|
+
# Production mode
|
|
117
|
+
npm start
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The server will be available at `http://localhost:{port}` using the port specified in the config.
|
|
121
|
+
In the production servers listed above, the server always sits behind an NGINX server which manages
|
|
122
|
+
SSL, security, rate limiting etc.
|
|
123
|
+
|
|
124
|
+
## Testing
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npm test
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Deployment
|
|
131
|
+
|
|
132
|
+
There are three deployment options: npm global install, Docker, or clone-and-run. All three
|
|
133
|
+
use the `FHIRSMITH_DATA_DIR` environment variable to locate the data directory.
|
|
134
|
+
|
|
135
|
+
### npm Global Install
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Install globally
|
|
139
|
+
npm install -g fhirsmith
|
|
140
|
+
|
|
141
|
+
# Create a data directory
|
|
142
|
+
mkdir -p /var/lib/fhirsmith
|
|
143
|
+
cp node_modules/fhirsmith/config-template.json /var/lib/fhirsmith/config.json
|
|
144
|
+
# Edit config.json as needed
|
|
145
|
+
|
|
146
|
+
# Set the data directory and run
|
|
147
|
+
export FHIRSMITH_DATA_DIR=/var/lib/fhirsmith
|
|
148
|
+
fhirsmith
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Or run it inline:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
FHIRSMITH_DATA_DIR=/var/lib/fhirsmith fhirsmith
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Docker Installation
|
|
158
|
+
|
|
159
|
+
The server is available as a Docker image. Mount a host directory as the data directory:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Pull the latest image
|
|
163
|
+
docker pull ghcr.io/healthintersections/fhirsmith:latest
|
|
164
|
+
|
|
165
|
+
# Create and populate data directory on host
|
|
166
|
+
mkdir -p /path/to/data
|
|
167
|
+
cp config-template.json /path/to/data/config.json
|
|
168
|
+
# Edit config.json as needed
|
|
169
|
+
|
|
170
|
+
# Run with data directory mounted
|
|
171
|
+
docker run -d --name fhirsmith \
|
|
172
|
+
-p 3000:3000 \
|
|
173
|
+
-e FHIRSMITH_DATA_DIR=/app/data \
|
|
174
|
+
-v /path/to/data:/app/data \
|
|
175
|
+
ghcr.io/healthintersections/fhirsmith:latest
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Available tags:
|
|
179
|
+
- `latest`: Latest stable release
|
|
180
|
+
- `vX.Y.Z`: Specific version (e.g., `v1.0.0`)
|
|
181
|
+
- `cibuild`: Latest build from the main branch
|
|
182
|
+
|
|
183
|
+
### Environment Variables
|
|
184
|
+
|
|
185
|
+
| Variable | Description | Default |
|
|
186
|
+
|---|---|---|
|
|
187
|
+
| `FHIRSMITH_DATA_DIR` | Path to the data directory | `./data` |
|
|
188
|
+
| `PORT` | Server port (overrides config) | from config.json |
|
|
189
|
+
| `NODE_ENV` | Node environment | `production` |
|
|
190
|
+
|
|
191
|
+
### Windows Installation
|
|
192
|
+
|
|
193
|
+
You can install as a windows service using [windows-install.js](windows-install.js). You might need to
|
|
194
|
+
hack that.
|
|
195
|
+
|
|
196
|
+
## Releases
|
|
197
|
+
|
|
198
|
+
This project follows [Semantic Versioning](https://semver.org/) and uses a [CHANGELOG.md](CHANGELOG.md) file to track changes.
|
|
199
|
+
|
|
200
|
+
### What's in a Release
|
|
201
|
+
|
|
202
|
+
Each GitHub Release includes:
|
|
203
|
+
- **Release notes** extracted from CHANGELOG.md
|
|
204
|
+
- **Source code** archives (zip and tar.gz)
|
|
205
|
+
- **Docker images** pushed to GitHub Container Registry:
|
|
206
|
+
- `ghcr.io/healthintersections/fhirsmith:latest`
|
|
207
|
+
- `ghcr.io/healthintersections/fhirsmith:vX.Y.Z`
|
|
208
|
+
- `ghcr.io/healthintersections/fhirsmith:X.Y.Z`
|
|
209
|
+
- **npm package** published to npmjs.org as `fhirsmith` *(if you add this)*
|
|
210
|
+
|
|
211
|
+
### Creating a Release
|
|
212
|
+
|
|
213
|
+
GitHub Actions will automatically:
|
|
214
|
+
- Run tests
|
|
215
|
+
- Create a GitHub Release with notes from CHANGELOG.md
|
|
216
|
+
- Build and publish Docker images with appropriate tags
|
|
217
|
+
|
|
218
|
+
**Prerequisites:**
|
|
219
|
+
- All tests passing on main branch
|
|
220
|
+
- CHANGELOG.md updated with changes
|
|
221
|
+
|
|
222
|
+
**Steps:**
|
|
223
|
+
1. Update `CHANGELOG.md` with your changes under a new version section:
|
|
224
|
+
```markdown
|
|
225
|
+
## [vX.Y.Z] - YYYY-MM-DD
|
|
226
|
+
### Added
|
|
227
|
+
- New feature description
|
|
228
|
+
### Changed
|
|
229
|
+
- Change description
|
|
230
|
+
### Fixed
|
|
231
|
+
- Bug fix description
|
|
232
|
+
```
|
|
233
|
+
2. Update `package.json` to have the same release version
|
|
234
|
+
|
|
235
|
+
3. Commit your changes:
|
|
236
|
+
```bash
|
|
237
|
+
git commit -m "Prepare release vX.Y.Z"
|
|
238
|
+
git push origin main
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
4. Tag and push the release:
|
|
242
|
+
```bash
|
|
243
|
+
git tag vX.Y.Z
|
|
244
|
+
git push origin vX.Y.Z
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
5. Monitor the release:
|
|
248
|
+
- Check [GitHub Actions](https://github.com/HealthIntersections/fhirsmith/actions) for the Release workflow
|
|
249
|
+
- Verify the [GitHub Release](https://github.com/HealthIntersections/fhirsmith/releases) was created
|
|
250
|
+
- Confirm Docker images are available at [GHCR](https://github.com/HealthIntersections/fhirsmith/pkgs/container/fhirsmith)
|
|
251
|
+
|
|
252
|
+
6. Update `package.json` to have the next release version -SNAPSHOT
|
|
253
|
+
|
|
254
|
+
**If a release fails:**
|
|
255
|
+
- Delete the tag: `git tag -d vX.Y.Z && git push origin :refs/tags/vX.Y.Z`
|
|
256
|
+
- Fix the issue
|
|
257
|
+
- Re-tag and push
|
|
258
|
+
|
|
259
|
+
### Creating a Release
|
|
260
|
+
|
|
261
|
+
## License
|
|
262
|
+
|
|
263
|
+
[BSD-3](https://opensource.org/license/bsd-3-clause)
|
|
264
|
+
|
|
265
|
+
## Contributing
|
|
266
|
+
|
|
267
|
+
1. Fork the repository
|
|
268
|
+
2. Create a feature branch
|
|
269
|
+
3. Make your changes
|
|
270
|
+
4. Add tests if applicable
|
|
271
|
+
5. Submit a pull request
|
|
272
|
+
|
|
273
|
+
## Support
|
|
274
|
+
|
|
275
|
+
- **Issues:** [GitHub Issues](repository-url/issues)
|
|
276
|
+
- **Documentation:** [Wiki](repository-url/wiki)
|
|
277
|
+
- **FHIR Community:** [chat.fhir.org](https://chat.fhir.org)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
{
|
|
2
|
+
"server": {
|
|
3
|
+
"port": 3000,
|
|
4
|
+
"cors": {
|
|
5
|
+
"origin": true,
|
|
6
|
+
"credentials": true
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"modules": {
|
|
10
|
+
"shl": {
|
|
11
|
+
"enabled": false,
|
|
12
|
+
"database": "shl.db",
|
|
13
|
+
"password": "<!-- Uuid -->",
|
|
14
|
+
"certificates": {
|
|
15
|
+
"certFile": "<!-- filename -->",
|
|
16
|
+
"keyFile": "<!-- filename -->",
|
|
17
|
+
"kid": "<!-- kid -->"
|
|
18
|
+
},
|
|
19
|
+
"vhl": {
|
|
20
|
+
"issuer": "XXX"
|
|
21
|
+
},
|
|
22
|
+
"cleanup": {
|
|
23
|
+
"schedule": "0 * * * *"
|
|
24
|
+
},
|
|
25
|
+
"validator": {
|
|
26
|
+
"enabled": true,
|
|
27
|
+
"version": "4.0.1",
|
|
28
|
+
"txServer": "http://tx.fhir.org/r4",
|
|
29
|
+
"txLog": "./tx.log",
|
|
30
|
+
"port": 8081,
|
|
31
|
+
"packages": [
|
|
32
|
+
"hl7.fhir.us.core#6.0.0",
|
|
33
|
+
"hl7.fhir.uv.ips#1.1.0"
|
|
34
|
+
],
|
|
35
|
+
"timeout": 60000
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"vcl": {
|
|
39
|
+
"enabled": false
|
|
40
|
+
},
|
|
41
|
+
"xig": {
|
|
42
|
+
"enabled": false,
|
|
43
|
+
"autoUpdate": true
|
|
44
|
+
},
|
|
45
|
+
"packages": {
|
|
46
|
+
"enabled": false,
|
|
47
|
+
"database": "/absolute/path/to/packages.db",
|
|
48
|
+
"mirrorPath": "/absolute/path/to/mirror",
|
|
49
|
+
"crawler": {
|
|
50
|
+
"enabled": true,
|
|
51
|
+
"schedule": "0 * * * *"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"registry": {
|
|
55
|
+
"enabled": true,
|
|
56
|
+
"masterUrl": "https://fhir.github.io/ig-registry/tx-servers.json",
|
|
57
|
+
"crawlInterval": 30,
|
|
58
|
+
"timeout": 30000,
|
|
59
|
+
"userAgent": "YourServer/1.0",
|
|
60
|
+
"apiKeys": {}
|
|
61
|
+
},
|
|
62
|
+
"publisher": {
|
|
63
|
+
"enabled": true,
|
|
64
|
+
"database": "/absolute/path/to/publisher.db",
|
|
65
|
+
"sessionSecret": "change-this-to-a-secure-random-string",
|
|
66
|
+
"workspaceRoot": "/absolute/path/to/workspaces"
|
|
67
|
+
},
|
|
68
|
+
"npmprojector": {
|
|
69
|
+
"enabled": true,
|
|
70
|
+
"fhirVersion": "r4", // Options: "r4", "r5", "stu3", "dstu2"
|
|
71
|
+
"basePath": "/fhir", // Default: "/npmprojector"
|
|
72
|
+
"npmPath": "./data/fhir-package",
|
|
73
|
+
"resourceTypes": null,
|
|
74
|
+
"searchParametersPath": null,
|
|
75
|
+
"debounceMs": 500
|
|
76
|
+
},
|
|
77
|
+
"token": {
|
|
78
|
+
"enabled": true,
|
|
79
|
+
"database": "/absolute/path/to/token.db",
|
|
80
|
+
"sessionSecret": "change-this-to-a-secure-random-string-for-token-sessions",
|
|
81
|
+
"oauth": {
|
|
82
|
+
"google": {
|
|
83
|
+
"clientId": "your-google-client-id",
|
|
84
|
+
"clientSecret": "your-google-client-secret",
|
|
85
|
+
"redirectUri": "http://localhost:3000/token/callback/google",
|
|
86
|
+
"scope": ["openid", "profile", "email"]
|
|
87
|
+
},
|
|
88
|
+
"facebook": {
|
|
89
|
+
"clientId": "your-facebook-app-id",
|
|
90
|
+
"clientSecret": "your-facebook-app-secret",
|
|
91
|
+
"redirectUri": "http://localhost:3000/token/callback/facebook",
|
|
92
|
+
"scope": ["email"]
|
|
93
|
+
},
|
|
94
|
+
"apple": {
|
|
95
|
+
"clientId": "your-apple-service-id",
|
|
96
|
+
"teamId": "your-apple-team-id",
|
|
97
|
+
"keyId": "your-apple-key-id",
|
|
98
|
+
"privateKey": "/path/to/apple-private-key.p8",
|
|
99
|
+
"redirectUri": "http://localhost:3000/token/callback/apple",
|
|
100
|
+
"scope": ["email", "name"]
|
|
101
|
+
},
|
|
102
|
+
"github": {
|
|
103
|
+
"clientId": "your-github-client-id",
|
|
104
|
+
"clientSecret": "your-github-client-secret",
|
|
105
|
+
"redirectUri": "http://localhost:3000/token/callback/github",
|
|
106
|
+
"scope": ["user:email"]
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"apiKeys": {
|
|
110
|
+
"defaultAllowedRequests": 50,
|
|
111
|
+
"maxKeysPerUser": 10,
|
|
112
|
+
"keyExpiration": null
|
|
113
|
+
},
|
|
114
|
+
"rateLimit": {
|
|
115
|
+
"windowMs": 900000,
|
|
116
|
+
"max": 100
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"tx": {
|
|
120
|
+
"enabled": false,
|
|
121
|
+
"librarySource": "/absolute/path/to/library.yml",
|
|
122
|
+
"cacheTimeout": 30,
|
|
123
|
+
"expansionCacheSize": 1000,
|
|
124
|
+
"expansionCacheMemoryThreshold": 0,
|
|
125
|
+
"endpoints": [
|
|
126
|
+
{
|
|
127
|
+
"path": "/tx/r5",
|
|
128
|
+
"fhirVersion": "5.0",
|
|
129
|
+
"context": null
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"path": "/tx/r4",
|
|
133
|
+
"fhirVersion": "4.0",
|
|
134
|
+
"context": null
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"path": "/tx/r3",
|
|
138
|
+
"fhirVersion": "3.0",
|
|
139
|
+
"context": null
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
class FolderSetup {
|
|
5
|
+
constructor() {
|
|
6
|
+
this._dataDir = null;
|
|
7
|
+
this._initialized = false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
init(dataDir = null) {
|
|
11
|
+
if (this._initialized) {
|
|
12
|
+
return this;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
this._dataDir = dataDir || process.env.FHIRSMITH_DATA_DIR || path.join(__dirname, '..', 'data');
|
|
16
|
+
fs.mkdirSync(this._dataDir, { recursive: true });
|
|
17
|
+
this._initialized = true;
|
|
18
|
+
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
dataDir() {
|
|
23
|
+
if (!this._initialized) {
|
|
24
|
+
this.init();
|
|
25
|
+
}
|
|
26
|
+
return this._dataDir;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
subDir(name) {
|
|
30
|
+
const dir = path.join(this.dataDir(), name);
|
|
31
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
32
|
+
return dir;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
filePath(...relativePath) {
|
|
36
|
+
return path.join(this.dataDir(), ...relativePath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
ensureFilePath(...relativePath) {
|
|
40
|
+
const filePath = path.join(this.dataDir(), ...relativePath);
|
|
41
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
42
|
+
return filePath;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
logsDir() {
|
|
46
|
+
return this.subDir('logs');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
cacheDir() {
|
|
50
|
+
return this.subDir('cache');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
databasesDir() {
|
|
54
|
+
return this.subDir('databases');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = new FolderSetup();
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
|
|
2
|
+
//
|
|
3
|
+
// Copyright 2025, Health Intersections Pty Ltd (http://www.healthintersections.com.au)
|
|
4
|
+
//
|
|
5
|
+
// Licensed under BSD-3: https://opensource.org/license/bsd-3-clause
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
class HtmlServer {
|
|
12
|
+
log;
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
this.templates = new Map(); // templateName -> template content
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
useLog(logv) {
|
|
19
|
+
this.log = logv;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Template Management
|
|
23
|
+
loadTemplate(templateName, templatePath) {
|
|
24
|
+
try {
|
|
25
|
+
if (fs.existsSync(templatePath)) {
|
|
26
|
+
const templateContent = fs.readFileSync(templatePath, 'utf8');
|
|
27
|
+
this.templates.set(templateName, templateContent);
|
|
28
|
+
return true;
|
|
29
|
+
} else {
|
|
30
|
+
this.log.error(`Template file not found: ${templatePath}`);
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
this.log.error(`Failed to load template '${templateName}':`, error.message);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getTemplate(templateName) {
|
|
40
|
+
return this.templates.get(templateName);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
hasTemplate(templateName) {
|
|
44
|
+
return this.templates.has(templateName);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// HTML Utilities
|
|
48
|
+
escapeHtml(text) {
|
|
49
|
+
if (typeof text !== 'string') {
|
|
50
|
+
return String(text);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const map = {
|
|
54
|
+
'&': '&',
|
|
55
|
+
'<': '<',
|
|
56
|
+
'>': '>',
|
|
57
|
+
'"': '"',
|
|
58
|
+
"'": '''
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Page Rendering - simple template substitution
|
|
65
|
+
renderPage(templateName, title, content, options = {}) {
|
|
66
|
+
const template = this.getTemplate(templateName);
|
|
67
|
+
if (!template) {
|
|
68
|
+
throw new Error(`Template '${templateName}' not found`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Default options
|
|
72
|
+
const renderOptions = {
|
|
73
|
+
version: '4.0.1',
|
|
74
|
+
downloadDate: 'Unknown',
|
|
75
|
+
totalResources: 0,
|
|
76
|
+
totalPackages: 0,
|
|
77
|
+
processingTime: 0,
|
|
78
|
+
...options
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Perform template replacements
|
|
82
|
+
let html = template
|
|
83
|
+
.replace(/\[%title%\]/g, this.escapeHtml(title))
|
|
84
|
+
.replace(/\[%content%\]/g, content) // Content is assumed to be already-safe HTML
|
|
85
|
+
.replace(/\[%ver%\]/g, this.escapeHtml(renderOptions.version))
|
|
86
|
+
.replace(/\[%download-date%\]/g, this.escapeHtml(renderOptions.downloadDate))
|
|
87
|
+
.replace(/\[%total-resources%\]/g, this.escapeHtml(renderOptions.totalResources.toLocaleString()))
|
|
88
|
+
.replace(/\[%total-packages%\]/g, this.escapeHtml(renderOptions.totalPackages.toLocaleString()))
|
|
89
|
+
.replace(/\[%endpoint-path%\]/g, this.escapeHtml(renderOptions.endpointpath))
|
|
90
|
+
.replace(/\[%fhir-version%\]/g, this.escapeHtml(renderOptions.fhirversion))
|
|
91
|
+
.replace(/\[%ms%\]/g, this.escapeHtml(renderOptions.processingTime.toString()));
|
|
92
|
+
|
|
93
|
+
// Handle any custom template variables
|
|
94
|
+
if (options.templateVars) {
|
|
95
|
+
for (const [key, value] of Object.entries(options.templateVars)) {
|
|
96
|
+
const placeholder = `[%${key}%]`;
|
|
97
|
+
const escapedValue = typeof value === 'string' ? this.escapeHtml(value) : String(value);
|
|
98
|
+
html = html.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), escapedValue);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return html;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Express Response Helper
|
|
106
|
+
sendHtmlResponse(res, templateName, title, content, options = {}) {
|
|
107
|
+
try {
|
|
108
|
+
const html = this.renderPage(templateName, title, content, options);
|
|
109
|
+
res.setHeader('Content-Type', 'text/html');
|
|
110
|
+
res.send(html);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
this.log.error('[HtmlServer] Error rendering page:', error);
|
|
113
|
+
res.status(500).send(`<h1>Error</h1><p>Failed to render page: ${this.escapeHtml(error.message)}</p>`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
sendErrorResponse(res, templateName, error, statusCode = 500) {
|
|
118
|
+
const errorContent = `
|
|
119
|
+
<div class="alert alert-danger">
|
|
120
|
+
<h4>Error</h4>
|
|
121
|
+
<p>${this.escapeHtml(error.message || error)}</p>
|
|
122
|
+
</div>
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const html = this.renderPage(templateName, 'Error', errorContent);
|
|
127
|
+
res.status(statusCode).setHeader('Content-Type', 'text/html');
|
|
128
|
+
res.send(html);
|
|
129
|
+
} catch (renderError) {
|
|
130
|
+
this.log.error('[HtmlServer] Error rendering error page:', renderError);
|
|
131
|
+
res.status(statusCode).send(`<h1>Error</h1><p>Failed to render error page: ${this.escapeHtml(renderError.message)}</p>`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Date Formatting Utility
|
|
136
|
+
formatDate(dateString) {
|
|
137
|
+
if (!dateString) return '';
|
|
138
|
+
try {
|
|
139
|
+
const date = new Date(dateString);
|
|
140
|
+
const year = date.getFullYear();
|
|
141
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
142
|
+
return `${year}-${month}`;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
return dateString; // Return original if parsing fails
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Initialize templates from directory
|
|
149
|
+
loadTemplatesFromDirectory(templatesDir) {
|
|
150
|
+
if (!fs.existsSync(templatesDir)) {
|
|
151
|
+
this.log.warn(`Templates directory not found: ${templatesDir}`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const templateFiles = fs.readdirSync(templatesDir).filter(file => file.endsWith('.html'));
|
|
156
|
+
|
|
157
|
+
templateFiles.forEach(file => {
|
|
158
|
+
const templateName = path.basename(file, '.html');
|
|
159
|
+
const templatePath = path.join(templatesDir, file);
|
|
160
|
+
this.loadTemplate(templateName, templatePath);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Export singleton instance
|
|
166
|
+
module.exports = new HtmlServer();
|