Add markdown event descriptions for hosts to provide context
Hosts can now add a free-form description (with markdown rendering via goldmark) when creating or editing events. Descriptions render safely with no raw HTML passthrough. Includes ALTER TABLE migration for existing databases. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+45
-13
@@ -6,6 +6,7 @@ import (
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
|
||||
const (
|
||||
maxFieldLen = 200
|
||||
maxDescLen = 5000
|
||||
maxNoteLen = 500
|
||||
maxSlots = 20
|
||||
maxRsvps = 200
|
||||
@@ -57,6 +59,7 @@ func (s *Server) handleCreateEvent(w http.ResponseWriter, r *http.Request) {
|
||||
date := sanitize(r.FormValue("date"), maxFieldLen)
|
||||
time_ := sanitize(r.FormValue("time"), maxFieldLen)
|
||||
location := sanitize(r.FormValue("location"), maxFieldLen)
|
||||
description := sanitize(r.FormValue("description"), maxDescLen)
|
||||
|
||||
if title == "" {
|
||||
http.Error(w, "Title is required", http.StatusBadRequest)
|
||||
@@ -66,7 +69,7 @@ func (s *Server) handleCreateEvent(w http.ResponseWriter, r *http.Request) {
|
||||
slug := randomSlug()
|
||||
token := randomToken()
|
||||
event, err := s.q.CreateEvent(r.Context(), db.CreateEventParams{
|
||||
Slug: slug, Title: title, Date: date, Time: time_, Location: location, AdminToken: token,
|
||||
Slug: slug, Title: title, Date: date, Time: time_, Location: location, AdminToken: token, Description: description,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("create event: %v", err)
|
||||
@@ -119,12 +122,13 @@ type SlotView struct {
|
||||
}
|
||||
|
||||
type EventPageData struct {
|
||||
Event db.Event
|
||||
Slots []SlotView
|
||||
Rsvps []db.Rsvp
|
||||
TotalGoing int64
|
||||
IsAdmin bool
|
||||
BaseURL string
|
||||
Event db.Event
|
||||
Slots []SlotView
|
||||
Rsvps []db.Rsvp
|
||||
TotalGoing int64
|
||||
IsAdmin bool
|
||||
BaseURL string
|
||||
DescriptionHTML template.HTML
|
||||
}
|
||||
|
||||
func (s *Server) loadEventPage(r *http.Request, slug string, isAdmin bool) (*EventPageData, error) {
|
||||
@@ -176,13 +180,19 @@ func (s *Server) loadEventPage(r *http.Request, slug string, isAdmin bool) (*Eve
|
||||
}
|
||||
totalGoing += int64(len(rsvps))
|
||||
|
||||
var descHTML template.HTML
|
||||
if event.Description != "" {
|
||||
descHTML = RenderMarkdown(event.Description)
|
||||
}
|
||||
|
||||
return &EventPageData{
|
||||
Event: event,
|
||||
Slots: slotViews,
|
||||
Rsvps: rsvps,
|
||||
TotalGoing: totalGoing,
|
||||
IsAdmin: isAdmin,
|
||||
BaseURL: s.baseURL,
|
||||
Event: event,
|
||||
Slots: slotViews,
|
||||
Rsvps: rsvps,
|
||||
TotalGoing: totalGoing,
|
||||
IsAdmin: isAdmin,
|
||||
BaseURL: s.baseURL,
|
||||
DescriptionHTML: descHTML,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -425,6 +435,28 @@ func (s *Server) handleAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
pageTmpl["event"].ExecuteTemplate(w, "layout", data)
|
||||
}
|
||||
|
||||
func (s *Server) handleUpdateDescription(w http.ResponseWriter, r *http.Request) {
|
||||
slug := chi.URLParam(r, "slug")
|
||||
token := chi.URLParam(r, "token")
|
||||
|
||||
event, err := s.q.GetEventBySlug(r.Context(), slug)
|
||||
if err != nil || event.AdminToken != token {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 32*1024)
|
||||
r.ParseForm()
|
||||
description := sanitize(r.FormValue("description"), maxDescLen)
|
||||
|
||||
s.q.UpdateEventDescription(r.Context(), db.UpdateEventDescriptionParams{
|
||||
Description: description,
|
||||
ID: event.ID,
|
||||
})
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("/e/%s/admin/%s", slug, token), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (s *Server) handleCreateSlot(w http.ResponseWriter, r *http.Request) {
|
||||
slug := chi.URLParam(r, "slug")
|
||||
token := chi.URLParam(r, "token")
|
||||
|
||||
Reference in New Issue
Block a user