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 "strconv"
5 "time"
6)
7
8// publicBooking is EVERYTHING a public page, a booker's mail or the ICS
9// may know about a booking (booking spec §5.5). Built from a
10// BookingView by exactly one function, and public templates receive
11// this and nothing else — never a BookingView, never an Event — so the
12// host's notes, location and attendee list cannot reach a stranger by
13// accident.
14type publicBooking struct {
15 Title, Description string
16
17 // The event's times, as instants and as wall clock in the booker's
18 // zone (When) and the host's (HostWhen) when they differ.
19 StartUTC, EndUTC int64
20 When, HostWhen string
21 Zone, HostZone string
22 Rev int
23
24 HostName, HostAddress string
25 Name, Address string
26
27 State string
28 CancelledBy string
29 Live bool // confirmed and the event stands
30 Held bool // held and not lapsed
31 Expired bool
32 Cancelled bool
33 ExpiresAt string // booker zone, for a held booking
34
35 // Token is set only on a page that already knows it (it came in
36 // the URL), for building the links on that page.
37 Token string
38}
39
40// publicOf is the one door. now decides whether a hold has lapsed.
41func publicOf(v BookingView, now time.Time) publicBooking {
42 pb := publicBooking{
43 Title: v.Title, Description: v.Description,
44 StartUTC: v.Event.StartUTC, EndUTC: v.Event.EndUTC,
45 Zone: v.BookerZone, HostZone: v.Event.TZ, Rev: v.Event.Sequence,
46 HostName: v.HostName, HostAddress: v.Host.Address,
47 Name: v.Name, Address: v.Address,
48 State: v.State, CancelledBy: v.CancelledBy,
49 }
50 pb.When = spanWords(v.Event.StartUTC, v.Event.EndUTC, v.BookerZone)
51 if v.Event.TZ != "" && v.Event.TZ != v.BookerZone {
52 pb.HostWhen = spanWords(v.Event.StartUTC, v.Event.EndUTC, v.Event.TZ)
53 }
54 pb.Cancelled = v.Cancelled()
55 pb.Live = v.Live()
56 switch v.State {
57 case BookingHeld:
58 if now.Unix() < v.HoldExpiresAt && !pb.Cancelled {
59 pb.Held = true
60 } else {
61 pb.Expired = true
62 }
63 pb.ExpiresAt = instantWords(v.HoldExpiresAt, v.BookerZone)
64 case BookingExpired:
65 pb.Expired = true
66 pb.ExpiresAt = instantWords(v.HoldExpiresAt, v.BookerZone)
67 }
68 if pb.CancelledBy == "" && pb.Cancelled {
69 pb.CancelledBy = CancelledByHost
70 }
71 return pb
72}
73
74// spanWords is "Monday 7 September 2026, 09:00–09:30 (Europe/Dublin)".
75func spanWords(startUTC, endUTC int64, zone string) string {
76 s, err := InZone(startUTC, zone)
77 if err != nil {
78 return ""
79 }
80 e, err := InZone(endUTC, zone)
81 if err != nil {
82 return ""
83 }
84 out := s.Format("Monday 2 January 2006, 15:04")
85 if e.Format("2006-01-02") == s.Format("2006-01-02") {
86 out += "–" + e.Format("15:04")
87 } else {
88 out += " – " + e.Format("Monday 2 January 2006, 15:04")
89 }
90 return out + " (" + zone + ")"
91}
92
93func instantWords(unix int64, zone string) string {
94 t, err := InZone(unix, zone)
95 if err != nil {
96 return ""
97 }
98 return t.Format("15:04 on Monday 2 January") + " (" + zone + ")"
99}
100
101// Link helpers, so templates never assemble a URL.
102func (h *handlers) manageURL(token string) string { return h.origin + "/book/m/" + token }
103func (h *handlers) eventURL(id int64) string {
104 return h.origin + "/events/" + strconv.FormatInt(id, 10)
105}
106