@openneuro/server 5.0.0 → 5.1.1

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.
Files changed (101) hide show
  1. package/package.json +8 -5
  2. package/src/app.ts +2 -4
  3. package/src/cache/__tests__/tree.spec.ts +2 -0
  4. package/src/cache/tree.ts +4 -0
  5. package/src/cache/types.ts +1 -0
  6. package/src/datalad/__tests__/contributors.spec.ts +1 -1
  7. package/src/datalad/__tests__/dataRetentionNotifications.spec.ts +11 -11
  8. package/src/datalad/__tests__/dataset.spec.ts +2 -2
  9. package/src/datalad/__tests__/files.spec.ts +31 -1
  10. package/src/datalad/__tests__/snapshots.spec.ts +5 -5
  11. package/src/datalad/contributors.ts +2 -2
  12. package/src/datalad/dataRetentionNotifications.ts +5 -5
  13. package/src/datalad/dataset.ts +26 -10
  14. package/src/datalad/description.ts +2 -2
  15. package/src/datalad/draft.ts +8 -3
  16. package/src/datalad/files.ts +71 -12
  17. package/src/datalad/readme.ts +2 -2
  18. package/src/datalad/snapshots.ts +19 -14
  19. package/src/elasticsearch/elastic-client.ts +11 -6
  20. package/src/elasticsearch/reindex-dataset.ts +8 -4
  21. package/src/graphql/__tests__/comment.spec.ts +3 -2
  22. package/src/graphql/__tests__/schema.spec.ts +28 -0
  23. package/src/graphql/builder.ts +42 -0
  24. package/src/graphql/resolvers/__tests__/dataset.spec.ts +6 -119
  25. package/src/graphql/resolvers/__tests__/derivatives.spec.ts +11 -0
  26. package/src/graphql/resolvers/__tests__/importRemoteDataset.spec.ts +2 -1
  27. package/src/graphql/resolvers/__tests__/permssions.spec.ts +3 -2
  28. package/src/graphql/resolvers/__tests__/user.spec.ts +39 -11
  29. package/src/graphql/resolvers/brainInitiative.ts +4 -3
  30. package/src/graphql/resolvers/cache.ts +7 -6
  31. package/src/graphql/resolvers/comment.ts +35 -19
  32. package/src/graphql/resolvers/dataset-search.ts +7 -6
  33. package/src/graphql/resolvers/dataset.ts +78 -46
  34. package/src/graphql/resolvers/datasetEvents.ts +18 -16
  35. package/src/graphql/resolvers/derivatives.ts +5 -1
  36. package/src/graphql/resolvers/description.ts +2 -1
  37. package/src/graphql/resolvers/draft.ts +14 -3
  38. package/src/graphql/resolvers/fileCheck.ts +5 -4
  39. package/src/graphql/resolvers/flaggedFiles.ts +2 -1
  40. package/src/graphql/resolvers/follow.ts +2 -1
  41. package/src/graphql/resolvers/git.ts +2 -1
  42. package/src/graphql/resolvers/gitEvents.ts +2 -1
  43. package/src/graphql/resolvers/history.ts +3 -1
  44. package/src/graphql/resolvers/holdDeletion.ts +1 -1
  45. package/src/graphql/resolvers/importRemoteDataset.ts +3 -2
  46. package/src/graphql/resolvers/issues.ts +2 -1
  47. package/src/graphql/resolvers/metadata.ts +3 -2
  48. package/src/graphql/resolvers/permissions.ts +26 -6
  49. package/src/graphql/resolvers/publish.ts +2 -1
  50. package/src/graphql/resolvers/readme.ts +2 -1
  51. package/src/graphql/resolvers/reexporter.ts +2 -1
  52. package/src/graphql/resolvers/relation.ts +3 -2
  53. package/src/graphql/resolvers/reset.ts +2 -1
  54. package/src/graphql/resolvers/reviewer.ts +14 -6
  55. package/src/graphql/resolvers/snapshots.ts +42 -10
  56. package/src/graphql/resolvers/stars.ts +2 -1
  57. package/src/graphql/resolvers/upload.ts +3 -2
  58. package/src/graphql/resolvers/user.ts +48 -32
  59. package/src/graphql/resolvers/validation.ts +6 -5
  60. package/src/graphql/resolvers/worker.ts +2 -1
  61. package/src/graphql/schema/analytics.ts +12 -0
  62. package/src/graphql/schema/comment.ts +30 -0
  63. package/src/graphql/schema/dataset-events.ts +91 -0
  64. package/src/graphql/schema/dataset-search.ts +32 -0
  65. package/src/graphql/schema/dataset.ts +167 -0
  66. package/src/graphql/schema/description.ts +44 -0
  67. package/src/graphql/schema/draft.ts +80 -0
  68. package/src/graphql/schema/enums.ts +55 -0
  69. package/src/graphql/schema/files.ts +44 -0
  70. package/src/graphql/schema/inputs.ts +231 -0
  71. package/src/graphql/schema/metadata.ts +86 -0
  72. package/src/graphql/schema/misc.ts +154 -0
  73. package/src/graphql/schema/mutation.ts +551 -0
  74. package/src/graphql/schema/pagination.ts +12 -0
  75. package/src/graphql/schema/permissions.ts +21 -0
  76. package/src/graphql/schema/query.ts +119 -0
  77. package/src/graphql/schema/refs.ts +23 -0
  78. package/src/graphql/schema/reviewer.ts +11 -0
  79. package/src/graphql/schema/scalars.ts +9 -0
  80. package/src/graphql/schema/snapshot.ts +111 -0
  81. package/src/graphql/schema/upload.ts +13 -0
  82. package/src/graphql/schema/user.ts +62 -0
  83. package/src/graphql/schema/validation.ts +70 -0
  84. package/src/graphql/schema/worker.ts +16 -0
  85. package/src/graphql/schema.ts +29 -1114
  86. package/src/handlers/subscriptions.ts +1 -1
  87. package/src/libs/apikey.ts +1 -1
  88. package/src/libs/authentication/crypto.ts +14 -9
  89. package/src/libs/notifications.ts +1 -1
  90. package/src/libs/presign.ts +32 -20
  91. package/src/libs/redis.ts +12 -2
  92. package/src/models/comment.ts +2 -4
  93. package/src/models/counter.ts +2 -2
  94. package/src/models/ingestDataset.ts +1 -2
  95. package/src/models/notification.ts +0 -2
  96. package/src/models/subscription.ts +0 -1
  97. package/src/models/user.ts +7 -2
  98. package/src/models/userMigration.ts +1 -1
  99. package/src/models/userNotificationStatus.ts +0 -1
  100. package/src/utils/__tests__/snapshots.spec.ts +120 -0
  101. package/src/utils/snapshots.ts +12 -0
@@ -2,19 +2,18 @@ import type { PipelineStage } from "mongoose"
2
2
  import User from "../../models/user"
3
3
  import DatasetEvent from "../../models/datasetEvents"
4
4
  import type { UserNotificationStatusDocument } from "../../models/userNotificationStatus"
5
-
6
5
  function isValidOrcid(orcid: string): boolean {
7
6
  return /^[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{3}[0-9X]$/.test(orcid || "")
8
7
  }
9
8
 
10
9
  // TODO - Use GraphQL codegen
11
- type GraphQLUserType = {
10
+ export type GraphQLUserType = {
12
11
  id: string
13
12
  provider: "orcid" | "google"
14
13
  avatar: string
15
14
  orcid: string
16
15
  created: Date
17
- modified: Date
16
+ updatedAt: Date
18
17
  lastSeen: Date
19
18
  email: string
20
19
  name: string
@@ -24,16 +23,16 @@ type GraphQLUserType = {
24
23
  institution: string
25
24
  github: string
26
25
  githubSynced: Date
27
- links: [string]
28
- notifications: [Record<string, unknown>]
29
- orcidConsent: boolean
26
+ links: string[]
27
+ orcidConsent: boolean | null
28
+ profilePrivate: boolean
30
29
  }
31
30
 
32
31
  export async function user(
33
- obj,
34
- { id },
35
- { userInfo }: { userInfo?: Record<string, unknown> } = {},
36
- ): Promise<Partial<GraphQLUserType> | null> {
32
+ obj: unknown,
33
+ { id }: { id: string },
34
+ { userInfo }: Partial<GraphQLContext> = {},
35
+ ): Promise<GraphQLUserType | null> {
37
36
  if (userInfo?.reviewer) {
38
37
  const oneWeekAgo = new Date()
39
38
  oneWeekAgo.setDate(oneWeekAgo.getDate() - 7)
@@ -42,15 +41,20 @@ export async function user(
42
41
  name: "Anonymous Reviewer",
43
42
  email: "reviewer@openneuro.org",
44
43
  provider: "orcid",
44
+ avatar: "",
45
45
  orcid: "0000-0000-0000-0000",
46
46
  admin: false,
47
47
  blocked: false,
48
48
  location: "",
49
49
  institution: "",
50
+ github: "",
51
+ githubSynced: oneWeekAgo,
52
+ links: [],
50
53
  orcidConsent: true,
54
+ profilePrivate: false,
51
55
  created: oneWeekAgo,
52
56
  lastSeen: new Date(),
53
- modified: oneWeekAgo,
57
+ updatedAt: oneWeekAgo,
54
58
  }
55
59
  }
56
60
 
@@ -76,19 +80,7 @@ export async function user(
76
80
  }
77
81
  }
78
82
 
79
- export interface UserInfo {
80
- userId: string
81
- admin: boolean
82
- username?: string
83
- provider?: string
84
- providerId?: string
85
- blocked?: boolean
86
- orcidConsent?: boolean | null
87
- }
88
-
89
- export interface GraphQLContext {
90
- userInfo: UserInfo | null
91
- }
83
+ import type { GraphQLContext } from "../builder"
92
84
 
93
85
  type MongoOperatorValue =
94
86
  | string
@@ -202,7 +194,11 @@ export const users = async (
202
194
  }
203
195
  }
204
196
 
205
- export const removeUser = (obj, { id }, { userInfo }) => {
197
+ export const removeUser = (
198
+ obj: unknown,
199
+ { id }: { id: string },
200
+ { userInfo }: GraphQLContext,
201
+ ) => {
206
202
  if (userInfo.admin) {
207
203
  return User.findByIdAndDelete(id).exec()
208
204
  } else {
@@ -210,7 +206,11 @@ export const removeUser = (obj, { id }, { userInfo }) => {
210
206
  }
211
207
  }
212
208
 
213
- export const setAdmin = (obj, { id, admin }, { userInfo }) => {
209
+ export const setAdmin = (
210
+ obj: unknown,
211
+ { id, admin }: { id: string; admin: boolean },
212
+ { userInfo }: GraphQLContext,
213
+ ) => {
214
214
  if (userInfo.admin) {
215
215
  return User.findOneAndUpdate({ id }, { admin }).exec()
216
216
  } else {
@@ -220,7 +220,11 @@ export const setAdmin = (obj, { id, admin }, { userInfo }) => {
220
220
  }
221
221
  }
222
222
 
223
- export const setBlocked = (obj, { id, blocked }, { userInfo }) => {
223
+ export const setBlocked = (
224
+ obj: unknown,
225
+ { id, blocked }: { id: string; blocked: boolean },
226
+ { userInfo }: GraphQLContext,
227
+ ) => {
224
228
  if (userInfo.admin) {
225
229
  return User.findOneAndUpdate({ id }, { blocked }).exec()
226
230
  } else {
@@ -229,9 +233,16 @@ export const setBlocked = (obj, { id, blocked }, { userInfo }) => {
229
233
  }
230
234
 
231
235
  export const updateUser = async (
232
- obj,
233
- { id, location, institution, links, orcidConsent },
234
- { userInfo },
236
+ obj: unknown,
237
+ { id, location, institution, links, orcidConsent, profilePrivate }: {
238
+ id: string
239
+ location?: string
240
+ institution?: string
241
+ links?: string[]
242
+ orcidConsent?: boolean
243
+ profilePrivate?: boolean
244
+ },
245
+ { userInfo }: GraphQLContext,
235
246
  ) => {
236
247
  if (!userInfo) {
237
248
  throw new Error("You must be logged in to update a user")
@@ -262,12 +273,13 @@ export const updateUser = async (
262
273
  if (institution !== undefined) user.institution = institution
263
274
  if (links !== undefined) user.links = links
264
275
  if (orcidConsent !== undefined) user.orcidConsent = orcidConsent
276
+ if (profilePrivate !== undefined) user.profilePrivate = profilePrivate
265
277
 
266
278
  await user.save()
267
279
 
268
280
  return user
269
281
  } catch (err) {
270
- throw new Error("Failed to update user: " + err.message)
282
+ throw new Error("Failed to update user: " + err.message, { cause: err })
271
283
  }
272
284
  }
273
285
 
@@ -275,7 +287,11 @@ export const updateUser = async (
275
287
  * Get all events associated with a specific user (for their notifications feed).
276
288
  * Uses a single aggregation pipeline for improved performance.
277
289
  */
278
- export async function notifications(obj, _, { userInfo }) {
290
+ export async function notifications(
291
+ obj: Pick<GraphQLUserType, "id">,
292
+ _: unknown,
293
+ { userInfo }: GraphQLContext,
294
+ ) {
279
295
  const userId = obj.id
280
296
 
281
297
  // Reviewers never have notifications
@@ -2,16 +2,17 @@ import config from "../../config"
2
2
  import { generateDataladCookie } from "../../libs/authentication/jwt"
3
3
  import { getDatasetWorker } from "../../libs/datalad-service"
4
4
  import Validation from "../../models/validation"
5
- import { redis } from "../../libs/redis"
5
+ import { getRedis } from "../../libs/redis"
6
6
  import CacheItem from "../../cache/item"
7
7
  import { CacheType } from "../../cache/types"
8
+ import type { GraphQLContext } from "../builder"
8
9
 
9
10
  /**
10
11
  * Issues resolver for schema validator
11
12
  */
12
- export const validation = async (dataset, _, { userInfo }) => {
13
+ export const validation = async (dataset, _, { userInfo }: GraphQLContext) => {
13
14
  const cache = new CacheItem(
14
- redis,
15
+ getRedis(),
15
16
  CacheType.validation,
16
17
  [dataset.id, dataset.revision],
17
18
  )
@@ -79,7 +80,7 @@ export const validation = async (dataset, _, { userInfo }) => {
79
80
  /**
80
81
  * Snapshot issues resolver for schema validator
81
82
  */
82
- export const snapshotValidation = async (snapshot, _, context) => {
83
+ export const snapshotValidation = async (snapshot, _, context: GraphQLContext) => {
83
84
  const dataset = {
84
85
  id: snapshot.id.split(":")[0],
85
86
  revision: snapshot.hexsha,
@@ -132,7 +133,7 @@ export const validationUrl = (datasetId, ref) => {
132
133
  * @param {string} args.datasetId Dataset accession number
133
134
  * @param {string} args.ref Git hexsha
134
135
  */
135
- export const revalidate = async (obj, { datasetId, ref }, { userInfo }) => {
136
+ export const revalidate = async (obj, { datasetId, ref }, { userInfo }: Pick<GraphQLContext, "userInfo">) => {
136
137
  try {
137
138
  const response = await fetch(validationUrl(datasetId, ref), {
138
139
  method: "POST",
@@ -1,12 +1,13 @@
1
1
  import WorkerTask from "../../models/worker-task"
2
2
  import { checkWorker } from "../permissions"
3
+ import type { GraphQLContext } from "../builder"
3
4
 
4
5
  /**
5
6
  * Update a worker task record
6
7
  *
7
8
  * This can be called for new tasks, or to update existing tasks.
8
9
  */
9
- export const updateWorkerTask = async (obj, args, { userInfo }) => {
10
+ export const updateWorkerTask = async (obj, args, { userInfo }: GraphQLContext) => {
10
11
  checkWorker(userInfo)
11
12
  const { id, ...updateData } = args
12
13
 
@@ -0,0 +1,12 @@
1
+ import { builder } from "../builder"
2
+
3
+ export const Analytic = builder.simpleObject("Analytic", {
4
+ description: "Analytics for a dataset",
5
+ directives: { cacheControl: { maxAge: 300 } },
6
+ fields: (t) => ({
7
+ datasetId: t.id({ nullable: false }),
8
+ tag: t.string(),
9
+ views: t.int(),
10
+ downloads: t.int(),
11
+ }),
12
+ })
@@ -0,0 +1,30 @@
1
+ import { CommentRef, UserRef } from "./refs"
2
+ import CommentFields from "../resolvers/comment"
3
+
4
+ CommentRef.implement({
5
+ fields: (t) => ({
6
+ id: t.id({
7
+ nullable: false,
8
+ resolve: (obj) => obj._id ?? obj.id,
9
+ }),
10
+ text: t.string({ nullable: false, resolve: (obj) => obj.text }),
11
+ user: t.field({
12
+ type: UserRef,
13
+ resolve: (obj) => CommentFields.user(obj),
14
+ }),
15
+ createDate: t.field({
16
+ type: "DateTime",
17
+ nullable: false,
18
+ resolve: (obj) => obj.createDate,
19
+ }),
20
+ parent: t.field({
21
+ type: CommentRef,
22
+ resolve: (obj) => CommentFields.parent(obj),
23
+ }),
24
+ replies: t.field({
25
+ type: [CommentRef],
26
+ nullable: { list: true, items: true },
27
+ resolve: (obj) => CommentFields.replies(obj),
28
+ }),
29
+ }),
30
+ })
@@ -0,0 +1,91 @@
1
+ import { DatasetEventDescriptionRef, DatasetEventRef, UserRef } from "./refs"
2
+ import { ResponseStatusType } from "./enums"
3
+ import { UserNotificationStatus } from "./misc"
4
+ import { Contributor } from "./description"
5
+ import {
6
+ DatasetEventDescriptionTypeResolvers,
7
+ DatasetEventTypeResolvers,
8
+ } from "../resolvers/datasetEvents"
9
+
10
+ DatasetEventDescriptionRef.implement({
11
+ fields: (t) => ({
12
+ type: t.string({ resolve: (obj) => obj.type }),
13
+ version: t.string({
14
+ resolve: (obj) => "version" in obj ? obj.version : null,
15
+ }),
16
+ public: t.boolean({
17
+ resolve: (obj) => "public" in obj ? obj.public : null,
18
+ }),
19
+ target: t.field({
20
+ type: UserRef,
21
+ resolve: (obj) => ("target" in obj ? obj.target : null) as never,
22
+ }),
23
+ targetUserId: t.id({
24
+ resolve: (obj) => "targetUserId" in obj ? obj.targetUserId : null,
25
+ }),
26
+ level: t.string({
27
+ resolve: (obj) => "level" in obj ? obj.level : null,
28
+ }),
29
+ ref: t.string({
30
+ resolve: (obj) => "reference" in obj ? obj.reference : null,
31
+ }),
32
+ message: t.string({
33
+ resolve: () => null,
34
+ }),
35
+ requestId: t.id({
36
+ resolve: (obj) => "requestId" in obj ? obj.requestId : null,
37
+ }),
38
+ reason: t.string({
39
+ resolve: (obj) => "reason" in obj ? obj.reason : null,
40
+ }),
41
+ datasetId: t.id({
42
+ resolve: (obj) => "datasetId" in obj ? obj.datasetId : null,
43
+ }),
44
+ resolutionStatus: t.field({
45
+ type: ResponseStatusType,
46
+ resolve: (obj) =>
47
+ DatasetEventDescriptionTypeResolvers.resolutionStatus(
48
+ obj as {
49
+ resolutionStatus?: "pending" | "accepted" | "denied" | null
50
+ },
51
+ ),
52
+ }),
53
+ contributorData: t.field({
54
+ type: Contributor,
55
+ resolve: (obj) =>
56
+ "contributorData" in obj ? obj.contributorData : null,
57
+ }),
58
+ }),
59
+ })
60
+
61
+ DatasetEventRef.implement({
62
+ fields: (t) => ({
63
+ id: t.id({ resolve: (obj) => obj.id }),
64
+ timestamp: t.field({
65
+ type: "DateTime",
66
+ resolve: (obj) => obj.timestamp,
67
+ }),
68
+ user: t.field({
69
+ type: UserRef,
70
+ resolve: (obj) => obj.user as never,
71
+ }),
72
+ event: t.field({
73
+ type: DatasetEventDescriptionRef,
74
+ resolve: (obj) => obj.event,
75
+ }),
76
+ success: t.boolean({ resolve: (obj) => obj.success }),
77
+ note: t.string({ resolve: (obj) => obj.note }),
78
+ datasetId: t.id({ resolve: (obj) => obj.datasetId }),
79
+ notificationStatus: t.field({
80
+ type: UserNotificationStatus,
81
+ resolve: (obj) => obj.notificationStatus,
82
+ }),
83
+ responseStatus: t.field({
84
+ type: ResponseStatusType,
85
+ resolve: (ev) => DatasetEventTypeResolvers.responseStatus(ev),
86
+ }),
87
+ hasBeenRespondedTo: t.boolean({
88
+ resolve: (ev) => DatasetEventTypeResolvers.hasBeenRespondedTo(ev),
89
+ }),
90
+ }),
91
+ })
@@ -0,0 +1,32 @@
1
+ import { builder } from "../builder"
2
+ import { DatasetConnection } from "./dataset"
3
+ import { DatasetSearchInput } from "./inputs"
4
+ import {
5
+ advancedDatasetSearchConnection,
6
+ datasetSearchConnection,
7
+ } from "../resolvers/dataset-search"
8
+
9
+ builder.queryFields((t) => ({
10
+ search: t.field({
11
+ type: DatasetConnection,
12
+ args: {
13
+ q: t.arg.string({ required: true }),
14
+ after: t.arg.string(),
15
+ first: t.arg.int(),
16
+ },
17
+ resolve: (root, args) => datasetSearchConnection(root, args as never),
18
+ }),
19
+ advancedSearch: t.field({
20
+ type: DatasetConnection,
21
+ args: {
22
+ query: t.arg({ type: DatasetSearchInput, required: true }),
23
+ allDatasets: t.arg.boolean(),
24
+ datasetType: t.arg.string(),
25
+ datasetStatus: t.arg.string(),
26
+ after: t.arg.string(),
27
+ first: t.arg.int(),
28
+ },
29
+ resolve: (root, args, ctx) =>
30
+ advancedDatasetSearchConnection(root, args as never, ctx),
31
+ }),
32
+ }))
@@ -0,0 +1,167 @@
1
+ import { builder } from "../builder"
2
+ import {
3
+ CommentRef,
4
+ DatasetEventRef,
5
+ DatasetRef,
6
+ DraftRef,
7
+ SnapshotRef,
8
+ UserRef,
9
+ } from "./refs"
10
+ import { PageInfo } from "./pagination"
11
+ import { DatasetPermissions } from "./permissions"
12
+ import { Analytic } from "./analytics"
13
+ import { DatasetCommit, DatasetDerivatives, Follower, Star } from "./misc"
14
+ import { Metadata } from "./metadata"
15
+ import { DatasetReviewer } from "./reviewer"
16
+ import {
17
+ analytics,
18
+ datasetName,
19
+ followers,
20
+ following,
21
+ starred,
22
+ stars,
23
+ } from "../resolvers/dataset"
24
+ import { user } from "../resolvers/user"
25
+ import { latestSnapshot, snapshots } from "../resolvers/snapshots"
26
+ import { permissions } from "../resolvers/permissions"
27
+ import { datasetComments } from "../resolvers/comment"
28
+ import { metadata } from "../resolvers/metadata"
29
+ import { history } from "../resolvers/history"
30
+ import { onBrainlife } from "../resolvers/brainlife"
31
+ import { brainInitiative } from "../resolvers/brainInitiative"
32
+ import { derivatives } from "../resolvers/derivatives"
33
+ import { reviewers } from "../resolvers/reviewer"
34
+ import { datasetEvents } from "../resolvers/datasetEvents"
35
+ import { getDatasetWorker } from "../../libs/datalad-service"
36
+ import { getDraftInfo } from "../../datalad/draft"
37
+
38
+ export const DatasetEdge = builder.simpleObject("DatasetEdge", {
39
+ fields: (t) => ({
40
+ id: t.string({ nullable: false }),
41
+ node: t.field({ type: DatasetRef, nullable: false }),
42
+ cursor: t.string({ nullable: false }),
43
+ }),
44
+ })
45
+
46
+ export const DatasetConnection = builder.simpleObject("DatasetConnection", {
47
+ fields: (t) => ({
48
+ edges: t.field({
49
+ type: [DatasetEdge],
50
+ nullable: { list: true, items: true },
51
+ }),
52
+ pageInfo: t.field({ type: PageInfo, nullable: false }),
53
+ }),
54
+ })
55
+
56
+ DatasetRef.implement({
57
+ fields: (t) => ({
58
+ id: t.id({ nullable: false, resolve: (obj) => obj.id }),
59
+ created: t.field({
60
+ type: "DateTime",
61
+ nullable: false,
62
+ resolve: (obj) => obj.created,
63
+ }),
64
+ uploader: t.field({
65
+ type: UserRef,
66
+ resolve: (obj, _args, ctx) =>
67
+ user(null, { id: obj.uploader }, ctx),
68
+ }),
69
+ public: t.boolean({ resolve: (obj) => obj.public }),
70
+ draft: t.field({
71
+ type: DraftRef,
72
+ resolve: async (obj) => {
73
+ const draftHead = await getDraftInfo(obj.id)
74
+ return {
75
+ id: obj.id,
76
+ revision: draftHead.ref,
77
+ modified: new Date(draftHead.modified),
78
+ }
79
+ },
80
+ }),
81
+ snapshots: t.field({
82
+ type: [SnapshotRef],
83
+ nullable: { list: true, items: true },
84
+ resolve: (obj) => snapshots(obj) as never,
85
+ }),
86
+ latestSnapshot: t.field({
87
+ type: SnapshotRef,
88
+ nullable: false,
89
+ resolve: (obj, _args, ctx) => latestSnapshot(obj, null, ctx),
90
+ }),
91
+ permissions: t.field({
92
+ type: DatasetPermissions,
93
+ resolve: (obj, _args, ctx) => permissions(obj, null, ctx) as never,
94
+ }),
95
+ analytics: t.field({
96
+ type: Analytic,
97
+ resolve: (obj) => analytics(obj),
98
+ }),
99
+ stars: t.field({
100
+ type: [Star],
101
+ nullable: { list: true, items: true },
102
+ resolve: (obj) => stars(obj),
103
+ }),
104
+ followers: t.field({
105
+ type: [Follower],
106
+ nullable: { list: true, items: true },
107
+ resolve: (obj) => followers(obj),
108
+ }),
109
+ name: t.string({
110
+ resolve: (obj) => datasetName(obj),
111
+ }),
112
+ comments: t.field({
113
+ type: [CommentRef],
114
+ nullable: { list: true, items: true },
115
+ resolve: (obj) => datasetComments(obj),
116
+ }),
117
+ following: t.boolean({
118
+ resolve: (obj, _args, ctx) => following(obj, _args, ctx),
119
+ }),
120
+ starred: t.boolean({
121
+ resolve: (obj, _args, ctx) => starred(obj, _args, ctx),
122
+ }),
123
+ publishDate: t.field({
124
+ type: "DateTime",
125
+ resolve: (obj) => obj.publishDate,
126
+ }),
127
+ onBrainlife: t.boolean({
128
+ directives: { cacheControl: { maxAge: 10080 } },
129
+ resolve: (obj) => onBrainlife(obj as never) as never,
130
+ }),
131
+ derivatives: t.field({
132
+ type: [DatasetDerivatives],
133
+ nullable: { list: true, items: true },
134
+ directives: { cacheControl: { maxAge: 3600 } },
135
+ resolve: (obj) => derivatives(obj as never) as never,
136
+ }),
137
+ metadata: t.field({
138
+ type: Metadata,
139
+ resolve: (obj, _args, ctx) => metadata(obj, null, ctx),
140
+ }),
141
+ history: t.field({
142
+ type: [DatasetCommit],
143
+ nullable: { list: true, items: true },
144
+ resolve: (obj) => history(obj),
145
+ }),
146
+ worker: t.string({
147
+ resolve: (obj) => getDatasetWorker(obj.id),
148
+ }),
149
+ reviewers: t.field({
150
+ type: [DatasetReviewer],
151
+ nullable: { list: true, items: true },
152
+ resolve: (obj, _args, ctx) => reviewers(obj, null, ctx),
153
+ }),
154
+ brainInitiative: t.boolean({
155
+ resolve: (obj, _args, ctx) =>
156
+ brainInitiative(obj as never, null, ctx) as never,
157
+ }),
158
+ events: t.field({
159
+ type: [DatasetEventRef],
160
+ nullable: { list: true, items: true },
161
+ resolve: (obj, _args, ctx) => datasetEvents(obj, null, ctx),
162
+ }),
163
+ holdDeletion: t.boolean({
164
+ resolve: (obj) => obj.holdDeletion
165
+ }),
166
+ }),
167
+ })
@@ -0,0 +1,44 @@
1
+ import { builder } from "../builder"
2
+ import { DatasetRef } from "./refs"
3
+
4
+ export const Description = builder.simpleObject("Description", {
5
+ description: "Contents of dataset_description.json",
6
+ directives: { cacheControl: { maxAge: 30 } },
7
+ fields: (t) => ({
8
+ id: t.id({ nullable: false }),
9
+ Name: t.string({ nullable: false }),
10
+ BIDSVersion: t.string({ nullable: false }),
11
+ License: t.string(),
12
+ Authors: t.stringList({ nullable: { list: true, items: true } }),
13
+ SeniorAuthor: t.string(),
14
+ Acknowledgements: t.string(),
15
+ HowToAcknowledge: t.string(),
16
+ Funding: t.stringList({ nullable: { list: true, items: true } }),
17
+ ReferencesAndLinks: t.stringList({ nullable: { list: true, items: true } }),
18
+ DatasetDOI: t.string(),
19
+ DatasetType: t.string(),
20
+ EthicsApprovals: t.stringList({ nullable: { list: true, items: true } }),
21
+ }),
22
+ })
23
+
24
+ export const Contributor = builder.simpleObject("Contributor", {
25
+ description: "Defines the Contributor type in contributors.ts",
26
+ fields: (t) => ({
27
+ name: t.string({ nullable: false }),
28
+ givenName: t.string(),
29
+ familyName: t.string(),
30
+ orcid: t.string(),
31
+ contributorType: t.string({ nullable: false }),
32
+ order: t.int(),
33
+ }),
34
+ })
35
+
36
+ export const UpdateContributorsPayload = builder.simpleObject(
37
+ "UpdateContributorsPayload",
38
+ {
39
+ fields: (t) => ({
40
+ success: t.boolean({ nullable: false }),
41
+ dataset: t.field({ type: DatasetRef }),
42
+ }),
43
+ },
44
+ )
@@ -0,0 +1,80 @@
1
+ import { DatasetRef, DraftRef } from "./refs"
2
+ import { Summary } from "./metadata"
3
+ import { ValidationIssue, ValidationIssueStatus } from "./validation"
4
+ import { DatasetValidation } from "./validation"
5
+ import { DatasetFile } from "./files"
6
+ import { Description } from "./description"
7
+ import { Contributor } from "./description"
8
+ import { UploadMetadata } from "./upload"
9
+ import { FileCheck } from "./misc"
10
+ import DraftFields, { draftFiles, fileCheck } from "../resolvers/draft"
11
+
12
+ DraftRef.implement({
13
+ fields: (t) => ({
14
+ id: t.id({ resolve: (obj) => obj.id }),
15
+ dataset: t.field({
16
+ type: DatasetRef,
17
+ resolve: (obj) => ({ id: obj.id }) as never,
18
+ }),
19
+ modified: t.field({
20
+ type: "DateTime",
21
+ resolve: (obj) =>
22
+ obj.modified ?? null,
23
+ }),
24
+ summary: t.field({
25
+ type: Summary,
26
+ resolve: (obj) => DraftFields.summary(obj) as never,
27
+ }),
28
+ issues: t.field({
29
+ type: [ValidationIssue],
30
+ nullable: { list: true, items: true },
31
+ resolve: (obj, _args, ctx) => DraftFields.issues(obj, null, ctx),
32
+ }),
33
+ issuesStatus: t.field({
34
+ type: ValidationIssueStatus,
35
+ resolve: (obj) => DraftFields.issuesStatus(obj),
36
+ }),
37
+ validation: t.field({
38
+ type: DatasetValidation,
39
+ resolve: (obj, _args, ctx) =>
40
+ DraftFields.validation(obj, null, ctx) as never,
41
+ }),
42
+ files: t.field({
43
+ type: [DatasetFile],
44
+ nullable: { list: true, items: true },
45
+ args: {
46
+ tree: t.arg.string(),
47
+ recursive: t.arg.boolean(),
48
+ },
49
+ resolve: (obj, args, ctx) => draftFiles(obj, args, ctx),
50
+ }),
51
+ description: t.field({
52
+ type: Description,
53
+ resolve: (obj) => DraftFields.description(obj),
54
+ }),
55
+ readme: t.string({
56
+ resolve: (obj) => DraftFields.readme(obj),
57
+ }),
58
+ uploads: t.field({
59
+ type: [UploadMetadata],
60
+ nullable: { list: true, items: true },
61
+ resolve: () => null,
62
+ }),
63
+ head: t.string({
64
+ resolve: (obj) => DraftFields.head(obj),
65
+ }),
66
+ size: t.field({
67
+ type: "BigInt",
68
+ resolve: (obj, _args, ctx) => DraftFields.size(obj, null, ctx) as never,
69
+ }),
70
+ fileCheck: t.field({
71
+ type: FileCheck,
72
+ resolve: (obj) => fileCheck(obj),
73
+ }),
74
+ contributors: t.field({
75
+ type: [Contributor],
76
+ nullable: { list: true, items: true },
77
+ resolve: (obj) => DraftFields.contributors(obj),
78
+ }),
79
+ }),
80
+ })