diff --git a/packages/skin-database/api/app.ts b/packages/skin-database/api/app.ts new file mode 100644 index 00000000..8225d027 --- /dev/null +++ b/packages/skin-database/api/app.ts @@ -0,0 +1,68 @@ +import router from "./router"; +import fileUpload from "express-fileupload"; +import cors, { CorsOptions } from "cors"; +import bodyParser from "body-parser"; +import Sentry from "@sentry/node"; +import expressSitemapXml from "express-sitemap-xml"; +import * as Skins from "../data/skins"; +import express from "express"; + +export function createApp() { + const app = express(); + app.use(Sentry.Handlers.requestHandler()); + + // Configure CORs + app.use(cors(corsOptions)); + app.options("*", cors(corsOptions)); + + // Configure json output + app.set("json spaces", 2); + + // parse application/json + app.use(bodyParser.json()); + + // Configure File Uploads + const limits = { fileSize: 50 * 1024 * 1024 }; + app.use(fileUpload({ limits })); + + // Configure sitemap + app.use(expressSitemapXml(getSitemapUrls, "https://skins.webamp.org")); + + // Add routes + app.use("/", router); + + // The error handler must be before any other error middleware and after all controllers + app.use(Sentry.Handlers.errorHandler()); + + // Optional fallthrough error handler + app.use(function onError(err, req, res, next) { + 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/, + /http:\/\/localhost:3000/, + /netlify.app/, +]; + +const corsOptions: CorsOptions = { + 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/router.ts b/packages/skin-database/api/router.ts similarity index 100% rename from packages/skin-database/router.ts rename to packages/skin-database/api/router.ts diff --git a/packages/skin-database/api/server.ts b/packages/skin-database/api/server.ts new file mode 100644 index 00000000..1e4510cb --- /dev/null +++ b/packages/skin-database/api/server.ts @@ -0,0 +1,18 @@ +import Sentry from "@sentry/node"; +import { createApp } from "./app"; + +const port = process.env.PORT ? Number(process.env.PORT) : 3001; + +// GO! +const app = createApp(); +app.listen(port, () => console.log(`Example app listening on port ${port}!`)); + +// Initialize Sentry after we start listening. Any crash at start time will appear in the console and we'll notice. +Sentry.init({ + dsn: + "https://0e6bc841b4f744b2953a1fe5981effe6@o68382.ingest.sentry.io/5508241", + + // We recommend adjusting this value in production, or using tracesSampler + // for finer control + tracesSampleRate: 1.0, +}); diff --git a/packages/skin-database/data/skins.ts b/packages/skin-database/data/skins.ts index fbe70f79..011e12f5 100644 --- a/packages/skin-database/data/skins.ts +++ b/packages/skin-database/data/skins.ts @@ -3,7 +3,6 @@ import path from "path"; import md5Hash from "md5"; import { searchIndex } from "../algolia"; import { truncate, MD5_REGEX } from "../utils"; -import { TweetStatus } from "../types"; import fetch from "node-fetch"; import * as S3 from "../s3"; import SkinModel from "./SkinModel"; @@ -557,7 +556,7 @@ LIMIT ? offset ?`, } export async function getAllClassicSkins(): Promise< - Array<{ fileName: string; url: string }> + Array<{ fileName: string; md5: string }> > { const skins = await knex.raw( ` diff --git a/packages/skin-database/index.js b/packages/skin-database/index.js deleted file mode 100644 index 55f6b8b6..00000000 --- a/packages/skin-database/index.js +++ /dev/null @@ -1,77 +0,0 @@ -const express = require("express"); -const app = express(); -const Skins = require("./data/skins"); -const port = process.env.PORT ? Number(process.env.PORT) : 3001; -const fileUpload = require("express-fileupload"); -const cors = require("cors"); -var bodyParser = require("body-parser"); -const expressSitemapXml = require("express-sitemap-xml"); -const router = require("./router"); - -const Sentry = require("@sentry/node"); -// or use es6 import statements -// import * as Sentry from '@sentry/node'; - -Sentry.init({ - dsn: - "https://0e6bc841b4f744b2953a1fe5981effe6@o68382.ingest.sentry.io/5508241", - - // We recommend adjusting this value in production, or using tracesSampler - // for finer control - tracesSampleRate: 1.0, -}); - -const allowList = [ - /https:\/\/skins\.webamp\.org/, - /http:\/\/localhost:3000/, - /netlify.app/, -]; -const corsOptions = { - origin: function (origin, callback) { - if (allowList.some((regex) => regex.test(origin)) || !origin) { - callback(null, true); - } else { - callback( - new Error(`Request from origin "${origin}" not allowed by CORS.`) - ); - } - }, -}; - -async function getUrls() { - const md5s = await Skins.getAllClassicSkins(); - const skinUrls = md5s.map(({ md5, fileName }) => `skin/${md5}/${fileName}`); - return ["/about", "/", "/upload", ...skinUrls]; -} -app.use(expressSitemapXml(getUrls, "https://skins.webamp.org")); - -// parse application/json -app.use(bodyParser.json()); - -app.use(Sentry.Handlers.requestHandler()); - -app.use(cors(corsOptions)); -app.options("*", cors(corsOptions)); - -// TODO: Look into 766c4fad9088037ab4839b18292be8b1 -// Has huge number of filenames in info.json - -app.set("json spaces", 2); -app.use( - fileUpload({ - limits: { fileSize: 50 * 1024 * 1024 }, - }) -); - -app.use(router); - -// The error handler must be before any other error middleware and after all controllers -app.use(Sentry.Handlers.errorHandler()); - -// Optional fallthrough error handler -app.use(function onError(err, req, res, next) { - res.statusCode = 500; - res.json({ errorId: res.sentry, message: err.message }); -}); - -app.listen(port, () => console.log(`Example app listening on port ${port}!`)); diff --git a/packages/skin-database/package.json b/packages/skin-database/package.json index eb677dfc..b7306896 100644 --- a/packages/skin-database/package.json +++ b/packages/skin-database/package.json @@ -6,7 +6,10 @@ "dependencies": { "@sentry/node": "^5.27.3", "@sentry/tracing": "^5.27.3", + "@types/cors": "^2.8.8", "@types/express": "^4.17.9", + "@types/express-fileupload": "^1.1.5", + "@types/express-sitemap-xml": "^1.1.1", "@types/lru-cache": "^5.1.0", "@types/node-fetch": "^2.5.7", "algoliasearch": "^4.3.0", @@ -37,7 +40,7 @@ "yargs": "^13.2.4" }, "scripts": { - "start": "ts-node --transpile-only index.js", + "start": "ts-node --transpile-only api/server.js", "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", diff --git a/yarn.lock b/yarn.lock index bc5fc7fd..8937008f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1687,6 +1687,12 @@ dependencies: "@types/node" "*" +"@types/cors@^2.8.8": + version "2.8.8" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.8.tgz#317a8d8561995c60e35b9e0fcaa8d36660c98092" + dependencies: + "@types/express" "*" + "@types/css-font-loading-module@^0.0.2": version "0.0.2" resolved "https://registry.yarnpkg.com/@types/css-font-loading-module/-/css-font-loading-module-0.0.2.tgz#09f1f1772975777e37851b7b7a4389d97c210add" @@ -1699,6 +1705,12 @@ version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" +"@types/express-fileupload@^1.1.5": + version "1.1.5" + resolved "https://registry.yarnpkg.com/@types/express-fileupload/-/express-fileupload-1.1.5.tgz#dc8a971f9060d50b12e3908a09248f1772e99484" + dependencies: + "@types/express" "*" + "@types/express-serve-static-core@*": version "4.17.13" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.13.tgz#d9af025e925fc8b089be37423b8d1eac781be084" @@ -1707,7 +1719,13 @@ "@types/qs" "*" "@types/range-parser" "*" -"@types/express@^4.17.9": +"@types/express-sitemap-xml@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/express-sitemap-xml/-/express-sitemap-xml-1.1.1.tgz#be4c20cd458e8d22cd742db34623100579cc1c49" + dependencies: + "@types/express" "*" + +"@types/express@*", "@types/express@^4.17.9": version "4.17.9" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.9.tgz#f5f2df6add703ff28428add52bdec8a1091b0a78" dependencies: