diff --git a/packages/skin-database/api/app.ts b/packages/skin-database/api/app.ts index 5bf0728d..cef4c1ad 100644 --- a/packages/skin-database/api/app.ts +++ b/packages/skin-database/api/app.ts @@ -21,7 +21,12 @@ export type ApiAction = | { 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; url?: string } + | { + type: "GOT_FEEDBACK"; + message: string; + email?: string | null; + url?: string | null; + } | { type: "SYNCED_TO_ARCHIVE"; successes: number; errors: number } | { type: "STARTED_SYNC_TO_ARCHIVE"; count: number } | { @@ -181,7 +186,7 @@ const allowList = [ /http:\/\/localhost:3000/, /http:\/\/localhost:3001/, /netlify.app/, - /https:\/\/dustinbrett.com/ + /https:\/\/dustinbrett.com/, ]; const corsOptions: CorsOptions = { diff --git a/packages/skin-database/api/graphql/ModernSkinsConnection.ts b/packages/skin-database/api/graphql/ModernSkinsConnection.ts index cf4dc672..f572ade9 100644 --- a/packages/skin-database/api/graphql/ModernSkinsConnection.ts +++ b/packages/skin-database/api/graphql/ModernSkinsConnection.ts @@ -55,8 +55,8 @@ export async function modern_skins( first = 10, offset = 0, }: { - first?: Int; - offset?: Int; + first: Int; + offset: Int; } ): Promise { if (first > 1000) { diff --git a/packages/skin-database/api/graphql/SkinsConnection.ts b/packages/skin-database/api/graphql/SkinsConnection.ts index 497e9831..3513cf01 100644 --- a/packages/skin-database/api/graphql/SkinsConnection.ts +++ b/packages/skin-database/api/graphql/SkinsConnection.ts @@ -45,9 +45,14 @@ async function getSkinMuseumPageFromCache(first: number, offset: number) { export default class SkinsConnection { _first: number; _offset: number; - _sort?: string; - _filter?: string; - constructor(first: number, offset: number, sort?: string, filter?: string) { + _sort?: string | null; + _filter?: string | null; + constructor( + first: number, + offset: number, + sort?: string | null, + filter?: string | null + ) { this._first = first; this._offset = offset; this._filter = filter; @@ -137,10 +142,10 @@ export function skins( sort, filter, }: { - first?: Int; - offset?: Int; - sort?: SkinsSortOption; - filter?: SkinsFilterOption; + first: Int; + offset: Int; + sort?: SkinsSortOption | null; + filter?: SkinsFilterOption | null; } ): SkinsConnection { if (first > 1000) { diff --git a/packages/skin-database/api/graphql/TweetsConnection.ts b/packages/skin-database/api/graphql/TweetsConnection.ts index b61cf4a4..b333992d 100644 --- a/packages/skin-database/api/graphql/TweetsConnection.ts +++ b/packages/skin-database/api/graphql/TweetsConnection.ts @@ -9,14 +9,14 @@ import { GqlCtx } from "./GqlCtx"; export type TweetsSortOption = "LIKES" | "RETWEETS"; /** - * A collection of tweets made by the @winampskins bot + * A collection of tweets made by the `@winampskins` bot * @gqlType */ export default class TweetsConnection { _first: number; _offset: number; - _sort?: TweetsSortOption; - constructor(first: number, offset: number, sort?: TweetsSortOption) { + _sort?: TweetsSortOption | null; + constructor(first: number, offset: number, sort?: TweetsSortOption | null) { this._first = first; this._offset = offset; this._sort = sort; @@ -61,7 +61,7 @@ export default class TweetsConnection { } /** - * Tweets tweeted by @winampskins + * Tweets tweeted by `@winampskins` * @gqlField */ export async function tweets( @@ -71,9 +71,9 @@ export async function tweets( offset = 0, sort, }: { - first?: Int; - offset?: Int; - sort?: TweetsSortOption; + first: Int; + offset: Int; + sort?: TweetsSortOption | null; } ): Promise { if (first > 1000) { diff --git a/packages/skin-database/api/graphql/grats-schema.graphql b/packages/skin-database/api/graphql/grats-schema.graphql deleted file mode 100644 index beaf1d52..00000000 --- a/packages/skin-database/api/graphql/grats-schema.graphql +++ /dev/null @@ -1,497 +0,0 @@ -schema { - query: Query - mutation: Mutation -} - -directive @exported(filename: String!, functionName: String!) on FIELD_DEFINITION - -directive @methodName(name: String!) on FIELD_DEFINITION - -"""A file found within a Winamp Skin's .wsz archive""" -type ArchiveFile { - """ - The date on the file inside the archive. Given in simplified extended ISO - format (ISO 8601). - """ - date: String - """The md5 hash of the file within the archive""" - file_md5: String - """Filename of the file within the archive""" - filename: String - """Is the file a directory?""" - is_directory: Boolean - """ - The uncompressed size of the file in bytes. - - **Note:** Will be `null` for directories - """ - size: Int - """The skin in which this file was found""" - skin: Skin - """The content of the file, if it's a text file""" - text_content: String - """ - A URL to download the file. **Note:** This is powered by a little - serverless Cloudflare function which tries to exctact the file on the fly. - It may not work for all files. - """ - url: String -} - -"""A classic Winamp skin""" -type ClassicSkin implements Node & Skin { - """List of files contained within the skin's .wsz archive""" - archive_files: [ArchiveFile] - """String representation (rgb usually) of the skin's average color""" - average_color: 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( - """ - If true, the the correct file extension (.wsz or .wal) will be . - Otherwise, the original user-uploaded file extension will be used. - """ - normalize_extension: Boolean = false - ): String - """Does the skin include sprite sheets for the media library?""" - has_media_library: Boolean - """GraphQL ID of the skin""" - id: ID! - """The skin's "item" at archive.org""" - internet_archive_item: InternetArchiveItem - """ - The date on which this skin was last updated in the Algolia search index. - Given in simplified extended ISO format (ISO 8601). - """ - last_algolia_index_update_date: String - """MD5 hash of the skin's file""" - md5: String - """URL of the skin on the Winamp Skin Museum""" - museum_url: String - """Has the skin been flagged as "not safe for wrok"?""" - nsfw: Boolean - """Text of the readme file extracted from the skin""" - readme_text: String - """ - Times that the skin has been reviewed either on the Museum's Tinder-style - reivew page, or via the Discord bot. - """ - reviews: [Review] - """URL of a screenshot of the skin""" - screenshot_url: String - """The number of transparent pixels rendered by the skin.""" - transparent_pixels: Int - """Has the skin been tweeted?""" - tweeted: Boolean - """List of @winampskins tweets that mentioned the skin.""" - tweets: [Tweet] - """URL of webamp.org with the skin loaded""" - webamp_url: String -} - -"""Statistics about the contents of the Museum's database.""" -type DatabaseStatistics { - """ - The number of skins that have been approved for tweeting. This includes both - tweeted and untweeted skins. - - **Note:** Skins can be both approved and rejected by different users. - """ - approved_skins_count: Int - """ - The number of skins that have been marked as NSFW. - - **Note:** Skins can be approved and rejected by different users. - **Note:** Generally skins that have been marked NSFW are also marked as rejected. - """ - nsfw_skins_count: Int - """ - The number of skins that have been rejected for tweeting. - - **Note:** Skins can be both approved and rejected by different users. - **Note:** Generally skins that have been marked NSFW are also marked as rejected. - """ - rejected_skins_count: Int - """ - The number of skins that have been approved for tweeting, but not yet tweeted. - """ - tweetable_skins_count: Int - """ - The number of skins in the Museum that have been tweeted by @winampskins - """ - tweeted_skins_count: Int - """The total number of classic skins in the Museum's database""" - unique_classic_skins_count: Int - """The number of skins that have never been reviewed.""" - unreviewed_skins_count: Int - """Skins uploads that have errored during processing.""" - uploads_in_error_state_count: Int - """ - Skins uplaods awaiting processing. This can happen when there are a large - number of skin uplaods at the same time, or when the skin uploading processing - pipeline gets stuck. - """ - uploads_pending_processing_count: Int - """ - Number of skins that have been uploaded to the Museum via the web interface. - """ - web_uploads_count: Int -} - -type InternetArchiveItem { - """The Internet Archive's unique identifier for this item""" - identifier: String - """ - The date and time that we last scraped this item's metadata. - **Note:** This field is temporary and will be removed in the future. - The date format is just what we get from the database, and it's ambiguous. - """ - last_metadata_scrape_date_UNSTABLE: String - """URL to get the Internet Archive's metadata for this item in JSON form.""" - metadata_url: String - """ - Our cached version of the metadata avaliable at \`metadata_url\` (above) - """ - raw_metadata_json: String - """The skin that this item contains""" - skin: Skin - """The URL where this item can be viewed on the Internet Archive""" - url: String -} - -""" -A "modern" Winamp skin. These skins use the `.wal` file extension and are free-form. - -Most functionality in the Winamp Skin Museum is centered around "classic" skins, -which are currently called just `Skin` in this schema. -""" -type ModernSkin implements Node & Skin { - """List of files contained within the skin's .wsz archive""" - archive_files: [ArchiveFile] - average_color: String @deprecated(reason: "Needed for migration") - """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( - """ - If true, the the correct file extension (.wsz or .wal) will be . - Otherwise, the original user-uploaded file extension will be used. - """ - normalize_extension: Boolean = false - ): String - """GraphQL ID of the skin""" - id: ID! - """The skin's "item" at archive.org""" - internet_archive_item: InternetArchiveItem - """MD5 hash of the skin's file""" - md5: String - museum_url: String @deprecated(reason: "Needed for migration") - nsfw: Boolean @deprecated(reason: "Needed for migration") - readme_text: String @deprecated(reason: "Needed for migration") - """ - Times that the skin has been reviewed either on the Museum's Tinder-style - reivew page, or via the Discord bot. - """ - reviews: [Review] - screenshot_url: String @deprecated(reason: "Needed for migration") - """Has the skin been tweeted?""" - tweeted: Boolean - """List of @winampskins tweets that mentioned the skin.""" - tweets: [Tweet] - webamp_url: String @deprecated(reason: "Needed for migration") -} - -"""A collection of "modern" Winamp skins""" -type ModernSkinsConnection { - """The total number of skins matching the filter""" - count: Int - """The list of skins""" - nodes: [ModernSkin] -} - -type Mutation { - """ - Approve skin for tweeting - - **Note:** Requires being logged in - """ - approve_skin(md5: String!): Boolean - """ - Mark a skin as NSFW - - **Note:** Requires being logged in - """ - mark_skin_nsfw(md5: String!): Boolean - """ - Reject skin for tweeting - - **Note:** Requires being logged in - """ - reject_skin(md5: String!): Boolean - """ - Request that an admin check if this skin is NSFW. - Unlike other review mutaiton endpoints, this one does not require being logged - in. - """ - request_nsfw_review_for_skin(md5: String!): Boolean - """ - Send a message to the admin of the site. Currently this appears in Discord. - """ - send_feedback(email: String, message: String!, url: String): Boolean - """Mutations for the upload flow""" - upload: UploadMutations -} - -""" -A globally unique object. The `id` here is intended only for use within -GraphQL. -https://graphql.org/learn/global-object-identification/ -""" -interface Node { - id: ID! -} - -type Query { - """ - Fetch archive file by it's MD5 hash - - Get information about a file found within a skin's wsz/wal/zip archive. - """ - fetch_archive_file_by_md5(md5: String!): ArchiveFile - """ - Get an archive.org item by its identifier. You can find this in the URL: - - https://archive.org/details// - """ - fetch_internet_archive_item_by_identifier(identifier: String!): InternetArchiveItem - """Get a skin by its MD5 hash""" - fetch_skin_by_md5(md5: String!): Skin - """Get a tweet by its URL""" - fetch_tweet_by_url(url: String!): Tweet - """The currently authenticated user, if any.""" - me: User - """All modern skins in the database""" - modern_skins(first: Int = 10, offset: Int = 0): ModernSkinsConnection - """ - Get a globally unique object by its ID. - - https://graphql.org/learn/global-object-identification/ - """ - node(id: ID!): Node - """ - Search the database using the Algolia search index used by the Museum. - - Useful for locating a particular skin. - """ - search_skins(first: Int = 10, offset: Int = 0, query: String!): [Skin] - """A random skin that needs to be reviewed""" - skin_to_review: Skin - """ - All classic skins in the database - - **Note:** We don't currently support combining sorting and filtering. - """ - skins(filter: SkinsFilterOption, first: Int = 10, offset: Int = 0, sort: SkinsSortOption): SkinsConnection - """A namespace for statistics about the database""" - statistics: DatabaseStatistics - """Tweets tweeted by @winampskins""" - tweets(first: Int = 10, offset: Int = 0, sort: TweetsSortOption): TweetsConnection - """Get the status of a batch of uploads by ids""" - upload_statuses(ids: [String!]!): [SkinUpload] - """Get the status of a batch of uploads by md5s""" - upload_statuses_by_md5(md5s: [String!]!): [SkinUpload] @deprecated(reason: "Prefer `upload_statuses` instead, were we operate on ids.") -} - -"""The judgement made about a skin by a moderator""" -enum Rating { - APPROVED - NSFW - REJECTED -} - -""" -A review of a skin. Done either on the Museum's Tinder-style -reivew page, or via the Discord bot. -""" -type Review { - """The rating that the user gave the skin""" - rating: Rating - """ - 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 skin that was reviewed""" - skin: Skin -} - -""" -A Winamp skin. Could be modern or classic. - -**Note**: At some point in the future, this might be renamed to `Skin`. -""" -interface Skin { - """List of files contained within the skin's .wsz archive""" - archive_files: [ArchiveFile] - average_color: String @deprecated(reason: "Needed for migration") - """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( - """ - If true, the the correct file extension (.wsz or .wal) will be . - Otherwise, the original user-uploaded file extension will be used. - """ - normalize_extension: Boolean = false - ): String - """GraphQL ID of the skin""" - id: ID! - """The skin's "item" at archive.org""" - internet_archive_item: InternetArchiveItem - """MD5 hash of the skin's file""" - md5: String - museum_url: String @deprecated(reason: "Needed for migration") - nsfw: Boolean @deprecated(reason: "Needed for migration") - readme_text: String @deprecated(reason: "Needed for migration") - """ - Times that the skin has been reviewed either on the Museum's Tinder-style - reivew page, or via the Discord bot. - """ - reviews: [Review] - screenshot_url: String @deprecated(reason: "Needed for migration") - """Has the skin been tweeted?""" - tweeted: Boolean - """List of @winampskins tweets that mentioned the skin.""" - tweets: [Tweet] - webamp_url: String @deprecated(reason: "Needed for migration") -} - -"""Information about an attempt to upload a skin to the Museum.""" -type SkinUpload { - id: String - """ - Skin that was uploaded. **Note:** This is null if the skin has not yet been - fully processed. (status == ARCHIVED) - """ - skin: Skin - status: SkinUploadStatus - """Md5 hash given when requesting the upload URL.""" - upload_md5: String -} - -""" -The current status of a pending upload. - -**Note:** Expect more values here as we try to be more transparent about -the status of a pending uploads. -""" -enum SkinUploadStatus { - ARCHIVED - DELAYED - ERRORED - UPLOAD_REPORTED - URL_REQUESTED -} - -"""A collection of classic Winamp skins""" -type SkinsConnection { - """The total number of skins matching the filter""" - count: Int - """The list of skins""" - nodes: [Skin] -} - -enum SkinsFilterOption { - APPROVED - NSFW - REJECTED - TWEETED -} - -enum SkinsSortOption { - MUSEUM -} - -"""A tweet made by @winampskins mentioning a Winamp skin""" -type Tweet { - """ - 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 - """The skin featured in this Tweet""" - skin: Skin - """ - URL of the tweet. **Note:** Early on in the bot's life we just recorded - _which_ skins were tweeted, not any info about the actual tweet. This means we - don't always know the URL of the tweet. - """ - url: String -} - -"""A collection of tweets made by the @winampskins bot""" -type TweetsConnection { - """The total number of tweets""" - count: Int - """The list of tweets""" - nodes: [Tweet] -} - -enum TweetsSortOption { - LIKES - RETWEETS -} - -""" -Mutations for the upload flow - -1. The user finds the md5 hash of their local files. -2. (`get_upload_urls`) The user requests upload URLs for each of their files. -3. The server returns upload URLs for each of their files which are not already in the collection. -4. The user uploads each of their files to the URLs returned in step 3. -5. (`report_skin_uploaded`) The user notifies the server that they're done uploading. -6. (TODO) The user polls for the status of their uploads. -""" -type UploadMutations { - """ - Get a (possibly incompelte) list of UploadUrls for each of the files. If an - UploadUrl is not returned for a given hash, it means the file is already in - the collection. - """ - get_upload_urls(files: [UploadUrlRequest!]!): [UploadUrl] - """Notify the server that the user is done uploading.""" - report_skin_uploaded(id: String!, md5: String!): Boolean -} - -""" -A URL that the client can use to upload a skin to S3, and then notify the server -when they're done. -""" -type UploadUrl { - id: String - md5: String - url: String -} - -"""Input object used for a user to request an UploadUrl""" -input UploadUrlRequest { - filename: String! - md5: String! -} - -type User { - username: String -} \ No newline at end of file diff --git a/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts b/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts index ae50ec97..8f5be5f2 100644 --- a/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts +++ b/packages/skin-database/api/graphql/resolvers/ClassicSkinResolver.ts @@ -32,7 +32,7 @@ export default class ClassicSkinResolver }: { /** If true, the the correct file extension (.wsz or .wal) will be . Otherwise, the original user-uploaded file extension will be used. */ - normalize_extension?: boolean; + normalize_extension?: boolean | null; }): Promise { const filename = await this._model.getFileName(); if (normalize_extension) { @@ -61,7 +61,7 @@ export default class ClassicSkinResolver } /** - * List of @winampskins tweets that mentioned the skin. + * List of `@winampskins` tweets that mentioned the skin. * @gqlField */ tweets(): Promise> { return super.tweets(); diff --git a/packages/skin-database/api/graphql/resolvers/CommonSkinResolver.ts b/packages/skin-database/api/graphql/resolvers/CommonSkinResolver.ts index bf885d28..9c4b5e3f 100644 --- a/packages/skin-database/api/graphql/resolvers/CommonSkinResolver.ts +++ b/packages/skin-database/api/graphql/resolvers/CommonSkinResolver.ts @@ -32,7 +32,7 @@ export interface ISkin { * If true, the the correct file extension (.wsz or .wal) will be . * Otherwise, the original user-uploaded file extension will be used. */ - normalize_extension?: boolean; + normalize_extension?: boolean | null; }): Promise; /** @@ -54,7 +54,7 @@ export interface ISkin { tweeted(): Promise; /** - * List of @winampskins tweets that mentioned the skin. + * List of `@winampskins` tweets that mentioned the skin. * @gqlField */ tweets(): Promise>; diff --git a/packages/skin-database/api/graphql/resolvers/DatabaseStatisticsResolver.ts b/packages/skin-database/api/graphql/resolvers/DatabaseStatisticsResolver.ts index 2205a727..30655971 100644 --- a/packages/skin-database/api/graphql/resolvers/DatabaseStatisticsResolver.ts +++ b/packages/skin-database/api/graphql/resolvers/DatabaseStatisticsResolver.ts @@ -15,7 +15,7 @@ export default class DatabaseStatisticsResolver { return Skins.getClassicSkinCount(); } /** - * The number of skins in the Museum that have been tweeted by @winampskins + * The number of skins in the Museum that have been tweeted by `@winampskins` * @gqlField */ tweeted_skins_count(): Promise { diff --git a/packages/skin-database/api/graphql/resolvers/ModernSkinResolver.ts b/packages/skin-database/api/graphql/resolvers/ModernSkinResolver.ts index 88e326d8..d6e9d941 100644 --- a/packages/skin-database/api/graphql/resolvers/ModernSkinResolver.ts +++ b/packages/skin-database/api/graphql/resolvers/ModernSkinResolver.ts @@ -33,7 +33,7 @@ export default class ModernSkinResolver }: { /** If true, the the correct file extension (.wsz or .wal) will be . Otherwise, the original user-uploaded file extension will be used. */ - normalize_extension?: boolean; + normalize_extension?: boolean | null; }): Promise { const filename = await this._model.getFileName(); if (normalize_extension) { @@ -72,7 +72,7 @@ export default class ModernSkinResolver return super.tweeted(); } /** - * List of @winampskins tweets that mentioned the skin. + * List of `@winampskins` tweets that mentioned the skin. * @gqlField */ async tweets(): Promise> { return super.tweets(); diff --git a/packages/skin-database/api/graphql/resolvers/MutationResolver.ts b/packages/skin-database/api/graphql/resolvers/MutationResolver.ts index 2bc3d2fa..8d42341a 100644 --- a/packages/skin-database/api/graphql/resolvers/MutationResolver.ts +++ b/packages/skin-database/api/graphql/resolvers/MutationResolver.ts @@ -18,7 +18,11 @@ export type MutationResolver = unknown; * @gqlField */ export async function send_feedback( _: MutationResolver, - { message, email, url }: { message: string; email?: string; url?: string }, + { + message, + email, + url, + }: { message: string; email?: string | null; url?: string | null }, req: GqlCtx ): Promise { req.notify({ diff --git a/packages/skin-database/api/graphql/resolvers/SkinResolver.ts b/packages/skin-database/api/graphql/resolvers/SkinResolver.ts index eb5f0a4a..555dafe6 100644 --- a/packages/skin-database/api/graphql/resolvers/SkinResolver.ts +++ b/packages/skin-database/api/graphql/resolvers/SkinResolver.ts @@ -43,11 +43,7 @@ export default class SkinResolver { */ export async function search_skins( _: RootResolver, - { - query, - first = 10, - offset = 0, - }: { query: string; first?: Int; offset?: Int }, + { query, first = 10, offset = 0 }: { query: string; first: Int; offset: Int }, { ctx }: GqlCtx ): Promise> { if (first > 1000) { diff --git a/packages/skin-database/api/graphql/resolvers/TweetResolver.ts b/packages/skin-database/api/graphql/resolvers/TweetResolver.ts index eacbd93a..53155d7c 100644 --- a/packages/skin-database/api/graphql/resolvers/TweetResolver.ts +++ b/packages/skin-database/api/graphql/resolvers/TweetResolver.ts @@ -6,7 +6,7 @@ import RootResolver from "./RootResolver"; import { GqlCtx } from "../GqlCtx"; /** - * A tweet made by @winampskins mentioning a Winamp skin + * A tweet made by `@winampskins` mentioning a Winamp skin * @gqlType Tweet */ export default class TweetResolver { diff --git a/packages/skin-database/api/graphql/schema.graphql b/packages/skin-database/api/graphql/schema.graphql index 94ac655e..da8b2333 100644 --- a/packages/skin-database/api/graphql/schema.graphql +++ b/packages/skin-database/api/graphql/schema.graphql @@ -1,6 +1,5 @@ # Schema generated by Grats (https://grats.capt.dev) # Do not manually edit. Regenerate by running `npx grats`. -# @generated """A file found within a Winamp Skin's .wsz archive""" type ArchiveFile { """ @@ -104,7 +103,7 @@ type ClassicSkin implements Node & Skin { """Has the skin been tweeted?""" tweeted: Boolean - """List of @winampskins tweets that mentioned the skin.""" + """List of `@winampskins` tweets that mentioned the skin.""" tweets: [Tweet] """URL of webamp.org with the skin loaded""" @@ -143,7 +142,7 @@ type DatabaseStatistics { tweetable_skins_count: Int """ - The number of skins in the Museum that have been tweeted by @winampskins + The number of skins in the Museum that have been tweeted by `@winampskins` """ tweeted_skins_count: Int @@ -243,7 +242,7 @@ type ModernSkin implements Node & Skin { """Has the skin been tweeted?""" tweeted: Boolean - """List of @winampskins tweets that mentioned the skin.""" + """List of `@winampskins` tweets that mentioned the skin.""" tweets: [Tweet] webamp_url: String @deprecated(reason: "Needed for migration") } @@ -329,7 +328,7 @@ type Query { me: User """All modern skins in the database""" - modern_skins(first: Int = 10, offset: Int = 0): ModernSkinsConnection + modern_skins(first: Int! = 10, offset: Int! = 0): ModernSkinsConnection """ Get a globally unique object by its ID. @@ -343,7 +342,7 @@ type Query { Useful for locating a particular skin. """ - search_skins(first: Int = 10, offset: Int = 0, query: String!): [Skin] + search_skins(first: Int! = 10, offset: Int! = 0, query: String!): [Skin] """A random skin that needs to be reviewed""" skin_to_review: Skin @@ -353,13 +352,13 @@ type Query { **Note:** We don't currently support combining sorting and filtering. """ - skins(filter: SkinsFilterOption, first: Int = 10, offset: Int = 0, sort: SkinsSortOption): SkinsConnection + skins(filter: SkinsFilterOption, first: Int! = 10, offset: Int! = 0, sort: SkinsSortOption): SkinsConnection """A namespace for statistics about the database""" statistics: DatabaseStatistics - """Tweets tweeted by @winampskins""" - tweets(first: Int = 10, offset: Int = 0, sort: TweetsSortOption): TweetsConnection + """Tweets tweeted by `@winampskins`""" + tweets(first: Int! = 10, offset: Int! = 0, sort: TweetsSortOption): TweetsConnection """Get the status of a batch of uploads by ids""" upload_statuses(ids: [String!]!): [SkinUpload] @@ -440,7 +439,7 @@ interface Skin { """Has the skin been tweeted?""" tweeted: Boolean - """List of @winampskins tweets that mentioned the skin.""" + """List of `@winampskins` tweets that mentioned the skin.""" tweets: [Tweet] webamp_url: String @deprecated(reason: "Needed for migration") } @@ -494,7 +493,7 @@ enum SkinsSortOption { MUSEUM } -"""A tweet made by @winampskins mentioning a Winamp skin""" +"""A tweet made by `@winampskins` mentioning a Winamp skin""" type Tweet { """ Number of likes the tweet has received. Updated nightly. (Note: Recent likes on older tweets may not be reflected here) @@ -517,7 +516,7 @@ type Tweet { url: String } -"""A collection of tweets made by the @winampskins bot""" +"""A collection of tweets made by the `@winampskins` bot""" type TweetsConnection { """The total number of tweets""" count: Int diff --git a/packages/skin-database/api/graphql/schema.ts b/packages/skin-database/api/graphql/schema.ts index 2cc34bfb..e192151c 100644 --- a/packages/skin-database/api/graphql/schema.ts +++ b/packages/skin-database/api/graphql/schema.ts @@ -1,89 +1,28 @@ /** * Executable schema generated by Grats (https://grats.capt.dev) * Do not manually edit. Regenerate by running `npx grats`. - * @generated */ -import { node as queryNodeResolver } from "./resolvers/NodeResolver"; -import { fetch_tweet_by_url as queryFetch_tweet_by_urlResolver } from "./resolvers/TweetResolver"; +import { fetch_archive_file_by_md5 as queryFetch_archive_file_by_md5Resolver } from "./resolvers/ArchiveFileResolver"; +import { fetch_internet_archive_item_by_identifier as queryFetch_internet_archive_item_by_identifierResolver } from "./resolvers/InternetArchiveItemResolver"; import { fetch_skin_by_md5 as queryFetch_skin_by_md5Resolver } from "./resolvers/ModernSkinResolver"; +import { fetch_tweet_by_url as queryFetch_tweet_by_urlResolver } from "./resolvers/TweetResolver"; +import { me as queryMeResolver } from "./resolvers/UserResolver"; +import { modern_skins as queryModern_skinsResolver } from "./ModernSkinsConnection"; +import { node as queryNodeResolver } from "./resolvers/NodeResolver"; import { search_skins as querySearch_skinsResolver } from "./resolvers/SkinResolver"; import { skin_to_review as querySkin_to_reviewResolver } from "./resolvers/SkinResolver"; -import { fetch_internet_archive_item_by_identifier as queryFetch_internet_archive_item_by_identifierResolver } from "./resolvers/InternetArchiveItemResolver"; -import { fetch_archive_file_by_md5 as queryFetch_archive_file_by_md5Resolver } from "./resolvers/ArchiveFileResolver"; -import { modern_skins as queryModern_skinsResolver } from "./ModernSkinsConnection"; import { skins as querySkinsResolver } from "./SkinsConnection"; -import { tweets as queryTweetsResolver } from "./TweetsConnection"; import { statistics as queryStatisticsResolver } from "./resolvers/DatabaseStatisticsResolver"; -import { upload_statuses_by_md5 as queryUpload_statuses_by_md5Resolver } from "./resolvers/SkinUploadResolver"; +import { tweets as queryTweetsResolver } from "./TweetsConnection"; import { upload_statuses as queryUpload_statusesResolver } from "./resolvers/SkinUploadResolver"; -import { me as queryMeResolver } from "./resolvers/UserResolver"; -import { send_feedback as mutationSend_feedbackResolver } from "./resolvers/MutationResolver"; -import { request_nsfw_review_for_skin as mutationRequest_nsfw_review_for_skinResolver } from "./resolvers/SkinResolver"; +import { upload_statuses_by_md5 as queryUpload_statuses_by_md5Resolver } from "./resolvers/SkinUploadResolver"; +import { approve_skin as mutationApprove_skinResolver } from "./resolvers/SkinResolver"; import { mark_skin_nsfw as mutationMark_skin_nsfwResolver } from "./resolvers/SkinResolver"; import { reject_skin as mutationReject_skinResolver } from "./resolvers/SkinResolver"; -import { approve_skin as mutationApprove_skinResolver } from "./resolvers/SkinResolver"; +import { request_nsfw_review_for_skin as mutationRequest_nsfw_review_for_skinResolver } from "./resolvers/SkinResolver"; +import { send_feedback as mutationSend_feedbackResolver } from "./resolvers/MutationResolver"; import { upload as mutationUploadResolver } from "./resolvers/UploadMutationResolver"; -import { GraphQLSchema, GraphQLObjectType, GraphQLInterfaceType, GraphQLNonNull, GraphQLID, GraphQLString, GraphQLInt, GraphQLBoolean, GraphQLList, GraphQLEnumType, GraphQLInputObjectType } from "graphql"; -const NodeType: GraphQLInterfaceType = new GraphQLInterfaceType({ - description: "A globally unique object. The `id` here is intended only for use within\nGraphQL.\nhttps://graphql.org/learn/global-object-identification/", - name: "Node", - fields() { - return { - id: { - name: "id", - type: new GraphQLNonNull(GraphQLID) - } - }; - } -}); -const ArchiveFileType: GraphQLObjectType = new GraphQLObjectType({ - name: "ArchiveFile", - description: "A file found within a Winamp Skin's .wsz archive", - fields() { - return { - filename: { - description: "Filename of the file within the archive", - name: "filename", - type: GraphQLString - }, - url: { - description: "A URL to download the file. **Note:** This is powered by a little\nserverless Cloudflare function which tries to exctact the file on the fly.\nIt may not work for all files.", - name: "url", - type: GraphQLString - }, - file_md5: { - description: "The md5 hash of the file within the archive", - name: "file_md5", - type: GraphQLString - }, - size: { - description: "The uncompressed size of the file in bytes.\n\n**Note:** Will be `null` for directories", - name: "size", - type: GraphQLInt - }, - text_content: { - description: "The content of the file, if it's a text file", - name: "text_content", - type: GraphQLString - }, - is_directory: { - description: "Is the file a directory?", - name: "is_directory", - type: GraphQLBoolean - }, - skin: { - description: "The skin in which this file was found", - name: "skin", - type: SkinType - }, - date: { - description: "The date on the file inside the archive. Given in simplified extended ISO\nformat (ISO 8601).", - name: "date", - type: GraphQLString - } - }; - } -}); +import { GraphQLSchema, GraphQLObjectType, GraphQLString, GraphQLBoolean, GraphQLInt, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLID, GraphQLEnumType, GraphQLInputObjectType } from "graphql"; const InternetArchiveItemType: GraphQLObjectType = new GraphQLObjectType({ name: "InternetArchiveItem", fields() { @@ -93,9 +32,9 @@ const InternetArchiveItemType: GraphQLObjectType = new GraphQLObjectType({ name: "identifier", type: GraphQLString }, - url: { - description: "The URL where this item can be viewed on the Internet Archive", - name: "url", + last_metadata_scrape_date_UNSTABLE: { + description: "The date and time that we last scraped this item's metadata.\n**Note:** This field is temporary and will be removed in the future.\nThe date format is just what we get from the database, and it's ambiguous.", + name: "last_metadata_scrape_date_UNSTABLE", type: GraphQLString }, metadata_url: { @@ -103,11 +42,6 @@ const InternetArchiveItemType: GraphQLObjectType = new GraphQLObjectType({ name: "metadata_url", type: GraphQLString }, - last_metadata_scrape_date_UNSTABLE: { - description: "The date and time that we last scraped this item's metadata.\n**Note:** This field is temporary and will be removed in the future.\nThe date format is just what we get from the database, and it's ambiguous.", - name: "last_metadata_scrape_date_UNSTABLE", - type: GraphQLString - }, raw_metadata_json: { description: "Our cached version of the metadata avaliable at \\`metadata_url\\` (above)", name: "raw_metadata_json", @@ -117,6 +51,11 @@ const InternetArchiveItemType: GraphQLObjectType = new GraphQLObjectType({ description: "The skin that this item contains", name: "skin", type: SkinType + }, + url: { + description: "The URL where this item can be viewed on the Internet Archive", + name: "url", + type: GraphQLString } }; } @@ -128,11 +67,11 @@ const RatingType: GraphQLEnumType = new GraphQLEnumType({ APPROVED: { value: "APPROVED" }, - REJECTED: { - value: "REJECTED" - }, NSFW: { value: "NSFW" + }, + REJECTED: { + value: "REJECTED" } } }); @@ -141,119 +80,29 @@ const ReviewType: GraphQLObjectType = new GraphQLObjectType({ description: "A review of a skin. Done either on the Museum's Tinder-style\nreview page, or via the Discord bot.", fields() { return { - skin: { - description: "The skin that was reviewed", - name: "skin", - type: SkinType + rating: { + description: "The rating that the user gave the skin", + name: "rating", + type: RatingType }, reviewer: { description: "The user who made the review (if known). **Note:** In the early days we didn't\ntrack this, so many will be null.", name: "reviewer", type: GraphQLString }, - rating: { - description: "The rating that the user gave the skin", - name: "rating", - type: RatingType - } - }; - } -}); -const SkinType: GraphQLInterfaceType = new GraphQLInterfaceType({ - description: "A Winamp skin. Could be modern or classic.\n\n**Note**: At some point in the future, this might be renamed to `Skin`.", - name: "Skin", - fields() { - return { - id: { - description: "GraphQL ID of the skin", - name: "id", - type: new GraphQLNonNull(GraphQLID) - }, - filename: { - description: "Filename of skin when uploaded to the Museum. Note: In some cases a skin\nhas been uploaded under multiple names. Here we just pick one.", - name: "filename", - type: GraphQLString, - args: { - normalize_extension: { - description: "If true, the the correct file extension (.wsz or .wal) will be .\nOtherwise, the original user-uploaded file extension will be used.", - name: "normalize_extension", - type: GraphQLBoolean, - defaultValue: false - } - } - }, - md5: { - description: "MD5 hash of the skin's file", - name: "md5", - type: GraphQLString - }, - download_url: { - description: "URL to download the skin", - name: "download_url", - type: GraphQLString - }, - tweeted: { - description: "Has the skin been tweeted?", - name: "tweeted", - type: GraphQLBoolean - }, - tweets: { - description: "List of @winampskins tweets that mentioned the skin.", - name: "tweets", - type: new GraphQLList(TweetType) - }, - archive_files: { - description: "List of files contained within the skin's .wsz archive", - name: "archive_files", - type: new GraphQLList(ArchiveFileType) - }, - internet_archive_item: { - description: "The skin's \"item\" at archive.org", - name: "internet_archive_item", - type: InternetArchiveItemType - }, - reviews: { - description: "Times that the skin has been reviewed either on the Museum's Tinder-style\nreivew page, or via the Discord bot.", - name: "reviews", - type: new GraphQLList(ReviewType) - }, - museum_url: { - name: "museum_url", - type: GraphQLString - }, - webamp_url: { - name: "webamp_url", - type: GraphQLString - }, - screenshot_url: { - name: "screenshot_url", - type: GraphQLString - }, - readme_text: { - name: "readme_text", - type: GraphQLString - }, - nsfw: { - name: "nsfw", - type: GraphQLBoolean - }, - average_color: { - name: "average_color", - type: GraphQLString + skin: { + description: "The skin that was reviewed", + name: "skin", + type: SkinType } }; } }); const TweetType: GraphQLObjectType = new GraphQLObjectType({ name: "Tweet", - description: "A tweet made by @winampskins mentioning a Winamp skin", + description: "A tweet made by `@winampskins` mentioning a Winamp skin", fields() { return { - url: { - description: "URL of the tweet. **Note:** Early on in the bot's life we just recorded\n_which_ skins were tweeted, not any info about the actual tweet. This means we\ndon't always know the URL of the tweet.", - name: "url", - type: GraphQLString - }, likes: { description: "Number of likes the tweet has received. Updated nightly. (Note: Recent likes on older tweets may not be reflected here)", name: "likes", @@ -268,15 +117,35 @@ const TweetType: GraphQLObjectType = new GraphQLObjectType({ description: "The skin featured in this Tweet", name: "skin", type: SkinType + }, + url: { + description: "URL of the tweet. **Note:** Early on in the bot's life we just recorded\n_which_ skins were tweeted, not any info about the actual tweet. This means we\ndon't always know the URL of the tweet.", + name: "url", + type: GraphQLString } }; } }); -const ModernSkinType: GraphQLObjectType = new GraphQLObjectType({ - name: "ModernSkin", - description: "A \"modern\" Winamp skin. These skins use the `.wal` file extension and are free-form.\n\nMost functionality in the Winamp Skin Museum is centered around \"classic\" skins,\nwhich are currently called just `Skin` in this schema.", +const SkinType: GraphQLInterfaceType = new GraphQLInterfaceType({ + description: "A Winamp skin. Could be modern or classic.\n\n**Note**: At some point in the future, this might be renamed to `Skin`.", + name: "Skin", fields() { return { + archive_files: { + description: "List of files contained within the skin's .wsz archive", + name: "archive_files", + type: new GraphQLList(ArchiveFileType) + }, + average_color: { + deprecationReason: "Needed for migration", + name: "average_color", + type: GraphQLString + }, + download_url: { + description: "URL to download the skin", + name: "download_url", + type: GraphQLString + }, filename: { description: "Filename of skin when uploaded to the Museum. Note: In some cases a skin\nhas been uploaded under multiple names. Here we just pick one.", name: "filename", @@ -295,14 +164,38 @@ const ModernSkinType: GraphQLObjectType = new GraphQLObjectType({ name: "id", type: new GraphQLNonNull(GraphQLID) }, + internet_archive_item: { + description: "The skin's \"item\" at archive.org", + name: "internet_archive_item", + type: InternetArchiveItemType + }, md5: { description: "MD5 hash of the skin's file", name: "md5", type: GraphQLString }, - download_url: { - description: "URL to download the skin", - name: "download_url", + museum_url: { + deprecationReason: "Needed for migration", + name: "museum_url", + type: GraphQLString + }, + nsfw: { + deprecationReason: "Needed for migration", + name: "nsfw", + type: GraphQLBoolean + }, + readme_text: { + deprecationReason: "Needed for migration", + name: "readme_text", + type: GraphQLString + }, + reviews: { + description: "Times that the skin has been reviewed either on the Museum's Tinder-style\nreivew page, or via the Discord bot.", + name: "reviews", + type: new GraphQLList(ReviewType) + }, + screenshot_url: { + name: "screenshot_url", type: GraphQLString }, tweeted: { @@ -311,47 +204,174 @@ const ModernSkinType: GraphQLObjectType = new GraphQLObjectType({ type: GraphQLBoolean }, tweets: { - description: "List of @winampskins tweets that mentioned the skin.", + description: "List of `@winampskins` tweets that mentioned the skin.", name: "tweets", type: new GraphQLList(TweetType) }, + webamp_url: { + deprecationReason: "Needed for migration", + name: "webamp_url", + type: GraphQLString + } + }; + } +}); +const ArchiveFileType: GraphQLObjectType = new GraphQLObjectType({ + name: "ArchiveFile", + description: "A file found within a Winamp Skin's .wsz archive", + fields() { + return { + date: { + description: "The date on the file inside the archive. Given in simplified extended ISO\nformat (ISO 8601).", + name: "date", + type: GraphQLString + }, + file_md5: { + description: "The md5 hash of the file within the archive", + name: "file_md5", + type: GraphQLString + }, + filename: { + description: "Filename of the file within the archive", + name: "filename", + type: GraphQLString + }, + is_directory: { + description: "Is the file a directory?", + name: "is_directory", + type: GraphQLBoolean + }, + size: { + description: "The uncompressed size of the file in bytes.\n\n**Note:** Will be `null` for directories", + name: "size", + type: GraphQLInt + }, + skin: { + description: "The skin in which this file was found", + name: "skin", + type: SkinType + }, + text_content: { + description: "The content of the file, if it's a text file", + name: "text_content", + type: GraphQLString + }, + url: { + description: "A URL to download the file. **Note:** This is powered by a little\nserverless Cloudflare function which tries to exctact the file on the fly.\nIt may not work for all files.", + name: "url", + type: GraphQLString + } + }; + } +}); +const UserType: GraphQLObjectType = new GraphQLObjectType({ + name: "User", + fields() { + return { + username: { + name: "username", + type: GraphQLString + } + }; + } +}); +const NodeType: GraphQLInterfaceType = new GraphQLInterfaceType({ + description: "A globally unique object. The `id` here is intended only for use within\nGraphQL.\nhttps://graphql.org/learn/global-object-identification/", + name: "Node", + fields() { + return { + id: { + name: "id", + type: new GraphQLNonNull(GraphQLID) + } + }; + } +}); +const ModernSkinType: GraphQLObjectType = new GraphQLObjectType({ + name: "ModernSkin", + description: "A \"modern\" Winamp skin. These skins use the `.wal` file extension and are free-form.\n\nMost functionality in the Winamp Skin Museum is centered around \"classic\" skins,\nwhich are currently called just `Skin` in this schema.", + fields() { + return { archive_files: { description: "List of files contained within the skin's .wsz archive", name: "archive_files", type: new GraphQLList(ArchiveFileType) }, + average_color: { + deprecationReason: "Needed for migration", + name: "average_color", + type: GraphQLString + }, + download_url: { + description: "URL to download the skin", + name: "download_url", + type: GraphQLString + }, + filename: { + description: "Filename of skin when uploaded to the Museum. Note: In some cases a skin\nhas been uploaded under multiple names. Here we just pick one.", + name: "filename", + type: GraphQLString, + args: { + normalize_extension: { + description: "If true, the the correct file extension (.wsz or .wal) will be .\nOtherwise, the original user-uploaded file extension will be used.", + name: "normalize_extension", + type: GraphQLBoolean, + defaultValue: false + } + } + }, + id: { + description: "GraphQL ID of the skin", + name: "id", + type: new GraphQLNonNull(GraphQLID) + }, internet_archive_item: { description: "The skin's \"item\" at archive.org", name: "internet_archive_item", type: InternetArchiveItemType }, + md5: { + description: "MD5 hash of the skin's file", + name: "md5", + type: GraphQLString + }, + museum_url: { + deprecationReason: "Needed for migration", + name: "museum_url", + type: GraphQLString + }, + nsfw: { + deprecationReason: "Needed for migration", + name: "nsfw", + type: GraphQLBoolean + }, + readme_text: { + deprecationReason: "Needed for migration", + name: "readme_text", + type: GraphQLString + }, reviews: { description: "Times that the skin has been reviewed either on the Museum's Tinder-style\nreivew page, or via the Discord bot.", name: "reviews", type: new GraphQLList(ReviewType) }, - museum_url: { - name: "museum_url", - type: GraphQLString - }, - webamp_url: { - name: "webamp_url", - type: GraphQLString - }, screenshot_url: { name: "screenshot_url", type: GraphQLString }, - readme_text: { - name: "readme_text", - type: GraphQLString - }, - nsfw: { - name: "nsfw", + tweeted: { + description: "Has the skin been tweeted?", + name: "tweeted", type: GraphQLBoolean }, - average_color: { - name: "average_color", + tweets: { + description: "List of `@winampskins` tweets that mentioned the skin.", + name: "tweets", + type: new GraphQLList(TweetType) + }, + webamp_url: { + deprecationReason: "Needed for migration", + name: "webamp_url", type: GraphQLString } }; @@ -396,6 +416,23 @@ const SkinsConnectionType: GraphQLObjectType = new GraphQLObjectType({ }; } }); +const SkinsFilterOptionType: GraphQLEnumType = new GraphQLEnumType({ + name: "SkinsFilterOption", + values: { + APPROVED: { + value: "APPROVED" + }, + NSFW: { + value: "NSFW" + }, + REJECTED: { + value: "REJECTED" + }, + TWEETED: { + value: "TWEETED" + } + } +}); const SkinsSortOptionType: GraphQLEnumType = new GraphQLEnumType({ name: "SkinsSortOption", values: { @@ -404,26 +441,67 @@ const SkinsSortOptionType: GraphQLEnumType = new GraphQLEnumType({ } } }); -const SkinsFilterOptionType: GraphQLEnumType = new GraphQLEnumType({ - name: "SkinsFilterOption", - values: { - APPROVED: { - value: "APPROVED" - }, - REJECTED: { - value: "REJECTED" - }, - NSFW: { - value: "NSFW" - }, - TWEETED: { - value: "TWEETED" - } +const DatabaseStatisticsType: GraphQLObjectType = new GraphQLObjectType({ + name: "DatabaseStatistics", + description: "Statistics about the contents of the Museum's database.", + fields() { + return { + approved_skins_count: { + description: "The number of skins that have been approved for tweeting. This includes both\ntweeted and untweeted skins.\n\n**Note:** Skins can be both approved and rejected by different users.", + name: "approved_skins_count", + type: GraphQLInt + }, + nsfw_skins_count: { + description: "The number of skins that have been marked as NSFW.\n\n**Note:** Skins can be approved and rejected by different users.\n**Note:** Generally skins that have been marked NSFW are also marked as rejected.", + name: "nsfw_skins_count", + type: GraphQLInt + }, + rejected_skins_count: { + description: "The number of skins that have been rejected for tweeting.\n\n**Note:** Skins can be both approved and rejected by different users.\n**Note:** Generally skins that have been marked NSFW are also marked as rejected.", + name: "rejected_skins_count", + type: GraphQLInt + }, + tweetable_skins_count: { + description: "The number of skins that have been approved for tweeting, but not yet tweeted.", + name: "tweetable_skins_count", + type: GraphQLInt + }, + tweeted_skins_count: { + description: "The number of skins in the Museum that have been tweeted by `@winampskins`", + name: "tweeted_skins_count", + type: GraphQLInt + }, + unique_classic_skins_count: { + description: "The total number of classic skins in the Museum's database", + name: "unique_classic_skins_count", + type: GraphQLInt + }, + unreviewed_skins_count: { + description: "The number of skins that have never been reviewed.", + name: "unreviewed_skins_count", + type: GraphQLInt + }, + uploads_in_error_state_count: { + description: "Skins uploads that have errored during processing.", + name: "uploads_in_error_state_count", + type: GraphQLInt + }, + uploads_pending_processing_count: { + description: "Skins uplaods awaiting processing. This can happen when there are a large\nnumber of skin uplaods at the same time, or when the skin uploading processing\npipeline gets stuck.", + name: "uploads_pending_processing_count", + type: GraphQLInt + }, + web_uploads_count: { + description: "Number of skins that have been uploaded to the Museum via the web interface.", + name: "web_uploads_count", + type: GraphQLInt + } + }; } }); const TweetsConnectionType: GraphQLObjectType = new GraphQLObjectType({ name: "TweetsConnection", - description: "A collection of tweets made by the @winampskins bot", + description: "A collection of tweets made by the `@winampskins` bot", fields() { return { count: { @@ -450,82 +528,24 @@ const TweetsSortOptionType: GraphQLEnumType = new GraphQLEnumType({ } } }); -const DatabaseStatisticsType: GraphQLObjectType = new GraphQLObjectType({ - name: "DatabaseStatistics", - description: "Statistics about the contents of the Museum's database.", - fields() { - return { - unique_classic_skins_count: { - description: "The total number of classic skins in the Museum's database", - name: "unique_classic_skins_count", - type: GraphQLInt - }, - tweeted_skins_count: { - description: "The number of skins in the Museum that have been tweeted by @winampskins", - name: "tweeted_skins_count", - type: GraphQLInt - }, - approved_skins_count: { - description: "The number of skins that have been approved for tweeting. This includes both\ntweeted and untweeted skins.\n\n**Note:** Skins can be both approved and rejected by different users.", - name: "approved_skins_count", - type: GraphQLInt - }, - rejected_skins_count: { - description: "The number of skins that have been rejected for tweeting.\n\n**Note:** Skins can be both approved and rejected by different users.\n**Note:** Generally skins that have been marked NSFW are also marked as rejected.", - name: "rejected_skins_count", - type: GraphQLInt - }, - nsfw_skins_count: { - description: "The number of skins that have been marked as NSFW.\n\n**Note:** Skins can be approved and rejected by different users.\n**Note:** Generally skins that have been marked NSFW are also marked as rejected.", - name: "nsfw_skins_count", - type: GraphQLInt - }, - unreviewed_skins_count: { - description: "The number of skins that have never been reviewed.", - name: "unreviewed_skins_count", - type: GraphQLInt - }, - tweetable_skins_count: { - description: "The number of skins that have been approved for tweeting, but not yet tweeted.", - name: "tweetable_skins_count", - type: GraphQLInt - }, - uploads_pending_processing_count: { - description: "Skins uplaods awaiting processing. This can happen when there are a large\nnumber of skin uplaods at the same time, or when the skin uploading processing\npipeline gets stuck.", - name: "uploads_pending_processing_count", - type: GraphQLInt - }, - uploads_in_error_state_count: { - description: "Skins uploads that have errored during processing.", - name: "uploads_in_error_state_count", - type: GraphQLInt - }, - web_uploads_count: { - description: "Number of skins that have been uploaded to the Museum via the web interface.", - name: "web_uploads_count", - type: GraphQLInt - } - }; - } -}); const SkinUploadStatusType: GraphQLEnumType = new GraphQLEnumType({ description: "The current status of a pending upload.\n\n**Note:** Expect more values here as we try to be more transparent about\nthe status of a pending uploads.", name: "SkinUploadStatus", values: { - URL_REQUESTED: { - value: "URL_REQUESTED" - }, - UPLOAD_REPORTED: { - value: "UPLOAD_REPORTED" - }, - ERRORED: { - value: "ERRORED" + ARCHIVED: { + value: "ARCHIVED" }, DELAYED: { value: "DELAYED" }, - ARCHIVED: { - value: "ARCHIVED" + ERRORED: { + value: "ERRORED" + }, + UPLOAD_REPORTED: { + value: "UPLOAD_REPORTED" + }, + URL_REQUESTED: { + value: "URL_REQUESTED" } } }); @@ -538,15 +558,15 @@ const SkinUploadType: GraphQLObjectType = new GraphQLObjectType({ name: "id", type: GraphQLString }, - status: { - name: "status", - type: SkinUploadStatusType - }, skin: { description: "Skin that was uploaded. **Note:** This is null if the skin has not yet been\nfully processed. (status == ARCHIVED)", name: "skin", type: SkinType }, + status: { + name: "status", + type: SkinUploadStatusType + }, upload_md5: { description: "Md5 hash given when requesting the upload URL.", name: "upload_md5", @@ -555,53 +575,14 @@ const SkinUploadType: GraphQLObjectType = new GraphQLObjectType({ }; } }); -const UserType: GraphQLObjectType = new GraphQLObjectType({ - name: "User", - fields() { - return { - username: { - name: "username", - type: GraphQLString - } - }; - } -}); const QueryType: GraphQLObjectType = new GraphQLObjectType({ name: "Query", fields() { return { - node: { - description: "Get a globally unique object by its ID.\n\nhttps://graphql.org/learn/global-object-identification/", - name: "node", - type: NodeType, - args: { - id: { - name: "id", - type: new GraphQLNonNull(GraphQLID) - } - }, - resolve(source, args, context) { - return queryNodeResolver(source, args, context); - } - }, - fetch_tweet_by_url: { - description: "Get a tweet by its URL", - name: "fetch_tweet_by_url", - type: TweetType, - args: { - url: { - name: "url", - type: new GraphQLNonNull(GraphQLString) - } - }, - resolve(source, args, context) { - return queryFetch_tweet_by_urlResolver(source, args, context); - } - }, - fetch_skin_by_md5: { - description: "Get a skin by its MD5 hash", - name: "fetch_skin_by_md5", - type: SkinType, + fetch_archive_file_by_md5: { + description: "Fetch archive file by it's MD5 hash\n\nGet information about a file found within a skin's wsz/wal/zip archive.", + name: "fetch_archive_file_by_md5", + type: ArchiveFileType, args: { md5: { name: "md5", @@ -609,39 +590,7 @@ const QueryType: GraphQLObjectType = new GraphQLObjectType({ } }, resolve(source, args, context) { - return queryFetch_skin_by_md5Resolver(source, args, context); - } - }, - search_skins: { - description: "Search the database using the Algolia search index used by the Museum.\n\nUseful for locating a particular skin.", - name: "search_skins", - type: new GraphQLList(SkinType), - args: { - query: { - name: "query", - type: new GraphQLNonNull(GraphQLString) - }, - first: { - name: "first", - type: GraphQLInt, - defaultValue: 10 - }, - offset: { - name: "offset", - type: GraphQLInt, - defaultValue: 0 - } - }, - resolve(source, args, context) { - return querySearch_skinsResolver(source, args, context); - } - }, - skin_to_review: { - description: "A random skin that needs to be reviewed", - name: "skin_to_review", - type: SkinType, - resolve(source, args, context) { - return querySkin_to_reviewResolver(source, args, context); + return queryFetch_archive_file_by_md5Resolver(source, args, context); } }, fetch_internet_archive_item_by_identifier: { @@ -658,10 +607,10 @@ const QueryType: GraphQLObjectType = new GraphQLObjectType({ return queryFetch_internet_archive_item_by_identifierResolver(source, args, context); } }, - fetch_archive_file_by_md5: { - description: "Fetch archive file by it's MD5 hash\n\nGet information about a file found within a skin's wsz/wal/zip archive.", - name: "fetch_archive_file_by_md5", - type: ArchiveFileType, + fetch_skin_by_md5: { + description: "Get a skin by its MD5 hash", + name: "fetch_skin_by_md5", + type: SkinType, args: { md5: { name: "md5", @@ -669,7 +618,29 @@ const QueryType: GraphQLObjectType = new GraphQLObjectType({ } }, resolve(source, args, context) { - return queryFetch_archive_file_by_md5Resolver(source, args, context); + return queryFetch_skin_by_md5Resolver(source, args, context); + } + }, + fetch_tweet_by_url: { + description: "Get a tweet by its URL", + name: "fetch_tweet_by_url", + type: TweetType, + args: { + url: { + name: "url", + type: new GraphQLNonNull(GraphQLString) + } + }, + resolve(source, args, context) { + return queryFetch_tweet_by_urlResolver(source, args, context); + } + }, + me: { + description: "The currently authenticated user, if any.", + name: "me", + type: UserType, + resolve(source) { + return queryMeResolver(source); } }, modern_skins: { @@ -679,12 +650,12 @@ const QueryType: GraphQLObjectType = new GraphQLObjectType({ args: { first: { name: "first", - type: GraphQLInt, + type: new GraphQLNonNull(GraphQLInt), defaultValue: 10 }, offset: { name: "offset", - type: GraphQLInt, + type: new GraphQLNonNull(GraphQLInt), defaultValue: 0 } }, @@ -692,58 +663,80 @@ const QueryType: GraphQLObjectType = new GraphQLObjectType({ return queryModern_skinsResolver(source, args); } }, + node: { + description: "Get a globally unique object by its ID.\n\nhttps://graphql.org/learn/global-object-identification/", + name: "node", + type: NodeType, + args: { + id: { + name: "id", + type: new GraphQLNonNull(GraphQLID) + } + }, + resolve(source, args, context) { + return queryNodeResolver(source, args, context); + } + }, + search_skins: { + description: "Search the database using the Algolia search index used by the Museum.\n\nUseful for locating a particular skin.", + name: "search_skins", + type: new GraphQLList(SkinType), + args: { + first: { + name: "first", + type: new GraphQLNonNull(GraphQLInt), + defaultValue: 10 + }, + offset: { + name: "offset", + type: new GraphQLNonNull(GraphQLInt), + defaultValue: 0 + }, + query: { + name: "query", + type: new GraphQLNonNull(GraphQLString) + } + }, + resolve(source, args, context) { + return querySearch_skinsResolver(source, args, context); + } + }, + skin_to_review: { + description: "A random skin that needs to be reviewed", + name: "skin_to_review", + type: SkinType, + resolve(source, args, context) { + return querySkin_to_reviewResolver(source, args, context); + } + }, skins: { description: "All classic skins in the database\n\n**Note:** We don't currently support combining sorting and filtering.", name: "skins", type: SkinsConnectionType, args: { + filter: { + name: "filter", + type: SkinsFilterOptionType + }, first: { name: "first", - type: GraphQLInt, + type: new GraphQLNonNull(GraphQLInt), defaultValue: 10 }, offset: { name: "offset", - type: GraphQLInt, + type: new GraphQLNonNull(GraphQLInt), defaultValue: 0 }, sort: { name: "sort", type: SkinsSortOptionType - }, - filter: { - name: "filter", - type: SkinsFilterOptionType } }, resolve(source, args) { return querySkinsResolver(source, args); } }, - tweets: { - description: "Tweets tweeted by @winampskins", - name: "tweets", - type: TweetsConnectionType, - args: { - first: { - name: "first", - type: GraphQLInt, - defaultValue: 10 - }, - offset: { - name: "offset", - type: GraphQLInt, - defaultValue: 0 - }, - sort: { - name: "sort", - type: TweetsSortOptionType - } - }, - resolve(source, args) { - return queryTweetsResolver(source, args); - } - }, statistics: { description: "A namespace for statistics about the database", name: "statistics", @@ -752,18 +745,28 @@ const QueryType: GraphQLObjectType = new GraphQLObjectType({ return queryStatisticsResolver(source); } }, - upload_statuses_by_md5: { - description: "Get the status of a batch of uploads by md5s", - name: "upload_statuses_by_md5", - type: new GraphQLList(SkinUploadType), + tweets: { + description: "Tweets tweeted by `@winampskins`", + name: "tweets", + type: TweetsConnectionType, args: { - md5s: { - name: "md5s", - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))) + first: { + name: "first", + type: new GraphQLNonNull(GraphQLInt), + defaultValue: 10 + }, + offset: { + name: "offset", + type: new GraphQLNonNull(GraphQLInt), + defaultValue: 0 + }, + sort: { + name: "sort", + type: TweetsSortOptionType } }, - resolve(source, args, context) { - return queryUpload_statuses_by_md5Resolver(source, args, context); + resolve(source, args) { + return queryTweetsResolver(source, args); } }, upload_statuses: { @@ -780,12 +783,19 @@ const QueryType: GraphQLObjectType = new GraphQLObjectType({ return queryUpload_statusesResolver(source, args, context); } }, - me: { - description: "The currently authenticated user, if any.", - name: "me", - type: UserType, - resolve(source) { - return queryMeResolver(source); + upload_statuses_by_md5: { + description: "Get the status of a batch of uploads by md5s", + deprecationReason: "Prefer `upload_statuses` instead, were we operate on ids.", + name: "upload_statuses_by_md5", + type: new GraphQLList(SkinUploadType), + args: { + md5s: { + name: "md5s", + type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))) + } + }, + resolve(source, args, context) { + return queryUpload_statuses_by_md5Resolver(source, args, context); } } }; @@ -800,13 +810,13 @@ const UploadUrlType: GraphQLObjectType = new GraphQLObjectType({ name: "id", type: GraphQLString }, - url: { - name: "url", - type: GraphQLString - }, md5: { name: "md5", type: GraphQLString + }, + url: { + name: "url", + type: GraphQLString } }; } @@ -865,31 +875,9 @@ const MutationType: GraphQLObjectType = new GraphQLObjectType({ name: "Mutation", fields() { return { - send_feedback: { - description: "Send a message to the admin of the site. Currently this appears in Discord.", - name: "send_feedback", - type: GraphQLBoolean, - args: { - message: { - name: "message", - type: new GraphQLNonNull(GraphQLString) - }, - email: { - name: "email", - type: GraphQLString - }, - url: { - name: "url", - type: GraphQLString - } - }, - resolve(source, args, context) { - return mutationSend_feedbackResolver(source, args, context); - } - }, - request_nsfw_review_for_skin: { - description: "Request that an admin check if this skin is NSFW.\nUnlike other review mutation endpoints, this one does not require being logged\nin.", - name: "request_nsfw_review_for_skin", + approve_skin: { + description: "Approve skin for tweeting\n\n**Note:** Requires being logged in", + name: "approve_skin", type: GraphQLBoolean, args: { md5: { @@ -898,7 +886,7 @@ const MutationType: GraphQLObjectType = new GraphQLObjectType({ } }, resolve(source, args, context) { - return mutationRequest_nsfw_review_for_skinResolver(source, args, context); + return mutationApprove_skinResolver(source, args, context); } }, mark_skin_nsfw: { @@ -929,9 +917,9 @@ const MutationType: GraphQLObjectType = new GraphQLObjectType({ return mutationReject_skinResolver(source, args, context); } }, - approve_skin: { - description: "Approve skin for tweeting\n\n**Note:** Requires being logged in", - name: "approve_skin", + request_nsfw_review_for_skin: { + description: "Request that an admin check if this skin is NSFW.\nUnlike other review mutation endpoints, this one does not require being logged\nin.", + name: "request_nsfw_review_for_skin", type: GraphQLBoolean, args: { md5: { @@ -940,7 +928,29 @@ const MutationType: GraphQLObjectType = new GraphQLObjectType({ } }, resolve(source, args, context) { - return mutationApprove_skinResolver(source, args, context); + return mutationRequest_nsfw_review_for_skinResolver(source, args, context); + } + }, + send_feedback: { + description: "Send a message to the admin of the site. Currently this appears in Discord.", + name: "send_feedback", + type: GraphQLBoolean, + args: { + email: { + name: "email", + type: GraphQLString + }, + message: { + name: "message", + type: new GraphQLNonNull(GraphQLString) + }, + url: { + name: "url", + type: GraphQLString + } + }, + resolve(source, args, context) { + return mutationSend_feedbackResolver(source, args, context); } }, upload: { @@ -959,10 +969,20 @@ const ClassicSkinType: GraphQLObjectType = new GraphQLObjectType({ description: "A classic Winamp skin", fields() { return { - id: { - description: "GraphQL ID of the skin", - name: "id", - type: new GraphQLNonNull(GraphQLID) + archive_files: { + description: "List of files contained within the skin's .wsz archive", + name: "archive_files", + type: new GraphQLList(ArchiveFileType) + }, + average_color: { + description: "String representation (rgb usually) of the skin's average color", + name: "average_color", + type: GraphQLString + }, + download_url: { + description: "URL to download the skin", + name: "download_url", + type: GraphQLString }, filename: { description: "Filename of skin when uploaded to the Museum. Note: In some cases a skin\nhas been uploaded under multiple names. Here we just pick one.", @@ -977,85 +997,75 @@ const ClassicSkinType: GraphQLObjectType = new GraphQLObjectType({ } } }, - md5: { - description: "MD5 hash of the skin's file", - name: "md5", - type: GraphQLString - }, - download_url: { - description: "URL to download the skin", - name: "download_url", - type: GraphQLString - }, - tweeted: { - description: "Has the skin been tweeted?", - name: "tweeted", + has_media_library: { + description: "Does the skin include sprite sheets for the media library?", + name: "has_media_library", type: GraphQLBoolean }, - tweets: { - description: "List of @winampskins tweets that mentioned the skin.", - name: "tweets", - type: new GraphQLList(TweetType) - }, - archive_files: { - description: "List of files contained within the skin's .wsz archive", - name: "archive_files", - type: new GraphQLList(ArchiveFileType) + id: { + description: "GraphQL ID of the skin", + name: "id", + type: new GraphQLNonNull(GraphQLID) }, internet_archive_item: { description: "The skin's \"item\" at archive.org", name: "internet_archive_item", type: InternetArchiveItemType }, + last_algolia_index_update_date: { + description: "The date on which this skin was last updated in the Algolia search index.\nGiven in simplified extended ISO format (ISO 8601).", + name: "last_algolia_index_update_date", + type: GraphQLString + }, + md5: { + description: "MD5 hash of the skin's file", + name: "md5", + type: GraphQLString + }, museum_url: { description: "URL of the skin on the Winamp Skin Museum", name: "museum_url", type: GraphQLString }, - webamp_url: { - description: "URL of webamp.org with the skin loaded", - name: "webamp_url", - type: GraphQLString - }, - screenshot_url: { - description: "URL of a screenshot of the skin", - name: "screenshot_url", - type: GraphQLString - }, - readme_text: { - description: "Text of the readme file extracted from the skin", - name: "readme_text", - type: GraphQLString - }, nsfw: { description: "Has the skin been flagged as \"not safe for wrok\"?", name: "nsfw", type: GraphQLBoolean }, - average_color: { - description: "String representation (rgb usually) of the skin's average color", - name: "average_color", + readme_text: { + description: "Text of the readme file extracted from the skin", + name: "readme_text", type: GraphQLString }, - has_media_library: { - description: "Does the skin include sprite sheets for the media library?", - name: "has_media_library", - type: GraphQLBoolean - }, reviews: { description: "Times that the skin has been reviewed either on the Museum's Tinder-style\nreivew page, or via the Discord bot.", name: "reviews", type: new GraphQLList(ReviewType) }, - last_algolia_index_update_date: { - description: "The date on which this skin was last updated in the Algolia search index.\nGiven in simplified extended ISO format (ISO 8601).", - name: "last_algolia_index_update_date", + screenshot_url: { + description: "URL of a screenshot of the skin", + name: "screenshot_url", type: GraphQLString }, transparent_pixels: { description: "The number of transparent pixels rendered by the skin.", name: "transparent_pixels", type: GraphQLInt + }, + tweeted: { + description: "Has the skin been tweeted?", + name: "tweeted", + type: GraphQLBoolean + }, + tweets: { + description: "List of `@winampskins` tweets that mentioned the skin.", + name: "tweets", + type: new GraphQLList(TweetType) + }, + webamp_url: { + description: "URL of webamp.org with the skin loaded", + name: "webamp_url", + type: GraphQLString } }; }, @@ -1066,6 +1076,6 @@ const ClassicSkinType: GraphQLObjectType = new GraphQLObjectType({ const schema = new GraphQLSchema({ query: QueryType, mutation: MutationType, - types: [RatingType, QueryType, NodeType, ReviewType, TweetType, ClassicSkinType, ModernSkinType, MutationType, InternetArchiveItemType, SkinType, ArchiveFileType, ModernSkinsConnectionType, SkinsConnectionType, SkinsSortOptionType, SkinsFilterOptionType, TweetsSortOptionType, TweetsConnectionType, DatabaseStatisticsType, SkinUploadType, SkinUploadStatusType, UserType, UploadUrlType, UploadUrlRequestType, UploadMutationsType] + types: [ArchiveFileType, ClassicSkinType, DatabaseStatisticsType, InternetArchiveItemType, ModernSkinType, ModernSkinsConnectionType, MutationType, NodeType, QueryType, RatingType, ReviewType, SkinType, SkinUploadType, SkinUploadStatusType, SkinsConnectionType, SkinsFilterOptionType, SkinsSortOptionType, TweetType, TweetsConnectionType, TweetsSortOptionType, UploadMutationsType, UploadUrlType, UploadUrlRequestType, UserType] }); export { schema }; diff --git a/packages/skin-database/package.json b/packages/skin-database/package.json index df0b99b3..504aaaab 100644 --- a/packages/skin-database/package.json +++ b/packages/skin-database/package.json @@ -56,7 +56,7 @@ "cli": "ts-node --transpile-only ./cli.ts", "sync": "ts-node --transpile-only ./tasks/syncWithArchive.ts", "migrate": "knex migrate:latest", - "grats": "grats --output=./api/graphql/schema.graphql" + "grats": "grats" }, "prettier": {}, "devDependencies": { @@ -64,11 +64,11 @@ "@types/supertest": "^2.0.10", "@typescript-eslint/eslint-plugin": "^5.15.0", "@typescript-eslint/parser": "^5.15.0", - "grats": "0.0.0-main-bfb182a3", "supertest": "^6.0.1", - "typescript": "^3.8.3" + "typescript": "^3.8.3", + "grats": "0.0.0-main-d4d43d4b" }, "resolutions": { - "graphql": "15.3.0" + "graphql": "16.6.0" } } diff --git a/packages/skin-database/tsconfig.json b/packages/skin-database/tsconfig.json index 9e9d3398..0e344331 100644 --- a/packages/skin-database/tsconfig.json +++ b/packages/skin-database/tsconfig.json @@ -1,7 +1,8 @@ { "grats": { "nullableByDefault": true, - "EXPERIMENTAL_codegenPath": "./api/graphql/schema.ts" + "tsSchema": "./api/graphql/schema.ts", + "graphqlSchema": "./api/graphql/schema.graphql" }, "compilerOptions": { "module": "commonjs", diff --git a/yarn.lock b/yarn.lock index fe171131..9ef84aff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2912,14 +2912,6 @@ dependencies: tslib "^2.4.0" -"@graphql-tools/utils@^9.2.1": - version "9.2.1" - resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-9.2.1.tgz#1b3df0ef166cfa3eae706e3518b17d5922721c57" - integrity sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A== - dependencies: - "@graphql-typed-document-node/core" "^3.1.1" - tslib "^2.4.0" - "@graphql-tools/wrap@9.2.1": version "9.2.1" resolved "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-9.2.1.tgz" @@ -2931,11 +2923,6 @@ tslib "^2.4.0" value-or-promise "1.0.11" -"@graphql-typed-document-node/core@^3.1.1": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861" - integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== - "@hapi/hoek@^9.0.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" @@ -14788,15 +14775,14 @@ graphql@^16.6.0: resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.8.1.tgz#1930a965bef1170603702acdb68aedd3f3cf6f07" integrity sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw== -grats@0.0.0-main-bfb182a3: - version "0.0.0-main-bfb182a3" - resolved "https://registry.yarnpkg.com/grats/-/grats-0.0.0-main-bfb182a3.tgz#c566889fce58016d24b976c3274d0dda143366c6" - integrity sha512-cFsUXkmqo5TsbV51danyyH3pFLWduTMSg6RNwskEOofgV/+GiKFdbmaLWsgI2B5/RcBkzPjdDGRN5B8u99pR7g== +grats@0.0.0-main-d4d43d4b: + version "0.0.0-main-d4d43d4b" + resolved "https://registry.yarnpkg.com/grats/-/grats-0.0.0-main-d4d43d4b.tgz#5e81c5558568251b9e026aeb77782198a8647a6d" + integrity sha512-SvxKQ5VV5W4tcxPHWutt5JSt2MUFbeWydKuiloPIIhzibiMMwNdCoPIO1E8YnQN//tCEsRdFqeV29zu/tENQeQ== dependencies: - "@graphql-tools/utils" "^9.2.1" commander "^10.0.0" graphql "^16.6.0" - typescript "^4.9.5" + typescript "^5.0.2" gulp-eslint@^3.0.1: version "3.0.1" @@ -26063,6 +26049,11 @@ typescript@^4.9.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript@^5.0.2: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + uid-safe@2.1.5: version "2.1.5" resolved "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz"