@openneuro/server 5.0.0 → 5.1.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.
Files changed (98) 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__/files.spec.ts +31 -1
  9. package/src/datalad/__tests__/snapshots.spec.ts +4 -4
  10. package/src/datalad/contributors.ts +2 -2
  11. package/src/datalad/dataRetentionNotifications.ts +5 -5
  12. package/src/datalad/dataset.ts +9 -5
  13. package/src/datalad/description.ts +2 -2
  14. package/src/datalad/draft.ts +8 -3
  15. package/src/datalad/files.ts +71 -12
  16. package/src/datalad/readme.ts +2 -2
  17. package/src/datalad/snapshots.ts +19 -14
  18. package/src/elasticsearch/elastic-client.ts +11 -6
  19. package/src/elasticsearch/reindex-dataset.ts +8 -4
  20. package/src/graphql/__tests__/comment.spec.ts +3 -2
  21. package/src/graphql/__tests__/schema.spec.ts +28 -0
  22. package/src/graphql/builder.ts +42 -0
  23. package/src/graphql/resolvers/__tests__/dataset.spec.ts +6 -119
  24. package/src/graphql/resolvers/__tests__/importRemoteDataset.spec.ts +2 -1
  25. package/src/graphql/resolvers/__tests__/permssions.spec.ts +3 -2
  26. package/src/graphql/resolvers/__tests__/user.spec.ts +39 -11
  27. package/src/graphql/resolvers/brainInitiative.ts +4 -3
  28. package/src/graphql/resolvers/cache.ts +7 -6
  29. package/src/graphql/resolvers/comment.ts +35 -19
  30. package/src/graphql/resolvers/dataset-search.ts +7 -6
  31. package/src/graphql/resolvers/dataset.ts +77 -45
  32. package/src/graphql/resolvers/datasetEvents.ts +18 -16
  33. package/src/graphql/resolvers/description.ts +2 -1
  34. package/src/graphql/resolvers/draft.ts +14 -3
  35. package/src/graphql/resolvers/fileCheck.ts +5 -4
  36. package/src/graphql/resolvers/flaggedFiles.ts +2 -1
  37. package/src/graphql/resolvers/follow.ts +2 -1
  38. package/src/graphql/resolvers/git.ts +2 -1
  39. package/src/graphql/resolvers/gitEvents.ts +2 -1
  40. package/src/graphql/resolvers/history.ts +3 -1
  41. package/src/graphql/resolvers/holdDeletion.ts +1 -1
  42. package/src/graphql/resolvers/importRemoteDataset.ts +3 -2
  43. package/src/graphql/resolvers/issues.ts +2 -1
  44. package/src/graphql/resolvers/metadata.ts +3 -2
  45. package/src/graphql/resolvers/permissions.ts +26 -6
  46. package/src/graphql/resolvers/publish.ts +2 -1
  47. package/src/graphql/resolvers/readme.ts +2 -1
  48. package/src/graphql/resolvers/reexporter.ts +2 -1
  49. package/src/graphql/resolvers/relation.ts +3 -2
  50. package/src/graphql/resolvers/reset.ts +2 -1
  51. package/src/graphql/resolvers/reviewer.ts +14 -6
  52. package/src/graphql/resolvers/snapshots.ts +42 -10
  53. package/src/graphql/resolvers/stars.ts +2 -1
  54. package/src/graphql/resolvers/upload.ts +3 -2
  55. package/src/graphql/resolvers/user.ts +44 -32
  56. package/src/graphql/resolvers/validation.ts +6 -5
  57. package/src/graphql/resolvers/worker.ts +2 -1
  58. package/src/graphql/schema/analytics.ts +12 -0
  59. package/src/graphql/schema/comment.ts +30 -0
  60. package/src/graphql/schema/dataset-events.ts +91 -0
  61. package/src/graphql/schema/dataset-search.ts +32 -0
  62. package/src/graphql/schema/dataset.ts +167 -0
  63. package/src/graphql/schema/description.ts +44 -0
  64. package/src/graphql/schema/draft.ts +80 -0
  65. package/src/graphql/schema/enums.ts +55 -0
  66. package/src/graphql/schema/files.ts +44 -0
  67. package/src/graphql/schema/inputs.ts +231 -0
  68. package/src/graphql/schema/metadata.ts +86 -0
  69. package/src/graphql/schema/misc.ts +154 -0
  70. package/src/graphql/schema/mutation.ts +549 -0
  71. package/src/graphql/schema/pagination.ts +12 -0
  72. package/src/graphql/schema/permissions.ts +21 -0
  73. package/src/graphql/schema/query.ts +119 -0
  74. package/src/graphql/schema/refs.ts +23 -0
  75. package/src/graphql/schema/reviewer.ts +11 -0
  76. package/src/graphql/schema/scalars.ts +9 -0
  77. package/src/graphql/schema/snapshot.ts +111 -0
  78. package/src/graphql/schema/upload.ts +13 -0
  79. package/src/graphql/schema/user.ts +61 -0
  80. package/src/graphql/schema/validation.ts +70 -0
  81. package/src/graphql/schema/worker.ts +16 -0
  82. package/src/graphql/schema.ts +29 -1114
  83. package/src/handlers/subscriptions.ts +1 -1
  84. package/src/libs/apikey.ts +1 -1
  85. package/src/libs/authentication/crypto.ts +14 -9
  86. package/src/libs/notifications.ts +1 -1
  87. package/src/libs/presign.ts +32 -20
  88. package/src/libs/redis.ts +12 -2
  89. package/src/models/comment.ts +2 -4
  90. package/src/models/counter.ts +2 -2
  91. package/src/models/ingestDataset.ts +1 -2
  92. package/src/models/notification.ts +0 -2
  93. package/src/models/subscription.ts +0 -1
  94. package/src/models/user.ts +1 -2
  95. package/src/models/userMigration.ts +1 -1
  96. package/src/models/userNotificationStatus.ts +0 -1
  97. package/src/utils/__tests__/snapshots.spec.ts +120 -0
  98. 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,15 @@ 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
30
28
  }
31
29
 
32
30
  export async function user(
33
- obj,
34
- { id },
35
- { userInfo }: { userInfo?: Record<string, unknown> } = {},
36
- ): Promise<Partial<GraphQLUserType> | null> {
31
+ obj: unknown,
32
+ { id }: { id: string },
33
+ { userInfo }: Partial<GraphQLContext> = {},
34
+ ): Promise<GraphQLUserType | null> {
37
35
  if (userInfo?.reviewer) {
38
36
  const oneWeekAgo = new Date()
39
37
  oneWeekAgo.setDate(oneWeekAgo.getDate() - 7)
@@ -42,15 +40,19 @@ export async function user(
42
40
  name: "Anonymous Reviewer",
43
41
  email: "reviewer@openneuro.org",
44
42
  provider: "orcid",
43
+ avatar: "",
45
44
  orcid: "0000-0000-0000-0000",
46
45
  admin: false,
47
46
  blocked: false,
48
47
  location: "",
49
48
  institution: "",
49
+ github: "",
50
+ githubSynced: oneWeekAgo,
51
+ links: [],
50
52
  orcidConsent: true,
51
53
  created: oneWeekAgo,
52
54
  lastSeen: new Date(),
53
- modified: oneWeekAgo,
55
+ updatedAt: oneWeekAgo,
54
56
  }
55
57
  }
56
58
 
@@ -76,19 +78,7 @@ export async function user(
76
78
  }
77
79
  }
78
80
 
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
- }
81
+ import type { GraphQLContext } from "../builder"
92
82
 
93
83
  type MongoOperatorValue =
94
84
  | string
@@ -202,7 +192,11 @@ export const users = async (
202
192
  }
203
193
  }
204
194
 
205
- export const removeUser = (obj, { id }, { userInfo }) => {
195
+ export const removeUser = (
196
+ obj: unknown,
197
+ { id }: { id: string },
198
+ { userInfo }: GraphQLContext,
199
+ ) => {
206
200
  if (userInfo.admin) {
207
201
  return User.findByIdAndDelete(id).exec()
208
202
  } else {
@@ -210,7 +204,11 @@ export const removeUser = (obj, { id }, { userInfo }) => {
210
204
  }
211
205
  }
212
206
 
213
- export const setAdmin = (obj, { id, admin }, { userInfo }) => {
207
+ export const setAdmin = (
208
+ obj: unknown,
209
+ { id, admin }: { id: string; admin: boolean },
210
+ { userInfo }: GraphQLContext,
211
+ ) => {
214
212
  if (userInfo.admin) {
215
213
  return User.findOneAndUpdate({ id }, { admin }).exec()
216
214
  } else {
@@ -220,7 +218,11 @@ export const setAdmin = (obj, { id, admin }, { userInfo }) => {
220
218
  }
221
219
  }
222
220
 
223
- export const setBlocked = (obj, { id, blocked }, { userInfo }) => {
221
+ export const setBlocked = (
222
+ obj: unknown,
223
+ { id, blocked }: { id: string; blocked: boolean },
224
+ { userInfo }: GraphQLContext,
225
+ ) => {
224
226
  if (userInfo.admin) {
225
227
  return User.findOneAndUpdate({ id }, { blocked }).exec()
226
228
  } else {
@@ -229,9 +231,15 @@ export const setBlocked = (obj, { id, blocked }, { userInfo }) => {
229
231
  }
230
232
 
231
233
  export const updateUser = async (
232
- obj,
233
- { id, location, institution, links, orcidConsent },
234
- { userInfo },
234
+ obj: unknown,
235
+ { id, location, institution, links, orcidConsent }: {
236
+ id: string
237
+ location?: string
238
+ institution?: string
239
+ links?: string[]
240
+ orcidConsent?: boolean
241
+ },
242
+ { userInfo }: GraphQLContext,
235
243
  ) => {
236
244
  if (!userInfo) {
237
245
  throw new Error("You must be logged in to update a user")
@@ -267,7 +275,7 @@ export const updateUser = async (
267
275
 
268
276
  return user
269
277
  } catch (err) {
270
- throw new Error("Failed to update user: " + err.message)
278
+ throw new Error("Failed to update user: " + err.message, { cause: err })
271
279
  }
272
280
  }
273
281
 
@@ -275,7 +283,11 @@ export const updateUser = async (
275
283
  * Get all events associated with a specific user (for their notifications feed).
276
284
  * Uses a single aggregation pipeline for improved performance.
277
285
  */
278
- export async function notifications(obj, _, { userInfo }) {
286
+ export async function notifications(
287
+ obj: Pick<GraphQLUserType, "id">,
288
+ _: unknown,
289
+ { userInfo }: GraphQLContext,
290
+ ) {
279
291
  const userId = obj.id
280
292
 
281
293
  // 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
+ })