@openneuro/server 4.20.5 → 4.20.6-alpha.3

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 (185) hide show
  1. package/package.json +4 -6
  2. package/src/__mocks__/{config.js → config.ts} +5 -5
  3. package/src/app.ts +32 -31
  4. package/src/cache/item.ts +6 -7
  5. package/src/cache/types.ts +8 -8
  6. package/src/{config.js → config.ts} +6 -6
  7. package/src/datalad/__tests__/changelog.spec.ts +83 -0
  8. package/src/datalad/__tests__/dataset.spec.ts +109 -0
  9. package/src/datalad/__tests__/description.spec.ts +141 -0
  10. package/src/datalad/__tests__/files.spec.ts +77 -0
  11. package/src/datalad/__tests__/pagination.spec.ts +136 -0
  12. package/src/datalad/__tests__/{snapshots.spec.js → snapshots.spec.ts} +17 -17
  13. package/src/datalad/{analytics.js → analytics.ts} +4 -4
  14. package/src/datalad/{changelog.js → changelog.ts} +17 -14
  15. package/src/datalad/{dataset.js → dataset.ts} +95 -93
  16. package/src/datalad/{description.js → description.ts} +37 -37
  17. package/src/datalad/draft.ts +38 -0
  18. package/src/datalad/files.ts +26 -20
  19. package/src/datalad/{pagination.js → pagination.ts} +47 -47
  20. package/src/datalad/{readme.js → readme.ts} +13 -11
  21. package/src/datalad/{reexporter.js → reexporter.ts} +4 -4
  22. package/src/datalad/{snapshots.js → snapshots.ts} +56 -62
  23. package/src/datalad/{upload.js → upload.ts} +7 -5
  24. package/src/elasticsearch/elastic-client.ts +11 -0
  25. package/src/elasticsearch/reindex-dataset.ts +7 -7
  26. package/src/graphql/__tests__/__snapshots__/permissions.spec.ts.snap +5 -0
  27. package/src/graphql/__tests__/{comment.spec.js → comment.spec.ts} +17 -17
  28. package/src/graphql/__tests__/permissions.spec.ts +113 -0
  29. package/src/graphql/{permissions.js → permissions.ts} +14 -14
  30. package/src/graphql/resolvers/__tests__/brainlife.spec.ts +11 -11
  31. package/src/graphql/resolvers/__tests__/{dataset-search.spec.js → dataset-search.spec.ts} +25 -23
  32. package/src/graphql/resolvers/__tests__/dataset.spec.ts +175 -0
  33. package/src/graphql/resolvers/__tests__/derivatives.spec.ts +19 -19
  34. package/src/graphql/resolvers/__tests__/importRemoteDataset.spec.ts +20 -20
  35. package/src/graphql/resolvers/__tests__/permssions.spec.ts +35 -0
  36. package/src/graphql/resolvers/__tests__/snapshots.spec.ts +59 -0
  37. package/src/graphql/resolvers/__tests__/user.spec.ts +18 -0
  38. package/src/graphql/resolvers/brainlife.ts +4 -4
  39. package/src/graphql/resolvers/cache.ts +4 -4
  40. package/src/graphql/resolvers/{comment.js → comment.ts} +16 -16
  41. package/src/graphql/resolvers/{dataset-search.js → dataset-search.ts} +45 -43
  42. package/src/graphql/resolvers/{dataset.js → dataset.ts} +38 -52
  43. package/src/graphql/resolvers/datasetType.ts +3 -3
  44. package/src/graphql/resolvers/derivatives.ts +11 -11
  45. package/src/graphql/resolvers/description.ts +18 -0
  46. package/src/graphql/resolvers/{draft.js → draft.ts} +13 -13
  47. package/src/graphql/resolvers/{flaggedFiles.js → flaggedFiles.ts} +4 -4
  48. package/src/graphql/resolvers/{follow.js → follow.ts} +1 -1
  49. package/src/graphql/resolvers/git.ts +3 -3
  50. package/src/graphql/resolvers/history.ts +13 -0
  51. package/src/graphql/resolvers/importRemoteDataset.ts +12 -11
  52. package/src/graphql/resolvers/index.ts +25 -0
  53. package/src/graphql/resolvers/{issues.js → issues.ts} +9 -9
  54. package/src/graphql/resolvers/metadata.ts +8 -8
  55. package/src/graphql/resolvers/{mutation.js → mutation.ts} +26 -26
  56. package/src/graphql/resolvers/{newsletter.js → newsletter.ts} +2 -2
  57. package/src/graphql/resolvers/permissions.ts +15 -21
  58. package/src/graphql/resolvers/publish.ts +17 -0
  59. package/src/graphql/resolvers/query.ts +21 -0
  60. package/src/graphql/resolvers/{readme.js → readme.ts} +3 -3
  61. package/src/graphql/resolvers/{reexporter.js → reexporter.ts} +2 -2
  62. package/src/graphql/resolvers/relation.ts +5 -5
  63. package/src/graphql/resolvers/{reset.js → reset.ts} +2 -2
  64. package/src/graphql/resolvers/reviewer.ts +4 -4
  65. package/src/graphql/resolvers/{snapshots.js → snapshots.ts} +49 -49
  66. package/src/graphql/resolvers/{stars.js → stars.ts} +1 -1
  67. package/src/graphql/resolvers/summary.ts +3 -3
  68. package/src/graphql/resolvers/{upload.js → upload.ts} +5 -5
  69. package/src/graphql/resolvers/{user.js → user.ts} +16 -18
  70. package/src/graphql/resolvers/{validation.js → validation.ts} +12 -14
  71. package/src/graphql/{schema.js → schema.ts} +4 -6
  72. package/src/graphql/utils/{file.js → file.ts} +2 -2
  73. package/src/handlers/{comments.js → comments.ts} +11 -11
  74. package/src/handlers/{config.js → config.ts} +1 -1
  75. package/src/handlers/{datalad.js → datalad.ts} +22 -22
  76. package/src/handlers/{doi.js → doi.ts} +6 -6
  77. package/src/handlers/reviewer.ts +6 -6
  78. package/src/handlers/{sitemap.js → sitemap.ts} +19 -19
  79. package/src/handlers/stars.ts +11 -10
  80. package/src/handlers/{subscriptions.js → subscriptions.ts} +17 -16
  81. package/src/handlers/{users.js → users.ts} +3 -3
  82. package/src/libs/__tests__/apikey.spec.ts +25 -0
  83. package/src/libs/__tests__/datalad-service.spec.ts +27 -0
  84. package/src/libs/__tests__/{dataset.spec.js → dataset.spec.ts} +9 -9
  85. package/src/libs/{apikey.js → apikey.ts} +5 -5
  86. package/src/libs/authentication/__tests__/jwt.spec.ts +59 -0
  87. package/src/libs/authentication/{crypto.js → crypto.ts} +16 -16
  88. package/src/libs/authentication/google.ts +18 -0
  89. package/src/libs/authentication/jwt.ts +40 -33
  90. package/src/libs/authentication/{orcid.js → orcid.ts} +11 -11
  91. package/src/libs/authentication/{passport.js → passport.ts} +45 -30
  92. package/src/libs/authentication/{states.js → states.ts} +17 -20
  93. package/src/libs/{counter.js → counter.ts} +1 -1
  94. package/src/libs/{datalad-service.js → datalad-service.ts} +4 -4
  95. package/src/libs/dataset.ts +9 -0
  96. package/src/libs/doi/__tests__/__snapshots__/doi.spec.ts.snap +17 -0
  97. package/src/libs/doi/__tests__/doi.spec.ts +25 -0
  98. package/src/libs/doi/__tests__/normalize.spec.ts +19 -19
  99. package/src/libs/doi/{index.js → index.ts} +27 -21
  100. package/src/libs/doi/normalize.ts +2 -2
  101. package/src/libs/email/__tests__/index.spec.ts +14 -14
  102. package/src/libs/email/index.ts +4 -4
  103. package/src/libs/email/templates/__tests__/comment-created.spec.ts +12 -12
  104. package/src/libs/email/templates/__tests__/dataset-deleted.spec.ts +6 -6
  105. package/src/libs/email/templates/__tests__/owner-unsubscribed.spec.ts +6 -6
  106. package/src/libs/email/templates/__tests__/snapshot-created.spec.ts +9 -9
  107. package/src/libs/email/templates/__tests__/snapshot-reminder.spec.ts +7 -7
  108. package/src/libs/email/templates/comment-created.ts +2 -1
  109. package/src/libs/email/templates/dataset-deleted.ts +2 -1
  110. package/src/libs/email/templates/dataset-import-failed.ts +2 -1
  111. package/src/libs/email/templates/dataset-imported.ts +2 -1
  112. package/src/libs/email/templates/owner-unsubscribed.ts +2 -1
  113. package/src/libs/email/templates/snapshot-created.ts +2 -1
  114. package/src/libs/email/templates/snapshot-reminder.ts +2 -1
  115. package/src/libs/{notifications.js → notifications.ts} +100 -113
  116. package/src/libs/{orcid.js → orcid.ts} +20 -20
  117. package/src/libs/{redis.js → redis.ts} +6 -6
  118. package/src/models/__tests__/ingestDataset.spec.ts +15 -15
  119. package/src/models/analytics.ts +2 -2
  120. package/src/models/badAnnexObject.ts +6 -6
  121. package/src/models/comment.ts +10 -10
  122. package/src/models/counter.ts +2 -2
  123. package/src/models/dataset.ts +16 -16
  124. package/src/models/deletion.ts +3 -3
  125. package/src/models/deprecatedSnapshot.ts +2 -2
  126. package/src/models/doi.ts +2 -2
  127. package/src/models/file.ts +2 -2
  128. package/src/models/ingestDataset.ts +4 -4
  129. package/src/models/issue.ts +2 -2
  130. package/src/models/key.ts +2 -2
  131. package/src/models/mailgunIdentifier.ts +2 -2
  132. package/src/models/metadata.ts +3 -3
  133. package/src/models/newsletter.ts +3 -3
  134. package/src/models/notification.ts +2 -2
  135. package/src/models/permission.ts +4 -4
  136. package/src/models/reviewer.ts +7 -7
  137. package/src/models/snapshot.ts +2 -2
  138. package/src/models/stars.ts +6 -6
  139. package/src/models/subscription.ts +2 -2
  140. package/src/models/summary.ts +2 -2
  141. package/src/models/upload.ts +3 -3
  142. package/src/models/user.ts +4 -4
  143. package/src/{routes.js → routes.ts} +62 -62
  144. package/src/server.ts +9 -9
  145. package/src/utils/__tests__/datasetOrSnapshot.spec.ts +25 -25
  146. package/src/utils/__tests__/validateUrl.spec.ts +10 -10
  147. package/src/utils/datasetOrSnapshot.ts +2 -2
  148. package/src/utils/validateUrl.ts +1 -1
  149. package/src/datalad/__tests__/changelog.spec.js +0 -82
  150. package/src/datalad/__tests__/dataset.spec.js +0 -109
  151. package/src/datalad/__tests__/description.spec.js +0 -137
  152. package/src/datalad/__tests__/files.spec.js +0 -75
  153. package/src/datalad/__tests__/pagination.spec.js +0 -136
  154. package/src/datalad/draft.js +0 -37
  155. package/src/elasticsearch/elastic-client.js +0 -11
  156. package/src/graphql/__tests__/permissions.spec.js +0 -107
  157. package/src/graphql/pubsub.js +0 -5
  158. package/src/graphql/resolvers/__tests__/dataset.spec.js +0 -175
  159. package/src/graphql/resolvers/__tests__/permssions.spec.js +0 -34
  160. package/src/graphql/resolvers/__tests__/snapshots.spec.js +0 -58
  161. package/src/graphql/resolvers/__tests__/user.spec.js +0 -17
  162. package/src/graphql/resolvers/description.js +0 -29
  163. package/src/graphql/resolvers/history.js +0 -11
  164. package/src/graphql/resolvers/index.js +0 -25
  165. package/src/graphql/resolvers/publish.js +0 -17
  166. package/src/graphql/resolvers/query.js +0 -21
  167. package/src/graphql/resolvers/subscriptions.js +0 -81
  168. package/src/graphql/utils/publish-draft-update.js +0 -13
  169. package/src/libs/__tests__/apikey.spec.js +0 -24
  170. package/src/libs/__tests__/datalad-service.spec.js +0 -26
  171. package/src/libs/authentication/__tests__/jwt.spec.js +0 -23
  172. package/src/libs/authentication/globus.js +0 -11
  173. package/src/libs/authentication/google.js +0 -19
  174. package/src/libs/bidsId.js +0 -68
  175. package/src/libs/dataset.js +0 -9
  176. package/src/libs/doi/__tests__/doi.spec.js +0 -24
  177. package/src/libs/redis-pubsub.js +0 -5
  178. package/src/libs/request.js +0 -155
  179. package/src/libs/scitran.js +0 -25
  180. package/src/libs/subscription-server.js +0 -20
  181. package/src/libs/testing-utils.js +0 -17
  182. package/src/persistent/datasets/.gitignore +0 -3
  183. package/src/persistent/temp/.gitignore +0 -3
  184. /package/src/libs/__mocks__/{notifications.js → notifications.ts} +0 -0
  185. /package/src/libs/authentication/{verifyUser.js → verifyUser.ts} +0 -0
@@ -3,30 +3,31 @@
3
3
  *
4
4
  * See resolvers for interaction with other data sources.
5
5
  */
6
- import request from 'superagent'
7
- import requestNode from 'request'
8
- import objectHash from 'object-hash'
9
- import { Readable } from 'stream'
10
- import config from '../config'
11
- import * as subscriptions from '../handlers/subscriptions.js'
12
- import { generateDataladCookie } from '../libs/authentication/jwt'
13
- import { redis } from '../libs/redis'
14
- import CacheItem, { CacheType } from '../cache/item'
15
- import { updateDatasetRevision } from './draft.js'
16
- import { fileUrl, getFileName, encodeFilePath, filesUrl } from './files'
17
- import { getAccessionNumber } from '../libs/dataset.js'
18
- import Dataset from '../models/dataset'
19
- import Metadata from '../models/metadata'
20
- import Permission from '../models/permission'
21
- import Star from '../models/stars'
22
- import Subscription from '../models/subscription'
23
- import BadAnnexObject from '../models/badAnnexObject'
24
- import { datasetsConnection } from './pagination'
25
- import { getDatasetWorker } from '../libs/datalad-service'
26
- import notifications from '../libs/notifications'
6
+ import request from "superagent"
7
+ import requestNode from "request"
8
+ import objectHash from "object-hash"
9
+ import { Readable } from "stream"
10
+ import * as Mongoose from "mongoose"
11
+ import config from "../config"
12
+ import * as subscriptions from "../handlers/subscriptions"
13
+ import { generateDataladCookie } from "../libs/authentication/jwt"
14
+ import { redis } from "../libs/redis"
15
+ import CacheItem, { CacheType } from "../cache/item"
16
+ import { updateDatasetRevision } from "./draft"
17
+ import { encodeFilePath, filesUrl, fileUrl, getFileName } from "./files"
18
+ import { getAccessionNumber } from "../libs/dataset"
19
+ import Dataset from "../models/dataset"
20
+ import Metadata from "../models/metadata"
21
+ import Permission from "../models/permission"
22
+ import Star from "../models/stars"
23
+ import Subscription from "../models/subscription"
24
+ import BadAnnexObject from "../models/badAnnexObject"
25
+ import { datasetsConnection } from "./pagination"
26
+ import { getDatasetWorker } from "../libs/datalad-service"
27
+ import notifications from "../libs/notifications"
27
28
 
28
29
  export const giveUploaderPermission = (datasetId, userId) => {
29
- const permission = new Permission({ datasetId, userId, level: 'admin' })
30
+ const permission = new Permission({ datasetId, userId, level: "admin" })
30
31
  return permission.save()
31
32
  }
32
33
 
@@ -51,8 +52,8 @@ export const createDataset = async (
51
52
  const ds = new Dataset({ id: datasetId, uploader })
52
53
  await request
53
54
  .post(`${getDatasetWorker(datasetId)}/datasets/${datasetId}`)
54
- .set('Accept', 'application/json')
55
- .set('Cookie', generateDataladCookie(config)(userInfo))
55
+ .set("Accept", "application/json")
56
+ .set("Cookie", generateDataladCookie(config)(userInfo))
56
57
  // Write the new dataset to mongo after creation
57
58
  await ds.save()
58
59
  const md = new Metadata({ datasetId, affirmedDefaced, affirmedConsent })
@@ -72,17 +73,17 @@ export const createDataset = async (
72
73
  * Return the latest commit
73
74
  * @param {string} id Dataset accession number
74
75
  */
75
- export const getDraftHead = async id => {
76
+ export const getDraftHead = async (id) => {
76
77
  const draftRes = await request
77
78
  .get(`${getDatasetWorker(id)}/datasets/${id}/draft`)
78
- .set('Accept', 'application/json')
79
+ .set("Accept", "application/json")
79
80
  return draftRes.body.hexsha
80
81
  }
81
82
 
82
83
  /**
83
84
  * Fetch dataset document and related fields
84
85
  */
85
- export const getDataset = async id => {
86
+ export const getDataset = async (id) => {
86
87
  const dataset = await Dataset.findOne({ id }).lean()
87
88
  return {
88
89
  ...dataset,
@@ -93,7 +94,7 @@ export const getDataset = async id => {
93
94
  /**
94
95
  * Delete dataset and associated documents
95
96
  */
96
- export const deleteDataset = id =>
97
+ export const deleteDataset = (id) =>
97
98
  request
98
99
  .del(`${getDatasetWorker(id)}/datasets/${id}`)
99
100
  .then(() => Dataset.deleteOne({ id }).exec())
@@ -103,7 +104,7 @@ export const deleteDataset = id =>
103
104
  * For public datasets, cache combinations of sorts/limits/cursors to speed responses
104
105
  * @param {object} options getDatasets options object
105
106
  */
106
- export const cacheDatasetConnection = options => connectionArguments => {
107
+ export const cacheDatasetConnection = (options) => (connectionArguments) => {
107
108
  const connection = datasetsConnection(options)
108
109
  const cache = new CacheItem(
109
110
  redis,
@@ -119,134 +120,134 @@ export const cacheDatasetConnection = options => connectionArguments => {
119
120
  * @param {object} match MongoDB $match aggregate
120
121
  * @returns {Array<object>} Array of MongoDB aggregate pipelines
121
122
  */
122
- const aggregateArraySetup = match => [{ $match: match }]
123
+ const aggregateArraySetup = (match): Mongoose.Expression => [{ $match: match }]
123
124
 
124
125
  /**
125
126
  * Add any filter steps based on the filterBy options provided
126
127
  * @param {object} options GraphQL query parameters
127
128
  * @returns {(match: object) => Array<any>} Array of aggregate stages
128
129
  */
129
- export const datasetsFilter = options => match => {
130
+ export const datasetsFilter = (options) => (match) => {
130
131
  const aggregates = aggregateArraySetup(match)
131
132
  if (options.modality) {
132
133
  aggregates.push(
133
134
  ...[
134
135
  {
135
136
  $lookup: {
136
- from: 'snapshots',
137
- localField: 'id',
138
- foreignField: 'datasetId',
139
- as: 'snapshots',
137
+ from: "snapshots",
138
+ localField: "id",
139
+ foreignField: "datasetId",
140
+ as: "snapshots",
140
141
  },
141
142
  },
142
- { $addFields: { snapshots: { $slice: ['$snapshots', -1] } } },
143
+ { $addFields: { snapshots: { $slice: ["$snapshots", -1] } } },
143
144
  {
144
145
  $lookup: {
145
- from: 'summaries',
146
- localField: 'snapshots.0.hexsha',
147
- foreignField: 'id',
148
- as: 'summaries',
146
+ from: "summaries",
147
+ localField: "snapshots.0.hexsha",
148
+ foreignField: "id",
149
+ as: "summaries",
149
150
  },
150
151
  },
151
152
  {
152
153
  $match: {
153
- 'summaries.0.modalities': options.modality,
154
+ "summaries.0.modalities": options.modality,
154
155
  },
155
156
  },
156
157
  ],
157
158
  )
158
159
  return aggregates
159
160
  }
160
- const filterMatch = {}
161
- if ('filterBy' in options) {
161
+ const filterMatch: Mongoose.Expression = {}
162
+ if ("filterBy" in options) {
162
163
  const filters = options.filterBy
163
164
  if (
164
- 'admin' in options &&
165
+ "admin" in options &&
165
166
  options.admin &&
166
- 'all' in filters &&
167
+ "all" in filters &&
167
168
  filters.all
168
169
  ) {
169
170
  // For admins and {filterBy: all}, ignore any passed in matches
170
171
  aggregates.length = 0
171
172
  }
172
173
  // Apply any filters as needed
173
- if ('public' in filters && filters.public) {
174
+ if ("public" in filters && filters.public) {
174
175
  filterMatch.public = true
175
176
  }
176
- if ('saved' in filters && filters.saved) {
177
+ if ("saved" in filters && filters.saved) {
177
178
  aggregates.push({
178
179
  $lookup: {
179
- from: 'stars',
180
- let: { datasetId: '$id' },
180
+ from: "stars",
181
+ let: { datasetId: "$id" },
181
182
  pipeline: [
182
183
  {
183
184
  $match: {
184
185
  $expr: {
185
186
  $and: [
186
- { $eq: ['$userId', options.userId] },
187
- { $eq: ['$datasetId', '$$datasetId'] },
187
+ { $eq: ["$userId", options.userId] },
188
+ { $eq: ["$datasetId", "$$datasetId"] },
188
189
  ],
189
190
  },
190
191
  },
191
192
  },
192
193
  ],
193
- as: 'saved',
194
+ as: "saved",
194
195
  },
195
196
  })
196
197
  filterMatch.saved = { $exists: true, $ne: [] } // arr datasetIds
197
198
  }
198
199
 
199
- if ('userId' in options && 'shared' in filters && filters.shared) {
200
+ if ("userId" in options && "shared" in filters && filters.shared) {
200
201
  filterMatch.uploader = { $ne: options.userId }
201
202
  }
202
- if ('userId' in options && 'starred' in filters && filters.starred) {
203
+ if ("userId" in options && "starred" in filters && filters.starred) {
203
204
  aggregates.push({
204
205
  $lookup: {
205
- from: 'stars',
206
- let: { datasetId: '$id' },
206
+ from: "stars",
207
+ let: { datasetId: "$id" },
207
208
  pipeline: [
208
209
  {
209
210
  $match: {
210
211
  $expr: {
211
212
  $and: [
212
- { $eq: ['$datasetId', '$$datasetId'] },
213
- { $eq: ['$userId', options.userId] },
213
+ { $eq: ["$datasetId", "$$datasetId"] },
214
+ { $eq: ["$userId", options.userId] },
214
215
  ],
215
216
  },
216
217
  },
217
218
  },
218
219
  ],
219
- as: 'starred',
220
+ as: "starred",
220
221
  },
221
222
  })
222
223
  filterMatch.starred = { $exists: true, $ne: [] }
223
224
  }
224
- if ('invalid' in filters && filters.invalid) {
225
+ if ("invalid" in filters && filters.invalid) {
225
226
  // SELECT * FROM datasets JOIN issues ON datasets.revision = issues.id WHERE ...
226
227
  aggregates.push({
227
228
  $lookup: {
228
- from: 'issues', //look at issues collection
229
- let: { revision: '$revision' }, // find issue match revision datasets.revision
229
+ from: "issues", //look at issues collection
230
+ let: { revision: "$revision" }, // find issue match revision datasets.revision
230
231
  pipeline: [
231
- { $unwind: '$issues' },
232
+ { $unwind: "$issues" },
232
233
  {
233
234
  $match: {
234
235
  $expr: {
235
236
  $and: [
236
- { $eq: ['$id', '$$revision'] }, // JOIN CONSTRAINT issues.id = datasets.revision
237
- { $eq: ['$issues.severity', 'error'] }, // WHERE severity = 'error' issues.severity db
237
+ { $eq: ["$id", "$$revision"] }, // JOIN CONSTRAINT issues.id = datasets.revision
238
+ { $eq: ["$issues.severity", "error"] }, // WHERE severity = 'error' issues.severity db
238
239
  ],
239
240
  },
240
241
  },
241
242
  },
242
243
  ],
243
- as: 'issues',
244
+ as: "issues",
244
245
  },
245
246
  })
246
247
  // Count how many error fields matched in previous step
247
248
  aggregates.push({
248
249
  $addFields: {
249
- errorCount: { $size: '$issues' },
250
+ errorCount: { $size: "$issues" },
250
251
  },
251
252
  })
252
253
  // Filter any datasets with no errors
@@ -260,19 +261,19 @@ export const datasetsFilter = options => match => {
260
261
  * Fetch all datasets
261
262
  * @param {object} options {orderBy: {created: 'ascending'}, filterBy: {public: true}}
262
263
  */
263
- export const getDatasets = options => {
264
+ export const getDatasets = (options) => {
264
265
  const filter = datasetsFilter(options)
265
266
  const connection = datasetsConnection(options)
266
- if (options && 'userId' in options) {
267
+ if (options && "userId" in options) {
267
268
  // Authenticated request
268
269
  return Permission.find({ userId: options.userId })
269
270
  .exec()
270
- .then(datasetsAllowed => {
271
+ .then((datasetsAllowed) => {
271
272
  const datasetIds = datasetsAllowed.map(
272
- permission => permission.datasetId,
273
+ (permission) => permission.datasetId,
273
274
  )
274
275
  // Match allowed datasets
275
- if ('myDatasets' in options && options.myDatasets) {
276
+ if ("myDatasets" in options && options.myDatasets) {
276
277
  // Exclude other users public datasets even though we have access to those
277
278
  return connection(filter({ id: { $in: datasetIds } }))
278
279
  } else {
@@ -330,18 +331,18 @@ export const addFile = async (datasetId, path, file) => {
330
331
  const downstreamRequest = requestNode(
331
332
  {
332
333
  url: fileUrl(datasetId, path, filename),
333
- method: 'post',
334
- headers: { 'Content-Type': mimetype },
334
+ method: "post",
335
+ headers: { "Content-Type": mimetype },
335
336
  },
336
- err => (err ? reject(err) : resolve(responseFile)),
337
+ (err) => (err ? reject(err) : resolve(responseFile)),
337
338
  )
338
339
  // Attach error handler for incoming request and start feeding downstream
339
340
  stream
340
- .on('data', chunk => {
341
+ .on("data", (chunk) => {
341
342
  responseFile.size += chunk.length
342
343
  })
343
- .on('error', err => {
344
- if (err.constructor.name === 'FileStreamDisconnectUploadError') {
344
+ .on("error", (err) => {
345
+ if (err.constructor.name === "FileStreamDisconnectUploadError") {
345
346
  // Catch client disconnects.
346
347
  // eslint-disable-next-line no-console
347
348
  console.warn(
@@ -356,7 +357,7 @@ export const addFile = async (datasetId, path, file) => {
356
357
  .pipe(downstreamRequest)
357
358
  })
358
359
  } catch (err) {
359
- if (err.constructor.name === 'UploadPromiseDisconnectUploadError') {
360
+ if (err.constructor.name === "UploadPromiseDisconnectUploadError") {
360
361
  // Catch client aborts silently
361
362
  } else {
362
363
  // Raise any unknown errors
@@ -371,7 +372,7 @@ export const addFile = async (datasetId, path, file) => {
371
372
  * Used to mock the stream interface in addFile
372
373
  */
373
374
  export const addFileString = (datasetId, filename, mimetype, content) =>
374
- addFile(datasetId, '', {
375
+ addFile(datasetId, "", {
375
376
  filename,
376
377
  mimetype,
377
378
  // Mock a stream so we can reuse addFile
@@ -401,9 +402,9 @@ export const commitFiles = (datasetId, user) => {
401
402
  const url = `${getDatasetWorker(datasetId)}/datasets/${datasetId}/draft`
402
403
  return request
403
404
  .post(url)
404
- .set('Cookie', generateDataladCookie(config)(user))
405
- .set('Accept', 'application/json')
406
- .then(res => {
405
+ .set("Cookie", generateDataladCookie(config)(user))
406
+ .set("Accept", "application/json")
407
+ .then((res) => {
407
408
  gitRef = res.body.ref
408
409
  return updateDatasetRevision(datasetId, gitRef).then(() => gitRef)
409
410
  })
@@ -414,12 +415,12 @@ export const commitFiles = (datasetId, user) => {
414
415
  */
415
416
  export const deleteFiles = (datasetId, files, user) => {
416
417
  const filenames = files.map(({ filename, path }) =>
417
- filename ? getFileName(path, filename) : encodeFilePath(path),
418
+ filename ? getFileName(path, filename) : encodeFilePath(path)
418
419
  )
419
420
  return request
420
421
  .del(filesUrl(datasetId))
421
- .set('Cookie', generateDataladCookie(config)(user))
422
- .set('Accept', 'application/json')
422
+ .set("Cookie", generateDataladCookie(config)(user))
423
+ .set("Accept", "application/json")
423
424
  .send({ filenames })
424
425
  .then(() => filenames)
425
426
  }
@@ -435,15 +436,16 @@ export const removeAnnexObject = (
435
436
  user,
436
437
  ) => {
437
438
  const worker = getDatasetWorker(datasetId)
438
- const url = `http://${worker}/datasets/${datasetId}/snapshots/${snapshot}/annex-key/${annexKey}`
439
+ const url =
440
+ `http://${worker}/datasets/${datasetId}/snapshots/${snapshot}/annex-key/${annexKey}`
439
441
  return request
440
442
  .del(url)
441
- .set('Cookie', generateDataladCookie(config)(user))
442
- .set('Accept', 'application/json')
443
+ .set("Cookie", generateDataladCookie(config)(user))
444
+ .set("Accept", "application/json")
443
445
  .then(async () => {
444
446
  const existingBAO = await BadAnnexObject.find({ annexKey }).exec()
445
447
  if (existingBAO) {
446
- existingBAO.forEach(bAO => {
448
+ existingBAO.forEach((bAO) => {
447
449
  bAO.remover = user._id
448
450
  bAO.removed = true
449
451
  bAO.save()
@@ -494,19 +496,19 @@ export const updatePublic = (datasetId, publicFlag) =>
494
496
  ).exec()
495
497
 
496
498
  export const getDatasetAnalytics = (datasetId, tag) => {
497
- return Dataset.findOne({ id: datasetId }).then(ds => ({
499
+ return Dataset.findOne({ id: datasetId }).then((ds) => ({
498
500
  datasetId,
499
501
  views: ds.views || 0,
500
502
  downloads: ds.downloads || 0,
501
503
  }))
502
504
  }
503
505
 
504
- export const getStars = datasetId => Star.find({ datasetId })
506
+ export const getStars = (datasetId) => Star.find({ datasetId })
505
507
 
506
508
  export const getUserStarred = (datasetId, userId) =>
507
509
  Star.countDocuments({ datasetId, userId }).exec()
508
510
 
509
- export const getFollowers = datasetId => {
511
+ export const getFollowers = (datasetId) => {
510
512
  return Subscription.find({
511
513
  datasetId: datasetId,
512
514
  }).exec()
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * Get description data from backend
3
3
  */
4
- import config from '../config'
5
- import request from 'superagent'
6
- import { redis } from '../libs/redis.js'
7
- import { commitFiles } from './dataset.js'
8
- import { fileUrl } from './files'
9
- import { generateDataladCookie } from '../libs/authentication/jwt'
10
- import { getDatasetWorker } from '../libs/datalad-service'
11
- import CacheItem, { CacheType } from '../cache/item'
12
- import { datasetOrSnapshot } from '../utils/datasetOrSnapshot'
4
+ import config from "../config"
5
+ import request from "superagent"
6
+ import { redis } from "../libs/redis"
7
+ import { commitFiles } from "./dataset"
8
+ import { fileUrl } from "./files"
9
+ import { generateDataladCookie } from "../libs/authentication/jwt"
10
+ import { getDatasetWorker } from "../libs/datalad-service"
11
+ import CacheItem, { CacheType } from "../cache/item"
12
+ import { datasetOrSnapshot } from "../utils/datasetOrSnapshot"
13
13
 
14
14
  /**
15
15
  * Find dataset_description.json id and fetch description object
@@ -18,10 +18,10 @@ import { datasetOrSnapshot } from '../utils/datasetOrSnapshot'
18
18
  */
19
19
  export const getDescriptionObject = async (datasetId, revision) => {
20
20
  const res = await fetch(
21
- fileUrl(datasetId, '', 'dataset_description.json', revision),
21
+ fileUrl(datasetId, "", "dataset_description.json", revision),
22
22
  )
23
- const contentType = res.headers.get('content-type')
24
- if (res.status === 200 && contentType.includes('application/json')) {
23
+ const contentType = res.headers.get("content-type")
24
+ if (res.status === 200 && contentType.includes("application/json")) {
25
25
  return await res.json()
26
26
  } else {
27
27
  throw new Error(
@@ -34,59 +34,59 @@ export const descriptionCacheKey = (datasetId, revision) => {
34
34
  return `openneuro:dataset_description.json:${datasetId}:${revision}`
35
35
  }
36
36
 
37
- export const repairDescriptionTypes = description => {
37
+ export const repairDescriptionTypes = (description) => {
38
38
  const newDescription = { ...description }
39
39
  // Array types
40
40
  if (
41
- description.hasOwnProperty('Authors') &&
41
+ description.hasOwnProperty("Authors") &&
42
42
  !Array.isArray(description.Authors)
43
43
  ) {
44
44
  newDescription.Authors = [description.Authors]
45
45
  }
46
46
  if (
47
- description.hasOwnProperty('ReferencesAndLinks') &&
47
+ description.hasOwnProperty("ReferencesAndLinks") &&
48
48
  !Array.isArray(description.ReferencesAndLinks)
49
49
  ) {
50
50
  newDescription.ReferencesAndLinks = [description.ReferencesAndLinks]
51
51
  }
52
52
  if (
53
- description.hasOwnProperty('Funding') &&
53
+ description.hasOwnProperty("Funding") &&
54
54
  !Array.isArray(description.Funding)
55
55
  ) {
56
56
  newDescription.Funding = [description.Funding]
57
57
  }
58
58
  if (
59
- description.hasOwnProperty('EthicsApprovals') &&
59
+ description.hasOwnProperty("EthicsApprovals") &&
60
60
  !Array.isArray(description.EthicsApprovals)
61
61
  ) {
62
62
  newDescription.EthicsApprovals = [description.EthicsApprovals]
63
63
  }
64
64
  // String types
65
65
  if (
66
- description.hasOwnProperty('Name') &&
67
- typeof description.Name !== 'string'
66
+ description.hasOwnProperty("Name") &&
67
+ typeof description.Name !== "string"
68
68
  ) {
69
- newDescription.Name = JSON.stringify(description.Name) || ''
69
+ newDescription.Name = JSON.stringify(description.Name) || ""
70
70
  }
71
71
  if (
72
- description.hasOwnProperty('DatasetDOI') &&
73
- typeof description.DatasetDOI !== 'string'
72
+ description.hasOwnProperty("DatasetDOI") &&
73
+ typeof description.DatasetDOI !== "string"
74
74
  ) {
75
- newDescription.DatasetDOI = JSON.stringify(description.DatasetDOI) || ''
75
+ newDescription.DatasetDOI = JSON.stringify(description.DatasetDOI) || ""
76
76
  }
77
77
  if (
78
- description.hasOwnProperty('Acknowledgements') &&
79
- typeof description.Acknowledgements !== 'string'
78
+ description.hasOwnProperty("Acknowledgements") &&
79
+ typeof description.Acknowledgements !== "string"
80
80
  ) {
81
81
  newDescription.Acknowledgements =
82
- JSON.stringify(description.Acknowledgements) || ''
82
+ JSON.stringify(description.Acknowledgements) || ""
83
83
  }
84
84
  if (
85
- description.hasOwnProperty('HowToAcknowledge') &&
86
- typeof description.HowToAcknowledge !== 'string'
85
+ description.hasOwnProperty("HowToAcknowledge") &&
86
+ typeof description.HowToAcknowledge !== "string"
87
87
  ) {
88
88
  newDescription.HowToAcknowledge =
89
- JSON.stringify(description.HowToAcknowledge) || ''
89
+ JSON.stringify(description.HowToAcknowledge) || ""
90
90
  }
91
91
  return newDescription
92
92
  }
@@ -94,7 +94,7 @@ export const repairDescriptionTypes = description => {
94
94
  /**
95
95
  * Return the last author in dataset_description as the senior author if available
96
96
  */
97
- export const appendSeniorAuthor = description => {
97
+ export const appendSeniorAuthor = (description) => {
98
98
  try {
99
99
  const SeniorAuthor = description?.Authors[description.Authors.length - 1]
100
100
  return { ...description, SeniorAuthor }
@@ -107,13 +107,13 @@ export const appendSeniorAuthor = description => {
107
107
  * Get a parsed dataset_description.json
108
108
  * @param {object} obj dataset or snapshot object
109
109
  */
110
- export const description = async obj => {
110
+ export const description = async (obj) => {
111
111
  // Obtain datasetId from Dataset or Snapshot objects
112
112
  const { datasetId, revision } = datasetOrSnapshot(obj)
113
113
  // Default fallback if dataset_description.json is not valid or missing
114
114
  const defaultDescription = {
115
115
  Name: datasetId,
116
- BIDSVersion: '1.8.0',
116
+ BIDSVersion: "1.8.0",
117
117
  }
118
118
  const cache = new CacheItem(redis, CacheType.datasetDescription, [
119
119
  datasetId,
@@ -122,7 +122,7 @@ export const description = async obj => {
122
122
  try {
123
123
  const datasetDescription = await cache.get(() => {
124
124
  return getDescriptionObject(datasetId, revision).then(
125
- uncachedDescription => ({ id: revision, ...uncachedDescription }),
125
+ (uncachedDescription) => ({ id: revision, ...uncachedDescription }),
126
126
  )
127
127
  })
128
128
  return appendSeniorAuthor(repairDescriptionTypes(datasetDescription))
@@ -136,11 +136,11 @@ export const setDescription = (datasetId, user, descriptionFieldUpdates) => {
136
136
  return request
137
137
  .post(url)
138
138
  .send({ description_fields: descriptionFieldUpdates })
139
- .set('Accept', 'application/json')
140
- .set('Cookie', generateDataladCookie(config)(user))
141
- .then(res => {
139
+ .set("Accept", "application/json")
140
+ .set("Cookie", generateDataladCookie(config)(user))
141
+ .then((res) => {
142
142
  const description = res.body
143
- return commitFiles(datasetId, user).then(gitRef => ({
143
+ return commitFiles(datasetId, user).then((gitRef) => ({
144
144
  id: gitRef,
145
145
  ...description,
146
146
  }))
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Manage serving a Draft object based on DataLad working trees
3
+ */
4
+ import request from "superagent"
5
+ import Dataset from "../models/dataset"
6
+ import { getDatasetWorker } from "../libs/datalad-service"
7
+
8
+ export const getDraftRevision = async (datasetId) => {
9
+ const draftUrl = `http://${
10
+ getDatasetWorker(
11
+ datasetId,
12
+ )
13
+ }/datasets/${datasetId}/draft`
14
+ const response = await fetch(draftUrl)
15
+ const { hexsha } = await response.json()
16
+ return hexsha
17
+ }
18
+
19
+ export const updateDatasetRevision = (datasetId, gitRef) => {
20
+ /**
21
+ * Update the revision modified time in a draft on changes
22
+ */
23
+ return Dataset.updateOne({ id: datasetId }, { modified: new Date() }).exec()
24
+ }
25
+
26
+ /**
27
+ * Run a git reset on the draft
28
+ * @param {string} datasetId Accession number
29
+ * @param {string} ref Git hexsha
30
+ */
31
+ export const resetDraft = (datasetId, ref) => {
32
+ const resetUrl = `${
33
+ getDatasetWorker(
34
+ datasetId,
35
+ )
36
+ }/datasets/${datasetId}/reset/${ref}`
37
+ return request.post(resetUrl).set("Accept", "application/json")
38
+ }