| 1 | package calendar |
| 2 | |
| 3 | import ( |
| 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. |
| 19 | func (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 | |
| 46 | type sealedBooker struct { |
| 47 | token, title, description, name, address, note []byte |
| 48 | } |
| 49 | |
| 50 | func (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 | |
| 70 | func 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. |
| 80 | func (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 | |