diff --git a/handlers/friends.go b/handlers/friends.go new file mode 100644 index 0000000..2f5c91a --- /dev/null +++ b/handlers/friends.go @@ -0,0 +1,124 @@ +package handlers + +import ( + "github.com/ectrc/snow/aid" + p "github.com/ectrc/snow/person" + "github.com/ectrc/snow/storage" + "github.com/gofiber/fiber/v2" +) + +func GetFriendList(c *fiber.Ctx) error { + person := c.Locals("person").(*p.Person) + result := map[string]aid.JSON{} + + for _, partial := range storage.Repo.GetFriendsForPerson(person.ID) { + friend := person.GetFriend(partial.ID) + if friend == nil { + continue + } + + result[partial.ID] = friend.GenerateFriendResponse() + } + + response := []aid.JSON{} + for _, friend := range result { + response = append(response, friend) + } + + return c.Status(200).JSON(response) +} + +func PostCreateFriend(c *fiber.Ctx) error { + person := c.Locals("person").(*p.Person) + wanted := c.Params("wanted") + + existing := person.GetFriend(wanted) + if existing != nil && (existing.Direction == "BOTH" || existing.Direction == "OUTGOING") { + return c.Status(400).JSON(aid.ErrorBadRequest("already active friend request")) + } + + person.AddFriend(wanted) + // send xmpp message to disply a popup + + return c.SendStatus(204) +} + +func DeleteFriend(c *fiber.Ctx) error { + person := c.Locals("person").(*p.Person) + wanted := c.Params("wanted") + + existing := person.GetFriend(wanted) + if existing == nil { + return c.Status(400).JSON(aid.ErrorBadRequest("not friends")) + } + + existing.Person.RemoveFriend(wanted) + person.RemoveFriend(wanted) + // send xmpp message to disply a popup + + return c.SendStatus(204) +} + +func GetFriendListSummary(c *fiber.Ctx) error { + person := c.Locals("person").(*p.Person) + + all := map[string]*p.Friend{} + for _, partial := range storage.Repo.GetFriendsForPerson(person.ID) { + friend := person.GetFriend(partial.ID) + if friend == nil { + continue + } + + all[partial.ID] = friend + } + + result := aid.JSON{ + "friends": []aid.JSON{}, + "incoming": []aid.JSON{}, + "outgoing": []aid.JSON{}, + "settings": aid.JSON{ + "acceptInvites": "public", + }, + } + + for _, friend := range all { + switch friend.Status { + case "ACCEPTED": + result["friends"] = append(result["friends"].([]aid.JSON), friend.GenerateSummaryResponse()) + case "PENDING": + switch friend.Direction { + case "INCOMING": + result["incoming"] = append(result["incoming"].([]aid.JSON), friend.GenerateSummaryResponse()) + case "OUTGOING": + result["outgoing"] = append(result["outgoing"].([]aid.JSON), friend.GenerateSummaryResponse()) + } + } + } + + return c.Status(200).JSON(result) +} + +func GetPersonSearch(c *fiber.Ctx) error { + query := c.Query("prefix") + + matches := storage.Repo.GetPersonsByPartialDisplayFromDB(query) + if matches == nil { + return c.Status(200).JSON([]aid.JSON{}) + } + + result := []aid.JSON{} + for i, match := range matches { + result = append(result, aid.JSON{ + "accountId": match.ID, + "epicMutuals": 0, + "sortPosition": i, + "matchType": "prefix", + "matches": []aid.JSON{{ + "value": match.DisplayName, + "matchType": "prefix", + }}, + }) + } + + return c.Status(200).JSON(result) +} \ No newline at end of file diff --git a/main.go b/main.go index 56d7744..b7fcba3 100644 --- a/main.go +++ b/main.go @@ -45,7 +45,8 @@ func init() { fortnite.GeneratePlaylistImages() if found := person.FindByDisplay("god"); found == nil { - fortnite.NewFortnitePerson("god", true) + god := fortnite.NewFortnitePerson("god", true) + god.AddPermission("all") } } func main() { @@ -63,6 +64,7 @@ func main() { r.Get("/waitingroom/api/waitingroom", handlers.GetWaitingRoomStatus) r.Get("/region", handlers.GetRegion) r.Put("/profile/play_region", handlers.AnyNoContent) + r.Get("/api/v1/search/:accountId", handlers.GetPersonSearch) r.Post("/api/v1/assets/Fortnite/:versionId/:assetName", handlers.PostAssets) account := r.Group("/account/api") @@ -98,6 +100,14 @@ func main() { user.Get("/:accountId/:fileName", handlers.GetUserStorageFile) user.Put("/:accountId/:fileName", handlers.PutUserStorageFile) + friends := r.Group("/friends/api") + friends.Use(handlers.MiddlewareFortnite) + friends.Get("/public/friends/:accountId", handlers.GetFriendList) + friends.Post("/public/friends/:accountId/:wanted", handlers.PostCreateFriend) + friends.Delete("/public/friends/:accountId/:wanted", handlers.DeleteFriend) + friends.Get("/:version/:accountId/summary", handlers.GetFriendListSummary) + friends.Get("/:version/:accountId/friends/:wanted", handlers.PostCreateFriend) + game := fortnite.Group("/game/v2") game.Get("/enabled_features", handlers.GetGameEnabledFeatures) game.Post("/tryPlayOnPlatform/account/:accountId", handlers.PostGamePlatform) diff --git a/person/friend.go b/person/friend.go new file mode 100644 index 0000000..c6d9ac1 --- /dev/null +++ b/person/friend.go @@ -0,0 +1,35 @@ +package person + +import ( + "time" + + "github.com/ectrc/snow/aid" +) + +type Friend struct { + Person *Person + Status string + Direction string +} + +func (f *Friend) GenerateSummaryResponse() aid.JSON { + return aid.JSON{ + "accountId": f.Person.ID, + "groups": []string{}, + "mutual": 0, + "alias": "", + "note": "", + "favorite": false, + "created": time.Now().Add(-time.Hour * 24 * 7).Format(time.RFC3339), + } +} + +func (f *Friend) GenerateFriendResponse() aid.JSON { + return aid.JSON{ + "accountId": f.Person.ID, + "status": f.Status, + "direction": f.Direction, + "created": time.Now().Add(-time.Hour * 24 * 7).Format(time.RFC3339), + "favourite": false, + } +} \ No newline at end of file diff --git a/person/person.go b/person/person.go index fe342f8..6120192 100644 --- a/person/person.go +++ b/person/person.go @@ -10,6 +10,7 @@ type Person struct { DisplayName string Permissions []string IsBanned bool + Friends []string AthenaProfile *Profile CommonCoreProfile *Profile CommonPublicProfile *Profile @@ -19,17 +20,29 @@ type Person struct { Discord *storage.DB_DiscordPerson } -type Option struct { - Key string - Value string -} - func NewPerson() *Person { return &Person{ ID: uuid.New().String(), DisplayName: uuid.New().String(), Permissions: []string{}, IsBanned: false, + Friends: []string{}, + AthenaProfile: NewProfile("athena"), + CommonCoreProfile: NewProfile("common_core"), + CommonPublicProfile: NewProfile("common_public"), + Profile0Profile: NewProfile("profile0"), + CollectionsProfile: NewProfile("collections"), + CreativeProfile: NewProfile("creative"), + } +} + +func NewPersonWithCustomID(id string) *Person { + return &Person{ + ID: id, + DisplayName: uuid.New().String(), + Permissions: []string{}, + IsBanned: false, + Friends: []string{}, AthenaProfile: NewProfile("athena"), CommonCoreProfile: NewProfile("common_core"), CommonPublicProfile: NewProfile("common_public"), @@ -138,6 +151,7 @@ func findHelper(databasePerson *storage.DB_Person) *Person { DisplayName: databasePerson.DisplayName, Permissions: databasePerson.Permissions, IsBanned: databasePerson.IsBanned, + Friends: databasePerson.Friends, AthenaProfile: athenaProfile, CommonCoreProfile: commonCoreProfile, CommonPublicProfile: commonPublicProfile, @@ -237,12 +251,71 @@ func (p *Person) HasPermission(permission Permission) bool { return false } +func (p *Person) AddFriend(friendId string) { + p.Friends = append(p.Friends, friendId) + p.Save() +} + +func (p *Person) RemoveFriend(friendId string) { + for i, friend := range p.Friends { + if friend == friendId { + p.Friends = append(p.Friends[:i], p.Friends[i+1:]...) + break + } + } + p.Save() +} + +func (p *Person) GetFriend(friendId string) *Friend { + friend := Find(friendId) + if friend == nil { + return nil + } + + if p.IsFriendInFriendList(friendId) { + if friend.IsFriendInFriendList(p.ID) { + return &Friend{ + Person: friend, + Status: "ACCEPTED", + Direction: "BOTH", + } + } + + return &Friend{ + Person: friend, + Status: "PENDING", + Direction: "OUTBOUND", + } + } + + if friend.IsFriendInFriendList(p.ID) { + return &Friend{ + Person: friend, + Status: "PENDING", + Direction: "INBOUND", + } + } + + return nil +} + +func (p *Person) IsFriendInFriendList(friendId string) bool { + for _, idA := range p.Friends { + if idA == friendId { + return true + } + } + + return false +} + func (p *Person) ToDatabase() *storage.DB_Person { dbPerson := storage.DB_Person{ ID: p.ID, DisplayName: p.DisplayName, Permissions: p.Permissions, IsBanned: p.IsBanned, + Friends: p.Friends, Profiles: []storage.DB_Profile{}, Stats: []storage.DB_SeasonStat{}, Discord: storage.DB_DiscordPerson{}, diff --git a/storage/postgres.go b/storage/postgres.go index 52f1527..ca81fdf 100644 --- a/storage/postgres.go +++ b/storage/postgres.go @@ -97,6 +97,29 @@ func (s *PostgresStorage) GetPersonByDisplay(displayName string) *DB_Person { return &dbPerson } +func (s *PostgresStorage) GetPersonsByPartialDisplay(displayName string) []*DB_Person { + var dbPersons []*DB_Person + s.Postgres. + Model(&DB_Person{}). + Preload("Profiles"). + Preload("Profiles.Loadouts"). + Preload("Profiles.Items.Variants"). + Preload("Profiles.Gifts.Loot"). + Preload("Profiles.Attributes"). + Preload("Profiles.Items"). + Preload("Profiles.Gifts"). + Preload("Profiles.Quests"). + Preload("Discord"). + Where("display_name LIKE ?", "%" + displayName + "%"). + Find(&dbPersons) + + if len(dbPersons) == 0 { + return nil + } + + return dbPersons +} + func (s *PostgresStorage) GetPersonByDiscordID(discordId string) *DB_Person { var discordEntry DB_DiscordPerson s.Postgres.Model(&DB_DiscordPerson{}).Where("id = ?", discordId).Find(&discordEntry) @@ -133,6 +156,42 @@ func (s *PostgresStorage) GetPersonsCount() int { return int(count) } +func (s *PostgresStorage) GetFriendsForPerson(personId string) []*DB_Person { + person := s.GetPerson(personId) + + var mine []*DB_Person + s.Postgres. + Model(&DB_Person{}). + Preload("Profiles"). + Preload("Profiles.Loadouts"). + Preload("Profiles.Items.Variants"). + Preload("Profiles.Gifts.Loot"). + Preload("Profiles.Attributes"). + Preload("Profiles.Items"). + Preload("Profiles.Gifts"). + Preload("Profiles.Quests"). + Preload("Discord"). + Where("id IN (?)", person.Friends). + Find(&mine) + + var theirs []*DB_Person + s.Postgres. + Model(&DB_Person{}). + Preload("Profiles"). + Preload("Profiles.Loadouts"). + Preload("Profiles.Items.Variants"). + Preload("Profiles.Gifts.Loot"). + Preload("Profiles.Attributes"). + Preload("Profiles.Items"). + Preload("Profiles.Gifts"). + Preload("Profiles.Quests"). + Preload("Discord"). + Where("? = ?", person.ID, gorm.Expr("ANY(friends)")). + Find(&theirs) + + return append(mine, theirs...) +} + func (s *PostgresStorage) TotalVBucks() int { var total int64 s.Postgres.Model(&DB_Item{}).Select("sum(quantity)").Where("template_id = ?", "Currency:MtxPurchased").Find(&total) diff --git a/storage/storage.go b/storage/storage.go index cadc021..35845f9 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -10,13 +10,16 @@ type Storage interface { GetAllPersons() []*DB_Person GetPersonsCount() int + TotalVBucks() int + GetPerson(personId string) *DB_Person GetPersonByDisplay(displayName string) *DB_Person + GetPersonsByPartialDisplay(displayName string) []*DB_Person GetPersonByDiscordID(discordId string) *DB_Person SavePerson(person *DB_Person) DeletePerson(personId string) - TotalVBucks() int + GetFriendsForPerson(personId string) []*DB_Person SaveProfile(profile *DB_Profile) DeleteProfile(profileId string) @@ -75,6 +78,15 @@ func (r *Repository) GetPersonByDisplayFromDB(displayName string) *DB_Person { return nil } +func (r *Repository) GetPersonsByPartialDisplayFromDB(displayName string) []*DB_Person { + storagePerson := r.Storage.GetPersonsByPartialDisplay(displayName) + if storagePerson != nil { + return storagePerson + } + + return nil +} + func (r *Repository) GetPersonByDiscordIDFromDB(discordId string) *DB_Person { storagePerson := r.Storage.GetPersonByDiscordID(discordId) if storagePerson != nil { @@ -84,6 +96,10 @@ func (r *Repository) GetPersonByDiscordIDFromDB(discordId string) *DB_Person { return nil } +func (r *Repository) TotalVBucks() int { + return r.Storage.TotalVBucks() +} + func (r *Repository) GetAllPersons() []*DB_Person { return r.Storage.GetAllPersons() } @@ -92,8 +108,8 @@ func (r *Repository) GetPersonsCount() int { return r.Storage.GetPersonsCount() } -func (r *Repository) TotalVBucks() int { - return r.Storage.TotalVBucks() +func (r *Repository) GetFriendsForPerson(personId string) []*DB_Person { + return r.Storage.GetFriendsForPerson(personId) } func (r *Repository) SavePerson(person *DB_Person) { diff --git a/storage/tables.go b/storage/tables.go index 4793e32..5a36229 100644 --- a/storage/tables.go +++ b/storage/tables.go @@ -13,6 +13,7 @@ type DB_Person struct { IsBanned bool Profiles []DB_Profile `gorm:"foreignkey:PersonID"` Stats []DB_SeasonStat `gorm:"foreignkey:PersonID"` + Friends pq.StringArray `gorm:"type:text[]"` Discord DB_DiscordPerson `gorm:"foreignkey:PersonID"` }