Compare commits

..

1 Commits

Author SHA1 Message Date
dean
b7b482694e WIP: Add challenges feature schema and operations
- Add Challenge, ChallengeEntry, ChallengeInvitation, RuleSet types
- Add queries: challenges, challenge, challengeLeaderboard, myChallengeInvitations, myChallengeEntries, ruleSets
- Add mutations: createChallenge, createRuleSet, inviteUsersToChallenge, respondToChallengeInvitation, startChallenge, submitChallengeEntry, recalculateChallengeEntry

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 16:12:03 -08:00
10 changed files with 7687 additions and 507 deletions

5423
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,6 @@ query GetAggregatedShotMetrics($aggregateInput: AggregateInputGQL!) {
targetMetrics { targetMetrics {
count count
makePercentage makePercentage
averageDifficulty
} }
} }
} }

View File

@@ -0,0 +1,207 @@
query GetChallenges {
challenges {
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
name
description
minimumShots
startDate
endDate
createdAt
updatedAt
requiredTableSize
requiredPocketSize
isPublic
maxAttempts
ruleSet {
id
name
description
}
createdBy {
id
username
profileImageUri
}
}
}
query GetRuleSets {
ruleSets {
id
name
description
}
}
query GetChallengeLeaderboard($challengeId: ID!, $limit: Int) {
challengeLeaderboard(challengeId: $challengeId, limit: $limit) {
id
status
shotsCount
makesCount
makeRate
qualified
createdAt
user {
id
username
profileImageUri
}
video {
id
createdAt
}
}
}
query GetMyChallengeInvitations {
myChallengeInvitations {
id
status
createdAt
challenge {
id
name
description
startDate
endDate
createdBy {
id
username
profileImageUri
}
}
inviter {
id
username
profileImageUri
}
}
}
mutation CreateRuleSet($name: String!, $description: String) {
createRuleSet(name: $name, description: $description) {
id
name
description
}
}
mutation CreateChallenge(
$name: String!
$ruleSetId: ID!
$minimumShots: Int!
$startDate: DateTime!
$endDate: DateTime!
$description: String
$requiredTableSize: Float
$requiredPocketSize: Float
$isPublic: Boolean! = false
$maxAttempts: Int
) {
createChallenge(
name: $name
ruleSetId: $ruleSetId
minimumShots: $minimumShots
startDate: $startDate
endDate: $endDate
description: $description
requiredTableSize: $requiredTableSize
requiredPocketSize: $requiredPocketSize
isPublic: $isPublic
maxAttempts: $maxAttempts
) {
id
name
description
requiredTableSize
requiredPocketSize
isPublic
maxAttempts
}
}
mutation InviteUsersToChallenge($challengeId: ID!, $userIds: [ID!]!) {
inviteUsersToChallenge(challengeId: $challengeId, userIds: $userIds) {
id
status
inviter {
id
username
}
}
}
mutation RespondToChallengeInvitation($invitationId: ID!, $accept: Boolean!) {
respondToChallengeInvitation(invitationId: $invitationId, accept: $accept) {
id
status
challenge {
id
}
}
}
mutation StartChallenge($challengeId: ID!) {
startChallenge(challengeId: $challengeId) {
id
status
createdAt
challenge {
id
name
}
}
}
mutation SubmitChallengeEntry($entryId: ID!, $videoId: ID!) {
submitChallengeEntry(entryId: $entryId, videoId: $videoId) {
id
status
qualified
makeRate
shotsCount
makesCount
video {
id
}
}
}
mutation RecalculateChallengeEntry($entryId: ID!) {
recalculateChallengeEntry(entryId: $entryId) {
id
status
qualified
makeRate
shotsCount
makesCount
}
}

View File

@@ -19,7 +19,11 @@ fragment UserSocialsFields on UserGQL {
id id
username username
profileImageUri profileImageUri
isFollowedByCurrentUser followers {
id
username
profileImageUri
}
} }
fragment VideoCardFields on VideoGQL { fragment VideoCardFields on VideoGQL {
@@ -112,6 +116,5 @@ query GetVideoFeed(
hasNextPage hasNextPage
endCursor endCursor
} }
hasFollowing
} }
} }

View File

@@ -192,7 +192,6 @@ fragment ShotWithAllFeatures on ShotGQL {
streamSegmentType streamSegmentType
} }
playlist { playlist {
videoId
segmentDurations segmentDurations
} }
} }

View File

@@ -88,16 +88,32 @@ query GetUserTags {
mutation followUser($followedUserId: Int!) { mutation followUser($followedUserId: Int!) {
followUser(followedUserId: $followedUserId) { followUser(followedUserId: $followedUserId) {
username
id
following {
id id
username username
} }
followers {
id
username
}
}
} }
mutation unfollowUser($followedUserId: Int!) { mutation unfollowUser($followedUserId: Int!) {
unfollowUser(followedUserId: $followedUserId) { unfollowUser(followedUserId: $followedUserId) {
username
id
following {
id id
username username
} }
followers {
id
username
}
}
} }
query getUserFollowingFollowers { query getUserFollowingFollowers {
@@ -107,13 +123,11 @@ query getUserFollowingFollowers {
id id
username username
profileImageUri profileImageUri
isFollowedByCurrentUser
} }
followers { followers {
id id
username username
profileImageUri profileImageUri
isFollowedByCurrentUser
} }
} }
} }
@@ -163,3 +177,13 @@ fragment UserFragment on UserGQL {
videosPrivateByDefault videosPrivateByDefault
agreesToMarketing agreesToMarketing
} }
query GetUsersMatching(
$matchString: String = null
$limit: Int = null
$after: String = null
) {
getUsersMatching(matchString: $matchString, limit: $limit, after: $after) {
...UserFragment
}
}

View File

@@ -88,7 +88,11 @@ fragment UserSocialsFields on UserGQL {
id id
username username
profileImageUri profileImageUri
isFollowedByCurrentUser followers {
id
username
profileImageUri
}
} }
query GetVideoSocialDetailsById($videoId: Int!) { query GetVideoSocialDetailsById($videoId: Int!) {

View File

@@ -3,6 +3,12 @@ type Query {
aggregateInput: AggregateInputGQL! aggregateInput: AggregateInputGQL!
): [AggregateResultGQL!]! ): [AggregateResultGQL!]!
getBucketSet(keyName: String!): BucketSetGQL getBucketSet(keyName: String!): BucketSetGQL
challenges: [Challenge!]!
challenge(id: ID!): Challenge
challengeLeaderboard(challengeId: ID!, limit: Int! = 50): [ChallengeEntry!]!
myChallengeInvitations: [ChallengeInvitation!]!
ruleSets: [RuleSet!]!
myChallengeEntries: [ChallengeEntry!]!
getDeployedConfig: DeployedConfigGQL! getDeployedConfig: DeployedConfigGQL!
waitFor(duration: Float!): Float! waitFor(duration: Float!): Float!
getFeedVideos( getFeedVideos(
@@ -298,35 +304,60 @@ type BucketGQL {
lowerBound: Float! lowerBound: Float!
} }
type DeployedConfigGQL { type Challenge {
allowNewUsers: Boolean! id: ID!
firebase: Boolean! name: String!
devMode: Boolean! description: String
environment: String! minimumShots: Int!
minimumAllowedAppVersion: String! requiredTableSize: Float
subscriptionGatingEnabled: Boolean! requiredPocketSize: Float
bannerMessages: [BannerGQL!]! isPublic: Boolean!
maxAttempts: Int
startDate: DateTime!
endDate: DateTime!
createdAt: DateTime!
updatedAt: DateTime!
ruleSet: RuleSet!
createdBy: UserGQL!
} }
type BannerGQL { type RuleSet {
id: ID!
name: String!
description: String
createdAt: DateTime!
updatedAt: DateTime!
}
type UserGQL {
id: Int! id: Int!
message: String! firebaseUid: String
color: String! username: String!
kind: BannerKindEnum! isAdmin: Boolean
dismissible: Boolean! fargoRating: Int
priority: Int! activeVideoId: Int
stripeCustomerId: String
profileImageUri: String
createdAt: DateTime
updatedAt: DateTime
videosPrivateByDefault: Boolean
agreesToMarketing: Boolean
following: [UserGQL!]
followers: [UserGQL!]
isFollowedByCurrentUser: Boolean
} }
enum BannerKindEnum { type ChallengeEntry {
INFO id: ID!
WARNING status: String!
ERROR shotsCount: Int
} makesCount: Int
makeRate: Float
type VideoHistoryGQL { qualified: Boolean
videos: [VideoGQL!]! createdAt: DateTime!
pageInfo: PageInfoGQL! challenge: Challenge!
hasFollowing: Boolean! video: VideoGQL
user: UserGQL!
} }
type VideoGQL { type VideoGQL {
@@ -360,24 +391,6 @@ type VideoGQL {
comments: [CommentGQL!]! comments: [CommentGQL!]!
} }
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 ShotGQL { type ShotGQL {
id: Int! id: Int!
videoId: Int! videoId: Int!
@@ -633,6 +646,45 @@ type CommentGQL {
replies: [CommentGQL!]! replies: [CommentGQL!]!
} }
type ChallengeInvitation {
id: ID!
status: String!
createdAt: DateTime!
challenge: Challenge!
inviter: 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 { type PageInfoGQL {
hasNextPage: Boolean! hasNextPage: Boolean!
endCursor: String endCursor: String
@@ -940,6 +992,30 @@ scalar JSON
type Mutation { type Mutation {
createBucketSet(params: CreateBucketSetInput!): BucketSetGQL! 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! setLoggerLevel(path: String!, level: String!): Boolean!
reactToVideo(videoId: Int!, reaction: ReactionEnum): Boolean! reactToVideo(videoId: Int!, reaction: ReactionEnum): Boolean!
commentOnVideo( commentOnVideo(

848
yarn.lock

File diff suppressed because it is too large Load Diff