diff --git a/main.go b/main.go index 75f6e299..25446f24 100644 --- a/main.go +++ b/main.go @@ -129,6 +129,7 @@ func configure(app *aero.Application) *aero.Application { app.Ajax("/shop", shop.Get) app.Ajax("/inventory", inventory.Get) app.Ajax("/charge", charge.Get) + app.Post("/api/shop/buy/:item/:quantity", shop.BuyItem) // Admin app.Ajax("/admin", admin.Get) diff --git a/mixins/Sidebar.pixy b/mixins/Sidebar.pixy index a9124078..e87709ea 100644 --- a/mixins/Sidebar.pixy +++ b/mixins/Sidebar.pixy @@ -20,7 +20,9 @@ component Sidebar(user *arn.User) //- SidebarButton("Search", "/search", "search") if user != nil - SidebarButton("Shop", "/shop", "shopping-cart") + if user.Role == "admin" || user.Role == "editor" + SidebarButton("Shop", "/shop", "shopping-cart") + SidebarButton("Statistics", "/statistics", "pie-chart") SidebarButton("Settings", "/settings", "cog") diff --git a/pages/inventory/inventory.pixy b/pages/inventory/inventory.pixy index 22b9b911..7eada3df 100644 --- a/pages/inventory/inventory.pixy +++ b/pages/inventory/inventory.pixy @@ -1,6 +1,8 @@ component Inventory(inventory *arn.Inventory, viewUser *arn.User, user *arn.User) ShopTabs(user) + h1.page-title Inventory + .inventory(data-api="/api/inventory/" + viewUser.ID) for index, slot := range inventory.Slots if slot.ItemID == "" diff --git a/pages/shop/shop.go b/pages/shop/shop.go index 16ec90a1..928fe12c 100644 --- a/pages/shop/shop.go +++ b/pages/shop/shop.go @@ -3,6 +3,7 @@ package shop import ( "net/http" "sort" + "sync" "github.com/animenotifier/arn" @@ -11,6 +12,8 @@ import ( "github.com/animenotifier/notify.moe/utils" ) +var itemBuyMutex sync.Mutex + // Get shop page. func Get(ctx *aero.Context) string { user := utils.GetUser(ctx) @@ -31,3 +34,56 @@ func Get(ctx *aero.Context) string { return ctx.HTML(components.Shop(user, items)) } + +// BuyItem ... +func BuyItem(ctx *aero.Context) string { + // Lock via mutex to prevent race conditions + itemBuyMutex.Lock() + defer itemBuyMutex.Unlock() + + // Logged in user + user := utils.GetUser(ctx) + + if user == nil { + return ctx.Error(http.StatusUnauthorized, "Not logged in", nil) + } + + // Item ID and quantity + itemID := ctx.Get("item") + quantity, err := ctx.GetInt("quantity") + + if err != nil || quantity == 0 { + return ctx.Error(http.StatusBadRequest, "Invalid item quantity", err) + } + + item, err := arn.GetItem(itemID) + + if err != nil { + return ctx.Error(http.StatusInternalServerError, "Error fetching item data", err) + } + + // Calculate total price and subtract balance + totalPrice := int(item.Price) * quantity + + if user.Balance < totalPrice { + return ctx.Error(http.StatusBadRequest, "Not enough gems", nil) + } + + user.Balance -= totalPrice + err = user.Save() + + if err != nil { + return ctx.Error(http.StatusInternalServerError, "Error saving user data", err) + } + + // Add item to user inventory + inventory := user.Inventory() + inventory.AddItem(itemID, uint(quantity)) + err = inventory.Save() + + if err != nil { + return ctx.Error(http.StatusInternalServerError, "Error saving inventory", err) + } + + return "ok" +} diff --git a/pages/shop/shop.pixy b/pages/shop/shop.pixy index e741f368..8556163e 100644 --- a/pages/shop/shop.pixy +++ b/pages/shop/shop.pixy @@ -1,8 +1,8 @@ component Shop(user *arn.User, items []*arn.Item) - h1.page-title Shop - ShopTabs(user) + h1.page-title Shop + .widgets.shop-items each item in items ShopItem(item) @@ -21,6 +21,6 @@ component ShopItem(item *arn.Item) //- span.shop-item-duration= " " + duration .shop-item-description!= aero.Markdown(item.Description) .buttons.shop-buttons - button.shop-button-buy + button.shop-button-buy.action(data-item-id=item.ID, data-item-name=item.Name, data-price=item.Price, data-trigger="click", data-action="buyItem") span.shop-item-price= item.Price Icon("diamond") \ No newline at end of file diff --git a/patches/add-balance/add-balance.go b/patches/add-balance/add-balance.go new file mode 100644 index 00000000..8289aab3 --- /dev/null +++ b/patches/add-balance/add-balance.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/animenotifier/arn" + "github.com/fatih/color" +) + +func main() { + color.Yellow("Adding balance to all users") + + // Get a stream of all users + allUsers, err := arn.StreamUsers() + arn.PanicOnError(err) + + // Iterate over the stream + for user := range allUsers { + user.Balance += 100000 + arn.PanicOnError(user.Save()) + } + + color.Green("Finished.") +} diff --git a/patches/add-inventories/add-inventories.go b/patches/add-inventories/add-inventories.go index c9c591a3..28966b6d 100644 --- a/patches/add-inventories/add-inventories.go +++ b/patches/add-inventories/add-inventories.go @@ -16,19 +16,19 @@ func main() { // Iterate over the stream for user := range allUsers { - // exists, err := arn.DB.Exists("Inventory", user.ID) + exists, err := arn.DB.Exists("Inventory", user.ID) - // if err != nil || exists { - // continue - // } + if err != nil || exists { + continue + } fmt.Println(user.Nick) inventory := arn.NewInventory(user.ID) - // TEST - inventory.AddItem("anime-support-ticket", 50) - inventory.AddItem("pro-account-24", 30) + // // TEST + // inventory.AddItem("anime-support-ticket", 50) + // inventory.AddItem("pro-account-24", 30) err = arn.DB.Set("Inventory", inventory.UserID, inventory) diff --git a/scripts/Actions.ts b/scripts/Actions.ts index 68f70029..24b08867 100644 --- a/scripts/Actions.ts +++ b/scripts/Actions.ts @@ -336,6 +336,35 @@ export function chargeUp(arn: AnimeNotifier, button: HTMLElement) { .then(() => arn.loading(false)) } +// Buy item +export function buyItem(arn: AnimeNotifier, button: HTMLElement) { + let itemId = button.dataset.itemId + let itemName = button.dataset.itemName + let price = button.dataset.price + + if(!confirm(`Would you like to buy ${itemName} for ${price} gems?`)) { + return + } + + arn.loading(true) + + fetch(`/api/shop/buy/${itemId}/1`, { + method: "POST", + credentials: "same-origin" + }) + .then(response => response.text()) + .then(body => { + if(body !== "ok") { + throw body + } + + return arn.reloadContent() + }) + .then(() => arn.statusMessage.showInfo(`You bought ${itemName} for ${price} gems. Check out your inventory to confirm the purchase.`, 4000)) + .catch(err => arn.statusMessage.showError(err)) + .then(() => arn.loading(false)) +} + // Chrome extension installation export function installExtension(arn: AnimeNotifier, button: HTMLElement) { let browser: any = window["chrome"] diff --git a/scripts/AnimeNotifier.ts b/scripts/AnimeNotifier.ts index ed7eb906..606e8cb5 100644 --- a/scripts/AnimeNotifier.ts +++ b/scripts/AnimeNotifier.ts @@ -471,6 +471,10 @@ export class AnimeNotifier { element.removeEventListener(oldAction.trigger, oldAction.handler) } + if(!(actionName in actions)) { + this.statusMessage.showError(`Action '${actionName}' has not been defined`) + } + let actionHandler = e => { actions[actionName](this, element, e)