@sjcrh/proteinpaint-server 2.214.0 → 2.216.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.
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import fs from 'fs'
7
+ import os from 'os'
7
8
  import path from 'path'
8
9
  import { fileURLToPath } from 'url'
9
10
 
@@ -202,6 +203,34 @@ if (serverconfig.debugmode && !serverconfig.binpath.includes('sjcrh/')) {
202
203
  serverconfig.routeSetters = routeSetters
203
204
  }
204
205
 
206
+ if (process.env.PP_CREDS_HANDOFF_FILE) {
207
+ // set by container/envHelpers.mjs, which passes {<NAME>_CREDS: value} as JSON in a private temp file instead
208
+ // of in this process env, so that the credentials are not in the initial env of this process
209
+ const file = process.env.PP_CREDS_HANDOFF_FILE
210
+ delete process.env.PP_CREDS_HANDOFF_FILE
211
+ const dir = path.dirname(file)
212
+ // only remove a dir as created by envHelpers.mjs
213
+ if (
214
+ path.basename(file) != 'creds.json' ||
215
+ // resolved, like the dir from path.join() in envHelpers.mjs, such as for TMPDIR=/tmp/.
216
+ path.dirname(dir) != path.resolve(os.tmpdir()) ||
217
+ !path.basename(dir).startsWith('pp-creds-')
218
+ )
219
+ throw `invalid process.env.PP_CREDS_HANDOFF_FILE`
220
+ let creds
221
+ try {
222
+ creds = JSON.parse(fs.readFileSync(file, 'utf8'))
223
+ } catch {
224
+ // do not include the parse error message, since it may quote part of the credentials
225
+ throw `unable to read credentials from process.env.PP_CREDS_HANDOFF_FILE`
226
+ } finally {
227
+ // before the server starts listening, so that a request cannot read this file
228
+ fs.rmSync(dir, { recursive: true, force: true })
229
+ }
230
+ // set at runtime instead of in the initial env; each <NAME>_CREDS is then read and deleted as before
231
+ for (const [name, value] of Object.entries(creds)) process.env[name] = value
232
+ }
233
+
205
234
  if (process.env.PP_CREDS) {
206
235
  // preferred over a dsCredentials file path, so that the server process does not need
207
236
  // read access to a credentials file that a path traversal bug could expose;
@@ -413,7 +442,7 @@ export default serverconfig
413
442
  the top-level serverconfig{} already has a null prototype, as set above right after parsing
414
443
 
415
444
  objects are locked in place instead of copied, so that a reference that a module captured at import time,
416
- e.g. `const bamCache = serverconfig.features.bamCache` in bam.js, is locked too
445
+ e.g. `const opts = serverconfig.features.cacheMonitor`, is locked too
417
446
 
418
447
  throws, to fail the launch, when a value cannot be fully locked
419
448
  */
@@ -1,154 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import serverconfig from "#src/serverconfig.js";
4
- import { getData } from "../src/termdb.matrix.js";
5
- import { filterSampleNamesByAccess } from "#src/termdb.sql.js";
6
- import { isNumericTerm } from "#shared";
7
- import { getColors } from "#shared";
8
- import { run_python } from "@sjcrh/proteinpaint-python";
9
- function init({ genomes }) {
10
- return async (req, res) => {
11
- try {
12
- const query = req.query;
13
- const g = genomes[query.genome];
14
- if (!g) throw "invalid genome name";
15
- const ds = g.datasets[query.dslabel];
16
- if (!ds) throw "invalid dataset name";
17
- let plane, index;
18
- if (query.l != void 0) {
19
- plane = "L";
20
- index = query.l;
21
- } else if (query.f != void 0) {
22
- plane = "F";
23
- index = query.f;
24
- } else {
25
- plane = "T";
26
- index = query.t;
27
- }
28
- const [brainImage, legend] = await getBrainImage(query, genomes, plane, index);
29
- res.send({ brainImage, plane, legend });
30
- } catch (e) {
31
- console.log(e);
32
- res.status(404).send(typeof e == "string" ? e : "Sample brain image not found");
33
- }
34
- };
35
- }
36
- async function getBrainImage(query, genomes, plane, index) {
37
- const ds = genomes[query.genome].datasets[query.dslabel];
38
- const q = ds.queries.NIdata;
39
- if (q.checkDataAccess) {
40
- if (!q.checkDataAccess(query)) throw "no access";
41
- }
42
- const ref = q.references[query.refKey];
43
- if (!ref) throw "invalid refKey";
44
- if (ref.referenceFile && ref.samples) {
45
- const refFile = path.join(serverconfig.tpmasterdir, ref.referenceFile);
46
- const dirPath = path.join(serverconfig.tpmasterdir, ref.samples);
47
- const terms = [];
48
- const divideByTW = query.divideByTW;
49
- const overlayTW = query.overlayTW;
50
- if (divideByTW) terms.push(divideByTW);
51
- if (overlayTW) terms.push(overlayTW);
52
- let selectedSampleNames = query.selectedSampleFileNames.map((s) => s.split(".nii")[0]);
53
- const existingFiles = new Set(await fs.promises.readdir(dirPath));
54
- selectedSampleNames = selectedSampleNames.filter((s) => existingFiles.has(s + ".nii"));
55
- if (!selectedSampleNames.length) throw "no brain imaging data for the requested sample(s)";
56
- selectedSampleNames = await filterSampleNamesByAccess(query, ds, selectedSampleNames);
57
- if (!selectedSampleNames.length) throw "no selected samples pass the current filter";
58
- const data = await getData({ terms, __protected__: query.__protected__ }, ds);
59
- if (data.error) throw data.error;
60
- const divideByCat = {};
61
- const uniqueOverlayTwCats = /* @__PURE__ */ new Set();
62
- const getCategories = (tw, value) => {
63
- const keys = value.values ? value.values.map((v) => v.key) : [value.key];
64
- return keys.map((k) => tw.term.values?.[k]?.label || k);
65
- };
66
- for (const sampleName of selectedSampleNames) {
67
- const sampleId = ds.sampleName2Id.get(sampleName);
68
- const sampleData = data.samples[sampleId];
69
- const samplePath = path.join(dirPath, sampleName) + ".nii";
70
- let divideCategories = ["default"];
71
- let overlayCategory = "default";
72
- if (divideByTW && sampleData) {
73
- const value = sampleData[divideByTW.$id];
74
- if (value) divideCategories = getCategories(divideByTW, value);
75
- }
76
- if (overlayTW && sampleData) {
77
- const value = sampleData[overlayTW.$id];
78
- if (value) {
79
- overlayCategory = getCategories(overlayTW, value).join(", ");
80
- uniqueOverlayTwCats.add(overlayCategory);
81
- }
82
- }
83
- for (const divideCategory of divideCategories) {
84
- if (!divideByCat[divideCategory]) divideByCat[divideCategory] = {};
85
- if (!query.legendFilter?.includes(overlayCategory)) {
86
- if (!divideByCat[divideCategory][overlayCategory]) {
87
- let color = overlayTW?.term?.values?.[overlayCategory]?.color;
88
- if (overlayTW && isNumericTerm(overlayTW.term)) {
89
- const bins = data.refs.byTermId[overlayTW.$id].bins;
90
- color = bins.find((b) => b.label == overlayCategory).color;
91
- }
92
- divideByCat[divideCategory][overlayCategory] = {
93
- samples: [],
94
- color
95
- };
96
- }
97
- divideByCat[divideCategory][overlayCategory].samples.push(samplePath);
98
- }
99
- }
100
- }
101
- const k2c = getColors(uniqueOverlayTwCats.size);
102
- const lengths = [];
103
- for (const dcategory in divideByCat) {
104
- for (const category in divideByCat[dcategory]) {
105
- const overlayCat = divideByCat[dcategory][category];
106
- const samples = overlayCat.samples;
107
- lengths.push(samples.length);
108
- if (!overlayCat.color) overlayCat.color = category == "default" ? "red" : k2c(category);
109
- }
110
- }
111
- const maxLength = Math.max(...lengths);
112
- const brainImageDict = {};
113
- const legend = {};
114
- for (const dcategory in divideByCat) {
115
- let catNum = 0;
116
- const filesByCat = divideByCat[dcategory];
117
- for (const category in filesByCat) {
118
- if (filesByCat[category].samples.length < 1) continue;
119
- catNum += filesByCat[category].samples.length;
120
- if (!legend[category]) legend[category] = { color: filesByCat[category].color, maxLength };
121
- }
122
- const arg = {
123
- refFile,
124
- plane,
125
- index,
126
- maxLength,
127
- filesByCat: JSON.stringify(filesByCat)
128
- };
129
- let url;
130
- try {
131
- url = await run_python("plotBrainImaging.py", JSON.stringify(arg));
132
- } catch (error) {
133
- const errmsg = "Error running Python script:" + error;
134
- throw new Error(errmsg);
135
- }
136
- brainImageDict[dcategory] = { url, catNum };
137
- }
138
- if (query.legendFilter) {
139
- for (const cat of query.legendFilter) {
140
- legend[cat] = {
141
- color: "white",
142
- maxLength,
143
- crossedOut: true
144
- };
145
- }
146
- }
147
- return [brainImageDict, legend];
148
- } else {
149
- throw "no reference or sample files";
150
- }
151
- }
152
- export {
153
- init
154
- };
@@ -1,71 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import serverconfig from "#src/serverconfig.js";
4
- import { getData } from "#src/termdb.matrix.js";
5
- import { filterSampleNamesByAccess } from "#src/termdb.sql.js";
6
- function init({ genomes }) {
7
- return async (req, res) => {
8
- try {
9
- const query = req.query;
10
- const g = genomes[query.genome];
11
- if (!g) throw "invalid genome name";
12
- const ds = g.datasets[query.dslabel];
13
- if (!ds) throw "invalid dataset name";
14
- const samples = await getBrainImageSamples(query, genomes);
15
- res.send({ samples });
16
- } catch (e) {
17
- console.log(e);
18
- res.status(404).send(typeof e == "string" ? e : "Cannot get brain imaging samples");
19
- }
20
- };
21
- }
22
- async function getBrainImageSamples(query, genomes) {
23
- const ds = genomes[query.genome].datasets[query.dslabel];
24
- const q = ds.queries.NIdata;
25
- if (q.checkDataAccess) {
26
- if (!q.checkDataAccess(query)) throw "no access";
27
- }
28
- const ref = q.references[query.refKey];
29
- if (!ref) throw "invalid refKey";
30
- if (ref.referenceFile && ref.samples) {
31
- const dirPath = path.join(serverconfig.tpmasterdir, ref.samples);
32
- const files = (await fs.promises.readdir(dirPath, { withFileTypes: true })).filter((f) => f.isFile() && f.name.endsWith(".nii")).map((f) => f.name);
33
- let sampleNames = files.map((name) => name.split(".nii")[0]);
34
- sampleNames = await filterSampleNamesByAccess(query, ds, sampleNames);
35
- if (query.samplesOnly) return sampleNames.map((name) => ({ sample: name }));
36
- if (ref.sampleColumns) {
37
- const terms = ref.sampleColumns.map((term) => {
38
- const termjson = ds.cohort.termdb.q.termjsonByOneid(term.termid);
39
- return {
40
- $id: term.termid,
41
- term: termjson,
42
- q: termjson?.type == "float" || termjson?.type == "integer" ? { mode: "continuous" } : {}
43
- };
44
- });
45
- const data = await getData({ terms, __protected__: query.__protected__ }, ds);
46
- if (data.error) throw data.error;
47
- const samples = {};
48
- for (const s of sampleNames) {
49
- const annoForOneS = { sample: s };
50
- const sid = ds.cohort.termdb.q.sampleName2id(s);
51
- const sampleData = data.samples?.[sid];
52
- if (sampleData) {
53
- for (const term of ref.sampleColumns) {
54
- const v = sampleData[term.termid];
55
- if (!v) continue;
56
- if (v.values) annoForOneS[term.termid] = v.values.map((x) => x.key).join(", ");
57
- else if (v.value !== void 0) annoForOneS[term.termid] = v.value;
58
- }
59
- }
60
- samples[s] = annoForOneS;
61
- }
62
- return Object.values(samples);
63
- }
64
- return sampleNames.map((name) => ({ sample: name }));
65
- } else {
66
- throw "no reference or sample files";
67
- }
68
- }
69
- export {
70
- init
71
- };