@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sjcrh/proteinpaint-server",
3
- "version": "2.140.1-0",
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.138.3-7",
67
- "@sjcrh/proteinpaint-types": "2.140.1-0",
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",
@@ -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
- return;
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 (req.method === "POST")
35
- editProject();
36
- if (req.method === "DELETE")
37
- deleteProject(db.connection, query);
38
- if (req.method === "PUT")
39
- addProject(db.connection, query);
40
- res.status(200).send({
41
- status: "ok",
42
- message: `Project ${query.project.name} processed successfully`
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 editProject() {
54
- console.log(58, "called editProject");
80
+ function getProjects(connection) {
81
+ const sql = "SELECT project.name as value, id FROM project";
82
+ return runSQL(connection, sql);
55
83
  }
56
- function deleteProject(connection, query) {
57
- const sql = `DELETE FROM project WHERE id= ?`;
58
- const params = [query.project.id];
59
- runSQL(connection, sql, params, "delete");
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, query) {
62
- const sql = `INSERT INTO project (name) VALUES (?)`;
63
- const params = [query.project.name];
64
- runSQL(connection, sql, params, "add");
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
- const rows = connection.prepare(sql).run(params);
69
- return rows;
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 SQL for ${errorText}:`, e);
177
+ console.error(`Error executing transaction for ${errorText}: ${e.message || e}`);
72
178
  throw new Error(`Failed to ${errorText} projects`);
73
179
  }
74
180
  }