Update profiles to add MultiUpdates and Notifications;

Add purchasing form storefront!
This commit is contained in:
eccentric 2023-12-03 20:16:40 +00:00
parent 4336da2e4d
commit f4fa7866fd
6 changed files with 239 additions and 40 deletions

View File

@ -28,7 +28,9 @@ func NewFortnitePerson(displayName string, key string) *p.Person {
person.AccessKey = key person.AccessKey = key
for _, item := range defaultAthenaItems { for _, item := range defaultAthenaItems {
person.AthenaProfile.Items.AddItem(p.NewItem(item, 1)) item := p.NewItem(item, 1)
item.HasSeen = true
person.AthenaProfile.Items.AddItem(item)
} }
for _, item := range defaultCommonCoreItems { for _, item := range defaultCommonCoreItems {
@ -51,10 +53,8 @@ func NewFortnitePerson(displayName string, key string) *p.Person {
} }
if item == "Currency:MtxPurchased" { if item == "Currency:MtxPurchased" {
item := p.NewItem(item, 0) person.CommonCoreProfile.Items.AddItem(p.NewItem(item, 9999)).Save()
item.HasSeen = true person.Profile0Profile.Items.AddItem(p.NewItem(item, 9999)).Save()
person.CommonCoreProfile.Items.AddItem(item).Save()
person.Profile0Profile.Items.AddItem(item).Save()
continue continue
} }

View File

@ -58,7 +58,7 @@ func (c *Catalog) GenerateFortniteCatalog(p *person.Person) aid.JSON {
return json return json
} }
func (c *Catalog) IsDuplicate(entry Entry) bool { func (c *Catalog) CheckIfOfferIsDuplicate(entry Entry) bool {
for _, storefront := range c.Storefronts { for _, storefront := range c.Storefronts {
for _, catalogEntry := range storefront.CatalogEntries { for _, catalogEntry := range storefront.CatalogEntries {
if catalogEntry.Grants[0] == entry.Grants[0] { if catalogEntry.Grants[0] == entry.Grants[0] {
@ -70,6 +70,18 @@ func (c *Catalog) IsDuplicate(entry Entry) bool {
return false return false
} }
func (c *Catalog) GetOfferById(id string) *Entry {
for _, storefront := range c.Storefronts {
for _, catalogEntry := range storefront.CatalogEntries {
if catalogEntry.ID == id {
return &catalogEntry
}
}
}
return nil
}
type Storefront struct { type Storefront struct {
Name string `json:"name"` Name string `json:"name"`
CatalogEntries []Entry `json:"catalogEntries"` CatalogEntries []Entry `json:"catalogEntries"`
@ -111,12 +123,14 @@ type Entry struct {
NewDisplayAssetPath string NewDisplayAssetPath string
Title string Title string
ShortDescription string ShortDescription string
ProfileType string
} }
func NewCatalogEntry(meta ...aid.JSON) *Entry { func NewCatalogEntry(profile string, meta ...aid.JSON) *Entry {
return &Entry{ return &Entry{
ID: uuid.New().String(), ID: uuid.New().String(),
Meta: meta, Meta: meta,
ProfileType: profile,
} }
} }
@ -305,7 +319,7 @@ func GenerateStorefront() {
} }
item := Cosmetics.GetRandomItemByType("AthenaCharacter") item := Cosmetics.GetRandomItemByType("AthenaCharacter")
entry := NewCatalogEntry() entry := NewCatalogEntry("athena")
entry.SetSection("Daily") entry.SetSection("Daily")
if !entry.IsNewDisplayAssetValid(item.ID) { if !entry.IsNewDisplayAssetValid(item.ID) {
@ -322,7 +336,11 @@ func GenerateStorefront() {
entry.SetTileSize("Normal") entry.SetTileSize("Normal")
entry.Priority = 1 entry.Priority = 1
if storefront.IsDuplicate(*entry) { if item.Backpack != "" {
entry.AddGrant("AthenaBackpack:" + item.Backpack)
}
if storefront.CheckIfOfferIsDuplicate(*entry) {
continue continue
} }
@ -331,7 +349,7 @@ func GenerateStorefront() {
for i := 0; i < 6; i++ { for i := 0; i < 6; i++ {
item := Cosmetics.GetRandomItemByNotType("AthenaCharacter") item := Cosmetics.GetRandomItemByNotType("AthenaCharacter")
entry := NewCatalogEntry() entry := NewCatalogEntry("athena")
entry.SetSection("Daily") entry.SetSection("Daily")
if !entry.IsNewDisplayAssetValid(item.ID) { if !entry.IsNewDisplayAssetValid(item.ID) {
@ -347,7 +365,7 @@ func GenerateStorefront() {
entry.AddGrant(item.Type.BackendValue + ":" + item.ID) entry.AddGrant(item.Type.BackendValue + ":" + item.ID)
entry.SetTileSize("Small") entry.SetTileSize("Small")
if storefront.IsDuplicate(*entry) { if storefront.CheckIfOfferIsDuplicate(*entry) {
continue continue
} }
@ -366,7 +384,7 @@ func GenerateStorefront() {
itemsAdded := 0 itemsAdded := 0
itemsToAdd := []*Entry{} itemsToAdd := []*Entry{}
for _, item := range set.Items { for _, item := range set.Items {
entry := NewCatalogEntry() entry := NewCatalogEntry("athena")
entry.SetSection("Featured") entry.SetSection("Featured")
entry.SetPanel(set.BackendName) entry.SetPanel(set.BackendName)
@ -402,7 +420,7 @@ func GenerateStorefront() {
} }
for _, entry := range itemsToAdd { for _, entry := range itemsToAdd {
if storefront.IsDuplicate(*entry) { if storefront.CheckIfOfferIsDuplicate(*entry) {
continue continue
} }
@ -416,4 +434,5 @@ func GenerateStorefront() {
storefront.Add(weekly) storefront.Add(weekly)
StaticCatalog = storefront StaticCatalog = storefront
aid.Print("Generated new random storefront")
} }

View File

@ -7,13 +7,14 @@ import (
"time" "time"
"github.com/ectrc/snow/aid" "github.com/ectrc/snow/aid"
"github.com/ectrc/snow/fortnite"
p "github.com/ectrc/snow/person" p "github.com/ectrc/snow/person"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
var ( var (
profileActions = map[string]func(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { profileActions = map[string]func(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
"QueryProfile": PostQueryProfileAction, "QueryProfile": PostQueryProfileAction,
"ClientQuestLogin": PostQueryProfileAction, "ClientQuestLogin": PostQueryProfileAction,
"MarkItemSeen": PostMarkItemSeenAction, "MarkItemSeen": PostMarkItemSeenAction,
@ -22,6 +23,7 @@ var (
"SetBattleRoyaleBanner": PostSetBattleRoyaleBannerAction, "SetBattleRoyaleBanner": PostSetBattleRoyaleBannerAction,
"SetCosmeticLockerSlot": PostSetCosmeticLockerSlotAction, "SetCosmeticLockerSlot": PostSetCosmeticLockerSlotAction,
"SetCosmeticLockerBanner": PostSetCosmeticLockerBannerAction, "SetCosmeticLockerBanner": PostSetCosmeticLockerBannerAction,
"PurchaseCatalogEntry": PostPurchaseCatalogEntryAction,
} }
) )
@ -32,27 +34,74 @@ func PostProfileAction(c *fiber.Ctx) error {
} }
profile := person.GetProfileFromType(c.Query("profileId")) profile := person.GetProfileFromType(c.Query("profileId"))
if action, ok := profileActions[c.Params("action")]; ok && profile != nil { if profile == nil {
return c.Status(404).JSON(aid.ErrorBadRequest("No Profile Found"))
}
defer profile.ClearProfileChanges() defer profile.ClearProfileChanges()
before := profile.Snapshot()
if err := action(c, person, profile); err != nil { profileSnapshots := map[string]*p.ProfileSnapshot{
"athena": nil,
"common_core": nil,
"common_public": nil,
}
for key := range profileSnapshots {
profileSnapshots[key] = person.GetProfileFromType(key).Snapshot()
}
notifications := []aid.JSON{}
action, ok := profileActions[c.Params("action")];
if ok && profile != nil {
if err := action(c, person, profile, &notifications); err != nil {
return c.Status(400).JSON(aid.ErrorBadRequest(err.Error())) return c.Status(400).JSON(aid.ErrorBadRequest(err.Error()))
} }
profile.Diff(before) }
for key, profileSnapshot := range profileSnapshots {
profile := person.GetProfileFromType(key)
if profile == nil {
continue
}
if profileSnapshot == nil {
continue
}
profile.Diff(profileSnapshot)
} }
revision, _ := strconv.Atoi(c.Query("rvn")) revision, _ := strconv.Atoi(c.Query("rvn"))
if revision == -1 && profile == nil { if revision == -1 {
revision = 1
}
if revision == -1 && profile != nil {
revision = profile.Revision revision = profile.Revision
} }
revision++ revision++
profile.Revision = revision
profile.Save()
delete(profileSnapshots, profile.Type)
changes := []interface{}{} multiUpdate := []aid.JSON{}
if profile != nil { for key := range profileSnapshots {
changes = profile.Changes profile := person.GetProfileFromType(key)
if profile == nil {
continue
}
profile.Revision++
if len(profile.Changes) == 0 {
continue
}
multiUpdate = append(multiUpdate, aid.JSON{
"profileId": profile.Type,
"profileRevision": profile.Revision,
"profileCommandRevision": profile.Revision,
"profileChangesBaseRevision": profile.Revision - 1,
"profileChanges": profile.Changes,
})
profile.ClearProfileChanges()
profile.Save()
} }
return c.Status(200).JSON(aid.JSON{ return c.Status(200).JSON(aid.JSON{
@ -60,20 +109,20 @@ func PostProfileAction(c *fiber.Ctx) error {
"profileRevision": revision, "profileRevision": revision,
"profileCommandRevision": revision, "profileCommandRevision": revision,
"profileChangesBaseRevision": revision - 1, "profileChangesBaseRevision": revision - 1,
"profileChanges": changes, "profileChanges": profile.Changes,
"multiUpdate": []aid.JSON{}, "multiUpdate": multiUpdate,
"notifications": []aid.JSON{}, "notifications": notifications,
"responseVersion": 1, "responseVersion": 1,
"serverTime": time.Now().Format("2006-01-02T15:04:05.999Z"), "serverTime": time.Now().Format("2006-01-02T15:04:05.999Z"),
}) })
} }
func PostQueryProfileAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { func PostQueryProfileAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
profile.CreateFullProfileUpdateChange() profile.CreateFullProfileUpdateChange()
return nil return nil
} }
func PostMarkItemSeenAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { func PostMarkItemSeenAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct { var body struct {
ItemIds []string `json:"itemIds"` ItemIds []string `json:"itemIds"`
} }
@ -96,7 +145,7 @@ func PostMarkItemSeenAction(c *fiber.Ctx, person *p.Person, profile *p.Profile)
return nil return nil
} }
func PostEquipBattleRoyaleCustomizationAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { func PostEquipBattleRoyaleCustomizationAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct { var body struct {
SlotName string `json:"slotName" binding:"required"` SlotName string `json:"slotName" binding:"required"`
ItemToSlot string `json:"itemToSlot"` ItemToSlot string `json:"itemToSlot"`
@ -141,7 +190,7 @@ func PostEquipBattleRoyaleCustomizationAction(c *fiber.Ctx, person *p.Person, pr
return nil return nil
} }
func PostSetBattleRoyaleBannerAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { func PostSetBattleRoyaleBannerAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct { var body struct {
HomebaseBannerColorID string `json:"homebaseBannerColorId" binding:"required"` HomebaseBannerColorID string `json:"homebaseBannerColorId" binding:"required"`
HomebaseBannerIconID string `json:"homebaseBannerIconId" binding:"required"` HomebaseBannerIconID string `json:"homebaseBannerIconId" binding:"required"`
@ -182,7 +231,7 @@ func PostSetBattleRoyaleBannerAction(c *fiber.Ctx, person *p.Person, profile *p.
return nil return nil
} }
func PostSetItemFavoriteStatusBatchAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { func PostSetItemFavoriteStatusBatchAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct { var body struct {
ItemIds []string `json:"itemIds" binding:"required"` ItemIds []string `json:"itemIds" binding:"required"`
Favorite []bool `json:"itemFavStatus" binding:"required"` Favorite []bool `json:"itemFavStatus" binding:"required"`
@ -206,7 +255,7 @@ func PostSetItemFavoriteStatusBatchAction(c *fiber.Ctx, person *p.Person, profil
return nil return nil
} }
func PostSetCosmeticLockerSlotAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { func PostSetCosmeticLockerSlotAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct { var body struct {
Category string `json:"category" binding:"required"` // item type e.g. Character Category string `json:"category" binding:"required"` // item type e.g. Character
ItemToSlot string `json:"itemToSlot" binding:"required"` // template id ItemToSlot string `json:"itemToSlot" binding:"required"` // template id
@ -275,7 +324,7 @@ func PostSetCosmeticLockerSlotAction(c *fiber.Ctx, person *p.Person, profile *p.
return nil return nil
} }
func PostSetCosmeticLockerBannerAction(c *fiber.Ctx, person *p.Person, profile *p.Profile) error { func PostSetCosmeticLockerBannerAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct { var body struct {
LockerItem string `json:"lockerItem" binding:"required"` // locker id LockerItem string `json:"lockerItem" binding:"required"` // locker id
BannerColorTemplateName string `json:"bannerColorTemplateName" binding:"required"` // template id BannerColorTemplateName string `json:"bannerColorTemplateName" binding:"required"` // template id
@ -294,7 +343,6 @@ func PostSetCosmeticLockerBannerAction(c *fiber.Ctx, person *p.Person, profile *
icon := profile.Items.GetItemByTemplateID("HomebaseBannerIcon:" + body.BannerIconTemplateName) icon := profile.Items.GetItemByTemplateID("HomebaseBannerIcon:" + body.BannerIconTemplateName)
if icon == nil { if icon == nil {
// return fmt.Errorf("icon item not found")
icon = &p.Item{ icon = &p.Item{
ID: body.BannerIconTemplateName, ID: body.BannerIconTemplateName,
} }
@ -311,3 +359,80 @@ func PostSetCosmeticLockerBannerAction(c *fiber.Ctx, person *p.Person, profile *
go currentLocker.Save() go currentLocker.Save()
return nil return nil
} }
func PostPurchaseCatalogEntryAction(c *fiber.Ctx, person *p.Person, profile *p.Profile, notifications *[]aid.JSON) error {
var body struct{
OfferID string `json:"offerId" binding:"required"`
PurchaseQuantity int `json:"purchaseQuantity" binding:"required"`
ExpectedTotalPrice int `json:"expectedTotalPrice" binding:"required"`
}
err := c.BodyParser(&body)
if err != nil {
return fmt.Errorf("invalid Body")
}
offer := fortnite.StaticCatalog.GetOfferById(body.OfferID)
if offer == nil {
return fmt.Errorf("offer not found")
}
if offer.Price != body.ExpectedTotalPrice {
return fmt.Errorf("invalid price")
}
vbucks := profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
if vbucks == nil {
return fmt.Errorf("vbucks not found")
}
profile0Vbucks := person.Profile0Profile.Items.GetItemByTemplateID("Currency:MtxPurchased")
if profile0Vbucks == nil {
return fmt.Errorf("profile0vbucks not found")
}
if vbucks.Quantity < body.ExpectedTotalPrice {
return fmt.Errorf("not enough vbucks")
}
vbucks.Quantity -= body.ExpectedTotalPrice
go func() {
profile0Vbucks.Quantity = vbucks.Quantity // for season 2 and lower
vbucks.Save()
profile0Vbucks.Save()
}()
if offer.ProfileType != "athena" {
return fmt.Errorf("save the world not implemeted yet!")
}
loot := []aid.JSON{}
for i := 0; i < body.PurchaseQuantity; i++ {
for _, grant := range offer.Grants {
if profile.Items.GetItemByTemplateID(grant) != nil {
continue
}
item := p.NewItem(grant, 1)
person.AthenaProfile.Items.AddItem(item)
loot = append(loot, aid.JSON{
"itemType": item.TemplateID,
"itemGuid": item.ID,
"quantity": item.Quantity,
"itemProfile": offer.ProfileType,
})
}
}
*notifications = append(*notifications, aid.JSON{
"type": "CatalogPurchase",
"lootResult": aid.JSON{
"items": loot,
},
"primary": true,
})
return nil
}

View File

@ -13,6 +13,7 @@ type Person struct {
CommonCoreProfile *Profile CommonCoreProfile *Profile
CommonPublicProfile *Profile CommonPublicProfile *Profile
Profile0Profile *Profile Profile0Profile *Profile
CollectionsProfile *Profile
} }
type Option struct { type Option struct {
@ -29,6 +30,7 @@ func NewPerson() *Person {
CommonCoreProfile: NewProfile("common_core"), CommonCoreProfile: NewProfile("common_core"),
CommonPublicProfile: NewProfile("common_public"), CommonPublicProfile: NewProfile("common_public"),
Profile0Profile: NewProfile("profile0"), Profile0Profile: NewProfile("profile0"),
CollectionsProfile: NewProfile("collections"),
} }
} }
@ -73,6 +75,7 @@ func findHelper(databasePerson *storage.DB_Person) *Person {
commonCoreProfile := NewProfile("common_core") commonCoreProfile := NewProfile("common_core")
commonPublicProfile := NewProfile("common_public") commonPublicProfile := NewProfile("common_public")
profile0 := NewProfile("profile0") profile0 := NewProfile("profile0")
collectionsProfile := NewProfile("collections")
for _, profile := range databasePerson.Profiles { for _, profile := range databasePerson.Profiles {
if profile.Type == "athena" { if profile.Type == "athena" {
@ -94,6 +97,11 @@ func findHelper(databasePerson *storage.DB_Person) *Person {
profile0.ID = profile.ID profile0.ID = profile.ID
profile0 = FromDatabaseProfile(&profile) profile0 = FromDatabaseProfile(&profile)
} }
if profile.Type == "collections" {
collectionsProfile.ID = profile.ID
collectionsProfile = FromDatabaseProfile(&profile)
}
} }
person := &Person{ person := &Person{
@ -104,6 +112,7 @@ func findHelper(databasePerson *storage.DB_Person) *Person {
CommonCoreProfile: commonCoreProfile, CommonCoreProfile: commonCoreProfile,
CommonPublicProfile: commonPublicProfile, CommonPublicProfile: commonPublicProfile,
Profile0Profile: profile0, Profile0Profile: profile0,
CollectionsProfile: collectionsProfile,
} }
cache.SavePerson(person) cache.SavePerson(person)
@ -144,6 +153,8 @@ func (p *Person) GetProfileFromType(profileType string) *Profile {
return p.CommonPublicProfile return p.CommonPublicProfile
case "profile0": case "profile0":
return p.Profile0Profile return p.Profile0Profile
case "collections":
return p.CollectionsProfile
} }
return nil return nil
@ -167,6 +178,7 @@ func (p *Person) ToDatabase() *storage.DB_Person {
"athena": p.AthenaProfile, "athena": p.AthenaProfile,
"common_public": p.CommonPublicProfile, "common_public": p.CommonPublicProfile,
"profile0": p.Profile0Profile, "profile0": p.Profile0Profile,
"collections": p.CollectionsProfile,
} }
for profileType, profile := range profilesToConvert { for profileType, profile := range profilesToConvert {
@ -234,5 +246,6 @@ func (p *Person) Snapshot() *PersonSnapshot {
CommonCoreProfile: *p.CommonCoreProfile.Snapshot(), CommonCoreProfile: *p.CommonCoreProfile.Snapshot(),
CommonPublicProfile: *p.CommonPublicProfile.Snapshot(), CommonPublicProfile: *p.CommonPublicProfile.Snapshot(),
Profile0Profile: *p.Profile0Profile.Snapshot(), Profile0Profile: *p.Profile0Profile.Snapshot(),
CollectionsProfile: *p.CollectionsProfile.Snapshot(),
} }
} }

View File

@ -129,7 +129,7 @@ func (p *Profile) GenerateFortniteProfileEntry() aid.JSON {
} }
func (p *Profile) Save() { func (p *Profile) Save() {
// storage.Repo.SaveProfile(p.ToDatabase()) storage.Repo.SaveProfile(p.ToDatabase())
} }
func (p *Profile) Snapshot() *ProfileSnapshot { func (p *Profile) Snapshot() *ProfileSnapshot {
@ -399,3 +399,44 @@ func (p *Profile) CreateFullProfileUpdateChange() {
func (p *Profile) ClearProfileChanges() { func (p *Profile) ClearProfileChanges() {
p.Changes = []interface{}{} p.Changes = []interface{}{}
} }
func (p *Profile) ToDatabase() *storage.DB_Profile {
dbProfile := storage.DB_Profile{
ID: p.ID,
PersonID: p.PersonID,
Type: p.Type,
Items: []storage.DB_Item{},
Gifts: []storage.DB_Gift{},
Quests: []storage.DB_Quest{},
Loadouts: []storage.DB_Loadout{},
Attributes: []storage.DB_PAttribute{},
Revision: p.Revision,
}
// p.Items.RangeItems(func(id string, item *Item) bool {
// dbProfile.Items = append(dbProfile.Items, *item.ToDatabase(dbProfile.PersonID))
// return true
// })
p.Gifts.RangeGifts(func(id string, gift *Gift) bool {
dbProfile.Gifts = append(dbProfile.Gifts, *gift.ToDatabase(dbProfile.PersonID))
return true
})
p.Quests.RangeQuests(func(id string, quest *Quest) bool {
dbProfile.Quests = append(dbProfile.Quests, *quest.ToDatabase(dbProfile.PersonID))
return true
})
p.Attributes.RangeAttributes(func(key string, value *Attribute) bool {
dbProfile.Attributes = append(dbProfile.Attributes, *value.ToDatabase(dbProfile.PersonID))
return true
})
p.Loadouts.RangeLoadouts(func(id string, loadout *Loadout) bool {
dbProfile.Loadouts = append(dbProfile.Loadouts, *loadout.ToDatabase(dbProfile.PersonID))
return true
})
return &dbProfile
}

View File

@ -7,6 +7,7 @@ type PersonSnapshot struct {
CommonCoreProfile ProfileSnapshot CommonCoreProfile ProfileSnapshot
CommonPublicProfile ProfileSnapshot CommonPublicProfile ProfileSnapshot
Profile0Profile ProfileSnapshot Profile0Profile ProfileSnapshot
CollectionsProfile ProfileSnapshot
} }
type ProfileSnapshot struct { type ProfileSnapshot struct {