diff --git a/Jenkinsfile b/Jenkinsfile index d873dd84..7edf8bdd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -36,16 +36,6 @@ pipeline { sh 'php ./vendor/bin/phpunit --colors=never' } } - stage('Code Cleanliness') { - agent any - steps { - sh "php ./vendor/bin/phpstan analyse -c phpstan.neon -n --no-progress --no-ansi --error-format=checkstyle | awk '{\$1=\$1;print}' > build/logs/phpstan.log" - recordIssues( - failOnError: false, - tools: [phpStan(reportEncoding: 'UTF-8', pattern: 'build/logs/phpstan.log')] - ) - } - } stage('Coverage') { agent any steps { diff --git a/src/AnimeClient/API/Kitsu/Queries/GetUserHistory.graphql b/src/AnimeClient/API/Kitsu/Queries/GetUserHistory.graphql index eb569d6d..4dd35bc2 100644 --- a/src/AnimeClient/API/Kitsu/Queries/GetUserHistory.graphql +++ b/src/AnimeClient/API/Kitsu/Queries/GetUserHistory.graphql @@ -1,6 +1,9 @@ query ($slug: String!) { findProfileBySlug(slug: $slug) { - libraryEvents(first: 100, kind: [PROGRESSED, UPDATED]) { + libraryEvents(first: 100, kind: [PROGRESSED, UPDATED], sort: [{ + direction: DESCENDING, + on: UPDATED_AT, + }]) { nodes { id changedData diff --git a/src/AnimeClient/API/Kitsu/Transformer/AnimeTransformer.php b/src/AnimeClient/API/Kitsu/Transformer/AnimeTransformer.php index 1a2caf00..6da23ae1 100644 --- a/src/AnimeClient/API/Kitsu/Transformer/AnimeTransformer.php +++ b/src/AnimeClient/API/Kitsu/Transformer/AnimeTransformer.php @@ -88,6 +88,13 @@ final class AnimeTransformer extends AbstractTransformer { $role = $staffing['role']; $name = $person['names']['localized'][$person['names']['canonical']]; + // If this person object is so broken as to not have a proper image object, + // just skip it. No point in showing a role with nothing in it. + if ($person === null || $person['id'] === null || $person['image'] === null || $person['image']['original'] === null) + { + continue; + } + if ( ! array_key_exists($role, $staff)) { $staff[$role] = []; diff --git a/src/AnimeClient/API/Kitsu/schema.graphql b/src/AnimeClient/API/Kitsu/schema.graphql index 49c62187..81806a10 100644 --- a/src/AnimeClient/API/Kitsu/schema.graphql +++ b/src/AnimeClient/API/Kitsu/schema.graphql @@ -115,6 +115,18 @@ interface Media { "Returns the last _n_ elements from the list." last: Int ): MappingConnection! + "A list of your wiki submissions for this media" + myWikiSubmissions( + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the elements in the list that come before the specified cursor." + before: String, + "Returns the first _n_ elements from the list." + first: Int, + "Returns the last _n_ elements from the list." + last: Int, + sort: [WikiSubmissionSortOption] + ): WikiSubmissionConnection! "The time of the next release of this media" nextRelease: ISO8601DateTime "The country in which the media was primarily produced" @@ -219,6 +231,9 @@ union FavoriteItemUnion = Anime | Character | Manga | Person "Objects which are Mappable" union MappingItemUnion = Anime | Category | Character | Episode | Manga | Person | Producer +"Objects which are Reportable" +union ReportItemUnion = Comment | MediaReaction | Post | Review + "A user account on Kitsu" type Account implements WithTimestamps { "The country this user resides in" @@ -252,6 +267,19 @@ type Account implements WithTimestamps { updatedAt: ISO8601DateTime! } +type AccountMutations { + "Send a password reset email" + resetPassword( + "The email address to reset the password for" + email: String! + ): AccountResetPasswordPayload +} + +"Autogenerated return type of AccountResetPassword" +type AccountResetPasswordPayload { + email: String! +} + type Anime implements Episodic & Media & WithTimestamps { "The recommended minimum age group for this media" ageRating: AgeRatingEnum @@ -318,6 +346,18 @@ type Anime implements Episodic & Media & WithTimestamps { "Returns the last _n_ elements from the list." last: Int ): MappingConnection! + "A list of your wiki submissions for this media" + myWikiSubmissions( + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the elements in the list that come before the specified cursor." + before: String, + "Returns the first _n_ elements from the list." + first: Int, + "Returns the last _n_ elements from the list." + last: Int, + sort: [WikiSubmissionSortOption] + ): WikiSubmissionConnection! "The time of the next release of this media" nextRelease: ISO8601DateTime "The country in which the media was primarily produced" @@ -647,9 +687,9 @@ type Comment implements WithTimestamps { "The user who created this comment for the parent post." author: Profile! "Unmodified content." - content: String! + content: String "Html formatted content." - contentFormatted: String! + contentFormatted: String createdAt: ISO8601DateTime! id: ID! "Users who liked this comment" @@ -811,6 +851,47 @@ type FavoriteEdge { node: Favorite } +"Related media grouped together" +type Franchise implements WithTimestamps { + createdAt: ISO8601DateTime! + id: ID! + "All media related to a franchise" + installments( + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the elements in the list that come before the specified cursor." + before: String, + "Returns the first _n_ elements from the list." + first: Int, + "Returns the last _n_ elements from the list." + last: Int, + sort: [InstallmentSortOption] + ): InstallmentConnection + "The name of this franchise in various languages" + titles: TitlesList! + updatedAt: ISO8601DateTime! +} + +"The connection type for Franchise." +type FranchiseConnection { + "A list of edges." + edges: [FranchiseEdge] + "A list of nodes." + nodes: [Franchise] + "Information to aid in pagination." + pageInfo: PageInfo! + "The total amount of nodes." + totalCount: Int! +} + +"An edge in a connection." +type FranchiseEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Franchise +} + type Generic implements Error { "The error code." code: String @@ -833,19 +914,54 @@ type Image { views(names: [String!]): [ImageView!]! } -type ImageView implements WithTimestamps { - createdAt: ISO8601DateTime! +type ImageView { "The height of the image" height: Int "The name of this view of the image" name: String! - updatedAt: ISO8601DateTime! "The URL of this view of the image" url: String! "The width of the image" width: Int } +"Individual media that belongs to a franchise" +type Installment implements WithTimestamps { + "Order based chronologically" + alternativeOrder: Int + createdAt: ISO8601DateTime! + "The franchise related to this installment" + franchise: Franchise! + id: ID! + "The media related to this installment" + media: Media! + "Order based by date released" + releaseOrder: Int + "Further explains the media relationship corresponding to a franchise" + tag: InstallmentTagEnum + updatedAt: ISO8601DateTime! +} + +"The connection type for Installment." +type InstallmentConnection { + "A list of edges." + edges: [InstallmentEdge] + "A list of nodes." + nodes: [Installment] + "Information to aid in pagination." + pageInfo: PageInfo! + "The total amount of nodes." + totalCount: Int! +} + +"An edge in a connection." +type InstallmentEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Installment +} + "The user library filterable by media_type and status" type Library { "All Library Entries for a specific Media" @@ -1201,6 +1317,18 @@ type Manga implements Media & WithTimestamps { "Returns the last _n_ elements from the list." last: Int ): MappingConnection! + "A list of your wiki submissions for this media" + myWikiSubmissions( + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the elements in the list that come before the specified cursor." + before: String, + "Returns the first _n_ elements from the list." + first: Int, + "Returns the last _n_ elements from the list." + last: Int, + sort: [WikiSubmissionSortOption] + ): WikiSubmissionConnection! "The time of the next release of this media" nextRelease: ISO8601DateTime "The country in which the media was primarily produced" @@ -1567,12 +1695,14 @@ type MediaStaffEdge { } type Mutation { + account: AccountMutations! anime: AnimeMutations! episode: EpisodeMutations! libraryEntry: LibraryEntryMutations! mapping: MappingMutations! post: PostMutations! pro: ProMutations! + wikiSubmission: WikiSubmissionMutations! } "Information about pagination in a connection." @@ -1645,9 +1775,9 @@ type Post implements WithTimestamps { sort: [CommentSortOption] ): CommentConnection! "Unmodified content." - content: String! + content: String "Html formatted content." - contentFormatted: String! + contentFormatted: String createdAt: ISO8601DateTime! "Users that are watching this post" follows( @@ -1718,12 +1848,12 @@ type PostMutations { "Lock a Post." lock( "Lock a Post." - input: LockInput! + input: PostLockInput! ): PostLockPayload "Unlock a Post." unlock( "Unlock a Post." - input: UnlockInput! + input: PostUnlockInput! ): PostUnlockPayload } @@ -1854,9 +1984,11 @@ type Profile implements WithTimestamps { before: String, "Returns the first _n_ elements from the list." first: Int, - kind: [LibraryEventKindEnum!], + "Will return all if not supplied" + kind: [LibraryEventKindEnum!] = [PROGRESSED, UPDATED, REACTED, RATED, ANNOTATED], "Returns the last _n_ elements from the list." - last: Int + last: Int, + sort: [LibraryEventSortOption] ): LibraryEventConnection! "The user's general location" location: String @@ -1891,6 +2023,18 @@ type Profile implements WithTimestamps { proMessage: String "The PRO level the user currently has" proTier: ProTierEnum + "Reviews created by this user" + reviews( + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the elements in the list that come before the specified cursor." + before: String, + "Returns the first _n_ elements from the list." + first: Int, + "Returns the last _n_ elements from the list." + last: Int, + sort: [WikiSubmissionSortOption] + ): ReviewConnection "Links to the user on other (social media) sites." siteLinks( "Returns the elements in the list that come after the specified cursor." @@ -1913,6 +2057,20 @@ type Profile implements WithTimestamps { waifu: Character "The properly-gendered term for the user's waifu. This should normally only be 'Waifu' or 'Husbando' but some people are jerks, including the person who wrote this..." waifuOrHusbando: String + "Wiki submissions created by this user" + wikiSubmissions( + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the elements in the list that come before the specified cursor." + before: String, + "Returns the first _n_ elements from the list." + first: Int, + "Returns the last _n_ elements from the list." + last: Int, + sort: [WikiSubmissionSortOption], + "Will return all if not supplied" + statuses: [WikiSubmissionStatusEnum!] = [DRAFT, PENDING, APPROVED, REJECTED] + ): WikiSubmissionConnection! } "The connection type for Profile." @@ -2014,6 +2172,21 @@ type Query { findProfileById(id: ID!): Profile "Find a single User by Slug" findProfileBySlug(slug: String!): Profile + "Find a single Report by ID" + findReportById(id: ID!): Report + "Find a single Wiki Submission by ID" + findWikiSubmissionById(id: ID!): WikiSubmission + "All Franchise in the Kitsu database" + franchises( + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the elements in the list that come before the specified cursor." + before: String, + "Returns the first _n_ elements from the list." + first: Int, + "Returns the last _n_ elements from the list." + last: Int + ): FranchiseConnection "List trending media on Kitsu" globalTrending( "Returns the elements in the list that come after the specified cursor." @@ -2101,6 +2274,30 @@ type Query { ): ProfileConnection! "Random anime or manga" randomMedia(ageRatings: [AgeRatingEnum!]!, mediaType: MediaTypeEnum!): Media! + "All Reports in the Kitsu database" + reports( + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the elements in the list that come before the specified cursor." + before: String, + "Returns the first _n_ elements from the list." + first: Int, + "Returns the last _n_ elements from the list." + last: Int + ): ReportConnection + "Select all Reports that match with a supplied status." + reportsByStatus( + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the elements in the list that come before the specified cursor." + before: String, + "Returns the first _n_ elements from the list." + first: Int, + "Returns the last _n_ elements from the list." + last: Int, + "Will return all if not supplied" + statuses: [ReportStatusEnum!] = [REPORTED, RESOLVED, DECLINED] + ): ReportConnection "Search for Anime by title using Algolia. The most relevant results will be at the top." searchAnimeByTitle( "Returns the elements in the list that come after the specified cursor." @@ -2153,6 +2350,20 @@ type Query { ): ProfileConnection "Get your current session info" session: Session! + "Select all Wiki Submissions that match with a supplied status." + wikiSubmissionsByStatuses( + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the elements in the list that come before the specified cursor." + before: String, + "Returns the first _n_ elements from the list." + first: Int, + "Returns the last _n_ elements from the list." + last: Int, + sort: [WikiSubmissionSortOption], + "Will return all if not supplied" + statuses: [WikiSubmissionStatusEnum!] = [DRAFT, PENDING, APPROVED, REJECTED] + ): WikiSubmissionConnection } "A quote from a media" @@ -2228,6 +2439,101 @@ type QuoteLineEdge { node: QuoteLine } +"A report made by a user" +type Report implements WithTimestamps { + createdAt: ISO8601DateTime! + "Additional information related to why the report was made" + explanation: String + id: ID! + "The moderator who responded to this report" + moderator: Profile + "The entity that the report is related to" + naughty: ReportItemUnion! + "The reason for why the report was made" + reason: ReportReasonEnum! + "The user who made this report" + reporter: Profile! + "The resolution status for this report" + status: ReportStatusEnum! + updatedAt: ISO8601DateTime! +} + +"The connection type for Report." +type ReportConnection { + "A list of edges." + edges: [ReportEdge] + "A list of nodes." + nodes: [Report] + "Information to aid in pagination." + pageInfo: PageInfo! + "The total amount of nodes." + totalCount: Int! +} + +"An edge in a connection." +type ReportEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Report +} + +"A media review made by a user" +type Review implements WithTimestamps { + "The author who wrote this review." + author: Profile! + "The review data" + content: String! + createdAt: ISO8601DateTime! + "The review data formatted" + formattedContent: String! + id: ID! + "Does this review contain spoilers from the media" + isSpoiler: Boolean! + "The library entry related to this review." + libraryEntry: LibraryEntry! + "Users who liked this review" + likes( + "Returns the elements in the list that come after the specified cursor." + after: String, + "Returns the elements in the list that come before the specified cursor." + before: String, + "Returns the first _n_ elements from the list." + first: Int, + "Returns the last _n_ elements from the list." + last: Int + ): ProfileConnection! + "The media related to this review." + media: Media! + "When this review was written based on media progress." + progress: Int! + "The user rating for this media" + rating: Int! + "Potentially migrated over from hummingbird." + source: String! + updatedAt: ISO8601DateTime! +} + +"The connection type for Review." +type ReviewConnection { + "A list of edges." + edges: [ReviewEdge] + "A list of nodes." + nodes: [Review] + "Information to aid in pagination." + pageInfo: PageInfo! + "The total amount of nodes." + totalCount: Int! +} + +"An edge in a connection." +type ReviewEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: Review +} + "Information about a user session" type Session { "The account associated with this session" @@ -2417,6 +2723,79 @@ type Volume implements WithTimestamps { updatedAt: ISO8601DateTime! } +"A Wiki Submission is used to either create or edit existing data in our database. This will allow a simple and convient way for users to submit issues/corrections without all the work being left to the mods." +type WikiSubmission implements WithTimestamps { + "The user who created this draft" + author: Profile! + createdAt: ISO8601DateTime! + "The full object that holds all the details for any modifications/additions/deletions made to the entity you are editing. This will be validated using JSON Schema." + data: JSON + id: ID! + "Any additional information that may need to be provided related to the Wiki Submission" + notes: String + "The status of the Wiki Submission" + status: WikiSubmissionStatusEnum! + "The title given to the Wiki Submission. This will default to the title of what is being edited." + title: String + updatedAt: ISO8601DateTime! +} + +"The connection type for WikiSubmission." +type WikiSubmissionConnection { + "A list of edges." + edges: [WikiSubmissionEdge] + "A list of nodes." + nodes: [WikiSubmission] + "Information to aid in pagination." + pageInfo: PageInfo! + "The total amount of nodes." + totalCount: Int! +} + +"Autogenerated return type of WikiSubmissionCreateDraft" +type WikiSubmissionCreateDraftPayload { + errors: [Error!] + wikiSubmission: WikiSubmission +} + +"An edge in a connection." +type WikiSubmissionEdge { + "A cursor for use in pagination." + cursor: String! + "The item at the end of the edge." + node: WikiSubmission +} + +type WikiSubmissionMutations { + "Create a wiki submission draft" + createDraft( + "Create a wiki submission draft." + input: WikiSubmissionCreateDraftInput! + ): WikiSubmissionCreateDraftPayload + "Submit a wiki submission draft" + submitDraft( + "Submit a wiki submission draft. This will change the status to pending." + input: WikiSubmissionSubmitDraftInput! + ): WikiSubmissionSubmitDraftPayload + "Update a wiki submission draft" + updateDraft( + "Update a wiki submission draft." + input: WikiSubmissionUpdateDraftInput! + ): WikiSubmissionUpdateDraftPayload +} + +"Autogenerated return type of WikiSubmissionSubmitDraft" +type WikiSubmissionSubmitDraftPayload { + errors: [Error!] + wikiSubmission: WikiSubmission +} + +"Autogenerated return type of WikiSubmissionUpdateDraft" +type WikiSubmissionUpdateDraftPayload { + errors: [Error!] + wikiSubmission: WikiSubmission +} + enum AgeRatingEnum { "Acceptable for all ages" G @@ -2468,6 +2847,26 @@ enum FollowSortEnum { FOLLOWING_FOLLOWER } +enum InstallmentSortEnum { + ALTERNATIVE_ORDER + RELEASE_ORDER +} + +enum InstallmentTagEnum { + "Same universe/world/reality/timeline, completely different characters." + ALTERNATE_SETTING + "Same setting, same characters, story is told differently." + ALTERNATE_VERSION + "Characters from different media meet in the same story." + CROSSOVER + "The main story." + MAIN_STORY + "Takes place sometime during the main storyline." + SIDE_STORY + "Uses characters of a different series, but is not an alternate setting or story." + SPINOFF +} + enum LibraryEntryStatusEnum { "The user completed this media." COMPLETED @@ -2494,6 +2893,11 @@ enum LibraryEventKindEnum { UPDATED } +enum LibraryEventSortEnum { + CREATED_AT + UPDATED_AT +} + enum LockedReasonEnum { CLOSED SPAM @@ -2622,6 +3026,23 @@ enum ReleaseStatusEnum { UPCOMING } +enum ReportReasonEnum { + "No bulli!" + BULLYING + "Not Safe For Work" + NSFW + OFFENSIVE + OTHER + SPAM + SPOILER +} + +enum ReportStatusEnum { + DECLINED + REPORTED + RESOLVED +} + enum SitePermissionEnum { "Administrator/staff member of Kitsu" ADMIN @@ -2645,6 +3066,18 @@ enum TitleLanguagePreferenceEnum { ROMANIZED } +enum WikiSubmissionSortEnum { + CREATED_AT + UPDATED_AT +} + +enum WikiSubmissionStatusEnum { + APPROVED + DRAFT + PENDING + REJECTED +} + input AnimeCreateInput { ageRating: AgeRatingEnum ageRatingGuide: String @@ -2716,6 +3149,11 @@ input GenericDeleteInput { id: ID! } +input InstallmentSortOption { + direction: SortDirection! + on: InstallmentSortEnum! +} + input LibraryEntryCreateInput { finishedAt: ISO8601DateTime mediaId: ID! @@ -2781,9 +3219,9 @@ input LibraryEntryUpdateStatusByMediaInput { status: LibraryEntryStatusEnum! } -input LockInput { - id: ID! - lockedReason: LockedReasonEnum! +input LibraryEventSortOption { + direction: SortDirection! + on: LibraryEventSortEnum! } input MappingCreateInput { @@ -2806,11 +3244,20 @@ input PostLikeSortOption { on: PostLikeSortEnum! } +input PostLockInput { + id: ID! + lockedReason: LockedReasonEnum! +} + input PostSortOption { direction: SortDirection! on: PostSortEnum! } +input PostUnlockInput { + id: ID! +} + input TitlesListInput { alternatives: [String!] canonical: String @@ -2818,8 +3265,28 @@ input TitlesListInput { localized: Map } -input UnlockInput { +input WikiSubmissionCreateDraftInput { + data: JSON! + notes: String + title: String +} + +input WikiSubmissionSortOption { + direction: SortDirection! + on: WikiSubmissionSortEnum! +} + +input WikiSubmissionSubmitDraftInput { + data: JSON! id: ID! + notes: String + title: String +} + +input WikiSubmissionUpdateDraftInput { + data: JSON! + id: ID! + notes: String } @@ -2832,6 +3299,9 @@ scalar ISO8601Date "An ISO 8601-encoded datetime" scalar ISO8601DateTime +"Represents untyped JSON" +scalar JSON + "A loose key-value map in GraphQL" scalar Map