oficina / calendar Public

Clone
git clone https://amadan.net/oficina/calendar

Plain git — no account needed to clone.

Download

Download this file

1package calendar
2
3import (
4 "net/http"
5 "strconv"
6 "strings"
7
8 "github.com/go-chi/chi/v5"
9)
10
11// bookingPageForm is the new/edit form's view data. Strings, so a
12// rejected form re-renders exactly what was typed.
13type bookingPageForm struct {
14 ID int64
15 Action string
16 Heading string
17 Submit string
18 Title string
19 Description string
20 Availability string
21 DurationMin string
22 BufferMin string
23 NoticeMin string
24 HorizonDays string
25 Pane BookingPaneView
26 Error string
27 ErrorField string
28}
29
30func newBookingPageForm() bookingPageForm {
31 return bookingPageForm{
32 Action: "/settings/booking", Heading: "New booking page", Submit: "Create",
33 DurationMin: "30",
34 BufferMin: strconv.Itoa(DefaultBufferMin),
35 NoticeMin: strconv.Itoa(DefaultNoticeMin),
36 HorizonDays: strconv.Itoa(DefaultHorizonDays),
37 Pane: BookingPaneView{Empty: true},
38 }
39}
40
41func formOfPage(p BookingPageView) bookingPageForm {
42 return bookingPageForm{
43 ID: p.ID,
44 Action: "/settings/booking/" + strconv.FormatInt(p.ID, 10),
45 Heading: "Edit booking page", Submit: "Save",
46 Title: p.Title, Description: p.Description, Availability: p.Availability,
47 DurationMin: strconv.Itoa(p.DurationMin), BufferMin: strconv.Itoa(p.BufferMin),
48 NoticeMin: strconv.Itoa(p.NoticeMin), HorizonDays: strconv.Itoa(p.HorizonDays),
49 Pane: bookingPaneFor(p.Availability),
50 }
51}
52
53// readBookingPageForm parses a submission. Blank knobs take the
54// defaults; a knob of zero is a choice and stays zero.
55func readBookingPageForm(r *http.Request) (BookingPageInput, bookingPageForm) {
56 _ = r.ParseForm()
57 num := func(name string, def int) (int, string) {
58 raw := strings.TrimSpace(r.FormValue(name))
59 if raw == "" {
60 return def, strconv.Itoa(def)
61 }
62 n, err := strconv.Atoi(raw)
63 if err != nil {
64 return -1, raw
65 }
66 return n, raw
67 }
68 f := bookingPageForm{
69 Title: r.FormValue("title"),
70 Description: r.FormValue("description"),
71 Availability: r.FormValue("availability"),
72 }
73 in := BookingPageInput{Title: f.Title, Description: f.Description, Availability: f.Availability}
74 in.DurationMin, f.DurationMin = num("duration_min", 30)
75 in.BufferMin, f.BufferMin = num("buffer_min", DefaultBufferMin)
76 in.NoticeMin, f.NoticeMin = num("notice_min", DefaultNoticeMin)
77 in.HorizonDays, f.HorizonDays = num("horizon_days", DefaultHorizonDays)
78 f.Pane = bookingPaneFor(in.Availability)
79 return in, f
80}
81
82func (h *handlers) newBookingPage(w http.ResponseWriter, r *http.Request) {
83 render(w, "booking_page_form", h.page(r, "New booking page", newBookingPageForm()))
84}
85
86func (h *handlers) editBookingPage(w http.ResponseWriter, r *http.Request) {
87 p, _ := CurrentPerson(r)
88 id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
89 page, err := h.store.BookingPage(r.Context(), p.ID, id)
90 if NotFound(err) {
91 h.errorPage(w, r, http.StatusNotFound, "")
92 return
93 }
94 if err != nil {
95 h.fail(w, r, "read booking page", err)
96 return
97 }
98 render(w, "booking_page_form", h.page(r, "Edit booking page", formOfPage(page)))
99}
100
101// saveBookingPage is create and update: parse the sentence, refuse the
102// form while any clause is unreadable (the pane shows every problem),
103// then write.
104func (h *handlers) saveBookingPage(w http.ResponseWriter, r *http.Request) {
105 p, _ := CurrentPerson(r)
106 in, f := readBookingPageForm(r)
107 if raw := chi.URLParam(r, "id"); raw != "" {
108 f.ID, _ = strconv.ParseInt(raw, 10, 64)
109 f.Action, f.Heading, f.Submit = "/settings/booking/"+raw, "Edit booking page", "Save"
110 } else {
111 f.Action, f.Heading, f.Submit = "/settings/booking", "New booking page", "Create"
112 }
113 reject := func(field, msg string) {
114 f.Error, f.ErrorField = msg, field
115 w.WriteHeader(http.StatusUnprocessableEntity)
116 render(w, "booking_page_form", h.page(r, f.Heading, f))
117 }
118
119 windows, problems := ParseAvailability(in.Availability)
120 if len(problems) > 0 {
121 reject("availability", "Some of that could not be read. The parts that could are listed below the box.")
122 return
123 }
124 windows = MergeWindows(windows)
125
126 var err error
127 if f.ID == 0 {
128 _, err = h.store.CreateBookingPage(r.Context(), p.ID, in, windows)
129 } else {
130 err = h.store.UpdateBookingPage(r.Context(), p.ID, f.ID, in, windows)
131 }
132 if NotFound(err) {
133 h.errorPage(w, r, http.StatusNotFound, "")
134 return
135 }
136 if ve, ok := Invalid(err); ok {
137 reject(ve.Field, ve.Message)
138 return
139 }
140 if err != nil {
141 h.fail(w, r, "save booking page", err)
142 return
143 }
144 http.Redirect(w, r, "/settings/booking?saved=1", http.StatusSeeOther)
145}
146