@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.
- package/package.json +4 -6
- package/src/__mocks__/{config.js → config.ts} +5 -5
- package/src/app.ts +32 -31
- package/src/cache/item.ts +6 -7
- package/src/cache/types.ts +8 -8
- package/src/{config.js → config.ts} +6 -6
- package/src/datalad/__tests__/changelog.spec.ts +83 -0
- package/src/datalad/__tests__/dataset.spec.ts +109 -0
- package/src/datalad/__tests__/description.spec.ts +141 -0
- package/src/datalad/__tests__/files.spec.ts +77 -0
- package/src/datalad/__tests__/pagination.spec.ts +136 -0
- package/src/datalad/__tests__/{snapshots.spec.js → snapshots.spec.ts} +17 -17
- package/src/datalad/{analytics.js → analytics.ts} +4 -4
- package/src/datalad/{changelog.js → changelog.ts} +17 -14
- package/src/datalad/{dataset.js → dataset.ts} +95 -93
- package/src/datalad/{description.js → description.ts} +37 -37
- package/src/datalad/draft.ts +38 -0
- package/src/datalad/files.ts +26 -20
- package/src/datalad/{pagination.js → pagination.ts} +47 -47
- package/src/datalad/{readme.js → readme.ts} +13 -11
- package/src/datalad/{reexporter.js → reexporter.ts} +4 -4
- package/src/datalad/{snapshots.js → snapshots.ts} +56 -62
- package/src/datalad/{upload.js → upload.ts} +7 -5
- package/src/elasticsearch/elastic-client.ts +11 -0
- package/src/elasticsearch/reindex-dataset.ts +7 -7
- package/src/graphql/__tests__/__snapshots__/permissions.spec.ts.snap +5 -0
- package/src/graphql/__tests__/{comment.spec.js → comment.spec.ts} +17 -17
- package/src/graphql/__tests__/permissions.spec.ts +113 -0
- package/src/graphql/{permissions.js → permissions.ts} +14 -14
- package/src/graphql/resolvers/__tests__/brainlife.spec.ts +11 -11
- package/src/graphql/resolvers/__tests__/{dataset-search.spec.js → dataset-search.spec.ts} +25 -23
- package/src/graphql/resolvers/__tests__/dataset.spec.ts +175 -0
- package/src/graphql/resolvers/__tests__/derivatives.spec.ts +19 -19
- package/src/graphql/resolvers/__tests__/importRemoteDataset.spec.ts +20 -20
- package/src/graphql/resolvers/__tests__/permssions.spec.ts +35 -0
- package/src/graphql/resolvers/__tests__/snapshots.spec.ts +59 -0
- package/src/graphql/resolvers/__tests__/user.spec.ts +18 -0
- package/src/graphql/resolvers/brainlife.ts +4 -4
- package/src/graphql/resolvers/cache.ts +4 -4
- package/src/graphql/resolvers/{comment.js → comment.ts} +16 -16
- package/src/graphql/resolvers/{dataset-search.js → dataset-search.ts} +45 -43
- package/src/graphql/resolvers/{dataset.js → dataset.ts} +38 -52
- package/src/graphql/resolvers/datasetType.ts +3 -3
- package/src/graphql/resolvers/derivatives.ts +11 -11
- package/src/graphql/resolvers/description.ts +18 -0
- package/src/graphql/resolvers/{draft.js → draft.ts} +13 -13
- package/src/graphql/resolvers/{flaggedFiles.js → flaggedFiles.ts} +4 -4
- package/src/graphql/resolvers/{follow.js → follow.ts} +1 -1
- package/src/graphql/resolvers/git.ts +3 -3
- package/src/graphql/resolvers/history.ts +13 -0
- package/src/graphql/resolvers/importRemoteDataset.ts +12 -11
- package/src/graphql/resolvers/index.ts +25 -0
- package/src/graphql/resolvers/{issues.js → issues.ts} +9 -9
- package/src/graphql/resolvers/metadata.ts +8 -8
- package/src/graphql/resolvers/{mutation.js → mutation.ts} +26 -26
- package/src/graphql/resolvers/{newsletter.js → newsletter.ts} +2 -2
- package/src/graphql/resolvers/permissions.ts +15 -21
- package/src/graphql/resolvers/publish.ts +17 -0
- package/src/graphql/resolvers/query.ts +21 -0
- package/src/graphql/resolvers/{readme.js → readme.ts} +3 -3
- package/src/graphql/resolvers/{reexporter.js → reexporter.ts} +2 -2
- package/src/graphql/resolvers/relation.ts +5 -5
- package/src/graphql/resolvers/{reset.js → reset.ts} +2 -2
- package/src/graphql/resolvers/reviewer.ts +4 -4
- package/src/graphql/resolvers/{snapshots.js → snapshots.ts} +49 -49
- package/src/graphql/resolvers/{stars.js → stars.ts} +1 -1
- package/src/graphql/resolvers/summary.ts +3 -3
- package/src/graphql/resolvers/{upload.js → upload.ts} +5 -5
- package/src/graphql/resolvers/{user.js → user.ts} +16 -18
- package/src/graphql/resolvers/{validation.js → validation.ts} +12 -14
- package/src/graphql/{schema.js → schema.ts} +4 -6
- package/src/graphql/utils/{file.js → file.ts} +2 -2
- package/src/handlers/{comments.js → comments.ts} +11 -11
- package/src/handlers/{config.js → config.ts} +1 -1
- package/src/handlers/{datalad.js → datalad.ts} +22 -22
- package/src/handlers/{doi.js → doi.ts} +6 -6
- package/src/handlers/reviewer.ts +6 -6
- package/src/handlers/{sitemap.js → sitemap.ts} +19 -19
- package/src/handlers/stars.ts +11 -10
- package/src/handlers/{subscriptions.js → subscriptions.ts} +17 -16
- package/src/handlers/{users.js → users.ts} +3 -3
- package/src/libs/__tests__/apikey.spec.ts +25 -0
- package/src/libs/__tests__/datalad-service.spec.ts +27 -0
- package/src/libs/__tests__/{dataset.spec.js → dataset.spec.ts} +9 -9
- package/src/libs/{apikey.js → apikey.ts} +5 -5
- package/src/libs/authentication/__tests__/jwt.spec.ts +59 -0
- package/src/libs/authentication/{crypto.js → crypto.ts} +16 -16
- package/src/libs/authentication/google.ts +18 -0
- package/src/libs/authentication/jwt.ts +40 -33
- package/src/libs/authentication/{orcid.js → orcid.ts} +11 -11
- package/src/libs/authentication/{passport.js → passport.ts} +45 -30
- package/src/libs/authentication/{states.js → states.ts} +17 -20
- package/src/libs/{counter.js → counter.ts} +1 -1
- package/src/libs/{datalad-service.js → datalad-service.ts} +4 -4
- package/src/libs/dataset.ts +9 -0
- package/src/libs/doi/__tests__/__snapshots__/doi.spec.ts.snap +17 -0
- package/src/libs/doi/__tests__/doi.spec.ts +25 -0
- package/src/libs/doi/__tests__/normalize.spec.ts +19 -19
- package/src/libs/doi/{index.js → index.ts} +27 -21
- package/src/libs/doi/normalize.ts +2 -2
- package/src/libs/email/__tests__/index.spec.ts +14 -14
- package/src/libs/email/index.ts +4 -4
- package/src/libs/email/templates/__tests__/comment-created.spec.ts +12 -12
- package/src/libs/email/templates/__tests__/dataset-deleted.spec.ts +6 -6
- package/src/libs/email/templates/__tests__/owner-unsubscribed.spec.ts +6 -6
- package/src/libs/email/templates/__tests__/snapshot-created.spec.ts +9 -9
- package/src/libs/email/templates/__tests__/snapshot-reminder.spec.ts +7 -7
- package/src/libs/email/templates/comment-created.ts +2 -1
- package/src/libs/email/templates/dataset-deleted.ts +2 -1
- package/src/libs/email/templates/dataset-import-failed.ts +2 -1
- package/src/libs/email/templates/dataset-imported.ts +2 -1
- package/src/libs/email/templates/owner-unsubscribed.ts +2 -1
- package/src/libs/email/templates/snapshot-created.ts +2 -1
- package/src/libs/email/templates/snapshot-reminder.ts +2 -1
- package/src/libs/{notifications.js → notifications.ts} +100 -113
- package/src/libs/{orcid.js → orcid.ts} +20 -20
- package/src/libs/{redis.js → redis.ts} +6 -6
- package/src/models/__tests__/ingestDataset.spec.ts +15 -15
- package/src/models/analytics.ts +2 -2
- package/src/models/badAnnexObject.ts +6 -6
- package/src/models/comment.ts +10 -10
- package/src/models/counter.ts +2 -2
- package/src/models/dataset.ts +16 -16
- package/src/models/deletion.ts +3 -3
- package/src/models/deprecatedSnapshot.ts +2 -2
- package/src/models/doi.ts +2 -2
- package/src/models/file.ts +2 -2
- package/src/models/ingestDataset.ts +4 -4
- package/src/models/issue.ts +2 -2
- package/src/models/key.ts +2 -2
- package/src/models/mailgunIdentifier.ts +2 -2
- package/src/models/metadata.ts +3 -3
- package/src/models/newsletter.ts +3 -3
- package/src/models/notification.ts +2 -2
- package/src/models/permission.ts +4 -4
- package/src/models/reviewer.ts +7 -7
- package/src/models/snapshot.ts +2 -2
- package/src/models/stars.ts +6 -6
- package/src/models/subscription.ts +2 -2
- package/src/models/summary.ts +2 -2
- package/src/models/upload.ts +3 -3
- package/src/models/user.ts +4 -4
- package/src/{routes.js → routes.ts} +62 -62
- package/src/server.ts +9 -9
- package/src/utils/__tests__/datasetOrSnapshot.spec.ts +25 -25
- package/src/utils/__tests__/validateUrl.spec.ts +10 -10
- package/src/utils/datasetOrSnapshot.ts +2 -2
- package/src/utils/validateUrl.ts +1 -1
- package/src/datalad/__tests__/changelog.spec.js +0 -82
- package/src/datalad/__tests__/dataset.spec.js +0 -109
- package/src/datalad/__tests__/description.spec.js +0 -137
- package/src/datalad/__tests__/files.spec.js +0 -75
- package/src/datalad/__tests__/pagination.spec.js +0 -136
- package/src/datalad/draft.js +0 -37
- package/src/elasticsearch/elastic-client.js +0 -11
- package/src/graphql/__tests__/permissions.spec.js +0 -107
- package/src/graphql/pubsub.js +0 -5
- package/src/graphql/resolvers/__tests__/dataset.spec.js +0 -175
- package/src/graphql/resolvers/__tests__/permssions.spec.js +0 -34
- package/src/graphql/resolvers/__tests__/snapshots.spec.js +0 -58
- package/src/graphql/resolvers/__tests__/user.spec.js +0 -17
- package/src/graphql/resolvers/description.js +0 -29
- package/src/graphql/resolvers/history.js +0 -11
- package/src/graphql/resolvers/index.js +0 -25
- package/src/graphql/resolvers/publish.js +0 -17
- package/src/graphql/resolvers/query.js +0 -21
- package/src/graphql/resolvers/subscriptions.js +0 -81
- package/src/graphql/utils/publish-draft-update.js +0 -13
- package/src/libs/__tests__/apikey.spec.js +0 -24
- package/src/libs/__tests__/datalad-service.spec.js +0 -26
- package/src/libs/authentication/__tests__/jwt.spec.js +0 -23
- package/src/libs/authentication/globus.js +0 -11
- package/src/libs/authentication/google.js +0 -19
- package/src/libs/bidsId.js +0 -68
- package/src/libs/dataset.js +0 -9
- package/src/libs/doi/__tests__/doi.spec.js +0 -24
- package/src/libs/redis-pubsub.js +0 -5
- package/src/libs/request.js +0 -155
- package/src/libs/scitran.js +0 -25
- package/src/libs/subscription-server.js +0 -20
- package/src/libs/testing-utils.js +0 -17
- package/src/persistent/datasets/.gitignore +0 -3
- package/src/persistent/temp/.gitignore +0 -3
- /package/src/libs/__mocks__/{notifications.js → notifications.ts} +0 -0
- /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
|
|
7
|
-
import requestNode from
|
|
8
|
-
import objectHash from
|
|
9
|
-
import { Readable } from
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
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:
|
|
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(
|
|
55
|
-
.set(
|
|
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(
|
|
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:
|
|
137
|
-
localField:
|
|
138
|
-
foreignField:
|
|
139
|
-
as:
|
|
137
|
+
from: "snapshots",
|
|
138
|
+
localField: "id",
|
|
139
|
+
foreignField: "datasetId",
|
|
140
|
+
as: "snapshots",
|
|
140
141
|
},
|
|
141
142
|
},
|
|
142
|
-
{ $addFields: { snapshots: { $slice: [
|
|
143
|
+
{ $addFields: { snapshots: { $slice: ["$snapshots", -1] } } },
|
|
143
144
|
{
|
|
144
145
|
$lookup: {
|
|
145
|
-
from:
|
|
146
|
-
localField:
|
|
147
|
-
foreignField:
|
|
148
|
-
as:
|
|
146
|
+
from: "summaries",
|
|
147
|
+
localField: "snapshots.0.hexsha",
|
|
148
|
+
foreignField: "id",
|
|
149
|
+
as: "summaries",
|
|
149
150
|
},
|
|
150
151
|
},
|
|
151
152
|
{
|
|
152
153
|
$match: {
|
|
153
|
-
|
|
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 (
|
|
161
|
+
const filterMatch: Mongoose.Expression = {}
|
|
162
|
+
if ("filterBy" in options) {
|
|
162
163
|
const filters = options.filterBy
|
|
163
164
|
if (
|
|
164
|
-
|
|
165
|
+
"admin" in options &&
|
|
165
166
|
options.admin &&
|
|
166
|
-
|
|
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 (
|
|
174
|
+
if ("public" in filters && filters.public) {
|
|
174
175
|
filterMatch.public = true
|
|
175
176
|
}
|
|
176
|
-
if (
|
|
177
|
+
if ("saved" in filters && filters.saved) {
|
|
177
178
|
aggregates.push({
|
|
178
179
|
$lookup: {
|
|
179
|
-
from:
|
|
180
|
-
let: { datasetId:
|
|
180
|
+
from: "stars",
|
|
181
|
+
let: { datasetId: "$id" },
|
|
181
182
|
pipeline: [
|
|
182
183
|
{
|
|
183
184
|
$match: {
|
|
184
185
|
$expr: {
|
|
185
186
|
$and: [
|
|
186
|
-
{ $eq: [
|
|
187
|
-
{ $eq: [
|
|
187
|
+
{ $eq: ["$userId", options.userId] },
|
|
188
|
+
{ $eq: ["$datasetId", "$$datasetId"] },
|
|
188
189
|
],
|
|
189
190
|
},
|
|
190
191
|
},
|
|
191
192
|
},
|
|
192
193
|
],
|
|
193
|
-
as:
|
|
194
|
+
as: "saved",
|
|
194
195
|
},
|
|
195
196
|
})
|
|
196
197
|
filterMatch.saved = { $exists: true, $ne: [] } // arr datasetIds
|
|
197
198
|
}
|
|
198
199
|
|
|
199
|
-
if (
|
|
200
|
+
if ("userId" in options && "shared" in filters && filters.shared) {
|
|
200
201
|
filterMatch.uploader = { $ne: options.userId }
|
|
201
202
|
}
|
|
202
|
-
if (
|
|
203
|
+
if ("userId" in options && "starred" in filters && filters.starred) {
|
|
203
204
|
aggregates.push({
|
|
204
205
|
$lookup: {
|
|
205
|
-
from:
|
|
206
|
-
let: { datasetId:
|
|
206
|
+
from: "stars",
|
|
207
|
+
let: { datasetId: "$id" },
|
|
207
208
|
pipeline: [
|
|
208
209
|
{
|
|
209
210
|
$match: {
|
|
210
211
|
$expr: {
|
|
211
212
|
$and: [
|
|
212
|
-
{ $eq: [
|
|
213
|
-
{ $eq: [
|
|
213
|
+
{ $eq: ["$datasetId", "$$datasetId"] },
|
|
214
|
+
{ $eq: ["$userId", options.userId] },
|
|
214
215
|
],
|
|
215
216
|
},
|
|
216
217
|
},
|
|
217
218
|
},
|
|
218
219
|
],
|
|
219
|
-
as:
|
|
220
|
+
as: "starred",
|
|
220
221
|
},
|
|
221
222
|
})
|
|
222
223
|
filterMatch.starred = { $exists: true, $ne: [] }
|
|
223
224
|
}
|
|
224
|
-
if (
|
|
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:
|
|
229
|
-
let: { revision:
|
|
229
|
+
from: "issues", //look at issues collection
|
|
230
|
+
let: { revision: "$revision" }, // find issue match revision datasets.revision
|
|
230
231
|
pipeline: [
|
|
231
|
-
{ $unwind:
|
|
232
|
+
{ $unwind: "$issues" },
|
|
232
233
|
{
|
|
233
234
|
$match: {
|
|
234
235
|
$expr: {
|
|
235
236
|
$and: [
|
|
236
|
-
{ $eq: [
|
|
237
|
-
{ $eq: [
|
|
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:
|
|
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:
|
|
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 &&
|
|
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 (
|
|
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:
|
|
334
|
-
headers: {
|
|
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(
|
|
341
|
+
.on("data", (chunk) => {
|
|
341
342
|
responseFile.size += chunk.length
|
|
342
343
|
})
|
|
343
|
-
.on(
|
|
344
|
-
if (err.constructor.name ===
|
|
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 ===
|
|
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(
|
|
405
|
-
.set(
|
|
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(
|
|
422
|
-
.set(
|
|
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 =
|
|
439
|
+
const url =
|
|
440
|
+
`http://${worker}/datasets/${datasetId}/snapshots/${snapshot}/annex-key/${annexKey}`
|
|
439
441
|
return request
|
|
440
442
|
.del(url)
|
|
441
|
-
.set(
|
|
442
|
-
.set(
|
|
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
|
|
5
|
-
import request from
|
|
6
|
-
import { redis } from
|
|
7
|
-
import { commitFiles } from
|
|
8
|
-
import { fileUrl } from
|
|
9
|
-
import { generateDataladCookie } from
|
|
10
|
-
import { getDatasetWorker } from
|
|
11
|
-
import CacheItem, { CacheType } from
|
|
12
|
-
import { datasetOrSnapshot } from
|
|
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,
|
|
21
|
+
fileUrl(datasetId, "", "dataset_description.json", revision),
|
|
22
22
|
)
|
|
23
|
-
const contentType = res.headers.get(
|
|
24
|
-
if (res.status === 200 && contentType.includes(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
67
|
-
typeof description.Name !==
|
|
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(
|
|
73
|
-
typeof description.DatasetDOI !==
|
|
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(
|
|
79
|
-
typeof description.Acknowledgements !==
|
|
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(
|
|
86
|
-
typeof description.HowToAcknowledge !==
|
|
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:
|
|
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(
|
|
140
|
-
.set(
|
|
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
|
+
}
|