diff --git a/package-lock.json b/package-lock.json index 283773f..65e9b7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "dependencies": { "@graphql-yoga/node": "^2.1.0", "@pothos/core": "^3.5.1", + "@pothos/plugin-prisma": "^3.4.0", + "@pothos/plugin-relay": "^3.10.0", "@prisma/client": "^3.11.1", "@tailwindcss/forms": "^0.4.0", "@tailwindcss/typography": "^0.5.1", @@ -2504,6 +2506,32 @@ "graphql": ">=15.1.0" } }, + "node_modules/@pothos/plugin-prisma": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@pothos/plugin-prisma/-/plugin-prisma-3.4.0.tgz", + "integrity": "sha512-J0uiTNX9cDXzJ64N6Gd7EVUW90bxSr/NrijY41zc5VZqHv/l1JlWmwKJK5qF5CBULlqNIba/KDLZHvszMNAA8A==", + "dependencies": { + "@prisma/generator-helper": "^3.12.0" + }, + "bin": { + "prisma-pothos-types": "bin/generator.js" + }, + "peerDependencies": { + "@pothos/core": "*", + "@prisma/client": "*", + "graphql": ">=15.1.0", + "typescript": ">4.5.2" + } + }, + "node_modules/@pothos/plugin-relay": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@pothos/plugin-relay/-/plugin-relay-3.10.0.tgz", + "integrity": "sha512-ZNkSYPhDKanMrd8aFD4nEUeMHoeD6wyejBsO1Ik/q8IRHRRPQzLsc/ignxGEl5j+hdu/SsTuhMNszV9Ef0JOJw==", + "peerDependencies": { + "@pothos/core": "*", + "graphql": ">=15.1.0" + } + }, "node_modules/@prisma/client": { "version": "3.11.1", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-3.11.1.tgz", @@ -2524,6 +2552,40 @@ } } }, + "node_modules/@prisma/debug": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-3.12.0.tgz", + "integrity": "sha512-MFNsK8jzao3VX+cEP6+txABa3w8bU4gD0QpqTbGS3v3TW1c7TtHvhsmI7xTx9Cll7biuJdaRd3YwG2Fa/CHVmA==", + "dependencies": { + "@types/debug": "4.1.7", + "ms": "2.1.3", + "strip-ansi": "6.0.1" + } + }, + "node_modules/@prisma/debug/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@prisma/debug/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/@prisma/debug/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@prisma/engines": { "version": "3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9.tgz", @@ -2535,6 +2597,17 @@ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9.tgz", "integrity": "sha512-HkcsDniA4iNb/gi0iuyOJNAM7nD/LwQ0uJm15v360O5dee3TM4lWdSQiTYBMK6FF68ACUItmzSur7oYuUZ2zkQ==" }, + "node_modules/@prisma/generator-helper": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@prisma/generator-helper/-/generator-helper-3.12.0.tgz", + "integrity": "sha512-wfEcSVUX0HH0A/CIrsecV1CLH3McNCLH2A76UL7QpSX1XLoIDo6SxgMwAfXufNGDhPET6NAoWm8mSmDXJopgrw==", + "dependencies": { + "@prisma/debug": "3.12.0", + "@types/cross-spawn": "6.0.2", + "chalk": "4.1.2", + "cross-spawn": "7.0.3" + } + }, "node_modules/@repeaterjs/repeater": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.4.tgz", @@ -3000,6 +3073,14 @@ "@types/responselike": "*" } }, + "node_modules/@types/cross-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.2.tgz", + "integrity": "sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types%2fdebug/-/debug-4.1.7.tgz", @@ -4719,7 +4800,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -7154,7 +7234,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true, "license": "ISC" }, "node_modules/isomorphic-fetch": { @@ -36108,7 +36187,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -37752,7 +37830,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -37765,7 +37842,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -39136,7 +39212,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -41173,6 +41248,20 @@ "integrity": "sha512-xop9VL3JmcF7/wUw79HpY3ulUfmbs57yFHEnxDy6pgZbLbnuaDBjgUrBRfSGSdHyu2jexd72USpgv4y/lD4xow==", "requires": {} }, + "@pothos/plugin-prisma": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@pothos/plugin-prisma/-/plugin-prisma-3.4.0.tgz", + "integrity": "sha512-J0uiTNX9cDXzJ64N6Gd7EVUW90bxSr/NrijY41zc5VZqHv/l1JlWmwKJK5qF5CBULlqNIba/KDLZHvszMNAA8A==", + "requires": { + "@prisma/generator-helper": "^3.12.0" + } + }, + "@pothos/plugin-relay": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@pothos/plugin-relay/-/plugin-relay-3.10.0.tgz", + "integrity": "sha512-ZNkSYPhDKanMrd8aFD4nEUeMHoeD6wyejBsO1Ik/q8IRHRRPQzLsc/ignxGEl5j+hdu/SsTuhMNszV9Ef0JOJw==", + "requires": {} + }, "@prisma/client": { "version": "3.11.1", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-3.11.1.tgz", @@ -41181,6 +41270,36 @@ "@prisma/engines-version": "3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9" } }, + "@prisma/debug": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-3.12.0.tgz", + "integrity": "sha512-MFNsK8jzao3VX+cEP6+txABa3w8bU4gD0QpqTbGS3v3TW1c7TtHvhsmI7xTx9Cll7biuJdaRd3YwG2Fa/CHVmA==", + "requires": { + "@types/debug": "4.1.7", + "ms": "2.1.3", + "strip-ansi": "6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "@prisma/engines": { "version": "3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9.tgz", @@ -41191,6 +41310,17 @@ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.11.1-1.1a2506facaf1a4727b7c26850735e88ec779dee9.tgz", "integrity": "sha512-HkcsDniA4iNb/gi0iuyOJNAM7nD/LwQ0uJm15v360O5dee3TM4lWdSQiTYBMK6FF68ACUItmzSur7oYuUZ2zkQ==" }, + "@prisma/generator-helper": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@prisma/generator-helper/-/generator-helper-3.12.0.tgz", + "integrity": "sha512-wfEcSVUX0HH0A/CIrsecV1CLH3McNCLH2A76UL7QpSX1XLoIDo6SxgMwAfXufNGDhPET6NAoWm8mSmDXJopgrw==", + "requires": { + "@prisma/debug": "3.12.0", + "@types/cross-spawn": "6.0.2", + "chalk": "4.1.2", + "cross-spawn": "7.0.3" + } + }, "@repeaterjs/repeater": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.4.tgz", @@ -41453,6 +41583,14 @@ "@types/responselike": "*" } }, + "@types/cross-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.2.tgz", + "integrity": "sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==", + "requires": { + "@types/node": "*" + } + }, "@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types%2fdebug/-/debug-4.1.7.tgz", @@ -42758,7 +42896,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -44475,8 +44612,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isomorphic-fetch": { "version": "3.0.0", @@ -66349,8 +66485,7 @@ "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-parse": { "version": "1.0.7", @@ -67417,7 +67552,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -67425,8 +67559,7 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "signal-exit": { "version": "3.0.7", @@ -68401,7 +68534,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } diff --git a/package.json b/package.json index f557eb0..04cdfc7 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,8 @@ "dependencies": { "@graphql-yoga/node": "^2.1.0", "@pothos/core": "^3.5.1", + "@pothos/plugin-prisma": "^3.4.0", + "@pothos/plugin-relay": "^3.10.0", "@prisma/client": "^3.11.1", "@tailwindcss/forms": "^0.4.0", "@tailwindcss/typography": "^0.5.1", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c69c78f..faa6483 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -2,12 +2,16 @@ generator client { provider = "prisma-client-js" } +generator pothos { + provider = "prisma-pothos-types" +} + datasource db { provider = "postgresql" url = env("DIGITALOCEAN_POSTGRES") } -model dashboards { +model Dashboard { id String @id title String description String @@ -15,15 +19,19 @@ model dashboards { timestamp DateTime @db.Timestamp(6) creator String extra Json + + @@map("dashboards") } -model frontpage { +model Frontpage { id Int @id @default(autoincrement()) frontpage_full Json frontpage_sliced Json + + @@map("frontpage") } -model history { +model History { id String title String url String @@ -37,9 +45,10 @@ model history { pk Int @id @default(autoincrement()) @@index([id]) + @@map("history") } -model questions { +model Question { id String @id title String url String @@ -50,4 +59,6 @@ model questions { stars Int qualityindicators Json extra Json + + @@map("questions") } diff --git a/src/backend/database/prisma.ts b/src/backend/database/prisma.ts new file mode 100644 index 0000000..c3cc0f5 --- /dev/null +++ b/src/backend/database/prisma.ts @@ -0,0 +1,3 @@ +import { PrismaClient } from "@prisma/client"; + +export const prisma = new PrismaClient({}); diff --git a/src/graphql/build-schema.js b/src/graphql/build-schema.js index 57e44eb..47ff961 100644 --- a/src/graphql/build-schema.js +++ b/src/graphql/build-schema.js @@ -1,3 +1,5 @@ +// see https://pothos-graphql.dev/docs/guide/generating-client-types#export-schema-in-a-js-file, +// but we use ts-node instead of @boost/module require("ts-node").register({}); module.exports = require("./schema.ts"); diff --git a/src/graphql/schema.ts b/src/graphql/schema.ts index 95b7baa..ad99436 100644 --- a/src/graphql/schema.ts +++ b/src/graphql/schema.ts @@ -1,25 +1,104 @@ import SchemaBuilder from "@pothos/core"; +import PrismaPlugin from "@pothos/plugin-prisma"; +import RelayPlugin from "@pothos/plugin-relay"; +import { Question } from "@prisma/client"; +import { prisma } from "../backend/database/prisma"; import { getFrontpage } from "../backend/frontpage"; -import { Question } from "../backend/platforms"; -const builder = new SchemaBuilder({}); +import type PrismaTypes from "@pothos/plugin-prisma/generated"; +const builder = new SchemaBuilder<{ + PrismaTypes: PrismaTypes; + Scalars: { + Date: { + Input: Date; + Output: Date; + }; + }; +}>({ + plugins: [PrismaPlugin, RelayPlugin], + prisma: { + client: prisma, + }, + relayOptions: { + clientMutationId: "omit", + cursorType: "String", + // these are required for some reason, though it's not documented and probably a bug + brandLoadedObjects: undefined, + encodeGlobalID: undefined, + decodeGlobalID: undefined, + }, +}); -const QuestionObj = builder.objectRef("Question").implement({ - description: "Forecast question.", +builder.scalarType("Date", { + description: "Date serialized as the Unix timestamp.", + serialize: (d) => d.getTime() / 1000, + parseValue: (d) => { + return new Date(d as string); // not sure if this is correct, need to check + }, +}); + +const QuestionObj = builder.prismaObject("Question", { + findUnique: (question) => ({ id: question.id }), fields: (t) => ({ - id: t.exposeString("id", {}), - title: t.exposeString("title", {}), + id: t.exposeID("id"), + title: t.exposeString("title"), + timestamp: t.field({ + type: "Date", + resolve: (parent) => parent.timestamp, + }), + }), +}); + +builder.queryType({ + fields: (t) => ({ + firstQuestion: t.prismaField({ + type: "Question", + resolve: async (query, root, args, ctx, info) => + prisma.question.findUnique({ + ...query, + rejectOnNotFound: true, + where: { id: "foretold-e1ca8cc6-33a4-4e38-9ef3-553a050ba0a9" }, + }), + }), }), }); builder.queryField("frontpage", (t) => t.field({ type: [QuestionObj], - resolve: async (parent) => { - return await getFrontpage(); + resolve: async () => { + const legacyQuestions = await getFrontpage(); + const ids = legacyQuestions.map((q) => q.id); + const questions = await prisma.question.findMany({ + where: { + id: { + in: ids, + }, + }, + }); + const id2q: { [k: string]: Question } = {}; + for (const q of questions) { + id2q[q.id] = q; + } + + return ids.map((id) => id2q[id] || null).filter((q) => q !== null); }, }) ); +builder.queryField("questions", (t) => + t.prismaConnection( + { + type: "Question", + cursor: "id", + maxSize: 1000, + resolve: (query, parent, args, context, info) => + prisma.question.findMany({ ...query }), + }, + {}, + {} + ) +); + export const schema = builder.toSchema({});