diff --git a/examples/gno.land/r/jjoptimist/eventix/eventix.gno b/examples/gno.land/r/jjoptimist/eventix/eventix.gno new file mode 100644 index 00000000000..2f9421719b8 --- /dev/null +++ b/examples/gno.land/r/jjoptimist/eventix/eventix.gno @@ -0,0 +1,119 @@ +package eventix + +import ( + "std" + "strconv" + "time" + + "gno.land/p/demo/avl" + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/ufmt" +) + +type Event struct { + name string + description string + date time.Time + maxTickets int + price uint64 // in gnot + ticketsSold int +} + +var ( + events *avl.Tree + eventCounter uint64 = 0 + tickets = grc721.NewBasicNFT("Event Ticket", "EVTIX") +) + +func init() { + events = avl.NewTree() +} + + +func CreateEvent(name, description string, dateStr string, maxTickets int, price uint64) uint64 { + + date, err := time.Parse("2006-01-02T15:04:05Z", dateStr) + if err != nil { + panic("Invalid date format. Use: YYYY-MM-DDThh:mm:ssZ") + } + + eventCounter++ + event := Event{ + name: name, + description: description, + date: date, + maxTickets: maxTickets, + price: price, + ticketsSold: 0, + } + events.Set(strconv.FormatUint(eventCounter, 10), event) + return eventCounter +} + + +func getEvent(eventId uint64) (Event, bool) { + value, exists := events.Get(strconv.FormatUint(eventId, 10)) + if !exists { + return Event{}, false + } + return value.(Event), true +} + + +func setEvent(eventId uint64, event Event) { + events.Set(strconv.FormatUint(eventId, 10), event) +} + + +func BuyTicket(eventId uint64) { + event, exists := getEvent(eventId) + if !exists { + panic("Event does not exist") + } + + if event.ticketsSold >= event.maxTickets { + panic("Event is sold out") + } + + + sent := std.GetOrigSend() + amount := sent.AmountOf("ugnot") + if amount != int64(event.price) { + panic(ufmt.Sprintf("Please send exactly %d ugnot", event.price)) + } + + + caller := std.PrevRealm().Addr() + tokenId := grc721.TokenID(strconv.FormatUint(eventId, 10) + "-" + strconv.Itoa(event.ticketsSold+1)) + tickets.Mint(caller, tokenId) + + + event.ticketsSold++ + setEvent(eventId, event) +} + +// all events and ticket info +func Render(path string) string { + output := "# Event Ticketing System\n\n" + + events.Iterate("", "", func(key string, value interface{}) bool { + id, _ := strconv.ParseUint(key, 10, 64) + event := value.(Event) + + output += ufmt.Sprintf("## Event #%d: %s\n", id, event.name) + output += ufmt.Sprintf("Description: %s\n", event.description) + output += ufmt.Sprintf("Date: %s\n", event.date.Format("2006-01-02 15:04:05")) + output += ufmt.Sprintf("Tickets: %d/%d\n", event.ticketsSold, event.maxTickets) + output += ufmt.Sprintf("Price: %d ugnot\n\n", event.price) + + if event.ticketsSold < event.maxTickets { + output += ufmt.Sprintf("[Buy Ticket](/r/jjoptimist/eventix/BuyTicket?eventId=%d)\n", id) + } else { + output += "**SOLD OUT**\n" + } + output += "---\n\n" + return false + }) + + return output +} \ No newline at end of file diff --git a/examples/gno.land/r/jjoptimist/eventix/eventix_test.gno b/examples/gno.land/r/jjoptimist/eventix/eventix_test.gno new file mode 100644 index 00000000000..303a71bd569 --- /dev/null +++ b/examples/gno.land/r/jjoptimist/eventix/eventix_test.gno @@ -0,0 +1,93 @@ +package eventix + +import ( + "std" + "testing" + "time" + + "gno.land/p/demo/grc/grc721" +) + +func TestCreateEvent(t *testing.T) { + // Test successful event creation + eventId := CreateEvent( + "Test Event", + "A test event", + "2024-12-31T23:59:59Z", + 100, + 1000000, + ) + + if eventId != 1 { + t.Errorf("Expected first event ID to be 1, got %d", eventId) + } + + event, exists := getEvent(eventId) + if !exists { + t.Error("Event was not created") + } + + if event.name != "Test Event" { + t.Errorf("Expected event name 'Test Event', got '%s'", event.name) + } + + // Test invalid date format + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic with invalid date format") + } + }() + CreateEvent("Test", "Test", "invalid-date", 100, 1000000) +} + +func TestBuyTicket(t *testing.T) { + // Setup test event + eventId := CreateEvent( + "Test Event", + "A test event", + "2024-12-31T23:59:59Z", + 2, + 1000000, + ) + + // Setup test buyer + buyer := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + std.TestSetOrigCaller(buyer) + + // Test buying without payment + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic when buying without payment") + } + }() + BuyTicket(eventId) + + // Test successful purchase + std.TestSetOrigSend(std.Coins{{"ugnot", 1000000}}, nil) + BuyTicket(eventId) + + event, _ := getEvent(eventId) + if event.ticketsSold != 1 { + t.Errorf("Expected 1 ticket sold, got %d", event.ticketsSold) + } + + // Verify NFT ownership + tokenId := grc721.TokenID("1-1") + owner, err := tickets.OwnerOf(tokenId) // Handle both return values + if err != nil { + t.Errorf("Error getting token owner: %v", err) + } + if owner != buyer { + t.Errorf("Expected ticket owner to be %s, got %s", buyer, owner) + } + + // Test buying sold out event + std.TestSetOrigSend(std.Coins{{"ugnot", 1000000}}, nil) + BuyTicket(eventId) // Buy second ticket + defer func() { + if r := recover(); r == nil { + t.Error("Expected panic when event is sold out") + } + }() + BuyTicket(eventId) // Should panic - sold out +} \ No newline at end of file diff --git a/examples/gno.land/r/jjoptimist/eventix/gno.mod b/examples/gno.land/r/jjoptimist/eventix/gno.mod new file mode 100644 index 00000000000..97aba2c3869 --- /dev/null +++ b/examples/gno.land/r/jjoptimist/eventix/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/jjoptimist/eventix + +dependencies ( + gno.land/p/demo/avl v0.0.0 + gno.land/p/demo/grc/grc721 v0.0.0 + gno.land/p/demo/ufmt v0.0.0 +) \ No newline at end of file