fhirsmith 0.5.5 → 0.6.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 CHANGED
@@ -5,6 +5,36 @@ All notable changes to the Health Intersections Node Server will be documented i
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [v0.6.0] - 2026-03-06
9
+
10
+ ### Added
11
+ - Add support to packages server for scoped packages
12
+ - Add support for exclusions and content tracking in tx-registry
13
+ - Add support for serving a host
14
+
15
+ ### Changed
16
+ - fix error in SCT expression validation
17
+ - fix null error in search
18
+ - fix search for code systems with uppercase letters in their name
19
+ - rework html interface for CodeSystem and ValueSet
20
+ - further work on publisehr
21
+
22
+ ### Tx Conformance Statement
23
+
24
+ FHIRsmith passed all 1382 HL7 terminology service tests (modes tx.fhir.org,omop,general,snomed, tests v1.9.0, runner v6.8.2)
25
+
26
+ ## [v0.5.6] - 2026-02-26
27
+
28
+ ### Changed
29
+ - Added content to TerminologyCapabilities.codeSystem
30
+ - fix LOINC list filter handling
31
+ - Improve Diagnostic Logging
32
+ - Add icd-9-cm parser
33
+
34
+ ### Tx Conformance Statement
35
+
36
+ FHIRsmith 0.5.5 passed all 1382 HL7 terminology service tests (modes tx.fhir.org,omop,general,snomed, tests v1.9.0, runner v6.8.1)
37
+
8
38
  ## [v0.5.5] - 2026-02-26
9
39
 
10
40
  ### Changed
@@ -72,7 +72,8 @@ class HtmlServer {
72
72
  .replace(/\[%total-packages%\]/g, escape(renderOptions.totalPackages.toLocaleString()))
73
73
  .replace(/\[%endpoint-path%\]/g, escape(renderOptions.endpointpath))
74
74
  .replace(/\[%fhir-version%\]/g, escape(renderOptions.fhirversion))
75
- .replace(/\[%ms%\]/g, escape(renderOptions.processingTime.toString()));
75
+ .replace(/\[%ms%\]/g, escape(renderOptions.processingTime.toString()))
76
+ .replace(/\[%about%\]/g, renderOptions.about || '');
76
77
 
77
78
  // Handle any custom template variables
78
79
  if (options.templateVars) {
@@ -594,41 +594,46 @@ class PackageManager {
594
594
  */
595
595
  async fetchUrl(url) {
596
596
  console.log("Fetch Package from URL: " + url);
597
- const client = new CIBuildClient();
598
- const packageData = await client.fetchFromUrlSpecific(url);
599
-
600
- // Extract to a temp location to read package.json for name and version
601
- const tempKey = `_url_temp_${Date.now()}`;
602
- const tempPath = await this.extractToCache(tempKey, 'url', packageData);
603
- const tempFullPath = path.join(this.cacheFolder, tempPath);
604
-
605
- // Read package name and version from the extracted package
606
- const pkgJsonPath = path.join(tempFullPath, 'package', 'package.json');
607
- const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'));
608
- const packageId = pkgJson.name;
609
- const version = pkgJson.version;
610
-
611
- if (!packageId || !version) {
612
- throw new Error(`Package at ${url} has no name or version in package.json`);
613
- }
597
+ try {
598
+ const client = new CIBuildClient();
599
+ const packageData = await client.fetchFromUrlSpecific(url);
600
+
601
+ // Extract to a temp location to read package.json for name and version
602
+ const tempKey = `_url_temp_${Date.now()}`;
603
+ const tempPath = await this.extractToCache(tempKey, 'url', packageData);
604
+ const tempFullPath = path.join(this.cacheFolder, tempPath);
605
+
606
+ // Read package name and version from the extracted package
607
+ const pkgJsonPath = path.join(tempFullPath, 'package', 'package.json');
608
+ const pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'));
609
+ const packageId = pkgJson.name;
610
+ const version = pkgJson.version;
611
+
612
+ if (!packageId || !version) {
613
+ throw new Error(`Package at ${url} has no name or version in package.json`);
614
+ }
614
615
 
615
- // Use the same cache key format as npm packages
616
- const finalName = `${packageId}#${version}`;
617
- const finalPath = path.join(this.cacheFolder, finalName);
616
+ // Use the same cache key format as npm packages
617
+ const finalName = `${packageId}#${version}`;
618
+ const finalPath = path.join(this.cacheFolder, finalName);
618
619
 
619
- // If it already exists, the same package is already loaded - that's a config error
620
- try {
621
- await fs.access(finalPath);
622
- await fs.rm(tempFullPath, { recursive: true, force: true });
623
- throw new Error(`Package ${finalName} already exists in cache. Check library config for duplicates (url: ${url})`);
624
- } catch (e) {
625
- if (e.message.includes('already exists')) throw e;
626
- // Doesn't exist yet, rename temp to final
627
- await fs.rename(tempFullPath, finalPath);
628
- }
620
+ // If it already exists, the same package is already loaded - that's a config error
621
+ try {
622
+ await fs.access(finalPath);
623
+ await fs.rm(tempFullPath, { recursive: true, force: true });
624
+ throw new Error(`Package ${finalName} already exists in cache. Check library config for duplicates (url: ${url})`);
625
+ } catch (e) {
626
+ if (e.message.includes('already exists')) throw e;
627
+ // Doesn't exist yet, rename temp to final
628
+ await fs.rename(tempFullPath, finalPath);
629
+ }
629
630
 
630
- this.totalDownloaded = this.totalDownloaded + packageData.length;
631
- return finalName;
631
+ this.totalDownloaded = this.totalDownloaded + packageData.length;
632
+ return finalName;
633
+ } catch (error) {
634
+ console.error(`Error fetching package from URL ${url}: ${error.message}`);
635
+ throw error;
636
+ }
632
637
  }
633
638
 
634
639
  /**
@@ -274,4 +274,13 @@ class ArrayMatcher {
274
274
  }
275
275
  }
276
276
 
277
- module.exports = { Utilities, ArrayMatcher, validateParameter, validateOptionalParameter, validateArrayParameter, validateResource, strToBool, getValuePrimitive, getValueDT, getValueName, isAbsoluteUrl };
277
+ const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
278
+
279
+ function formatDateMMDDYYYY(s) {
280
+ const mm = parseInt(s.substring(0, 2), 10);
281
+ const dd = s.substring(2, 4);
282
+ const yyyy = s.substring(4, 8);
283
+ return dd + '-' + months[mm - 1] + ' ' + yyyy;
284
+ }
285
+
286
+ module.exports = { Utilities, ArrayMatcher, validateParameter, validateOptionalParameter, validateArrayParameter, validateResource, strToBool, getValuePrimitive, getValueDT, getValueName, isAbsoluteUrl, formatDateMMDDYYYY };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fhirsmith",
3
- "version": "0.5.5",
3
+ "version": "0.6.0",
4
4
  "description": "A Node.js server that provides a collection of tools to serve the FHIR ecosystem",
5
5
  "main": "server.js",
6
6
  "engines": {
@@ -12,6 +12,7 @@ const path = require('path');
12
12
 
13
13
  class PackageCrawler {
14
14
  log;
15
+ packages = new Set();
15
16
 
16
17
  constructor(config, db, stats) {
17
18
  this.config = config;
@@ -26,6 +27,7 @@ class PackageCrawler {
26
27
 
27
28
  async crawl(log) {
28
29
  this.log = log;
30
+ this.packages.clear();
29
31
 
30
32
  const startTime = Date.now();
31
33
  this.crawlerLog = {
@@ -56,15 +58,30 @@ class PackageCrawler {
56
58
  this.log.info('Skipping feed with no URL: '+ feedConfig);
57
59
  continue;
58
60
  }
59
- this.stats.task('Package Crawler', 'Running for '+feedConfig.url);
61
+ try {
62
+ let url = this.fixUrl(feedConfig.url)
63
+ if (!url.includes('simplifier')) {
64
+ this.stats.task('Package Crawler', 'Running for '+feedConfig.url);
65
+ await this.updateTheFeed(url, this.config.masterUrl,feedConfig.errors ? feedConfig.errors.replace(/\|/g, '@').replace(/_/g, '.') : '', packageRestrictions);
66
+ }
67
+ } catch (feedError) {
68
+ this.log.error(`Failed to process feed ${feedConfig.url}: `+ feedError.message);
69
+ // Continue with next feed even if this one fails
70
+ }
71
+ }
72
+ // process simplifier last
73
+ for (const feedConfig of masterResponse.feeds) {
74
+ if (!feedConfig.url) {
75
+ this.log.info('Skipping feed with no URL: '+ feedConfig);
76
+ continue;
77
+ }
60
78
 
61
79
  try {
62
- await this.updateTheFeed(
63
- this.fixUrl(feedConfig.url),
64
- this.config.masterUrl,
65
- feedConfig.errors ? feedConfig.errors.replace(/\|/g, '@').replace(/_/g, '.') : '',
66
- packageRestrictions
67
- );
80
+ let url = this.fixUrl(feedConfig.url)
81
+ if (url.includes('simplifier')) {
82
+ this.stats.task('Package Crawler', 'Running for '+feedConfig.url);
83
+ await this.updateTheFeed(url, this.config.masterUrl,feedConfig.errors ? feedConfig.errors.replace(/\|/g, '@').replace(/_/g, '.') : '', packageRestrictions);
84
+ }
68
85
  } catch (feedError) {
69
86
  this.log.error(`Failed to process feed ${feedConfig.url}: `+ feedError.message);
70
87
  // Continue with next feed even if this one fails
@@ -100,14 +117,20 @@ class PackageCrawler {
100
117
 
101
118
  async fetchJson(url) {
102
119
  try {
103
- const response = await axios.get(url, {
104
- timeout: 30000,
105
- headers: {
106
- 'User-Agent': 'FHIR Package Crawler/1.0'
107
- }
108
- });
109
- return response.data;
120
+ if (url.startsWith("/")) {
121
+ const content = await fs.promises.readFile(url, "utf8");
122
+ return JSON.parse(content);
123
+ } else {
124
+ const response = await axios.get(url, {
125
+ timeout: 30000,
126
+ headers: {
127
+ 'User-Agent': 'FHIR Package Crawler/1.0'
128
+ }
129
+ });
130
+ return response.data;
131
+ }
110
132
  } catch (error) {
133
+ console.log(error);
111
134
  if (error.response && error.response.status === 429) {
112
135
  throw new Error(`RATE_LIMITED: Server returned 429 Too Many Requests for ${url}`);
113
136
  }
@@ -117,20 +140,30 @@ class PackageCrawler {
117
140
 
118
141
  async fetchXml(url) {
119
142
  try {
120
- const response = await axios.get(url, {
121
- timeout: 30000,
122
- headers: {
123
- 'User-Agent': 'FHIR Package Crawler/1.0'
124
- }
125
- });
143
+ if (url.startsWith("/")) {
144
+ const content = await fs.promises.readFile(url, 'utf8');
145
+ const parser = new XMLParser({
146
+ ignoreAttributes: false,
147
+ attributeNamePrefix: '@_',
148
+ textNodeName: '#text'
149
+ });
150
+ return parser.parse(content);
151
+ } else {
152
+ const response = await axios.get(url, {
153
+ timeout: 30000,
154
+ headers: {
155
+ 'User-Agent': 'FHIR Package Crawler/1.0'
156
+ }
157
+ });
126
158
 
127
- const parser = new XMLParser({
128
- ignoreAttributes: false,
129
- attributeNamePrefix: '@_',
130
- textNodeName: '#text'
131
- });
159
+ const parser = new XMLParser({
160
+ ignoreAttributes: false,
161
+ attributeNamePrefix: '@_',
162
+ textNodeName: '#text'
163
+ });
132
164
 
133
- return parser.parse(response.data);
165
+ return parser.parse(response.data);
166
+ }
134
167
  } catch (error) {
135
168
  if (error.response && error.response.status === 429) {
136
169
  throw new Error(`RATE_LIMITED: Server returned 429 Too Many Requests for ${url}`);
@@ -141,16 +174,22 @@ class PackageCrawler {
141
174
 
142
175
  async fetchUrl(url) {
143
176
  try {
144
- const response = await axios.get(url, {
145
- timeout: 60000,
146
- responseType: 'arraybuffer',
147
- headers: {
148
- 'User-Agent': 'FHIR Package Crawler/1.0'
149
- }
150
- });
177
+ if (url.startsWith("/")) {
178
+ const buffer = await fs.promises.readFile(url);
179
+ this.totalBytes += buffer.byteLength;
180
+ return buffer;
181
+ } else {
182
+ const response = await axios.get(url, {
183
+ timeout: 60000,
184
+ responseType: 'arraybuffer',
185
+ headers: {
186
+ 'User-Agent': 'FHIR Package Crawler/1.0'
187
+ }
188
+ });
151
189
 
152
- this.totalBytes += response.data.byteLength;
153
- return Buffer.from(response.data);
190
+ this.totalBytes += response.data.byteLength;
191
+ return Buffer.from(response.data);
192
+ }
154
193
  } catch (error) {
155
194
  if (error.response && error.response.status === 429) {
156
195
  throw new Error(`RATE_LIMITED: Server returned 429 Too Many Requests for ${url}`);
@@ -166,7 +205,7 @@ class PackageCrawler {
166
205
  };
167
206
  this.crawlerLog.feeds.push(feedLog);
168
207
 
169
- this.log.info('Processing feed: '+ url);
208
+ this.log.info('Processing feed: ' + url);
170
209
  const startTime = Date.now();
171
210
 
172
211
  try {
@@ -195,7 +234,7 @@ class PackageCrawler {
195
234
  break; // Stop processing this feed
196
235
  }
197
236
  // For other errors, log and continue with next item
198
- this.log.error(`Error processing item ${i} from ${url}:`+ itemError.message);
237
+ this.log.error(`Error processing item ${i} from ${url}:` + itemError.message);
199
238
  }
200
239
  }
201
240
 
@@ -205,6 +244,7 @@ class PackageCrawler {
205
244
  }
206
245
 
207
246
  } catch (error) {
247
+ console.log(error);
208
248
  // Check if this is a 429 error on feed fetch
209
249
  if (error.message.includes('RATE_LIMITED')) {
210
250
  this.log.info(`Rate limited while fetching feed ${url}, skipping this feed`);
@@ -216,7 +256,7 @@ class PackageCrawler {
216
256
 
217
257
  feedLog.exception = error.message;
218
258
  feedLog.failTime = `${Date.now() - startTime}ms`;
219
- this.log.error(`Exception processing feed ${url}:`+ error.message);
259
+ this.log.error(`Exception processing feed ${url}:` + error.message);
220
260
 
221
261
  // TODO: Send email notification for non-rate-limit errors
222
262
  if (email) {
@@ -275,6 +315,12 @@ class PackageCrawler {
275
315
  return;
276
316
  }
277
317
 
318
+ if (this.packages.has(id)) {
319
+ this.log.info(`Ignoring package ${id} because it's already been seen in another feed`);
320
+ return;
321
+ }
322
+ this.packages.add(id);
323
+
278
324
  // Check if already processed
279
325
  if (await this.hasStored(guid)) {
280
326
  itemLog.status = 'Already Processed';
@@ -301,7 +347,7 @@ class PackageCrawler {
301
347
  }
302
348
 
303
349
  itemLog.url = url;
304
- this.log.info('Fetching package: '+ url);
350
+ this.log.info('Fetching package: ' + url);
305
351
 
306
352
  const packageContent = await this.fetchUrl(url, 'application/tar+gzip');
307
353
  await this.store(source, url, guid, pubDate, packageContent, id, itemLog);
@@ -316,6 +362,7 @@ class PackageCrawler {
316
362
  throw error;
317
363
  }
318
364
  }
365
+
319
366
  }
320
367
 
321
368
  isPackageAllowed(packageId, source, restrictions) {
@@ -416,18 +463,19 @@ class PackageCrawler {
416
463
  itemLog.warning = warning;
417
464
  }
418
465
 
466
+ // Validate package data
467
+ if (!this.isValidPackageId(id)) {
468
+ throw new Error(`NPM Id "${id}" is not valid from ${source}`);
469
+ }
470
+
419
471
  // Save to mirror if configured
420
472
  if (this.config.mirrorPath) {
421
- const filename = `${id}-${version}.tgz`;
473
+ let fid = this.fixPrefix(id);
474
+ const filename = `${fid}-${version}.tgz`;
422
475
  const filepath = path.join(this.config.mirrorPath, filename);
423
476
  fs.writeFileSync(filepath, packageBuffer);
424
477
  }
425
478
 
426
- // Validate package data
427
- if (!this.isValidPackageId(id)) {
428
- throw new Error(`NPM Id "${id}" is not valid from ${source}`);
429
- }
430
-
431
479
  if (!this.isValidSemVersion(version)) {
432
480
  throw new Error(`NPM Version "${version}" is not valid from ${source}`);
433
481
  }
@@ -444,6 +492,7 @@ class PackageCrawler {
444
492
  await this.commit(packageBuffer, npmPackage, date, guid, id, version, canonical, urls);
445
493
 
446
494
  } catch (error) {
495
+ console.log(error);
447
496
  this.log.error(`Error storing package ${guid}:`+ error.message);
448
497
  throw error;
449
498
  }
@@ -668,7 +717,7 @@ class PackageCrawler {
668
717
 
669
718
  isValidPackageId(id) {
670
719
  // Simple package ID validation
671
- return /^[a-z0-9][a-z0-9._-]*$/.test(id);
720
+ return /^(@[a-z0-9._-]+\/)?[a-z0-9][a-z0-9._-]*$/.test(id);
672
721
  }
673
722
 
674
723
  isValidSemVersion(version) {
@@ -846,6 +895,14 @@ class PackageCrawler {
846
895
  });
847
896
  });
848
897
  }
898
+
899
+ fixPrefix(id) {
900
+ if (id && id.startsWith("@") && id.includes("/")) {
901
+ return id.replace("@", "$$").replace("/", "$");
902
+ } else {
903
+ return id;
904
+ }
905
+ }
849
906
  }
850
907
 
851
908
  module.exports = PackageCrawler;
@@ -1067,13 +1067,13 @@ class PackagesModule {
1067
1067
  const {id, version} = req.params;
1068
1068
 
1069
1069
  if (!id || !version ||
1070
- !/^[a-zA-Z0-9._-]+$/.test(id) ||
1070
+ !/^(@[a-z0-9._-]+\/)?[a-zA-Z0-9._-]+$/.test(id) ||
1071
1071
  !/^[a-zA-Z0-9._-]+$/.test(version)) {
1072
- return res.status(400).json({error: 'Invalid package id or version format'});
1072
+ return res.status(400).json({error: `Invalid package id or version format: ${id}`});
1073
1073
  }
1074
1074
 
1075
1075
  if (id.length > 100 || version.length > 50) {
1076
- return res.status(400).json({error: 'Package id or version too long'});
1076
+ return res.status(400).json({error: `Package id or version too long: ${id}`});
1077
1077
  }
1078
1078
 
1079
1079
  next();
@@ -1517,7 +1517,7 @@ class PackagesModule {
1517
1517
  // Check if we should redirect to bucket storage
1518
1518
  if (this.config.bucketPath) {
1519
1519
  let bucketUrl = this.getBucketUrl(secure);
1520
- const redirectUrl = `${bucketUrl}${id}-${version}.tgz`;
1520
+ const redirectUrl = `${bucketUrl}${this.fixPrefix(id)}-${version}.tgz`;
1521
1521
  res.redirect(redirectUrl);
1522
1522
  return;
1523
1523
  }
@@ -1888,7 +1888,7 @@ class PackagesModule {
1888
1888
  table += `<td>${escape(this.codeForKind(pv.Kind))}</td>`;
1889
1889
  table += `<td>${new Date(pv.PubDate).toLocaleDateString()}</td>`;
1890
1890
  table += `<td>${(pv.DownloadCount || 0).toLocaleString()}</td>`;
1891
- table += `<td><a href="/packages/${escape(id)}/${escape(pv.Version)}" class="btn btn-sm btn-primary">Download</a></td>`;
1891
+ table += `<td><a href="/packages/${encodeURIComponent(id)}/${escape(pv.Version)}" class="btn btn-sm btn-primary">Download</a></td>`;
1892
1892
  table += '</tr>';
1893
1893
  }
1894
1894
 
@@ -2126,8 +2126,8 @@ class PackagesModule {
2126
2126
 
2127
2127
  for (const pkg of results) {
2128
2128
  table += '<tr>';
2129
- table += `<td><a href="${escape(pkg.url)}">${escape(pkg.name)}</a></td>`;
2130
- table += `<td>${escape(pkg.version)} (<a href="/packages/${escape(pkg.name)}">all</a>)</td>`;
2129
+ table += `<td><a href="${escape(this.fixPrefix(pkg.url))}">${escape(pkg.name)}</a></td>`;
2130
+ table += `<td>${escape(pkg.version)} (<a href="/packages/${encodeURIComponent(pkg.name)}">all</a>)</td>`;
2131
2131
  table += `<td>${escape(pkg.fhirVersion)}</td>`;
2132
2132
  table += `<td>${escape(pkg.kind)}</td>`;
2133
2133
  table += `<td>${pkg.date ? new Date(pkg.date).toLocaleDateString() : 'N/A'}</td>`;
@@ -2817,6 +2817,13 @@ class PackagesModule {
2817
2817
  return content;
2818
2818
  }
2819
2819
 
2820
+ fixPrefix(id) {
2821
+ if (id && id.startsWith("@") && id.includes("/")) {
2822
+ return id.replace("@", "$$").replace("/", "$");
2823
+ } else {
2824
+ return id;
2825
+ }
2826
+ }
2820
2827
  }
2821
2828
 
2822
2829
  module.exports = PackagesModule;
@@ -91,6 +91,8 @@ class PublisherModule {
91
91
  id INTEGER PRIMARY KEY AUTOINCREMENT,
92
92
  name TEXT NOT NULL,
93
93
  local_folder TEXT NOT NULL,
94
+ history_templates TEXT NOT NULL,
95
+ web_templates TEXT NOT NULL,
94
96
  server_update_script TEXT NOT NULL,
95
97
  is_active BOOLEAN DEFAULT 1,
96
98
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP
@@ -1605,6 +1607,14 @@ class PublisherModule {
1605
1607
  content += '<input type="text" class="form-control" id="local_folder" name="local_folder" required>';
1606
1608
  content += '</div>';
1607
1609
  content += '<div class="col-md-4">';
1610
+ content += '<label for="history_templates" class="form-label">History Templates</label>';
1611
+ content += '<input type="text" class="form-control" id="history_templates" name="history_templates" required>';
1612
+ content += '</div>';
1613
+ content += '<div class="col-md-4">';
1614
+ content += '<label for="web_templates" class="form-label">Web Templates</label>';
1615
+ content += '<input type="text" class="form-control" id="web_templates" name="web_templates" required>';
1616
+ content += '</div>';
1617
+ content += '<div class="col-md-4">';
1608
1618
  content += '<label for="server_update_script" class="form-label">Update Script</label>';
1609
1619
  content += '<input type="text" class="form-control" id="server_update_script" name="server_update_script" required>';
1610
1620
  content += '</div>';
@@ -1631,6 +1641,8 @@ class PublisherModule {
1631
1641
  content += '<tr>';
1632
1642
  content += '<td>' + website.name + '</td>';
1633
1643
  content += '<td><code>' + website.local_folder + '</code></td>';
1644
+ content += '<td><code>' + website.history_templates + '</code></td>';
1645
+ content += '<td><code>' + website.web_templates + '</code></td>';
1634
1646
  content += '<td><code>' + website.server_update_script + '</code></td>';
1635
1647
  content += '<td>' + (website.is_active ? '✓' : '✗') + '</td>';
1636
1648
  content += '<td>' + new Date(website.created_at).toLocaleString() + '</td>';
@@ -1665,12 +1677,12 @@ class PublisherModule {
1665
1677
  const start = Date.now();
1666
1678
  try {
1667
1679
  try {
1668
- const {name, local_folder, server_update_script} = req.body;
1680
+ const {name, local_folder, history_templates, web_templates, server_update_script} = req.body;
1669
1681
 
1670
1682
  await new Promise((resolve, reject) => {
1671
1683
  this.db.run(
1672
- 'INSERT INTO websites (name, local_folder, server_update_script) VALUES (?, ?, ?)',
1673
- [name, local_folder, server_update_script],
1684
+ 'INSERT INTO websites (name, local_folder, history_templates, web_templates, server_update_script) VALUES (?, ?, ?, ?, ?)',
1685
+ [name, local_folder, history_templates, web_templates, server_update_script],
1674
1686
  function (err) {
1675
1687
  if (err) reject(err);
1676
1688
  else resolve();