Skip to content

Commit ae8683a

Browse files
authored
Merge pull request #3890 from effigies/refactor/pothos-schema-v2
rf(graphql): Generate GraphQL schema with Pothos
2 parents 95f239b + fbc95c0 commit ae8683a

71 files changed

Lines changed: 2670 additions & 1327 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pnp.cjs

Lines changed: 81 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/openneuro-server/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
"@graphql-tools/schema": "^10.0.0",
2424
"@keyv/redis": "^4.5.0",
2525
"@openneuro/search": "^5.0.1",
26+
"@pothos/core": "^4.12.0",
27+
"@pothos/plugin-directives": "^4.3.0",
28+
"@pothos/plugin-simple-objects": "^4.1.3",
2629
"@sentry/node": "^10.37.0",
2730
"@sentry/profiling-node": "^10.37.0",
2831
"base64url": "^3.0.0",
@@ -34,9 +37,9 @@
3437
"express": "5",
3538
"graphql": "16.8.1",
3639
"graphql-bigint": "^1.0.0",
37-
"graphql-compose": "9.0.10",
3840
"graphql-iso-date": "^3.6.1",
3941
"graphql-tools": "9.0.0",
42+
"graphql-type-json": "^0.3.2",
4043
"hash-wasm": "^4.12.0",
4144
"immutable": "^4.3.8",
4245
"ioredis": "^5.6.1",
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Script to dump the GraphQL schema to a file.
3+
* This was written during the transition to Pothos-generated schema,
4+
* for comparison with the hand-written schema.
5+
*
6+
* It is written as a test because the schema's imports are entangled
7+
* with the rest of the server code, and this allows us to mock out the dependencies.
8+
*/
9+
import { vi } from "vitest"
10+
11+
vi.mock("ioredis", () => {
12+
const RedisMock = vi.fn()
13+
RedisMock.prototype.on = vi.fn()
14+
RedisMock.prototype.connect = vi.fn()
15+
return { default: RedisMock }
16+
})
17+
vi.mock("../src/elasticsearch/elastic-client", () => ({
18+
elasticClient: {},
19+
}))
20+
vi.mock("../src/config", () => ({
21+
default: {
22+
url: "http://localhost",
23+
auth: { jwt: { secret: "test-secret-for-schema-dump" } },
24+
datalad: { uri: "http://localhost" },
25+
mongo: { url: "mongodb://localhost", dbName: "test" },
26+
redis: {},
27+
},
28+
}))
29+
30+
import { writeFileSync } from "fs"
31+
import { dirname, join } from "path"
32+
import { fileURLToPath } from "url"
33+
import { lexicographicSortSchema, printSchema } from "graphql/index.js"
34+
import schema from "../src/graphql/schema"
35+
36+
it("dumps Pothos schema to SDL", () => {
37+
const sdl = printSchema(lexicographicSortSchema(schema))
38+
const dir = dirname(fileURLToPath(import.meta.url))
39+
writeFileSync(join(dir, "..", "schema.graphql"), sdl + "\n")
40+
})

packages/openneuro-server/src/app.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ export { Express } from "express-serve-static-core"
2828

2929
interface OpenNeuroRequestContext {
3030
user: string
31-
isSuperUser: boolean
3231
userInfo: {
3332
id: string
3433
exp: string
@@ -109,7 +108,6 @@ export async function expressApolloSetup() {
109108
if (req.isAuthenticated()) {
110109
return {
111110
user: req.user.id,
112-
isSuperUser: req.user.admin,
113111
userInfo: req.user,
114112
}
115113
}

packages/openneuro-server/src/datalad/draft.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type DraftInfo = {
1313
hexsha: string // Duplicate of ref for backwards compatibility
1414
tree: string
1515
message: string
16-
modified: Date
16+
modified: string
1717
}
1818

1919
export const getDraftRevision = async (datasetId): Promise<string> => {

packages/openneuro-server/src/graphql/__tests__/comment.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { connect } from "mongoose"
33
import { deleteComment, flatten } from "../resolvers/comment"
44
import { MongoMemoryServer } from "mongodb-memory-server"
55
import Comment from "../../models/comment"
6+
import type { GraphQLContext } from "../builder"
67

78
vi.mock("ioredis")
89

@@ -12,11 +13,11 @@ describe("comment resolver helpers", () => {
1213
const adminUser = {
1314
user: "1234",
1415
userInfo: { admin: true },
15-
}
16+
} as GraphQLContext
1617
const nonAdminUser = {
1718
user: "5678",
1819
userInfo: { admin: false },
19-
}
20+
} as GraphQLContext
2021
let mongod
2122
beforeAll(async () => {
2223
// Setup MongoDB with mongodb-memory-server
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { vi } from "vitest"
2+
3+
vi.mock("ioredis", () => {
4+
const RedisMock = vi.fn()
5+
RedisMock.prototype.on = vi.fn()
6+
RedisMock.prototype.connect = vi.fn()
7+
return { default: RedisMock }
8+
})
9+
vi.mock("../../elasticsearch/elastic-client", () => ({
10+
elasticClient: {},
11+
}))
12+
vi.mock("../../config", () => ({
13+
default: {
14+
url: "http://localhost",
15+
auth: { jwt: { secret: "test-secret-for-schema-smoke" } },
16+
datalad: { uri: "http://localhost" },
17+
mongo: { url: "mongodb://localhost", dbName: "test" },
18+
redis: {},
19+
},
20+
}))
21+
22+
import schema from "../schema"
23+
24+
it("builds the schema without error", () => {
25+
expect(schema).toBeDefined()
26+
expect(schema.getQueryType()).toBeDefined()
27+
expect(schema.getMutationType()).toBeDefined()
28+
})
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import SchemaBuilder from "@pothos/core"
2+
import SimpleObjectsPlugin from "@pothos/plugin-simple-objects"
3+
import DirectivesPlugin from "@pothos/plugin-directives"
4+
5+
export interface UserInfo {
6+
id: string
7+
userId: string
8+
admin: boolean
9+
username?: string
10+
provider?: string
11+
providerId?: string
12+
blocked?: boolean
13+
orcidConsent?: boolean | null
14+
reviewer?: boolean
15+
exp?: string
16+
scopes?: string[]
17+
indexer?: boolean
18+
}
19+
20+
export interface GraphQLContext {
21+
user: string
22+
userInfo: UserInfo
23+
}
24+
25+
export const builder = new SchemaBuilder<{
26+
Context: GraphQLContext
27+
Scalars: {
28+
ID: { Input: string; Output: string }
29+
Date: { Input: string; Output: Date }
30+
DateTime: { Input: string; Output: Date }
31+
BigInt: { Input: number; Output: number }
32+
JSON: { Input: unknown; Output: unknown }
33+
}
34+
DefaultFieldNullability: true
35+
}>({
36+
plugins: [SimpleObjectsPlugin, DirectivesPlugin],
37+
notStrict:
38+
"Pothos may not work correctly when strict mode is not enabled in tsconfig.json",
39+
directives: {
40+
useGraphQLToolsUnorderedDirectives: true,
41+
},
42+
})

packages/openneuro-server/src/graphql/resolvers/__tests__/dataset.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ describe("dataset resolvers", () => {
2121
const { id: dsId } = await ds.createDataset(
2222
null,
2323
{ affirmedDefaced: true, affirmedConsent: false },
24-
{ user: "123456", userInfo: {} },
24+
{
25+
user: "123456",
26+
userInfo: { id: "123456", userId: "123456", admin: false },
27+
},
2528
)
2629
expect(dsId).toEqual(expect.stringMatching(/^ds[0-9]{6}$/))
2730
})
@@ -170,6 +173,8 @@ describe("dataset resolvers", () => {
170173
user: "a_user_id",
171174
userInfo: {
172175
// bypass permission checks
176+
id: "a_user_id",
177+
userId: "a_user_id",
173178
admin: true,
174179
},
175180
},

packages/openneuro-server/src/graphql/resolvers/__tests__/importRemoteDataset.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { vi } from "vitest"
22
import { allowedImportUrl, importRemoteDataset } from "../importRemoteDataset"
3+
import type { GraphQLContext } from "../../builder"
34

45
vi.mock("ioredis")
56
vi.mock("../../../config")
@@ -10,7 +11,7 @@ describe("importRemoteDataset mutation", () => {
1011
await importRemoteDataset(
1112
{},
1213
{ datasetId: "ds000000", url: "" },
13-
{ user: "1234", userInfo: { admin: true } },
14+
{ user: "1234", userInfo: { admin: true } } as GraphQLContext,
1415
)
1516
})
1617
describe("allowedImportUrl()", () => {

0 commit comments

Comments
 (0)