@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
@@ -10,9 +10,16 @@ import { filterRemovedAnnexObjects } from "../utils/file.js"
10
10
  import { validation } from "./validation"
11
11
  import FileCheck from "../../models/fileCheck"
12
12
  import { contributors } from "../../datalad/contributors"
13
+ import type { GraphQLContext } from "../builder"
14
+
15
+ export type DraftShape = { id: string; revision?: string; modified?: Date }
13
16
 
14
17
  // A draft must have a dataset parent
15
- export const draftFiles = async (dataset, args, { userInfo }) => {
18
+ export const draftFiles = async (
19
+ dataset: DraftShape,
20
+ args: { tree?: string | null; recursive?: boolean | null },
21
+ { userInfo }: Pick<GraphQLContext, "userInfo">,
22
+ ) => {
16
23
  const hexsha = await getDraftRevision(dataset.id)
17
24
  const treeish = args.tree || hexsha
18
25
  const files = args.recursive
@@ -32,14 +39,18 @@ const draftSize = async (dataset, args, { userInfo }) => {
32
39
  /**
33
40
  * Mutation to move the draft HEAD reference forward or backward
34
41
  */
35
- export const revalidate = async (obj, { datasetId }, { user, userInfo }) => {
42
+ export const revalidate = async (
43
+ obj: unknown,
44
+ { datasetId }: { datasetId: string },
45
+ { user, userInfo }: GraphQLContext,
46
+ ) => {
36
47
  await checkDatasetWrite(datasetId, user, userInfo)
37
48
  }
38
49
 
39
50
  /**
40
51
  * Resolve any issues with files recorded for the current commit
41
52
  */
42
- export const fileCheck = async (dataset) => {
53
+ export const fileCheck = async (dataset: DraftShape) => {
43
54
  const hexsha = await getDraftRevision(dataset.id)
44
55
  // For drafts, obtain the local check results
45
56
  const checks = await FileCheck.findOne({
@@ -1,14 +1,15 @@
1
1
  import config from "../../config"
2
2
  import { generateDataladCookie } from "../../libs/authentication/jwt"
3
3
  import { getDatasetWorker } from "../../libs/datalad-service"
4
- import { redlock } from "../../libs/redis"
4
+ import { getRedlock } from "../../libs/redis"
5
5
  import FileCheck from "../../models/fileCheck"
6
6
  import { checkDatasetAdmin, checkDatasetWrite } from "../permissions"
7
+ import type { GraphQLContext } from "../builder"
7
8
 
8
9
  export const updateFileCheck = async (
9
10
  obj,
10
11
  { datasetId, hexsha, refs, remote, annexFsck },
11
- { user, userInfo },
12
+ { user, userInfo }: GraphQLContext,
12
13
  ) => {
13
14
  await checkDatasetAdmin(datasetId, user, userInfo)
14
15
  return await FileCheck.findOneAndUpdate(
@@ -28,7 +29,7 @@ export const fsckUrl = (datasetId) => {
28
29
  }/datasets/${datasetId}/fsck`
29
30
  }
30
31
 
31
- export const fsckDataset = async (_, { datasetId }, { user, userInfo }) => {
32
+ export const fsckDataset = async (_, { datasetId }, { user, userInfo }: GraphQLContext) => {
32
33
  // Anonymous users can't trigger fsck
33
34
  try {
34
35
  await checkDatasetWrite(datasetId, user, userInfo)
@@ -37,7 +38,7 @@ export const fsckDataset = async (_, { datasetId }, { user, userInfo }) => {
37
38
  }
38
39
  try {
39
40
  // Lock for 30 minutes to avoid stacking fsck requests
40
- await redlock.lock(`openneuro:fsck-local-lock:${datasetId}`, 1800000)
41
+ await getRedlock().lock(`openneuro:fsck-local-lock:${datasetId}`, 1800000)
41
42
  const response = await fetch(fsckUrl(datasetId), {
42
43
  method: "POST",
43
44
  body: JSON.stringify({}),
@@ -1,10 +1,11 @@
1
1
  import BadAnnexObject from "../../models/badAnnexObject"
2
2
  import { checkAdmin } from "../permissions"
3
+ import type { GraphQLContext } from "../builder"
3
4
 
4
5
  export const flaggedFiles = async (
5
6
  obj,
6
7
  { flagged = true, deleted = false },
7
- { user, userInfo },
8
+ { user, userInfo }: GraphQLContext,
8
9
  ) => {
9
10
  await checkAdmin(user, userInfo)
10
11
  return BadAnnexObject.find({ flagged, removed: deleted })
@@ -1,6 +1,7 @@
1
1
  import Subscription from "../../models/subscription"
2
+ import type { GraphQLContext } from "../builder"
2
3
 
3
- export const followDataset = async (obj, { datasetId }, { user }) => {
4
+ export const followDataset = async (obj, { datasetId }, { user }: GraphQLContext) => {
4
5
  const following = await Subscription.findOne({
5
6
  datasetId,
6
7
  userId: user,
@@ -1,6 +1,7 @@
1
1
  import { checkDatasetRead, checkDatasetWrite } from "../permissions.js"
2
2
  import { generateRepoToken } from "../../libs/authentication/jwt"
3
3
  import { getDatasetEndpoint } from "../../libs/datalad-service.js"
4
+ import type { GraphQLContext } from "../builder"
4
5
 
5
6
  /**
6
7
  * Generate a short lived token for git operations
@@ -14,7 +15,7 @@ import { getDatasetEndpoint } from "../../libs/datalad-service.js"
14
15
  export async function prepareRepoAccess(
15
16
  _: Record<string, unknown>,
16
17
  { datasetId }: { datasetId: string },
17
- { user, userInfo }: { user: string; userInfo: Record<string, unknown> },
18
+ { user, userInfo }: GraphQLContext,
18
19
  ): Promise<{ token: string; endpoint: number }> {
19
20
  try {
20
21
  await checkDatasetWrite(datasetId, user, userInfo)
@@ -1,5 +1,6 @@
1
1
  import { createEvent } from "../../libs/events"
2
2
  import { checkDatasetWrite } from "../permissions"
3
+ import type { GraphQLContext } from "../builder"
3
4
 
4
5
  /**
5
6
  * Create a git event
@@ -7,7 +8,7 @@ import { checkDatasetWrite } from "../permissions"
7
8
  export const createGitEvent = async (
8
9
  obj,
9
10
  { datasetId, commit, reference },
10
- { user, userInfo },
11
+ { user, userInfo }: GraphQLContext,
11
12
  ) => {
12
13
  await checkDatasetWrite(datasetId, user, userInfo)
13
14
  const event = await createEvent(
@@ -1,6 +1,8 @@
1
+ import type { FlattenMaps } from "mongoose"
1
2
  import { getDatasetWorker } from "../../libs/datalad-service"
3
+ import type { DatasetDocument } from "../../models/dataset"
2
4
 
3
- export const history = async (obj) => {
5
+ export const history = async (obj: FlattenMaps<DatasetDocument>) => {
4
6
  const datasetId = obj.id
5
7
  const historyUrl = `http://${
6
8
  getDatasetWorker(
@@ -5,7 +5,7 @@ import Dataset from "../../models/dataset"
5
5
  * Requires site admin access.
6
6
  */
7
7
  export async function holdDeletion(
8
- _obj: Record<string, unknown>,
8
+ _obj: unknown,
9
9
  { datasetId, hold }: { datasetId: string; hold: boolean },
10
10
  { userInfo }: { userInfo: { admin: boolean } },
11
11
  ): Promise<boolean> {
@@ -4,6 +4,7 @@ import { getDatasetWorker } from "../../libs/datalad-service"
4
4
  import { generateDataladCookie } from "../../libs/authentication/jwt"
5
5
  import notifications from "../../libs/notifications"
6
6
  import config from "../../config"
7
+ import type { GraphQLContext } from "../builder"
7
8
 
8
9
  /**
9
10
  * Test if a URL is allowed to be imported
@@ -34,7 +35,7 @@ export function allowedImportUrl(raw: string): boolean {
34
35
  export async function importRemoteDataset(
35
36
  _: Record<string, unknown>,
36
37
  { datasetId, url }: { datasetId: string; url: string },
37
- { user, userInfo }: { user: string; userInfo: Record<string, unknown> },
38
+ { user, userInfo }: GraphQLContext,
38
39
  ): Promise<string | null> {
39
40
  await checkDatasetWrite(datasetId, user, userInfo)
40
41
  if (!allowedImportUrl(url)) {
@@ -66,7 +67,7 @@ export async function finishImportRemoteDataset(
66
67
  _: Record<string, unknown>,
67
68
  { id, success, message }: { id: string; success: boolean; message: string },
68
69
  /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
69
- { user, userInfo }: { user: string; userInfo: Record<string, unknown> },
70
+ { user, userInfo }: GraphQLContext,
70
71
  ): Promise<boolean> {
71
72
  const ingest = await IngestDataset.findById(id)
72
73
  ingest.imported = success
@@ -1,11 +1,12 @@
1
1
  import Issue from "../../models/issue"
2
2
  import { datasetType } from "./datasetType"
3
3
  import { revalidate } from "./validation.js"
4
+ import type { GraphQLContext } from "../builder"
4
5
 
5
6
  /**
6
7
  * Issues resolver
7
8
  */
8
- export const issues = async (dataset, _, { userInfo }) => {
9
+ export const issues = async (dataset, _, { userInfo }: GraphQLContext) => {
9
10
  return Issue.findOne({
10
11
  id: dataset.revision,
11
12
  datasetId: dataset.id,
@@ -5,6 +5,7 @@ import MetadataModel from "../../models/metadata"
5
5
  import type { MetadataDocument } from "../../models/metadata"
6
6
  import { latestSnapshot } from "./snapshots"
7
7
  import { permissions } from "./permissions"
8
+ import type { GraphQLContext } from "../builder"
8
9
 
9
10
  /**
10
11
  * Summary resolver
@@ -14,7 +15,7 @@ import { permissions } from "./permissions"
14
15
  export const metadata = async (
15
16
  dataset,
16
17
  _,
17
- context,
18
+ context: GraphQLContext,
18
19
  ): Promise<FlattenMaps<MetadataDocument>> => {
19
20
  const record = await MetadataModel.findOne({
20
21
  datasetId: dataset.id,
@@ -79,7 +80,7 @@ export async function publicMetadata(
79
80
  }).lean()
80
81
  const dsMetadata: FlattenMaps<MetadataDocument>[] = []
81
82
  for (const ds of datasets) {
82
- dsMetadata.push(await metadata(ds, null, {}))
83
+ dsMetadata.push(await metadata(ds, null, {} as GraphQLContext))
83
84
  }
84
85
  return dsMetadata
85
86
  }
@@ -3,22 +3,31 @@ import type { UserDocument } from "../../models/user"
3
3
  import Permission from "../../models/permission"
4
4
  import type { PermissionDocument } from "../../models/permission"
5
5
  import { checkDatasetAdmin } from "../permissions"
6
+ import type { GraphQLContext } from "../builder"
6
7
  import { user } from "./user"
7
8
  import { createEvent, updateEvent } from "../../libs/events"
8
9
 
9
- interface DatasetPermission {
10
+ export interface DatasetPermission {
10
11
  id: string
11
12
  userPermissions: (PermissionDocument & { user: Promise<UserDocument> })[]
12
13
  }
13
14
 
14
- export async function permissions(ds, _, context): Promise<DatasetPermission> {
15
+ export async function permissions(
16
+ ds: { id: string },
17
+ _: unknown,
18
+ context: Partial<GraphQLContext> | null,
19
+ ): Promise<DatasetPermission> {
15
20
  const permissions = await Permission.find({ datasetId: ds.id }).exec()
16
21
  return {
17
22
  id: ds.id,
18
23
  userPermissions: permissions.map(
19
24
  (userPermission) => ({
20
25
  ...userPermission.toJSON(),
21
- user: user(ds, { id: userPermission.userId }, context),
26
+ user: user(
27
+ ds,
28
+ { id: userPermission.userId },
29
+ context as Partial<GraphQLContext>,
30
+ ),
22
31
  } as unknown as PermissionDocument & { user: Promise<UserDocument> }),
23
32
  ),
24
33
  }
@@ -71,7 +80,11 @@ async function updateUsers(datasetId: string, level: string, users) {
71
80
  /**
72
81
  * Update user permissions on a dataset
73
82
  */
74
- export const updatePermissions = async (obj, args, { user, userInfo }) => {
83
+ export const updatePermissions = async (
84
+ obj: unknown,
85
+ args: { datasetId: string; userEmail: string; level: string },
86
+ { user, userInfo }: GraphQLContext,
87
+ ) => {
75
88
  await checkDatasetAdmin(args.datasetId, user, userInfo)
76
89
  // get all users the the email specified by permissions arg
77
90
  const users = await User.find({ email: args.userEmail })
@@ -88,7 +101,11 @@ export const updatePermissions = async (obj, args, { user, userInfo }) => {
88
101
  /**
89
102
  * ORCID variant of updatePermissions
90
103
  */
91
- export const updateOrcidPermissions = async (obj, args, { user, userInfo }) => {
104
+ export const updateOrcidPermissions = async (
105
+ obj: unknown,
106
+ args: { datasetId: string; userOrcid: string; level: string },
107
+ { user, userInfo }: GraphQLContext,
108
+ ) => {
92
109
  await checkDatasetAdmin(args.datasetId, user, userInfo)
93
110
  // Get all users associated with the ORCID provided
94
111
  const users = await User.find({
@@ -110,7 +127,10 @@ export const updateOrcidPermissions = async (obj, args, { user, userInfo }) => {
110
127
  /**
111
128
  * Remove user permissions on a dataset
112
129
  */
113
- export const removePermissions = (obj, args) => {
130
+ export const removePermissions = (
131
+ obj: unknown,
132
+ args: { datasetId: string; userId: string },
133
+ ) => {
114
134
  return Permission.deleteOne({
115
135
  datasetId: args.datasetId,
116
136
  userId: args.userId,
@@ -4,8 +4,9 @@ import { updatePublic } from "../../datalad/dataset"
4
4
  import { checkDatasetWrite } from "../permissions.js"
5
5
  import { generateDataladCookie } from "../../libs/authentication/jwt"
6
6
  import { getDatasetWorker } from "../../libs/datalad-service"
7
+ import type { GraphQLContext } from "../builder"
7
8
 
8
- export const publishDataset = (obj, { datasetId }, { user, userInfo }) => {
9
+ export const publishDataset = (obj, { datasetId }, { user, userInfo }: GraphQLContext) => {
9
10
  return checkDatasetWrite(datasetId, user, userInfo).then(async () => {
10
11
  await updatePublic(datasetId, true, userInfo)
11
12
  const uri = `${getDatasetWorker(datasetId)}/datasets/${datasetId}/publish`
@@ -4,13 +4,14 @@
4
4
  */
5
5
  import { setReadme } from "../../datalad/readme"
6
6
  import { checkDatasetWrite } from "../permissions"
7
+ import type { GraphQLContext } from "../builder"
7
8
  export { readme } from "../../datalad/readme"
8
9
  import { draftFiles } from "./draft"
9
10
 
10
11
  export async function updateReadme(
11
12
  obj,
12
13
  { datasetId, value },
13
- { user, userInfo },
14
+ { user, userInfo }: GraphQLContext,
14
15
  ) {
15
16
  await checkDatasetWrite(datasetId, user, userInfo)
16
17
  const files = await draftFiles({ id: datasetId }, { tree: "HEAD" }, {
@@ -3,11 +3,12 @@
3
3
  */
4
4
  import { checkAdmin } from "../permissions"
5
5
  import { runReexporter } from "../../datalad/reexporter"
6
+ import type { GraphQLContext } from "../builder"
6
7
 
7
8
  export const reexportRemotes = async (
8
9
  obj,
9
10
  { datasetId },
10
- { user, userInfo },
11
+ { user, userInfo }: GraphQLContext,
11
12
  ) => {
12
13
  await checkAdmin(user, userInfo)
13
14
  await runReexporter(datasetId)
@@ -1,11 +1,12 @@
1
1
  import Dataset from "../../models/dataset"
2
2
  import { checkDatasetAdmin } from "../permissions"
3
3
  import { DOIPattern } from "../../libs/doi/normalize"
4
+ import type { GraphQLContext } from "../builder"
4
5
 
5
6
  export const createRelation = async (
6
7
  obj,
7
8
  { datasetId, doi, relation, kind, description },
8
- { user, userInfo },
9
+ { user, userInfo }: GraphQLContext,
9
10
  ) => {
10
11
  await checkDatasetAdmin(datasetId, user, userInfo)
11
12
  // Validate the right DOI format
@@ -27,7 +28,7 @@ export const createRelation = async (
27
28
  export const deleteRelation = async (
28
29
  obj,
29
30
  { datasetId, doi },
30
- { user, userInfo },
31
+ { user, userInfo }: GraphQLContext,
31
32
  ) => {
32
33
  await checkDatasetAdmin(datasetId, user, userInfo)
33
34
  return await Dataset.findOneAndUpdate(
@@ -1,5 +1,6 @@
1
1
  import { checkDatasetWrite } from "../permissions"
2
2
  import { resetDraft as resetDraftTask } from "../../datalad/draft"
3
+ import type { GraphQLContext } from "../builder"
3
4
 
4
5
  /**
5
6
  * Mutation to move the draft HEAD reference forward or backward
@@ -7,7 +8,7 @@ import { resetDraft as resetDraftTask } from "../../datalad/draft"
7
8
  export const resetDraft = async (
8
9
  obj,
9
10
  { datasetId, ref },
10
- { user, userInfo },
11
+ { user, userInfo }: GraphQLContext,
11
12
  ) => {
12
13
  await checkDatasetWrite(datasetId, user, userInfo)
13
14
  try {
@@ -2,6 +2,7 @@ import config from "../../config"
2
2
  import Reviewer from "../../models/reviewer"
3
3
  import { checkDatasetAdmin } from "../permissions.js"
4
4
  import { generateReviewerToken } from "../../libs/authentication/jwt"
5
+ import type { GraphQLContext } from "../builder"
5
6
 
6
7
  /**
7
8
  * Create an anonymous read-only access key
@@ -12,7 +13,11 @@ import { generateReviewerToken } from "../../libs/authentication/jwt"
12
13
  * @param {string} context.user User id
13
14
  * @param {object} context.userInfo Decoded userInfo from token
14
15
  */
15
- export async function createReviewer(obj, { datasetId }, { user, userInfo }) {
16
+ export async function createReviewer(
17
+ obj: unknown,
18
+ { datasetId }: { datasetId: string },
19
+ { user, userInfo }: GraphQLContext,
20
+ ) {
16
21
  await checkDatasetAdmin(datasetId, user, userInfo)
17
22
  const reviewer = new Reviewer({ datasetId, creator: user })
18
23
  await reviewer.save()
@@ -26,9 +31,9 @@ export async function createReviewer(obj, { datasetId }, { user, userInfo }) {
26
31
  }
27
32
 
28
33
  export async function deleteReviewer(
29
- obj,
30
- { datasetId, id },
31
- { user, userInfo },
34
+ obj: unknown,
35
+ { datasetId, id }: { datasetId: string; id: string },
36
+ { user, userInfo }: GraphQLContext,
32
37
  ) {
33
38
  await checkDatasetAdmin(datasetId, user, userInfo)
34
39
  return Reviewer.findOneAndDelete({ id }).lean().exec()
@@ -37,7 +42,10 @@ export async function deleteReviewer(
37
42
  /**
38
43
  * Resolver for dataset reviewers
39
44
  */
40
- /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
41
- export function reviewers(obj, _, { user, userInfo }) {
45
+ export function reviewers(
46
+ obj: { id: string },
47
+ _: unknown,
48
+ _ctx: GraphQLContext,
49
+ ) {
42
50
  return Reviewer.find({ datasetId: obj.id }).lean().exec()
43
51
  }
@@ -1,5 +1,6 @@
1
1
  import * as datalad from "../../datalad/snapshots"
2
- import { analytics, dataset, snapshotCreationComparison } from "./dataset.js"
2
+ import { analytics, dataset } from "./dataset.js"
3
+ import { snapshotCreationComparison } from "../../utils/snapshots"
3
4
  import { onBrainlife } from "./brainlife"
4
5
  import { checkDatasetRead, checkDatasetWrite } from "../permissions"
5
6
  import { readme } from "./readme.js"
@@ -11,23 +12,49 @@ import Summary from "../../models/summary"
11
12
  import DatasetModel from "../../models/dataset"
12
13
  import { filterRemovedAnnexObjects } from "../utils/file"
13
14
  import DeprecatedSnapshot from "../../models/deprecatedSnapshot"
14
- import { redis } from "../../libs/redis"
15
+ import { getRedis } from "../../libs/redis"
15
16
  import CacheItem, { CacheType } from "../../cache/item"
16
17
  import { normalizeDOI } from "../../libs/doi/normalize"
17
18
  import { snapshotValidation } from "./validation"
18
19
  import { advancedDatasetSearchConnection } from "./dataset-search"
19
20
  import { contributors } from "../../datalad/contributors"
20
21
  import { getDraftInfo } from "../../datalad/draft"
22
+ import type { GraphQLContext } from "../builder"
23
+
24
+ /**
25
+ * Shape returned by the snapshot() resolver, combining plain data
26
+ * from the HTTP API with lazy-loaded thunk fields.
27
+ */
28
+ export interface SnapshotShape {
29
+ id: string
30
+ datasetId: string
31
+ tag: string
32
+ created?: Date
33
+ hexsha?: string
34
+ dataset: () => Promise<unknown>
35
+ description: () => Promise<unknown>
36
+ readme: () => Promise<unknown>
37
+ summary: () => Promise<unknown>
38
+ files: (args: {
39
+ tree?: string | null
40
+ recursive?: boolean | null
41
+ }) => Promise<unknown>
42
+ size: () => Promise<unknown>
43
+ deprecated: () => Promise<unknown>
44
+ related: () => Promise<unknown>
45
+ onBrainlife: () => Promise<boolean>
46
+ }
21
47
 
22
48
  export const snapshots = (obj) => {
23
49
  return datalad.getSnapshots(obj.id)
24
50
  }
25
51
 
26
- export const snapshot = (obj, { datasetId, tag }, context) => {
52
+ export const snapshot = (obj, { datasetId, tag }, context: GraphQLContext) => {
27
53
  return checkDatasetRead(datasetId, context.user, context.userInfo).then(
28
54
  () => {
29
55
  return datalad.getSnapshot(datasetId, tag).then((snapshot) => ({
30
56
  ...snapshot,
57
+ created: snapshot.created ? new Date(snapshot.created) : undefined,
31
58
  datasetId,
32
59
  dataset: () => dataset(snapshot, { id: datasetId }, context),
33
60
  description: () => description(snapshot),
@@ -100,7 +127,7 @@ export const matchKnownObjects = (desc) => {
100
127
  export const deprecateSnapshot = async (
101
128
  obj,
102
129
  { datasetId, tag, reason },
103
- { user, userInfo },
130
+ { user, userInfo }: GraphQLContext,
104
131
  ) => {
105
132
  const id = `${datasetId}:${tag}`
106
133
  await checkDatasetWrite(datasetId, user, userInfo)
@@ -129,7 +156,7 @@ export const deprecateSnapshot = async (
129
156
  export const undoDeprecateSnapshot = async (
130
157
  obj,
131
158
  { datasetId, tag },
132
- { user, userInfo },
159
+ { user, userInfo }: GraphQLContext,
133
160
  ) => {
134
161
  const id = `${datasetId}:${tag}`
135
162
  await checkDatasetWrite(datasetId, user, userInfo)
@@ -148,7 +175,7 @@ const brainInitiativeQuery = {
148
175
  export const participantCount = (obj, { modality }) => {
149
176
  const cacheKey = modality || "all"
150
177
  const cache = new CacheItem(
151
- redis,
178
+ getRedis(),
152
179
  CacheType.participantCount,
153
180
  [cacheKey],
154
181
  86400,
@@ -172,11 +199,12 @@ export const participantCount = (obj, { modality }) => {
172
199
  datasetStatus: "",
173
200
  after,
174
201
  first: 100,
175
- }, { user: null, userInfo: {} })
202
+ }, { user: null, userInfo: {} } as unknown as GraphQLContext)
176
203
  nihDatasets.push(...results.edges.map((edge) => edge.id))
177
204
  if (!results.pageInfo.hasNextPage) {
178
205
  break
179
206
  } else {
207
+ // eslint-disable-next-line no-useless-assignment
180
208
  after = results.pageInfo.endCursor
181
209
  }
182
210
  }
@@ -261,7 +289,7 @@ export const filterLatestSnapshot = (snapshots) => {
261
289
  }
262
290
  }
263
291
 
264
- export const latestSnapshot = async (obj, _, context) => {
292
+ export const latestSnapshot = async (obj, _, context: GraphQLContext) => {
265
293
  const snapshots = await datalad.getSnapshots(obj.id)
266
294
  const snapshotTag = filterLatestSnapshot(snapshots)
267
295
  if (snapshotTag) {
@@ -282,7 +310,7 @@ export const latestSnapshot = async (obj, _, context) => {
282
310
  export const createSnapshot = (
283
311
  obj,
284
312
  { datasetId, tag, changes },
285
- { user, userInfo },
313
+ { user, userInfo }: GraphQLContext,
286
314
  ) => {
287
315
  return checkDatasetWrite(datasetId, user, userInfo).then(async () => {
288
316
  return datalad.createSnapshot(datasetId, tag, userInfo, {}, changes)
@@ -292,7 +320,11 @@ export const createSnapshot = (
292
320
  /**
293
321
  * Remove a tag from a dataset
294
322
  */
295
- export const deleteSnapshot = (obj, { datasetId, tag }, { user, userInfo }) => {
323
+ export const deleteSnapshot = (
324
+ obj,
325
+ { datasetId, tag },
326
+ { user, userInfo }: GraphQLContext,
327
+ ) => {
296
328
  return checkDatasetWrite(datasetId, user, userInfo).then(() => {
297
329
  return datalad.deleteSnapshot(datasetId, tag)
298
330
  })
@@ -1,6 +1,7 @@
1
1
  import Star from "../../models/stars"
2
+ import type { GraphQLContext } from "../builder"
2
3
 
3
- export const starDataset = async (obj, { datasetId }, { user }) => {
4
+ export const starDataset = async (obj, { datasetId }, { user }: GraphQLContext) => {
4
5
  const star = await Star.findOne({ datasetId, userId: user }).exec()
5
6
  const newStar = {
6
7
  datasetId,
@@ -3,6 +3,7 @@ import { checkDatasetWrite } from "../permissions.js"
3
3
  import { generateUploadToken } from "../../libs/authentication/jwt"
4
4
  import { finishUploadRequest } from "../../datalad/upload.js"
5
5
  import { getDatasetEndpoint } from "../../libs/datalad-service.js"
6
+ import type { GraphQLContext } from "../builder"
6
7
 
7
8
  /**
8
9
  * Track initial state for a new upload
@@ -19,7 +20,7 @@ import { getDatasetEndpoint } from "../../libs/datalad-service.js"
19
20
  export async function prepareUpload(
20
21
  obj,
21
22
  { datasetId, uploadId },
22
- { user, userInfo },
23
+ { user, userInfo }: GraphQLContext,
23
24
  ) {
24
25
  await checkDatasetWrite(datasetId, user, userInfo)
25
26
  const upload = await Upload.findOneAndUpdate(
@@ -47,7 +48,7 @@ export async function prepareUpload(
47
48
  * @param {string} context.user User id
48
49
  * @param {object} context.userInfo Decoded userInfo from token
49
50
  */
50
- export async function finishUpload(obj, { uploadId }, { user, userInfo }) {
51
+ export async function finishUpload(obj, { uploadId }, { user, userInfo }: GraphQLContext) {
51
52
  const upload = await Upload.findOne({ id: uploadId })
52
53
  const datasetId = upload.datasetId
53
54
  await checkDatasetWrite(datasetId, user, userInfo)