Compare commits

...

19 Commits

Author SHA1 Message Date
Dean Wenstrand
3ad12182d8 Regenerate schema and hooks from backend master
Union of the cohort funnel, Shot Lab picker queries, and transient
homography IDs — converges the three divergent submodule pointers
(gql master, railbird master, railbird-mobile master).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 18:00:43 -07:00
Dean Wenstrand
4122284bbd Merge Shot Lab queries with cohort funnel 2026-07-12 17:59:33 -07:00
b30dbd01f5 Fetch shot paths and features for the Shot Lab picker
All checks were successful
Tests / Tests (pull_request) Successful in 11s
The recorded-shot picker now renders per-shot table diagrams, so
GetRecordedStreamShots needs serializedShotPaths plus the pocketing and
cue-object features that PlaneShotDiagram consumes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:13:17 -07:00
6d13dc3eb1 Merge remote-tracking branch 'origin/master' into colonelpanic/allow-transient-homography-ids 2026-07-12 00:19:46 -07:00
e3524cab97 Merge pull request 'Add weekly cohort funnel to getUsageStats' (#293) from dean/usage-stats-cohorts into master
Reviewed-on: #293
2026-07-12 04:07:43 +00:00
303261f2d7 Add recorded Shot Lab queries 2026-07-11 20:56:47 -07:00
20a70fad2a Allow transient homography IDs 2026-07-11 20:55:53 -07:00
Dean Wenstrand
35c7c23ba9 Add disjoint activated segment to cohorts for stacking
All checks were successful
Tests / Tests (pull_request) Successful in 10s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 17:45:01 -07:00
Dean Wenstrand
40c4b82a59 Add weekly cohort funnel to getUsageStats
All checks were successful
Tests / Tests (pull_request) Successful in 13s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 17:40:10 -07:00
9ebe2e61df Merge pull request 'dean/usage-stats' (#292) from dean/usage-stats into master
Reviewed-on: #292
2026-07-11 19:26:13 +00:00
Dean Wenstrand
6f356f9535 Add freemium epoch + era conversion to funnel
All checks were successful
Tests / Tests (pull_request) Successful in 12s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:38:20 -07:00
Dean Wenstrand
5c0ff8c31b Add freemium funnel to getUsageStats
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:48:25 -07:00
Dean Wenstrand
582c20170d Add getUsageStats admin query + operation (schema regen on shot-lab tip)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:36:47 -07:00
dc02fd8387 Merge pull request 'Select detected ball colors for Shot Lab' (#291) from colonelpanic/shot-lab-image-colors into master
Reviewed-on: #291
2026-07-10 17:20:37 +00:00
ef4782fc14 Select detected ball colors for Shot Lab
All checks were successful
Tests / Tests (pull_request) Successful in 33s
2026-07-10 00:12:06 -07:00
905a1ca6db Merge pull request 'Add RGBColorGQL and identifierToColor to TableStateGQL' (#290) from shot-lab-ball-colors into master 2026-07-10 03:36:33 +00:00
6f63189703 Add RGBColorGQL and identifierToColor to TableStateGQL
All checks were successful
Tests / Tests (pull_request) Successful in 12s
Regenerated from backend: Shot Lab table-state endpoints now expose a
per-ball dominant color (RGB + hex) index-aligned with ball positions.
2026-07-09 20:35:48 -07:00
958057764c Merge pull request 'Add getLiveTableState query and live-stream operations' (#288) from live-table-state into master 2026-07-10 02:12:31 +00:00
a150e1a300 Add getLiveTableState query and live-stream operations
All checks were successful
Tests / Tests (pull_request) Successful in 10s
- schema: getLiveTableState(videoId) returning LiveTableStateGQL
  (videoId, frameIndex, tableState)
- operations: GetLiveTableState in shot_simulation.gql, lightweight
  GetLiveStreams listing in live_streams.gql
- regenerated TS client outputs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 01:15:33 -07:00
6 changed files with 1217 additions and 226 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
# Lightweight listing of live (in-progress) streams for pickers like the
# Shot Lab "follow a live stream" flow. Selects only what a picker card
# needs; intentionally avoids the heavy VideoCardFields fragment.
query GetLiveStreams($limit: Int! = 25) {
getFeedVideos(
limit: $limit
filters: { isStreamCompleted: false, requireCursorCompletion: false }
includePrivate: MINE
feedInput: { allUsers: true }
) {
videos {
id
name
startTime
createdAt
screenshotUri
elapsedTime
owner {
id
username
profileImageUri
}
stream {
id
isCompleted
lastSegmentUploadedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}

View File

@@ -0,0 +1,62 @@
# Lightweight two-step picker data for loading a recorded shot into Shot Lab.
query GetRecordedStreams($limit: Int! = 25) {
getFeedVideos(
limit: $limit
filters: { isStreamCompleted: true, excludeVideosWithNoShots: true }
includePrivate: MINE
feedInput: { allUsers: true }
) {
videos {
id
name
createdAt
screenshotUri
framesPerSecond
totalShots
owner {
id
username
profileImageUri
}
}
}
}
query GetRecordedStreamShots($videoId: Int!, $limit: Int! = 200) {
getOrderedShots(
filterInput: { videoId: [$videoId] }
shotsOrdering: { orderings: [{ startFrame: { descending: false } }] }
limit: $limit
) {
shots {
id
videoId
startFrame
endFrame
serializedShotPaths {
b64EncodedBuffer
}
cueObjectFeatures {
cueObjectDistance
cueObjectAngle
cueBallSpeed
shotDirection
spinType
}
pocketingIntentionFeatures {
make
targetPocketDistance
targetPocketAngle
targetPocketAngleDirection
marginOfErrorInDegrees
intendedPocketType
difficulty
}
pocketingIntentionInfo {
ballId
pocketId
pathMetadataIndex
}
}
}
}

View File

@@ -9,6 +9,36 @@ query GetTableState(
useHomography: $useHomography useHomography: $useHomography
) { ) {
identifierToPosition identifierToPosition
identifierToColor {
hex
}
}
}
query GetLiveTableState($videoId: Int!) {
getLiveTableState(videoId: $videoId) {
videoId
frameIndex
tableState {
identifierToPosition
identifierToColor {
hex
}
}
}
}
query GetShotTableState($shotId: Int!) {
getShotTableState(shotId: $shotId) {
shotId
videoId
frameIndex
tableState {
identifierToPosition
identifierToColor {
hex
}
}
} }
} }

View File

@@ -0,0 +1,45 @@
query getUsageStats($days: Int! = 30) {
getUsageStats(days: $days) {
funnel {
freemiumEpoch
eraSignups
eraPaying
activationCohort
activationActivated
payingUsers
newPaid7d
newPaid30d
retentionPrevActive
retentionReturned
}
cohorts {
weekStart
signups
activated7d
paid7d
activatedNotPaid7d
}
totalUsers
totalVideos
totalShots
totalRuns
windows {
label
newUsers
activeUsers
sessions
shots
}
daily {
day
newUsers
activeUsers
sessions
shots
}
processingLast24h {
status
count
}
}
}

View File

@@ -1,7 +1,5 @@
type Query { type Query {
getAggregatedShotMetrics( getAggregatedShotMetrics(aggregateInput: AggregateInputGQL!): [AggregateResultGQL!]!
aggregateInput: AggregateInputGQL!
): [AggregateResultGQL!]!
getBucketSet(keyName: String!): BucketSetGQL getBucketSet(keyName: String!): BucketSetGQL
challenges(includeDismissed: Boolean! = false): [Challenge!]! challenges(includeDismissed: Boolean! = false): [Challenge!]!
myDismissedChallenges: [Challenge!]! myDismissedChallenges: [Challenge!]!
@@ -13,44 +11,14 @@ type Query {
myChallengeEntries: [ChallengeEntry!]! myChallengeEntries: [ChallengeEntry!]!
getDeployedConfig: DeployedConfigGQL! getDeployedConfig: DeployedConfigGQL!
waitFor(duration: Float!): Float! waitFor(duration: Float!): Float!
getFeedVideos( getFeedVideos(limit: Int! = 5, after: String = null, includePrivate: IncludePrivateEnum! = MINE, includeCallersVideos: Boolean = true, filters: VideoFilterInput = null, feedInput: VideoFeedInputGQL = null): VideoHistoryGQL!
limit: Int! = 5 getVideoMakePercentageIntervals(videoId: ID!, intervalDuration: Int! = 300): [MakePercentageIntervalGQL!]!
after: String = null getLongestRunsLeaderboard(interval: TimeInterval = null, when: DateTime = null, limit: Int! = 50, requiredTags: [String!] = null): RunLeaderboardGQL!
includePrivate: IncludePrivateEnum! = MINE getDrillRunLeaderboard(drillTag: String!, interval: TimeInterval = null, limit: Int! = 50, tableSizeMin: Float = null, tableSizeMax: Float = null, pocketSizeMin: Float = null, pocketSizeMax: Float = null): DrillRunLeaderboardGQL!
includeCallersVideos: Boolean = true
filters: VideoFilterInput = null
feedInput: VideoFeedInputGQL = null
): VideoHistoryGQL!
getVideoMakePercentageIntervals(
videoId: ID!
intervalDuration: Int! = 300
): [MakePercentageIntervalGQL!]!
getLongestRunsLeaderboard(
interval: TimeInterval = null
when: DateTime = null
limit: Int! = 50
requiredTags: [String!] = null
): RunLeaderboardGQL!
getDrillRunLeaderboard(
drillTag: String!
interval: TimeInterval = null
limit: Int! = 50
tableSizeMin: Float = null
tableSizeMax: Float = null
pocketSizeMin: Float = null
pocketSizeMax: Float = null
): DrillRunLeaderboardGQL!
getMyDrillRuns(drillTag: String!, limit: Int! = 50): [RunGQL!]! getMyDrillRuns(drillTag: String!, limit: Int! = 50): [RunGQL!]!
getMakesLeaderboard( getMakesLeaderboard(interval: TimeInterval = null, when: DateTime = null): CountLeaderboardGQL!
interval: TimeInterval = null
when: DateTime = null
): CountLeaderboardGQL!
getMedals(scope: MedalScope!, userId: Int = null): RequestedMedalsGQL! getMedals(scope: MedalScope!, userId: Int = null): RequestedMedalsGQL!
notifications( notifications(limit: Int! = 20, offset: Int! = 0, filters: NotificationFilters = null): NotificationConnection!
limit: Int! = 20
offset: Int! = 0
filters: NotificationFilters = null
): NotificationConnection!
unreadNotificationCount: Int! unreadNotificationCount: Int!
poolHalls: [PoolHall!]! poolHalls: [PoolHall!]!
claimablePoolHalls: [PoolHall!]! claimablePoolHalls: [PoolHall!]!
@@ -58,66 +26,25 @@ type Query {
claimableCameras(poolHallId: ID!): [PoolHallCamera!]! claimableCameras(poolHallId: ID!): [PoolHallCamera!]!
cameraClaimSession(id: ID!): CameraClaimSession cameraClaimSession(id: ID!): CameraClaimSession
activeCameraLease: CameraLease activeCameraLease: CameraLease
getRuns( getRuns(filterInput: RunFilterInput!, runIds: [Int!] = null, runsOrdering: GetRunsOrdering = null, limit: Int! = 500, countRespectsLimit: Boolean! = false): GetRunsResult!
filterInput: RunFilterInput!
runIds: [Int!] = null
runsOrdering: GetRunsOrdering = null
limit: Int! = 500
countRespectsLimit: Boolean! = false
): GetRunsResult!
videoPlayerClusters(videoId: Int!): [PlayerClusterGQL!]! videoPlayerClusters(videoId: Int!): [PlayerClusterGQL!]!
getShotAnnotationTypes(errorTypes: Boolean = false): [ShotAnnotationTypeGQL!]! getShotAnnotationTypes(errorTypes: Boolean = false): [ShotAnnotationTypeGQL!]!
getTableState( getTableState(b64Image: String!, tableSize: Float = 100, useHomography: HomographyInputGQL = null): TableStateGQL!
b64Image: String! getLiveTableState(videoId: Int!): LiveTableStateGQL!
tableSize: Float = 100 getShotTableState(shotId: Int!): ShotTableStateGQL!
useHomography: HomographyInputGQL = null
): TableStateGQL!
simulateShot(simulationInput: SimulateShotInputGQL!): ShotProjectionGQL! simulateShot(simulationInput: SimulateShotInputGQL!): ShotProjectionGQL!
computePotAim( computePotAim(simulationInput: SimulateShotInputGQL!, targetBallId: Int!, pocket: PocketIdentifier!): PotAimGQL!
simulationInput: SimulateShotInputGQL! getOrderedShots(filterInput: FilterInput!, ids: [Int!] = null, shotsOrdering: GetShotsOrdering = null, limit: Int! = 500, countRespectsLimit: Boolean! = false): GetShotsResult!
targetBallId: Int! getShotsWithMetadata(filterInput: FilterInput!, ids: [Int!] = null, shotsPagination: GetShotsPagination = null, limit: Int! = 500, countRespectsLimit: Boolean! = false): GetShotsResult!
pocket: PocketIdentifier! getShots(filterInput: FilterInput!, shotsPagination: GetShotsPagination = null, limit: Int! = 500, countRespectsLimit: Boolean! = false): [ShotGQL!]!
): PotAimGQL!
getOrderedShots(
filterInput: FilterInput!
ids: [Int!] = null
shotsOrdering: GetShotsOrdering = null
limit: Int! = 500
countRespectsLimit: Boolean! = false
): GetShotsResult!
getShotsWithMetadata(
filterInput: FilterInput!
ids: [Int!] = null
shotsPagination: GetShotsPagination = null
limit: Int! = 500
countRespectsLimit: Boolean! = false
): GetShotsResult!
getShots(
filterInput: FilterInput!
shotsPagination: GetShotsPagination = null
limit: Int! = 500
countRespectsLimit: Boolean! = false
): [ShotGQL!]!
getShotsByIds(ids: [Int!]!): [ShotGQL!]! getShotsByIds(ids: [Int!]!): [ShotGQL!]!
getUsageStats(days: Int! = 30): UsageStatsGQL!
getUser(userId: Int!): UserGQL getUser(userId: Int!): UserGQL
doesUsernameExist(candidateUsername: String!): Boolean! doesUsernameExist(candidateUsername: String!): Boolean!
getLoggedInUser: UserGQL getLoggedInUser: UserGQL
getUsernames( getUsernames(matchString: String = null, limit: Int = null, after: String = null): [String!]!
matchString: String = null getUsersMatching(matchString: String = null, limit: Int = null, after: String = null): [UserGQL!]!
limit: Int = null getUserRelationshipsMatching(userId: Int!, matchString: String = null, limit: Int = 100, after: String = null): UserRelationshipsResult!
after: String = null
): [String!]!
getUsersMatching(
matchString: String = null
limit: Int = null
after: String = null
): [UserGQL!]!
getUserRelationshipsMatching(
userId: Int!
matchString: String = null
limit: Int = 100
after: String = null
): UserRelationshipsResult!
getAvailableSubscriptionOptions: StripeSubscriptionOptionsGQL! getAvailableSubscriptionOptions: StripeSubscriptionOptionsGQL!
getUserSubscriptionStatus: UserSubscriptionStatusGQL! getUserSubscriptionStatus: UserSubscriptionStatusGQL!
getResolvedTier: ResolvedTierGQL! getResolvedTier: ResolvedTierGQL!
@@ -125,12 +52,7 @@ type Query {
getQuotaStatus: QuotaStatusGQL! getQuotaStatus: QuotaStatusGQL!
getStorageStatus: StorageStatusGQL getStorageStatus: StorageStatusGQL
getPlayTime(userId: Int!, filters: VideoFilterInput = null): UserPlayTimeGQL! getPlayTime(userId: Int!, filters: VideoFilterInput = null): UserPlayTimeGQL!
getUserVideos( getUserVideos(userId: Int = null, limit: Int! = 5, after: String = null, filters: VideoFilterInput = null): VideoHistoryGQL!
userId: Int = null
limit: Int! = 5
after: String = null
filters: VideoFilterInput = null
): VideoHistoryGQL!
getUserTags(includeRetiredTags: Boolean = false): [TagGQL!]! getUserTags(includeRetiredTags: Boolean = false): [TagGQL!]!
getGameTypeTagMetrics(input: GameTypeTagMetricsInput!): [GameTypeTagMetric!]! getGameTypeTagMetrics(input: GameTypeTagMetricsInput!): [GameTypeTagMetric!]!
videoExportJob(jobId: Int!): VideoExportJobGQL videoExportJob(jobId: Int!): VideoExportJobGQL
@@ -202,9 +124,7 @@ input DatetimeRangeAggregationInput {
feature: String! = "created_at" feature: String! = "created_at"
} }
""" """Date with time (isoformat)"""
Date with time (isoformat)
"""
scalar DateTime scalar DateTime
input TimeInterval @oneOf { input TimeInterval @oneOf {
@@ -320,9 +240,7 @@ input DateRangeFilter {
greaterThanInclusive: Boolean! = true greaterThanInclusive: Boolean! = true
} }
""" """Date (isoformat)"""
Date (isoformat)
"""
scalar Date scalar Date
type BucketSetGQL { type BucketSetGQL {
@@ -607,7 +525,7 @@ type VideoTagClass {
} }
type HomographyInfoGQL { type HomographyInfoGQL {
id: Int! id: Int
frameIndex: Int! frameIndex: Int!
crop: BoundingBoxGQL! crop: BoundingBoxGQL!
pockets: [BoundingBoxGQL!]! pockets: [BoundingBoxGQL!]!
@@ -848,8 +766,7 @@ type MedalGQL {
input MedalScope @oneOf { input MedalScope @oneOf {
videoId: Int videoId: Int
interval: TimeInterval interval: TimeInterval @deprecated(reason: "NO LONGER SUPPORTED, USE DATETIME_RANGE")
@deprecated(reason: "NO LONGER SUPPORTED, USE DATETIME_RANGE")
datetimeRange: DatetimeRangeAggregationInput datetimeRange: DatetimeRangeAggregationInput
} }
@@ -1019,6 +936,14 @@ type PlayerClusterShotGQL {
type TableStateGQL { type TableStateGQL {
identifierToPosition: [[Float!]!]! identifierToPosition: [[Float!]!]!
homography: HomographyInfoGQL homography: HomographyInfoGQL
identifierToColor: [RGBColorGQL]!
}
type RGBColorGQL {
r: Int!
g: Int!
b: Int!
hex: String!
} }
input HomographyInputGQL { input HomographyInputGQL {
@@ -1049,6 +974,19 @@ input IntPoint2DInput {
y: Int! y: Int!
} }
type LiveTableStateGQL {
videoId: Int!
frameIndex: Int!
tableState: TableStateGQL!
}
type ShotTableStateGQL {
shotId: Int!
videoId: Int!
frameIndex: Int!
tableState: TableStateGQL!
}
type ShotProjectionGQL { type ShotProjectionGQL {
trajectories: [BallTrajectoryGQL!]! trajectories: [BallTrajectoryGQL!]!
events: [SimulationEventGQL!]! events: [SimulationEventGQL!]!
@@ -1153,6 +1091,60 @@ input CreatedAfter @oneOf {
createdAt: DateTime createdAt: DateTime
} }
type UsageStatsGQL {
funnel: FunnelStatsGQL!
totalUsers: Int!
totalVideos: Int!
totalShots: Int!
totalRuns: Int!
cohorts: [CohortStatsGQL!]!
windows: [UsageStatsWindowGQL!]!
daily: [UsageStatsDailyGQL!]!
processingLast24h: [ProcessingStatusCountGQL!]!
}
type FunnelStatsGQL {
freemiumEpoch: Date!
eraSignups: Int!
eraPaying: Int!
activationCohort: Int!
activationActivated: Int!
payingUsers: Int!
newPaid7d: Int!
newPaid30d: Int!
retentionPrevActive: Int!
retentionReturned: Int!
}
type CohortStatsGQL {
weekStart: Date!
signups: Int!
activated7d: Int!
paid7d: Int!
activatedNotPaid7d: Int!
}
type UsageStatsWindowGQL {
label: String!
newUsers: Int!
activeUsers: Int!
sessions: Int!
shots: Int!
}
type UsageStatsDailyGQL {
day: Date!
newUsers: Int!
activeUsers: Int!
sessions: Int!
shots: Int!
}
type ProcessingStatusCountGQL {
status: String!
count: Int!
}
type UserRelationshipsResult { type UserRelationshipsResult {
inquiringUser: UserGQL! inquiringUser: UserGQL!
relationships: [UserRelationship!]! relationships: [UserRelationship!]!
@@ -1293,9 +1285,7 @@ type StorageStatusGQL {
lastCalculatedAt: DateTime lastCalculatedAt: DateTime
} }
""" """Integer value that can exceed GraphQL Int's 32-bit range."""
Integer value that can exceed GraphQL Int's 32-bit range.
"""
scalar BigInt scalar BigInt
type UserPlayTimeGQL { type UserPlayTimeGQL {
@@ -1366,34 +1356,14 @@ enum VideoExportStatusEnum {
""" """
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). 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 scalar JSON @specifiedBy(url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf")
@specifiedBy(
url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf"
)
type Mutation { type Mutation {
createBucketSet(params: CreateBucketSetInput!): BucketSetGQL! createBucketSet(params: CreateBucketSetInput!): BucketSetGQL!
createRuleSet(name: String!, description: String = null): RuleSet! createRuleSet(name: String!, description: String = null): RuleSet!
createChallenge( 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!
name: String! inviteUsersToChallenge(challengeId: ID!, userIds: [ID!]!): [ChallengeInvitation!]!
ruleSetId: ID! respondToChallengeInvitation(invitationId: ID!, accept: Boolean!): ChallengeInvitation!
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! startChallenge(challengeId: ID!): ChallengeEntry!
recalculateChallengeEntry(entryId: ID!): ChallengeEntry! recalculateChallengeEntry(entryId: ID!): ChallengeEntry!
submitChallengeEntry(entryId: ID!, videoId: ID!): ChallengeEntry! submitChallengeEntry(entryId: ID!, videoId: ID!): ChallengeEntry!
@@ -1401,61 +1371,30 @@ type Mutation {
undismissChallenge(challengeId: ID!): Boolean! undismissChallenge(challengeId: ID!): Boolean!
setLoggerLevel(path: String!, level: String!): Boolean! setLoggerLevel(path: String!, level: String!): Boolean!
reactToVideo(videoId: Int!, reaction: ReactionEnum): Boolean! reactToVideo(videoId: Int!, reaction: ReactionEnum): Boolean!
commentOnVideo( commentOnVideo(videoId: Int!, message: String!, parentCommentId: Int): Boolean!
videoId: Int!
message: String!
parentCommentId: Int
): Boolean!
editComment(videoId: Int!, commentId: Int!, newMessage: String!): Boolean! editComment(videoId: Int!, commentId: Int!, newMessage: String!): Boolean!
deleteComment(videoId: Int!, commentId: Int!): Boolean! deleteComment(videoId: Int!, commentId: Int!): Boolean!
blockContent(videoId: Int!): Boolean! blockContent(videoId: Int!): Boolean!
blockUser(userId: Int!): Boolean! blockUser(userId: Int!): Boolean!
reportContent( reportContent(videoId: Int!, reason: ReportReasonEnum!, customReason: String = null): Boolean!
videoId: Int!
reason: ReportReasonEnum!
customReason: String = null
): Boolean!
markNotificationAsRead(notificationId: Int!): Boolean! markNotificationAsRead(notificationId: Int!): Boolean!
markAllNotificationsAsRead: Boolean! markAllNotificationsAsRead: Boolean!
markNotificationsAsRead(notificationIds: [Int!]!): Boolean! markNotificationsAsRead(notificationIds: [Int!]!): Boolean!
deleteNotification(notificationId: Int!): Boolean! deleteNotification(notificationId: Int!): Boolean!
createPoolHall(input: CreatePoolHallInput!): PoolHall! createPoolHall(input: CreatePoolHallInput!): PoolHall!
updatePoolHall(input: UpdatePoolHallInput!): PoolHall! updatePoolHall(input: UpdatePoolHallInput!): PoolHall!
createPoolHallCamera( createPoolHallCamera(input: CreatePoolHallCameraInput!): PoolHallCameraStreamCredentials!
input: CreatePoolHallCameraInput!
): PoolHallCameraStreamCredentials!
updatePoolHallCamera(input: UpdatePoolHallCameraInput!): PoolHallCamera! updatePoolHallCamera(input: UpdatePoolHallCameraInput!): PoolHallCamera!
rotatePoolHallCameraStreamKey(cameraId: ID!): PoolHallCameraStreamCredentials! rotatePoolHallCameraStreamKey(cameraId: ID!): PoolHallCameraStreamCredentials!
createCameraClaimSession( createCameraClaimSession(cameraId: ID!, durationMinutes: Int = null, videoName: String = null, videoPrivate: Boolean = null, agreedTermsVersion: Int = null, tags: [VideoTagInput!] = null): CameraClaimSession!
cameraId: ID!
durationMinutes: Int = null
videoName: String = null
videoPrivate: Boolean = null
agreedTermsVersion: Int = null
tags: [VideoTagInput!] = null
): CameraClaimSession!
cancelCameraClaimSession(claimSessionId: ID!): CameraClaimSession! cancelCameraClaimSession(claimSessionId: ID!): CameraClaimSession!
endCameraLease(leaseId: ID!): CameraLease! endCameraLease(leaseId: ID!): CameraLease!
extendCameraLease(leaseId: ID!, additionalMinutes: Int! = 60): CameraLease! extendCameraLease(leaseId: ID!, additionalMinutes: Int! = 60): CameraLease!
finalizePlayerAssignments( finalizePlayerAssignments(input: FinalizePlayerAssignmentsInput!): [PlayerClusterGQL!]!
input: FinalizePlayerAssignmentsInput! addAnnotationToShot(shotId: Int!, annotationName: String!, notes: String = null): AddShotAnnotationReturn!
): [PlayerClusterGQL!]! updateShotAnnotations(shotId: Int!, annotations: [UpdateAnnotationInputGQL!]!): UpdateShotAnnotationReturn!
addAnnotationToShot( editShot(shotId: Int!, fieldsToEdit: EditableShotFieldInputGQL!): EditShotReturn!
shotId: Int! getProfileImageUploadLink(fileExt: String = ".png"): GetProfileUploadLinkReturn!
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! editProfileImageUri(profileImageUri: String!): UserGQL!
editUser(input: EditUserInputGQL!): UserGQL! editUser(input: EditUserInputGQL!): UserGQL!
followUser(followedUserId: Int!): UserGQL! followUser(followedUserId: Int!): UserGQL!
@@ -1463,39 +1402,20 @@ type Mutation {
retireTags(tagIds: [Int!]!): Boolean! retireTags(tagIds: [Int!]!): Boolean!
ensureStripeCustomerExists: UserGQL! ensureStripeCustomerExists: UserGQL!
deleteUser: Boolean! deleteUser: Boolean!
syncAppleSubscription( syncAppleSubscription(input: SyncAppleSubscriptionInputGQL!): SyncAppleSubscriptionResultGQL!
input: SyncAppleSubscriptionInputGQL!
): SyncAppleSubscriptionResultGQL!
createSubscription(priceId: String!): CreateSubscriptionResultGQL! createSubscription(priceId: String!): CreateSubscriptionResultGQL!
createCustomerPortalSession: CreateCustomerPortalSessionResultGQL! createCustomerPortalSession: CreateCustomerPortalSessionResultGQL!
cancelSubscription: UserSubscriptionStatusGQL! cancelSubscription: UserSubscriptionStatusGQL!
grantManualEntitlement( grantManualEntitlement(userId: Int!, tierName: String! = "pro", startsAt: DateTime = null, endsAt: DateTime = null, reason: String = null): UserSubscriptionStatusGQL!
userId: Int!
tierName: String! = "pro"
startsAt: DateTime = null
endsAt: DateTime = null
reason: String = null
): UserSubscriptionStatusGQL!
revokeManualEntitlement(userId: Int!): UserSubscriptionStatusGQL! revokeManualEntitlement(userId: Int!): UserSubscriptionStatusGQL!
submitCancellationFeedback( submitCancellationFeedback(reasons: [CancellationReasonEnum!] = null, feedback: String = null, metadata: CancellationFeedbackMetadataInput = null): Boolean!
reasons: [CancellationReasonEnum!] = null
feedback: String = null
metadata: CancellationFeedbackMetadataInput = null
): Boolean!
requestVideoExport(input: RequestVideoExportInput!): VideoExportJobGQL! requestVideoExport(input: RequestVideoExportInput!): VideoExportJobGQL!
dismissVideoExport(jobId: Int!): Boolean! dismissVideoExport(jobId: Int!): Boolean!
findPrerecordTableLayout(b64Image: String!, videoId: Int!): HomographyInfoGQL findPrerecordTableLayout(b64Image: String!, videoId: Int!): HomographyInfoGQL
createUploadStream( createUploadStream(videoMetadata: VideoMetadataInput!, expectedDurationSeconds: Float = null): CreateUploadStreamReturn!
videoMetadata: VideoMetadataInput!
expectedDurationSeconds: Float = null
): CreateUploadStreamReturn!
getUploadLink(videoId: Int!, segmentIndex: Int!): GetUploadLinkReturn! getUploadLink(videoId: Int!, segmentIndex: Int!): GetUploadLinkReturn!
getHlsInitUploadLink(videoId: Int!): GetUploadLinkReturn! getHlsInitUploadLink(videoId: Int!): GetUploadLinkReturn!
setSegmentDuration( setSegmentDuration(videoId: Int!, segmentIndex: Int!, duration: Float!): Boolean!
videoId: Int!
segmentIndex: Int!
duration: Float!
): Boolean!
editUploadStream(videoId: Int!, videoMetadata: VideoMetadataInput!): Boolean! editUploadStream(videoId: Int!, videoMetadata: VideoMetadataInput!): Boolean!
deleteVideo(videoId: Int!): Boolean! deleteVideo(videoId: Int!): Boolean!
deleteTags(videoId: Int!, tagsToDelete: [VideoTagInput!]!): Boolean! deleteTags(videoId: Int!, tagsToDelete: [VideoTagInput!]!): Boolean!
@@ -1587,9 +1507,7 @@ type AddShotAnnotationReturn {
value: SuccessfulAddAddShotAnnotationErrors! value: SuccessfulAddAddShotAnnotationErrors!
} }
union SuccessfulAddAddShotAnnotationErrors = union SuccessfulAddAddShotAnnotationErrors = SuccessfulAdd | AddShotAnnotationErrors
SuccessfulAdd
| AddShotAnnotationErrors
type SuccessfulAdd { type SuccessfulAdd {
value: Boolean! value: Boolean!
@@ -1599,9 +1517,7 @@ type AddShotAnnotationErrors {
error: DoesNotOwnShotErrOtherErrorNeedsNote! error: DoesNotOwnShotErrOtherErrorNeedsNote!
} }
union DoesNotOwnShotErrOtherErrorNeedsNote = union DoesNotOwnShotErrOtherErrorNeedsNote = DoesNotOwnShotErr | OtherErrorNeedsNote
DoesNotOwnShotErr
| OtherErrorNeedsNote
type DoesNotOwnShotErr { type DoesNotOwnShotErr {
shotId: Int! shotId: Int!
@@ -1642,9 +1558,7 @@ type GetProfileUploadLinkReturn {
value: UploadLinkGetProfileUploadLinkErrors! value: UploadLinkGetProfileUploadLinkErrors!
} }
union UploadLinkGetProfileUploadLinkErrors = union UploadLinkGetProfileUploadLinkErrors = UploadLink | GetProfileUploadLinkErrors
UploadLink
| GetProfileUploadLinkErrors
type UploadLink { type UploadLink {
uploadUrl: String! uploadUrl: String!
@@ -1731,12 +1645,9 @@ input VideoMetadataInput {
videoName: String = null videoName: String = null
startTime: DateTime = null startTime: DateTime = null
endTime: DateTime = null endTime: DateTime = null
gameType: String = null gameType: String = null @deprecated(reason: "`game_type` is deprecated. Use `tags` instead.")
@deprecated(reason: "`game_type` is deprecated. Use `tags` instead.")
""" """A list of tags associated with the video. Replace `game_type`"""
A list of tags associated with the video. Replace `game_type`
"""
tags: [VideoTagInput!] = null tags: [VideoTagInput!] = null
tableSize: Float = null tableSize: Float = null
pocketSize: Float = null pocketSize: Float = null
@@ -1765,15 +1676,7 @@ type GetUploadLinkErrors {
error: MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErrStorageLimitExceededErr! error: MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErrStorageLimitExceededErr!
} }
union MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErrStorageLimitExceededErr = union MustHaveSetForUploadLinkErrSegmentAlreadyUploadedErrProcessingFailedErrNoInitForChunkedUploadErrTooManyProfileImageUploadsErrInitUploadAlreadyCompletedErrTooManyInitUploadsErrStorageLimitExceededErr = MustHaveSetForUploadLinkErr | SegmentAlreadyUploadedErr | ProcessingFailedErr | NoInitForChunkedUploadErr | TooManyProfileImageUploadsErr | InitUploadAlreadyCompletedErr | TooManyInitUploadsErr | StorageLimitExceededErr
MustHaveSetForUploadLinkErr
| SegmentAlreadyUploadedErr
| ProcessingFailedErr
| NoInitForChunkedUploadErr
| TooManyProfileImageUploadsErr
| InitUploadAlreadyCompletedErr
| TooManyInitUploadsErr
| StorageLimitExceededErr
type MustHaveSetForUploadLinkErr { type MustHaveSetForUploadLinkErr {
resolution: Boolean resolution: Boolean