Compare commits

..

5 Commits

Author SHA1 Message Date
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
5 changed files with 43 additions and 169 deletions

View File

@@ -3178,6 +3178,7 @@ export type UserGql = {
following?: Maybe<Array<UserGql>>;
id: Scalars["Int"]["output"];
isAdmin?: Maybe<Scalars["Boolean"]["output"]>;
isFollowedByCurrentUser?: Maybe<Scalars["Boolean"]["output"]>;
profileImageUri?: Maybe<Scalars["String"]["output"]>;
stripeCustomerId?: Maybe<Scalars["String"]["output"]>;
updatedAt?: Maybe<Scalars["DateTime"]["output"]>;
@@ -3227,16 +3228,25 @@ export type VideoFeedInputGql =
| {
allUsers: Scalars["Boolean"]["input"];
followedByUserId?: never;
home?: never;
userId?: never;
}
| {
allUsers?: never;
followedByUserId: Scalars["Int"]["input"];
home?: never;
userId?: never;
}
| {
allUsers?: never;
followedByUserId?: never;
home: Scalars["Boolean"]["input"];
userId?: never;
}
| {
allUsers?: never;
followedByUserId?: never;
home?: never;
userId: Scalars["Int"]["input"];
};
@@ -3265,7 +3275,6 @@ export type VideoGql = {
name?: Maybe<Scalars["String"]["output"]>;
owner?: Maybe<UserGql>;
playlist?: Maybe<HlsPlaylistGql>;
pocketSize?: Maybe<Scalars["Float"]["output"]>;
private: Scalars["Boolean"]["output"];
reactions: Array<ReactionGql>;
screenshotUri?: Maybe<Scalars["String"]["output"]>;
@@ -3294,7 +3303,6 @@ export type VideoMetadataInput = {
/** @deprecated `game_type` is deprecated. Use `tags` instead. */
gameType?: InputMaybe<Scalars["String"]["input"]>;
lastIntendedSegmentBound?: InputMaybe<Scalars["Int"]["input"]>;
pocketSize?: InputMaybe<Scalars["Float"]["input"]>;
private?: InputMaybe<Scalars["Boolean"]["input"]>;
resolution?: InputMaybe<VideoResolution>;
startTime?: InputMaybe<Scalars["DateTime"]["input"]>;
@@ -3538,12 +3546,7 @@ export type GetFeedQuery = {
id: number;
username: string;
profileImageUri?: string | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
}> | null;
isFollowedByCurrentUser?: boolean | null;
};
}>;
comments: Array<{
@@ -3555,12 +3558,7 @@ export type GetFeedQuery = {
id: number;
username: string;
profileImageUri?: string | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
}> | null;
isFollowedByCurrentUser?: boolean | null;
};
replies: Array<{
__typename?: "CommentGQL";
@@ -3571,12 +3569,7 @@ export type GetFeedQuery = {
id: number;
username: string;
profileImageUri?: string | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
}> | null;
isFollowedByCurrentUser?: boolean | null;
};
}>;
}>;
@@ -3594,12 +3587,7 @@ export type UserSocialsFieldsFragment = {
id: number;
username: string;
profileImageUri?: string | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
}> | null;
isFollowedByCurrentUser?: boolean | null;
};
export type VideoCardFieldsFragment = {
@@ -3656,12 +3644,7 @@ export type VideoCardFieldsFragment = {
id: number;
username: string;
profileImageUri?: string | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
}> | null;
isFollowedByCurrentUser?: boolean | null;
};
}>;
comments: Array<{
@@ -3673,12 +3656,7 @@ export type VideoCardFieldsFragment = {
id: number;
username: string;
profileImageUri?: string | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
}> | null;
isFollowedByCurrentUser?: boolean | null;
};
replies: Array<{
__typename?: "CommentGQL";
@@ -3689,12 +3667,7 @@ export type VideoCardFieldsFragment = {
id: number;
username: string;
profileImageUri?: string | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
}> | null;
isFollowedByCurrentUser?: boolean | null;
};
}>;
}>;
@@ -3713,6 +3686,7 @@ export type GetVideoFeedQuery = {
__typename?: "Query";
getFeedVideos: {
__typename?: "VideoHistoryGQL";
hasFollowing: boolean;
videos: Array<{
__typename?: "VideoGQL";
id: number;
@@ -3770,12 +3744,7 @@ export type GetVideoFeedQuery = {
id: number;
username: string;
profileImageUri?: string | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
}> | null;
isFollowedByCurrentUser?: boolean | null;
};
}>;
comments: Array<{
@@ -3787,12 +3756,7 @@ export type GetVideoFeedQuery = {
id: number;
username: string;
profileImageUri?: string | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
}> | null;
isFollowedByCurrentUser?: boolean | null;
};
replies: Array<{
__typename?: "CommentGQL";
@@ -3803,12 +3767,7 @@ export type GetVideoFeedQuery = {
id: number;
username: string;
profileImageUri?: string | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
}> | null;
isFollowedByCurrentUser?: boolean | null;
};
}>;
}>;
@@ -4975,21 +4934,7 @@ export type FollowUserMutationVariables = Exact<{
export type FollowUserMutation = {
__typename?: "Mutation";
followUser: {
__typename?: "UserGQL";
username: string;
id: number;
following?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
}> | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
}> | null;
};
followUser: { __typename?: "UserGQL"; id: number; username: string };
};
export type UnfollowUserMutationVariables = Exact<{
@@ -4998,21 +4943,7 @@ export type UnfollowUserMutationVariables = Exact<{
export type UnfollowUserMutation = {
__typename?: "Mutation";
unfollowUser: {
__typename?: "UserGQL";
username: string;
id: number;
following?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
}> | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
}> | null;
};
unfollowUser: { __typename?: "UserGQL"; id: number; username: string };
};
export type GetUserFollowingFollowersQueryVariables = Exact<{
@@ -5029,12 +4960,14 @@ export type GetUserFollowingFollowersQuery = {
id: number;
username: string;
profileImageUri?: string | null;
isFollowedByCurrentUser?: boolean | null;
}> | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
isFollowedByCurrentUser?: boolean | null;
}> | null;
} | null;
};
@@ -5170,7 +5103,6 @@ export type GetVideoUpdatePageDetailsQuery = {
makePercentage: number;
elapsedTime?: number | null;
tableSize: number;
pocketSize?: number | null;
private: boolean;
tags: Array<{
__typename?: "VideoTag";
@@ -5211,7 +5143,6 @@ export type GetVideoDetailsQuery = {
createdAt?: any | null;
updatedAt?: any | null;
tableSize: number;
pocketSize?: number | null;
private: boolean;
owner?: {
__typename?: "UserGQL";
@@ -5262,12 +5193,7 @@ export type GetVideoSocialDetailsByIdQuery = {
id: number;
username: string;
profileImageUri?: string | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
}> | null;
isFollowedByCurrentUser?: boolean | null;
};
}>;
comments: Array<{
@@ -5279,12 +5205,7 @@ export type GetVideoSocialDetailsByIdQuery = {
id: number;
username: string;
profileImageUri?: string | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
}> | null;
isFollowedByCurrentUser?: boolean | null;
};
replies: Array<{
__typename?: "CommentGQL";
@@ -5295,12 +5216,7 @@ export type GetVideoSocialDetailsByIdQuery = {
id: number;
username: string;
profileImageUri?: string | null;
followers?: Array<{
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
}> | null;
isFollowedByCurrentUser?: boolean | null;
};
}>;
}>;
@@ -5888,11 +5804,7 @@ export const UserSocialsFieldsFragmentDoc = gql`
id
username
profileImageUri
followers {
id
username
profileImageUri
}
isFollowedByCurrentUser
}
`;
export const VideoCardFieldsFragmentDoc = gql`
@@ -6797,6 +6709,7 @@ export const GetVideoFeedDocument = gql`
hasNextPage
endCursor
}
hasFollowing
}
}
${VideoCardFieldsFragmentDoc}
@@ -9571,16 +9484,8 @@ export type GetUserTagsQueryResult = Apollo.QueryResult<
export const FollowUserDocument = gql`
mutation followUser($followedUserId: Int!) {
followUser(followedUserId: $followedUserId) {
username
id
following {
id
username
}
followers {
id
username
}
username
}
}
`;
@@ -9630,16 +9535,8 @@ export type FollowUserMutationOptions = Apollo.BaseMutationOptions<
export const UnfollowUserDocument = gql`
mutation unfollowUser($followedUserId: Int!) {
unfollowUser(followedUserId: $followedUserId) {
username
id
following {
id
username
}
followers {
id
username
}
username
}
}
`;
@@ -9694,11 +9591,13 @@ export const GetUserFollowingFollowersDocument = gql`
id
username
profileImageUri
isFollowedByCurrentUser
}
followers {
id
username
profileImageUri
isFollowedByCurrentUser
}
}
}
@@ -10062,7 +9961,6 @@ export const GetVideoUpdatePageDetailsDocument = gql`
makePercentage
elapsedTime
tableSize
pocketSize
private
tags {
tagClasses {
@@ -10204,7 +10102,6 @@ export const GetVideoDetailsDocument = gql`
createdAt
updatedAt
tableSize
pocketSize
private
owner {
id

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

@@ -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
}
}
}

View File

@@ -36,7 +36,6 @@ query GetVideoUpdatePageDetails($videoId: Int!) {
makePercentage
elapsedTime
tableSize
pocketSize
private
tags {
tagClasses {
@@ -67,7 +66,6 @@ query GetVideoDetails($videoId: Int!) {
createdAt
updatedAt
tableSize
pocketSize
private
owner {
id
@@ -88,11 +86,7 @@ fragment UserSocialsFields on UserGQL {
id
username
profileImageUri
followers {
id
username
profileImageUri
}
isFollowedByCurrentUser
}
query GetVideoSocialDetailsById($videoId: Int!) {

View File

@@ -348,7 +348,6 @@ type VideoGQL {
elapsedTime: Float
framesPerSecond: Float!
tableSize: Float!
pocketSize: Float
private: Boolean!
stream: UploadStreamGQL
playlist: HLSPlaylistGQL
@@ -375,6 +374,7 @@ type UserGQL {
agreesToMarketing: Boolean
following: [UserGQL!]
followers: [UserGQL!]
isFollowedByCurrentUser: Boolean
}
type ShotGQL {
@@ -654,6 +654,7 @@ input VideoFeedInputGQL @oneOf {
followedByUserId: Int
userId: Int
allUsers: Boolean
home: Boolean
}
type MakePercentageIntervalGQL {
@@ -1123,7 +1124,6 @@ input VideoMetadataInput {
"""
tags: [VideoTagInput!] = null
tableSize: Float = null
pocketSize: Float = null
lastIntendedSegmentBound: Int = null
streamSegmentType: StreamSegmentTypeEnum = null
private: Boolean = null