diff --git a/packages/skin-database/README.md b/packages/skin-database/README.md index eda64c4f..90a8379e 100644 --- a/packages/skin-database/README.md +++ b/packages/skin-database/README.md @@ -17,3 +17,4 @@ The discord bot allows us to: ## Server This package also includes a GraphQL interface for exploring skins. It is not currently used by anything, but can be useful for inspecting the data. +sudo systemctl reload apache2 diff --git a/packages/skin-database/api/DiscordEventHandler.ts b/packages/skin-database/api/DiscordEventHandler.ts index 06368a53..20717ee7 100644 --- a/packages/skin-database/api/DiscordEventHandler.ts +++ b/packages/skin-database/api/DiscordEventHandler.ts @@ -1,4 +1,4 @@ -import { ApiAction } from "./app"; +import { ApiAction } from "./types"; import Discord, { TextChannel } from "discord.js"; import * as Config from "../config"; import SkinModel from "../data/SkinModel"; diff --git a/packages/skin-database/api/__tests__/graphql.test.ts b/packages/skin-database/api/__tests__/graphql.test.ts index c4c3a51c..93df227c 100644 --- a/packages/skin-database/api/__tests__/graphql.test.ts +++ b/packages/skin-database/api/__tests__/graphql.test.ts @@ -1,18 +1,17 @@ -import { Application } from "express"; import { knex } from "../../db"; -import request from "supertest"; // supertest is a framework that allows to easily test web apis -import { createApp } from "../app"; import SkinModel from "../../data/SkinModel"; import * as S3 from "../../s3"; import { processUserUploads } from "../processUserUploads"; import UserContext from "../../data/UserContext"; import { searchIndex } from "../../algolia"; +import { createYogaInstance } from "../../app/graphql/yoga"; +import { YogaServerInstance } from "graphql-yoga"; jest.mock("../../s3"); jest.mock("../../algolia"); jest.mock("../processUserUploads"); jest.mock("../auth"); -let app: Application; +let yoga: YogaServerInstance; const handler = jest.fn(); const log = jest.fn(); const logError = jest.fn(); @@ -22,12 +21,9 @@ let username: string | undefined; beforeEach(async () => { jest.clearAllMocks(); username = ""; - app = createApp({ + yoga = createYogaInstance({ eventHandler: handler, - extraMiddleware: (req, res, next) => { - req.session.username = username; - next(); - }, + getUserContext: () => new UserContext(username), logger: { log, logError }, }); await knex.migrate.latest(); @@ -39,9 +35,12 @@ function gql(templateString: TemplateStringsArray): string { } async function graphQLRequest(query: string, variables?: any) { - const { body } = await request(app) - .post("/graphql") - .send({ query, variables: variables ?? {} }); + const response = await yoga.fetch("/graphql", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ query, variables }), + }); + const body = await response.json(); if (body.errors && body.errors.length) { for (const err of body.errors) { console.warn(err.message); diff --git a/packages/skin-database/api/app.ts b/packages/skin-database/api/app.ts deleted file mode 100644 index 88deabca..00000000 --- a/packages/skin-database/api/app.ts +++ /dev/null @@ -1,191 +0,0 @@ -import graphql from "./graphql"; -import cors, { CorsOptions } from "cors"; -import * as Sentry from "@sentry/node"; -import expressSitemapXml from "express-sitemap-xml"; -import * as Skins from "../data/skins"; -import express, { RequestHandler, ErrorRequestHandler, Handler } from "express"; -import UserContext from "../data/UserContext"; -import cookieSession from "cookie-session"; -import { SECRET } from "../config"; - -export type ApiAction = - | { type: "REVIEW_REQUESTED"; md5: string } - | { type: "REJECTED_SKIN"; md5: string } - | { type: "APPROVED_SKIN"; md5: string } - | { type: "MARKED_SKIN_NSFW"; md5: string } - | { type: "SKIN_UPLOADED"; md5: string } - | { type: "ERROR_PROCESSING_UPLOAD"; id: string; message: string } - | { type: "CLASSIC_SKIN_UPLOADED"; md5: string } - | { type: "MODERN_SKIN_UPLOADED"; md5: string } - | { type: "SKIN_UPLOAD_ERROR"; uploadId: string; message: string } - | { - type: "GOT_FEEDBACK"; - message: string; - email?: string | null; - url?: string | null; - } - | { - type: "SYNCED_TO_ARCHIVE"; - successes: number; - errors: number; - skips: number; - } - | { type: "STARTED_SYNC_TO_ARCHIVE"; count: number } - | { - type: "POPULAR_TWEET"; - bracket: number; - url: string; - likes: number; - date: Date; - } - | { type: "TWEET_BOT_MILESTONE"; bracket: number; count: number }; - -export type EventHandler = (event: ApiAction) => void; -export type Logger = { - log(message: string, context: any): void; - logError(message: string, context: any): void; -}; - -// Add UserContext to req objects globally -declare global { - namespace Express { - interface Request { - ctx: UserContext; - notify(action: ApiAction): void; - log(message: string): void; - logError(message: string): void; - session: { - username: string | undefined; - }; - } - } -} - -type Options = { - eventHandler?: EventHandler; - extraMiddleware?: Handler; - logger?: Logger; -}; - -export function createApp({ eventHandler, logger, extraMiddleware }: Options) { - const app = express(); - if (Sentry) { - Sentry.init({ - dsn: "https://0e6bc841b4f744b2953a1fe5981effe6@o68382.ingest.us.sentry.io/5508241", - }); - } - - // https://expressjs.com/en/guide/behind-proxies.html - // This is needed in order to allow `cookieSession({secure: true})` cookies to be sent. - app.set("trust proxy", "loopback"); - - const cookieHandler: RequestHandler = cookieSession({ - secure: true, - sameSite: "none", - httpOnly: false, - name: "session", - secret: SECRET, - maxAge: 24 * 60 * 60 * 1000, // 24 hours - // @ts-ignore Tests fail if this is missing, but prod is fine. - keys: "what", - }); - - app.use(cookieHandler); - - if (extraMiddleware != null) { - app.use(extraMiddleware); - } - - // Add UserContext to request - app.use((req, res, next) => { - req.ctx = new UserContext(req.session.username); - next(); - // TODO: Dispose of context? - }); - - // Attach event handler - app.use((req, res, next) => { - req.notify = (action) => { - if (eventHandler) { - eventHandler(action); - } - }; - next(); - }); - - // Attach logger - app.use((req, res, next) => { - const context = { - url: req.url, - params: req.params, - query: req.query, - username: req.ctx.username, - }; - req.log = (message) => { - if (logger != null) { - logger.log(message, context); - } - }; - req.logError = (message) => { - if (logger != null) { - logger.logError(message, context); - } - }; - next(); - }); - - // Configure CORs - app.use(cors(corsOptions)); - app.options("*", cors(corsOptions)); - - // Configure sitemap - app.use(expressSitemapXml(getSitemapUrls, "https://skins.webamp.org")); - - // Add routes - app.use("/graphql", graphql); - - // The error handler must be before any other error middleware and after all controllers - if (Sentry) { - app.use(Sentry.Handlers.errorHandler() as ErrorRequestHandler); - } - - // Optional fallthrough error handler - app.use(function onError(err, _req, res, _next) { - console.error(err); - res.statusCode = 500; - res.json({ errorId: res.sentry, message: err.message }); - }); - - return app; -} - -async function getSitemapUrls() { - const md5s = await Skins.getAllClassicSkins(); - const skinUrls = md5s.map(({ md5, fileName }) => `skin/${md5}/${fileName}`); - return ["/about", "/", "/upload", ...skinUrls]; -} - -const allowList = [ - /https:\/\/skins\.webamp\.org/, - /https:\/\/api\.webamp\.org/, - /https:\/\/webamp\.org/, - /https:\/\/[^.]*\.csb\.app/, - /https:\/\/winamp-skin-museum\.pages\.dev/, - /http:\/\/localhost:3000/, - /http:\/\/localhost:3001/, - /netlify.app/, - /https:\/\/dustinbrett.com/, -]; - -const corsOptions: CorsOptions = { - credentials: true, - origin: function (origin, callback) { - if (!origin || allowList.some((regex) => regex.test(origin))) { - callback(null, true); - } else { - callback( - new Error(`Request from origin "${origin}" not allowed by CORS.`) - ); - } - }, -}; diff --git a/packages/skin-database/api/processUserUploads.ts b/packages/skin-database/api/processUserUploads.ts index d580208c..25462fd0 100644 --- a/packages/skin-database/api/processUserUploads.ts +++ b/packages/skin-database/api/processUserUploads.ts @@ -1,7 +1,7 @@ import * as Skins from "../data/skins"; import S3 from "../s3"; import { addSkinFromBuffer } from "../addSkin"; -import { EventHandler } from "./app"; +import { EventHandler } from "./types"; import DiscordEventHandler from "./DiscordEventHandler"; async function* reportedUploads(): AsyncGenerator< diff --git a/packages/skin-database/api/server.ts b/packages/skin-database/api/server.ts deleted file mode 100644 index 9a79eacd..00000000 --- a/packages/skin-database/api/server.ts +++ /dev/null @@ -1,26 +0,0 @@ -import dotenv from "dotenv"; - -dotenv.config(); - -import { createApp } from "./app"; -import DiscordEventHandler from "./DiscordEventHandler"; - -const port = process.env.PORT ? Number(process.env.PORT) : 3001; - -const handler = new DiscordEventHandler(); - -// GO! -const app = createApp({ - eventHandler: (action) => handler.handle(action), - logger: { - log: (message, context) => console.log(message, context), - logError: (message, context) => console.error(message, context), - }, -}); -app.listen(port, () => { - console.log( - `Winamp Skin Museum database API app listening on http://localhost:${port}` - ); - console.log(`Explore: http://localhost:${port}/graphql`); -}); - diff --git a/packages/skin-database/api/types.ts b/packages/skin-database/api/types.ts new file mode 100644 index 00000000..d04c857a --- /dev/null +++ b/packages/skin-database/api/types.ts @@ -0,0 +1,54 @@ +import UserContext from "../data/UserContext"; + +export type ApiAction = + | { type: "REVIEW_REQUESTED"; md5: string } + | { type: "REJECTED_SKIN"; md5: string } + | { type: "APPROVED_SKIN"; md5: string } + | { type: "MARKED_SKIN_NSFW"; md5: string } + | { type: "SKIN_UPLOADED"; md5: string } + | { type: "ERROR_PROCESSING_UPLOAD"; id: string; message: string } + | { type: "CLASSIC_SKIN_UPLOADED"; md5: string } + | { type: "MODERN_SKIN_UPLOADED"; md5: string } + | { type: "SKIN_UPLOAD_ERROR"; uploadId: string; message: string } + | { + type: "GOT_FEEDBACK"; + message: string; + email?: string | null; + url?: string | null; + } + | { + type: "SYNCED_TO_ARCHIVE"; + successes: number; + errors: number; + skips: number; + } + | { type: "STARTED_SYNC_TO_ARCHIVE"; count: number } + | { + type: "POPULAR_TWEET"; + bracket: number; + url: string; + likes: number; + date: Date; + } + | { type: "TWEET_BOT_MILESTONE"; bracket: number; count: number }; + +export type EventHandler = (event: ApiAction) => void; +export type Logger = { + log(message: string, context: any): void; + logError(message: string, context: any): void; +}; + +// Add UserContext to req objects globally +declare global { + namespace Express { + interface Request { + ctx: UserContext; + notify(action: ApiAction): void; + log(message: string): void; + logError(message: string): void; + session: { + username: string | undefined; + }; + } + } +} diff --git a/packages/skin-database/app/graphql/route.ts b/packages/skin-database/app/graphql/route.ts index c29b6d2d..db44d309 100644 --- a/packages/skin-database/app/graphql/route.ts +++ b/packages/skin-database/app/graphql/route.ts @@ -1,46 +1,21 @@ -import { createYoga, YogaInitialContext } from "graphql-yoga"; -import { getSchema } from "../../api/graphql/schema"; import UserContext from "../../data/UserContext"; import DiscordEventHandler from "../../api/DiscordEventHandler"; +import { createYogaInstance } from "./yoga"; const handler = new DiscordEventHandler(); -interface NextContext { - params: Promise>; -} - -const { handleRequest } = createYoga({ - schema: getSchema(), - - // While using Next.js file convention for routing, we need to configure Yoga to use the correct endpoint - graphqlEndpoint: "/graphql", - - // Yoga needs to know how to create a valid Next response - fetchAPI: { Response }, - - graphiql: true, - - cors: { - origin: "*", // Allow all origins for simplicity, adjust as needed - methods: ["GET", "POST", "OPTIONS"], - credentials: true, +const { handleRequest } = createYogaInstance({ + eventHandler: (action) => handler.handle(action), + getUserContext() { + return new UserContext(); }, - context: (ctx: YogaInitialContext) => { - return { - ...ctx.request, - ctx: new UserContext(), - notify: (action) => handler.handle(action), - log(message: string) { - console.log(message, { - url: ctx.request.url, - }); - }, - error(message: string) { - console.error(message, { - url: ctx.request.url, - }); - }, - }; + logger: { + log: (message: string, context: Record) => { + console.log(message, context); + }, + logError: (message: string, context: Record) => { + console.error(message, context); + }, }, }); diff --git a/packages/skin-database/app/graphql/yoga.ts b/packages/skin-database/app/graphql/yoga.ts new file mode 100644 index 00000000..140332fa --- /dev/null +++ b/packages/skin-database/app/graphql/yoga.ts @@ -0,0 +1,63 @@ +import { + createYoga, + YogaInitialContext, + YogaServerInstance, +} from "graphql-yoga"; +import { getSchema } from "../../api/graphql/schema"; +import UserContext from "../../data/UserContext"; +import { ApiAction, EventHandler, Logger } from "../../api/types"; + +interface NextContext { + params: Promise>; +} + +type Options = { + eventHandler?: EventHandler; + getUserContext(): UserContext; + logger?: Logger; +}; + +export function createYogaInstance({ + eventHandler, + logger, + getUserContext, +}: Options): YogaServerInstance { + return createYoga({ + schema: getSchema(), + + // While using Next.js file convention for routing, we need to configure Yoga to use the correct endpoint + graphqlEndpoint: "/graphql", + + graphiql: true, + + cors: { + origin: "*", // Allow all origins for simplicity, adjust as needed + methods: ["GET", "POST", "OPTIONS"], + credentials: true, + }, + context: (ctx: YogaInitialContext) => { + const context = { + url: ctx.request.url, + }; + return { + ...ctx.request, + ctx: getUserContext(), + notify(event: ApiAction) { + if (eventHandler) { + eventHandler(event); + } + }, + log(message) { + if (logger != null) { + logger.log(message, context); + } + }, + logError(message) { + if (logger != null) { + logger.logError(message, context); + } + }, + }; + }, + }); +} diff --git a/packages/skin-database/babel.config.js b/packages/skin-database/babel.config.js deleted file mode 100644 index dd242dc9..00000000 --- a/packages/skin-database/babel.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - presets: [ - ["@babel/preset-env", { targets: { node: "current" } }], - "@babel/preset-typescript", - ], -}; diff --git a/packages/skin-database/ecosystem.config.js b/packages/skin-database/ecosystem.config.js index 610fe89f..0416d7e7 100644 --- a/packages/skin-database/ecosystem.config.js +++ b/packages/skin-database/ecosystem.config.js @@ -18,7 +18,7 @@ module.exports = { name: "skin-database-green", script: "yarn", interpreter: "bash", - args: "start:next", + args: "start", env: { NODE_ENV: "production", PORT: 3002, diff --git a/packages/skin-database/package.json b/packages/skin-database/package.json index 648f9186..b31916eb 100644 --- a/packages/skin-database/package.json +++ b/packages/skin-database/package.json @@ -11,14 +11,9 @@ "aws-sdk": "^2.814.0", "commander": "^9.0.0", "cookie-session": "^1.4.0", - "cors": "^2.8.5", "dataloader": "^2.0.0", "discord.js": "^12.5.3", "dotenv": "^16.0.0", - "express": "^4.17.1", - "express-async-handler": "^1.1.4", - "express-graphql": "^0.12.0", - "express-sitemap-xml": "^2.0.0", "fast-xml-parser": "^4.2.2", "graphql": "^16.8.1", "graphql-http": "^1.22.1", @@ -46,8 +41,10 @@ }, "scripts": { "lint": "eslint .", - "start": "ts-node --transpile-only api/server.ts", - "dev": "NODE_ENV=production ts-node --transpile-only api/server.ts", + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint:next": "next lint", "tweet": "ts-node --transpile-only ./cli.ts tweet", "fetch-metadata": "ts-node --transpile-only ./cli.ts fetch-metadata", "bot": "ts-node --transpile-only ./discord-bot/index.js", @@ -55,26 +52,16 @@ "sync": "ts-node --transpile-only ./tasks/syncWithArchive.ts", "migrate": "knex migrate:latest", "grats": "grats", - "typecheck": "tsc --noEmit", - "dev:next": "next dev", - "build:next": "next build", - "start:next": "next start", - "lint:next": "next lint" + "typecheck": "tsc --noEmit" }, "prettier": {}, "devDependencies": { - "@babel/preset-typescript": "^7.10.1", "@types/cookie-session": "^2.0.48", - "@types/cors": "^2.8.17", - "@types/express": "4.17.9", - "@types/express-sitemap-xml": "^1.1.1", "@types/lru-cache": "^5.1.0", "@types/node-fetch": "^2.5.7", - "@types/supertest": "^2.0.10", "@typescript-eslint/eslint-plugin": "^7.1.0", "@typescript-eslint/parser": "^7.1.0", "grats": "^0.0.31", - "supertest": "^6.0.1", "typescript": "^5.3.3" }, "resolutions": { diff --git a/yarn.lock b/yarn.lock index 269d949d..8773bd31 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1257,7 +1257,7 @@ "@babel/plugin-transform-react-jsx-development" "^7.22.5" "@babel/plugin-transform-react-pure-annotations" "^7.24.1" -"@babel/preset-typescript@^7.10.1", "@babel/preset-typescript@^7.16.0", "@babel/preset-typescript@^7.16.7", "@babel/preset-typescript@^7.18.6", "@babel/preset-typescript@^7.20.0": +"@babel/preset-typescript@^7.16.0", "@babel/preset-typescript@^7.16.7", "@babel/preset-typescript@^7.18.6", "@babel/preset-typescript@^7.20.0": version "7.24.1" resolved "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz" integrity sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ== @@ -6167,18 +6167,6 @@ resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz" integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== -"@types/cookiejar@^2.1.5": - version "2.1.5" - resolved "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz" - integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q== - -"@types/cors@^2.8.17": - version "2.8.17" - resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz" - integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== - dependencies: - "@types/node" "*" - "@types/debug@^4.0.0": version "4.1.12" resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz" @@ -6265,13 +6253,6 @@ "@types/range-parser" "*" "@types/send" "*" -"@types/express-sitemap-xml@^1.1.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@types/express-sitemap-xml/-/express-sitemap-xml-1.1.1.tgz" - integrity sha512-D0zeqqRNrZnmYZxnWJLvZzMSVv13+AmsBOQUg9iDBOVPbYqCDwCFp52x/DxBwLVpUSAOFpbiFmue+U5NsmHB1A== - dependencies: - "@types/express" "*" - "@types/express@*", "@types/express@^4.17.13": version "4.17.21" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz" @@ -6282,16 +6263,6 @@ "@types/qs" "*" "@types/serve-static" "*" -"@types/express@4.17.9": - version "4.17.9" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.9.tgz" - integrity sha512-SDzEIZInC4sivGIFY4Sz1GG6J9UObPwCInYJjko2jzOf/Imx/dlpume6Xxwj1ORL82tBbmN4cPDIDkLbWHk9hw== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "*" - "@types/qs" "*" - "@types/serve-static" "*" - "@types/fscreen@^1.0.1": version "1.0.4" resolved "https://registry.npmjs.org/@types/fscreen/-/fscreen-1.0.4.tgz" @@ -6463,11 +6434,6 @@ resolved "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz" integrity sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA== -"@types/methods@^1.1.4": - version "1.1.4" - resolved "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz" - integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ== - "@types/mime@^1": version "1.3.5" resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz" @@ -6669,22 +6635,6 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== -"@types/superagent@*": - version "8.1.6" - resolved "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.6.tgz" - integrity sha512-yzBOv+6meEHSzV2NThYYOA6RtqvPr3Hbob9ZLp3i07SH27CrYVfm8CrF7ydTmidtelsFiKx2I4gZAiAOamGgvQ== - dependencies: - "@types/cookiejar" "^2.1.5" - "@types/methods" "^1.1.4" - "@types/node" "*" - -"@types/supertest@^2.0.10": - version "2.0.16" - resolved "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.16.tgz" - integrity sha512-6c2ogktZ06tr2ENoZivgm7YnprnhYE4ZoXGMY+oA7IuAf17M8FWvujXZGmxLv8y0PTyts4x5A+erSwVUFA8XSg== - dependencies: - "@types/superagent" "*" - "@types/tapable@^1": version "1.0.12" resolved "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.12.tgz" @@ -7632,7 +7582,7 @@ abortcontroller-polyfill@^1.1.9: resolved "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz" integrity sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ== -accepts@^1.3.7, accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: version "1.3.8" resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -10463,7 +10413,7 @@ commondir@^1.0.1: resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== -component-emitter@^1.2.1, component-emitter@^1.3.0: +component-emitter@^1.2.1: version "1.3.1" resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz" integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== @@ -10653,11 +10603,6 @@ cookie@^0.4.0, cookie@^0.4.1: resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== -cookiejar@^2.1.4: - version "2.1.4" - resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz" - integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== - cookies@0.8.0: version "0.8.0" resolved "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz" @@ -10747,14 +10692,6 @@ core-util-is@~1.0.0: resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - corser@~2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz" @@ -11873,7 +11810,7 @@ devtools-protocol@0.0.981744: resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.981744.tgz" integrity sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg== -dezalgo@^1.0.0, dezalgo@^1.0.4: +dezalgo@^1.0.0: version "1.0.4" resolved "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz" integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== @@ -13814,21 +13751,6 @@ expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -express-async-handler@^1.1.4: - version "1.2.0" - resolved "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz" - integrity sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w== - -express-graphql@^0.12.0: - version "0.12.0" - resolved "https://registry.npmjs.org/express-graphql/-/express-graphql-0.12.0.tgz" - integrity sha512-DwYaJQy0amdy3pgNtiTDuGGM2BLdj+YO2SgbKoLliCfuHv3VVTt7vNG/ZqK2hRYjtYHE2t2KB705EU94mE64zg== - dependencies: - accepts "^1.3.7" - content-type "^1.0.4" - http-errors "1.8.0" - raw-body "^2.4.1" - express-logging@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/express-logging/-/express-logging-1.1.1.tgz" @@ -13836,14 +13758,6 @@ express-logging@^1.1.1: dependencies: on-headers "^1.0.0" -express-sitemap-xml@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/express-sitemap-xml/-/express-sitemap-xml-2.0.0.tgz" - integrity sha512-7yluJFHjUWeZbg5mdQPIEj0x3FPVQFyZY76spYidFkYJYExSICZjsd+gaElUv/4bh2R+hrnytGOIU8rsp+7+Fw== - dependencies: - p-memoize "^4.0.1" - xmlbuilder "^15.1.1" - express@^4.17.1, express@^4.17.3: version "4.19.2" resolved "https://registry.npmjs.org/express/-/express-4.19.2.tgz" @@ -14075,7 +13989,7 @@ fast-querystring@^1.1.1: dependencies: fast-decode-uri-component "^1.0.1" -fast-safe-stringify@^2.0.7, fast-safe-stringify@^2.1.1: +fast-safe-stringify@^2.0.7: version "2.1.1" resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== @@ -14617,16 +14531,6 @@ formdata-node@^4.3.2: node-domexception "1.0.0" web-streams-polyfill "4.0.0-beta.3" -formidable@^2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz" - integrity sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g== - dependencies: - dezalgo "^1.0.4" - hexoid "^1.0.0" - once "^1.4.0" - qs "^6.11.0" - forwarded@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" @@ -15492,15 +15396,9 @@ graphql@^16.8.1: integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== graphql@^16.9.0: -<<<<<<< HEAD - version "16.11.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.11.0.tgz#96d17f66370678027fdf59b2d4c20b4efaa8a633" - integrity sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw== -======= version "16.10.0" resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.10.0.tgz#24c01ae0af6b11ea87bf55694429198aaa8e220c" integrity sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ== ->>>>>>> 285979f6 (More stuff) grats@^0.0.31: version "0.0.31" @@ -15856,11 +15754,6 @@ he@^1.1.1, he@^1.2.0: resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -hexoid@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz" - integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== - hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz" @@ -16025,17 +15918,6 @@ http-deceiver@^1.2.7: resolved "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz" integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== -http-errors@1.8.0: - version "1.8.0" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz" - integrity sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - http-errors@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" @@ -19902,13 +19784,6 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -map-age-cleaner@^0.1.3: - version "0.1.3" - resolved "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz" @@ -20261,7 +20136,7 @@ meros@^1.2.1: resolved "https://registry.npmjs.org/meros/-/meros-1.3.0.tgz" integrity sha512-2BNGOimxEz5hmjUG2FwoxCt5HN7BXdaWyFqEwxPTrJzVdABtrL4TiHTcsWSFAxPQ/tOnEaQEJh3qWq71QRMY+w== -methods@^1.1.2, methods@~1.1.2: +methods@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== @@ -20662,7 +20537,7 @@ mime@1.6.0, mime@^1.2.11, mime@^1.3.4, mime@^1.6.0: resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@2.6.0, mime@^2.0.3, mime@^2.4.4: +mime@^2.0.3, mime@^2.4.4: version "2.6.0" resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== @@ -20677,7 +20552,7 @@ mimic-fn@^2.1.0: resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-fn@^3.0.0, mimic-fn@^3.1.0: +mimic-fn@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz" integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== @@ -21842,7 +21717,7 @@ object-assign@^3.0.0: resolved "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz" integrity sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ== -object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -22246,11 +22121,6 @@ p-cancelable@^2.0.0: resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz" integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz" - integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw== - p-event@^1.0.0: version "1.3.0" resolved "https://registry.npmjs.org/p-event/-/p-event-1.3.0.tgz" @@ -22301,7 +22171,7 @@ p-limit@3.1.0, p-limit@^3.0.2, p-limit@^3.1.0: dependencies: yocto-queue "^0.1.0" -p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.2: +p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -22355,15 +22225,6 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" -p-memoize@^4.0.1: - version "4.0.4" - resolved "https://registry.npmjs.org/p-memoize/-/p-memoize-4.0.4.tgz" - integrity sha512-ijdh0DP4Mk6J4FXlOM6vPPoCjPytcEseW8p/k5SDTSSfGV3E9bpt9Yzfifvzp6iohIieoLTkXRb32OWV0fB2Lw== - dependencies: - map-age-cleaner "^0.1.3" - mimic-fn "^3.0.0" - p-settle "^4.1.1" - p-pipe@^1.1.0: version "1.2.0" resolved "https://registry.npmjs.org/p-pipe/-/p-pipe-1.2.0.tgz" @@ -22392,11 +22253,6 @@ p-reduce@^2.1.0: resolved "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz" integrity sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw== -p-reflect@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/p-reflect/-/p-reflect-2.1.0.tgz" - integrity sha512-paHV8NUz8zDHu5lhr/ngGWQiW067DK/+IbJ+RfZ4k+s8y4EKyYCz8pGYWjxCg35eHztpJAt+NUgvN4L+GCbPlg== - p-retry@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz" @@ -22412,14 +22268,6 @@ p-retry@^4.5.0: "@types/retry" "0.12.0" retry "^0.13.1" -p-settle@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/p-settle/-/p-settle-4.1.1.tgz" - integrity sha512-6THGh13mt3gypcNMm0ADqVNCcYa3BK6DWsuJWFCuEKP1rpY+OKGp7gaZwVmLspmic01+fsg/fN57MfvDzZ/PuQ== - dependencies: - p-limit "^2.2.2" - p-reflect "^2.1.0" - p-timeout@^1.1.1: version "1.2.1" resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz" @@ -24247,7 +24095,7 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" -qs@^6.11.0, qs@^6.11.2, qs@^6.4.0, qs@^6.9.3, qs@^6.9.6: +qs@^6.11.2, qs@^6.4.0, qs@^6.9.3, qs@^6.9.6: version "6.12.0" resolved "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz" integrity sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg== @@ -27274,30 +27122,6 @@ sucrase@^3.18.1, sucrase@^3.32.0: pirates "^4.0.1" ts-interface-checker "^0.1.9" -superagent@^8.1.2: - version "8.1.2" - resolved "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz" - integrity sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA== - dependencies: - component-emitter "^1.3.0" - cookiejar "^2.1.4" - debug "^4.3.4" - fast-safe-stringify "^2.1.1" - form-data "^4.0.0" - formidable "^2.1.2" - methods "^1.1.2" - mime "2.6.0" - qs "^6.11.0" - semver "^7.3.8" - -supertest@^6.0.1: - version "6.3.4" - resolved "https://registry.npmjs.org/supertest/-/supertest-6.3.4.tgz" - integrity sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw== - dependencies: - methods "^1.1.2" - superagent "^8.1.2" - supports-color@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz" @@ -27886,11 +27710,6 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - toidentifier@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" @@ -28929,7 +28748,7 @@ value-or-promise@^1.0.11, value-or-promise@^1.0.12: resolved "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.12.tgz" integrity sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q== -vary@^1, vary@~1.1.2: +vary@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== @@ -30038,11 +29857,6 @@ xml2js@0.6.2: sax ">=0.6.0" xmlbuilder "~11.0.0" -xmlbuilder@^15.1.1: - version "15.1.1" - resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz" - integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== - xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz"