@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.
- package/package.json +8 -5
- package/src/app.ts +2 -4
- package/src/cache/__tests__/tree.spec.ts +2 -0
- package/src/cache/tree.ts +4 -0
- package/src/cache/types.ts +1 -0
- package/src/datalad/__tests__/contributors.spec.ts +1 -1
- package/src/datalad/__tests__/dataRetentionNotifications.spec.ts +11 -11
- package/src/datalad/__tests__/files.spec.ts +31 -1
- package/src/datalad/__tests__/snapshots.spec.ts +4 -4
- package/src/datalad/contributors.ts +2 -2
- package/src/datalad/dataRetentionNotifications.ts +5 -5
- package/src/datalad/dataset.ts +9 -5
- package/src/datalad/description.ts +2 -2
- package/src/datalad/draft.ts +8 -3
- package/src/datalad/files.ts +71 -12
- package/src/datalad/readme.ts +2 -2
- package/src/datalad/snapshots.ts +19 -14
- package/src/elasticsearch/elastic-client.ts +11 -6
- package/src/elasticsearch/reindex-dataset.ts +8 -4
- package/src/graphql/__tests__/comment.spec.ts +3 -2
- package/src/graphql/__tests__/schema.spec.ts +28 -0
- package/src/graphql/builder.ts +42 -0
- package/src/graphql/resolvers/__tests__/dataset.spec.ts +6 -119
- package/src/graphql/resolvers/__tests__/importRemoteDataset.spec.ts +2 -1
- package/src/graphql/resolvers/__tests__/permssions.spec.ts +3 -2
- package/src/graphql/resolvers/__tests__/user.spec.ts +39 -11
- package/src/graphql/resolvers/brainInitiative.ts +4 -3
- package/src/graphql/resolvers/cache.ts +7 -6
- package/src/graphql/resolvers/comment.ts +35 -19
- package/src/graphql/resolvers/dataset-search.ts +7 -6
- package/src/graphql/resolvers/dataset.ts +77 -45
- package/src/graphql/resolvers/datasetEvents.ts +18 -16
- package/src/graphql/resolvers/description.ts +2 -1
- package/src/graphql/resolvers/draft.ts +14 -3
- package/src/graphql/resolvers/fileCheck.ts +5 -4
- package/src/graphql/resolvers/flaggedFiles.ts +2 -1
- package/src/graphql/resolvers/follow.ts +2 -1
- package/src/graphql/resolvers/git.ts +2 -1
- package/src/graphql/resolvers/gitEvents.ts +2 -1
- package/src/graphql/resolvers/history.ts +3 -1
- package/src/graphql/resolvers/holdDeletion.ts +1 -1
- package/src/graphql/resolvers/importRemoteDataset.ts +3 -2
- package/src/graphql/resolvers/issues.ts +2 -1
- package/src/graphql/resolvers/metadata.ts +3 -2
- package/src/graphql/resolvers/permissions.ts +26 -6
- package/src/graphql/resolvers/publish.ts +2 -1
- package/src/graphql/resolvers/readme.ts +2 -1
- package/src/graphql/resolvers/reexporter.ts +2 -1
- package/src/graphql/resolvers/relation.ts +3 -2
- package/src/graphql/resolvers/reset.ts +2 -1
- package/src/graphql/resolvers/reviewer.ts +14 -6
- package/src/graphql/resolvers/snapshots.ts +42 -10
- package/src/graphql/resolvers/stars.ts +2 -1
- package/src/graphql/resolvers/upload.ts +3 -2
- package/src/graphql/resolvers/user.ts +44 -32
- package/src/graphql/resolvers/validation.ts +6 -5
- package/src/graphql/resolvers/worker.ts +2 -1
- package/src/graphql/schema/analytics.ts +12 -0
- package/src/graphql/schema/comment.ts +30 -0
- package/src/graphql/schema/dataset-events.ts +91 -0
- package/src/graphql/schema/dataset-search.ts +32 -0
- package/src/graphql/schema/dataset.ts +167 -0
- package/src/graphql/schema/description.ts +44 -0
- package/src/graphql/schema/draft.ts +80 -0
- package/src/graphql/schema/enums.ts +55 -0
- package/src/graphql/schema/files.ts +44 -0
- package/src/graphql/schema/inputs.ts +231 -0
- package/src/graphql/schema/metadata.ts +86 -0
- package/src/graphql/schema/misc.ts +154 -0
- package/src/graphql/schema/mutation.ts +549 -0
- package/src/graphql/schema/pagination.ts +12 -0
- package/src/graphql/schema/permissions.ts +21 -0
- package/src/graphql/schema/query.ts +119 -0
- package/src/graphql/schema/refs.ts +23 -0
- package/src/graphql/schema/reviewer.ts +11 -0
- package/src/graphql/schema/scalars.ts +9 -0
- package/src/graphql/schema/snapshot.ts +111 -0
- package/src/graphql/schema/upload.ts +13 -0
- package/src/graphql/schema/user.ts +61 -0
- package/src/graphql/schema/validation.ts +70 -0
- package/src/graphql/schema/worker.ts +16 -0
- package/src/graphql/schema.ts +29 -1114
- package/src/handlers/subscriptions.ts +1 -1
- package/src/libs/apikey.ts +1 -1
- package/src/libs/authentication/crypto.ts +14 -9
- package/src/libs/notifications.ts +1 -1
- package/src/libs/presign.ts +32 -20
- package/src/libs/redis.ts +12 -2
- package/src/models/comment.ts +2 -4
- package/src/models/counter.ts +2 -2
- package/src/models/ingestDataset.ts +1 -2
- package/src/models/notification.ts +0 -2
- package/src/models/subscription.ts +0 -1
- package/src/models/user.ts +1 -2
- package/src/models/userMigration.ts +1 -1
- package/src/models/userNotificationStatus.ts +0 -1
- package/src/utils/__tests__/snapshots.spec.ts +120 -0
- 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
|
-
|
|
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: [
|
|
28
|
-
|
|
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 }:
|
|
36
|
-
): Promise<
|
|
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
|
-
|
|
55
|
+
updatedAt: oneWeekAgo,
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
|
|
@@ -76,19 +78,7 @@ export async function user(
|
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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
|
-
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
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
|
+
})
|