diff --git a/aid/aid.go b/aid/aid.go index 61580f5..1173183 100644 --- a/aid/aid.go +++ b/aid/aid.go @@ -1,6 +1,7 @@ package aid import ( + "encoding/json" "os" "os/signal" "syscall" @@ -10,4 +11,15 @@ func WaitForExit() { sc := make(chan os.Signal, 1) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) <-sc +} + +func JSONStringify(input interface{}) string { + json, _ := json.Marshal(input) + return string(json) +} + +func JSONParse(input string) interface{} { + var output interface{} + json.Unmarshal([]byte(input), &output) + return output } \ No newline at end of file diff --git a/aid/config.go b/aid/config.go index 97af250..7de6efb 100644 --- a/aid/config.go +++ b/aid/config.go @@ -2,6 +2,8 @@ package aid import ( "os" + "strconv" + "strings" "gopkg.in/ini.v1" ) @@ -22,6 +24,10 @@ type CS struct { JWT struct { Secret string } + Fortnite struct { + Season int + Build float64 + } } var ( @@ -74,4 +80,28 @@ func LoadConfig() { if Config.JWT.Secret == "" { panic("JWT Secret is empty") } + + build, err := cfg.Section("fortnite").Key("build").Float64() + if err != nil { + panic("Fortnite Build is empty") + } + + Config.Fortnite.Build = build + + buildStr := strconv.FormatFloat(build, 'f', -1, 64) + if buildStr == "" { + panic("Fortnite Build is empty") + } + + buildInfo := strings.Split(buildStr, ".") + if len(buildInfo) < 2 { + panic("Fortnite Build is invalid") + } + + parsedSeason, err := strconv.Atoi(buildInfo[0]) + if err != nil { + panic("Fortnite Season is invalid") + } + + Config.Fortnite.Season = parsedSeason } \ No newline at end of file diff --git a/aid/time.go b/aid/time.go new file mode 100644 index 0000000..c1bc8c4 --- /dev/null +++ b/aid/time.go @@ -0,0 +1,15 @@ +package aid + +import "time" + +func TimeStartOfDay() string { + return time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Now().Location()).Format("2006-01-02T15:04:05.999Z") +} + +func TimeEndOfDay() string { + return time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 23, 59, 59, 999999999, time.Now().Location()).Format("2006-01-02T15:04:05.999Z") +} + +func TimeEndOfWeekString() string { + return time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 23, 59, 59, 999999999, time.Now().Location()).AddDate(0, 0, 7).Format("2006-01-02T15:04:05.999Z") +} \ No newline at end of file diff --git a/default.config.ini b/default.config.ini index 0a402b2..0d4a91c 100644 --- a/default.config.ini +++ b/default.config.ini @@ -22,4 +22,8 @@ host="0.0.0.0" [jwt] ; secret for jwt signing -secret="secret" \ No newline at end of file +secret="secret" + +[fortnite] +; the game build to use +build=2.5 \ No newline at end of file diff --git a/handlers/auth.go b/handlers/auth.go index 2810a58..e3b6008 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -40,14 +40,14 @@ func PostOAuthToken(c *fiber.Ctx) error { func PostOAuthTokenClientCredentials(c *fiber.Ctx, body *OAuthTokenBody) error { credentials, err := aid.JWTSign(aid.JSON{ - "snow_id": 0, // custom + "snow_id": 0, // custom "t": "s", "am": "client_credentials", // authorization method - "ic": true, // internal client - "mver": false, // mobile version - "clsvc": "snow", // client service - "clid": c.IP(), // client id - "jti": rand.Int63(), // jwt id + "ic": true, // internal client + "mver": false, // mobile version + "clsvc": "snow", // client service + "clid": c.IP(), // client id + "jti": rand.Int63(), // jwt id "p": base64.StdEncoding.EncodeToString([]byte(c.IP())), // payload "hours_expire": 1, "creation_date": time.Now().Format("2006-01-02T15:04:05.999Z"), @@ -86,20 +86,20 @@ func PostOAuthTokenPassword(c *fiber.Ctx, body *OAuthTokenBody) error { } access, err := aid.JWTSign(aid.JSON{ - "snow_id": person.ID, // custom - "iai": person.ID, // account id - "dn": person.DisplayName, // display name + "snow_id": person.ID, // custom + "iai": person.ID, // account id + "dn": person.DisplayName, // display name "t": "s", - "am": "password", // authorization method - "ic": true, // internal client - "mver": false, // mobile version - "clsvc": "snow", // client service - "app": "com.epicgames.fortnite", // app name - "clid": c.IP(), // client id - "dvid": "default", // device id - "jti": rand.Int63(), // jwt id + "am": "password", // authorization method + "ic": true, // internal client + "mver": false, // mobile version + "clsvc": "snow", // client service + "app": "com.epicgames.fortnite", // app name + "clid": c.IP(), // client id + "dvid": "default", // device id + "jti": rand.Int63(), // jwt id "p": base64.StdEncoding.EncodeToString([]byte(c.IP())), // payload - "sec": 1, // security level + "sec": 1, // security level "hours_expire": 24, "creation_date": time.Now().Format("2006-01-02T15:04:05.999Z"), }) @@ -108,12 +108,12 @@ func PostOAuthTokenPassword(c *fiber.Ctx, body *OAuthTokenBody) error { } refresh, err := aid.JWTSign(aid.JSON{ - "snow_id": person.ID, // custom - "sub": person.ID, // account id - "clid": c.IP(), // client id - "jti": rand.Int63(), // jwt id + "snow_id": person.ID, // custom + "sub": person.ID, // account id + "clid": c.IP(), // client id + "jti": rand.Int63(), // jwt id "t": "s", - "am": "refresh_token", // authorization method + "am": "refresh_token", // authorization method "hours_expire": 24, "creation_date": time.Now().Format("2006-01-02T15:04:05.999Z"), }) diff --git a/handlers/common.go b/handlers/common.go index c493c50..fb19f8d 100644 --- a/handlers/common.go +++ b/handlers/common.go @@ -33,4 +33,46 @@ func GetVersionCheck(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(aid.JSON{ "type": "NO_UPDATE", }) +} + +func GetContentPages(c *fiber.Ctx) error { + return c.Status(fiber.StatusOK).JSON(aid.JSON{ + "battlepassaboutmessages": aid.JSON{ + "news": aid.JSON{ + "messages": []aid.JSON{}, + }, + "lastModified": "0000-00-00T00:00:00.000Z", + }, + "subgameselectdata": aid.JSON{ + "saveTheWorldUnowned": aid.JSON{ + "message": aid.JSON{ + "title": "Co-op PvE", + "body": "Cooperative PvE storm-fighting adventure!", + "spotlight": false, + "hidden": true, + "messagetype": "normal", + }, + }, + "battleRoyale": aid.JSON{ + "message": aid.JSON{ + "title": "100 Player PvP", + "body": "100 Player PvP Battle Royale.\n\nPvE progress does not affect Battle Royale.", + "spotlight": false, + "hidden": true, + "messagetype": "normal", + }, + }, + "creative": aid.JSON{ + "message": aid.JSON{ + "title": "New Featured Islands!", + "body": "Your Island. Your Friends. Your Rules.\n\nDiscover new ways to play Fortnite, play community made games with friends and build your dream island.", + "spotlight": false, + "hidden": true, + "messagetype": "normal", + }, + }, + "lastModified": "0000-00-00T00:00:00.000Z", + }, + "lastModified": "0000-00-00T00:00:00.000Z", + }) } \ No newline at end of file diff --git a/handlers/lightswitch.go b/handlers/lightswitch.go index 5c5a5f7..9362935 100644 --- a/handlers/lightswitch.go +++ b/handlers/lightswitch.go @@ -1,6 +1,9 @@ package handlers import ( + "strconv" + "time" + "github.com/ectrc/snow/aid" "github.com/gofiber/fiber/v2" ) @@ -11,4 +14,51 @@ func GetLightswitchBulkStatus(c *fiber.Ctx) error { "status": "UP", "banned": false, }}) +} + +func GetTimelineCalendar(c *fiber.Ctx) error { + events := []aid.JSON{ + { + "activeUntil": aid.TimeEndOfWeekString(), + "activeSince": "0001-01-01T00:00:00Z", + "activeEventId": "EventFlag.Season" + strconv.Itoa(aid.Config.Fortnite.Season), + }, + { + "activeUntil": aid.TimeEndOfWeekString(), + "activeSince": "0001-01-01T00:00:00Z", + "activeEventId": "EventFlag.LobbySeason" + strconv.Itoa(aid.Config.Fortnite.Season), + }, + } + + state := aid.JSON{ + "seasonNumber": aid.Config.Fortnite.Season, + "seasonTemplateId": "AthenaSeason:AthenaSeason" + strconv.Itoa(aid.Config.Fortnite.Season), + "seasonBegin": time.Now().Add(-time.Hour * 24 * 7).Format("2006-01-02T15:04:05.000Z"), + "seasonEnd": time.Now().Add(time.Hour * 24 * 7).Format("2006-01-02T15:04:05.000Z"), + "seasonDisplayedEnd": time.Now().Add(time.Hour * 24 * 7).Format("2006-01-02T15:04:05.000Z"), + "activeStorefronts": []aid.JSON{}, + "dailyStoreEnd": aid.TimeEndOfDay(), + "weeklyStoreEnd": aid.TimeEndOfWeekString(), + "sectionStoreEnds": aid.JSON{}, + "stwEventStoreEnd": aid.TimeEndOfWeekString(), + "stwWeeklyStoreEnd": aid.TimeEndOfWeekString(), + } + + client := aid.JSON{ + "states": []aid.JSON{{ + "activeEvents": events, + "state": state, + "validFrom": "0001-01-01T00:00:00Z", + }}, + "cacheExpire": "9999-12-31T23:59:59.999Z", + } + + return c.Status(fiber.StatusOK).JSON(aid.JSON{ + "channels": aid.JSON{ + "client-events": client, + }, + "currentTime": time.Now().Format("2006-01-02T15:04:05.000Z"), + "cacheIntervalMins": 5, + "eventsTimeOffsetHrs": 0, + }) } \ No newline at end of file diff --git a/handlers/profile.go b/handlers/profile.go index c348113..d1f16c9 100644 --- a/handlers/profile.go +++ b/handlers/profile.go @@ -1,6 +1,8 @@ package handlers import ( + "strconv" + "strings" "time" "github.com/ectrc/snow/aid" @@ -13,6 +15,8 @@ var ( profileActions = map[string]func(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { "QueryProfile": PostQueryProfileAction, "ClientQuestLogin": PostQueryProfileAction, + "MarkItemSeen": PostMarkItemSeenAction, + "EquipBattleRoyaleCustomization": PostEquipBattleRoyaleCustomizationAction, } ) @@ -21,27 +25,33 @@ func PostProfileAction(c *fiber.Ctx) error { if person == nil { return c.Status(404).JSON(aid.ErrorBadRequest("No Account Found")) } + defer person.Save() profile := person.GetProfileFromType(c.Query("profileId")) - if profile == nil { - return c.Status(404).JSON(aid.ErrorBadRequest("No Profile Found")) - } + defer profile.ClearProfileChanges() - snapshot := profile.Snapshot() + before := profile.Snapshot() if action, ok := profileActions[c.Params("action")]; ok { - err := action(c, person, profile) - if err != nil { + if err := action(c, person, profile); err != nil { return err } } - profile.Diff(snapshot) - profile.Revision++ + changes := profile.Diff(before) + + aid.Print("Changes: " + strconv.Itoa(len(changes))) + aid.PrintJSON(changes) + + revision, _ := strconv.Atoi(c.Query("rvn")) + if revision == -1 { + revision = profile.Revision + } + revision++ return c.Status(200).JSON(aid.JSON{ "profileId": profile.Type, - "profileRevision": profile.Revision, - "profileCommandRevision": profile.Revision, - "profileChangesBaseRevision": profile.Revision - 1, + "profileRevision": revision, + "profileCommandRevision": revision, + "profileChangesBaseRevision": revision - 1, "profileChanges": profile.Changes, "multiUpdate": []aid.JSON{}, "notifications": []aid.JSON{}, @@ -51,7 +61,66 @@ func PostProfileAction(c *fiber.Ctx) error { } func PostQueryProfileAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { - profile.Changes = []interface{}{} profile.CreateFullProfileUpdateChange() + return nil +} + +func PostMarkItemSeenAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { + var body struct { + ItemIds []string `json:"itemIds"` + } + + err := c.BodyParser(&body) + if err != nil { + return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Body")) + } + + for _, itemId := range body.ItemIds { + item := profile.Items.GetItem(itemId) + if item == nil { + continue + } + + item.HasSeen = true + } + + return nil +} + +func PostEquipBattleRoyaleCustomizationAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { + var body struct { + SlotName string `json:"slotName"` + ItemToSlot string `json:"itemToSlot"` + IndexWithinSlot int `json:"indexWithinSlot"` + } + + err := c.BodyParser(&body) + if err != nil { + return c.Status(400).JSON(aid.ErrorBadRequest("Invalid Body")) + } + + item := profile.Items.GetItem(body.ItemToSlot) + if item == nil { + return c.Status(400).JSON(aid.ErrorBadRequest("Item not found")) + } + + attr := profile.Attributes.GetAttributeByKey("favorite_" + strings.ToLower(body.SlotName)) + if attr == nil { + return c.Status(400).JSON(aid.ErrorBadRequest("Attribute not found")) + } + + switch body.SlotName { + case "Dance": + value := aid.JSONParse(attr.ValueJSON) + value.([]any)[body.IndexWithinSlot] = item.ID + attr.ValueJSON = aid.JSONStringify(value) + case "ItemWrap": + value := aid.JSONParse(attr.ValueJSON) + value.([]any)[body.IndexWithinSlot] = item.ID + attr.ValueJSON = aid.JSONStringify(value) + default: + attr.ValueJSON = aid.JSONStringify(item.ID) + } + return nil } \ No newline at end of file diff --git a/handlers/storage.go b/handlers/storage.go index 6fec2bb..5c86f70 100644 --- a/handlers/storage.go +++ b/handlers/storage.go @@ -17,7 +17,7 @@ func GetCloudStorageConfig(c *fiber.Ctx) error { "epicAppName": "Live", "isAuthenticated": true, "disableV2": true, - "lastUpdated": "2021-01-01T00:00:00Z", + "lastUpdated": "0000-00-00T00:00:00.000Z", "transports": []string{}, }) } @@ -28,5 +28,17 @@ func GetCloudStorageFile(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(aid.ErrorBadRequest) } + return c.Status(fiber.StatusOK).JSON(aid.JSON{}) +} + +func GetUserStorageFiles(c *fiber.Ctx) error { + return c.Status(fiber.StatusOK).JSON([]aid.JSON{}) +} + +func GetUserStorageFile(c *fiber.Ctx) error { + return c.Status(fiber.StatusOK).JSON(aid.JSON{}) +} + +func PutUserStorageFile(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(aid.JSON{}) } \ No newline at end of file diff --git a/main.go b/main.go index 0b2b269..790828a 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,6 @@ func init() { } postgresStorage.Migrate(&storage.DB_Person{}, "Persons") - postgresStorage.Migrate(&storage.DB_Loadout{}, "Loadouts") postgresStorage.Migrate(&storage.DB_Profile{}, "Profiles") postgresStorage.Migrate(&storage.DB_Item{}, "Items") postgresStorage.Migrate(&storage.DB_Gift{}, "Gifts") @@ -35,12 +34,11 @@ func init() { } storage.Repo = storage.NewStorage(device) - storage.Cache = storage.NewPersonsCacheMutex() } func init() { if aid.Config.Database.DropAllTables { - person.NewFortnitePerson("ac", "ket") + person.NewFortnitePerson("ac", "1") } aid.PrintTime("Loading all persons from database", func() { @@ -49,7 +47,7 @@ func init() { } }) - go storage.Cache.CacheKiller() + // go storage.Cache.CacheKiller() } func main() { @@ -59,6 +57,8 @@ func main() { r.Use(aid.FiberLimiter()) r.Use(aid.FiberCors()) + r.Get("/content/api/pages/fortnite-game", handlers.GetContentPages) + account := r.Group("/account/api") account.Get("/public/account/:accountId", handlers.GetPublicAccount) account.Get("/public/account/:accountId/externalAuths", handlers.GetPublicAccountExternalAuths) @@ -68,6 +68,7 @@ func main() { fortnite := r.Group("/fortnite/api") fortnite.Get("/receipts/v1/account/:accountId/receipts", handlers.GetAccountReceipts) fortnite.Get("/versioncheck/*", handlers.GetVersionCheck) + fortnite.Get("/calendar/v1/timeline", handlers.GetTimelineCalendar) matchmaking := fortnite.Group("/matchmaking") matchmaking.Get("/session/findPlayer/:accountId", handlers.GetSessionFindPlayer) @@ -76,6 +77,9 @@ func main() { storage.Get("/system", handlers.GetCloudStorageFiles) storage.Get("/system/config", handlers.GetCloudStorageConfig) storage.Get("/system/:fileName", handlers.GetCloudStorageFile) + storage.Get("/user/:accountId", handlers.GetUserStorageFiles) + storage.Get("/user/:accountId/:fileName", handlers.GetUserStorageFile) + storage.Put("/user/:accountId/:fileName", handlers.PutUserStorageFile) game := fortnite.Group("/game/v2") game.Post("/tryPlayOnPlatform/account/:accountId", handlers.PostTryPlayOnPlatform) diff --git a/person/attribute.go b/person/attribute.go index 5b11a8c..ded96c2 100644 --- a/person/attribute.go +++ b/person/attribute.go @@ -1,59 +1,58 @@ package person import ( - "encoding/json" "reflect" + "github.com/ectrc/snow/aid" "github.com/ectrc/snow/storage" "github.com/google/uuid" ) type Attribute struct { - ID string - Key string - Value interface{} + ID string + ProfileID string + Key string + ValueJSON string Type string } func NewAttribute(key string, value interface{}) *Attribute { return &Attribute{ - ID: uuid.New().String(), - Key: key, - Value: value, - Type: reflect.TypeOf(value).String(), + ID: uuid.New().String(), + Key: key, + ValueJSON: aid.JSONStringify(value), + Type: reflect.TypeOf(value).String(), } } func FromDatabaseAttribute(db *storage.DB_PAttribute) *Attribute { - var value interface{} - err := json.Unmarshal([]byte(db.ValueJSON), &value) - if err != nil { - return nil - } - return &Attribute{ - ID: db.ID, - Key: db.Key, - Value: value, - Type: db.Type, + ID: db.ID, + ProfileID: db.ProfileID, + Key: db.Key, + ValueJSON: db.ValueJSON, + Type: db.Type, } } func (a *Attribute) ToDatabase(profileId string) *storage.DB_PAttribute { - value, err := json.Marshal(a.Value) - if err != nil { - return nil - } - return &storage.DB_PAttribute{ - ID: a.ID, + ID: a.ID, ProfileID: profileId, - Key: a.Key, - ValueJSON: string(value), - Type: a.Type, + Key: a.Key, + ValueJSON: a.ValueJSON, + Type: a.Type, } } func (a *Attribute) Delete() { storage.Repo.DeleteAttribute(a.ID) +} + +func (a *Attribute) Save() { + if a.ProfileID == "" { + aid.Print("error saving attribute", a.Key, "profile id is empty") + return + } + storage.Repo.SaveAttribute(a.ToDatabase(a.ProfileID)) } \ No newline at end of file diff --git a/storage/cache.go b/person/cache.go similarity index 56% rename from storage/cache.go rename to person/cache.go index 64b1994..097b872 100644 --- a/storage/cache.go +++ b/person/cache.go @@ -1,12 +1,17 @@ -package storage +package person import ( + "fmt" "sync" "time" ) +var ( + cache *PersonsCache +) + type CacheEntry struct { - Entry interface{} + Entry *Person LastAccessed time.Time } @@ -20,15 +25,15 @@ func NewPersonsCacheMutex() *PersonsCache { func (m *PersonsCache) CacheKiller() { for { - if Cache.Count() == 0 { + if m.Count() == 0 { continue } - Cache.Range(func(key, value interface{}) bool { + m.Range(func(key, value interface{}) bool { cacheEntry := value.(*CacheEntry) if time.Since(cacheEntry.LastAccessed) >= 30 * time.Minute { - Cache.Delete(key) + m.Delete(key) } return true @@ -38,20 +43,21 @@ func (m *PersonsCache) CacheKiller() { } } -func (m *PersonsCache) GetPerson(id string) *DB_Person { +func (m *PersonsCache) GetPerson(id string) *Person { if p, ok := m.Load(id); ok { + fmt.Println("Cache hit", id) cacheEntry := p.(*CacheEntry) - return cacheEntry.Entry.(*DB_Person) + return cacheEntry.Entry } return nil } -func (m *PersonsCache) GetPersonByDisplay(displayName string) *DB_Person { - var person *DB_Person - m.Range(func(key, value interface{}) bool { - if value.(*CacheEntry).Entry.(*DB_Person).DisplayName == displayName { - person = value.(*CacheEntry).Entry.(*DB_Person) +func (m *PersonsCache) GetPersonByDisplay(displayName string) *Person { + var person *Person + m.RangeEntry(func(key string, value *CacheEntry) bool { + if value.Entry.DisplayName == displayName { + person = value.Entry return false } @@ -61,7 +67,7 @@ func (m *PersonsCache) GetPersonByDisplay(displayName string) *DB_Person { return person } -func (m *PersonsCache) SavePerson(p *DB_Person) { +func (m *PersonsCache) SavePerson(p *Person) { m.Store(p.ID, &CacheEntry{ Entry: p, LastAccessed: time.Now(), @@ -72,9 +78,9 @@ func (m *PersonsCache) DeletePerson(id string) { m.Delete(id) } -func (m *PersonsCache) RangePersons(f func(key string, value *DB_Person) bool) { +func (m *PersonsCache) RangeEntry(f func(key string, value *CacheEntry) bool) { m.Range(func(key, value interface{}) bool { - return f(key.(string), value.(*CacheEntry).Entry.(*DB_Person)) + return f(key.(string), value.(*CacheEntry)) }) } diff --git a/person/fortnite.go b/person/fortnite.go index efde58a..f440238 100644 --- a/person/fortnite.go +++ b/person/fortnite.go @@ -7,22 +7,14 @@ func NewFortnitePerson(displayName string, key string) { person.DisplayName = displayName person.AccessKey = key - character := NewItem("AthenaCharacter:CID_001_Athena_Commando_F_Default", 1) - pickaxe := NewItem("AthenaPickaxe:DefaultPickaxe", 1) - glider := NewItem("AthenaGlider:DefaultGlider", 1) - default_dance := NewItem("AthenaDance:EID_DanceMoves", 1) - - person.AthenaProfile.Items.AddItem(character) - person.AthenaProfile.Items.AddItem(pickaxe) - person.AthenaProfile.Items.AddItem(glider) - person.AthenaProfile.Items.AddItem(default_dance) + person.AthenaProfile.Items.AddItem(NewItem("AthenaCharacter:CID_001_Athena_Commando_F_Default", 1)) + person.AthenaProfile.Items.AddItem(NewItem("AthenaCharacter:CID_032_Athena_Commando_M_Medieval", 1)) + person.AthenaProfile.Items.AddItem(NewItem("AthenaCharacter:CID_033_Athena_Commando_F_Medieval", 1)) + person.AthenaProfile.Items.AddItem(NewItem("AthenaPickaxe:DefaultPickaxe", 1)) + person.AthenaProfile.Items.AddItem(NewItem("AthenaGlider:DefaultGlider", 1)) + person.AthenaProfile.Items.AddItem(NewItem("AthenaDance:EID_DanceMoves", 1)) person.CommonCoreProfile.Items.AddItem(NewItem("Currency:MtxPurchased", 0)) - person.Loadout.Character = character.ID - person.Loadout.Pickaxe = pickaxe.ID - person.Loadout.Glider = glider.ID - person.Loadout.Dances[0] = default_dance.ID - person.AthenaProfile.Attributes.AddAttribute(NewAttribute("mfa_reward_claimed", true)) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("rested_xp_overflow", 0)) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("lifetime_wins", 0)) @@ -32,7 +24,7 @@ func NewFortnitePerson(displayName string, key string) { person.AthenaProfile.Attributes.AddAttribute(NewAttribute("daily_rewards", []aid.JSON{})) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("competitive_identity", aid.JSON{})) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("season_update", 0)) - person.AthenaProfile.Attributes.AddAttribute(NewAttribute("season_num", 2)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("season_num", aid.Config.Fortnite.Season)) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("permissions", []aid.JSON{})) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("loadouts", []aid.JSON{})) @@ -51,17 +43,17 @@ func NewFortnitePerson(displayName string, key string) { person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_level", 1)) person.AthenaProfile.Attributes.AddAttribute(NewAttribute("book_xp", 0)) - person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_character", person.Loadout.Character)) - person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_backpack", person.Loadout.Backpack)) - person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_pickaxe", person.Loadout.Pickaxe)) - person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_glider", person.Loadout.Glider)) - person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_skydivecontrail", person.Loadout.SkyDiveContrail)) - person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_dance", person.Loadout.Dances)) - person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_itemwraps", person.Loadout.ItemWraps)) - person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_loadingscreen", person.Loadout.LoadingScreen)) - person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_musicpack", person.Loadout.MusicPack)) - person.AthenaProfile.Attributes.AddAttribute(NewAttribute("banner_icon", person.Loadout.BannerIcon)) - person.AthenaProfile.Attributes.AddAttribute(NewAttribute("banner_color", person.Loadout.BannerColor)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_character", person.AthenaProfile.Items.GetItemByTemplateID("AthenaCharacter:CID_001_Athena_Commando_F_Default").ID)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_backpack", "")) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_pickaxe", person.AthenaProfile.Items.GetItemByTemplateID("AthenaPickaxe:DefaultPickaxe").ID)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_glider", person.AthenaProfile.Items.GetItemByTemplateID("AthenaGlider:DefaultGlider").ID)) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_skydivecontrail", "")) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_dance", make([]string, 6))) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_itemwraps", make([]string, 7))) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_loadingscreen", "")) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("favorite_musicpack", "")) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("banner_icon", "")) + person.AthenaProfile.Attributes.AddAttribute(NewAttribute("banner_color", "")) person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("mfa_enabled", true)) person.CommonCoreProfile.Attributes.AddAttribute(NewAttribute("mtx_affiliate", "")) diff --git a/person/gift.go b/person/gift.go index 4b8c846..efb3b4d 100644 --- a/person/gift.go +++ b/person/gift.go @@ -9,24 +9,25 @@ import ( ) type Gift struct { - ID string + ID string + ProfileID string TemplateID string - Quantity int - FromID string - GiftedAt int64 - Message string - Loot []*Item + Quantity int + FromID string + GiftedAt int64 + Message string + Loot []*Item } func NewGift(templateID string, quantity int, fromID string, message string) *Gift { return &Gift{ - ID: uuid.New().String(), + ID: uuid.New().String(), TemplateID: templateID, - Quantity: quantity, - FromID: fromID, - GiftedAt: time.Now().Unix(), - Message: message, - Loot: []*Item{}, + Quantity: quantity, + FromID: fromID, + GiftedAt: time.Now().Unix(), + Message: message, + Loot: []*Item{}, } } @@ -38,13 +39,14 @@ func FromDatabaseGift(gift *storage.DB_Gift) *Gift { } return &Gift{ - ID: gift.ID, + ID: gift.ID, + ProfileID: gift.ProfileID, TemplateID: gift.TemplateID, - Quantity: gift.Quantity, - FromID: gift.FromID, - GiftedAt: gift.GiftedAt, - Message: gift.Message, - Loot: loot, + Quantity: gift.Quantity, + FromID: gift.FromID, + GiftedAt: gift.GiftedAt, + Message: gift.Message, + Loot: loot, } } @@ -101,19 +103,24 @@ func (g *Gift) ToDatabase(profileId string) *storage.DB_Gift { } return &storage.DB_Gift{ + ID: g.ID, ProfileID: profileId, - ID: g.ID, TemplateID: g.TemplateID, - Quantity: g.Quantity, - FromID: g.FromID, - GiftedAt: g.GiftedAt, - Message: g.Message, - Loot: profileLoot, + Quantity: g.Quantity, + FromID: g.FromID, + GiftedAt: g.GiftedAt, + Message: g.Message, + Loot: profileLoot, } } func (g *Gift) Save() { - //storage.Repo.SaveGift(g.ToDatabase()) + if g.ProfileID == "" { + aid.Print("error saving gift", g.ID, "no profile id") + return + } + + storage.Repo.SaveGift(g.ToDatabase(g.ProfileID)) } func (g *Gift) Snapshot() GiftSnapshot { @@ -124,12 +131,12 @@ func (g *Gift) Snapshot() GiftSnapshot { } return GiftSnapshot{ - ID: g.ID, + ID: g.ID, TemplateID: g.TemplateID, - Quantity: g.Quantity, - FromID: g.FromID, - GiftedAt: g.GiftedAt, - Message: g.Message, - Loot: loot, + Quantity: g.Quantity, + FromID: g.FromID, + GiftedAt: g.GiftedAt, + Message: g.Message, + Loot: loot, } } \ No newline at end of file diff --git a/person/item.go b/person/item.go index c367a67..a2940bc 100644 --- a/person/item.go +++ b/person/item.go @@ -7,39 +7,40 @@ import ( ) type Item struct { - ID string - TemplateID string - Quantity int - Favorite bool - HasSeen bool - Variants []*VariantChannel + ID string + ProfileID string + TemplateID string + Quantity int + Favorite bool + HasSeen bool + Variants []*VariantChannel ProfileType string } func NewItem(templateID string, quantity int) *Item { return &Item{ - ID: uuid.New().String(), + ID: uuid.New().String(), TemplateID: templateID, - Quantity: quantity, - Favorite: false, - HasSeen: false, - Variants: []*VariantChannel{}, + Quantity: quantity, + Favorite: false, + HasSeen: false, + Variants: []*VariantChannel{}, } } func NewItemWithType(templateID string, quantity int, profile string) *Item { return &Item{ - ID: uuid.New().String(), + ID: uuid.New().String(), TemplateID: templateID, - Quantity: quantity, - Favorite: false, - HasSeen: false, - Variants: []*VariantChannel{}, + Quantity: quantity, + Favorite: false, + HasSeen: false, + Variants: []*VariantChannel{}, ProfileType: profile, } } -func FromDatabaseItem(item *storage.DB_Item, profileType *string) *Item { +func FromDatabaseItem(item *storage.DB_Item) *Item { variants := []*VariantChannel{} for _, variant := range item.Variants { @@ -47,13 +48,12 @@ func FromDatabaseItem(item *storage.DB_Item, profileType *string) *Item { } return &Item{ - ID: item.ID, - TemplateID: item.TemplateID, - Quantity: item.Quantity, - Favorite: item.Favorite, - HasSeen: item.HasSeen, - Variants: variants, - ProfileType: *profileType, + ID: item.ID, + TemplateID: item.TemplateID, + Quantity: item.Quantity, + Favorite: item.Favorite, + HasSeen: item.HasSeen, + Variants: variants, } } @@ -114,10 +114,10 @@ func (i *Item) DeleteLoot() { func (i *Item) NewChannel(channel string, owned []string, active string) *VariantChannel { return &VariantChannel{ - ItemID: i.ID, + ItemID: i.ID, Channel: channel, - Owned: owned, - Active: active, + Owned: owned, + Active: active, } } @@ -160,27 +160,32 @@ func (i *Item) ToDatabase(profileId string) *storage.DB_Item { } return &storage.DB_Item{ - ProfileID: profileId, - ID: i.ID, + ProfileID: profileId, + ID: i.ID, TemplateID: i.TemplateID, - Quantity: i.Quantity, - Favorite: i.Favorite, - HasSeen: i.HasSeen, - Variants: variants, + Quantity: i.Quantity, + Favorite: i.Favorite, + HasSeen: i.HasSeen, + Variants: variants, } } func (i *Item) Save() { - //storage.Repo.SaveItem(i.ToDatabase()) + if i.ProfileID == "" { + aid.Print("error saving item", i.ID, "no profile id") + return + } + + storage.Repo.SaveItem(i.ToDatabase(i.ProfileID)) } func (i *Item) ToLootDatabase(giftId string) *storage.DB_Loot { return &storage.DB_Loot{ - GiftID: giftId, + GiftID: giftId, ProfileType: i.ProfileType, - ID: i.ID, - TemplateID: i.TemplateID, - Quantity: i.Quantity, + ID: i.ID, + TemplateID: i.TemplateID, + Quantity: i.Quantity, } } @@ -196,12 +201,12 @@ func (i *Item) Snapshot() ItemSnapshot { } return ItemSnapshot{ - ID: i.ID, - TemplateID: i.TemplateID, - Quantity: i.Quantity, - Favorite: i.Favorite, - HasSeen: i.HasSeen, - Variants: variants, + ID: i.ID, + TemplateID: i.TemplateID, + Quantity: i.Quantity, + Favorite: i.Favorite, + HasSeen: i.HasSeen, + Variants: variants, ProfileType: i.ProfileType, } } @@ -216,21 +221,21 @@ type VariantChannel struct { func FromDatabaseVariant(variant *storage.DB_VariantChannel) *VariantChannel { return &VariantChannel{ - ID: variant.ID, - ItemID: variant.ItemID, + ID: variant.ID, + ItemID: variant.ItemID, Channel: variant.Channel, - Owned: variant.Owned, - Active: variant.Active, + Owned: variant.Owned, + Active: variant.Active, } } func (v *VariantChannel) ToDatabase() *storage.DB_VariantChannel { return &storage.DB_VariantChannel{ - ID: v.ID, - ItemID: v.ItemID, + ID: v.ID, + ItemID: v.ItemID, Channel: v.Channel, - Owned: v.Owned, - Active: v.Active, + Owned: v.Owned, + Active: v.Active, } } diff --git a/person/person.go b/person/person.go index 99fdc14..1b859ef 100644 --- a/person/person.go +++ b/person/person.go @@ -1,19 +1,20 @@ package person import ( + "time" + "github.com/ectrc/snow/storage" "github.com/google/uuid" ) type Person struct { - ID string + ID string DisplayName string AccessKey string - AthenaProfile *Profile + AthenaProfile *Profile CommonCoreProfile *Profile CommonPublicProfile *Profile Profile0 *Profile - Loadout *Loadout } type Option struct { @@ -30,69 +31,52 @@ func NewPerson() *Person { CommonCoreProfile: NewProfile("common_core"), CommonPublicProfile: NewProfile("common_public"), Profile0: NewProfile("profile0"), - Loadout: NewLoadout(), } } func Find(personId string) *Person { - person := storage.Repo.GetPerson(personId) + if cache == nil { + cache = NewPersonsCacheMutex() + } + + cachedPerson := cache.GetPerson(personId) + if cachedPerson != nil { + return cachedPerson + } + + person := storage.Repo.GetPersonFromDB(personId) if person == nil { return nil } - loadout := FromDatabaseLoadout(&person.Loadout) - athenaProfile := NewProfile("athena") - commonCoreProfile := NewProfile("common_core") - commonPublicProfile := NewProfile("common_public") - profile0 := NewProfile("profile0") - - for _, profile := range person.Profiles { - if profile.Type == "athena" { - athenaProfile.ID = profile.ID - athenaProfile = FromDatabaseProfile(&profile) - } - - if profile.Type == "common_core" { - commonCoreProfile.ID = profile.ID - commonCoreProfile = FromDatabaseProfile(&profile) - } - - if profile.Type == "common_public" { - commonPublicProfile.ID = profile.ID - commonPublicProfile = FromDatabaseProfile(&profile) - } - - if profile.Type == "profile0" { - profile0.ID = profile.ID - profile0 = FromDatabaseProfile(&profile) - } - } - - return &Person{ - ID: person.ID, - DisplayName: person.DisplayName, - AccessKey: person.AccessKey, - AthenaProfile: athenaProfile, - CommonCoreProfile: commonCoreProfile, - CommonPublicProfile: commonPublicProfile, - Profile0: profile0, - Loadout: loadout, - } + return findHelper(person) } func FindByDisplay(displayName string) *Person { - person := storage.Repo.GetPersonByDisplay(displayName) + if cache == nil { + cache = NewPersonsCacheMutex() + } + + person := storage.Repo.GetPersonByDisplayFromDB(displayName) if person == nil { return nil } - loadout := FromDatabaseLoadout(&person.Loadout) + cachedPerson := cache.GetPerson(person.ID) + if cachedPerson != nil { + return cachedPerson + } + + return findHelper(person) +} + +func findHelper(databasePerson *storage.DB_Person) *Person { athenaProfile := NewProfile("athena") commonCoreProfile := NewProfile("common_core") commonPublicProfile := NewProfile("common_public") profile0 := NewProfile("profile0") - for _, profile := range person.Profiles { + for _, profile := range databasePerson.Profiles { if profile.Type == "athena" { athenaProfile.ID = profile.ID athenaProfile = FromDatabaseProfile(&profile) @@ -113,17 +97,23 @@ func FindByDisplay(displayName string) *Person { profile0 = FromDatabaseProfile(&profile) } } - - return &Person{ - ID: person.ID, - DisplayName: person.DisplayName, - AccessKey: person.AccessKey, + + person := &Person{ + ID: databasePerson.ID, + DisplayName: databasePerson.DisplayName, + AccessKey: databasePerson.AccessKey, AthenaProfile: athenaProfile, CommonCoreProfile: commonCoreProfile, CommonPublicProfile: commonPublicProfile, Profile0: profile0, - Loadout: loadout, } + + cache.Store(person.ID, &CacheEntry{ + Entry: person, + LastAccessed: time.Now(), + }) + + return person } func AllFromDatabase() []*Person { @@ -152,7 +142,8 @@ func (p *Person) GetProfileFromType(profileType string) *Profile { } func (p *Person) Save() { - storage.Repo.SavePerson(p.ToDatabase()) + dbPerson := p.ToDatabase() + storage.Repo.SavePerson(dbPerson) } func (p *Person) ToDatabase() *storage.DB_Person { @@ -160,7 +151,6 @@ func (p *Person) ToDatabase() *storage.DB_Person { ID: p.ID, DisplayName: p.DisplayName, Profiles: []storage.DB_Profile{}, - Loadout: *p.Loadout.ToDatabase(), AccessKey: p.AccessKey, } @@ -178,7 +168,9 @@ func (p *Person) ToDatabase() *storage.DB_Person { Type: profileType, Items: []storage.DB_Item{}, Gifts: []storage.DB_Gift{}, + Quests: []storage.DB_Quest{}, Attributes: []storage.DB_PAttribute{}, + Revision: profile.Revision, } profile.Items.RangeItems(func(id string, item *Item) bool { @@ -225,7 +217,6 @@ func (p *Person) Snapshot() *PersonSnapshot { ID: p.ID, DisplayName: p.DisplayName, AthenaProfile: *p.AthenaProfile.Snapshot(), - CommonCoreProfile:* p.CommonCoreProfile.Snapshot(), - Loadout: *p.Loadout, + CommonCoreProfile: *p.CommonCoreProfile.Snapshot(), } } \ No newline at end of file diff --git a/person/profile.go b/person/profile.go index 60b3017..b74eadb 100644 --- a/person/profile.go +++ b/person/profile.go @@ -10,10 +10,10 @@ import ( ) type Profile struct { - ID string - PersonID string - Items *ItemMutex - Gifts *GiftMutex + ID string + PersonID string + Items *ItemMutex + Gifts *GiftMutex Quests *QuestMutex Attributes *AttributeMutex Type string @@ -22,27 +22,28 @@ type Profile struct { } func NewProfile(profile string) *Profile { + id := uuid.New().String() return &Profile{ - ID: uuid.New().String(), - PersonID: "", - Items: NewItemMutex(profile), - Gifts: NewGiftMutex(), - Quests: NewQuestMutex(), - Attributes: NewAttributeMutex(), - Type: profile, - Revision: 0, + ID: id, + PersonID: "", + Items: NewItemMutex(&storage.DB_Profile{ID: id, Type: profile}), + Gifts: NewGiftMutex(&storage.DB_Profile{ID: id, Type: profile}), + Quests: NewQuestMutex(&storage.DB_Profile{ID: id, Type: profile}), + Attributes: NewAttributeMutex(&storage.DB_Profile{ID: id, Type: profile}), + Type: profile, + Revision: 0, Changes: []interface{}{}, } } func FromDatabaseProfile(profile *storage.DB_Profile) *Profile { - items := NewItemMutex(profile.Type) - gifts := NewGiftMutex() - quests := NewQuestMutex() - attributes := NewAttributeMutex() + items := NewItemMutex(profile) + gifts := NewGiftMutex(profile) + quests := NewQuestMutex(profile) + attributes := NewAttributeMutex(profile) for _, item := range profile.Items { - items.AddItem(FromDatabaseItem(&item, &profile.Type)) + items.AddItem(FromDatabaseItem(&item)) } for _, gift := range profile.Gifts { @@ -50,7 +51,7 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile { } for _, quest := range profile.Quests { - quests.AddQuest(FromDatabaseQuest(&quest, &profile.Type)) + quests.AddQuest(FromDatabaseQuest(&quest)) } for _, attribute := range profile.Attributes { @@ -64,14 +65,15 @@ func FromDatabaseProfile(profile *storage.DB_Profile) *Profile { } return &Profile{ - ID: profile.ID, - PersonID: profile.PersonID, - Items: items, - Gifts: gifts, - Quests: quests, + ID: profile.ID, + PersonID: profile.PersonID, + Items: items, + Gifts: gifts, + Quests: quests, Attributes: attributes, - Type: profile.Type, - Revision: profile.Revision, + Type: profile.Type, + Revision: profile.Revision, + Changes: []interface{}{}, } } @@ -95,7 +97,7 @@ func (p *Profile) GenerateFortniteProfileEntry() aid.JSON { }) p.Attributes.RangeAttributes(func(id string, attribute *Attribute) bool { - attributes[attribute.Key] = attribute.Value + attributes[attribute.Key] = aid.JSONParse(attribute.ValueJSON) return true }) @@ -114,7 +116,7 @@ func (p *Profile) GenerateFortniteProfileEntry() aid.JSON { } func (p *Profile) Save() { - //storage.Repo.SaveProfile(p.ToDatabase()) + // storage.Repo.SaveProfile(p.ToDatabase()) } func (p *Profile) Snapshot() *ProfileSnapshot { @@ -144,18 +146,18 @@ func (p *Profile) Snapshot() *ProfileSnapshot { }) return &ProfileSnapshot{ - ID: p.ID, - Items: items, - Gifts: gifts, - Quests: quests, + ID: p.ID, + Items: items, + Gifts: gifts, + Quests: quests, Attributes: attributes, - Type: p.Type, - Revision: p.Revision, + Type: p.Type, + Revision: p.Revision, } } -func (p *Profile) Diff(snapshot *ProfileSnapshot) []diff.Change { - changes, err := diff.Diff(snapshot, p.Snapshot()) +func (p *Profile) Diff(b *ProfileSnapshot) []diff.Change { + changes, err := diff.Diff(*b, *p.Snapshot()) if err != nil { fmt.Printf("error diffing profile: %v\n", err) return nil @@ -200,7 +202,7 @@ func (p *Profile) Diff(snapshot *ProfileSnapshot) []diff.Change { p.CreateStatModifiedChange(p.Attributes.GetAttribute(change.Path[1])) } - if change.Type == "update" && change.Path[2] == "Value" { + if change.Type == "update" && change.Path[2] == "ValueJSON" { p.CreateStatModifiedChange(p.Attributes.GetAttribute(change.Path[1])) } } @@ -209,6 +211,11 @@ func (p *Profile) Diff(snapshot *ProfileSnapshot) []diff.Change { return changes } +func (p *Profile) CreateAttribute(key string, value interface{}) *Attribute { + p.Attributes.AddAttribute(NewAttribute(key, value)) + return p.Attributes.GetAttribute(key) +} + func (p *Profile) CreateStatModifiedChange(attribute *Attribute) { if attribute == nil { fmt.Println("error getting attribute from profile", attribute.ID) @@ -218,7 +225,7 @@ func (p *Profile) CreateStatModifiedChange(attribute *Attribute) { p.Changes = append(p.Changes, StatModified{ ChangeType: "statModified", Name: attribute.Key, - Value: attribute.Value, + Value: aid.JSONParse(attribute.ValueJSON), }) } @@ -302,78 +309,12 @@ func (p *Profile) CreateItemAttributeChangedChange(item *Item, attribute string) } func (p *Profile) CreateFullProfileUpdateChange() { - p.Changes = append(p.Changes, FullProfileUpdate{ + p.Changes = []interface{}{FullProfileUpdate{ ChangeType: "fullProfileUpdate", Profile: p.GenerateFortniteProfileEntry(), - }) + }} } -type Loadout struct { - ID string - Character string - Backpack string - Pickaxe string - Glider string - Dances []string - ItemWraps []string - LoadingScreen string - SkyDiveContrail string - MusicPack string - BannerIcon string - BannerColor string -} - -func NewLoadout() *Loadout { - return &Loadout{ - ID: uuid.New().String(), - Character: "", - Backpack: "", - Pickaxe: "", - Glider: "", - Dances: make([]string, 6), - ItemWraps: make([]string, 7), - LoadingScreen: "", - SkyDiveContrail: "", - MusicPack: "", - BannerIcon: "", - BannerColor: "", - } -} - -func FromDatabaseLoadout(l *storage.DB_Loadout) *Loadout { - return &Loadout{ - ID: l.ID, - Character: l.Character, - Backpack: l.Backpack, - Pickaxe: l.Pickaxe, - Glider: l.Glider, - Dances: l.Dances, - ItemWraps: l.ItemWraps, - LoadingScreen: l.LoadingScreen, - SkyDiveContrail: l.SkyDiveContrail, - MusicPack: l.MusicPack, - BannerIcon: l.BannerIcon, - BannerColor: l.BannerColor, - } -} - -func (l *Loadout) ToDatabase() *storage.DB_Loadout { - return &storage.DB_Loadout{ - ID: l.ID, - Character: l.Character, - Backpack: l.Backpack, - Pickaxe: l.Pickaxe, - Glider: l.Glider, - Dances: l.Dances, - ItemWraps: l.ItemWraps, - LoadingScreen: l.LoadingScreen, - SkyDiveContrail: l.SkyDiveContrail, - MusicPack: l.MusicPack, - BannerIcon: l.BannerIcon, - BannerColor: l.BannerColor, - } -} - -func (l *Loadout) Save() { - //storage.Repo.SaveLoadout(l.ToDatabase()) +func (p *Profile) ClearProfileChanges() { + p.Changes = []interface{}{} } \ No newline at end of file diff --git a/person/quest.go b/person/quest.go index ff3f107..fda5389 100644 --- a/person/quest.go +++ b/person/quest.go @@ -7,46 +7,48 @@ import ( ) type Quest struct { - ID string - TemplateID string - State string - Objectives []string + ID string + ProfileID string + TemplateID string + State string + Objectives []string ObjectiveCounts []int64 - BundleID string - ScheduleID string + BundleID string + ScheduleID string } func NewQuest(templateID string, bundleID string, scheduleID string) *Quest { return &Quest{ - ID: uuid.New().String(), - TemplateID: templateID, - State: "Active", - Objectives: []string{}, + ID: uuid.New().String(), + TemplateID: templateID, + State: "Active", + Objectives: []string{}, ObjectiveCounts: []int64{}, - BundleID: bundleID, - ScheduleID: scheduleID, + BundleID: bundleID, + ScheduleID: scheduleID, } } func NewDailyQuest(templateID string) *Quest { return &Quest{ - ID: uuid.New().String(), - TemplateID: templateID, - State: "Active", - Objectives: []string{}, + ID: uuid.New().String(), + TemplateID: templateID, + State: "Active", + Objectives: []string{}, ObjectiveCounts: []int64{}, } } -func FromDatabaseQuest(quest *storage.DB_Quest, profileType *string) *Quest { +func FromDatabaseQuest(quest *storage.DB_Quest) *Quest { return &Quest{ - ID: quest.ID, - TemplateID: quest.TemplateID, - State: quest.State, - Objectives: quest.Objectives, + ID: quest.ID, + ProfileID: quest.ProfileID, + TemplateID: quest.TemplateID, + State: quest.State, + Objectives: quest.Objectives, ObjectiveCounts: quest.ObjectiveCounts, - BundleID: quest.BundleID, - ScheduleID: quest.ScheduleID, + BundleID: quest.BundleID, + ScheduleID: quest.ScheduleID, } } @@ -126,17 +128,17 @@ func (q *Quest) RemoveObjective(objective string) { func (q *Quest) ToDatabase(profileId string) *storage.DB_Quest { return &storage.DB_Quest{ - ProfileID: profileId, - ID: q.ID, - TemplateID: q.TemplateID, - State: q.State, - Objectives: q.Objectives, + ID: q.ID, + ProfileID: profileId, + TemplateID: q.TemplateID, + State: q.State, + Objectives: q.Objectives, ObjectiveCounts: q.ObjectiveCounts, - BundleID: q.BundleID, - ScheduleID: q.ScheduleID, + BundleID: q.BundleID, + ScheduleID: q.ScheduleID, } } func (q *Quest) Save() { - //storage.Repo.SaveQuest(q.ToDatabase()) + storage.Repo.SaveQuest(q.ToDatabase(q.ProfileID)) } \ No newline at end of file diff --git a/person/snapshot.go b/person/snapshot.go index 1fde77b..5538c17 100644 --- a/person/snapshot.go +++ b/person/snapshot.go @@ -5,7 +5,6 @@ type PersonSnapshot struct { DisplayName string AthenaProfile ProfileSnapshot CommonCoreProfile ProfileSnapshot - Loadout Loadout } type ProfileSnapshot struct { diff --git a/person/sync.go b/person/sync.go index 480d187..6de6ba2 100644 --- a/person/sync.go +++ b/person/sync.go @@ -2,23 +2,28 @@ package person import ( "sync" + + "github.com/ectrc/snow/storage" ) type ItemMutex struct { sync.Map ProfileType string + ProfileID string } -func NewItemMutex(profile string) *ItemMutex { +func NewItemMutex(profile *storage.DB_Profile) *ItemMutex { return &ItemMutex{ - ProfileType: profile, + ProfileType: profile.Type, + ProfileID: profile.ID, } } func (m *ItemMutex) AddItem(item *Item) { item.ProfileType = m.ProfileType + item.ProfileID = m.ProfileID m.Store(item.ID, item) - // storage.Repo.SaveItem(item) + storage.Repo.SaveItem(item.ToDatabase(m.ProfileID)) } func (m *ItemMutex) DeleteItem(id string) { @@ -29,7 +34,7 @@ func (m *ItemMutex) DeleteItem(id string) { item.Delete() m.Delete(id) - // storage.Repo.DeleteItem(id) + storage.Repo.DeleteItem(id) } func (m *ItemMutex) GetItem(id string) *Item { @@ -74,20 +79,25 @@ func (m *ItemMutex) Count() int { type GiftMutex struct { sync.Map ProfileType string + ProfileID string } -func NewGiftMutex() *GiftMutex { - return &GiftMutex{} +func NewGiftMutex(profile *storage.DB_Profile) *GiftMutex { + return &GiftMutex{ + ProfileType: profile.Type, + ProfileID: profile.ID, + } } func (m *GiftMutex) AddGift(gift *Gift) { + gift.ProfileID = m.ProfileID m.Store(gift.ID, gift) - // storage.Repo.SaveGift(gift) + storage.Repo.SaveGift(gift.ToDatabase(m.ProfileID)) } func (m *GiftMutex) DeleteGift(id string) { m.Delete(id) - // storage.Repo.DeleteGift(id) + storage.Repo.DeleteGift(id) } func (m *GiftMutex) GetGift(id string) *Gift { @@ -116,20 +126,26 @@ func (m *GiftMutex) Count() int { type QuestMutex struct { sync.Map + ProfileType string + ProfileID string } -func NewQuestMutex() *QuestMutex { - return &QuestMutex{} +func NewQuestMutex(profile *storage.DB_Profile) *QuestMutex { + return &QuestMutex{ + ProfileType: profile.Type, + ProfileID: profile.ID, + } } func (m *QuestMutex) AddQuest(quest *Quest) { + quest.ProfileID = m.ProfileID m.Store(quest.ID, quest) - // storage.Repo.SaveQuest(quest) + storage.Repo.SaveQuest(quest.ToDatabase(m.ProfileID)) } func (m *QuestMutex) DeleteQuest(id string) { m.Delete(id) - // storage.Repo.DeleteQuest(id) + storage.Repo.DeleteQuest(id) } func (m *QuestMutex) GetQuest(id string) *Quest { @@ -158,20 +174,25 @@ func (m *QuestMutex) Count() int { type AttributeMutex struct { sync.Map + ProfileType string + ProfileID string } -func NewAttributeMutex() *AttributeMutex { - return &AttributeMutex{} +func NewAttributeMutex(profile *storage.DB_Profile) *AttributeMutex { + return &AttributeMutex{ + ProfileID: profile.ID, + } } func (m *AttributeMutex) AddAttribute(attribute *Attribute) { + attribute.ProfileID = m.ProfileID m.Store(attribute.ID, attribute) - // storage.Repo.SaveAttribute(key, value) + storage.Repo.SaveAttribute(attribute.ToDatabase(m.ProfileID)) } func (m *AttributeMutex) DeleteAttribute(id string) { m.Delete(id) - // storage.Repo.DeleteAttribute(key) + storage.Repo.DeleteAttribute(id) } func (m *AttributeMutex) GetAttribute(id string) *Attribute { diff --git a/storage/postgres.go b/storage/postgres.go index d8ca9ad..cffaf36 100644 --- a/storage/postgres.go +++ b/storage/postgres.go @@ -88,26 +88,63 @@ func (s *PostgresStorage) SavePerson(person *DB_Person) { s.Postgres.Save(person) } +func (s *PostgresStorage) DeletePerson(personId string) { + s.Postgres.Delete(&DB_Person{}, "id = ?", personId) +} + +func (s *PostgresStorage) SaveProfile(profile *DB_Profile) { + s.Postgres.Save(profile) +} + +func (s *PostgresStorage) DeleteProfile(profileId string) { + s.Postgres.Delete(&DB_Profile{}, "id = ?", profileId) +} + +func (s *PostgresStorage) SaveItem(item *DB_Item) { + s.Postgres.Save(item) +} + func (s *PostgresStorage) DeleteItem(itemId string) { s.Postgres.Delete(&DB_Item{}, "id = ?", itemId) } +func (s *PostgresStorage) SaveVariant(variant *DB_VariantChannel) { + s.Postgres.Save(variant) +} + func (s *PostgresStorage) DeleteVariant(variantId string) { s.Postgres.Delete(&DB_VariantChannel{}, "id = ?", variantId) } +func (s *PostgresStorage) SaveQuest(quest *DB_Quest) { + s.Postgres.Save(quest) +} + func (s *PostgresStorage) DeleteQuest(questId string) { s.Postgres.Delete(&DB_Quest{}, "id = ?", questId) } +func (s *PostgresStorage) SaveLoot(loot *DB_Loot) { + s.Postgres.Save(loot) +} + func (s *PostgresStorage) DeleteLoot(lootId string) { s.Postgres.Delete(&DB_Loot{}, "id = ?", lootId) } +func (s *PostgresStorage) SaveGift(gift *DB_Gift) { + s.Postgres.Save(gift) +} + func (s *PostgresStorage) DeleteGift(giftId string) { s.Postgres.Delete(&DB_Gift{}, "id = ?", giftId) } func (s *PostgresStorage) DeleteAttribute(attributeId string) { s.Postgres.Delete(&DB_PAttribute{}, "id = ?", attributeId) +} + +func (s *PostgresStorage) SaveAttribute(attribute *DB_PAttribute) { + aid.Print("saving attribute", attribute.Key, attribute.ValueJSON) + s.Postgres.Save(attribute) } \ No newline at end of file diff --git a/storage/storage.go b/storage/storage.go index a9ed148..1538dac 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -1,8 +1,7 @@ package storage var ( - Repo *Repository - Cache *PersonsCache + Repo *Repository ) type Storage interface { @@ -13,11 +12,25 @@ type Storage interface { GetAllPersons() []*DB_Person SavePerson(person *DB_Person) + SaveProfile(profile *DB_Profile) + DeleteProfile(profileId string) + + SaveItem(item *DB_Item) DeleteItem(itemId string) + + SaveVariant(variant *DB_VariantChannel) DeleteVariant(variantId string) + + SaveQuest(quest *DB_Quest) DeleteQuest(questId string) + + SaveLoot(loot *DB_Loot) DeleteLoot(lootId string) + + SaveGift(gift *DB_Gift) DeleteGift(giftId string) + + SaveAttribute(attribute *DB_PAttribute) DeleteAttribute(attributeId string) } @@ -31,30 +44,18 @@ func NewStorage(s Storage) *Repository { } } -func (r *Repository) GetPerson(personId string) *DB_Person { - cachePerson := Cache.GetPerson(personId) - if cachePerson != nil { - return cachePerson - } - +func (r *Repository) GetPersonFromDB(personId string) *DB_Person { storagePerson := r.Storage.GetPerson(personId) if storagePerson != nil { - Cache.SavePerson(storagePerson) return storagePerson } return nil } -func (r *Repository) GetPersonByDisplay(displayName string) *DB_Person { - cachePerson := Cache.GetPersonByDisplay(displayName) - if cachePerson != nil { - return cachePerson - } - +func (r *Repository) GetPersonByDisplayFromDB(displayName string) *DB_Person { storagePerson := r.Storage.GetPersonByDisplay(displayName) if storagePerson != nil { - Cache.SavePerson(storagePerson) return storagePerson } @@ -66,30 +67,61 @@ func (r *Repository) GetAllPersons() []*DB_Person { } func (r *Repository) SavePerson(person *DB_Person) { - Cache.SavePerson(person) r.Storage.SavePerson(person) } +func (r *Repository) SaveProfile(profile *DB_Profile) { + r.Storage.SaveProfile(profile) +} + +func (r *Repository) DeleteProfile(profileId string) { + r.Storage.DeleteProfile(profileId) +} + +func (r *Repository) SaveItem(item *DB_Item) { + r.Storage.SaveItem(item) +} + func (r *Repository) DeleteItem(itemId string) { r.Storage.DeleteItem(itemId) } +func (r *Repository) SaveVariant(variant *DB_VariantChannel) { + r.Storage.SaveVariant(variant) +} + func (r *Repository) DeleteVariant(variantId string) { r.Storage.DeleteVariant(variantId) } +func (r *Repository) SaveQuest(quest *DB_Quest) { + r.Storage.SaveQuest(quest) +} + func (r *Repository) DeleteQuest(questId string) { r.Storage.DeleteQuest(questId) } +func (r *Repository) SaveLoot(loot *DB_Loot) { + r.Storage.SaveLoot(loot) +} + func (r *Repository) DeleteLoot(lootId string) { r.Storage.DeleteLoot(lootId) } +func (r *Repository) SaveGift(gift *DB_Gift) { + r.Storage.SaveGift(gift) +} + func (r *Repository) DeleteGift(giftId string) { r.Storage.DeleteGift(giftId) } +func (r *Repository) SaveAttribute(attribute *DB_PAttribute) { + r.Storage.SaveAttribute(attribute) +} + func (r *Repository) DeleteAttribute(attributeId string) { r.Storage.DeleteAttribute(attributeId) } \ No newline at end of file diff --git a/storage/tables.go b/storage/tables.go index 6a853d2..8d37957 100644 --- a/storage/tables.go +++ b/storage/tables.go @@ -7,45 +7,24 @@ type Tabler interface { } type DB_Person struct { - ID string + ID string DisplayName string - AccessKey string - Profiles []DB_Profile `gorm:"foreignkey:PersonID"` - Loadout DB_Loadout `gorm:"foreignkey:PersonID"` + AccessKey string + Profiles []DB_Profile `gorm:"foreignkey:PersonID"` } func (DB_Person) TableName() string { return "Persons" } -type DB_Loadout struct { - ID string `gorm:"primary_key"` - PersonID string - Character string - Backpack string - Pickaxe string - Glider string - Dances pq.StringArray `gorm:"type:text[]"` - ItemWraps pq.StringArray `gorm:"type:text[]"` - LoadingScreen string - SkyDiveContrail string - MusicPack string - BannerIcon string - BannerColor string -} - -func (DB_Loadout) TableName() string { - return "Loadouts" -} - type DB_Profile struct { - ID string `gorm:"primary_key"` - PersonID string - Items []DB_Item `gorm:"foreignkey:ProfileID"` - Gifts []DB_Gift `gorm:"foreignkey:ProfileID"` - Quests []DB_Quest `gorm:"foreignkey:ProfileID"` + ID string `gorm:"primary_key"` + PersonID string + Items []DB_Item `gorm:"foreignkey:ProfileID"` + Gifts []DB_Gift `gorm:"foreignkey:ProfileID"` + Quests []DB_Quest `gorm:"foreignkey:ProfileID"` Attributes []DB_PAttribute `gorm:"foreignkey:ProfileID"` - Type string + Type string Revision int } @@ -54,11 +33,11 @@ func (DB_Profile) TableName() string { } type DB_PAttribute struct { - ID string `gorm:"primary_key"` + ID string `gorm:"primary_key"` ProfileID string - Key string + Key string ValueJSON string - Type string + Type string } func (DB_PAttribute) TableName() string { @@ -66,13 +45,13 @@ func (DB_PAttribute) TableName() string { } type DB_Item struct { - ID string `gorm:"primary_key"` - ProfileID string + ID string `gorm:"primary_key"` + ProfileID string TemplateID string - Quantity int - Favorite bool - HasSeen bool - Variants []DB_VariantChannel `gorm:"foreignkey:ItemID"` + Quantity int + Favorite bool + HasSeen bool + Variants []DB_VariantChannel `gorm:"foreignkey:ItemID"` } func (DB_Item) TableName() string { @@ -80,11 +59,11 @@ func (DB_Item) TableName() string { } type DB_VariantChannel struct { - ID string `gorm:"primary_key"` - ItemID string + ID string `gorm:"primary_key"` + ItemID string Channel string - Owned pq.StringArray `gorm:"type:text[]"` - Active string + Owned pq.StringArray `gorm:"type:text[]"` + Active string } func (DB_VariantChannel) TableName() string { @@ -92,13 +71,13 @@ func (DB_VariantChannel) TableName() string { } type DB_Quest struct { - ID string `gorm:"primary_key"` - ProfileID string + ID string `gorm:"primary_key"` + ProfileID string TemplateID string - State string + State string Objectives pq.StringArray `gorm:"type:text[]"` ObjectiveCounts pq.Int64Array `gorm:"type:bigint[]"` - BundleID string + BundleID string ScheduleID string } @@ -107,14 +86,14 @@ func (DB_Quest) TableName() string { } type DB_Gift struct { - ID string `gorm:"primary_key"` - ProfileID string + ID string `gorm:"primary_key"` + ProfileID string TemplateID string - Quantity int - FromID string - GiftedAt int64 - Message string - Loot []DB_Loot `gorm:"foreignkey:GiftID"` + Quantity int + FromID string + GiftedAt int64 + Message string + Loot []DB_Loot `gorm:"foreignkey:GiftID"` } func (DB_Gift) TableName() string { @@ -122,11 +101,11 @@ func (DB_Gift) TableName() string { } type DB_Loot struct { - ID string `gorm:"primary_key"` - GiftID string + ID string `gorm:"primary_key"` + GiftID string TemplateID string - Quantity int - ProfileType string + Quantity int + ProfileType string } func (DB_Loot) TableName() string {