diff --git a/packages/skin-database/api/app.ts b/packages/skin-database/api/app.ts index 0ef77522..dcd116b9 100644 --- a/packages/skin-database/api/app.ts +++ b/packages/skin-database/api/app.ts @@ -1,4 +1,5 @@ import router from "./router"; +import graphql from "./graphql"; import fileUpload from "express-fileupload"; import cors, { CorsOptions } from "cors"; import bodyParser from "body-parser"; @@ -23,8 +24,14 @@ export type ApiAction = | { type: "GOT_FEEDBACK"; message: string; email?: string; url?: string } | { type: "SYNCED_TO_ARCHIVE"; successes: number; errors: 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}; + | { + 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 = { @@ -135,6 +142,7 @@ export function createApp({ eventHandler, extraMiddleware, logger }: Options) { // Add routes app.use("/", router); + app.use("/graphql", graphql); // The error handler must be before any other error middleware and after all controllers if (Sentry) { @@ -143,6 +151,7 @@ export function createApp({ eventHandler, extraMiddleware, logger }: Options) { // 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 }); }); @@ -160,6 +169,7 @@ const allowList = [ /https:\/\/skins\.webamp\.org/, /https:\/\/winamp-skin-museum\.pages\.dev/, /http:\/\/localhost:3000/, + /http:\/\/localhost:3001/, /netlify.app/, ]; diff --git a/packages/skin-database/api/graphql/index.ts b/packages/skin-database/api/graphql/index.ts new file mode 100644 index 00000000..580de35c --- /dev/null +++ b/packages/skin-database/api/graphql/index.ts @@ -0,0 +1,266 @@ +import { Router } from "express"; +import { graphqlHTTP } from "express-graphql"; + +import { buildSchema } from "graphql"; +import SkinModel from "../../data/SkinModel"; +import { knex } from "../../db"; +import SkinResolver from "./resolvers/SkinResolver"; +import * as Skins from "../../data/skins"; +import UserResolver from "./resolvers/UserResolver"; + +const router = Router(); + +const schema = buildSchema(` + +"""A classic Winamp skin""" +type Skin { + """Database ID of the skin""" + id: Int, + + """MD5 hash of the skin's file""" + md5: String, + + """URL of the skin on the Winamp Skin Museum""" + museum_url: String, + + """URL of webamp.org with the skin loaded""" + webamp_url: String, + + """URL of a screenshot of the skin""" + screenshot_url: String, + + """URL to download the skin""" + download_url: String, + + """ + Filename of skin when uploaded to the Museum. Note: In some cases a skin + has been uploaded under multiple names. Here we just pick one. + """ + filename: String, + + """Text of the readme file extracted from the skin""" + readme_text: String, + + """Has the skin been flagged as "not safe for wrok"?""" + nsfw: Boolean, + + """String representation (rgb usually) of the skin's average color""" + average_color: String, + + """Has the skin been tweeted?""" + tweeted: Boolean + + """List of @winampskins tweets that mentioned the skin.""" + tweets: [Tweet] + + """List of files contained within the skin's .wsz archive""" + archive_files: [ArchiveFile] + + """The skin's "item" at archive.org""" + internet_archive_item: InternetArchiveItem + + """ + Times that the skin has been reviewed either on the Museum's Tinder-style + reivew page, or via the Discord bot. + """ + reviews: [Review] +} + + +"""The judgement made about a skin by a moderator""" +enum Rating { + APPROVED + REJECTED + NSFW +} + +""" +A review of a skin. Done either on the Museum's Tinder-style +reivew page, or via the Discord bot. +""" +type Review { + """The skin that was reviewed""" + skin: Skin + + """ + The user who made the review (if known). **Note:** In the early days we didn't + track this, so many will be null. + """ + reviewer: String + + """The rating that the user gave the skin""" + rating: Rating +} + + +"""A file found within a Winamp Skin's .wsz archive""" +type ArchiveFile { + """Filename of the file within the archive""" + filename: String, +} + +"""A tweet made by @winampskins mentioning a Winamp skin""" +type Tweet { + """URL of the tweet""" + url: String + + """Number of likes the tweet has received. Updated nightly. (Note: Recent likes on older tweets may not be reflected here)""" + likes: Int + + """Number of retweets the tweet has received. Updated nightly. (Note: Recent retweets on older tweets may not be reflected here)""" + retweets: Int + skin: Skin +} + +type InternetArchiveItem { + """The Internet Archive's unique identifier for this item""" + identifier: String + + """The URL where this item can be viewed on the Internet Archive""" + url: String + + """The skin that this item contains""" + skin: Skin +} + +"""A collection of classic Winamp skins""" +type SkinsConnection { + """The total number of skins""" + count: Int + + """The list of skins""" + nodes: [Skin] +} + +type User { + username: String +} + +enum SkinsSortOption { + """ + the Museum's (https://skins.webamp.org) special sorting rules. + + Roughly speaking, it's: + + 1. The four classic default skins + 2. Tweeted skins first (sorted by the number of likes/retweets) + 3. Approved, but not tweeted yet, skins + 4. Unreviwed skins + 5. Rejected skins + 6. NSFW skins + """ + MUSEUM +} + +enum SkinsFilterOption { + """All the skins that have been approved for tweeting""" + APPROVED +} + +type Query { + """The currently authenticated user, if any.""" + me: User + + """Get a skin by its MD5 hash""" + fetch_skin_by_md5(md5: String!): Skin + + """ + All skins in the database + + **Note:** We don't currently support combining sorting and filtering. + """ + skins( + first: Int, + offset: Int, + sort: SkinsSortOption, + filter: SkinsFilterOption + ): SkinsConnection +}`); + +class SkinsConnection { + _first: number; + _offset: number; + _sort: string; + _filter: string; + constructor(first: number, offset: number, sort: string, filter: string) { + this._first = first; + this._offset = offset; + this._filter = filter; + this._sort = sort; + } + async count() { + const count = await knex("skins") + .where({ skin_type: 1 }) + .count("*", { as: "count" }); + return count[0].count; + } + async nodes(args, ctx) { + if (this._sort === "MUSEUM") { + if (this._filter) { + throw new Error( + "We don't support combining sorting and filtering at the same time." + ); + } + const items = await Skins.getMuseumPage({ + first: this._first, + offset: this._offset, + }); + return Promise.all( + items.map(async (item) => { + const model = await SkinModel.fromMd5Assert(ctx, item.md5); + return new SkinResolver(model); + }) + ); + } + + let query = knex("skins"); + + if (this._filter === "APPROVED") { + query = query + .leftJoin("skin_reviews", "skin_reviews.skin_md5", "=", "skins.md5") + .where("review", "APPROVED"); + } + + const skins = await query + .where({ skin_type: 1 }) + .select() + .limit(this._first) + .offset(this._offset); + return skins.map((skin) => { + return new SkinResolver(new SkinModel(ctx, skin)); + }); + } +} + +const root = { + async fetch_skin_by_md5({ md5 }, { ctx }) { + const skin = await SkinModel.fromMd5(ctx, md5); + if (skin == null) { + return null; + } + return new SkinResolver(skin); + }, + async skins({ first, offset, sort, filter }) { + if (first > 1000) { + throw new Error("Maximum limit is 1000"); + } + return new SkinsConnection(first, offset, sort, filter); + }, + me() { + return new UserResolver(); + }, +}; + +router.use( + "/", + graphqlHTTP({ + typeResolver(_type) { + throw new Error("We probably need to implement typeResolver"); + }, + schema: schema, + rootValue: root, + graphiql: true, + }) +); + +export default router; diff --git a/packages/skin-database/api/graphql/resolvers/ArchiveFileResolver.ts b/packages/skin-database/api/graphql/resolvers/ArchiveFileResolver.ts new file mode 100644 index 00000000..229f6c1f --- /dev/null +++ b/packages/skin-database/api/graphql/resolvers/ArchiveFileResolver.ts @@ -0,0 +1,11 @@ +import ArchiveFileModel from "../../../data/ArchiveFileModel"; + +export default class ArchiveFileResolver { + _model: ArchiveFileModel; + constructor(model: ArchiveFileModel) { + this._model = model; + } + filename() { + return this._model.getFileName(); + } +} diff --git a/packages/skin-database/api/graphql/resolvers/InternetArchiveItemResolver.ts b/packages/skin-database/api/graphql/resolvers/InternetArchiveItemResolver.ts new file mode 100644 index 00000000..77e23c0b --- /dev/null +++ b/packages/skin-database/api/graphql/resolvers/InternetArchiveItemResolver.ts @@ -0,0 +1,22 @@ +import IaItemModel from "../../../data/IaItemModel"; +import SkinResolver from "./SkinResolver"; + +export default class InternetArchiveItemResolver { + _model: IaItemModel; + constructor(model: IaItemModel) { + this._model = model; + } + identifier() { + return this._model.getIdentifier(); + } + url() { + return this._model.getUrl(); + } + async skin() { + const skin = await this._model.getSkin(); + if (skin == null) { + return null; + } + return new SkinResolver(skin); + } +} diff --git a/packages/skin-database/api/graphql/resolvers/ReviewResolver.ts b/packages/skin-database/api/graphql/resolvers/ReviewResolver.ts new file mode 100644 index 00000000..6447c492 --- /dev/null +++ b/packages/skin-database/api/graphql/resolvers/ReviewResolver.ts @@ -0,0 +1,14 @@ +import { ReviewRow } from "../../../types"; + +export default class ReviewResolver { + _model: ReviewRow; + constructor(model: ReviewRow) { + this._model = model; + } + reviewer() { + return this._model.reviewer; + } + rating() { + return this._model.review; + } +} diff --git a/packages/skin-database/api/graphql/resolvers/SkinResolver.ts b/packages/skin-database/api/graphql/resolvers/SkinResolver.ts new file mode 100644 index 00000000..503e41e5 --- /dev/null +++ b/packages/skin-database/api/graphql/resolvers/SkinResolver.ts @@ -0,0 +1,65 @@ +import SkinModel from "../../../data/SkinModel"; +import ArchiveFileResolver from "./ArchiveFileResolver"; +import InternetArchiveItemResolver from "./InternetArchiveItemResolver"; +import ReviewResolver from "./ReviewResolver"; +import TweetResolver from "./TweetResolver"; + +export default class SkinResolver { + _model: SkinModel; + constructor(model: SkinModel) { + this._model = model; + } + id() { + return this._model.getId(); + } + md5() { + return this._model.getMd5(); + } + filename() { + return this._model.getFileName(); + } + museum_url() { + return this._model.getMuseumUrl(); + } + webamp_url() { + return this._model.getWebampUrl(); + } + screenshot_url() { + return this._model.getScreenshotUrl(); + } + download_url() { + return this._model.getSkinUrl(); + } + readme_text() { + return this._model.getReadme(); + } + nsfw() { + return this._model.getIsNsfw(); + } + average_color() { + return this._model.getAverageColor(); + } + tweeted() { + return this._model.tweeted(); + } + async tweets() { + const tweets = await this._model.getTweets(); + return tweets.map((tweetModel) => new TweetResolver(tweetModel)); + } + async archive_files() { + const files = await this._model.getArchiveFiles(); + return files.map((file) => new ArchiveFileResolver(file)); + } + async internet_archive_item() { + const item = await this._model.getIaItem(); + if (item == null) { + return null; + } + return new InternetArchiveItemResolver(item); + } + + async reviews() { + const reviews = await this._model.getReviews(); + return reviews.map((row) => new ReviewResolver(row)); + } +} diff --git a/packages/skin-database/api/graphql/resolvers/TweetResolver.ts b/packages/skin-database/api/graphql/resolvers/TweetResolver.ts new file mode 100644 index 00000000..3ca12632 --- /dev/null +++ b/packages/skin-database/api/graphql/resolvers/TweetResolver.ts @@ -0,0 +1,26 @@ +import TweetModel from "../../../data/TweetModel"; +import SkinResolver from "./SkinResolver"; + +export default class TweetResolver { + _model: TweetModel; + constructor(model: TweetModel) { + this._model = model; + } + + url() { + return this._model.getUrl(); + } + likes() { + return this._model.getLikes(); + } + retweets() { + return this._model.getRetweets(); + } + async skin() { + const skin = await this._model.getSkin(); + if (skin == null) { + return null; + } + return new SkinResolver(skin); + } +} diff --git a/packages/skin-database/api/graphql/resolvers/UserResolver.ts b/packages/skin-database/api/graphql/resolvers/UserResolver.ts new file mode 100644 index 00000000..7cd46c15 --- /dev/null +++ b/packages/skin-database/api/graphql/resolvers/UserResolver.ts @@ -0,0 +1,6 @@ +export default class UserResolver { + username(_args, ctx) { + // For now every user is the current user. + return ctx.user.username; + } +} diff --git a/packages/skin-database/data/SkinModel.ts b/packages/skin-database/data/SkinModel.ts index b83d672a..05bee3f6 100644 --- a/packages/skin-database/data/SkinModel.ts +++ b/packages/skin-database/data/SkinModel.ts @@ -35,8 +35,8 @@ export default class SkinModel { md5: string ): Promise { const skin = await SkinModel.fromMd5(ctx, md5); - if(skin == null) { - throw new Error(`Expected to find skin with md5 "${md5}".`); + if (skin == null) { + throw new Error(`Expected to find skin with md5 "${md5}".`); } return skin; } @@ -79,6 +79,10 @@ export default class SkinModel { throw new Error(`Unknown skin_type ${this.row.skin_type}`); } + getId(): number { + return this.row.id; + } + async tweeted(): Promise { return (await this.getTweet()) != null; } diff --git a/packages/skin-database/package.json b/packages/skin-database/package.json index 016f4a24..ffb126b4 100644 --- a/packages/skin-database/package.json +++ b/packages/skin-database/package.json @@ -24,7 +24,9 @@ "express": "^4.17.1", "express-async-handler": "^1.1.4", "express-fileupload": "^1.1.7-alpha.3", + "express-graphql": "^0.12.0", "express-sitemap-xml": "^2.0.0", + "graphql": "14.7.0", "imagemin": "^7.0.0", "imagemin-optipng": "^7.0.0", "knex": "^0.21.1", @@ -35,7 +37,7 @@ "sharp": "^0.28.0", "sqlite3": "^5.0.2", "temp": "^0.9.0", - "ts-node": "^8.10.2", + "ts-node": "^10.5.0", "twit": "^2.2.11", "winston": "^3.2.1", "yargs": "^13.2.4" @@ -63,5 +65,8 @@ "prettier": "^2.3.2", "supertest": "^6.0.1", "typescript": "^3.8.3" + }, + "resolutions": { + "graphql": "15.3.0" } } diff --git a/packages/skin-database/types.ts b/packages/skin-database/types.ts index 2d6b472a..f63479fa 100644 --- a/packages/skin-database/types.ts +++ b/packages/skin-database/types.ts @@ -8,6 +8,7 @@ export type TweetStatus = export type SkinType = "MODERN" | "CLASSIC"; export type SkinRow = { + id: number; md5: string; skin_type: number; emails: string; diff --git a/yarn.lock b/yarn.lock index 761f3aea..95595fa4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2132,6 +2132,18 @@ exec-sh "^0.3.2" minimist "^1.2.0" +"@cspotcode/source-map-consumer@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" + integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== + +"@cspotcode/source-map-support@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" + integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== + dependencies: + "@cspotcode/source-map-consumer" "0.8.0" + "@discordjs/collection@^0.1.6": version "0.1.6" resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.1.6.tgz#9e9a7637f4e4e0688fd8b2b5c63133c91607682c" @@ -2948,6 +2960,26 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== +"@tsconfig/node10@^1.0.7": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" + integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + +"@tsconfig/node12@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + +"@tsconfig/node14@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" + integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + +"@tsconfig/node16@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== + "@types/anymatch@*": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -3729,6 +3761,14 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +accepts@^1.3.7: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -3772,6 +3812,11 @@ acorn-walk@^8.0.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.0.0.tgz#56ae4c0f434a45fff4a125e7ea95fa9c98f67a16" integrity sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA== +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + acorn@^5.5.3: version "5.7.4" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" @@ -3793,6 +3838,11 @@ acorn@^8.0.4: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.4.tgz#7a3ae4191466a6984eee0fe3407a4f3aa9db8354" integrity sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ== +acorn@^8.4.1: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + add-dom-event-listener@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310" @@ -4925,6 +4975,11 @@ bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + cac@^6.7.2: version "6.7.3" resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.3.tgz#10410b8611677990cc2e3c8b576d471c1d71b768" @@ -5579,7 +5634,7 @@ content-disposition@0.5.3, content-disposition@^0.5.2: dependencies: safe-buffer "5.1.2" -content-type@~1.0.4: +content-type@^1.0.4, content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" @@ -5727,6 +5782,11 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -7297,6 +7357,16 @@ express-fileupload@^1.1.7-alpha.3: dependencies: busboy "^0.3.1" +express-graphql@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/express-graphql/-/express-graphql-0.12.0.tgz#58deabc309909ca2c9fe2f83f5fbe94429aa23df" + 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-sitemap-xml@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/express-sitemap-xml/-/express-sitemap-xml-2.0.0.tgz#190b4993458007a61b4747a66e3a723121a207f6" @@ -8279,6 +8349,13 @@ graceful-fs@^4.2.3: version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" +graphql@14.7.0: + version "14.7.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.7.0.tgz#7fa79a80a69be4a31c27dda824dc04dac2035a72" + integrity sha512-l0xWZpoPKpppFzMfvVyFmp9vLN7w/ZZJPefUicMCepfJeQ8sMcztloGYY9DfjVPo6tIUDzU5Hw3MUbIjj9AVVA== + dependencies: + iterall "^1.2.2" + growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -8588,6 +8665,28 @@ http-errors@1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-errors@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507" + 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@1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.1" + http-errors@~1.6.2: version "1.6.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" @@ -9472,6 +9571,11 @@ isurl@^1.0.0-alpha5: has-to-string-tag-x "^1.2.0" is-object "^1.0.1" +iterall@^1.2.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" + integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== + jest-changed-files@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039" @@ -11375,12 +11479,24 @@ mime-db@1.44.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.28.0: version "1.44.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" +mime-db@1.51.0: + version "1.51.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c" + integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g== + mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" dependencies: mime-db "1.44.0" +mime-types@~2.1.34: + version "2.1.34" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24" + integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A== + dependencies: + mime-db "1.51.0" + mime@1.6.0, mime@^1.3.4, mime@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -11689,6 +11805,11 @@ negotiator@0.6.2, negotiator@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + neo-async@^2.5.0, neo-async@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" @@ -13449,6 +13570,16 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" +raw-body@^2.4.1: + version "2.4.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c" + integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g== + dependencies: + bytes "3.1.2" + http-errors "1.8.1" + iconv-lite "0.4.24" + unpipe "1.0.0" + rc-align@^2.4.0: version "2.4.5" resolved "https://registry.yarnpkg.com/rc-align/-/rc-align-2.4.5.tgz#c941a586f59d1017f23a428f0b468663fb7102ab" @@ -14439,6 +14570,11 @@ setprototypeof@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" @@ -14687,7 +14823,7 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.16, source-map-support@^0.5.17, source-map-support@^0.5.6, source-map-support@~0.5.12: +source-map-support@^0.5.16, source-map-support@^0.5.6, source-map-support@~0.5.12: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" dependencies: @@ -15513,6 +15649,11 @@ toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + token-types@^1.0.1: version "1.3.2" resolved "https://registry.yarnpkg.com/token-types/-/token-types-1.3.2.tgz#2cd58386be7e1ff316cd6b8d5aa25ea070deb2f0" @@ -15594,14 +15735,23 @@ ts-interface-checker@^0.1.9: resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== -ts-node@^8.10.2: - version "8.10.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" +ts-node@^10.5.0: + version "10.5.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.5.0.tgz#618bef5854c1fbbedf5e31465cbb224a1d524ef9" + integrity sha512-6kEJKwVxAJ35W4akuiysfKwKmjkbYxwQMTBaAxo9KKAx/Yd26mPUyhGz3ji+EsJoAgrLqVsYHNuuYwQe22lbtw== dependencies: + "@cspotcode/source-map-support" "0.7.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" arg "^4.1.0" + create-require "^1.1.0" diff "^4.0.1" make-error "^1.1.1" - source-map-support "^0.5.17" + v8-compile-cache-lib "^3.0.0" yn "3.1.1" tslib@^1.10.0: @@ -15992,6 +16142,11 @@ uuid@^8.3.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +v8-compile-cache-lib@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8" + integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA== + v8-compile-cache@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe"