diff --git a/src/index.tsx b/src/index.tsx index 2e1d24d..8cfad3c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -162,6 +162,7 @@ export type Challenge = { export type ChallengeEntry = { __typename?: "ChallengeEntry"; + attemptCount?: Maybe; challenge: Challenge; createdAt: Scalars["DateTime"]["output"]; id: Scalars["ID"]["output"]; @@ -2336,6 +2337,7 @@ export type Mutation = { deleteTags: Scalars["Boolean"]["output"]; deleteUser: Scalars["Boolean"]["output"]; deleteVideo: Scalars["Boolean"]["output"]; + dismissChallenge: Scalars["Boolean"]["output"]; editComment: Scalars["Boolean"]["output"]; editProfileImageUri: UserGql; editShot: EditShotReturn; @@ -2360,6 +2362,7 @@ export type Mutation = { setSegmentDuration: Scalars["Boolean"]["output"]; startChallenge: ChallengeEntry; submitChallengeEntry: ChallengeEntry; + undismissChallenge: Scalars["Boolean"]["output"]; unfollowUser: UserGql; updateShotAnnotations: UpdateShotAnnotationReturn; }; @@ -2432,6 +2435,10 @@ export type MutationDeleteVideoArgs = { videoId: Scalars["Int"]["input"]; }; +export type MutationDismissChallengeArgs = { + challengeId: Scalars["ID"]["input"]; +}; + export type MutationEditCommentArgs = { commentId: Scalars["Int"]["input"]; newMessage: Scalars["String"]["input"]; @@ -2535,6 +2542,10 @@ export type MutationSubmitChallengeEntryArgs = { videoId: Scalars["ID"]["input"]; }; +export type MutationUndismissChallengeArgs = { + challengeId: Scalars["ID"]["input"]; +}; + export type MutationUnfollowUserArgs = { followedUserId: Scalars["Int"]["input"]; }; @@ -2697,8 +2708,10 @@ export type Query = { getVideo: VideoGql; getVideoMakePercentageIntervals: Array; getVideos: Array; + isChallengeDismissed: Scalars["Boolean"]["output"]; myChallengeEntries: Array; myChallengeInvitations: Array; + myDismissedChallenges: Array; notifications: NotificationConnection; ruleSets: Array; unreadNotificationCount: Scalars["Int"]["output"]; @@ -2714,6 +2727,10 @@ export type QueryChallengeLeaderboardArgs = { limit?: Scalars["Int"]["input"]; }; +export type QueryChallengesArgs = { + includeDismissed?: Scalars["Boolean"]["input"]; +}; + export type QueryDoesUsernameExistArgs = { candidateUsername: Scalars["String"]["input"]; }; @@ -2850,6 +2867,10 @@ export type QueryGetVideosArgs = { videoIds: Array; }; +export type QueryIsChallengeDismissedArgs = { + challengeId: Scalars["ID"]["input"]; +}; + export type QueryNotificationsArgs = { filters?: InputMaybe; limit?: Scalars["Int"]["input"]; @@ -3551,6 +3572,41 @@ export type GetChallengesQuery = { }>; }; +export type GetMyDismissedChallengesQueryVariables = Exact<{ + [key: string]: never; +}>; + +export type GetMyDismissedChallengesQuery = { + __typename?: "Query"; + myDismissedChallenges: Array<{ + __typename?: "Challenge"; + id: string; + name: string; + description?: string | null; + minimumShots: number; + startDate: any; + endDate: any; + createdAt: any; + updatedAt: any; + requiredTableSize?: number | null; + requiredPocketSize?: number | null; + isPublic: boolean; + maxAttempts?: number | null; + ruleSet: { + __typename?: "RuleSet"; + id: string; + name: string; + description?: string | null; + }; + createdBy: { + __typename?: "UserGQL"; + id: number; + username: string; + profileImageUri?: string | null; + }; + }>; +}; + export type GetChallengeQueryVariables = Exact<{ id: Scalars["ID"]["input"]; }>; @@ -3633,6 +3689,7 @@ export type GetChallengeLeaderboardQuery = { makeRate?: number | null; qualified?: boolean | null; createdAt: any; + attemptCount?: number | null; user: { __typename?: "UserGQL"; id: number; @@ -3824,6 +3881,33 @@ export type RecalculateChallengeEntryMutation = { }; }; +export type IsChallengeDismissedQueryVariables = Exact<{ + challengeId: Scalars["ID"]["input"]; +}>; + +export type IsChallengeDismissedQuery = { + __typename?: "Query"; + isChallengeDismissed: boolean; +}; + +export type DismissChallengeMutationVariables = Exact<{ + challengeId: Scalars["ID"]["input"]; +}>; + +export type DismissChallengeMutation = { + __typename?: "Mutation"; + dismissChallenge: boolean; +}; + +export type UndismissChallengeMutationVariables = Exact<{ + challengeId: Scalars["ID"]["input"]; +}>; + +export type UndismissChallengeMutation = { + __typename?: "Mutation"; + undismissChallenge: boolean; +}; + export type CommentOnVideoMutationVariables = Exact<{ videoId: Scalars["Int"]["input"]; message: Scalars["String"]["input"]; @@ -6788,6 +6872,99 @@ export type GetChallengesQueryResult = Apollo.QueryResult< GetChallengesQuery, GetChallengesQueryVariables >; +export const GetMyDismissedChallengesDocument = gql` + query GetMyDismissedChallenges { + myDismissedChallenges { + id + name + description + minimumShots + startDate + endDate + createdAt + updatedAt + requiredTableSize + requiredPocketSize + isPublic + maxAttempts + ruleSet { + id + name + description + } + createdBy { + id + username + profileImageUri + } + } + } +`; + +/** + * __useGetMyDismissedChallengesQuery__ + * + * To run a query within a React component, call `useGetMyDismissedChallengesQuery` and pass it any options that fit your needs. + * When your component renders, `useGetMyDismissedChallengesQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetMyDismissedChallengesQuery({ + * variables: { + * }, + * }); + */ +export function useGetMyDismissedChallengesQuery( + baseOptions?: Apollo.QueryHookOptions< + GetMyDismissedChallengesQuery, + GetMyDismissedChallengesQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useQuery< + GetMyDismissedChallengesQuery, + GetMyDismissedChallengesQueryVariables + >(GetMyDismissedChallengesDocument, options); +} +export function useGetMyDismissedChallengesLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions< + GetMyDismissedChallengesQuery, + GetMyDismissedChallengesQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useLazyQuery< + GetMyDismissedChallengesQuery, + GetMyDismissedChallengesQueryVariables + >(GetMyDismissedChallengesDocument, options); +} +export function useGetMyDismissedChallengesSuspenseQuery( + baseOptions?: Apollo.SuspenseQueryHookOptions< + GetMyDismissedChallengesQuery, + GetMyDismissedChallengesQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useSuspenseQuery< + GetMyDismissedChallengesQuery, + GetMyDismissedChallengesQueryVariables + >(GetMyDismissedChallengesDocument, options); +} +export type GetMyDismissedChallengesQueryHookResult = ReturnType< + typeof useGetMyDismissedChallengesQuery +>; +export type GetMyDismissedChallengesLazyQueryHookResult = ReturnType< + typeof useGetMyDismissedChallengesLazyQuery +>; +export type GetMyDismissedChallengesSuspenseQueryHookResult = ReturnType< + typeof useGetMyDismissedChallengesSuspenseQuery +>; +export type GetMyDismissedChallengesQueryResult = Apollo.QueryResult< + GetMyDismissedChallengesQuery, + GetMyDismissedChallengesQueryVariables +>; export const GetChallengeDocument = gql` query GetChallenge($id: ID!) { challenge(id: $id) { @@ -6980,6 +7157,7 @@ export const GetChallengeLeaderboardDocument = gql` makeRate qualified createdAt + attemptCount user { id username @@ -7658,6 +7836,173 @@ export type RecalculateChallengeEntryMutationOptions = RecalculateChallengeEntryMutation, RecalculateChallengeEntryMutationVariables >; +export const IsChallengeDismissedDocument = gql` + query IsChallengeDismissed($challengeId: ID!) { + isChallengeDismissed(challengeId: $challengeId) + } +`; + +/** + * __useIsChallengeDismissedQuery__ + * + * To run a query within a React component, call `useIsChallengeDismissedQuery` and pass it any options that fit your needs. + * When your component renders, `useIsChallengeDismissedQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useIsChallengeDismissedQuery({ + * variables: { + * challengeId: // value for 'challengeId' + * }, + * }); + */ +export function useIsChallengeDismissedQuery( + baseOptions: Apollo.QueryHookOptions< + IsChallengeDismissedQuery, + IsChallengeDismissedQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useQuery< + IsChallengeDismissedQuery, + IsChallengeDismissedQueryVariables + >(IsChallengeDismissedDocument, options); +} +export function useIsChallengeDismissedLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions< + IsChallengeDismissedQuery, + IsChallengeDismissedQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useLazyQuery< + IsChallengeDismissedQuery, + IsChallengeDismissedQueryVariables + >(IsChallengeDismissedDocument, options); +} +export function useIsChallengeDismissedSuspenseQuery( + baseOptions?: Apollo.SuspenseQueryHookOptions< + IsChallengeDismissedQuery, + IsChallengeDismissedQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useSuspenseQuery< + IsChallengeDismissedQuery, + IsChallengeDismissedQueryVariables + >(IsChallengeDismissedDocument, options); +} +export type IsChallengeDismissedQueryHookResult = ReturnType< + typeof useIsChallengeDismissedQuery +>; +export type IsChallengeDismissedLazyQueryHookResult = ReturnType< + typeof useIsChallengeDismissedLazyQuery +>; +export type IsChallengeDismissedSuspenseQueryHookResult = ReturnType< + typeof useIsChallengeDismissedSuspenseQuery +>; +export type IsChallengeDismissedQueryResult = Apollo.QueryResult< + IsChallengeDismissedQuery, + IsChallengeDismissedQueryVariables +>; +export const DismissChallengeDocument = gql` + mutation DismissChallenge($challengeId: ID!) { + dismissChallenge(challengeId: $challengeId) + } +`; +export type DismissChallengeMutationFn = Apollo.MutationFunction< + DismissChallengeMutation, + DismissChallengeMutationVariables +>; + +/** + * __useDismissChallengeMutation__ + * + * To run a mutation, you first call `useDismissChallengeMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useDismissChallengeMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [dismissChallengeMutation, { data, loading, error }] = useDismissChallengeMutation({ + * variables: { + * challengeId: // value for 'challengeId' + * }, + * }); + */ +export function useDismissChallengeMutation( + baseOptions?: Apollo.MutationHookOptions< + DismissChallengeMutation, + DismissChallengeMutationVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useMutation< + DismissChallengeMutation, + DismissChallengeMutationVariables + >(DismissChallengeDocument, options); +} +export type DismissChallengeMutationHookResult = ReturnType< + typeof useDismissChallengeMutation +>; +export type DismissChallengeMutationResult = + Apollo.MutationResult; +export type DismissChallengeMutationOptions = Apollo.BaseMutationOptions< + DismissChallengeMutation, + DismissChallengeMutationVariables +>; +export const UndismissChallengeDocument = gql` + mutation UndismissChallenge($challengeId: ID!) { + undismissChallenge(challengeId: $challengeId) + } +`; +export type UndismissChallengeMutationFn = Apollo.MutationFunction< + UndismissChallengeMutation, + UndismissChallengeMutationVariables +>; + +/** + * __useUndismissChallengeMutation__ + * + * To run a mutation, you first call `useUndismissChallengeMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUndismissChallengeMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [undismissChallengeMutation, { data, loading, error }] = useUndismissChallengeMutation({ + * variables: { + * challengeId: // value for 'challengeId' + * }, + * }); + */ +export function useUndismissChallengeMutation( + baseOptions?: Apollo.MutationHookOptions< + UndismissChallengeMutation, + UndismissChallengeMutationVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useMutation< + UndismissChallengeMutation, + UndismissChallengeMutationVariables + >(UndismissChallengeDocument, options); +} +export type UndismissChallengeMutationHookResult = ReturnType< + typeof useUndismissChallengeMutation +>; +export type UndismissChallengeMutationResult = + Apollo.MutationResult; +export type UndismissChallengeMutationOptions = Apollo.BaseMutationOptions< + UndismissChallengeMutation, + UndismissChallengeMutationVariables +>; export const CommentOnVideoDocument = gql` mutation CommentOnVideo( $videoId: Int! diff --git a/src/operations/challenges.gql b/src/operations/challenges.gql index 8d129f2..c109c57 100644 --- a/src/operations/challenges.gql +++ b/src/operations/challenges.gql @@ -25,6 +25,33 @@ query GetChallenges { } } +query GetMyDismissedChallenges { + myDismissedChallenges { + id + name + description + minimumShots + startDate + endDate + createdAt + updatedAt + requiredTableSize + requiredPocketSize + isPublic + maxAttempts + ruleSet { + id + name + description + } + createdBy { + id + username + profileImageUri + } + } +} + query GetChallenge($id: ID!) { challenge(id: $id) { id @@ -85,6 +112,7 @@ query GetChallengeLeaderboard($challengeId: ID!, $limit: Int) { makeRate qualified createdAt + attemptCount user { id username @@ -240,3 +268,15 @@ mutation RecalculateChallengeEntry($entryId: ID!) { makesCount } } + +query IsChallengeDismissed($challengeId: ID!) { + isChallengeDismissed(challengeId: $challengeId) +} + +mutation DismissChallenge($challengeId: ID!) { + dismissChallenge(challengeId: $challengeId) +} + +mutation UndismissChallenge($challengeId: ID!) { + undismissChallenge(challengeId: $challengeId) +} diff --git a/src/schema.gql b/src/schema.gql index 749ca2c..c0c68a4 100644 --- a/src/schema.gql +++ b/src/schema.gql @@ -1,9 +1,666 @@ +directive @oneOf on INPUT_OBJECT + +type AddShotAnnotationErrors { + error: DoesNotOwnShotErrOtherErrorNeedsNote! +} + +type AddShotAnnotationReturn { + value: SuccessfulAddAddShotAnnotationErrors! +} + +input AggregateInputGQL { + aggregations: [AggregationInput!]! + filterInput: FilterInput +} + +type AggregateResultGQL { + aggregationIdentifiers: [AggregationIdentifierGQL!]! + targetMetrics: TargetMetricsGQL! +} + +type AggregationIdentifierGQL { + featureName: String! + groupName: String! +} + +input AggregationInput @oneOf { + bucketSet: BucketSetInputGQL + enum: EnumAggregation + datetimeRange: DatetimeRangeAggregationInput +} + +enum AlignedIntervalEnum { + MONTH + YEAR + WEEK + DAY +} + +type BankFeaturesGQL { + wallsHit: [WallTypeEnum!]! + bankAngle: Float! + distance: Float! +} + +type BannerGQL { + id: Int! + message: String! + color: String! + kind: BannerKindEnum! + dismissible: Boolean! + priority: Int! +} + +enum BannerKindEnum { + INFO + WARNING + ERROR +} + +type BoundingBoxGQL { + left: Float! + top: Float! + width: Float! + height: Float! +} + +input BoundingBoxInputGQL { + left: Float! + top: Float! + width: Float! + height: Float! +} + +type BucketGQL { + rangeKey: String! + lowerBound: Float! +} + +input BucketInputGQL { + rangeKey: String! + lowerBound: Float! +} + +type BucketSetGQL { + keyName: String! + feature: String! + buckets: [BucketGQL!]! +} + +input BucketSetInputGQL { + feature: String! + buckets: [BucketInputGQL!]! +} + +type Challenge { + id: ID! + name: String! + description: String + minimumShots: Int! + requiredTableSize: Float + requiredPocketSize: Float + isPublic: Boolean! + maxAttempts: Int + startDate: DateTime! + endDate: DateTime! + createdAt: DateTime! + updatedAt: DateTime! + ruleSet: RuleSet! + createdBy: UserGQL! + invitations: [ChallengeInvitation!]! + participantCount: Int! +} + +type ChallengeEntry { + id: ID! + status: String! + shotsCount: Int + makesCount: Int + makeRate: Float + qualified: Boolean + createdAt: DateTime! + attemptCount: Int + challenge: Challenge! + video: VideoGQL + user: UserGQL! +} + +type ChallengeInvitation { + id: ID! + status: String! + createdAt: DateTime! + challenge: Challenge! + inviter: UserGQL! + invitee: UserGQL! +} + +enum ClientUploadStatusEnum { + UPLOAD_ENABLED + UPLOAD_DISABLED +} + +type CommentGQL { + id: Int! + user: UserGQL! + message: String! + replies: [CommentGQL!]! +} + +type CountLeaderboardGQL { + entries: [UserShotCountEntry!]! +} + +input CreateBucketSetInput { + keyName: String! + feature: String! + buckets: [BucketInputGQL!]! +} + +type CreateSubscriptionResultGQL { + checkoutUrl: String! + sessionId: String! +} + +type CreateUploadStreamReturn { + videoId: Int! +} + +input CreatedAfter @oneOf { + videoId: Int + createdAt: DateTime +} + +type CueObjectFeaturesGQL { + cueObjectDistance: Float + cueObjectAngle: Float + cueBallSpeed: Float + shotDirection: ShotDirectionEnum + spinType: SpinTypeEnum +} + +""" +Date (isoformat) +""" +scalar Date + +input DateRangeFilter { + lessThan: Date = null + greaterThanEqualTo: Date = null + greaterThan: Date = null + includeOnNone: Boolean! = false + lessThanInclusive: Boolean! = false + greaterThanInclusive: Boolean! = true +} + +""" +Date with time (isoformat) +""" +scalar DateTime + +input DatetimeOrdering { + descending: Boolean! = true + startingAt: DateTime = null +} + +input DatetimeRangeAggregationInput { + startDatetime: DateTime = null + endDatetime: DateTime = null + interval: TimeInterval! + feature: String! = "created_at" +} + +type DeployedConfigGQL { + allowNewUsers: Boolean! + firebase: Boolean! + devMode: Boolean! + environment: String! + minimumAllowedAppVersion: String! + subscriptionGatingEnabled: Boolean! + bannerMessages: [BannerGQL!]! +} + +type DoesNotOwnShotErr { + shotId: Int! + msg: String +} + +union DoesNotOwnShotErrOtherErrorNeedsNote = + DoesNotOwnShotErr + | OtherErrorNeedsNote + +type EditShotReturn { + shot: ShotGQL + error: DoesNotOwnShotErr +} + +input EditUserInputGQL { + username: String = null + fargoRating: Int = null + videosPrivateByDefault: Boolean = null + agreesToMarketing: Boolean = null +} + +input EditableShotFieldInputGQL { + intendedPocketType: PocketEnum + shotDirection: ShotDirectionEnum + spinType: SpinTypeEnum + targetPocketAngleDirection: ShotDirectionEnum + make: Boolean + backcut: Boolean + excludeFromStats: Boolean + notes: String +} + +input EnumAggregation { + feature: String! +} + +input FilterInput @oneOf { + andFilters: [FilterInput!] + orFilters: [FilterInput!] + notFilter: FilterInput + cueObjectDistance: FloatRangeFilter + targetPocketDistance: FloatRangeFilter + cueObjectAngle: FloatRangeFilter + cueBallSpeed: FloatRangeFilter + difficulty: FloatRangeFilter + intendedPocketType: [PocketEnum!] + shotDirection: [ShotDirectionEnum!] + videoId: [Int!] + userId: [Int!] + runId: [Int!] + username: [String!] + fargoRating: FloatRangeFilter + make: [Boolean!] + tags: [VideoTagInput!] + annotations: [ShotAnnotationInput!] + isStraight: [Boolean!] + isRight: [Boolean!] + isLeft: [Boolean!] + isLeftMiss: [Boolean!] + isRightMiss: [Boolean!] + isDirect: [Boolean!] + isBreakHeuristic: [Boolean!] + tableSize: FloatRangeFilter + bankAngle: FloatRangeFilter + bankDistance: FloatRangeFilter + kickAngle: FloatRangeFilter + kickDistance: FloatRangeFilter + cueAngleAfterObject: FloatRangeFilter + spinType: [SpinTypeEnum!] + cueSpeedAfterObject: FloatRangeFilter + falsePositiveScore: FloatRangeFilter + backcut: [Boolean!] + targetPocketAngleDirection: [ShotDirectionEnum!] + targetPocketAngle: FloatRangeFilter + missAngleInDegrees: FloatRangeFilter + marginOfErrorInDegrees: FloatRangeFilter + createdAt: DateRangeFilter + totalDistance: FloatRangeFilter + runLength: FloatRangeFilter +} + +input FloatOrdering { + descending: Boolean! = true + startingAt: Float = null +} + +input FloatRangeFilter { + lessThan: Float = null + greaterThanEqualTo: Float = null + greaterThan: Float = null + includeOnNone: Boolean! = false + lessThanInclusive: Boolean! = false + greaterThanInclusive: Boolean! = true +} + +type GetProfileUploadLinkErrors { + error: TooManyProfileImageUploadsErr! +} + +type GetProfileUploadLinkReturn { + value: UploadLinkGetProfileUploadLinkErrors! +} + +input GetRunsOrdering { + orderings: [RunsOrderingComponent!]! +} + +type GetRunsResult { + runs: [RunGQL!]! + count: Int + runIds: [Int!]! +} + +input GetShotsOrdering { + orderings: [ShotsOrderingComponent!]! +} + +input GetShotsPagination { + createdAfter: CreatedAfter! + startFrameAfter: Int! +} + +type GetShotsResult { + shots: [ShotGQL!]! + count: Int + ids: [Int!]! +} + +type GetUploadLinkErrors { + error: MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErr! +} + +type GetUploadLinkReturn { + value: UploadLinkGetUploadLinkErrors! + stream: UploadStreamGQL +} + +type HLSPlaylistGQL { + videoId: Int! + m3u8Text: String! + segmentDurations: [Float!]! +} + +type Header { + key: String! + value: String! +} + +type HomographyInfoGQL { + id: Int! + frameIndex: Int! + crop: BoundingBoxGQL! + pockets: [BoundingBoxGQL!]! + sourcePoints: PocketPointsGQL! + destPoints: PocketPointsGQL! +} + +input HomographyInputGQL { + crop: BoundingBoxInputGQL! + pockets: [BoundingBoxInputGQL!]! + sourcePoints: PocketPointsInputGQL! + destPoints: PocketPointsInputGQL! +} + +enum IncludePrivateEnum { + ALL + MINE + NONE +} + +enum InitPlaylistUploadStatusEnum { + NOT_APPLICABLE + NOT_UPLOADED + UPLOADED +} + +type InitUploadAlreadyCompletedErr { + segmentType: StreamSegmentTypeEnum! +} + +input IntOrdering { + descending: Boolean! = true + startingAt: Int = null +} + +type IntPoint2D { + x: Int! + y: Int! +} + +input IntPoint2DInput { + x: Int! + y: Int! +} + +""" +The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf). +""" +scalar JSON + @specifiedBy( + url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf" + ) + +type MakePercentageIntervalGQL { + makePercentage: Float! + elapsedTime: Float! +} + +type MedalGQL { + count: Int! + nickname: String +} + +input MedalScope @oneOf { + videoId: Int + interval: TimeInterval + @deprecated(reason: "NO LONGER SUPPORTED, USE DATETIME_RANGE") + datetimeRange: DatetimeRangeAggregationInput +} + +type MustHaveSetForUploadLinkErr { + resolution: Boolean + framesPerSecond: Boolean +} + +union MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErr = + MustHaveSetForUploadLinkErr + | SegmentAlreadyUploadedErr + | ProcessingFailedErr + | NoInitForChunkedUploadErr + | TooManyProfileImageUploadsErr + | InitUploadAlreadyCompletedErr + | TooManyInitUploadsErr + +type Mutation { + createBucketSet(params: CreateBucketSetInput!): BucketSetGQL! + createRuleSet(name: String!, description: String = null): RuleSet! + createChallenge( + name: String! + ruleSetId: ID! + minimumShots: Int! + startDate: DateTime! + endDate: DateTime! + description: String = null + requiredTableSize: Float = null + requiredPocketSize: Float = null + isPublic: Boolean! = false + maxAttempts: Int = null + ): Challenge! + inviteUsersToChallenge( + challengeId: ID! + userIds: [ID!]! + ): [ChallengeInvitation!]! + respondToChallengeInvitation( + invitationId: ID! + accept: Boolean! + ): ChallengeInvitation! + startChallenge(challengeId: ID!): ChallengeEntry! + recalculateChallengeEntry(entryId: ID!): ChallengeEntry! + submitChallengeEntry(entryId: ID!, videoId: ID!): ChallengeEntry! + dismissChallenge(challengeId: ID!): Boolean! + undismissChallenge(challengeId: ID!): Boolean! + setLoggerLevel(path: String!, level: String!): Boolean! + reactToVideo(videoId: Int!, reaction: ReactionEnum): Boolean! + commentOnVideo( + videoId: Int! + message: String! + parentCommentId: Int + ): Boolean! + editComment(videoId: Int!, commentId: Int!, newMessage: String!): Boolean! + deleteComment(videoId: Int!, commentId: Int!): Boolean! + blockContent(videoId: Int!): Boolean! + blockUser(userId: Int!): Boolean! + reportContent( + videoId: Int! + reason: ReportReasonEnum! + customReason: String = null + ): Boolean! + markNotificationAsRead(notificationId: Int!): Boolean! + markAllNotificationsAsRead: Boolean! + markNotificationsAsRead(notificationIds: [Int!]!): Boolean! + deleteNotification(notificationId: Int!): Boolean! + addAnnotationToShot( + shotId: Int! + annotationName: String! + notes: String = null + ): AddShotAnnotationReturn! + updateShotAnnotations( + shotId: Int! + annotations: [UpdateAnnotationInputGQL!]! + ): UpdateShotAnnotationReturn! + editShot( + shotId: Int! + fieldsToEdit: EditableShotFieldInputGQL! + ): EditShotReturn! + getProfileImageUploadLink( + fileExt: String = ".png" + ): GetProfileUploadLinkReturn! + editProfileImageUri(profileImageUri: String!): UserGQL! + editUser(input: EditUserInputGQL!): UserGQL! + followUser(followedUserId: Int!): UserGQL! + unfollowUser(followedUserId: Int!): UserGQL! + retireTags(tagIds: [Int!]!): Boolean! + ensureStripeCustomerExists: UserGQL! + deleteUser: Boolean! + createSubscription(priceId: String!): CreateSubscriptionResultGQL! + cancelSubscription: UserSubscriptionStatusGQL! + findPrerecordTableLayout(b64Image: String!, videoId: Int!): HomographyInfoGQL + createUploadStream( + videoMetadata: VideoMetadataInput! + ): CreateUploadStreamReturn! + getUploadLink(videoId: Int!, segmentIndex: Int!): GetUploadLinkReturn! + getHlsInitUploadLink(videoId: Int!): GetUploadLinkReturn! + setSegmentDuration( + videoId: Int! + segmentIndex: Int! + duration: Float! + ): Boolean! + editUploadStream(videoId: Int!, videoMetadata: VideoMetadataInput!): Boolean! + deleteVideo(videoId: Int!): Boolean! + deleteTags(videoId: Int!, tagsToDelete: [VideoTagInput!]!): Boolean! +} + +type NoInitForChunkedUploadErr { + segmentType: StreamSegmentTypeEnum! +} + +type NotificationConnection { + notifications: [NotificationGQL!]! + totalCount: Int! + unreadCount: Int! + hasMore: Boolean! +} + +input NotificationFilters { + isRead: Boolean = null + notificationTypes: [NotificationTypeEnum!] = null +} + +type NotificationGQL { + id: Int! + notificationType: NotificationTypeEnum! + actor: UserGQL! + videoId: Int + challengeId: Int + challenge: Challenge + comment: CommentGQL + reactionType: String + isRead: Boolean! + createdAt: DateTime! + readAt: DateTime +} + +enum NotificationTypeEnum { + COMMENT + COMMENT_REPLY + REACTION + FOLLOW + CHALLENGE_INVITE +} + +type OtherErrorNeedsNote { + msg: String +} + +type PageInfoGQL { + hasNextPage: Boolean! + endCursor: String +} + +enum PocketEnum { + CORNER + SIDE +} + +enum PocketIdentifier { + TOP_LEFT + TOP_SIDE + TOP_RIGHT + BOTTOM_LEFT + BOTTOM_SIDE + BOTTOM_RIGHT +} + +type PocketPointsGQL { + topLeft: IntPoint2D! + topSide: IntPoint2D! + topRight: IntPoint2D! + bottomLeft: IntPoint2D! + bottomSide: IntPoint2D! + bottomRight: IntPoint2D! +} + +input PocketPointsInputGQL { + topLeft: IntPoint2DInput! + topSide: IntPoint2DInput! + topRight: IntPoint2DInput! + bottomLeft: IntPoint2DInput! + bottomSide: IntPoint2DInput! + bottomRight: IntPoint2DInput! +} + +type PocketingIntentionFeaturesGQL { + targetPocketDistance: Float + make: Boolean + intendedPocketType: PocketEnum + difficulty: Float + targetPocketAngle: Float + targetPocketAngleDirection: ShotDirectionEnum + marginOfErrorInDegrees: Float + backcut: Boolean +} + +type PocketingIntentionInfoGQL { + ballId: Int! + pocketId: PocketIdentifier! + pathMetadataIndex: Int! +} + +type ProcessingFailedErr { + processing: VideoProcessingGQL! +} + +enum ProcessingStatusEnum { + STARTED + FAILED + SUCCEEDED + SUSPENDED + CREATED + QUEUED + RUNNING + REEXTRACTING_FEATURES +} + type Query { getAggregatedShotMetrics( aggregateInput: AggregateInputGQL! ): [AggregateResultGQL!]! getBucketSet(keyName: String!): BucketSetGQL - challenges: [Challenge!]! + challenges(includeDismissed: Boolean! = false): [Challenge!]! + myDismissedChallenges: [Challenge!]! + isChallengeDismissed(challengeId: ID!): Boolean! challenge(id: ID!): Challenge challengeLeaderboard(challengeId: ID!, limit: Int! = 50): [ChallengeEntry!]! myChallengeInvitations: [ChallengeInvitation!]! @@ -107,523 +764,11 @@ type Query { getVideos(videoIds: [Int!]!): [VideoGQL!]! } -type AggregateResultGQL { - aggregationIdentifiers: [AggregationIdentifierGQL!]! - targetMetrics: TargetMetricsGQL! -} - -type AggregationIdentifierGQL { - featureName: String! - groupName: String! -} - -type TargetMetricsGQL { - count: Int! - makePercentage: Float - averageDifficulty: Float - spinTypeCounts: SpinTypeCountsGQL - shotDirectionCounts: ShotDirectionCountsGQL -} - -type SpinTypeCountsGQL { - follow: Int! - draw: Int! - center: Int! - unknown: Int! -} - -type ShotDirectionCountsGQL { - left: Int! - right: Int! - straight: Int! -} - -input AggregateInputGQL { - aggregations: [AggregationInput!]! - filterInput: FilterInput -} - -input AggregationInput @oneOf { - bucketSet: BucketSetInputGQL - enum: EnumAggregation - datetimeRange: DatetimeRangeAggregationInput -} - -input BucketSetInputGQL { - feature: String! - buckets: [BucketInputGQL!]! -} - -input BucketInputGQL { - rangeKey: String! - lowerBound: Float! -} - -input EnumAggregation { - feature: String! -} - -input DatetimeRangeAggregationInput { - startDatetime: DateTime = null - endDatetime: DateTime = null - interval: TimeInterval! - feature: String! = "created_at" -} - -""" -Date with time (isoformat) -""" -scalar DateTime - -input TimeInterval @oneOf { - timedelta: TimeDeltaGQL - aligned: AlignedIntervalEnum -} - -input TimeDeltaGQL { - days: Int = 0 - weeks: Int = 0 - months: Int = 0 - years: Int = 0 -} - -enum AlignedIntervalEnum { - MONTH - YEAR - WEEK - DAY -} - -input FilterInput @oneOf { - andFilters: [FilterInput!] - orFilters: [FilterInput!] - notFilter: FilterInput - cueObjectDistance: FloatRangeFilter - targetPocketDistance: FloatRangeFilter - cueObjectAngle: FloatRangeFilter - cueBallSpeed: FloatRangeFilter - difficulty: FloatRangeFilter - intendedPocketType: [PocketEnum!] - shotDirection: [ShotDirectionEnum!] - videoId: [Int!] - userId: [Int!] - runId: [Int!] - username: [String!] - fargoRating: FloatRangeFilter - make: [Boolean!] - tags: [VideoTagInput!] - annotations: [ShotAnnotationInput!] - isStraight: [Boolean!] - isRight: [Boolean!] - isLeft: [Boolean!] - isLeftMiss: [Boolean!] - isRightMiss: [Boolean!] - isDirect: [Boolean!] - isBreakHeuristic: [Boolean!] - tableSize: FloatRangeFilter - bankAngle: FloatRangeFilter - bankDistance: FloatRangeFilter - kickAngle: FloatRangeFilter - kickDistance: FloatRangeFilter - cueAngleAfterObject: FloatRangeFilter - spinType: [SpinTypeEnum!] - cueSpeedAfterObject: FloatRangeFilter - falsePositiveScore: FloatRangeFilter - backcut: [Boolean!] - targetPocketAngleDirection: [ShotDirectionEnum!] - targetPocketAngle: FloatRangeFilter - missAngleInDegrees: FloatRangeFilter - marginOfErrorInDegrees: FloatRangeFilter - createdAt: DateRangeFilter - totalDistance: FloatRangeFilter - runLength: FloatRangeFilter -} - -input FloatRangeFilter { - lessThan: Float = null - greaterThanEqualTo: Float = null - greaterThan: Float = null - includeOnNone: Boolean! = false - lessThanInclusive: Boolean! = false - greaterThanInclusive: Boolean! = true -} - -enum PocketEnum { - CORNER - SIDE -} - -enum ShotDirectionEnum { - LEFT - RIGHT - STRAIGHT -} - -input VideoTagInput { - tagClasses: [VideoTagClassInput!]! = [] - name: String! -} - -input VideoTagClassInput { - name: String! -} - -input ShotAnnotationInput { - name: String! -} - -enum SpinTypeEnum { - DRAW - FOLLOW - CENTER - UNKNOWN -} - -input DateRangeFilter { - lessThan: Date = null - greaterThanEqualTo: Date = null - greaterThan: Date = null - includeOnNone: Boolean! = false - lessThanInclusive: Boolean! = false - greaterThanInclusive: Boolean! = true -} - -""" -Date (isoformat) -""" -scalar Date - -type BucketSetGQL { - keyName: String! - feature: String! - buckets: [BucketGQL!]! -} - -type BucketGQL { - rangeKey: String! - lowerBound: Float! -} - -type Challenge { - id: ID! - name: String! - description: String - minimumShots: Int! - requiredTableSize: Float - requiredPocketSize: Float - isPublic: Boolean! - maxAttempts: Int - startDate: DateTime! - endDate: DateTime! - createdAt: DateTime! - updatedAt: DateTime! - ruleSet: RuleSet! - createdBy: UserGQL! - invitations: [ChallengeInvitation!]! - participantCount: Int! -} - -type RuleSet { - id: ID! - name: String! - description: String - createdAt: DateTime! - updatedAt: DateTime! -} - -type UserGQL { - id: Int! - firebaseUid: String - username: String! - isAdmin: Boolean - fargoRating: Int - activeVideoId: Int - stripeCustomerId: String - profileImageUri: String - createdAt: DateTime - updatedAt: DateTime - videosPrivateByDefault: Boolean - agreesToMarketing: Boolean - following: [UserGQL!] - followers: [UserGQL!] - isFollowedByCurrentUser: Boolean -} - -type ChallengeEntry { - id: ID! - status: String! - shotsCount: Int - makesCount: Int - makeRate: Float - qualified: Boolean - createdAt: DateTime! - challenge: Challenge! - video: VideoGQL - user: UserGQL! -} - -type VideoGQL { - id: Int! - owner: UserGQL - name: String - screenshotUri: String - totalShotsMade: Int! - totalShots: Int! - makePercentage: Float! - medianRun: Float - averageTimeBetweenShots: Float - averageDifficulty: Float - createdAt: DateTime - updatedAt: DateTime - shots: [ShotGQL!]! - startTime: DateTime - endTime: DateTime - elapsedTime: Float - framesPerSecond: Float! - tableSize: Float! - pocketSize: Float - private: Boolean! - stream: UploadStreamGQL - playlist: HLSPlaylistGQL - tags: [VideoTag!]! - currentHomography: HomographyInfoGQL - homographyHistory: [HomographyInfoGQL!]! - currentProcessing: VideoProcessingGQL - reactions: [ReactionGQL!]! - comments: [CommentGQL!]! -} - -type ShotGQL { - id: Int! - videoId: Int! - startFrame: Int! - endFrame: Int! - createdAt: DateTime - updatedAt: DateTime - cueObjectFeatures: CueObjectFeaturesGQL - pocketingIntentionFeatures: PocketingIntentionFeaturesGQL - pocketingIntentionInfo: PocketingIntentionInfoGQL - bankFeatures: BankFeaturesGQL - serializedShotPaths: SerializedShotPathsGQL - user: UserGQL - annotations: [ShotAnnotationGQL!]! - falsePositiveScore: Float - video: VideoGQL - run: RunGQL - runFeatures: RunFeaturesGQL -} - -type CueObjectFeaturesGQL { - cueObjectDistance: Float - cueObjectAngle: Float - cueBallSpeed: Float - shotDirection: ShotDirectionEnum - spinType: SpinTypeEnum -} - -type PocketingIntentionFeaturesGQL { - targetPocketDistance: Float - make: Boolean - intendedPocketType: PocketEnum - difficulty: Float - targetPocketAngle: Float - targetPocketAngleDirection: ShotDirectionEnum - marginOfErrorInDegrees: Float - backcut: Boolean -} - -type PocketingIntentionInfoGQL { - ballId: Int! - pocketId: PocketIdentifier! - pathMetadataIndex: Int! -} - -enum PocketIdentifier { - TOP_LEFT - TOP_SIDE - TOP_RIGHT - BOTTOM_LEFT - BOTTOM_SIDE - BOTTOM_RIGHT -} - -type BankFeaturesGQL { - wallsHit: [WallTypeEnum!]! - bankAngle: Float! - distance: Float! -} - -enum WallTypeEnum { - LONG - SHORT -} - -type SerializedShotPathsGQL { - b64EncodedBuffer: String -} - -type ShotAnnotationGQL { - shotId: Int! - type: ShotAnnotationTypeGQL! - creator: UserGQL! - notes: String! - errorDefault: Boolean! - createdAt: DateTime - updatedAt: DateTime -} - -type ShotAnnotationTypeGQL { - id: Int! - name: String! -} - -type RunGQL { - id: Int! - runLength: Int! - videoId: Int! - userId: Int! - shots: [ShotGQL!]! - video: VideoGQL! - user: UserGQL! -} - -type RunFeaturesGQL { - runId: Int! - indexInRun: Int! -} - -type UploadStreamGQL { - id: ID! - linksRequested: Int! - uploadsCompleted: Int! - segmentProcessingCursor: Int! - lastIntendedSegmentBound: Int - isCompleted: Boolean! - initPlaylistUploadStatus: InitPlaylistUploadStatusEnum - lowestUnuploadedSegmentIndex: Int! - uploadCompletionCursor: Int! - errors: [StreamErrorGQL!]! - createdAt: DateTime! - updatedAt: DateTime! - segments: [UploadSegmentGQL!]! - clientUploadStatus: ClientUploadStatusEnum - resolution: VideoResolutionGQL! - streamSegmentType: StreamSegmentTypeEnum! -} - -enum InitPlaylistUploadStatusEnum { - NOT_APPLICABLE - NOT_UPLOADED - UPLOADED -} - -type StreamErrorGQL { - message: String! -} - -type UploadSegmentGQL { - segmentIndex: Int! - uploaded: Boolean! - valid: Boolean! - endFrameIndex: Int - framesPerSecond: Float - durationInSeconds: Float - linksRequested: Int! -} - -enum ClientUploadStatusEnum { - UPLOAD_ENABLED - UPLOAD_DISABLED -} - -type VideoResolutionGQL { - width: Int - height: Int -} - -enum StreamSegmentTypeEnum { - FRAGMENTED_MP4 - RB_CHUNKED_MP4 -} - -type HLSPlaylistGQL { - videoId: Int! - m3u8Text: String! - segmentDurations: [Float!]! -} - -type VideoTag { - tagClasses: [VideoTagClass!]! - name: String! -} - -type VideoTagClass { - name: String! -} - -type HomographyInfoGQL { - id: Int! - frameIndex: Int! - crop: BoundingBoxGQL! - pockets: [BoundingBoxGQL!]! - sourcePoints: PocketPointsGQL! - destPoints: PocketPointsGQL! -} - -type BoundingBoxGQL { - left: Float! - top: Float! - width: Float! - height: Float! -} - -type PocketPointsGQL { - topLeft: IntPoint2D! - topSide: IntPoint2D! - topRight: IntPoint2D! - bottomLeft: IntPoint2D! - bottomSide: IntPoint2D! - bottomRight: IntPoint2D! -} - -type IntPoint2D { - x: Int! - y: Int! -} - -type VideoProcessingGQL { - id: Int! - errors: [VideoProcessingErrorGQL!]! - status: ProcessingStatusEnum! - statuses: [VideoProcessingStatusGQL!]! - framesProcessed: Int - currentSegment: Int - progressPercentage: Float -} - -type VideoProcessingErrorGQL { - message: String! - startSegmentIndex: Int - endSegmentIndex: Int -} - -enum ProcessingStatusEnum { - STARTED - FAILED - SUCCEEDED - SUSPENDED - CREATED - QUEUED - RUNNING - REEXTRACTING_FEATURES -} - -type VideoProcessingStatusGQL { - status: ProcessingStatusEnum! - appVersion: String! - sequenceId: Int! - createdAt: DateTime - updatedAt: DateTime +enum ReactionEnum { + LIKE + HEART + BULLSEYE + HUNDRED } type ReactionGQL { @@ -634,104 +779,13 @@ type ReactionGQL { updatedAt: DateTime } -enum ReactionEnum { - LIKE - HEART - BULLSEYE - HUNDRED -} - -type CommentGQL { - id: Int! - user: UserGQL! - message: String! - replies: [CommentGQL!]! -} - -type ChallengeInvitation { - id: ID! - status: String! - createdAt: DateTime! - challenge: Challenge! - inviter: UserGQL! - invitee: UserGQL! -} - -type DeployedConfigGQL { - allowNewUsers: Boolean! - firebase: Boolean! - devMode: Boolean! - environment: String! - minimumAllowedAppVersion: String! - subscriptionGatingEnabled: Boolean! - bannerMessages: [BannerGQL!]! -} - -type BannerGQL { - id: Int! - message: String! - color: String! - kind: BannerKindEnum! - dismissible: Boolean! - priority: Int! -} - -enum BannerKindEnum { - INFO - WARNING - ERROR -} - -type VideoHistoryGQL { - videos: [VideoGQL!]! - pageInfo: PageInfoGQL! - hasFollowing: Boolean! -} - -type PageInfoGQL { - hasNextPage: Boolean! - endCursor: String -} - -enum IncludePrivateEnum { - ALL - MINE - NONE -} - -input VideoFilterInput { - isStreamCompleted: Boolean = null - requireCursorCompletion: Boolean! = true - createdAt: DateRangeFilter = null - excludeVideosWithNoShots: Boolean = null -} - -input VideoFeedInputGQL @oneOf { - followedByUserId: Int - userId: Int - allUsers: Boolean - home: Boolean -} - -type MakePercentageIntervalGQL { - makePercentage: Float! - elapsedTime: Float! -} - -type RunLeaderboardGQL { - entries: [RunGQL!]! -} - -type CountLeaderboardGQL { - entries: [UserShotCountEntry!]! -} - -type UserShotCountEntry { - user: UserGQL! - value: Int! - total: Int! - proportionMade: Float! - videos: Int! +enum ReportReasonEnum { + SPAM + NUDITY + VIOLENCE + HATE + COPYRIGHT + OTHER } type RequestedMedalsGQL { @@ -767,56 +821,17 @@ type RequestedMedalsGQL { dailyMakes250: MedalGQL } -type MedalGQL { - count: Int! - nickname: String -} - -input MedalScope @oneOf { - videoId: Int - interval: TimeInterval - @deprecated(reason: "NO LONGER SUPPORTED, USE DATETIME_RANGE") - datetimeRange: DatetimeRangeAggregationInput -} - -type NotificationConnection { - notifications: [NotificationGQL!]! - totalCount: Int! - unreadCount: Int! - hasMore: Boolean! -} - -type NotificationGQL { - id: Int! - notificationType: NotificationTypeEnum! - actor: UserGQL! - videoId: Int - challengeId: Int - challenge: Challenge - comment: CommentGQL - reactionType: String - isRead: Boolean! +type RuleSet { + id: ID! + name: String! + description: String createdAt: DateTime! - readAt: DateTime + updatedAt: DateTime! } -enum NotificationTypeEnum { - COMMENT - COMMENT_REPLY - REACTION - FOLLOW - CHALLENGE_INVITE -} - -input NotificationFilters { - isRead: Boolean = null - notificationTypes: [NotificationTypeEnum!] = null -} - -type GetRunsResult { - runs: [RunGQL!]! - count: Int - runIds: [Int!]! +type RunFeaturesGQL { + runId: Int! + indexInRun: Int! } input RunFilterInput { @@ -831,8 +846,18 @@ input RunFilterInput { runLength: FloatRangeFilter } -input GetRunsOrdering { - orderings: [RunsOrderingComponent!]! +type RunGQL { + id: Int! + runLength: Int! + videoId: Int! + userId: Int! + shots: [ShotGQL!]! + video: VideoGQL! + user: UserGQL! +} + +type RunLeaderboardGQL { + entries: [RunGQL!]! } input RunsOrderingComponent @oneOf { @@ -841,57 +866,63 @@ input RunsOrderingComponent @oneOf { videoCreation: DatetimeOrdering } -input IntOrdering { - descending: Boolean! = true - startingAt: Int = null +type SegmentAlreadyUploadedErr { + segmentId: Int! } -input DatetimeOrdering { - descending: Boolean! = true - startingAt: DateTime = null +type SerializedShotPathsGQL { + b64EncodedBuffer: String } -type TableStateGQL { - identifierToPosition: [[Float!]!]! - homography: HomographyInfoGQL +type ShotAnnotationGQL { + shotId: Int! + type: ShotAnnotationTypeGQL! + creator: UserGQL! + notes: String! + errorDefault: Boolean! + createdAt: DateTime + updatedAt: DateTime } -input HomographyInputGQL { - crop: BoundingBoxInputGQL! - pockets: [BoundingBoxInputGQL!]! - sourcePoints: PocketPointsInputGQL! - destPoints: PocketPointsInputGQL! +input ShotAnnotationInput { + name: String! } -input BoundingBoxInputGQL { - left: Float! - top: Float! - width: Float! - height: Float! +type ShotAnnotationTypeGQL { + id: Int! + name: String! } -input PocketPointsInputGQL { - topLeft: IntPoint2DInput! - topSide: IntPoint2DInput! - topRight: IntPoint2DInput! - bottomLeft: IntPoint2DInput! - bottomSide: IntPoint2DInput! - bottomRight: IntPoint2DInput! +type ShotDirectionCountsGQL { + left: Int! + right: Int! + straight: Int! } -input IntPoint2DInput { - x: Int! - y: Int! +enum ShotDirectionEnum { + LEFT + RIGHT + STRAIGHT } -type GetShotsResult { - shots: [ShotGQL!]! - count: Int - ids: [Int!]! -} - -input GetShotsOrdering { - orderings: [ShotsOrderingComponent!]! +type ShotGQL { + id: Int! + videoId: Int! + startFrame: Int! + endFrame: Int! + createdAt: DateTime + updatedAt: DateTime + cueObjectFeatures: CueObjectFeaturesGQL + pocketingIntentionFeatures: PocketingIntentionFeaturesGQL + pocketingIntentionInfo: PocketingIntentionInfoGQL + bankFeatures: BankFeaturesGQL + serializedShotPaths: SerializedShotPathsGQL + user: UserGQL + annotations: [ShotAnnotationGQL!]! + falsePositiveScore: Float + video: VideoGQL + run: RunGQL + runFeatures: RunFeaturesGQL } input ShotsOrderingComponent @oneOf { @@ -903,42 +934,27 @@ input ShotsOrderingComponent @oneOf { runLength: IntOrdering } -input FloatOrdering { - descending: Boolean! = true - startingAt: Float = null +type SpinTypeCountsGQL { + follow: Int! + draw: Int! + center: Int! + unknown: Int! } -input GetShotsPagination { - createdAfter: CreatedAfter! - startFrameAfter: Int! +enum SpinTypeEnum { + DRAW + FOLLOW + CENTER + UNKNOWN } -input CreatedAfter @oneOf { - videoId: Int - createdAt: DateTime +type StreamErrorGQL { + message: String! } -type UserRelationshipsResult { - inquiringUser: UserGQL! - relationships: [UserRelationship!]! -} - -type UserRelationship { - toUser: UserGQL! - toUserFollows: Boolean! - toUserIsFollowedBy: Boolean! -} - -type StripeSubscriptionOptionsGQL { - products: [StripeProductGQL!]! -} - -type StripeProductGQL { - id: String! - name: String! - description: String - active: Boolean! - prices: [StripePriceGQL!]! +enum StreamSegmentTypeEnum { + FRAGMENTED_MP4 + RB_CHUNKED_MP4 } type StripePriceGQL { @@ -951,14 +967,16 @@ type StripePriceGQL { active: Boolean! } -type UserSubscriptionStatusGQL { - hasActiveSubscription: Boolean! - subscriptionStatus: StripeSubscriptionStatusEnum - currentPeriodStart: DateTime - currentPeriodEnd: DateTime - validUntil: DateTime - stripePriceId: String - stripeSubscriptionId: String +type StripeProductGQL { + id: String! + name: String! + description: String + active: Boolean! + prices: [StripePriceGQL!]! +} + +type StripeSubscriptionOptionsGQL { + products: [StripeProductGQL!]! } enum StripeSubscriptionStatusEnum { @@ -972,8 +990,22 @@ enum StripeSubscriptionStatusEnum { PAUSED } -type UserPlayTimeGQL { - totalSeconds: Float! +type SuccessfulAdd { + value: Boolean! +} + +union SuccessfulAddAddShotAnnotationErrors = + SuccessfulAdd + | AddShotAnnotationErrors + +type TableStateGQL { + identifierToPosition: [[Float!]!]! + homography: HomographyInfoGQL +} + +type TagClassGQL { + id: Int! + name: String! } type TagGQL { @@ -983,153 +1015,32 @@ type TagGQL { retired: Boolean! } -type TagClassGQL { - id: Int! - name: String! +type TargetMetricsGQL { + count: Int! + makePercentage: Float + averageDifficulty: Float + spinTypeCounts: SpinTypeCountsGQL + shotDirectionCounts: ShotDirectionCountsGQL } -""" -The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf). -""" -scalar JSON - @specifiedBy( - url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf" - ) - -type Mutation { - createBucketSet(params: CreateBucketSetInput!): BucketSetGQL! - createRuleSet(name: String!, description: String = null): RuleSet! - createChallenge( - name: String! - ruleSetId: ID! - minimumShots: Int! - startDate: DateTime! - endDate: DateTime! - description: String = null - requiredTableSize: Float = null - requiredPocketSize: Float = null - isPublic: Boolean! = false - maxAttempts: Int = null - ): Challenge! - inviteUsersToChallenge( - challengeId: ID! - userIds: [ID!]! - ): [ChallengeInvitation!]! - respondToChallengeInvitation( - invitationId: ID! - accept: Boolean! - ): ChallengeInvitation! - startChallenge(challengeId: ID!): ChallengeEntry! - recalculateChallengeEntry(entryId: ID!): ChallengeEntry! - submitChallengeEntry(entryId: ID!, videoId: ID!): ChallengeEntry! - setLoggerLevel(path: String!, level: String!): Boolean! - reactToVideo(videoId: Int!, reaction: ReactionEnum): Boolean! - commentOnVideo( - videoId: Int! - message: String! - parentCommentId: Int - ): Boolean! - editComment(videoId: Int!, commentId: Int!, newMessage: String!): Boolean! - deleteComment(videoId: Int!, commentId: Int!): Boolean! - blockContent(videoId: Int!): Boolean! - blockUser(userId: Int!): Boolean! - reportContent( - videoId: Int! - reason: ReportReasonEnum! - customReason: String = null - ): Boolean! - markNotificationAsRead(notificationId: Int!): Boolean! - markAllNotificationsAsRead: Boolean! - markNotificationsAsRead(notificationIds: [Int!]!): Boolean! - deleteNotification(notificationId: Int!): Boolean! - addAnnotationToShot( - shotId: Int! - annotationName: String! - notes: String = null - ): AddShotAnnotationReturn! - updateShotAnnotations( - shotId: Int! - annotations: [UpdateAnnotationInputGQL!]! - ): UpdateShotAnnotationReturn! - editShot( - shotId: Int! - fieldsToEdit: EditableShotFieldInputGQL! - ): EditShotReturn! - getProfileImageUploadLink( - fileExt: String = ".png" - ): GetProfileUploadLinkReturn! - editProfileImageUri(profileImageUri: String!): UserGQL! - editUser(input: EditUserInputGQL!): UserGQL! - followUser(followedUserId: Int!): UserGQL! - unfollowUser(followedUserId: Int!): UserGQL! - retireTags(tagIds: [Int!]!): Boolean! - ensureStripeCustomerExists: UserGQL! - deleteUser: Boolean! - createSubscription(priceId: String!): CreateSubscriptionResultGQL! - cancelSubscription: UserSubscriptionStatusGQL! - findPrerecordTableLayout(b64Image: String!, videoId: Int!): HomographyInfoGQL - createUploadStream( - videoMetadata: VideoMetadataInput! - ): CreateUploadStreamReturn! - getUploadLink(videoId: Int!, segmentIndex: Int!): GetUploadLinkReturn! - getHlsInitUploadLink(videoId: Int!): GetUploadLinkReturn! - setSegmentDuration( - videoId: Int! - segmentIndex: Int! - duration: Float! - ): Boolean! - editUploadStream(videoId: Int!, videoMetadata: VideoMetadataInput!): Boolean! - deleteVideo(videoId: Int!): Boolean! - deleteTags(videoId: Int!, tagsToDelete: [VideoTagInput!]!): Boolean! +input TimeDeltaGQL { + days: Int = 0 + weeks: Int = 0 + months: Int = 0 + years: Int = 0 } -input CreateBucketSetInput { - keyName: String! - feature: String! - buckets: [BucketInputGQL!]! +input TimeInterval @oneOf { + timedelta: TimeDeltaGQL + aligned: AlignedIntervalEnum } -enum ReportReasonEnum { - SPAM - NUDITY - VIOLENCE - HATE - COPYRIGHT - OTHER +type TooManyInitUploadsErr { + linksRequested: Int! } -type AddShotAnnotationReturn { - value: SuccessfulAddAddShotAnnotationErrors! -} - -union SuccessfulAddAddShotAnnotationErrors = - SuccessfulAdd - | AddShotAnnotationErrors - -type SuccessfulAdd { - value: Boolean! -} - -type AddShotAnnotationErrors { - error: DoesNotOwnShotErrOtherErrorNeedsNote! -} - -union DoesNotOwnShotErrOtherErrorNeedsNote = - DoesNotOwnShotErr - | OtherErrorNeedsNote - -type DoesNotOwnShotErr { - shotId: Int! - msg: String -} - -type OtherErrorNeedsNote { - msg: String -} - -type UpdateShotAnnotationReturn { - shot: ShotGQL - error: DoesNotOwnShotErr +type TooManyProfileImageUploadsErr { + linksRequested: Int! } input UpdateAnnotationInputGQL { @@ -1137,62 +1048,151 @@ input UpdateAnnotationInputGQL { notes: String = null } -type EditShotReturn { +type UpdateShotAnnotationReturn { shot: ShotGQL error: DoesNotOwnShotErr } -input EditableShotFieldInputGQL { - intendedPocketType: PocketEnum - shotDirection: ShotDirectionEnum - spinType: SpinTypeEnum - targetPocketAngleDirection: ShotDirectionEnum - make: Boolean - backcut: Boolean - excludeFromStats: Boolean - notes: String -} - -type GetProfileUploadLinkReturn { - value: UploadLinkGetProfileUploadLinkErrors! -} - -union UploadLinkGetProfileUploadLinkErrors = - UploadLink - | GetProfileUploadLinkErrors - type UploadLink { uploadUrl: String! headers: [Header]! } -type Header { - key: String! - value: String! -} +union UploadLinkGetProfileUploadLinkErrors = + UploadLink + | GetProfileUploadLinkErrors -type GetProfileUploadLinkErrors { - error: TooManyProfileImageUploadsErr! -} +union UploadLinkGetUploadLinkErrors = UploadLink | GetUploadLinkErrors -type TooManyProfileImageUploadsErr { +type UploadSegmentGQL { + segmentIndex: Int! + uploaded: Boolean! + valid: Boolean! + endFrameIndex: Int + framesPerSecond: Float + durationInSeconds: Float linksRequested: Int! } -input EditUserInputGQL { - username: String = null - fargoRating: Int = null - videosPrivateByDefault: Boolean = null - agreesToMarketing: Boolean = null +type UploadStreamGQL { + id: ID! + linksRequested: Int! + uploadsCompleted: Int! + segmentProcessingCursor: Int! + lastIntendedSegmentBound: Int + isCompleted: Boolean! + initPlaylistUploadStatus: InitPlaylistUploadStatusEnum + lowestUnuploadedSegmentIndex: Int! + uploadCompletionCursor: Int! + errors: [StreamErrorGQL!]! + createdAt: DateTime! + updatedAt: DateTime! + segments: [UploadSegmentGQL!]! + clientUploadStatus: ClientUploadStatusEnum + resolution: VideoResolutionGQL! + streamSegmentType: StreamSegmentTypeEnum! } -type CreateSubscriptionResultGQL { - checkoutUrl: String! - sessionId: String! +type UserGQL { + id: Int! + firebaseUid: String + username: String! + isAdmin: Boolean + fargoRating: Int + activeVideoId: Int + stripeCustomerId: String + profileImageUri: String + createdAt: DateTime + updatedAt: DateTime + videosPrivateByDefault: Boolean + agreesToMarketing: Boolean + following: [UserGQL!] + followers: [UserGQL!] + isFollowedByCurrentUser: Boolean } -type CreateUploadStreamReturn { - videoId: Int! +type UserPlayTimeGQL { + totalSeconds: Float! +} + +type UserRelationship { + toUser: UserGQL! + toUserFollows: Boolean! + toUserIsFollowedBy: Boolean! +} + +type UserRelationshipsResult { + inquiringUser: UserGQL! + relationships: [UserRelationship!]! +} + +type UserShotCountEntry { + user: UserGQL! + value: Int! + total: Int! + proportionMade: Float! + videos: Int! +} + +type UserSubscriptionStatusGQL { + hasActiveSubscription: Boolean! + subscriptionStatus: StripeSubscriptionStatusEnum + currentPeriodStart: DateTime + currentPeriodEnd: DateTime + validUntil: DateTime + stripePriceId: String + stripeSubscriptionId: String +} + +input VideoFeedInputGQL @oneOf { + followedByUserId: Int + userId: Int + allUsers: Boolean + home: Boolean +} + +input VideoFilterInput { + isStreamCompleted: Boolean = null + requireCursorCompletion: Boolean! = true + createdAt: DateRangeFilter = null + excludeVideosWithNoShots: Boolean = null +} + +type VideoGQL { + id: Int! + owner: UserGQL + name: String + screenshotUri: String + totalShotsMade: Int! + totalShots: Int! + makePercentage: Float! + medianRun: Float + averageTimeBetweenShots: Float + averageDifficulty: Float + createdAt: DateTime + updatedAt: DateTime + shots: [ShotGQL!]! + startTime: DateTime + endTime: DateTime + elapsedTime: Float + framesPerSecond: Float! + tableSize: Float! + pocketSize: Float + private: Boolean! + stream: UploadStreamGQL + playlist: HLSPlaylistGQL + tags: [VideoTag!]! + currentHomography: HomographyInfoGQL + homographyHistory: [HomographyInfoGQL!]! + currentProcessing: VideoProcessingGQL + reactions: [ReactionGQL!]! + comments: [CommentGQL!]! +} + +type VideoHistoryGQL { + videos: [VideoGQL!]! + pageInfo: PageInfoGQL! + hasFollowing: Boolean! } input VideoMetadataInput { @@ -1217,52 +1217,59 @@ input VideoMetadataInput { framesPerSecond: Float = null } +type VideoProcessingErrorGQL { + message: String! + startSegmentIndex: Int + endSegmentIndex: Int +} + +type VideoProcessingGQL { + id: Int! + errors: [VideoProcessingErrorGQL!]! + status: ProcessingStatusEnum! + statuses: [VideoProcessingStatusGQL!]! + framesProcessed: Int + currentSegment: Int + progressPercentage: Float +} + +type VideoProcessingStatusGQL { + status: ProcessingStatusEnum! + appVersion: String! + sequenceId: Int! + createdAt: DateTime + updatedAt: DateTime +} + input VideoResolution { width: Int! height: Int! } -type GetUploadLinkReturn { - value: UploadLinkGetUploadLinkErrors! - stream: UploadStreamGQL +type VideoResolutionGQL { + width: Int + height: Int } -union UploadLinkGetUploadLinkErrors = UploadLink | GetUploadLinkErrors - -type GetUploadLinkErrors { - error: MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErr! +type VideoTag { + tagClasses: [VideoTagClass!]! + name: String! } -union MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErr = - MustHaveSetForUploadLinkErr - | SegmentAlreadyUploadedErr - | ProcessingFailedErr - | NoInitForChunkedUploadErr - | TooManyProfileImageUploadsErr - | InitUploadAlreadyCompletedErr - | TooManyInitUploadsErr - -type MustHaveSetForUploadLinkErr { - resolution: Boolean - framesPerSecond: Boolean +type VideoTagClass { + name: String! } -type SegmentAlreadyUploadedErr { - segmentId: Int! +input VideoTagClassInput { + name: String! } -type ProcessingFailedErr { - processing: VideoProcessingGQL! +input VideoTagInput { + tagClasses: [VideoTagClassInput!]! = [] + name: String! } -type NoInitForChunkedUploadErr { - segmentType: StreamSegmentTypeEnum! -} - -type InitUploadAlreadyCompletedErr { - segmentType: StreamSegmentTypeEnum! -} - -type TooManyInitUploadsErr { - linksRequested: Int! +enum WallTypeEnum { + LONG + SHORT }