Compare commits

..

10 Commits

Author SHA1 Message Date
beeba9207c add difficulty field to aggregate shot metrics operation
All checks were successful
Tests / Tests (pull_request) Successful in 22s
2025-11-13 11:10:58 -08:00
f14e117416 Merge pull request 'Add videoId field to playlist in ShotWithAllFeatures fragment' (#210) from loewy/add-video-id-to-playlist-shots-fragment into master
Reviewed-on: #210
2025-11-12 22:16:58 +00:00
3d86c13291 add videoId field to playlist in shot w features fragment
All checks were successful
Tests / Tests (pull_request) Successful in 10s
2025-11-12 14:08:49 -08:00
e63f8600aa Merge pull request 'Add pocket size field to GraphQL schema and operations' (#209) from dean/add-pocket-size-clean into master
Reviewed-on: #209
2025-11-11 21:09:28 +00:00
7e822446da Merge branch 'master' into dean/add-pocket-size-clean
All checks were successful
Tests / Tests (pull_request) Successful in 9s
2025-11-11 20:34:55 +00:00
75ecc5894b Merge pull request 'dean/add-home-feed-option' (#208) from dean/add-home-feed-option into master
Reviewed-on: #208
2025-11-10 22:09:09 +00:00
73c8dd5f57 Merge branch 'master' into dean/add-home-feed-option
All checks were successful
Tests / Tests (pull_request) Successful in 10s
2025-11-10 19:55:16 +00:00
dean
e4223a1a17 Add isFollowedByCurrentUser field and hasFollowing to feed query
All checks were successful
Tests / Tests (pull_request) Successful in 9s
Updated GraphQL operations to support efficient follow status checking:
- Added isFollowedByCurrentUser field to UserSocialsFields fragment
- Removed nested followers array from UserSocialsFields (over-fetching)
- Simplified followUser/unfollowUser mutations to return minimal data
- Added hasFollowing field to GetVideoFeed query for feed mode detection
- Updated getUserFollowingFollowers query to include isFollowedByCurrentUser

These changes enable the mobile app to:
- Display correct follow/unfollow button states without client-side lookups
- Differentiate between "Following" and "Popular" feed modes
- Reduce payload size by removing unnecessary nested data

Backend handles efficient resolution via request-scoped caching.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-09 23:44:39 -08:00
dean
83b7f0d0f6 Add home feed and isFollowedByCurrentUser field
- Add home option to VideoFeedInputGQL for automatic feed selection
- Add hasFollowing field to VideoHistoryGQL response
- Add isFollowedByCurrentUser field to UserGQL to replace followers N+1

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 09:15:39 -08:00
dean
a882f98d91 Add home option to VideoFeedInputGQL
The home feed option enables smart feed selection on the backend:
- If user has following: returns FOLLOWING feed
- If user has no following: returns ALL feed
- Always includes hasFollowing flag so frontend knows which feed it got

This allows frontend to make a single query instead of needing to
check following status separately.
2025-11-07 12:16:01 -08:00
10 changed files with 507 additions and 7687 deletions

5423
package-lock.json generated

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,207 +0,0 @@
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,11 +19,7 @@ fragment UserSocialsFields on UserGQL {
id
username
profileImageUri
followers {
id
username
profileImageUri
}
isFollowedByCurrentUser
}
fragment VideoCardFields on VideoGQL {
@@ -116,5 +112,6 @@ query GetVideoFeed(
hasNextPage
endCursor
}
hasFollowing
}
}

View File

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

View File

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

View File

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

View File

@@ -3,12 +3,6 @@ type Query {
aggregateInput: AggregateInputGQL!
): [AggregateResultGQL!]!
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!
waitFor(duration: Float!): Float!
getFeedVideos(
@@ -304,60 +298,35 @@ type BucketGQL {
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!
type DeployedConfigGQL {
allowNewUsers: Boolean!
firebase: Boolean!
devMode: Boolean!
environment: String!
minimumAllowedAppVersion: String!
subscriptionGatingEnabled: Boolean!
bannerMessages: [BannerGQL!]!
}
type RuleSet {
id: ID!
name: String!
description: String
createdAt: DateTime!
updatedAt: DateTime!
}
type UserGQL {
type BannerGQL {
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
message: String!
color: String!
kind: BannerKindEnum!
dismissible: Boolean!
priority: Int!
}
type ChallengeEntry {
id: ID!
status: String!
shotsCount: Int
makesCount: Int
makeRate: Float
qualified: Boolean
createdAt: DateTime!
challenge: Challenge!
video: VideoGQL
user: UserGQL!
enum BannerKindEnum {
INFO
WARNING
ERROR
}
type VideoHistoryGQL {
videos: [VideoGQL!]!
pageInfo: PageInfoGQL!
hasFollowing: Boolean!
}
type VideoGQL {
@@ -391,6 +360,24 @@ type VideoGQL {
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 {
id: Int!
videoId: Int!
@@ -646,45 +633,6 @@ type 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 {
hasNextPage: Boolean!
endCursor: String
@@ -992,30 +940,6 @@ scalar JSON
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(

848
yarn.lock

File diff suppressed because it is too large Load Diff