@openneuro/server 4.29.9 → 4.30.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/package.json +4 -4
- package/src/graphql/resolvers/__tests__/user.spec.ts +9 -0
- package/src/graphql/resolvers/__tests__/validation.spec.ts +22 -0
- package/src/graphql/resolvers/mutation.ts +2 -1
- package/src/graphql/resolvers/user.ts +44 -1
- package/src/graphql/resolvers/validation.ts +9 -0
- package/src/graphql/schema.ts +6 -1
- package/src/models/user.ts +9 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openneuro/server",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.30.0",
|
|
4
4
|
"description": "Core service for the OpenNeuro platform.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "src/server.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"@elastic/elasticsearch": "8.13.1",
|
|
22
22
|
"@graphql-tools/schema": "^10.0.0",
|
|
23
23
|
"@keyv/redis": "^2.7.0",
|
|
24
|
-
"@openneuro/search": "^4.
|
|
24
|
+
"@openneuro/search": "^4.30.0",
|
|
25
25
|
"@sentry/node": "^8.25.0",
|
|
26
26
|
"@sentry/profiling-node": "^8.25.0",
|
|
27
27
|
"base64url": "^3.0.0",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"keyv": "^4.5.3",
|
|
44
44
|
"mime-types": "^2.1.19",
|
|
45
45
|
"mongodb-memory-server": "^9.2.0",
|
|
46
|
-
"mongoose": "6.
|
|
46
|
+
"mongoose": "6.13.5",
|
|
47
47
|
"morgan": "^1.6.1",
|
|
48
48
|
"node-mailjet": "^3.3.5",
|
|
49
49
|
"object-hash": "2.1.1",
|
|
@@ -85,5 +85,5 @@
|
|
|
85
85
|
"publishConfig": {
|
|
86
86
|
"access": "public"
|
|
87
87
|
},
|
|
88
|
-
"gitHead": "
|
|
88
|
+
"gitHead": "e66b349543fde2fb8a8ccba580647e6ad8f4e01b"
|
|
89
89
|
}
|
|
@@ -14,5 +14,14 @@ describe("user resolvers", () => {
|
|
|
14
14
|
),
|
|
15
15
|
).rejects.toEqual(new Error("You must be a site admin to retrieve users"))
|
|
16
16
|
})
|
|
17
|
+
it("throws an error for non-admins", () => {
|
|
18
|
+
expect(
|
|
19
|
+
users(
|
|
20
|
+
null,
|
|
21
|
+
{ id: "0000-0000-0000-000X" },
|
|
22
|
+
{ userInfo: {} },
|
|
23
|
+
),
|
|
24
|
+
).rejects.toEqual(new Error("You must be a site admin to retrieve users"))
|
|
25
|
+
})
|
|
17
26
|
})
|
|
18
27
|
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { validationSeveritySort } from "../validation"
|
|
2
|
+
|
|
3
|
+
vi.mock("../../../config.ts")
|
|
4
|
+
|
|
5
|
+
describe("validation resolvers", () => {
|
|
6
|
+
describe("validationSeveritySort", () => {
|
|
7
|
+
it("sorts severity error before warning", () => {
|
|
8
|
+
const issues = [
|
|
9
|
+
{ severity: "warning" },
|
|
10
|
+
{ severity: "error" },
|
|
11
|
+
{ severity: "error" },
|
|
12
|
+
{ severity: "warning" },
|
|
13
|
+
{ severity: "error" },
|
|
14
|
+
]
|
|
15
|
+
const sortedIssues = issues.sort(validationSeveritySort)
|
|
16
|
+
expect(sortedIssues[0].severity).toBe("error")
|
|
17
|
+
expect(sortedIssues[1].severity).toBe("error")
|
|
18
|
+
expect(sortedIssues[2].severity).toBe("error")
|
|
19
|
+
expect(sortedIssues[3].severity).toBe("warning")
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
})
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
deprecateSnapshot,
|
|
17
17
|
undoDeprecateSnapshot,
|
|
18
18
|
} from "./snapshots.js"
|
|
19
|
-
import { removeUser, setAdmin, setBlocked } from "./user.js"
|
|
19
|
+
import { removeUser, setAdmin, setBlocked, updateUser } from "./user.js"
|
|
20
20
|
import { updateSummary } from "./summary"
|
|
21
21
|
import { revalidate, updateValidation } from "./validation.js"
|
|
22
22
|
import {
|
|
@@ -88,6 +88,7 @@ const Mutation = {
|
|
|
88
88
|
deleteRelation,
|
|
89
89
|
importRemoteDataset,
|
|
90
90
|
finishImportRemoteDataset,
|
|
91
|
+
updateUser,
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
export default Mutation
|
|
@@ -2,9 +2,19 @@
|
|
|
2
2
|
* User resolvers
|
|
3
3
|
*/
|
|
4
4
|
import User from "../../models/user"
|
|
5
|
+
function isValidOrcid(orcid: string): boolean {
|
|
6
|
+
return /^[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{3}[0-9X]$/.test(orcid || "")
|
|
7
|
+
}
|
|
5
8
|
|
|
6
9
|
export const user = (obj, { id }) => {
|
|
7
|
-
|
|
10
|
+
if (isValidOrcid(id)) {
|
|
11
|
+
return User.findOne({
|
|
12
|
+
$or: [{ "orcid": id }, { "providerId": id }],
|
|
13
|
+
}).exec()
|
|
14
|
+
} else {
|
|
15
|
+
// If it's not a valid ORCID, fall back to querying by user id
|
|
16
|
+
return User.findOne({ "id": id }).exec()
|
|
17
|
+
}
|
|
8
18
|
}
|
|
9
19
|
|
|
10
20
|
export const users = (obj, args, { userInfo }) => {
|
|
@@ -43,6 +53,36 @@ export const setBlocked = (obj, { id, blocked }, { userInfo }) => {
|
|
|
43
53
|
}
|
|
44
54
|
}
|
|
45
55
|
|
|
56
|
+
export const updateUser = async (obj, { id, location, institution, links }) => {
|
|
57
|
+
try {
|
|
58
|
+
let user // Declare user outside the if block
|
|
59
|
+
|
|
60
|
+
if (isValidOrcid(id)) {
|
|
61
|
+
user = await User.findOne({
|
|
62
|
+
$or: [{ "orcid": id }, { "providerId": id }],
|
|
63
|
+
}).exec()
|
|
64
|
+
} else {
|
|
65
|
+
user = await User.findOne({ "id": id }).exec()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!user) {
|
|
69
|
+
throw new Error("User not found")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Update user fields (optional values based on provided inputs)
|
|
73
|
+
if (location !== undefined) user.location = location
|
|
74
|
+
if (institution !== undefined) user.institution = institution
|
|
75
|
+
if (links !== undefined) user.links = links
|
|
76
|
+
|
|
77
|
+
// Save the updated user
|
|
78
|
+
await user.save()
|
|
79
|
+
|
|
80
|
+
return user // Return the updated user object
|
|
81
|
+
} catch (err) {
|
|
82
|
+
throw new Error("Failed to update user: " + err.message)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
46
86
|
const UserResolvers = {
|
|
47
87
|
id: (obj) => obj.id,
|
|
48
88
|
provider: (obj) => obj.provider,
|
|
@@ -55,6 +95,9 @@ const UserResolvers = {
|
|
|
55
95
|
name: (obj) => obj.name,
|
|
56
96
|
admin: (obj) => obj.admin,
|
|
57
97
|
blocked: (obj) => obj.blocked,
|
|
98
|
+
location: (obj) => obj.location,
|
|
99
|
+
institution: (obj) => obj.institution,
|
|
100
|
+
links: (obj) => obj.links,
|
|
58
101
|
}
|
|
59
102
|
|
|
60
103
|
export default UserResolvers
|
|
@@ -38,12 +38,21 @@ export const snapshotValidation = async (snapshot) => {
|
|
|
38
38
|
.exec()
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
export function validationSeveritySort(a, b) {
|
|
42
|
+
return a.severity.localeCompare(b.severity)
|
|
43
|
+
}
|
|
44
|
+
|
|
41
45
|
/**
|
|
42
46
|
* Save issues data returned by the datalad service
|
|
43
47
|
*
|
|
44
48
|
* Returns only a boolean if successful or not
|
|
45
49
|
*/
|
|
46
50
|
export const updateValidation = (obj, args) => {
|
|
51
|
+
// Limit to 50k issues with errors sorted first
|
|
52
|
+
if (args.validation.issues.length > 50000) {
|
|
53
|
+
args.validation.issues.sort(validationSeveritySort)
|
|
54
|
+
args.validation.issues = args.validation.issues.slice(0, 50000)
|
|
55
|
+
}
|
|
47
56
|
return Validation.updateOne(
|
|
48
57
|
{
|
|
49
58
|
id: args.validation.id,
|
package/src/graphql/schema.ts
CHANGED
|
@@ -132,6 +132,8 @@ export const typeDefs = `
|
|
|
132
132
|
setAdmin(id: ID!, admin: Boolean!): User
|
|
133
133
|
# Sets a users admin status
|
|
134
134
|
setBlocked(id: ID!, blocked: Boolean!): User
|
|
135
|
+
# Mutation for updating user data
|
|
136
|
+
updateUser(id: ID!, location: String, institution: String, links: [String]): User
|
|
135
137
|
# Tracks a view or download for a dataset
|
|
136
138
|
trackAnalytics(datasetId: ID!, tag: String, type: AnalyticTypes): Boolean
|
|
137
139
|
# Follow dataset
|
|
@@ -322,6 +324,10 @@ export const typeDefs = `
|
|
|
322
324
|
name: String
|
|
323
325
|
admin: Boolean
|
|
324
326
|
blocked: Boolean
|
|
327
|
+
location: String
|
|
328
|
+
institution: String
|
|
329
|
+
github: String
|
|
330
|
+
links: [String]
|
|
325
331
|
}
|
|
326
332
|
|
|
327
333
|
# Which provider a user login comes from
|
|
@@ -832,5 +838,4 @@ schemaComposer.addTypeDefs(typeDefs)
|
|
|
832
838
|
schemaComposer.addResolveMethods(resolvers)
|
|
833
839
|
schemaComposer.Query.addFields(datasetSearch)
|
|
834
840
|
schemaComposer.Query.addFields(advancedDatasetSearch)
|
|
835
|
-
|
|
836
841
|
export default schemaComposer.buildSchema()
|
package/src/models/user.ts
CHANGED
|
@@ -15,6 +15,10 @@ export interface UserDocument extends Document {
|
|
|
15
15
|
blocked: boolean
|
|
16
16
|
created: Date
|
|
17
17
|
lastSeen: Date
|
|
18
|
+
location: string
|
|
19
|
+
institution: string
|
|
20
|
+
github: string
|
|
21
|
+
links: string[]
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
const userSchema = new Schema({
|
|
@@ -29,10 +33,14 @@ const userSchema = new Schema({
|
|
|
29
33
|
blocked: { type: Boolean, default: false },
|
|
30
34
|
created: { type: Date, default: Date.now },
|
|
31
35
|
lastSeen: { type: Date, default: Date.now },
|
|
36
|
+
location: { type: String, default: "" },
|
|
37
|
+
institution: { type: String, default: "" },
|
|
38
|
+
github: { type: String, default: "" },
|
|
39
|
+
links: { type: [String], default: [] },
|
|
32
40
|
})
|
|
33
41
|
|
|
34
42
|
userSchema.index({ id: 1, provider: 1 }, { unique: true })
|
|
35
|
-
// Allow case
|
|
43
|
+
// Allow case-insensitive email queries
|
|
36
44
|
userSchema.index(
|
|
37
45
|
{ email: 1 },
|
|
38
46
|
{
|