diff --git a/aid/syncer.go b/aid/syncer.go
index 94ec55a..f5cae4f 100644
--- a/aid/syncer.go
+++ b/aid/syncer.go
@@ -21,6 +21,15 @@ func (s *GenericSyncMap[T]) Get(key string) (*T, bool) {
return v.(*T), true
}
+func (s *GenericSyncMap[T]) MustGet(key string) *T {
+ v, ok := s.m.Load(key)
+ if !ok {
+ return nil
+ }
+
+ return v.(*T)
+}
+
func (s *GenericSyncMap[T]) Delete(key string) {
s.m.Delete(key)
}
@@ -29,4 +38,9 @@ func (s *GenericSyncMap[T]) Range(f func(key string, value *T) bool) {
s.m.Range(func(key, value interface{}) bool {
return f(key.(string), value.(*T))
})
+}
+
+func (s *GenericSyncMap[T]) Has(key string) bool {
+ _, ok := s.Get(key)
+ return ok
}
\ No newline at end of file
diff --git a/handlers/friends.go b/handlers/friends.go
index 7ec9b8a..afacbde 100644
--- a/handlers/friends.go
+++ b/handlers/friends.go
@@ -1,15 +1,83 @@
package handlers
import (
+ "fmt"
+ "time"
+
"github.com/ectrc/snow/aid"
+ p "github.com/ectrc/snow/person"
+ "github.com/ectrc/snow/socket"
"github.com/gofiber/fiber/v2"
)
func GetFriendList(c *fiber.Ctx) error {
- return c.Status(200).JSON([]aid.JSON{})
+ person := c.Locals("person").(*p.Person)
+ result := []aid.JSON{}
+
+ person.IncomingRelationships.Range(func(key string, value *p.Relationship[p.RelationshipInboundDirection]) bool {
+ result = append(result, value.GenerateFortniteFriendEntry())
+ return true
+ })
+
+ person.OutgoingRelationships.Range(func(key string, value *p.Relationship[p.RelationshipOutboundDirection]) bool {
+ result = append(result, value.GenerateFortniteFriendEntry())
+ return true
+ })
+
+ return c.Status(200).JSON(result)
}
func PostCreateFriend(c *fiber.Ctx) error {
+ person := c.Locals("person").(*p.Person)
+ wanted := c.Params("wanted")
+
+ direction, err := person.CreateRelationship(wanted)
+ if err != nil {
+ aid.Print(fmt.Sprintf("Error creating relationship: %s", err.Error()))
+ return c.Status(400).JSON(aid.ErrorBadRequest(err.Error()))
+ }
+
+ socket.JabberSockets.Range(func(key string, value *socket.Socket[socket.JabberData]) bool {
+ aid.Print(fmt.Sprintf("Checking socket: %s", key, value))
+ return true
+ })
+
+ personSocket, found := socket.GetJabberSocketByPersonID(wanted)
+ aid.Print(fmt.Sprintf("Found socket: %t", found), fmt.Sprintf("Direction: %s", direction))
+ if found {
+ payload := aid.JSON{}
+ switch direction {
+ case "INBOUND":
+ payload = personSocket.Person.IncomingRelationships.MustGet(wanted).GenerateFortniteFriendEntry()
+ case "OUTBOUND":
+ payload = personSocket.Person.OutgoingRelationships.MustGet(wanted).GenerateFortniteFriendEntry()
+ }
+
+ personSocket.JabberSendMessageToPerson(aid.JSON{
+ "type": "com.epicgames.friends.core.apiobjects.Friend",
+ "timestamp": time.Now().Format(time.RFC3339),
+ "payload": payload,
+ })
+ }
+
+ friendSocket, found := socket.GetJabberSocketByPersonID(wanted)
+ aid.Print(fmt.Sprintf("Found friend socket: %t", found), fmt.Sprintf("Direction: %s", direction))
+ if found {
+ payload := aid.JSON{}
+ switch direction {
+ case "INBOUND":
+ payload = friendSocket.Person.OutgoingRelationships.MustGet(person.ID).GenerateFortniteFriendEntry()
+ case "OUTBOUND":
+ payload = friendSocket.Person.IncomingRelationships.MustGet(person.ID).GenerateFortniteFriendEntry()
+ }
+
+ friendSocket.JabberSendMessageToPerson(aid.JSON{
+ "type": "com.epicgames.friends.core.apiobjects.Friend",
+ "timestamp": time.Now().Format(time.RFC3339),
+ "payload": payload,
+ })
+ }
+
return c.SendStatus(204)
}
diff --git a/handlers/socket.go b/handlers/socket.go
index c81ec47..8efda12 100644
--- a/handlers/socket.go
+++ b/handlers/socket.go
@@ -13,19 +13,16 @@ func MiddlewareWebsocket(c *fiber.Ctx) error {
return fiber.ErrUpgradeRequired
}
- var identifier string
var protocol string
switch c.Get("Sec-WebSocket-Protocol") {
case "xmpp":
- identifier = c.Get("Sec-WebSocket-Key")
protocol = "jabber"
default:
protocol = "matchmaking"
- identifier = uuid.New().String()
}
- c.Locals("identifier", identifier)
+ c.Locals("identifier", uuid.New().String())
c.Locals("protocol", protocol)
return c.Next()
@@ -44,4 +41,23 @@ func WebsocketConnection(c *websocket.Conn) {
default:
aid.Print("Invalid protocol: " + protocol)
}
+}
+
+func GetConnectedSockets(c *fiber.Ctx) error {
+ jabber := []socket.Socket[socket.JabberData]{}
+ socket.JabberSockets.Range(func(key string, value *socket.Socket[socket.JabberData]) bool {
+ jabber = append(jabber, *value)
+ return true
+ })
+
+ matchmaking := []socket.Socket[socket.MatchmakerData]{}
+ socket.MatchmakerSockets.Range(func(key string, value *socket.Socket[socket.MatchmakerData]) bool {
+ matchmaking = append(matchmaking, *value)
+ return true
+ })
+
+ return c.Status(200).JSON(aid.JSON{
+ "jabber": jabber,
+ "matchmaking": matchmaking,
+ })
}
\ No newline at end of file
diff --git a/main.go b/main.go
index 23c3a05..05bb62c 100644
--- a/main.go
+++ b/main.go
@@ -129,6 +129,7 @@ func main() {
lightswitch.Get("/service/bulk/status", handlers.GetLightswitchBulkStatus)
snow := r.Group("/snow")
+ snow.Get("/sockets", handlers.GetConnectedSockets)
snow.Get("/cosmetics", handlers.GetPreloadedCosmetics)
snow.Get("/image/:playlist", handlers.GetPlaylistImage)
diff --git a/person/person.go b/person/person.go
index da4ec6e..e41b06b 100644
--- a/person/person.go
+++ b/person/person.go
@@ -1,6 +1,7 @@
package person
import (
+ "github.com/ectrc/snow/aid"
"github.com/ectrc/snow/storage"
"github.com/google/uuid"
)
@@ -17,6 +18,8 @@ type Person struct {
CollectionsProfile *Profile
CreativeProfile *Profile
Discord *storage.DB_DiscordPerson
+ OutgoingRelationships aid.GenericSyncMap[Relationship[RelationshipOutboundDirection]]
+ IncomingRelationships aid.GenericSyncMap[Relationship[RelationshipInboundDirection]]
}
func NewPerson() *Person {
@@ -64,7 +67,25 @@ func Find(personId string) *Person {
return nil
}
- return findHelper(person)
+ return findHelper(person, false, true)
+}
+
+func FindShallow(personId string) *Person {
+ if cache == nil {
+ cache = NewPersonsCacheMutex()
+ }
+
+ cachedPerson := cache.GetPerson(personId)
+ if cachedPerson != nil {
+ return cachedPerson
+ }
+
+ person := storage.Repo.GetPersonFromDB(personId)
+ if person == nil {
+ return nil
+ }
+
+ return findHelper(person, true, true)
}
func FindByDisplay(displayName string) *Person {
@@ -82,7 +103,25 @@ func FindByDisplay(displayName string) *Person {
return nil
}
- return findHelper(person)
+ return findHelper(person, false, true)
+}
+
+func FindByDisplayShallow(displayName string) *Person {
+ if cache == nil {
+ cache = NewPersonsCacheMutex()
+ }
+
+ cachedPerson := cache.GetPersonByDisplay(displayName)
+ if cachedPerson != nil {
+ return cachedPerson
+ }
+
+ person := storage.Repo.GetPersonByDisplayFromDB(displayName)
+ if person == nil {
+ return nil
+ }
+
+ return findHelper(person, true, true)
}
func FindByDiscord(discordId string) *Person {
@@ -100,10 +139,10 @@ func FindByDiscord(discordId string) *Person {
return nil
}
- return findHelper(person)
+ return findHelper(person, false, true)
}
-func findHelper(databasePerson *storage.DB_Person) *Person {
+func findHelper(databasePerson *storage.DB_Person, shallow bool, save bool) *Person {
athenaProfile := NewProfile("athena")
commonCoreProfile := NewProfile("common_core")
commonPublicProfile := NewProfile("common_public")
@@ -155,9 +194,17 @@ func findHelper(databasePerson *storage.DB_Person) *Person {
CollectionsProfile: collectionsProfile,
CreativeProfile: creativeProfile,
Discord: &databasePerson.Discord,
+ OutgoingRelationships: aid.GenericSyncMap[Relationship[RelationshipOutboundDirection]]{},
+ IncomingRelationships: aid.GenericSyncMap[Relationship[RelationshipInboundDirection]]{},
}
- cache.SavePerson(person)
+ if !shallow {
+ person.LoadRelationships()
+ }
+
+ if save {
+ cache.SavePerson(person)
+ }
return person
}
diff --git a/person/relationships.go b/person/relationships.go
new file mode 100644
index 0000000..fccaba3
--- /dev/null
+++ b/person/relationships.go
@@ -0,0 +1,141 @@
+package person
+
+import (
+ "fmt"
+
+ "github.com/ectrc/snow/aid"
+ "github.com/ectrc/snow/storage"
+)
+
+type RelationshipDirection string
+
+type RelationshipInboundDirection RelationshipDirection
+const RelationshipInboundDirectionValue RelationshipInboundDirection = "INBOUND"
+
+type RelationshipOutboundDirection RelationshipDirection
+const RelationshipOutboundDirectionValue RelationshipOutboundDirection = "OUTBOUND"
+
+type Relationship[T RelationshipInboundDirection | RelationshipOutboundDirection] struct {
+ Me *Person
+ Towards *Person
+ Status string
+ Direction T
+}
+
+func (r *Relationship[T]) ToDatabase() *storage.DB_Relationship {
+ return &storage.DB_Relationship{
+ IncomingPersonID: r.Me.ID,
+ OutgoingPersonID: r.Towards.ID,
+ Status: r.Status,
+ }
+}
+
+func (r *Relationship[T]) GenerateFortniteFriendEntry() aid.JSON {
+ return aid.JSON{
+ "accountId": r.Towards.ID,
+ "status": r.Status,
+ "direction": string(r.Direction),
+ "created": "0000-00-00T00:00:00.000Z",
+ "favorite": false,
+ }
+}
+
+func (r *Relationship[T]) Save() {
+ storage.Repo.Storage.SaveRelationship(r.ToDatabase())
+}
+
+func (r *Relationship[T]) Delete() {
+ storage.Repo.Storage.DeleteRelationship(r.ToDatabase())
+}
+
+func (p *Person) LoadRelationships() {
+ incoming := storage.Repo.Storage.GetIncomingRelationships(p.ID)
+ for _, entry := range incoming {
+ relationship := &Relationship[RelationshipInboundDirection]{
+ Status: entry.Status,
+ Me: p,
+ Towards: FindShallow(entry.OutgoingPersonID),
+ Direction: RelationshipInboundDirectionValue,
+ }
+
+ p.IncomingRelationships.Set(entry.OutgoingPersonID, relationship)
+ }
+
+ outgoing := storage.Repo.Storage.GetOutgoingRelationships(p.ID)
+ for _, entry := range outgoing {
+ relationship := &Relationship[RelationshipOutboundDirection]{
+ Status: entry.Status,
+ Me: p,
+ Towards: FindShallow(entry.IncomingPersonID),
+ Direction: RelationshipOutboundDirectionValue,
+ }
+
+ p.OutgoingRelationships.Set(entry.IncomingPersonID, relationship)
+ }
+}
+
+func (p *Person) CreateRelationship(personId string) (string, error) {
+ if p.ID == personId {
+ return "", fmt.Errorf("cannot create relationship with yourself")
+ }
+
+ if p.IncomingRelationships.Has(personId) {
+ return "INBOUND", p.createAcceptInboundRelationship(personId)
+ }
+
+ return "OUTBOUND", p.createOutboundRelationship(personId)
+}
+
+func (p *Person) createOutboundRelationship(towards string) error {
+ towardsPerson := Find(towards)
+ if towardsPerson == nil {
+ return fmt.Errorf("person not found")
+ }
+
+ relationship := &Relationship[RelationshipOutboundDirection]{
+ Me: p,
+ Towards: towardsPerson,
+ Status: "PENDING",
+ Direction: RelationshipOutboundDirectionValue,
+ }
+ relationship.Save()
+ p.OutgoingRelationships.Set(towards, relationship)
+
+ tempRelationship := &Relationship[RelationshipInboundDirection]{
+ Me: towardsPerson,
+ Towards: p,
+ Status: "PENDING",
+ Direction: RelationshipInboundDirectionValue,
+ }
+ tempRelationship.Save()
+ towardsPerson.IncomingRelationships.Set(p.ID, tempRelationship)
+
+ return nil
+}
+
+func (p *Person) createAcceptInboundRelationship(towards string) error {
+ towardsPerson := Find(towards)
+ if towardsPerson == nil {
+ return fmt.Errorf("person not found")
+ }
+
+ relationship := &Relationship[RelationshipInboundDirection]{
+ Me: p,
+ Towards: towardsPerson,
+ Status: "ACCEPTED",
+ Direction: RelationshipInboundDirectionValue,
+ }
+ relationship.Save()
+ p.IncomingRelationships.Set(towards, relationship)
+
+ tempRelationship := &Relationship[RelationshipOutboundDirection]{
+ Me: towardsPerson,
+ Towards: p,
+ Status: "ACCEPTED",
+ Direction: RelationshipOutboundDirectionValue,
+ }
+ tempRelationship.Save()
+ towardsPerson.OutgoingRelationships.Set(p.ID, tempRelationship)
+
+ return nil
+}
\ No newline at end of file
diff --git a/socket/jabber.go b/socket/jabber.go
index 9fb01e8..c8f1759 100644
--- a/socket/jabber.go
+++ b/socket/jabber.go
@@ -2,6 +2,7 @@ package socket
import (
"fmt"
+ "reflect"
"github.com/beevik/etree"
"github.com/ectrc/snow/aid"
@@ -91,4 +92,39 @@ func jabberIqSetHandler(socket *Socket[JabberData], parsed *etree.Document) erro
func jabberIqGetHandler(socket *Socket[JabberData], parsed *etree.Document) error {
socket.Connection.WriteMessage(websocket.TextMessage, []byte(``))
return nil
+}
+
+func GetJabberSocketByPersonID(id string) (*Socket[JabberData], bool) {
+ var found *Socket[JabberData]
+
+ JabberSockets.Range(func(key string, socket *Socket[JabberData]) bool {
+ if socket.Person.ID == id {
+ found = socket
+ return false
+ }
+
+ return true
+ })
+
+ return found, found != nil
+}
+
+func (s *Socket[T]) JabberSendMessageToPerson(data aid.JSON) {
+ if reflect.TypeOf(s.Data) != reflect.TypeOf(&JabberData{}) {
+ return
+ }
+
+ jabberSocket, ok := JabberSockets.Get(s.ID)
+ if !ok {
+ aid.Print("jabber socket not found even though it should be")
+ return
+ }
+
+ aid.Print(`
+ `+ string(data.ToBytes()) +`
+ `)
+
+ s.Connection.WriteMessage(1, []byte(`
+ `+ string(data.ToBytes()) +`
+ `))
}
\ No newline at end of file
diff --git a/storage/postgres.go b/storage/postgres.go
index dd30de3..0846f1a 100644
--- a/storage/postgres.go
+++ b/storage/postgres.go
@@ -45,6 +45,7 @@ func (s *PostgresStorage) MigrateAll() {
s.Migrate(&DB_PAttribute{}, "Attributes")
s.Migrate(&DB_DiscordPerson{}, "Discords")
s.Migrate(&DB_SeasonStat{}, "Stats")
+ s.Migrate(&DB_Relationship{}, "Relationships")
}
func (s *PostgresStorage) DropTables() {
@@ -181,6 +182,26 @@ func (s *PostgresStorage) DeletePerson(personId string) {
Delete(&DB_Person{}, "id = ?", personId)
}
+func (s *PostgresStorage) GetIncomingRelationships(personId string) []*DB_Relationship {
+ var dbRelationships []*DB_Relationship
+ s.Postgres.Model(&DB_Relationship{}).Where("incoming_person_id = ?", personId).Find(&dbRelationships)
+ return dbRelationships
+}
+
+func (s *PostgresStorage) GetOutgoingRelationships(personId string) []*DB_Relationship {
+ var dbRelationships []*DB_Relationship
+ s.Postgres.Model(&DB_Relationship{}).Where("outgoing_person_id = ?", personId).Find(&dbRelationships)
+ return dbRelationships
+}
+
+func (s *PostgresStorage) SaveRelationship(relationship *DB_Relationship) {
+ s.Postgres.Save(relationship)
+}
+
+func (s *PostgresStorage) DeleteRelationship(relationship *DB_Relationship) {
+ s.Postgres.Delete(&DB_Relationship{}, "incoming_person_id = ? AND outgoing_person_id = ?", relationship.IncomingPersonID, relationship.OutgoingPersonID)
+}
+
func (s *PostgresStorage) SaveProfile(profile *DB_Profile) {
s.Postgres.Save(profile)
}
diff --git a/storage/storage.go b/storage/storage.go
index 4fe808a..caf1364 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -9,7 +9,6 @@ type Storage interface {
GetAllPersons() []*DB_Person
GetPersonsCount() int
-
TotalVBucks() int
GetPerson(personId string) *DB_Person
@@ -19,6 +18,11 @@ type Storage interface {
SavePerson(person *DB_Person)
DeletePerson(personId string)
+ GetIncomingRelationships(personId string) []*DB_Relationship
+ GetOutgoingRelationships(personId string) []*DB_Relationship
+ SaveRelationship(relationship *DB_Relationship)
+ DeleteRelationship(relationship *DB_Relationship)
+
SaveProfile(profile *DB_Profile)
DeleteProfile(profileId string)
@@ -122,6 +126,22 @@ func (r *Repository) DeleteProfile(profileId string) {
r.Storage.DeleteProfile(profileId)
}
+func (r *Repository) GetIncomingRelationships(personId string) []*DB_Relationship {
+ return r.Storage.GetIncomingRelationships(personId)
+}
+
+func (r *Repository) GetOutgoingRelationships(personId string) []*DB_Relationship {
+ return r.Storage.GetOutgoingRelationships(personId)
+}
+
+func (r *Repository) SaveRelationship(relationship *DB_Relationship) {
+ r.Storage.SaveRelationship(relationship)
+}
+
+func (r *Repository) DeleteRelationship(relationship *DB_Relationship) {
+ r.Storage.DeleteRelationship(relationship)
+}
+
func (r *Repository) SaveItem(item *DB_Item) {
r.Storage.SaveItem(item)
}
diff --git a/storage/tables.go b/storage/tables.go
index 4793e32..b048df3 100644
--- a/storage/tables.go
+++ b/storage/tables.go
@@ -20,6 +20,16 @@ func (DB_Person) TableName() string {
return "Persons"
}
+type DB_Relationship struct {
+ OutgoingPersonID string `gorm:"primary_key"`
+ IncomingPersonID string `gorm:"primary_key"`
+ Status string
+}
+
+func (DB_Relationship) TableName() string {
+ return "Relationships"
+}
+
type DB_Profile struct {
ID string `gorm:"primary_key"`
PersonID string