🍱 graphql schema stitching related changes
This commit is contained in:
@@ -861,6 +861,19 @@ export const resolvers = {
|
||||
}),
|
||||
},
|
||||
|
||||
// Field resolvers for statistics types
|
||||
FileTypeStats: {
|
||||
id: (stats: any) => stats._id || stats.id,
|
||||
},
|
||||
|
||||
PublisherStats: {
|
||||
id: (stats: any) => stats._id || stats.id,
|
||||
},
|
||||
|
||||
IssueStats: {
|
||||
id: (stats: any) => stats._id || stats.id,
|
||||
},
|
||||
|
||||
UserPreferences: {
|
||||
id: (prefs: any) => prefs._id.toString(),
|
||||
fieldPreferences: (prefs: any) => {
|
||||
|
||||
@@ -133,6 +133,9 @@ export const typeDefs = gql`
|
||||
# Raw sourced metadata (for transparency)
|
||||
sourcedMetadata: SourcedMetadata
|
||||
|
||||
# Inferred metadata (from filename parsing)
|
||||
inferredMetadata: InferredMetadata
|
||||
|
||||
# File information
|
||||
rawFileDetails: RawFileDetails
|
||||
|
||||
@@ -387,6 +390,18 @@ export const typeDefs = gql`
|
||||
subtitle: String
|
||||
}
|
||||
|
||||
# Inferred metadata output type
|
||||
type InferredMetadata {
|
||||
issue: Issue
|
||||
}
|
||||
|
||||
type Issue {
|
||||
name: String
|
||||
number: Int
|
||||
year: String
|
||||
subtitle: String
|
||||
}
|
||||
|
||||
input RawFileDetailsInput {
|
||||
name: String!
|
||||
filePath: String!
|
||||
|
||||
318
package-lock.json
generated
318
package-lock.json
generated
@@ -11,6 +11,11 @@
|
||||
"@apollo/server": "^4.12.2",
|
||||
"@bluelovers/fast-glob": "https://github.com/rishighan/fast-glob-v2-api.git",
|
||||
"@elastic/elasticsearch": "^8.13.1",
|
||||
"@graphql-tools/delegate": "^12.0.8",
|
||||
"@graphql-tools/schema": "^10.0.31",
|
||||
"@graphql-tools/stitch": "^10.1.12",
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"@graphql-tools/wrap": "^11.1.8",
|
||||
"@jorgeferrero/stream-to-buffer": "^2.0.6",
|
||||
"@npcz/magic": "^1.3.14",
|
||||
"@root/walk": "^1.1.0",
|
||||
@@ -56,6 +61,7 @@
|
||||
"sharp": "^0.33.3",
|
||||
"threetwo-ui-typings": "^1.0.14",
|
||||
"through2": "^4.0.2",
|
||||
"undici": "^7.22.0",
|
||||
"unrar": "^0.2.0",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
@@ -189,6 +195,47 @@
|
||||
"graphql": "14.x || 15.x || 16.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@apollo/server/node_modules/@graphql-tools/merge": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.2.tgz",
|
||||
"integrity": "sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/utils": "^9.2.1",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@apollo/server/node_modules/@graphql-tools/schema": {
|
||||
"version": "9.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz",
|
||||
"integrity": "sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/merge": "^8.4.1",
|
||||
"@graphql-tools/utils": "^9.2.1",
|
||||
"tslib": "^2.4.0",
|
||||
"value-or-promise": "^1.0.12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@apollo/server/node_modules/@graphql-tools/utils": {
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz",
|
||||
"integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-typed-document-node/core": "^3.1.1",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@apollo/server/node_modules/lru-cache": {
|
||||
"version": "7.18.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
|
||||
@@ -1735,6 +1782,27 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@elastic/transport/node_modules/@fastify/busboy": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
||||
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@elastic/transport/node_modules/undici": {
|
||||
"version": "5.29.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
||||
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz",
|
||||
@@ -1822,43 +1890,174 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/merge": {
|
||||
"version": "8.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.2.tgz",
|
||||
"integrity": "sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==",
|
||||
"node_modules/@graphql-tools/batch-delegate": {
|
||||
"version": "10.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/batch-delegate/-/batch-delegate-10.0.14.tgz",
|
||||
"integrity": "sha512-jHp3TLbetZus5GGTU1CC/syfb/hrJc1d+HcZwlh4atjrZaWPWgWNAP033899FNnVPZY4w9A+sOwCVFe1wxKL8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/utils": "^9.2.1",
|
||||
"@graphql-tools/delegate": "^12.0.8",
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"@whatwg-node/promise-helpers": "^1.3.2",
|
||||
"dataloader": "^2.2.3",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/batch-execute": {
|
||||
"version": "10.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/batch-execute/-/batch-execute-10.0.5.tgz",
|
||||
"integrity": "sha512-dL13tXkfGvAzLq2XfzTKAy9logIcltKYRuPketxdh3Ok3U6PN1HKMCHfrE9cmtAsxD96/8Hlghz5AtM+LRv/ig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"@whatwg-node/promise-helpers": "^1.3.2",
|
||||
"dataloader": "^2.2.3",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/delegate": {
|
||||
"version": "12.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/delegate/-/delegate-12.0.8.tgz",
|
||||
"integrity": "sha512-yltGepWaJ9KsBY3QREJrZUKadhaiT4mO4ZO42hF/vfD2fIIOKZjn99qCSZBJ0YpVbLctPrgWrgDs3WgAl13fsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/batch-execute": "^10.0.5",
|
||||
"@graphql-tools/executor": "^1.4.13",
|
||||
"@graphql-tools/schema": "^10.0.29",
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"@repeaterjs/repeater": "^3.0.6",
|
||||
"@whatwg-node/promise-helpers": "^1.3.2",
|
||||
"dataloader": "^2.2.3",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/executor": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/executor/-/executor-1.5.1.tgz",
|
||||
"integrity": "sha512-n94Qcu875Mji9GQ52n5UbgOTxlgvFJicBPYD+FRks9HKIQpdNPjkkrKZUYNG51XKa+bf03rxNflm4+wXhoHHrA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@repeaterjs/repeater": "^3.0.4",
|
||||
"@whatwg-node/disposablestack": "^0.0.6",
|
||||
"@whatwg-node/promise-helpers": "^1.0.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/merge": {
|
||||
"version": "9.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.1.7.tgz",
|
||||
"integrity": "sha512-Y5E1vTbTabvcXbkakdFUt4zUIzB1fyaEnVmIWN0l0GMed2gdD01TpZWLUm4RNAxpturvolrb24oGLQrBbPLSoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/schema": {
|
||||
"version": "9.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz",
|
||||
"integrity": "sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==",
|
||||
"version": "10.0.31",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.31.tgz",
|
||||
"integrity": "sha512-ZewRgWhXef6weZ0WiP7/MV47HXiuFbFpiDUVLQl6mgXsWSsGELKFxQsyUCBos60Qqy1JEFAIu3Ns6GGYjGkqkQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/merge": "^8.4.1",
|
||||
"@graphql-tools/utils": "^9.2.1",
|
||||
"tslib": "^2.4.0",
|
||||
"value-or-promise": "^1.0.12"
|
||||
"@graphql-tools/merge": "^9.1.7",
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/stitch": {
|
||||
"version": "10.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/stitch/-/stitch-10.1.12.tgz",
|
||||
"integrity": "sha512-dx5dRDVC2OOBXrXgekCmJh22EXGlMrfESMWaNFJKlypP40BK36bPAlUBpMyM1kwx5BQWwjTKUlSghB1Z5j/PgA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/batch-delegate": "^10.0.14",
|
||||
"@graphql-tools/delegate": "^12.0.8",
|
||||
"@graphql-tools/executor": "^1.4.13",
|
||||
"@graphql-tools/merge": "^9.1.5",
|
||||
"@graphql-tools/schema": "^10.0.29",
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"@graphql-tools/wrap": "^11.1.8",
|
||||
"@whatwg-node/promise-helpers": "^1.3.2",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/utils": {
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz",
|
||||
"integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==",
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-11.0.0.tgz",
|
||||
"integrity": "sha512-bM1HeZdXA2C3LSIeLOnH/bcqSgbQgKEDrjxODjqi3y58xai2TkNrtYcQSoWzGbt9VMN1dORGjR7Vem8SPnUFQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-typed-document-node/core": "^3.1.1",
|
||||
"@whatwg-node/promise-helpers": "^1.0.0",
|
||||
"cross-inspect": "1.0.1",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@graphql-tools/wrap": {
|
||||
"version": "11.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-11.1.8.tgz",
|
||||
"integrity": "sha512-VnU7K6IDvj7kM9Viz6oAQNc6lV380u7oOG1hYau5pzHB+h1VrTYg/jHXNtWrXwB88lhCgGHjrQCJJt4wz4QdQQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/delegate": "^12.0.8",
|
||||
"@graphql-tools/schema": "^10.0.29",
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"@whatwg-node/promise-helpers": "^1.3.2",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
@@ -3073,6 +3272,12 @@
|
||||
"@redis/client": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@repeaterjs/repeater": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.6.tgz",
|
||||
"integrity": "sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@root/walk": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@root/walk/-/walk-1.1.0.tgz",
|
||||
@@ -4266,6 +4471,19 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@whatwg-node/disposablestack": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@whatwg-node/disposablestack/-/disposablestack-0.0.6.tgz",
|
||||
"integrity": "sha512-LOtTn+JgJvX8WfBVJtF08TGrdjuFzGJc4mkP8EdDI8ADbvO7kiexYep1o8dwnt0okb0jYclCDXF13xU7Ge4zSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@whatwg-node/promise-helpers": "^1.0.0",
|
||||
"tslib": "^2.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@whatwg-node/promise-helpers": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@whatwg-node/promise-helpers/-/promise-helpers-1.3.2.tgz",
|
||||
@@ -11147,57 +11365,6 @@
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/moleculer-apollo-server/node_modules/@graphql-tools/merge": {
|
||||
"version": "9.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.1.7.tgz",
|
||||
"integrity": "sha512-Y5E1vTbTabvcXbkakdFUt4zUIzB1fyaEnVmIWN0l0GMed2gdD01TpZWLUm4RNAxpturvolrb24oGLQrBbPLSoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/moleculer-apollo-server/node_modules/@graphql-tools/schema": {
|
||||
"version": "10.0.31",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.31.tgz",
|
||||
"integrity": "sha512-ZewRgWhXef6weZ0WiP7/MV47HXiuFbFpiDUVLQl6mgXsWSsGELKFxQsyUCBos60Qqy1JEFAIu3Ns6GGYjGkqkQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-tools/merge": "^9.1.7",
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/moleculer-apollo-server/node_modules/@graphql-tools/utils": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-11.0.0.tgz",
|
||||
"integrity": "sha512-bM1HeZdXA2C3LSIeLOnH/bcqSgbQgKEDrjxODjqi3y58xai2TkNrtYcQSoWzGbt9VMN1dORGjR7Vem8SPnUFQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@graphql-typed-document-node/core": "^3.1.1",
|
||||
"@whatwg-node/promise-helpers": "^1.0.0",
|
||||
"cross-inspect": "1.0.1",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/moleculer-apollo-server/node_modules/body-parser": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
|
||||
@@ -17515,23 +17682,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "5.29.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz",
|
||||
"integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==",
|
||||
"version": "7.22.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz",
|
||||
"integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici/node_modules/@fastify/busboy": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
|
||||
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/unit-compare": {
|
||||
|
||||
@@ -39,10 +39,13 @@
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/server": "^4.12.2",
|
||||
"moleculer-apollo-server": "^0.4.0",
|
||||
"@bluelovers/fast-glob": "https://github.com/rishighan/fast-glob-v2-api.git",
|
||||
"@elastic/elasticsearch": "^8.13.1",
|
||||
"@graphql-tools/delegate": "^12.0.8",
|
||||
"@graphql-tools/schema": "^10.0.31",
|
||||
"@graphql-tools/stitch": "^10.1.12",
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"@graphql-tools/wrap": "^11.1.8",
|
||||
"@jorgeferrero/stream-to-buffer": "^2.0.6",
|
||||
"@npcz/magic": "^1.3.14",
|
||||
"@root/walk": "^1.1.0",
|
||||
@@ -71,6 +74,7 @@
|
||||
"leven": "^3.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mkdirp": "^0.5.5",
|
||||
"moleculer-apollo-server": "^0.4.0",
|
||||
"moleculer-bullmq": "^3.0.0",
|
||||
"moleculer-db": "^0.8.23",
|
||||
"moleculer-db-adapter-mongoose": "^0.9.2",
|
||||
@@ -87,6 +91,7 @@
|
||||
"sharp": "^0.33.3",
|
||||
"threetwo-ui-typings": "^1.0.14",
|
||||
"through2": "^4.0.2",
|
||||
"undici": "^7.22.0",
|
||||
"unrar": "^0.2.0",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
|
||||
@@ -66,81 +66,8 @@ export default class ApiService extends Service {
|
||||
maxAge: 3600,
|
||||
},
|
||||
aliases: {
|
||||
"POST /": async (req: any, res: any) => {
|
||||
try {
|
||||
const { query, variables, operationName } = req.body;
|
||||
const result = await req.$service.broker.call("graphql.query", {
|
||||
query,
|
||||
variables,
|
||||
operationName,
|
||||
});
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.end(JSON.stringify(result));
|
||||
} catch (error: any) {
|
||||
res.statusCode = 500;
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
errors: [{ message: error.message }],
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
"GET /": async (req: any, res: any) => {
|
||||
// Support GraphQL Playground or introspection queries via GET
|
||||
const query = req.$params.query;
|
||||
const variables = req.$params.variables
|
||||
? JSON.parse(req.$params.variables)
|
||||
: undefined;
|
||||
const operationName = req.$params.operationName;
|
||||
|
||||
if (query) {
|
||||
try {
|
||||
const result = await req.$service.broker.call("graphql.query", {
|
||||
query,
|
||||
variables,
|
||||
operationName,
|
||||
});
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.end(JSON.stringify(result));
|
||||
} catch (error: any) {
|
||||
res.statusCode = 500;
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
errors: [{ message: error.message }],
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Return GraphQL Playground HTML
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.end(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>GraphQL Playground</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/graphql-playground-react/build/static/css/index.css" />
|
||||
<link rel="shortcut icon" href="https://cdn.jsdelivr.net/npm/graphql-playground-react/build/favicon.png" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/graphql-playground-react/build/static/js/middleware.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
window.addEventListener('load', function (event) {
|
||||
GraphQLPlayground.init(document.getElementById('root'), {
|
||||
endpoint: '/graphql',
|
||||
settings: {
|
||||
'request.credentials': 'same-origin',
|
||||
},
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
}
|
||||
},
|
||||
"POST /": "graphql.graphql",
|
||||
"GET /": "graphql.graphql",
|
||||
},
|
||||
mappingPolicy: "restrict",
|
||||
bodyParsers: {
|
||||
|
||||
@@ -1,213 +1,278 @@
|
||||
import { Service, ServiceBroker } from "moleculer";
|
||||
import { ApolloServer } from "@apollo/server";
|
||||
import { ServiceBroker, Context } from "moleculer";
|
||||
import { graphql, GraphQLSchema, parse, validate, execute } from "graphql";
|
||||
import { makeExecutableSchema } from "@graphql-tools/schema";
|
||||
import { stitchSchemas } from "@graphql-tools/stitch";
|
||||
import { wrapSchema } from "@graphql-tools/wrap";
|
||||
import { print, getIntrospectionQuery, buildClientSchema, IntrospectionQuery } from "graphql";
|
||||
import { fetch } from "undici";
|
||||
import { typeDefs } from "../models/graphql/typedef";
|
||||
import { resolvers } from "../models/graphql/resolvers";
|
||||
|
||||
/**
|
||||
* Fetch remote GraphQL schema via introspection
|
||||
*/
|
||||
async function fetchRemoteSchema(url: string) {
|
||||
const introspectionQuery = getIntrospectionQuery();
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ query: introspectionQuery }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to introspect remote schema: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json() as { data?: IntrospectionQuery; errors?: any[] };
|
||||
|
||||
if (result.errors) {
|
||||
throw new Error(`Introspection errors: ${JSON.stringify(result.errors)}`);
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
throw new Error("No data returned from introspection query");
|
||||
}
|
||||
|
||||
return buildClientSchema(result.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create executor for remote GraphQL endpoint
|
||||
*/
|
||||
function createRemoteExecutor(url: string) {
|
||||
return async ({ document, variables }: any) => {
|
||||
const query = print(document);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ query, variables }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Remote GraphQL request failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error("Error executing remote GraphQL query:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* GraphQL Service
|
||||
* Provides a GraphQL API for canonical metadata queries and mutations
|
||||
* Integrates Apollo Server with Moleculer
|
||||
* Standalone service that exposes a graphql action for moleculer-web
|
||||
* Stitches remote metadata-graphql schema from port 3080
|
||||
*/
|
||||
export default class GraphQLService extends Service {
|
||||
private apolloServer?: ApolloServer;
|
||||
export default {
|
||||
name: "graphql",
|
||||
|
||||
public constructor(broker: ServiceBroker) {
|
||||
super(broker);
|
||||
settings: {
|
||||
// Remote metadata GraphQL endpoint
|
||||
metadataGraphqlUrl: process.env.METADATA_GRAPHQL_URL || "http://localhost:3080/metadata-graphql",
|
||||
},
|
||||
|
||||
this.parseServiceSchema({
|
||||
name: "graphql",
|
||||
|
||||
settings: {
|
||||
// GraphQL endpoint path
|
||||
path: "/graphql",
|
||||
actions: {
|
||||
/**
|
||||
* Execute GraphQL queries and mutations
|
||||
* This action is called by moleculer-web from the /graphql route
|
||||
*/
|
||||
graphql: {
|
||||
params: {
|
||||
query: { type: "string" },
|
||||
variables: { type: "object", optional: true },
|
||||
operationName: { type: "string", optional: true },
|
||||
},
|
||||
async handler(ctx: Context<{ query: string; variables?: any; operationName?: string }>) {
|
||||
try {
|
||||
const { query, variables, operationName } = ctx.params;
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* Execute a GraphQL query
|
||||
*/
|
||||
query: {
|
||||
params: {
|
||||
query: "string",
|
||||
variables: { type: "object", optional: true },
|
||||
operationName: { type: "string", optional: true },
|
||||
},
|
||||
async handler(ctx: any) {
|
||||
try {
|
||||
if (!this.apolloServer) {
|
||||
throw new Error("Apollo Server not initialized");
|
||||
}
|
||||
|
||||
const { query, variables, operationName } = ctx.params;
|
||||
|
||||
const response = await this.apolloServer.executeOperation(
|
||||
{
|
||||
query,
|
||||
variables,
|
||||
operationName,
|
||||
},
|
||||
{
|
||||
contextValue: {
|
||||
broker: this.broker,
|
||||
ctx,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (response.body.kind === "single") {
|
||||
return response.body.singleResult;
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
this.logger.error("GraphQL query error:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Get GraphQL schema
|
||||
*/
|
||||
getSchema: {
|
||||
async handler() {
|
||||
return {
|
||||
typeDefs: typeDefs.loc?.source.body || "",
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Initialize Apollo Server
|
||||
*/
|
||||
async initApolloServer() {
|
||||
this.logger.info("Initializing Apollo Server...");
|
||||
|
||||
this.apolloServer = new ApolloServer({
|
||||
typeDefs,
|
||||
resolvers,
|
||||
introspection: true, // Enable GraphQL Playground in development
|
||||
formatError: (error) => {
|
||||
this.logger.error("GraphQL Error:", error);
|
||||
return {
|
||||
message: error.message,
|
||||
locations: error.locations,
|
||||
path: error.path,
|
||||
extensions: {
|
||||
code: error.extensions?.code,
|
||||
},
|
||||
};
|
||||
// Execute the GraphQL query
|
||||
const result = await graphql({
|
||||
schema: this.schema,
|
||||
source: query,
|
||||
variableValues: variables,
|
||||
operationName,
|
||||
contextValue: {
|
||||
broker: this.broker,
|
||||
ctx,
|
||||
},
|
||||
});
|
||||
|
||||
await this.apolloServer.start();
|
||||
this.logger.info("Apollo Server started successfully");
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop Apollo Server
|
||||
*/
|
||||
async stopApolloServer() {
|
||||
if (this.apolloServer) {
|
||||
this.logger.info("Stopping Apollo Server...");
|
||||
await this.apolloServer.stop();
|
||||
this.apolloServer = undefined;
|
||||
this.logger.info("Apollo Server stopped");
|
||||
}
|
||||
},
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
this.logger.error("GraphQL execution error:", error);
|
||||
return {
|
||||
errors: [{
|
||||
message: error.message,
|
||||
extensions: {
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
},
|
||||
}],
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
events: {
|
||||
/**
|
||||
* Trigger metadata resolution when new metadata is imported
|
||||
*/
|
||||
"metadata.imported": {
|
||||
async handler(ctx: any) {
|
||||
const { comicId, source } = ctx.params;
|
||||
/**
|
||||
* Get GraphQL schema
|
||||
*/
|
||||
getSchema: {
|
||||
async handler() {
|
||||
return {
|
||||
typeDefs: typeDefs.loc?.source.body || "",
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
events: {
|
||||
/**
|
||||
* Trigger metadata resolution when new metadata is imported
|
||||
*/
|
||||
"metadata.imported": {
|
||||
async handler(ctx: any) {
|
||||
const { comicId, source } = ctx.params;
|
||||
this.logger.info(
|
||||
`Metadata imported for comic ${comicId} from ${source}`
|
||||
);
|
||||
|
||||
// Optionally trigger auto-resolution if enabled
|
||||
try {
|
||||
const UserPreferences = require("../models/userpreferences.model").default;
|
||||
const preferences = await UserPreferences.findOne({
|
||||
userId: "default",
|
||||
});
|
||||
|
||||
if (
|
||||
preferences?.autoMerge?.enabled &&
|
||||
preferences?.autoMerge?.onMetadataUpdate
|
||||
) {
|
||||
this.logger.info(
|
||||
`Metadata imported for comic ${comicId} from ${source}`
|
||||
`Auto-resolving metadata for comic ${comicId}`
|
||||
);
|
||||
|
||||
// Optionally trigger auto-resolution if enabled
|
||||
try {
|
||||
const UserPreferences = require("../models/userpreferences.model").default;
|
||||
const preferences = await UserPreferences.findOne({
|
||||
userId: "default",
|
||||
});
|
||||
|
||||
if (
|
||||
preferences?.autoMerge?.enabled &&
|
||||
preferences?.autoMerge?.onMetadataUpdate
|
||||
) {
|
||||
this.logger.info(
|
||||
`Auto-resolving metadata for comic ${comicId}`
|
||||
);
|
||||
await this.broker.call("graphql.query", {
|
||||
query: `
|
||||
mutation ResolveMetadata($comicId: ID!) {
|
||||
resolveMetadata(comicId: $comicId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { comicId },
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error("Error in auto-resolution:", error);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger metadata resolution when comic is imported
|
||||
*/
|
||||
"comic.imported": {
|
||||
async handler(ctx: any) {
|
||||
const { comicId } = ctx.params;
|
||||
this.logger.info(`Comic imported: ${comicId}`);
|
||||
|
||||
// Optionally trigger auto-resolution if enabled
|
||||
try {
|
||||
const UserPreferences = require("../models/userpreferences.model").default;
|
||||
const preferences = await UserPreferences.findOne({
|
||||
userId: "default",
|
||||
});
|
||||
|
||||
if (
|
||||
preferences?.autoMerge?.enabled &&
|
||||
preferences?.autoMerge?.onImport
|
||||
) {
|
||||
this.logger.info(
|
||||
`Auto-resolving metadata for newly imported comic ${comicId}`
|
||||
);
|
||||
await this.broker.call("graphql.query", {
|
||||
query: `
|
||||
mutation ResolveMetadata($comicId: ID!) {
|
||||
resolveMetadata(comicId: $comicId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { comicId },
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error("Error in auto-resolution on import:", error);
|
||||
}
|
||||
},
|
||||
},
|
||||
// Call the graphql action
|
||||
await this.broker.call("graphql.graphql", {
|
||||
query: `
|
||||
mutation ResolveMetadata($comicId: ID!) {
|
||||
resolveMetadata(comicId: $comicId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { comicId },
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error("Error in auto-resolution:", error);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
started: async function (this: any) {
|
||||
await this.initApolloServer();
|
||||
},
|
||||
/**
|
||||
* Trigger metadata resolution when comic is imported
|
||||
*/
|
||||
"comic.imported": {
|
||||
async handler(ctx: any) {
|
||||
const { comicId } = ctx.params;
|
||||
this.logger.info(`Comic imported: ${comicId}`);
|
||||
|
||||
stopped: async function (this: any) {
|
||||
await this.stopApolloServer();
|
||||
// Optionally trigger auto-resolution if enabled
|
||||
try {
|
||||
const UserPreferences = require("../models/userpreferences.model").default;
|
||||
const preferences = await UserPreferences.findOne({
|
||||
userId: "default",
|
||||
});
|
||||
|
||||
if (
|
||||
preferences?.autoMerge?.enabled &&
|
||||
preferences?.autoMerge?.onImport
|
||||
) {
|
||||
this.logger.info(
|
||||
`Auto-resolving metadata for newly imported comic ${comicId}`
|
||||
);
|
||||
// Call the graphql action
|
||||
await this.broker.call("graphql.graphql", {
|
||||
query: `
|
||||
mutation ResolveMetadata($comicId: ID!) {
|
||||
resolveMetadata(comicId: $comicId) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { comicId },
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error("Error in auto-resolution on import:", error);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
async started() {
|
||||
this.logger.info("GraphQL service starting...");
|
||||
|
||||
// Create local schema
|
||||
const localSchema = makeExecutableSchema({
|
||||
typeDefs,
|
||||
resolvers,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Try to stitch remote schema if available
|
||||
try {
|
||||
this.logger.info(`Attempting to introspect remote schema at ${this.settings.metadataGraphqlUrl}`);
|
||||
|
||||
// Fetch and build the remote schema
|
||||
const remoteSchema = await fetchRemoteSchema(this.settings.metadataGraphqlUrl);
|
||||
|
||||
this.logger.info("Successfully introspected remote metadata schema");
|
||||
|
||||
// Create executor for remote schema
|
||||
const remoteExecutor = createRemoteExecutor(this.settings.metadataGraphqlUrl);
|
||||
|
||||
// Wrap the remote schema with executor
|
||||
const wrappedRemoteSchema = wrapSchema({
|
||||
schema: remoteSchema,
|
||||
executor: remoteExecutor,
|
||||
});
|
||||
|
||||
// Stitch schemas together
|
||||
this.schema = stitchSchemas({
|
||||
subschemas: [
|
||||
{
|
||||
schema: localSchema,
|
||||
},
|
||||
{
|
||||
schema: wrappedRemoteSchema,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
this.logger.info("Successfully stitched local and remote schemas");
|
||||
} catch (remoteError: any) {
|
||||
this.logger.warn(
|
||||
`Could not connect to remote metadata GraphQL at ${this.settings.metadataGraphqlUrl}: ${remoteError.message}`
|
||||
);
|
||||
this.logger.warn("Continuing with local schema only");
|
||||
|
||||
// Use local schema only
|
||||
this.schema = localSchema;
|
||||
}
|
||||
|
||||
this.logger.info("GraphQL service started successfully");
|
||||
},
|
||||
|
||||
stopped() {
|
||||
this.logger.info("GraphQL service stopped");
|
||||
},
|
||||
};
|
||||
|
||||
@@ -98,10 +98,17 @@ export const getSizeOfDirectory = async (
|
||||
const files = await readdir(directoryPath);
|
||||
const stats = files.map((file) => stat(path.join(directoryPath, file)));
|
||||
|
||||
return (await Promise.all(stats)).reduce(
|
||||
const totalSizeInBytes = (await Promise.all(stats)).reduce(
|
||||
(accumulator, { size }) => accumulator + size,
|
||||
0
|
||||
);
|
||||
|
||||
return {
|
||||
totalSize: totalSizeInBytes,
|
||||
totalSizeInMB: totalSizeInBytes / (1024 * 1024),
|
||||
totalSizeInGB: totalSizeInBytes / (1024 * 1024 * 1024),
|
||||
fileCount: files.length,
|
||||
};
|
||||
};
|
||||
|
||||
export const isValidImageFileExtension = (fileName: string): boolean => {
|
||||
|
||||
Reference in New Issue
Block a user