diff --git a/database/migrations/000007_create_user_relationships.down.sql b/database/migrations/000007_create_user_relationships.down.sql new file mode 100644 index 0000000..afc7697 --- /dev/null +++ b/database/migrations/000007_create_user_relationships.down.sql @@ -0,0 +1 @@ +DROP TABLE user_relationships; \ No newline at end of file diff --git a/database/migrations/000007_create_user_relationships.up.sql b/database/migrations/000007_create_user_relationships.up.sql new file mode 100644 index 0000000..4aa92f9 --- /dev/null +++ b/database/migrations/000007_create_user_relationships.up.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS user_relationships +( + id BIGSERIAL PRIMARY KEY, + follower_id int REFERENCES users(id), + followee_id int REFERENCES users(id), + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + UNIQUE(follower_id, followee_id) +); \ No newline at end of file diff --git a/database/userRelationship.go b/database/userRelationship.go new file mode 100644 index 0000000..c76b644 --- /dev/null +++ b/database/userRelationship.go @@ -0,0 +1,80 @@ +package database + +import ( + "time" + + log "github.com/sirupsen/logrus" + "github.com/well-informed/wellinformed/graph/model" +) + +func (db DB) SaveUserRelationship(followerID int64, followeeID int64) (*model.UserRelationship, error) { + stmt := `INSERT INTO user_relationships + ( + follower_id, + followee_id, + created_at, + updated_at + ) + VALUES($1, $2, $3, $4) + ON CONFLICT (follower_id, followee_id) + DO UPDATE SET + follower_id = $1, + followee_id = $2, + updated_at = $3 + RETURNING id, created_at, updated_at` + var ID int64 + var CreatedAt time.Time + var UpdatedAt time.Time + err := db.QueryRowx(stmt, + followerID, + followeeID, + time.Now(), + time.Now(), + ).Scan(&ID, &CreatedAt, &UpdatedAt) + if err != nil { + log.Error("failed to save interactions entry: err: ", err) + return nil, err + } + userRelationship := &model.UserRelationship{ + ID: ID, + Follower: followerID, + Followee: followeeID, + CreatedAt: CreatedAt, + UpdatedAt: UpdatedAt, + } + return userRelationship, nil +} + +func (db DB) DeleteUserRelationship(followerID int64, followeeID int64) error { + stmt := `DELETE FROM user_relationships WHERE follower_id = $1 AND followee_id = $2` + _, err := db.Exec(stmt, followerID, followeeID) + if err != nil { + log.Error("unable to delete user relationship", err) + return err + } + return nil +} + +func (db DB) ListUserRelationshipsByFollowerID(followerID int64) ([]*model.UserRelationship, error) { + stmt := `SELECT * FROM user_relationships WHERE follower_id = $1` + + userRelationships := make([]*model.UserRelationship, 0) + err := db.Select(&userRelationships, stmt, followerID) + if err != nil { + log.Error("error listing relationships by follower ID", err) + return nil, err + } + return userRelationships, nil +} + +func (db DB) ListUserRelationshipsByFolloweeID(followeeID int64) ([]*model.UserRelationship, error) { + stmt := `SELECT * FROM user_relationships WHERE followee_id = $1` + + userRelationships := make([]*model.UserRelationship, 0) + err := db.Select(&userRelationships, stmt, followeeID) + if err != nil { + log.Error("error listing relationships by followee ID", err) + return nil, err + } + return userRelationships, nil +} diff --git a/graph/Follow.go b/graph/Follow.go new file mode 100644 index 0000000..9f5bb73 --- /dev/null +++ b/graph/Follow.go @@ -0,0 +1,28 @@ +package graph + +import ( + "context" + "errors" + + log "github.com/sirupsen/logrus" + "github.com/well-informed/wellinformed/graph/model" +) + +func (r *mutationResolver) followUser(ctx context.Context, user *model.User, input model.UserRelationshipInput) (*model.UserRelationship, error) { + if user.ID != input.FollowerID { + return nil, errors.New("follower must be signed in user") + } + return r.DB.SaveUserRelationship(input.FollowerID, input.FolloweeID) +} + +func (r *mutationResolver) unfollowUser(ctx context.Context, user *model.User, input model.UserRelationshipInput) (*model.DeleteResponse, error) { + if user.ID != input.FollowerID { + return &model.DeleteResponse{Ok: false}, errors.New("follower must be signed in user") + } + err := r.DB.DeleteUserRelationship(input.FollowerID, input.FolloweeID) + if err != nil { + log.Error("unable to delete user relationship") + return &model.DeleteResponse{Ok: false}, err + } + return &model.DeleteResponse{Ok: true}, nil +} diff --git a/graph/generated/generated.go b/graph/generated/generated.go index 931adad..08bfffd 100644 --- a/graph/generated/generated.go +++ b/graph/generated/generated.go @@ -46,6 +46,7 @@ type ResolverRoot interface { SrcRSSFeed() SrcRSSFeedResolver User() UserResolver UserFeed() UserFeedResolver + UserRelationship() UserRelationshipResolver UserSubscription() UserSubscriptionResolver } @@ -155,11 +156,13 @@ type ComplexityRoot struct { AddSrcRSSFeed func(childComplexity int, feedLink string, targetFeedID int64) int AddUserFeed func(childComplexity int, input model.AddUserFeedInput) int DeleteSubscription func(childComplexity int, srcRssfeedID int64) int + FollowUser func(childComplexity int, input model.UserRelationshipInput) int Login func(childComplexity int, input model.LoginInput) int Register func(childComplexity int, input model.RegisterInput) int SaveEngine func(childComplexity int, engine model.EngineInput) int SaveInteraction func(childComplexity int, input *model.InteractionInput) int SwitchActiveUserFeed func(childComplexity int, feedID int64) int + UnfollowUser func(childComplexity int, input model.UserRelationshipInput) int } Query struct { @@ -211,6 +214,8 @@ type ComplexityRoot struct { Feed func(childComplexity int) int Feeds func(childComplexity int) int Firstname func(childComplexity int) int + Followers func(childComplexity int) int + Follows func(childComplexity int) int ID func(childComplexity int) int Interactions func(childComplexity int, readState *model.ReadState, input model.InteractionConnectionInput) int Lastname func(childComplexity int) int @@ -233,6 +238,14 @@ type ComplexityRoot struct { UserID func(childComplexity int) int } + UserRelationship struct { + CreatedAt func(childComplexity int) int + Followee func(childComplexity int) int + Follower func(childComplexity int) int + ID func(childComplexity int) int + UpdatedAt func(childComplexity int) int + } + UserSubscription struct { CreatedAt func(childComplexity int) int ID func(childComplexity int) int @@ -282,6 +295,8 @@ type MutationResolver interface { SaveInteraction(ctx context.Context, input *model.InteractionInput) (*model.ContentItem, error) SaveEngine(ctx context.Context, engine model.EngineInput) (*model.Engine, error) SwitchActiveUserFeed(ctx context.Context, feedID int64) (*model.User, error) + FollowUser(ctx context.Context, input model.UserRelationshipInput) (*model.UserRelationship, error) + UnfollowUser(ctx context.Context, input model.UserRelationshipInput) (*model.DeleteResponse, error) } type QueryResolver interface { SrcRSSFeed(ctx context.Context, input *model.SrcRSSFeedInput) (*model.SrcRSSFeed, error) @@ -305,6 +320,8 @@ type UserResolver interface { Subscriptions(ctx context.Context, obj *model.User, input *model.UserSubscriptionConnectionInput) (*model.UserSubscriptionConnection, error) Interactions(ctx context.Context, obj *model.User, readState *model.ReadState, input model.InteractionConnectionInput) (*model.InteractionConnection, error) + Follows(ctx context.Context, obj *model.User) ([]*model.User, error) + Followers(ctx context.Context, obj *model.User) ([]*model.User, error) } type UserFeedResolver interface { User(ctx context.Context, obj *model.UserFeed) (*model.User, error) @@ -314,6 +331,10 @@ type UserFeedResolver interface { Engine(ctx context.Context, obj *model.UserFeed) (*model.Engine, error) IsActive(ctx context.Context, obj *model.UserFeed) (bool, error) } +type UserRelationshipResolver interface { + Follower(ctx context.Context, obj *model.UserRelationship) (*model.User, error) + Followee(ctx context.Context, obj *model.UserRelationship) (*model.User, error) +} type UserSubscriptionResolver interface { User(ctx context.Context, obj *model.UserSubscription) (*model.User, error) SrcRSSFeed(ctx context.Context, obj *model.UserSubscription) (*model.SrcRSSFeed, error) @@ -793,6 +814,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.DeleteSubscription(childComplexity, args["srcRSSFeedID"].(int64)), true + case "Mutation.followUser": + if e.complexity.Mutation.FollowUser == nil { + break + } + + args, err := ec.field_Mutation_followUser_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.FollowUser(childComplexity, args["input"].(model.UserRelationshipInput)), true + case "Mutation.login": if e.complexity.Mutation.Login == nil { break @@ -853,6 +886,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.SwitchActiveUserFeed(childComplexity, args["feedID"].(int64)), true + case "Mutation.unfollowUser": + if e.complexity.Mutation.UnfollowUser == nil { + break + } + + args, err := ec.field_Mutation_unfollowUser_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.UnfollowUser(childComplexity, args["input"].(model.UserRelationshipInput)), true + case "Query.engines": if e.complexity.Query.Engines == nil { break @@ -1114,6 +1159,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.User.Firstname(childComplexity), true + case "User.followers": + if e.complexity.User.Followers == nil { + break + } + + return e.complexity.User.Followers(childComplexity), true + + case "User.follows": + if e.complexity.User.Follows == nil { + break + } + + return e.complexity.User.Follows(childComplexity), true + case "User.id": if e.complexity.User.ID == nil { break @@ -1253,6 +1312,41 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.UserFeed.UserID(childComplexity), true + case "UserRelationship.createdAt": + if e.complexity.UserRelationship.CreatedAt == nil { + break + } + + return e.complexity.UserRelationship.CreatedAt(childComplexity), true + + case "UserRelationship.followee": + if e.complexity.UserRelationship.Followee == nil { + break + } + + return e.complexity.UserRelationship.Followee(childComplexity), true + + case "UserRelationship.follower": + if e.complexity.UserRelationship.Follower == nil { + break + } + + return e.complexity.UserRelationship.Follower(childComplexity), true + + case "UserRelationship.id": + if e.complexity.UserRelationship.ID == nil { + break + } + + return e.complexity.UserRelationship.ID(childComplexity), true + + case "UserRelationship.updatedAt": + if e.complexity.UserRelationship.UpdatedAt == nil { + break + } + + return e.complexity.UserRelationship.UpdatedAt(childComplexity), true + case "UserSubscription.createdAt": if e.complexity.UserSubscription.CreatedAt == nil { break @@ -1523,6 +1617,8 @@ type User { updatedAt: Time! subscriptions(input: UserSubscriptionConnectionInput): UserSubscriptionConnection! interactions(readState: ReadState, input: InteractionConnectionInput!): InteractionConnection! + follows: [User!]! + followers: [User!]! } enum sortType { @@ -1553,6 +1649,19 @@ enum ReadState { unread } +type UserRelationship { + id: ID! + follower: User! + followee: User! + createdAt: Time! + updatedAt: Time! +} + +input UserRelationshipInput { + followerID: ID! + followeeID: ID! +} + input InteractionInput { contentItemID: ID! readState: ReadState! @@ -1715,6 +1824,8 @@ type Mutation { saveInteraction(input: InteractionInput): ContentItem! saveEngine(engine: EngineInput!): Engine! switchActiveUserFeed(feedID: ID!): User! + followUser(input: UserRelationshipInput!): UserRelationship! + unfollowUser(input: UserRelationshipInput!): DeleteResponse! } `, BuiltIn: false}, } @@ -1802,6 +1913,20 @@ func (ec *executionContext) field_Mutation_deleteSubscription_args(ctx context.C return args, nil } +func (ec *executionContext) field_Mutation_followUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.UserRelationshipInput + if tmp, ok := rawArgs["input"]; ok { + arg0, err = ec.unmarshalNUserRelationshipInput2githubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserRelationshipInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_login_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -1872,6 +1997,20 @@ func (ec *executionContext) field_Mutation_switchActiveUserFeed_args(ctx context return args, nil } +func (ec *executionContext) field_Mutation_unfollowUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.UserRelationshipInput + if tmp, ok := rawArgs["input"]; ok { + arg0, err = ec.unmarshalNUserRelationshipInput2githubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserRelationshipInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["input"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -4388,6 +4527,88 @@ func (ec *executionContext) _Mutation_switchActiveUserFeed(ctx context.Context, return ec.marshalNUser2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_followUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_followUser_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().FollowUser(rctx, args["input"].(model.UserRelationshipInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.UserRelationship) + fc.Result = res + return ec.marshalNUserRelationship2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserRelationship(ctx, field.Selections, res) +} + +func (ec *executionContext) _Mutation_unfollowUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_unfollowUser_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().UnfollowUser(rctx, args["input"].(model.UserRelationshipInput)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.DeleteResponse) + fc.Result = res + return ec.marshalNDeleteResponse2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐDeleteResponse(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_srcRSSFeed(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -5902,6 +6123,74 @@ func (ec *executionContext) _User_interactions(ctx context.Context, field graphq return ec.marshalNInteractionConnection2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐInteractionConnection(ctx, field.Selections, res) } +func (ec *executionContext) _User_follows(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "User", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.User().Follows(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.User) + fc.Result = res + return ec.marshalNUser2ᚕᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _User_followers(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "User", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.User().Followers(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.User) + fc.Result = res + return ec.marshalNUser2ᚕᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserᚄ(ctx, field.Selections, res) +} + func (ec *executionContext) _UserFeed_id(ctx context.Context, field graphql.CollectedField, obj *model.UserFeed) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -6212,7 +6501,7 @@ func (ec *executionContext) _UserFeed_isActive(ctx context.Context, field graphq return ec.marshalNBoolean2bool(ctx, field.Selections, res) } -func (ec *executionContext) _UserSubscription_id(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscription) (ret graphql.Marshaler) { +func (ec *executionContext) _UserRelationship_id(ctx context.Context, field graphql.CollectedField, obj *model.UserRelationship) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -6220,7 +6509,7 @@ func (ec *executionContext) _UserSubscription_id(ctx context.Context, field grap } }() fc := &graphql.FieldContext{ - Object: "UserSubscription", + Object: "UserRelationship", Field: field, Args: nil, IsMethod: false, @@ -6246,7 +6535,7 @@ func (ec *executionContext) _UserSubscription_id(ctx context.Context, field grap return ec.marshalNID2int64(ctx, field.Selections, res) } -func (ec *executionContext) _UserSubscription_user(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscription) (ret graphql.Marshaler) { +func (ec *executionContext) _UserRelationship_follower(ctx context.Context, field graphql.CollectedField, obj *model.UserRelationship) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -6254,7 +6543,7 @@ func (ec *executionContext) _UserSubscription_user(ctx context.Context, field gr } }() fc := &graphql.FieldContext{ - Object: "UserSubscription", + Object: "UserRelationship", Field: field, Args: nil, IsMethod: true, @@ -6263,7 +6552,7 @@ func (ec *executionContext) _UserSubscription_user(ctx context.Context, field gr ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.UserSubscription().User(rctx, obj) + return ec.resolvers.UserRelationship().Follower(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -6280,7 +6569,7 @@ func (ec *executionContext) _UserSubscription_user(ctx context.Context, field gr return ec.marshalNUser2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) } -func (ec *executionContext) _UserSubscription_srcRSSFeed(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscription) (ret graphql.Marshaler) { +func (ec *executionContext) _UserRelationship_followee(ctx context.Context, field graphql.CollectedField, obj *model.UserRelationship) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -6288,7 +6577,7 @@ func (ec *executionContext) _UserSubscription_srcRSSFeed(ctx context.Context, fi } }() fc := &graphql.FieldContext{ - Object: "UserSubscription", + Object: "UserRelationship", Field: field, Args: nil, IsMethod: true, @@ -6297,7 +6586,7 @@ func (ec *executionContext) _UserSubscription_srcRSSFeed(ctx context.Context, fi ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.UserSubscription().SrcRSSFeed(rctx, obj) + return ec.resolvers.UserRelationship().Followee(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -6309,12 +6598,12 @@ func (ec *executionContext) _UserSubscription_srcRSSFeed(ctx context.Context, fi } return graphql.Null } - res := resTmp.(*model.SrcRSSFeed) + res := resTmp.(*model.User) fc.Result = res - return ec.marshalNSrcRSSFeed2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐSrcRSSFeed(ctx, field.Selections, res) + return ec.marshalNUser2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) } -func (ec *executionContext) _UserSubscription_createdAt(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscription) (ret graphql.Marshaler) { +func (ec *executionContext) _UserRelationship_createdAt(ctx context.Context, field graphql.CollectedField, obj *model.UserRelationship) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -6322,7 +6611,7 @@ func (ec *executionContext) _UserSubscription_createdAt(ctx context.Context, fie } }() fc := &graphql.FieldContext{ - Object: "UserSubscription", + Object: "UserRelationship", Field: field, Args: nil, IsMethod: false, @@ -6348,7 +6637,7 @@ func (ec *executionContext) _UserSubscription_createdAt(ctx context.Context, fie return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _UserSubscriptionConnection_edges(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscriptionConnection) (ret graphql.Marshaler) { +func (ec *executionContext) _UserRelationship_updatedAt(ctx context.Context, field graphql.CollectedField, obj *model.UserRelationship) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -6356,7 +6645,7 @@ func (ec *executionContext) _UserSubscriptionConnection_edges(ctx context.Contex } }() fc := &graphql.FieldContext{ - Object: "UserSubscriptionConnection", + Object: "UserRelationship", Field: field, Args: nil, IsMethod: false, @@ -6365,7 +6654,7 @@ func (ec *executionContext) _UserSubscriptionConnection_edges(ctx context.Contex ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Edges, nil + return obj.UpdatedAt, nil }) if err != nil { ec.Error(ctx, err) @@ -6377,12 +6666,12 @@ func (ec *executionContext) _UserSubscriptionConnection_edges(ctx context.Contex } return graphql.Null } - res := resTmp.([]*model.UserSubscriptionEdge) + res := resTmp.(time.Time) fc.Result = res - return ec.marshalNUserSubscriptionEdge2ᚕᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserSubscriptionEdgeᚄ(ctx, field.Selections, res) + return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) } -func (ec *executionContext) _UserSubscriptionConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscriptionConnection) (ret graphql.Marshaler) { +func (ec *executionContext) _UserSubscription_id(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscription) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -6390,7 +6679,7 @@ func (ec *executionContext) _UserSubscriptionConnection_pageInfo(ctx context.Con } }() fc := &graphql.FieldContext{ - Object: "UserSubscriptionConnection", + Object: "UserSubscription", Field: field, Args: nil, IsMethod: false, @@ -6399,7 +6688,7 @@ func (ec *executionContext) _UserSubscriptionConnection_pageInfo(ctx context.Con ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.PageInfo, nil + return obj.ID, nil }) if err != nil { ec.Error(ctx, err) @@ -6411,12 +6700,12 @@ func (ec *executionContext) _UserSubscriptionConnection_pageInfo(ctx context.Con } return graphql.Null } - res := resTmp.(*model.UserSubscriptionPageInfo) + res := resTmp.(int64) fc.Result = res - return ec.marshalNUserSubscriptionPageInfo2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserSubscriptionPageInfo(ctx, field.Selections, res) + return ec.marshalNID2int64(ctx, field.Selections, res) } -func (ec *executionContext) _UserSubscriptionEdge_node(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscriptionEdge) (ret graphql.Marshaler) { +func (ec *executionContext) _UserSubscription_user(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscription) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { ec.Error(ctx, ec.Recover(ctx, r)) @@ -6424,16 +6713,16 @@ func (ec *executionContext) _UserSubscriptionEdge_node(ctx context.Context, fiel } }() fc := &graphql.FieldContext{ - Object: "UserSubscriptionEdge", + Object: "UserSubscription", Field: field, Args: nil, - IsMethod: false, + IsMethod: true, } ctx = graphql.WithFieldContext(ctx, fc) resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return obj.Node, nil + return ec.resolvers.UserSubscription().User(rctx, obj) }) if err != nil { ec.Error(ctx, err) @@ -6445,9 +6734,179 @@ func (ec *executionContext) _UserSubscriptionEdge_node(ctx context.Context, fiel } return graphql.Null } - res := resTmp.(*model.UserSubscription) + res := resTmp.(*model.User) fc.Result = res - return ec.marshalNUserSubscription2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserSubscription(ctx, field.Selections, res) + return ec.marshalNUser2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) +} + +func (ec *executionContext) _UserSubscription_srcRSSFeed(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscription) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "UserSubscription", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.UserSubscription().SrcRSSFeed(rctx, obj) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.SrcRSSFeed) + fc.Result = res + return ec.marshalNSrcRSSFeed2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐSrcRSSFeed(ctx, field.Selections, res) +} + +func (ec *executionContext) _UserSubscription_createdAt(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscription) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "UserSubscription", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.CreatedAt, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(time.Time) + fc.Result = res + return ec.marshalNTime2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) _UserSubscriptionConnection_edges(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscriptionConnection) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "UserSubscriptionConnection", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Edges, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*model.UserSubscriptionEdge) + fc.Result = res + return ec.marshalNUserSubscriptionEdge2ᚕᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserSubscriptionEdgeᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) _UserSubscriptionConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscriptionConnection) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "UserSubscriptionConnection", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.PageInfo, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.UserSubscriptionPageInfo) + fc.Result = res + return ec.marshalNUserSubscriptionPageInfo2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserSubscriptionPageInfo(ctx, field.Selections, res) +} + +func (ec *executionContext) _UserSubscriptionEdge_node(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscriptionEdge) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "UserSubscriptionEdge", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Node, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.UserSubscription) + fc.Result = res + return ec.marshalNUserSubscription2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserSubscription(ctx, field.Selections, res) } func (ec *executionContext) _UserSubscriptionEdge_cursor(ctx context.Context, field graphql.CollectedField, obj *model.UserSubscriptionEdge) (ret graphql.Marshaler) { @@ -8059,6 +8518,30 @@ func (ec *executionContext) unmarshalInputUserInteractionsInput(ctx context.Cont return it, nil } +func (ec *executionContext) unmarshalInputUserRelationshipInput(ctx context.Context, obj interface{}) (model.UserRelationshipInput, error) { + var it model.UserRelationshipInput + var asMap = obj.(map[string]interface{}) + + for k, v := range asMap { + switch k { + case "followerID": + var err error + it.FollowerID, err = ec.unmarshalNID2int64(ctx, v) + if err != nil { + return it, err + } + case "followeeID": + var err error + it.FolloweeID, err = ec.unmarshalNID2int64(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputUserSubscriptionConnectionInput(ctx context.Context, obj interface{}) (model.UserSubscriptionConnectionInput, error) { var it model.UserSubscriptionConnectionInput var asMap = obj.(map[string]interface{}) @@ -8774,6 +9257,16 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "followUser": + out.Values[i] = ec._Mutation_followUser(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } + case "unfollowUser": + out.Values[i] = ec._Mutation_unfollowUser(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -9251,6 +9744,34 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj } return res }) + case "follows": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._User_follows(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "followers": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._User_followers(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -9371,6 +9892,71 @@ func (ec *executionContext) _UserFeed(ctx context.Context, sel ast.SelectionSet, return out } +var userRelationshipImplementors = []string{"UserRelationship"} + +func (ec *executionContext) _UserRelationship(ctx context.Context, sel ast.SelectionSet, obj *model.UserRelationship) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, userRelationshipImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("UserRelationship") + case "id": + out.Values[i] = ec._UserRelationship_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "follower": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._UserRelationship_follower(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "followee": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._UserRelationship_followee(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) + case "createdAt": + out.Values[i] = ec._UserRelationship_createdAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + case "updatedAt": + out.Values[i] = ec._UserRelationship_updatedAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var userSubscriptionImplementors = []string{"UserSubscription"} func (ec *executionContext) _UserSubscription(ctx context.Context, sel ast.SelectionSet, obj *model.UserSubscription) graphql.Marshaler { @@ -10330,6 +10916,43 @@ func (ec *executionContext) marshalNUser2githubᚗcomᚋwellᚑinformedᚋwellin return ec._User(ctx, sel, &v) } +func (ec *executionContext) marshalNUser2ᚕᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.User) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNUser2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUser(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + func (ec *executionContext) marshalNUser2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUser(ctx context.Context, sel ast.SelectionSet, v *model.User) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -10391,6 +11014,24 @@ func (ec *executionContext) marshalNUserFeed2ᚖgithubᚗcomᚋwellᚑinformed return ec._UserFeed(ctx, sel, v) } +func (ec *executionContext) marshalNUserRelationship2githubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserRelationship(ctx context.Context, sel ast.SelectionSet, v model.UserRelationship) graphql.Marshaler { + return ec._UserRelationship(ctx, sel, &v) +} + +func (ec *executionContext) marshalNUserRelationship2ᚖgithubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserRelationship(ctx context.Context, sel ast.SelectionSet, v *model.UserRelationship) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._UserRelationship(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNUserRelationshipInput2githubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserRelationshipInput(ctx context.Context, v interface{}) (model.UserRelationshipInput, error) { + return ec.unmarshalInputUserRelationshipInput(ctx, v) +} + func (ec *executionContext) marshalNUserSubscription2githubᚗcomᚋwellᚑinformedᚋwellinformedᚋgraphᚋmodelᚐUserSubscription(ctx context.Context, sel ast.SelectionSet, v model.UserSubscription) graphql.Marshaler { return ec._UserSubscription(ctx, sel, &v) } diff --git a/graph/model/model.go b/graph/model/model.go index bf08324..87272cb 100644 --- a/graph/model/model.go +++ b/graph/model/model.go @@ -97,3 +97,11 @@ type Interaction struct { CreatedAt time.Time `json:"createdAt" db:"created_at"` UpdatedAt time.Time `json:"updatedAt" db:"updated_at"` } + +type UserRelationship struct { + ID int64 `json:"id"` + Follower int64 `json:"follower" db:"follower_id"` + Followee int64 `json:"followee" db:"followee_id"` + CreatedAt time.Time `json:"createdAt" db:"created_at"` + UpdatedAt time.Time `json:"updatedAt" db:"updated_at"` +} diff --git a/graph/model/models_gen.go b/graph/model/models_gen.go index 2d14f05..6258c6f 100644 --- a/graph/model/models_gen.go +++ b/graph/model/models_gen.go @@ -158,6 +158,11 @@ type UserInteractionsInput struct { ReadState *ReadState `json:"readState"` } +type UserRelationshipInput struct { + FollowerID int64 `json:"followerID"` + FolloweeID int64 `json:"followeeID"` +} + type UserSubscriptionConnection struct { Edges []*UserSubscriptionEdge `json:"edges"` PageInfo *UserSubscriptionPageInfo `json:"pageInfo"` diff --git a/graph/schema.graphql b/graph/schema.graphql index c200bb7..23c10e9 100644 --- a/graph/schema.graphql +++ b/graph/schema.graphql @@ -120,6 +120,8 @@ type User { updatedAt: Time! subscriptions(input: UserSubscriptionConnectionInput): UserSubscriptionConnection! interactions(readState: ReadState, input: InteractionConnectionInput!): InteractionConnection! + follows: [User!]! + followers: [User!]! } enum sortType { @@ -150,6 +152,19 @@ enum ReadState { unread } +type UserRelationship { + id: ID! + follower: User! + followee: User! + createdAt: Time! + updatedAt: Time! +} + +input UserRelationshipInput { + followerID: ID! + followeeID: ID! +} + input InteractionInput { contentItemID: ID! readState: ReadState! @@ -312,4 +327,6 @@ type Mutation { saveInteraction(input: InteractionInput): ContentItem! saveEngine(engine: EngineInput!): Engine! switchActiveUserFeed(feedID: ID!): User! + followUser(input: UserRelationshipInput!): UserRelationship! + unfollowUser(input: UserRelationshipInput!): DeleteResponse! } diff --git a/graph/schema.resolvers.go b/graph/schema.resolvers.go index a7393de..38d1aa9 100644 --- a/graph/schema.resolvers.go +++ b/graph/schema.resolvers.go @@ -179,6 +179,22 @@ func (r *mutationResolver) SwitchActiveUserFeed(ctx context.Context, feedID int6 return user, nil } +func (r *mutationResolver) FollowUser(ctx context.Context, input model.UserRelationshipInput) (*model.UserRelationship, error) { + user, err := auth.GetCurrentUserFromCTX(ctx) + if err != nil { + return nil, err + } + return r.followUser(ctx, user, input) +} + +func (r *mutationResolver) UnfollowUser(ctx context.Context, input model.UserRelationshipInput) (*model.DeleteResponse, error) { + user, err := auth.GetCurrentUserFromCTX(ctx) + if err != nil { + return nil, err + } + return r.unfollowUser(ctx, user, input) +} + func (r *queryResolver) SrcRSSFeed(ctx context.Context, input *model.SrcRSSFeedInput) (*model.SrcRSSFeed, error) { _, err := auth.GetCurrentUserFromCTX(ctx) if err != nil { @@ -361,6 +377,44 @@ func (r *userResolver) Interactions(ctx context.Context, obj *model.User, readSt return page.BuildInteractionPage(input.First, input.After, interactions) } +func (r *userResolver) Follows(ctx context.Context, obj *model.User) ([]*model.User, error) { + userRelationships, err := r.DB.ListUserRelationshipsByFollowerID(obj.ID) + if err != nil { + log.Errorf("error while reading user relationships. err: ", err) + } + users := make([]*model.User, 0) + if len(userRelationships) != 0 { + for _, relationship := range userRelationships { + user, err := r.DB.GetUserByID(relationship.Followee) + if err != nil { + log.Error("could not retrieve users to compile follows list", err) + return nil, err + } + users = append(users, user) + } + } + return users, nil +} + +func (r *userResolver) Followers(ctx context.Context, obj *model.User) ([]*model.User, error) { + userRelationships, err := r.DB.ListUserRelationshipsByFolloweeID(obj.ID) + if err != nil { + log.Errorf("could not list followers for id %v. err: %v", obj.ID, err) + } + followers := make([]*model.User, 0) + if len(userRelationships) != 0 { + for _, relationship := range userRelationships { + follower, err := r.DB.GetUserByID(relationship.Follower) + if err != nil { + log.Error("could not retrieve user for followers list: ", err) + return nil, err + } + followers = append(followers, follower) + } + } + return followers, nil +} + func (r *userFeedResolver) User(ctx context.Context, obj *model.UserFeed) (*model.User, error) { return r.DB.GetUserByID(obj.UserID) } @@ -393,6 +447,14 @@ func (r *userFeedResolver) IsActive(ctx context.Context, obj *model.UserFeed) (b return false, nil } +func (r *userRelationshipResolver) Follower(ctx context.Context, obj *model.UserRelationship) (*model.User, error) { + return r.DB.GetUserByID(obj.Follower) +} + +func (r *userRelationshipResolver) Followee(ctx context.Context, obj *model.UserRelationship) (*model.User, error) { + return r.DB.GetUserByID(obj.Followee) +} + func (r *userSubscriptionResolver) User(ctx context.Context, obj *model.UserSubscription) (*model.User, error) { user, err := r.DB.GetUserByID(obj.UserID) if err != nil { @@ -441,6 +503,11 @@ func (r *Resolver) User() generated.UserResolver { return &userResolver{r} } // UserFeed returns generated.UserFeedResolver implementation. func (r *Resolver) UserFeed() generated.UserFeedResolver { return &userFeedResolver{r} } +// UserRelationship returns generated.UserRelationshipResolver implementation. +func (r *Resolver) UserRelationship() generated.UserRelationshipResolver { + return &userRelationshipResolver{r} +} + // UserSubscription returns generated.UserSubscriptionResolver implementation. func (r *Resolver) UserSubscription() generated.UserSubscriptionResolver { return &userSubscriptionResolver{r} @@ -455,4 +522,5 @@ type queryResolver struct{ *Resolver } type srcRSSFeedResolver struct{ *Resolver } type userResolver struct{ *Resolver } type userFeedResolver struct{ *Resolver } +type userRelationshipResolver struct{ *Resolver } type userSubscriptionResolver struct{ *Resolver } diff --git a/pagination/generator/pageables.yml b/pagination/generator/pageables.yml index 91224d9..63025ab 100644 --- a/pagination/generator/pageables.yml +++ b/pagination/generator/pageables.yml @@ -3,4 +3,3 @@ types: - "ContentItem" - "SrcRSSFeed" - "UserSubscription" - \ No newline at end of file diff --git a/pagination/pageables_gen.go b/pagination/pageables_gen.go index f78e195..cd9c3fa 100644 --- a/pagination/pageables_gen.go +++ b/pagination/pageables_gen.go @@ -1,6 +1,6 @@ // Code generated by go generate; DO NOT EDIT. // This file was generated from "pagination/generator/pageables_generator.go" at -// 2020-12-12 16:47:03.645248 -0600 CST m=+0.002386191 +// 2020-12-12 18:33:29.26955 -0600 CST m=+0.002548799 // using types listed in "pagination/generator/pageables.yml" package pagination diff --git a/wellinformed.go b/wellinformed.go index 7d28ff0..399f5c7 100644 --- a/wellinformed.go +++ b/wellinformed.go @@ -58,6 +58,12 @@ type Persistor interface { // FeedSubscription CreateFeedSubscription(feedID int64, sourceID int64, sourceType model.SourceType) (*model.FeedSubscription, error) ListFeedSubscriptionsByFeedID(feedID int64) ([]*model.FeedSubscription, error) + + //UserRelationship + SaveUserRelationship(followerID int64, followeeID int64) (*model.UserRelationship, error) + DeleteUserRelationship(followerID int64, followeeID int64) error + ListUserRelationshipsByFollowerID(followerID int64) ([]*model.UserRelationship, error) + ListUserRelationshipsByFolloweeID(followeeID int64) ([]*model.UserRelationship, error) } type RSS interface {