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 "errors"
5 "fmt"
6 "time"
7
8 "gorm.io/gorm"
9
10 "amadan.net/oficina/calendar/internal/calendar/seal"
11)
12
13// The hold's helpers, split from store_booking_flow.go so neither file
14// passes the 300-line cap.
15
16// currentPageOn re-reads a page, its windows and its host on a
17// transaction. A page that is no longer published is ErrSlotTaken to
18// the caller: the time is not on offer any more.
19func (s *Store) currentPageOn(tx *gorm.DB, pageID int64) (BookingPageView, Person, error) {
20 var page BookingPage
21 if err := tx.Where("id = ? AND published_at IS NOT NULL", pageID).First(&page).Error; err != nil {
22 if errors.Is(err, gorm.ErrRecordNotFound) {
23 return BookingPageView{}, Person{}, ErrSlotTaken
24 }
25 return BookingPageView{}, Person{}, fmt.Errorf("calendar: re-read page: %w", err)
26 }
27 var rows []BookingWindow
28 if err := tx.Where("page_id = ?", pageID).Order("weekday, start_min").Find(&rows).Error; err != nil {
29 return BookingPageView{}, Person{}, fmt.Errorf("calendar: re-read windows: %w", err)
30 }
31 windows := make([]Window, 0, len(rows))
32 for _, r := range rows {
33 windows = append(windows, Window{Weekday: time.Weekday(r.Weekday), StartMin: r.StartMin, EndMin: r.EndMin})
34 }
35 view, err := s.openPage(page, windows)
36 if err != nil {
37 return BookingPageView{}, Person{}, err
38 }
39 var host Person
40 if err := tx.First(&host, page.PersonID).Error; err != nil {
41 return BookingPageView{}, Person{}, fmt.Errorf("calendar: re-read host: %w", err)
42 }
43 return view, host, nil
44}
45
46type sealedBooker struct {
47 token, title, description, name, address, note []byte
48}
49
50func (s *Store) sealBooker(in HoldInput, token string) (sealedBooker, error) {
51 var out sealedBooker
52 var err error
53 seal1 := func(dst *[]byte, purpose, plain, what string) {
54 if err != nil {
55 return
56 }
57 if *dst, err = s.seal.SealString(purpose, plain); err != nil {
58 err = fmt.Errorf("calendar: seal booking %s: %w", what, err)
59 }
60 }
61 seal1(&out.token, seal.PurposeBookingToken, token, "token")
62 seal1(&out.title, seal.PurposeBookingTitle, in.Page.Title, "title")
63 seal1(&out.description, seal.PurposeBookingDescription, in.Page.Description, "description")
64 seal1(&out.name, seal.PurposeBookerName, in.Name, "name")
65 seal1(&out.address, seal.PurposeBookerAddress, normalizeAddress(in.Address), "address")
66 seal1(&out.note, seal.PurposeBookerNote, in.Note, "note")
67 return out, err
68}
69
70func hasSlot(slots []Slot, startUTC, endUTC int64) bool {
71 for _, s := range slots {
72 if s.StartUTC == startUTC && s.EndUTC == endUTC {
73 return true
74 }
75 }
76 return false
77}
78
79// defaultCalendarOn is DefaultCalendarID on a transaction.
80func (s *Store) defaultCalendarOn(tx *gorm.DB, personID int64) (int64, error) {
81 var cal Calendar
82 err := tx.Where("owner_id = ? AND kind = ? AND archived_at IS NULL", personID, "personal").
83 Order("id").First(&cal).Error
84 if err != nil {
85 return 0, fmt.Errorf("calendar: host has no personal calendar: %w", err)
86 }
87 return cal.ID, nil
88}
89