@sjcrh/proteinpaint-server 2.191.3-1 → 2.191.3
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 +2 -2
- package/routes/aiProjectAdmin.js +33 -6
- package/routes/deleteWSITileSelection.js +13 -5
- package/routes/saveWSIAnnotation.js +16 -21
- package/src/app.js +110 -70
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.191.3
|
|
3
|
+
"version": "2.191.3",
|
|
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,7 +63,7 @@
|
|
|
63
63
|
"@sjcrh/proteinpaint-r": "2.190.0",
|
|
64
64
|
"@sjcrh/proteinpaint-rust": "2.191.1",
|
|
65
65
|
"@sjcrh/proteinpaint-shared": "2.191.2",
|
|
66
|
-
"@sjcrh/proteinpaint-types": "2.191.3
|
|
66
|
+
"@sjcrh/proteinpaint-types": "2.191.3",
|
|
67
67
|
"@types/express": "^5.0.0",
|
|
68
68
|
"@types/express-session": "^1.18.1",
|
|
69
69
|
"better-sqlite3": "^12.4.1",
|
package/routes/aiProjectAdmin.js
CHANGED
|
@@ -11,13 +11,23 @@ function init({ genomes }) {
|
|
|
11
11
|
const ds = g.datasets[query.dslabel];
|
|
12
12
|
if (!ds.queries?.WSImages?.db) throw new Error("WSImages database not found.");
|
|
13
13
|
const connection = getDbConnection(ds);
|
|
14
|
+
const aiHalAuth = ds.queries?.AIHalAuth;
|
|
15
|
+
if (!aiHalAuth) throw new Error("AIHalAuth queries not found in dataset.");
|
|
16
|
+
const userEmail = req.query.__protected__.clientAuthResult?.email || "";
|
|
14
17
|
if (query.for === "list") {
|
|
15
|
-
|
|
18
|
+
let projects = getProjects(connection);
|
|
19
|
+
if (!aiHalAuth.checkAuthorization(req, "listAllProjects")) {
|
|
20
|
+
projects = projects.filter((p) => aiHalAuth.getUsers(connection, p.id).includes(userEmail));
|
|
21
|
+
}
|
|
16
22
|
res.send(projects);
|
|
17
23
|
} else if (query.for === "admin") {
|
|
18
|
-
if (req.method === "PUT" || query.project.type === "new"
|
|
19
|
-
|
|
20
|
-
else if (req.method === "
|
|
24
|
+
if (req.method === "PUT" || query.project.type === "new" && aiHalAuth.checkAuthorization(req, "addProject"))
|
|
25
|
+
addProject(connection, query.project);
|
|
26
|
+
else if (req.method === "POST" && aiHalAuth.checkAuthorization(req, "editProject")) {
|
|
27
|
+
editProject(connection, query.project);
|
|
28
|
+
aiHalAuth.setUser(connection, query.project.id, userEmail || "admin", 12);
|
|
29
|
+
} else if (req.method === "DELETE" && aiHalAuth.checkAuthorization(req, "deleteProject"))
|
|
30
|
+
deleteProject(connection, query.project.id);
|
|
21
31
|
else throw new Error('Invalid request method for="admin" in aiProjectAdmin route.');
|
|
22
32
|
let projectId = query.project.id;
|
|
23
33
|
if (!projectId) {
|
|
@@ -33,6 +43,8 @@ function init({ genomes }) {
|
|
|
33
43
|
message: `Project ${query.project.name} processed successfully`
|
|
34
44
|
});
|
|
35
45
|
} else if (query.for === "filterImages") {
|
|
46
|
+
const parsedLogin = aiHalAuth.parseLogin(req, connection);
|
|
47
|
+
if (parsedLogin.status === "error") throw new Error("Authentication failed: " + parsedLogin.error);
|
|
36
48
|
const q = ds.cohort.termdb.q;
|
|
37
49
|
const data = await q.getFilteredImages(query.project.filter);
|
|
38
50
|
data.selectedImages = await ds.queries?.WSImages?.selectWSIImages();
|
|
@@ -41,8 +53,23 @@ function init({ genomes }) {
|
|
|
41
53
|
data
|
|
42
54
|
});
|
|
43
55
|
} else if (query.for === "images") {
|
|
56
|
+
const parsedLogin = aiHalAuth.parseLogin(req, connection);
|
|
57
|
+
if (parsedLogin.status === "error") throw new Error("Authentication failed: " + parsedLogin.error);
|
|
44
58
|
const images = getImages(connection, query.project);
|
|
45
|
-
res.send({ images });
|
|
59
|
+
res.send({ images, status: "ok" });
|
|
60
|
+
} else if (query.for === "logout") {
|
|
61
|
+
aiHalAuth.setUser(connection, query.project.id, null);
|
|
62
|
+
res.status(200).send({
|
|
63
|
+
status: "ok",
|
|
64
|
+
message: "User logged out successfully"
|
|
65
|
+
});
|
|
66
|
+
} else if (query.for === "auth") {
|
|
67
|
+
const authorizations = {};
|
|
68
|
+
const actions = query.auth || [];
|
|
69
|
+
for (const action of actions) {
|
|
70
|
+
authorizations[action] = aiHalAuth.checkAuthorization(req, action);
|
|
71
|
+
}
|
|
72
|
+
res.status(200).send(authorizations);
|
|
46
73
|
} else {
|
|
47
74
|
res.send({
|
|
48
75
|
status: "error",
|
|
@@ -59,7 +86,7 @@ function init({ genomes }) {
|
|
|
59
86
|
};
|
|
60
87
|
}
|
|
61
88
|
function getProjects(connection) {
|
|
62
|
-
const sql = "SELECT name, id FROM project";
|
|
89
|
+
const sql = "SELECT name, id, current_user FROM project";
|
|
63
90
|
return runSQL(connection, sql);
|
|
64
91
|
}
|
|
65
92
|
function getImages(connection, project) {
|
|
@@ -14,7 +14,7 @@ function init({ genomes }) {
|
|
|
14
14
|
const ds = g.datasets[query.dslabel];
|
|
15
15
|
if (!ds) throw new Error("invalid dataset name");
|
|
16
16
|
if (typeof ds.queries?.WSImages?.deleteAnnotation === "function") {
|
|
17
|
-
const result = await ds.queries.WSImages.deleteAnnotation(query);
|
|
17
|
+
const result = await ds.queries.WSImages.deleteAnnotation(query, req);
|
|
18
18
|
if (result?.status === "error") {
|
|
19
19
|
return res.status(500).send(result);
|
|
20
20
|
}
|
|
@@ -38,8 +38,16 @@ async function validate_query_deleteWSIAnnotation(ds) {
|
|
|
38
38
|
validateQuery(ds, connection);
|
|
39
39
|
}
|
|
40
40
|
function validateQuery(ds, connection) {
|
|
41
|
-
ds.queries.WSImages.deleteAnnotation = async (query) => {
|
|
41
|
+
ds.queries.WSImages.deleteAnnotation = async (query, req) => {
|
|
42
42
|
const zoomCoordinates = JSON.stringify(query.tileSelection.zoomCoordinates);
|
|
43
|
+
const { current_user: currentUser } = connection.prepare("SELECT current_user FROM project WHERE id = ?").get(query.projectId);
|
|
44
|
+
if (!await ds.queries?.AIHalAuth?.checkAuthorization(req, "annotate", currentUser)) {
|
|
45
|
+
return {
|
|
46
|
+
status: "error",
|
|
47
|
+
error: "logout"
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const email = req.query.__protected__.clientAuthResult?.email || "";
|
|
43
51
|
if (checkSelectionType(query.tileSelection, SelectionPrefixes.Prediction) && query.tileSelection.flag !== FlagStatus.Normal) {
|
|
44
52
|
try {
|
|
45
53
|
const projectId = query.projectId;
|
|
@@ -80,10 +88,10 @@ function validateQuery(ds, connection) {
|
|
|
80
88
|
connection.prepare(
|
|
81
89
|
`
|
|
82
90
|
INSERT INTO project_flagged_predictions
|
|
83
|
-
(project_id, prediction_class_id, coordinates, flag_type, timestamp, image_id)
|
|
84
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
91
|
+
(project_id, user_email, prediction_class_id, coordinates, flag_type, timestamp, image_id)
|
|
92
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
85
93
|
`
|
|
86
|
-
).run(projectId, predictionId, zoomCoordinates, flagType, (/* @__PURE__ */ new Date()).toISOString(), imageId);
|
|
94
|
+
).run(projectId, email, predictionId, zoomCoordinates, flagType, (/* @__PURE__ */ new Date()).toISOString(), imageId);
|
|
87
95
|
return { status: "ok" };
|
|
88
96
|
} catch (error) {
|
|
89
97
|
console.error("Error inserting flagged prediction:", error);
|
|
@@ -11,7 +11,7 @@ function init({ genomes }) {
|
|
|
11
11
|
const ds = g.datasets[query.dslabel];
|
|
12
12
|
if (!ds) throw new Error("invalid dataset name");
|
|
13
13
|
if (typeof ds.queries?.WSImages?.saveWSIAnnotation === "function") {
|
|
14
|
-
const result = await ds.queries.WSImages.saveWSIAnnotation(query);
|
|
14
|
+
const result = await ds.queries.WSImages.saveWSIAnnotation(query, req);
|
|
15
15
|
if (result?.status === "error") {
|
|
16
16
|
return res.status(500).send(result);
|
|
17
17
|
}
|
|
@@ -35,8 +35,9 @@ async function validate_query_saveWSIAnnotation(ds) {
|
|
|
35
35
|
validateQuery(ds, connection);
|
|
36
36
|
}
|
|
37
37
|
function validateQuery(ds, connection) {
|
|
38
|
-
ds.queries.WSImages.saveWSIAnnotation = async (annotation) => {
|
|
38
|
+
ds.queries.WSImages.saveWSIAnnotation = async (annotation, req) => {
|
|
39
39
|
try {
|
|
40
|
+
const { email = "" } = req.query.__protected__.clientAuthResult ?? {};
|
|
40
41
|
const tileSelection = annotation.tileSelection;
|
|
41
42
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
42
43
|
const projectId = annotation.projectId;
|
|
@@ -46,6 +47,13 @@ function validateQuery(ds, connection) {
|
|
|
46
47
|
const classId = annotation.classId;
|
|
47
48
|
const isAnnotation = checkSelectionType(tileSelection, SelectionPrefixes.Annotation);
|
|
48
49
|
const isPrediction = checkSelectionType(tileSelection, SelectionPrefixes.Prediction);
|
|
50
|
+
const currentUser = connection.prepare("SELECT current_user FROM project WHERE id = ?").get(projectId);
|
|
51
|
+
if (!await ds.queries?.AIHalAuth?.checkAuthorization(req, "annotate", currentUser?.current_user ?? null)) {
|
|
52
|
+
return {
|
|
53
|
+
status: "error",
|
|
54
|
+
error: "logout"
|
|
55
|
+
};
|
|
56
|
+
}
|
|
49
57
|
if (!isAnnotation && !isPrediction) {
|
|
50
58
|
return {
|
|
51
59
|
status: "error",
|
|
@@ -65,8 +73,7 @@ function validateQuery(ds, connection) {
|
|
|
65
73
|
AND image_path = ?
|
|
66
74
|
LIMIT 1
|
|
67
75
|
`;
|
|
68
|
-
const
|
|
69
|
-
const imageRow = getImageStmt.get(projectId, wsimageFilename);
|
|
76
|
+
const imageRow = connection.prepare(getImageIdSql).get(projectId, wsimageFilename);
|
|
70
77
|
if (!imageRow?.id) {
|
|
71
78
|
return {
|
|
72
79
|
status: "error",
|
|
@@ -93,19 +100,6 @@ function validateQuery(ds, connection) {
|
|
|
93
100
|
AND coordinates = ?`
|
|
94
101
|
).run(projectId, imageId, coords);
|
|
95
102
|
if (isAnnotation) {
|
|
96
|
-
const userRow = connection.prepare(
|
|
97
|
-
`SELECT id
|
|
98
|
-
FROM project_users
|
|
99
|
-
ORDER BY id
|
|
100
|
-
LIMIT 1`
|
|
101
|
-
).get();
|
|
102
|
-
const userId = userRow?.id;
|
|
103
|
-
if (userId === void 0) {
|
|
104
|
-
return {
|
|
105
|
-
status: "error",
|
|
106
|
-
error: "No users found in project_users table."
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
103
|
if (tileSelection.flag === FlagStatus.Normal) {
|
|
110
104
|
connection.prepare(
|
|
111
105
|
`
|
|
@@ -113,7 +107,7 @@ function validateQuery(ds, connection) {
|
|
|
113
107
|
project_id, user_id, coordinates, timestamp,class_id, image_id
|
|
114
108
|
) VALUES (?, ?, ?, ?, ?, ?)
|
|
115
109
|
`
|
|
116
|
-
).run(projectId,
|
|
110
|
+
).run(projectId, email, coords, timestamp, classId, imageId);
|
|
117
111
|
} else {
|
|
118
112
|
connection.prepare(
|
|
119
113
|
`
|
|
@@ -121,17 +115,18 @@ function validateQuery(ds, connection) {
|
|
|
121
115
|
project_id, user_id, coordinates, timestamp, flagged,class_id, image_id
|
|
122
116
|
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
123
117
|
`
|
|
124
|
-
).run(projectId,
|
|
118
|
+
).run(projectId, email, coords, timestamp, flag, classId, imageId);
|
|
125
119
|
}
|
|
126
120
|
} else if (isPrediction) {
|
|
127
121
|
if (tileSelection.flag !== FlagStatus.Normal) {
|
|
128
122
|
const insertSql = `
|
|
129
|
-
INSERT INTO project_flagged_predictions (project_id, prediction_class_id, coordinates, flag_type,image_id,timestamp)
|
|
130
|
-
VALUES (?, ?, ?,
|
|
123
|
+
INSERT INTO project_flagged_predictions (project_id, user_email, prediction_class_id, coordinates, flag_type,image_id,timestamp)
|
|
124
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
131
125
|
`;
|
|
132
126
|
const insertStmt = connection.prepare(insertSql);
|
|
133
127
|
insertStmt.run(
|
|
134
128
|
annotation.projectId,
|
|
129
|
+
email,
|
|
135
130
|
annotation.classId,
|
|
136
131
|
JSON.stringify(tileSelection.zoomCoordinates),
|
|
137
132
|
tileSelection.flag,
|