Skip to content

Commit

Permalink
Merge pull request #2 from CIS-Projects-in-CS-S21/external-service-in…
Browse files Browse the repository at this point in the history
…terface

External service interface and Start to Feed Generation Algorithm
  • Loading branch information
ryan-mcgregor authored Apr 5, 2021
2 parents 18692e3 + 5b47dc2 commit 9324da0
Show file tree
Hide file tree
Showing 7 changed files with 836 additions and 275 deletions.
1 change: 1 addition & 0 deletions cmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func main() {
server.NewFriendClientWrapper(conn),
server.NewUserClientWrapper(conn),
server.NewMediaClientWrapper(conn),
server.NewHealthClientWrapper(conn),
)

serv := server.NewFeedService(feedGen, logger)
Expand Down
93 changes: 87 additions & 6 deletions internal/server/clientWrappers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package server

import (
"context"
"fmt"
pbcommon "github.com/kic/feed/pkg/proto/common"
"google.golang.org/grpc/metadata"
"strconv"

"google.golang.org/grpc"
"google.golang.org/grpc/metadata"

pbcommon "github.com/kic/feed/pkg/proto/common"
pbfriends "github.com/kic/feed/pkg/proto/friends"
pbhealth "github.com/kic/feed/pkg/proto/health"
pbmedia "github.com/kic/feed/pkg/proto/media"
pbusers "github.com/kic/feed/pkg/proto/users"
)
Expand All @@ -28,7 +29,7 @@ func (f *FriendClientWrapper) GetFriendsForUser(
userID int64,
authCredentials string,
) ([]uint64, error) {
md := metadata.Pairs("Authorization", fmt.Sprintf("Bearer %v", authCredentials))
md := metadata.Pairs("Authorization", authCredentials)
ctx = metadata.NewOutgoingContext(ctx, md)
req := &pbfriends.GetFriendsForUserRequest{
User: &pbcommon.User{
Expand All @@ -44,8 +45,13 @@ func (f *FriendClientWrapper) GetFriendsForUser(
return resp.Friends, nil
}

func (f *FriendClientWrapper) GetConnectionBetweenUsers(ctx context.Context, uid1 int64, uid2 int64, authCredentials string) (float32, error) {
md := metadata.Pairs("Authorization", fmt.Sprintf("Bearer %v", authCredentials))
func (f *FriendClientWrapper) GetConnectionBetweenUsers(
ctx context.Context,
uid1 int64,
uid2 int64,
authCredentials string,
) (float32, error) {
md := metadata.Pairs("Authorization", authCredentials)
ctx = metadata.NewOutgoingContext(ctx, md)
req := &pbfriends.GetConnectionBetweenUsersRequest{
FirstUserID: uint64(uid1),
Expand All @@ -71,6 +77,32 @@ func NewMediaClientWrapper(conn *grpc.ClientConn) *MediaClientWrapper {
}
}

func (m *MediaClientWrapper) GetFilesForUser(
ctx context.Context,
userID int64,
authCredentials string,
) ([]*pbcommon.File, error) {
md := metadata.Pairs("Authorization", authCredentials)
ctx = metadata.NewOutgoingContext(ctx, md)

fileMetadata := make(map[string]string)

fileMetadata["userID"] = strconv.FormatInt(userID, 10)

req := &pbmedia.GetFilesByMetadataRequest{
DesiredMetadata: fileMetadata,
Strictness: pbmedia.MetadataStrictness_STRICT,
}

resp, err := m.mediaClient.GetFilesWithMetadata(ctx, req)

if err != nil {
return nil, err
}

return resp.FileInfos, nil
}

type UserClientWrapper struct {
usersClient pbusers.UsersClient
}
Expand All @@ -80,3 +112,52 @@ func NewUserClientWrapper(conn *grpc.ClientConn) *UserClientWrapper {
usersClient: pbusers.NewUsersClient(conn),
}
}

func (w *UserClientWrapper) GetUserNameForID(
ctx context.Context,
userID int64,
authCredentials string,
) (string, error) {
md := metadata.Pairs("Authorization", authCredentials)
ctx = metadata.NewOutgoingContext(ctx, md)

req := &pbusers.GetUserNameByIDRequest{UserID: userID}

resp, err := w.usersClient.GetUserNameByID(ctx, req)

if err != nil {
return "", err
}

return resp.Username, nil
}

type HealthClientWrapper struct {
healthClient pbhealth.HealthTrackingClient
}

func NewHealthClientWrapper(conn *grpc.ClientConn) *HealthClientWrapper {
return &HealthClientWrapper{
healthClient: pbhealth.NewHealthTrackingClient(conn),
}
}

func (h *HealthClientWrapper) GetMentalHealthScoreForUser(
ctx context.Context,
userID int64,
authCredentials string,
) (int32, error) {

md := metadata.Pairs("Authorization", authCredentials)
ctx = metadata.NewOutgoingContext(ctx, md)

req := &pbhealth.GetMentalHealthScoreForUserRequest{UserID: userID}

resp, err := h.healthClient.GetMentalHealthScoreForUser(ctx, req)

if err != nil {
return 0, err
}

return resp.Score, nil
}
127 changes: 124 additions & 3 deletions internal/server/feedGenerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@ package server

import (
"context"
"errors"
pbcommon "github.com/kic/feed/pkg/proto/common"
"go.uber.org/zap"
"math/rand"
"sort"
"strconv"
"time"
)

const (
healthThreshold = -10
)

type FeedGenerator struct {
Expand All @@ -12,19 +21,22 @@ type FeedGenerator struct {
friendsClient FriendServicer
usersClient UserServicer
mediaClient MediaServicer
healthClient HealthServicer
}

func NewFeedGenerator(
logger *zap.SugaredLogger,
friendsClient FriendServicer,
usersClient UserServicer,
mediaClient MediaServicer,
healthClient HealthServicer,
) *FeedGenerator {
return &FeedGenerator{
logger: logger,
friendsClient: friendsClient,
usersClient: usersClient,
mediaClient: mediaClient,
healthClient: healthClient,
}
}

Expand All @@ -44,11 +56,30 @@ func (f *FeedGenerator) getFilesForFriend(
userID int64,
authCredentials string,
) ([]*pbcommon.File, error) {
return nil, nil
files, err := f.mediaClient.GetFilesForUser(ctx, userID, authCredentials)

if err != nil {
f.logger.Errorf("Failed to get files for user: %v", err)
return nil, err
}

return files, nil
}

func (f *FeedGenerator) getUserNameForID(ctx context.Context, userID int64, authCredentials string) (string, error) {
return "", nil
username, err := f.usersClient.GetUserNameForID(ctx, userID, authCredentials)

if err != nil {
f.logger.Errorf("Failed to get username for user: %v", err)
return "", err
}

if username == "" {
f.logger.Errorf("Username blank for user: %v", err)
return "", errors.New("empty username for friend")
}

return username, nil
}

func (f *FeedGenerator) rankAndSortPosts(
Expand All @@ -57,6 +88,87 @@ func (f *FeedGenerator) rankAndSortPosts(
authCredentials string,
posts []*pbcommon.File,
) error {
// fisher-yates shuffle the array to try and break up posts by a given user
for i := range posts {
j := rand.Intn(i + 1)
posts[i], posts[j] = posts[j], posts[i]
}

// we want chronological order to be the principle ordering, so the first index will be the most recent post
sort.SliceStable(posts, func(i, j int) bool {
if posts[i].DateStored.Year > posts[j].DateStored.Year {
return true
}
if posts[i].DateStored.Month > posts[j].DateStored.Month {
return true
}
if posts[i].DateStored.Day > posts[j].DateStored.Day {
return true
}
return false
})

// since we do not distinguish between times in a given day, we sort today further by friend strength
dateToday := time.Now()

day := dateToday.Day()

endFriendIndex := 0

for {
if int(posts[endFriendIndex].DateStored.Day) != day {
break
}
endFriendIndex += 1
}

// we have multiple posts for today, do the sort
if endFriendIndex > 2 {
f.logger.Debug("Sorting by connection strength")
todaySlice := posts[:endFriendIndex+1]
strengthMap := make(map[int]float32)

for i := 0; i < endFriendIndex; i++ {
uid, err := strconv.Atoi(todaySlice[i].Metadata["userID"])
if err != nil {
return err
}
if _, ok := strengthMap[uid]; !ok {
strength, err := f.friendsClient.GetConnectionBetweenUsers(ctx, userID, int64(uid), authCredentials)
if err != nil {
return err
}
strengthMap[uid] = strength
}
}

sort.SliceStable(todaySlice, func(i, j int) bool {
user1ID, _ := strconv.Atoi(todaySlice[i].Metadata["userID"])
user2ID, _ := strconv.Atoi(todaySlice[j].Metadata["userID"])
return strengthMap[user1ID] < strengthMap[user2ID]
})
}

return nil
}

func (f *FeedGenerator) injectMentalHealthPosts(
ctx context.Context,
userID int64,
authCredentials string,
posts []*pbcommon.File,
) error {
score, err := f.healthClient.GetMentalHealthScoreForUser(ctx, userID, authCredentials)

if err != nil {
f.logger.Errorf("Failed to get health score for user: %v", err)
return err
}

if score < healthThreshold {
// inject posts here
}

return nil
}

Expand All @@ -79,7 +191,7 @@ func (f *FeedGenerator) GenerateFeedForUser(
userName, err := f.getUserNameForID(ctx, int64(friendID), authCredentials)
if err != nil {
f.logger.Debugf("Failed to get username for uid %v, err: %v", friendID, err)
return nil, err
continue
}

files, err := f.getFilesForFriend(ctx, int64(friendID), authCredentials)
Expand All @@ -104,5 +216,14 @@ func (f *FeedGenerator) GenerateFeedForUser(
return nil, err
}

// Finally inject mental health posts if needed

err = f.injectMentalHealthPosts(ctx, userID, authCredentials, allPosts)

if err != nil {
f.logger.Debugf("Failed to inject posts for uid %v, err: %v", userID, err)
return nil, err
}

return allPosts, nil
}
11 changes: 10 additions & 1 deletion internal/server/interfaces.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
package server

import "context"
import (
"context"
pbcommon "github.com/kic/feed/pkg/proto/common"
)

type FriendServicer interface {
GetFriendsForUser(context.Context, int64, string) ([]uint64, error)
GetConnectionBetweenUsers(context.Context, int64, int64, string) (float32, error)
}

type UserServicer interface {
GetUserNameForID(ctx context.Context, userID int64, authCredentials string) (string, error)
}

type MediaServicer interface {
GetFilesForUser(ctx context.Context, userID int64, authCredentials string) ([]*pbcommon.File, error)
}

type HealthServicer interface {
GetMentalHealthScoreForUser(ctx context.Context, userID int64, authCredentials string) (int32, error)
}
Loading

0 comments on commit 9324da0

Please sign in to comment.