@openneuro/server 5.0.0-alpha.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -5
- package/src/app.ts +2 -4
- package/src/cache/__tests__/tree.spec.ts +2 -0
- package/src/cache/tree.ts +4 -0
- package/src/cache/types.ts +1 -0
- package/src/datalad/__tests__/contributors.spec.ts +1 -1
- package/src/datalad/__tests__/dataRetentionNotifications.spec.ts +11 -11
- package/src/datalad/__tests__/files.spec.ts +31 -1
- package/src/datalad/__tests__/snapshots.spec.ts +4 -4
- package/src/datalad/contributors.ts +2 -2
- package/src/datalad/dataRetentionNotifications.ts +5 -5
- package/src/datalad/dataset.ts +9 -5
- package/src/datalad/description.ts +2 -2
- package/src/datalad/draft.ts +8 -3
- package/src/datalad/files.ts +71 -12
- package/src/datalad/readme.ts +2 -2
- package/src/datalad/snapshots.ts +19 -14
- package/src/elasticsearch/elastic-client.ts +11 -6
- package/src/elasticsearch/reindex-dataset.ts +8 -4
- package/src/graphql/__tests__/comment.spec.ts +3 -2
- package/src/graphql/__tests__/schema.spec.ts +28 -0
- package/src/graphql/builder.ts +42 -0
- package/src/graphql/resolvers/__tests__/dataset.spec.ts +6 -119
- package/src/graphql/resolvers/__tests__/importRemoteDataset.spec.ts +2 -1
- package/src/graphql/resolvers/__tests__/permssions.spec.ts +3 -2
- package/src/graphql/resolvers/__tests__/user.spec.ts +39 -11
- package/src/graphql/resolvers/brainInitiative.ts +4 -3
- package/src/graphql/resolvers/cache.ts +7 -6
- package/src/graphql/resolvers/comment.ts +35 -19
- package/src/graphql/resolvers/dataset-search.ts +7 -6
- package/src/graphql/resolvers/dataset.ts +77 -45
- package/src/graphql/resolvers/datasetEvents.ts +18 -16
- package/src/graphql/resolvers/description.ts +2 -1
- package/src/graphql/resolvers/draft.ts +14 -3
- package/src/graphql/resolvers/fileCheck.ts +5 -4
- package/src/graphql/resolvers/flaggedFiles.ts +2 -1
- package/src/graphql/resolvers/follow.ts +2 -1
- package/src/graphql/resolvers/git.ts +2 -1
- package/src/graphql/resolvers/gitEvents.ts +2 -1
- package/src/graphql/resolvers/history.ts +3 -1
- package/src/graphql/resolvers/holdDeletion.ts +1 -1
- package/src/graphql/resolvers/importRemoteDataset.ts +3 -2
- package/src/graphql/resolvers/issues.ts +2 -1
- package/src/graphql/resolvers/metadata.ts +3 -2
- package/src/graphql/resolvers/permissions.ts +26 -6
- package/src/graphql/resolvers/publish.ts +2 -1
- package/src/graphql/resolvers/readme.ts +2 -1
- package/src/graphql/resolvers/reexporter.ts +2 -1
- package/src/graphql/resolvers/relation.ts +3 -2
- package/src/graphql/resolvers/reset.ts +2 -1
- package/src/graphql/resolvers/reviewer.ts +14 -6
- package/src/graphql/resolvers/snapshots.ts +42 -10
- package/src/graphql/resolvers/stars.ts +2 -1
- package/src/graphql/resolvers/upload.ts +3 -2
- package/src/graphql/resolvers/user.ts +44 -32
- package/src/graphql/resolvers/validation.ts +6 -5
- package/src/graphql/resolvers/worker.ts +2 -1
- package/src/graphql/schema/analytics.ts +12 -0
- package/src/graphql/schema/comment.ts +30 -0
- package/src/graphql/schema/dataset-events.ts +91 -0
- package/src/graphql/schema/dataset-search.ts +32 -0
- package/src/graphql/schema/dataset.ts +167 -0
- package/src/graphql/schema/description.ts +44 -0
- package/src/graphql/schema/draft.ts +80 -0
- package/src/graphql/schema/enums.ts +55 -0
- package/src/graphql/schema/files.ts +44 -0
- package/src/graphql/schema/inputs.ts +231 -0
- package/src/graphql/schema/metadata.ts +86 -0
- package/src/graphql/schema/misc.ts +154 -0
- package/src/graphql/schema/mutation.ts +549 -0
- package/src/graphql/schema/pagination.ts +12 -0
- package/src/graphql/schema/permissions.ts +21 -0
- package/src/graphql/schema/query.ts +119 -0
- package/src/graphql/schema/refs.ts +23 -0
- package/src/graphql/schema/reviewer.ts +11 -0
- package/src/graphql/schema/scalars.ts +9 -0
- package/src/graphql/schema/snapshot.ts +111 -0
- package/src/graphql/schema/upload.ts +13 -0
- package/src/graphql/schema/user.ts +61 -0
- package/src/graphql/schema/validation.ts +70 -0
- package/src/graphql/schema/worker.ts +16 -0
- package/src/graphql/schema.ts +29 -1114
- package/src/handlers/subscriptions.ts +1 -1
- package/src/libs/apikey.ts +1 -1
- package/src/libs/authentication/crypto.ts +14 -9
- package/src/libs/notifications.ts +1 -1
- package/src/libs/presign.ts +32 -20
- package/src/libs/redis.ts +12 -2
- package/src/models/comment.ts +2 -4
- package/src/models/counter.ts +2 -2
- package/src/models/ingestDataset.ts +1 -2
- package/src/models/notification.ts +0 -2
- package/src/models/subscription.ts +0 -1
- package/src/models/user.ts +1 -2
- package/src/models/userMigration.ts +1 -1
- package/src/models/userNotificationStatus.ts +0 -1
- package/src/utils/__tests__/snapshots.spec.ts +120 -0
- package/src/utils/snapshots.ts +12 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openneuro/server",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Core service for the OpenNeuro platform.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "src/server.js",
|
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
"@elastic/elasticsearch": "8.13.1",
|
|
23
23
|
"@graphql-tools/schema": "^10.0.0",
|
|
24
24
|
"@keyv/redis": "^4.5.0",
|
|
25
|
-
"@openneuro/search": "^5.
|
|
25
|
+
"@openneuro/search": "^5.1.0",
|
|
26
|
+
"@pothos/core": "^4.12.0",
|
|
27
|
+
"@pothos/plugin-directives": "^4.3.0",
|
|
28
|
+
"@pothos/plugin-simple-objects": "^4.1.3",
|
|
26
29
|
"@sentry/node": "^10.37.0",
|
|
27
30
|
"@sentry/profiling-node": "^10.37.0",
|
|
28
31
|
"base64url": "^3.0.0",
|
|
@@ -34,9 +37,9 @@
|
|
|
34
37
|
"express": "5",
|
|
35
38
|
"graphql": "16.8.1",
|
|
36
39
|
"graphql-bigint": "^1.0.0",
|
|
37
|
-
"graphql-compose": "9.0.10",
|
|
38
40
|
"graphql-iso-date": "^3.6.1",
|
|
39
41
|
"graphql-tools": "9.0.0",
|
|
42
|
+
"graphql-type-json": "^0.3.2",
|
|
40
43
|
"hash-wasm": "^4.12.0",
|
|
41
44
|
"immutable": "^4.3.8",
|
|
42
45
|
"ioredis": "^5.6.1",
|
|
@@ -69,7 +72,7 @@
|
|
|
69
72
|
"ts-node": "10.9.2",
|
|
70
73
|
"typescript": "5.6.3",
|
|
71
74
|
"underscore": "^1.8.3",
|
|
72
|
-
"uuid": "
|
|
75
|
+
"uuid": "14.0.0",
|
|
73
76
|
"xmldoc": "^1.1.0"
|
|
74
77
|
},
|
|
75
78
|
"devDependencies": {
|
|
@@ -92,5 +95,5 @@
|
|
|
92
95
|
"publishConfig": {
|
|
93
96
|
"access": "public"
|
|
94
97
|
},
|
|
95
|
-
"gitHead": "
|
|
98
|
+
"gitHead": "e3444f63f43cb7e9d3ecfac51d2a42cdcc4f4e60"
|
|
96
99
|
}
|
package/src/app.ts
CHANGED
|
@@ -22,13 +22,12 @@ import * as jwt from "./libs/authentication/jwt"
|
|
|
22
22
|
import * as auth from "./libs/authentication/states"
|
|
23
23
|
import { sitemapHandler } from "./handlers/sitemap"
|
|
24
24
|
import { setupPassportAuth } from "./libs/authentication/passport"
|
|
25
|
-
import {
|
|
25
|
+
import { getRedis } from "./libs/redis"
|
|
26
26
|
import { version } from "./lerna.json"
|
|
27
27
|
export { Express } from "express-serve-static-core"
|
|
28
28
|
|
|
29
29
|
interface OpenNeuroRequestContext {
|
|
30
30
|
user: string
|
|
31
|
-
isSuperUser: boolean
|
|
32
31
|
userInfo: {
|
|
33
32
|
id: string
|
|
34
33
|
exp: string
|
|
@@ -68,7 +67,7 @@ export async function expressApolloSetup() {
|
|
|
68
67
|
// Always allow introspection - our schema is public
|
|
69
68
|
introspection: true,
|
|
70
69
|
// @ts-expect-error Type mismatch for keyv and ioredis recent releases
|
|
71
|
-
cache: new KeyvAdapter(new Keyv({ store: new KeyvRedis(
|
|
70
|
+
cache: new KeyvAdapter(new Keyv({ store: new KeyvRedis(getRedis()) })),
|
|
72
71
|
plugins: [
|
|
73
72
|
ApolloServerPluginLandingPageLocalDefault(),
|
|
74
73
|
ApolloServerPluginDrainHttpServer({
|
|
@@ -109,7 +108,6 @@ export async function expressApolloSetup() {
|
|
|
109
108
|
if (req.isAuthenticated()) {
|
|
110
109
|
return {
|
|
111
110
|
user: req.user.id,
|
|
112
|
-
isSuperUser: req.user.admin,
|
|
113
111
|
userInfo: req.user,
|
|
114
112
|
}
|
|
115
113
|
}
|
package/src/cache/tree.ts
CHANGED
package/src/cache/types.ts
CHANGED
|
@@ -89,7 +89,7 @@ describe("checkDataRetentionNotifications", () => {
|
|
|
89
89
|
await Deletion.create({
|
|
90
90
|
datasetId: TEST_DATASET,
|
|
91
91
|
reason: "test deletion",
|
|
92
|
-
user: {
|
|
92
|
+
user: { id: TEST_USER.id },
|
|
93
93
|
})
|
|
94
94
|
mockDraft(daysAgo(15))
|
|
95
95
|
mockSnapshots([{ hexsha: "other" }])
|
|
@@ -131,7 +131,7 @@ describe("checkDataRetentionNotifications", () => {
|
|
|
131
131
|
expect(notifications.send).toHaveBeenCalledTimes(1)
|
|
132
132
|
expect(notifications.send).toHaveBeenCalledWith(
|
|
133
133
|
expect.objectContaining({
|
|
134
|
-
|
|
134
|
+
id: expect.stringContaining("no_snapshot_reminder"),
|
|
135
135
|
}),
|
|
136
136
|
)
|
|
137
137
|
})
|
|
@@ -145,7 +145,7 @@ describe("checkDataRetentionNotifications", () => {
|
|
|
145
145
|
expect(notifications.send).toHaveBeenCalledTimes(1)
|
|
146
146
|
expect(notifications.send).toHaveBeenCalledWith(
|
|
147
147
|
expect.objectContaining({
|
|
148
|
-
|
|
148
|
+
id: expect.stringContaining("retention_14day"),
|
|
149
149
|
}),
|
|
150
150
|
)
|
|
151
151
|
})
|
|
@@ -158,7 +158,7 @@ describe("checkDataRetentionNotifications", () => {
|
|
|
158
158
|
expect(notifications.send).toHaveBeenCalledTimes(1)
|
|
159
159
|
expect(notifications.send).toHaveBeenCalledWith(
|
|
160
160
|
expect.objectContaining({
|
|
161
|
-
|
|
161
|
+
id: expect.stringContaining("retention_14day"),
|
|
162
162
|
}),
|
|
163
163
|
)
|
|
164
164
|
})
|
|
@@ -172,7 +172,7 @@ describe("checkDataRetentionNotifications", () => {
|
|
|
172
172
|
expect(notifications.send).toHaveBeenCalledTimes(1)
|
|
173
173
|
expect(notifications.send).toHaveBeenCalledWith(
|
|
174
174
|
expect.objectContaining({
|
|
175
|
-
|
|
175
|
+
id: expect.stringContaining("retention_14day"),
|
|
176
176
|
}),
|
|
177
177
|
)
|
|
178
178
|
|
|
@@ -214,7 +214,7 @@ describe("checkDataRetentionNotifications", () => {
|
|
|
214
214
|
expect(notifications.send).toHaveBeenCalledTimes(1)
|
|
215
215
|
expect(notifications.send).toHaveBeenCalledWith(
|
|
216
216
|
expect.objectContaining({
|
|
217
|
-
|
|
217
|
+
id: expect.stringContaining("retention_7day"),
|
|
218
218
|
}),
|
|
219
219
|
)
|
|
220
220
|
})
|
|
@@ -235,7 +235,7 @@ describe("checkDataRetentionNotifications", () => {
|
|
|
235
235
|
expect(notifications.send).toHaveBeenCalledTimes(1)
|
|
236
236
|
expect(notifications.send).toHaveBeenCalledWith(
|
|
237
237
|
expect.objectContaining({
|
|
238
|
-
|
|
238
|
+
id: expect.stringContaining("retention_deletion"),
|
|
239
239
|
}),
|
|
240
240
|
)
|
|
241
241
|
})
|
|
@@ -274,7 +274,7 @@ describe("checkDataRetentionNotifications", () => {
|
|
|
274
274
|
expect(notifications.send).toHaveBeenCalledTimes(1)
|
|
275
275
|
expect(notifications.send).toHaveBeenCalledWith(
|
|
276
276
|
expect.objectContaining({
|
|
277
|
-
|
|
277
|
+
id: expect.stringContaining("retention_14day"),
|
|
278
278
|
}),
|
|
279
279
|
)
|
|
280
280
|
|
|
@@ -295,7 +295,7 @@ describe("checkDataRetentionNotifications", () => {
|
|
|
295
295
|
expect(notifications.send).toHaveBeenCalledTimes(1)
|
|
296
296
|
expect(notifications.send).toHaveBeenLastCalledWith(
|
|
297
297
|
expect.objectContaining({
|
|
298
|
-
|
|
298
|
+
id: expect.stringContaining("retention_14day"),
|
|
299
299
|
}),
|
|
300
300
|
)
|
|
301
301
|
|
|
@@ -312,7 +312,7 @@ describe("checkDataRetentionNotifications", () => {
|
|
|
312
312
|
expect(notifications.send).toHaveBeenCalledTimes(1)
|
|
313
313
|
expect(notifications.send).toHaveBeenLastCalledWith(
|
|
314
314
|
expect.objectContaining({
|
|
315
|
-
|
|
315
|
+
id: expect.stringContaining("retention_7day"),
|
|
316
316
|
}),
|
|
317
317
|
)
|
|
318
318
|
|
|
@@ -329,7 +329,7 @@ describe("checkDataRetentionNotifications", () => {
|
|
|
329
329
|
expect(notifications.send).toHaveBeenCalledTimes(1)
|
|
330
330
|
expect(notifications.send).toHaveBeenLastCalledWith(
|
|
331
331
|
expect.objectContaining({
|
|
332
|
-
|
|
332
|
+
id: expect.stringContaining("retention_deletion"),
|
|
333
333
|
}),
|
|
334
334
|
)
|
|
335
335
|
|
|
@@ -13,7 +13,7 @@ import type { TreeEntry } from "../../cache/tree"
|
|
|
13
13
|
|
|
14
14
|
vi.mock("ioredis")
|
|
15
15
|
vi.mock("../../config.ts")
|
|
16
|
-
vi.mock("../../libs/redis", () => ({
|
|
16
|
+
vi.mock("../../libs/redis", () => ({ getRedis: () => ({}) }))
|
|
17
17
|
vi.mock("../../libs/presign", () => ({
|
|
18
18
|
getPresignedUrl: vi.fn().mockResolvedValue(
|
|
19
19
|
"https://s3.amazonaws.com/bucket/key?presigned=true",
|
|
@@ -74,6 +74,8 @@ describe("datalad files", () => {
|
|
|
74
74
|
b: "",
|
|
75
75
|
p: false,
|
|
76
76
|
d: true,
|
|
77
|
+
a: false,
|
|
78
|
+
l: false,
|
|
77
79
|
}
|
|
78
80
|
const result = await entryToDatasetFile(entry, "ds000001")
|
|
79
81
|
expect(result).toEqual({
|
|
@@ -82,6 +84,8 @@ describe("datalad files", () => {
|
|
|
82
84
|
directory: true,
|
|
83
85
|
size: 0,
|
|
84
86
|
urls: [],
|
|
87
|
+
annexed: false,
|
|
88
|
+
symlink: false,
|
|
85
89
|
})
|
|
86
90
|
})
|
|
87
91
|
it("returns a presigned URL for private files", async () => {
|
|
@@ -94,6 +98,8 @@ describe("datalad files", () => {
|
|
|
94
98
|
b: "private-bucket",
|
|
95
99
|
p: true,
|
|
96
100
|
d: false,
|
|
101
|
+
a: false,
|
|
102
|
+
l: false,
|
|
97
103
|
}
|
|
98
104
|
const result = await entryToDatasetFile(entry, "ds000001")
|
|
99
105
|
expect(result).toEqual({
|
|
@@ -102,6 +108,8 @@ describe("datalad files", () => {
|
|
|
102
108
|
directory: false,
|
|
103
109
|
size: 12345,
|
|
104
110
|
urls: ["https://s3.amazonaws.com/bucket/key?presigned=true"],
|
|
111
|
+
annexed: false,
|
|
112
|
+
symlink: false,
|
|
105
113
|
})
|
|
106
114
|
})
|
|
107
115
|
it("returns a public S3 URL for public files with S3 keys", async () => {
|
|
@@ -114,6 +122,8 @@ describe("datalad files", () => {
|
|
|
114
122
|
b: "",
|
|
115
123
|
p: false,
|
|
116
124
|
d: false,
|
|
125
|
+
a: false,
|
|
126
|
+
l: false,
|
|
117
127
|
}
|
|
118
128
|
const result = await entryToDatasetFile(entry, "ds000001")
|
|
119
129
|
expect(result).toEqual({
|
|
@@ -122,6 +132,8 @@ describe("datalad files", () => {
|
|
|
122
132
|
directory: false,
|
|
123
133
|
size: 500,
|
|
124
134
|
urls: ["https://s3.amazonaws.com/bucket/key?versionId=abc123"],
|
|
135
|
+
annexed: false,
|
|
136
|
+
symlink: false,
|
|
125
137
|
})
|
|
126
138
|
})
|
|
127
139
|
it("falls back to server object URL when no S3 key/version", async () => {
|
|
@@ -135,6 +147,8 @@ describe("datalad files", () => {
|
|
|
135
147
|
b: "",
|
|
136
148
|
p: false,
|
|
137
149
|
d: false,
|
|
150
|
+
a: false,
|
|
151
|
+
l: false,
|
|
138
152
|
}
|
|
139
153
|
const result = await entryToDatasetFile(entry, "ds000001")
|
|
140
154
|
expect(result).toEqual({
|
|
@@ -145,6 +159,8 @@ describe("datalad files", () => {
|
|
|
145
159
|
urls: [
|
|
146
160
|
"https://openneuro.org/crn/datasets/ds000001/objects/jkl012?filename=sub-01_T1w.nii.gz",
|
|
147
161
|
],
|
|
162
|
+
annexed: false,
|
|
163
|
+
symlink: false,
|
|
148
164
|
})
|
|
149
165
|
})
|
|
150
166
|
})
|
|
@@ -192,6 +208,8 @@ describe("datalad files", () => {
|
|
|
192
208
|
directory: true,
|
|
193
209
|
size: 0,
|
|
194
210
|
urls: [],
|
|
211
|
+
annexed: false,
|
|
212
|
+
symlink: false,
|
|
195
213
|
}
|
|
196
214
|
expect(workerFileToEntry(file, false)).toEqual({
|
|
197
215
|
n: "sub-01",
|
|
@@ -202,6 +220,8 @@ describe("datalad files", () => {
|
|
|
202
220
|
b: "",
|
|
203
221
|
p: false,
|
|
204
222
|
d: true,
|
|
223
|
+
a: false,
|
|
224
|
+
l: false,
|
|
205
225
|
})
|
|
206
226
|
})
|
|
207
227
|
it("converts a public file with S3 URL", () => {
|
|
@@ -213,6 +233,8 @@ describe("datalad files", () => {
|
|
|
213
233
|
urls: [
|
|
214
234
|
"https://s3.amazonaws.com/openneuro.org/datasets/ds000001/README?versionId=ver2",
|
|
215
235
|
],
|
|
236
|
+
annexed: false,
|
|
237
|
+
symlink: false,
|
|
216
238
|
}
|
|
217
239
|
const result = workerFileToEntry(file, false)
|
|
218
240
|
expect(result).toEqual({
|
|
@@ -224,6 +246,8 @@ describe("datalad files", () => {
|
|
|
224
246
|
b: "openneuro.org",
|
|
225
247
|
p: false,
|
|
226
248
|
d: false,
|
|
249
|
+
a: false,
|
|
250
|
+
l: false,
|
|
227
251
|
})
|
|
228
252
|
})
|
|
229
253
|
it("stores empty bucket when it matches the default bucket", () => {
|
|
@@ -237,6 +261,8 @@ describe("datalad files", () => {
|
|
|
237
261
|
urls: [
|
|
238
262
|
"https://s3.amazonaws.com/openneuro.org/datasets/ds000001/README?versionId=ver2",
|
|
239
263
|
],
|
|
264
|
+
symlink: false,
|
|
265
|
+
annexed: false,
|
|
240
266
|
}
|
|
241
267
|
const result = workerFileToEntry(file, false)
|
|
242
268
|
expect(result.b).toBe("")
|
|
@@ -251,6 +277,8 @@ describe("datalad files", () => {
|
|
|
251
277
|
urls: [
|
|
252
278
|
"https://s3.amazonaws.com/private-bucket/data.nii.gz?versionId=v1",
|
|
253
279
|
],
|
|
280
|
+
symlink: false,
|
|
281
|
+
annexed: false,
|
|
254
282
|
}
|
|
255
283
|
expect(workerFileToEntry(file, true).p).toBe(true)
|
|
256
284
|
expect(workerFileToEntry(file, false).p).toBe(false)
|
|
@@ -262,6 +290,8 @@ describe("datalad files", () => {
|
|
|
262
290
|
directory: false,
|
|
263
291
|
size: 1000,
|
|
264
292
|
urls: [],
|
|
293
|
+
symlink: false,
|
|
294
|
+
annexed: false,
|
|
265
295
|
}
|
|
266
296
|
const result = workerFileToEntry(file, false)
|
|
267
297
|
expect(result.k).toBe("")
|
|
@@ -10,12 +10,12 @@ import { connect } from "mongoose"
|
|
|
10
10
|
// Mock requests to Datalad service
|
|
11
11
|
vi.mock("superagent")
|
|
12
12
|
vi.mock("../../libs/redis.js", () => ({
|
|
13
|
-
|
|
13
|
+
getRedis: () => ({
|
|
14
14
|
del: vi.fn(),
|
|
15
|
-
},
|
|
16
|
-
|
|
15
|
+
}),
|
|
16
|
+
getRedlock: () => ({
|
|
17
17
|
lock: vi.fn().mockImplementation(() => ({ unlock: vi.fn() })),
|
|
18
|
-
},
|
|
18
|
+
}),
|
|
19
19
|
}))
|
|
20
20
|
// Mock draft files calls
|
|
21
21
|
vi.mock("../draft.ts", () => ({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Sentry from "@sentry/node"
|
|
2
2
|
import CacheItem, { CacheType } from "../cache/item"
|
|
3
|
-
import {
|
|
3
|
+
import { getRedis } from "../libs/redis"
|
|
4
4
|
import {
|
|
5
5
|
type DatasetOrSnapshot,
|
|
6
6
|
datasetOrSnapshot,
|
|
@@ -26,7 +26,7 @@ export const contributors = async (
|
|
|
26
26
|
if (!datasetId) return []
|
|
27
27
|
|
|
28
28
|
const revisionShort = revision ? revision.substring(0, 7) : "HEAD"
|
|
29
|
-
const dataciteCache = new CacheItem(
|
|
29
|
+
const dataciteCache = new CacheItem(getRedis(), CacheType.dataciteYml, [
|
|
30
30
|
datasetId,
|
|
31
31
|
revisionShort,
|
|
32
32
|
])
|
|
@@ -18,7 +18,7 @@ const DAY = 24 * 60 * 60 * 1000
|
|
|
18
18
|
*/
|
|
19
19
|
async function notifyWriteUsers(
|
|
20
20
|
datasetId: string,
|
|
21
|
-
makeEmail: (user: {
|
|
21
|
+
makeEmail: (user: { id: string; email: string; name: string }) => object,
|
|
22
22
|
) {
|
|
23
23
|
const permissions = await Permission.find({
|
|
24
24
|
datasetId,
|
|
@@ -86,7 +86,7 @@ export async function checkDataRetentionNotifications(
|
|
|
86
86
|
!lastSnapshot && age >= DAY && age < 14 * DAY && !record.notifiedNoSnapshot
|
|
87
87
|
) {
|
|
88
88
|
await notifyWriteUsers(datasetId, (user) => ({
|
|
89
|
-
|
|
89
|
+
id: `${datasetId}_${user.id}_no_snapshot_reminder`,
|
|
90
90
|
type: "email",
|
|
91
91
|
email: {
|
|
92
92
|
to: user.email,
|
|
@@ -107,7 +107,7 @@ export async function checkDataRetentionNotifications(
|
|
|
107
107
|
// Retention warnings sent in order from 14 days, 7 days, and 0 days.
|
|
108
108
|
if (age >= 14 * DAY && !record.notifiedAt14Days) {
|
|
109
109
|
await notifyWriteUsers(datasetId, (user) => ({
|
|
110
|
-
|
|
110
|
+
id: `${datasetId}_${user.id}_retention_14day`,
|
|
111
111
|
type: "email",
|
|
112
112
|
email: {
|
|
113
113
|
to: user.email,
|
|
@@ -129,7 +129,7 @@ export async function checkDataRetentionNotifications(
|
|
|
129
129
|
now.getTime() - new Date(record.notifiedAt14Days).getTime() >= 7 * DAY
|
|
130
130
|
) {
|
|
131
131
|
await notifyWriteUsers(datasetId, (user) => ({
|
|
132
|
-
|
|
132
|
+
id: `${datasetId}_${user.id}_retention_7day`,
|
|
133
133
|
type: "email",
|
|
134
134
|
email: {
|
|
135
135
|
to: user.email,
|
|
@@ -151,7 +151,7 @@ export async function checkDataRetentionNotifications(
|
|
|
151
151
|
now.getTime() - new Date(record.notifiedAt7Days).getTime() >= 7 * DAY
|
|
152
152
|
) {
|
|
153
153
|
await notifyWriteUsers(datasetId, (user) => ({
|
|
154
|
-
|
|
154
|
+
id: `${datasetId}_${user.id}_retention_deletion`,
|
|
155
155
|
type: "email",
|
|
156
156
|
email: {
|
|
157
157
|
to: user.email,
|
package/src/datalad/dataset.ts
CHANGED
|
@@ -12,12 +12,12 @@ import type * as Mongoose from "mongoose"
|
|
|
12
12
|
import config from "../config"
|
|
13
13
|
import * as subscriptions from "../handlers/subscriptions"
|
|
14
14
|
import { generateDataladCookie } from "../libs/authentication/jwt"
|
|
15
|
-
import {
|
|
15
|
+
import { getRedis } from "../libs/redis"
|
|
16
16
|
import CacheItem, { CacheType } from "../cache/item"
|
|
17
17
|
import { getDraftRevision, updateDatasetRevision } from "./draft"
|
|
18
18
|
import { encodeFilePath, filesUrl, fileUrl, getFileName } from "./files"
|
|
19
19
|
import { getAccessionNumber } from "../libs/dataset"
|
|
20
|
-
import Dataset from "../models/dataset"
|
|
20
|
+
import Dataset, { type DatasetDocument } from "../models/dataset"
|
|
21
21
|
import Metadata from "../models/metadata"
|
|
22
22
|
import Permission from "../models/permission"
|
|
23
23
|
import Star from "../models/stars"
|
|
@@ -80,12 +80,16 @@ export const createDataset = async (
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
type DatasetWithRevision = Mongoose.FlattenMaps<DatasetDocument> & {
|
|
84
|
+
revision: Promise<string>
|
|
85
|
+
}
|
|
86
|
+
|
|
83
87
|
/**
|
|
84
88
|
* Fetch dataset document and related fields
|
|
85
89
|
*/
|
|
86
|
-
export const getDataset = async (id) => {
|
|
90
|
+
export const getDataset = async (id): Promise<DatasetWithRevision | null> => {
|
|
87
91
|
const dataset = await Dataset.findOne({ id }).lean()
|
|
88
|
-
return {
|
|
92
|
+
return dataset && {
|
|
89
93
|
...dataset,
|
|
90
94
|
revision: getDraftRevision(id),
|
|
91
95
|
}
|
|
@@ -114,7 +118,7 @@ export const deleteDataset = async (datasetId, user) => {
|
|
|
114
118
|
export const cacheDatasetConnection = (options) => (connectionArguments) => {
|
|
115
119
|
const connection = datasetsConnection(options)
|
|
116
120
|
const cache = new CacheItem(
|
|
117
|
-
|
|
121
|
+
getRedis(),
|
|
118
122
|
CacheType.datasetsConnection,
|
|
119
123
|
[objectHash(options)],
|
|
120
124
|
60,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import config from "../config"
|
|
5
5
|
import request from "superagent"
|
|
6
|
-
import {
|
|
6
|
+
import { getRedis } from "../libs/redis"
|
|
7
7
|
import { commitFiles } from "./dataset"
|
|
8
8
|
import { fileUrl } from "./files"
|
|
9
9
|
import { generateDataladCookie } from "../libs/authentication/jwt"
|
|
@@ -135,7 +135,7 @@ export const description = async (obj) => {
|
|
|
135
135
|
Name: datasetId,
|
|
136
136
|
BIDSVersion: "1.8.0",
|
|
137
137
|
}
|
|
138
|
-
const cache = new CacheItem(
|
|
138
|
+
const cache = new CacheItem(getRedis(), CacheType.datasetDescription, [
|
|
139
139
|
datasetId,
|
|
140
140
|
revision.substring(0, 7),
|
|
141
141
|
])
|
package/src/datalad/draft.ts
CHANGED
|
@@ -5,7 +5,7 @@ import request from "superagent"
|
|
|
5
5
|
import Dataset from "../models/dataset"
|
|
6
6
|
import { getDatasetWorker } from "../libs/datalad-service"
|
|
7
7
|
import CacheItem, { CacheType } from "../cache/item"
|
|
8
|
-
import {
|
|
8
|
+
import { getRedis } from "../libs/redis"
|
|
9
9
|
|
|
10
10
|
// Draft info resolver
|
|
11
11
|
type DraftInfo = {
|
|
@@ -13,7 +13,7 @@ type DraftInfo = {
|
|
|
13
13
|
hexsha: string // Duplicate of ref for backwards compatibility
|
|
14
14
|
tree: string
|
|
15
15
|
message: string
|
|
16
|
-
modified:
|
|
16
|
+
modified: string
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export const getDraftRevision = async (datasetId): Promise<string> => {
|
|
@@ -22,7 +22,12 @@ export const getDraftRevision = async (datasetId): Promise<string> => {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export const getDraftInfo = async (datasetId) => {
|
|
25
|
-
const cache = new CacheItem(
|
|
25
|
+
const cache = new CacheItem(
|
|
26
|
+
getRedis(),
|
|
27
|
+
CacheType.draftRevision,
|
|
28
|
+
[datasetId],
|
|
29
|
+
10,
|
|
30
|
+
)
|
|
26
31
|
return cache.get(async (_doNotCache): Promise<DraftInfo> => {
|
|
27
32
|
const draftUrl = `http://${
|
|
28
33
|
getDatasetWorker(
|