@sjcrh/proteinpaint-server 2.140.1-0 → 2.140.1-2
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/package.json +3 -3
- package/routes/aiProjectAdmin.js +133 -27
- package/src/app.js +800 -518
- package/src/mds3.gdc.filter.js +13 -8
- package/routes/aiProjectList.js +0 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.140.1-
|
|
3
|
+
"version": "2.140.1-2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "a genomics visualization tool for exploring a cohort's genotype and phenotype data",
|
|
6
6
|
"main": "src/app.js",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"@sjcrh/proteinpaint-python": "2.139.1",
|
|
64
64
|
"@sjcrh/proteinpaint-r": "2.137.2-0",
|
|
65
65
|
"@sjcrh/proteinpaint-rust": "2.138.3-7",
|
|
66
|
-
"@sjcrh/proteinpaint-shared": "2.
|
|
67
|
-
"@sjcrh/proteinpaint-types": "2.140.1-
|
|
66
|
+
"@sjcrh/proteinpaint-shared": "2.140.1-1",
|
|
67
|
+
"@sjcrh/proteinpaint-types": "2.140.1-2",
|
|
68
68
|
"@types/express": "^5.0.0",
|
|
69
69
|
"@types/express-session": "^1.18.1",
|
|
70
70
|
"better-sqlite3": "^9.4.1",
|
package/routes/aiProjectAdmin.js
CHANGED
|
@@ -4,18 +4,23 @@ const routePath = "aiProjectAdmin";
|
|
|
4
4
|
const api = {
|
|
5
5
|
endpoint: `${routePath}`,
|
|
6
6
|
methods: {
|
|
7
|
+
get: {
|
|
8
|
+
//all requests
|
|
9
|
+
...aiProjectAdminPayload,
|
|
10
|
+
init
|
|
11
|
+
},
|
|
7
12
|
post: {
|
|
8
|
-
//edit
|
|
13
|
+
//'admin' -> edit
|
|
9
14
|
...aiProjectAdminPayload,
|
|
10
15
|
init
|
|
11
16
|
},
|
|
12
17
|
delete: {
|
|
13
|
-
//delete
|
|
18
|
+
//'admin' -> delete
|
|
14
19
|
...aiProjectAdminPayload,
|
|
15
20
|
init
|
|
16
21
|
},
|
|
17
22
|
put: {
|
|
18
|
-
//add
|
|
23
|
+
//'admin' -> add
|
|
19
24
|
...aiProjectAdminPayload,
|
|
20
25
|
init
|
|
21
26
|
}
|
|
@@ -25,22 +30,44 @@ function init({ genomes }) {
|
|
|
25
30
|
return async (req, res) => {
|
|
26
31
|
try {
|
|
27
32
|
const query = req.query;
|
|
33
|
+
if (!query.genome || !query.dslabel) {
|
|
34
|
+
throw new Error("Genome and dataset label are required for aiProjectAdmin request.");
|
|
35
|
+
}
|
|
28
36
|
const g = genomes[query.genome];
|
|
29
37
|
const ds = g.datasets[query.dslabel];
|
|
30
38
|
if (!ds.queries?.WSImages?.db)
|
|
31
|
-
|
|
39
|
+
throw new Error("WSImages database not found.");
|
|
32
40
|
const db = ds.queries.WSImages.db;
|
|
33
41
|
db.connection = connect_db(db.file, { readonly: false, fileMustExist: true });
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
if (query.for === "list") {
|
|
43
|
+
const projects = getProjects(db.connection);
|
|
44
|
+
res.send(projects);
|
|
45
|
+
} else if (query.for === "admin") {
|
|
46
|
+
if (req.method === "PUT" || query.project.type === "new")
|
|
47
|
+
addProject(db.connection, query.project);
|
|
48
|
+
else if (req.method === "POST")
|
|
49
|
+
editProject(db.connection, query.project);
|
|
50
|
+
else if (req.method === "DELETE")
|
|
51
|
+
deleteProject(db.connection, query.project.id);
|
|
52
|
+
else
|
|
53
|
+
throw new Error('Invalid request method for="admin" in aiProjectAdmin route.');
|
|
54
|
+
res.status(200).send({
|
|
55
|
+
status: "ok",
|
|
56
|
+
message: `Project ${query.project.name} processed successfully`
|
|
57
|
+
});
|
|
58
|
+
} else if (query.for === "images") {
|
|
59
|
+
const q = ds.cohort.termdb.q;
|
|
60
|
+
const data = await q.getFilteredImages(query.project.filter);
|
|
61
|
+
res.status(200).send({
|
|
62
|
+
status: "ok",
|
|
63
|
+
data
|
|
64
|
+
});
|
|
65
|
+
} else {
|
|
66
|
+
res.send({
|
|
67
|
+
status: "error",
|
|
68
|
+
message: "Invalid request"
|
|
69
|
+
});
|
|
70
|
+
}
|
|
44
71
|
} catch (e) {
|
|
45
72
|
console.warn(e);
|
|
46
73
|
res.status(500).send({
|
|
@@ -50,25 +77,104 @@ function init({ genomes }) {
|
|
|
50
77
|
}
|
|
51
78
|
};
|
|
52
79
|
}
|
|
53
|
-
function
|
|
54
|
-
|
|
80
|
+
function getProjects(connection) {
|
|
81
|
+
const sql = "SELECT project.name as value, id FROM project";
|
|
82
|
+
return runSQL(connection, sql);
|
|
55
83
|
}
|
|
56
|
-
function
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
84
|
+
function editProject(connection, project) {
|
|
85
|
+
const stmts = [];
|
|
86
|
+
if (!project.id) {
|
|
87
|
+
const res = connection.prepare(`SELECT id FROM project WHERE name = ?`).get(project.name);
|
|
88
|
+
project.id = res.id;
|
|
89
|
+
}
|
|
90
|
+
if (project.images) {
|
|
91
|
+
stmts.push({
|
|
92
|
+
sql: `DELETE FROM project_images WHERE project_id = ? AND image NOT IN (${project.images.map(() => "?").join(",") || "''"})`,
|
|
93
|
+
params: [[project.id, ...project.images]]
|
|
94
|
+
});
|
|
95
|
+
const existingImg = connection.prepare(`SELECT 1 FROM project_images WHERE project_id = ? AND image = ?`);
|
|
96
|
+
const multiParams = [];
|
|
97
|
+
for (const img of project.images) {
|
|
98
|
+
const exists = existingImg.get(project.id, img);
|
|
99
|
+
if (!exists)
|
|
100
|
+
multiParams.push([project.id, img]);
|
|
101
|
+
}
|
|
102
|
+
if (multiParams.length > 0) {
|
|
103
|
+
const insertImg = `INSERT INTO project_images (project_id, image) VALUES (?, ?)`;
|
|
104
|
+
stmts.push({ sql: insertImg, params: multiParams });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (project.filter) {
|
|
108
|
+
stmts.push({
|
|
109
|
+
sql: `UPDATE project SET filter = ? WHERE id = ?`,
|
|
110
|
+
params: [[JSON.stringify(project.filter), project.id]]
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (project.classes) {
|
|
114
|
+
stmts.push({
|
|
115
|
+
sql: `DELETE FROM project_classes WHERE project_id = ? AND name NOT IN (${project.classes.map(() => "?").join(",") || "''"})`,
|
|
116
|
+
params: [project.id, ...project.classes.map((c) => c.name)]
|
|
117
|
+
});
|
|
118
|
+
const existingClasses = connection.prepare(`SELECT 1 FROM project_classes WHERE project_id = ? AND name = ?`);
|
|
119
|
+
const multiParams = [];
|
|
120
|
+
for (const cls of project.classes) {
|
|
121
|
+
const exists = existingClasses.get(project.id, cls.name);
|
|
122
|
+
if (!exists)
|
|
123
|
+
multiParams.push([project.id, cls.name, cls.color, cls.key_shortcut || ""]);
|
|
124
|
+
}
|
|
125
|
+
if (multiParams.length > 0) {
|
|
126
|
+
const insertClass = `INSERT INTO project_classes (project_id, name, color, key_shortcut) VALUES (?, ?, ?, ?)`;
|
|
127
|
+
stmts.push({ sql: insertClass, params: multiParams });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
runMultiStmtSQL(connection, stmts, "add");
|
|
131
|
+
}
|
|
132
|
+
function deleteProject(connection, projectId) {
|
|
133
|
+
if (!projectId)
|
|
134
|
+
throw new Error("Invalid project ID [aiProjectAdmin route deleteProject()]");
|
|
135
|
+
const stmts = [
|
|
136
|
+
{ sql: "DELETE FROM project_annotations WHERE project_id = ?", params: [[projectId]] },
|
|
137
|
+
{ sql: "DELETE FROM project_classes WHERE project_id = ?", params: [[projectId]] },
|
|
138
|
+
{ sql: "DELETE FROM project_images WHERE project_id = ?", params: [[projectId]] },
|
|
139
|
+
{ sql: "DELETE FROM project_users WHERE project_id = ?", params: [[projectId]] },
|
|
140
|
+
{ sql: "DELETE FROM project WHERE id = ?", params: [[projectId]] }
|
|
141
|
+
];
|
|
142
|
+
runMultiStmtSQL(connection, stmts, "delete");
|
|
60
143
|
}
|
|
61
|
-
function addProject(connection,
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
runSQL(connection,
|
|
144
|
+
function addProject(connection, project) {
|
|
145
|
+
const projectSql = `INSERT INTO project (name, filter) VALUES (?, ?)`;
|
|
146
|
+
const projectParams = [project.name, JSON.stringify(project.filter)];
|
|
147
|
+
const rows = runSQL(connection, projectSql, projectParams, "add");
|
|
148
|
+
const classSql = `INSERT INTO project_classes (project_id, name, color, key_shortcut) VALUES (?, ?, ?, ?)`;
|
|
149
|
+
const classParams = project.classes.map((c) => [rows.lastInsertRowid, c.label, c.color, c.key_shortcut || ""]);
|
|
150
|
+
for (const params of classParams) {
|
|
151
|
+
runSQL(connection, classSql, params, "add");
|
|
152
|
+
}
|
|
65
153
|
}
|
|
66
154
|
function runSQL(connection, sql, params = [], errorText = "fetch") {
|
|
67
155
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
156
|
+
if (!params.length) {
|
|
157
|
+
return connection.prepare(sql).all();
|
|
158
|
+
}
|
|
159
|
+
return connection.prepare(sql).run(params);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.error(`Error executing SQL for ${errorText}: ${e.message || e}`);
|
|
162
|
+
throw new Error(`Failed to ${errorText} projects`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function runMultiStmtSQL(connection, stmts, errorText = "execute") {
|
|
166
|
+
const transaction = connection.transaction((batch) => {
|
|
167
|
+
for (const { sql, params = [] } of batch) {
|
|
168
|
+
const sqlStmt = connection.prepare(sql);
|
|
169
|
+
for (const item of params) {
|
|
170
|
+
sqlStmt.run(item);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
try {
|
|
175
|
+
transaction(stmts);
|
|
70
176
|
} catch (e) {
|
|
71
|
-
console.error(`Error executing
|
|
177
|
+
console.error(`Error executing transaction for ${errorText}: ${e.message || e}`);
|
|
72
178
|
throw new Error(`Failed to ${errorText} projects`);
|
|
73
179
|
}
|
|
74
180
|
}
|