@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openneuro/server",
3
- "version": "4.29.9",
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.29.9",
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.12.8",
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": "992fc9e3044c67f6d85757ecf252845cfda6e809"
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
- return User.findOne({ id }).exec()
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,
@@ -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()
@@ -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 insensitive email queries
43
+ // Allow case-insensitive email queries
36
44
  userSchema.index(
37
45
  { email: 1 },
38
46
  {