Remove express in favor of next.js

This commit is contained in:
Jordan Eldredge 2025-06-05 18:13:58 -07:00
parent 77e022aa56
commit e8c677d97b
13 changed files with 161 additions and 491 deletions

View file

@ -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

View file

@ -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";

View file

@ -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<any, any>;
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 = "<MOCKED>";
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);

View file

@ -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.`)
);
}
},
};

View file

@ -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<

View file

@ -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`);
});

View file

@ -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;
};
}
}
}

View file

@ -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<Record<string, string>>;
}
const { handleRequest } = createYoga<NextContext>({
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<string, any>) => {
console.log(message, context);
},
logError: (message: string, context: Record<string, any>) => {
console.error(message, context);
},
},
});

View file

@ -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<Record<string, string>>;
}
type Options = {
eventHandler?: EventHandler;
getUserContext(): UserContext;
logger?: Logger;
};
export function createYogaInstance({
eventHandler,
logger,
getUserContext,
}: Options): YogaServerInstance<any, any> {
return createYoga<NextContext>({
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);
}
},
};
},
});
}

View file

@ -1,6 +0,0 @@
module.exports = {
presets: [
["@babel/preset-env", { targets: { node: "current" } }],
"@babel/preset-typescript",
],
};

View file

@ -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,

View file

@ -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": {

210
yarn.lock
View file

@ -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"