diff --git a/projects/gnocal/gnocal.go b/projects/gnocal/gnocal.go index 3937e38..cc2b474 100644 --- a/projects/gnocal/gnocal.go +++ b/projects/gnocal/gnocal.go @@ -21,6 +21,16 @@ var f = fmt.Sprintf //go:embed static/* var static embed.FS +// Templae pre-parsing +var ( + tmplConnectionRefused = mustParseTemplate("connection_refused.html") + tmplInvalidRealmPath = mustParseTemplate("invalid_realm_path.html") + tmplRenderCalNotDeclared = mustParseTemplate("rendercal_not_declared.html") + tmplNoRenderDefined = mustParseTemplate("no_render_defined.html") + tmplUnknownRenderError = mustParseTemplate("unknown_render_error.html") + tmplLandingPage = mustParseTemplate("landing_page.html") +) + type Server struct { router *chi.Mux gnoClient *gnoclient.Client @@ -60,15 +70,13 @@ func (s *Server) Run() error { } func (s *Server) RenderCalFromRealm(w http.ResponseWriter, r *http.Request) { - //gnocal.com/gno.land/r/buidlthefuture000/events/gnolandlaunch/calendar?format=ics - //gnocal.com/gno.land/r/buidlthefuture000/events/gnolandlaunch/calendar calendarPath := chi.URLParam(r, "*") if calendarPath == "" { http.Error(w, "missing realm path", http.StatusBadRequest) return } - path := strconv.Quote("?" + r.URL.RawQuery) // e.g. "?" + "apple=2&session=1&session=100&session=0&format=json" + path := strconv.Quote("?" + r.URL.RawQuery) stringToken, _, err := s.gnoClient.QEval(calendarPath, f(`RenderCal(%s)`, path)) if err != nil { w.Header().Set("Content-Type", "text/html") @@ -76,80 +84,54 @@ func (s *Server) RenderCalFromRealm(w http.ResponseWriter, r *http.Request) { errStr := err.Error() switch { - case strings.Contains(errStr, "connect: connection refused"): - tmpl, parseErr := template.ParseFS(static, "static/connection_refused.html") - if parseErr != nil { - http.Error(w, "Template error", http.StatusInternalServerError) - return - } - tmpl.Execute(w, map[string]string{ + tmplConnectionRefused.Execute(w, map[string]string{ "RpcUrl": s.config.GnolandRpcUrl, }) - return - case strings.Contains(errStr, "invalid package path"): - tmpl, parseErr := template.ParseFS(static, "static/invalid_realm_path.html") - if parseErr != nil { - http.Error(w, "Template error", http.StatusInternalServerError) - return - } - tmpl.Execute(w, map[string]string{ + tmplInvalidRealmPath.Execute(w, map[string]string{ "InputPath": calendarPath, }) - return - case strings.Contains(errStr, "name RenderCal not declared"): if _, _, renderErr := s.gnoClient.QEval(calendarPath, `Render("")`); renderErr == nil { - tmpl, parseErr := template.ParseFS(static, "static/rendercal_not_declared.html") - if parseErr != nil { - http.Error(w, "Template error", http.StatusInternalServerError) - return - } - tmpl.Execute(w, map[string]string{ + tmplRenderCalNotDeclared.Execute(w, map[string]string{ "RealmPath": calendarPath, }) - return - } - tmpl, parseErr := template.ParseFS(static, "static/no_render_defined.html") - if parseErr != nil { - http.Error(w, "Template error", http.StatusInternalServerError) - return + } else { + tmplNoRenderDefined.Execute(w, nil) } - tmpl.Execute(w, nil) - return - default: - tmpl, parseErr := template.ParseFS(static, "static/unknown_render_error.html") - if parseErr != nil { - http.Error(w, "Template error", http.StatusInternalServerError) - return - } - tmpl.Execute(w, map[string]string{ + tmplUnknownRenderError.Execute(w, map[string]string{ "InputPath": calendarPath, "ErrorMessage": errStr, }) - return } + return } - var out string // TODO: this is output post-processing, should be refactored out w/ better design + var out string if removedLParen, cutPrefix := strings.CutPrefix(stringToken, `("`); cutPrefix { out = removedLParen } if removedRParen, cutSuffix := strings.CutSuffix(out, `" string)`); cutSuffix { out = removedRParen } + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.Header().Set("Content-Disposition", "inline; filename=calendar.ics") w.Write([]byte(strings.ReplaceAll(out, `\n`, "\n"))) } func (s *Server) RenderLandingPage(w http.ResponseWriter, r *http.Request) { - content, err := static.ReadFile("static/landing_page.html") - if err != nil { - http.Error(w, "static/landing_page.html not found", http.StatusInternalServerError) - return - } w.Header().Set("Content-Type", "text/html") w.WriteHeader(http.StatusOK) - w.Write(content) + tmplLandingPage.Execute(w, nil) +} + +func mustParseTemplate(filename string) *template.Template { + tmpl, err := template.ParseFS(static, "static/"+filename) + if err != nil { + panic("Template parsing failed: " + filename + " → " + err.Error()) + } + return tmpl } diff --git a/projects/gnoland/gno.land/r/buidlthefuture000/events/gnolandlaunch/app.gno b/projects/gnoland/gno.land/r/buidlthefuture000/events/gnolandlaunch/app.gno index 83f7e01..28c9f26 100644 --- a/projects/gnoland/gno.land/r/buidlthefuture000/events/gnolandlaunch/app.gno +++ b/projects/gnoland/gno.land/r/buidlthefuture000/events/gnolandlaunch/app.gno @@ -145,63 +145,83 @@ func (*App) AdminSetRole(role string, addr std.Address) { func (*App) AdminRemoveRole(role string, addr std.Address) { acl.AdminRemoveRole(role, addr) } + func (*App) ResetRoles() { acl.ResetRoles() } + func (*App) JoinWaitlist() { acl.JoinWaitlist() } + func (*App) RemoveSelfFromWaitlist() { acl.RemoveSelfFromWaitlist() } + func (*App) JoinAsAttendee() { acl.JoinAsAttendee() } + func (*App) RemoveSelfAsAttendee() { acl.RemoveSelfAsAttendee() } + func (*App) AddSpeaker(addr std.Address) { acl.AddSpeaker(addr) } + func (*App) RemoveSpeaker(addr std.Address) { acl.RemoveSpeaker(addr) } + func (*App) AddOrganizer(addr std.Address) { acl.AddOrganizer(addr) } + func (*App) RemoveOrganizer(addr std.Address) { acl.RemoveOrganizer(addr) } + func (*App) AddProposer(addr, sender std.Address) { acl.AddProposer(addr, sender) } + func (*App) RemoveProposer(addr, sender std.Address) { acl.RemoveProposer(addr, sender) } + func (*App) AddReviewer(addr, sender std.Address) { acl.AddReviewer(addr, sender) } + func (*App) RemoveReviewer(addr, sender std.Address) { acl.RemoveReviewer(addr, sender) } + func (*App) RoleExists(role string) bool { return acl.RoleExists(role) } + func (*App) HasRole(role string, addr std.Address) bool { return acl.HasRole(role, addr) } + func (*App) ListRoles() []string { return acl.ListRoles() } + func (*App) SetRoleHandler(role string, fn func(string) bool) { acl.SetRoleHandler(role, fn) } + func (*App) UnsetRoleHandler(role string) { acl.UnsetRoleHandler(role) } + func (*App) AssertAtLeastRole(role string, sender std.Address) { acl.AssertAtLeastRole(role, sender) } + func (*App) RenderList(role string) string { return acl.RenderList(role) } diff --git a/projects/gnoland/gno.land/r/buidlthefuture000/events/gnolandlaunch/calendar/calendar.gno b/projects/gnoland/gno.land/r/buidlthefuture000/events/gnolandlaunch/calendar/calendar.gno index 0ade008..eb06b43 100644 --- a/projects/gnoland/gno.land/r/buidlthefuture000/events/gnolandlaunch/calendar/calendar.gno +++ b/projects/gnoland/gno.land/r/buidlthefuture000/events/gnolandlaunch/calendar/calendar.gno @@ -99,7 +99,7 @@ func RenderCal(path string) string { w("VERSION:2.0") w("CALSCALE:GREGORIAN") w("PRODID:-//gno.land//Launch Calendar//EN") - w("METHOD:PUBLISH") + w("METHOD:PUBLISH\n") for i, s := range sessions { id := event.Pad3(strconv.Itoa(i)) if !include(id) { diff --git a/projects/gnoland/gno.land/r/buidlthefuture000/events/gnolandlaunch/event.gno b/projects/gnoland/gno.land/r/buidlthefuture000/events/gnolandlaunch/event.gno index 371c414..06e834d 100644 --- a/projects/gnoland/gno.land/r/buidlthefuture000/events/gnolandlaunch/event.gno +++ b/projects/gnoland/gno.land/r/buidlthefuture000/events/gnolandlaunch/event.gno @@ -11,10 +11,10 @@ import ( ) func init() { - renderOpts := map[string]interface{}{ - "Svg": struct{}{}, - "Schedule": struct{}{}, - } + renderOpts := map[string]interface{}{ + "Svg": struct{}{}, + "Schedule": struct{}{}, + } app.RegisterEvent(gnolandlaunch, renderOpts) } @@ -76,8 +76,8 @@ var Sessions = map[string]*session.Session{ Title: "The Road to Web7", Description: "Join us for an inspiring keynote at the Build The Future Meeting featuring Jae Kwon, visionary blockchain pioneer and creator of Gno.land.", Speaker: speakerBios["jae"], - StartTime: time.Date(2025, 6, 25, 10, 0, 0, 0, time.UTC), - EndTime: time.Date(2025, 6, 25, 11, 30, 0, 0, time.UTC), + StartTime: time.Date(2025, 7, 25, 10, 0, 0, 0, time.UTC), + EndTime: time.Date(2025, 7, 25, 11, 30, 0, 0, time.UTC), Location: locations["gnowhere"], }, @@ -85,8 +85,8 @@ var Sessions = map[string]*session.Session{ Title: "Generics in Action: Simplifying Go Code", Description: "Learn practical use cases for Go generics to simplify and enhance your code.", Speaker: speakerBios["alice"], - StartTime: time.Date(2025, 6, 26, 10, 0, 0, 0, time.UTC), - EndTime: time.Date(2025, 6, 26, 11, 30, 0, 0, time.UTC), + StartTime: time.Date(2025, 7, 26, 10, 0, 0, 0, time.UTC), + EndTime: time.Date(2025, 7, 26, 11, 30, 0, 0, time.UTC), Location: locations["room-a1"], }, @@ -94,8 +94,8 @@ var Sessions = map[string]*session.Session{ Title: "Concurrency Patterns in Real‐World Applications", Description: "A deep dive into concurrency patterns to efficiently handle complex workflows.", Speaker: speakerBios["bob"], - StartTime: time.Date(2025, 6, 27, 13, 0, 0, 0, time.UTC), - EndTime: time.Date(2025, 6, 27, 14, 30, 0, 0, time.UTC), + StartTime: time.Date(2025, 7, 27, 13, 0, 0, 0, time.UTC), + EndTime: time.Date(2025, 7, 27, 14, 30, 0, 0, time.UTC), Location: locations["room-b2"], }, @@ -103,8 +103,8 @@ var Sessions = map[string]*session.Session{ Title: "Building High‐Performance APIs with Go", Description: "Techniques and best practices for building scalable and performant APIs.", Speaker: speakerBios["carol"], - StartTime: time.Date(2025, 6, 28, 15, 0, 0, 0, time.UTC), - EndTime: time.Date(2025, 6, 28, 16, 30, 0, 0, time.UTC), + StartTime: time.Date(2025, 7, 28, 15, 0, 0, 0, time.UTC), + EndTime: time.Date(2025, 7, 28, 16, 30, 0, 0, time.UTC), Location: locations["room-c3"], }, } @@ -115,11 +115,11 @@ var gnolandlaunch = &event.Event{ // StartDate: time.Date(2025, 7, 25, 0, 0, 0, 0, time.UTC), // EndDate: time.Date(2025, 7, 25, 23, 59, 59, 0, time.UTC), Description: "Join us as Gno.land creator Jae Kwon shares his vision of a logic-first internet—where code is law and realms are the new web.", - Sessions: []*session.Session{ + Sessions: []*session.Session{ // TODO: add finalized sessions here - // Sessions["keynote"], - // Sessions["generics"], - // Sessions["concurrency"], - // Sessions["apis"], + Sessions["keynote"], + Sessions["generics"], + Sessions["concurrency"], + Sessions["apis"], }, }