rastrillo / abono Public

Clone
git clone https://amadan.net/rastrillo/abono

Plain git — no account needed to clone.

Download

Download this file

1package abono
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "time"
8
9 "gorm.io/gorm"
10)
11
12// Comp statuses, as the schema's CHECK allows them.
13const (
14 compActive = "active"
15 compCanceled = "canceled"
16)
17
18type compRow struct {
19 ID int64
20 Subject string
21 Plan string
22 Status string
23 EndsAt *time.Time
24 Note string
25 CreatedAt time.Time
26 UpdatedAt time.Time
27}
28
29func (compRow) TableName() string { return "abono_comps" }
30
31// Comp gives subject the plan without payment, until endsAt or for good
32// when endsAt is nil, and returns the comp's id. It is a comp, not a
33// grant, so it is never confused with "grants access". OnChange runs in the
34// same transaction, and its error undoes the comp.
35func (s *Service) Comp(ctx context.Context, subject, plan string, endsAt *time.Time, note string) (int64, error) {
36 if subject == "" || plan == "" {
37 return 0, errors.New("abono: Comp needs a subject and a plan")
38 }
39 now := s.now()
40 row := compRow{Subject: subject, Plan: plan, Status: compActive, Note: note, CreatedAt: now, UpdatedAt: now}
41 if endsAt != nil {
42 e := endsAt.UTC()
43 row.EndsAt = &e
44 }
45 err := s.tx(ctx, func(tx *gorm.DB) error {
46 if err := tx.Create(&row).Error; err != nil {
47 return err
48 }
49 return s.changed(ctx, tx, subject)
50 })
51 if err != nil {
52 return 0, err
53 }
54 return row.ID, nil
55}
56
57// EndComp ends a comp now. Ending one already ended changes nothing and
58// tells nobody. OnChange runs in the same transaction, and its error leaves
59// the comp as it was.
60func (s *Service) EndComp(ctx context.Context, id int64) error {
61 return s.tx(ctx, func(tx *gorm.DB) error {
62 var row compRow
63 switch err := tx.Where("id = ?", id).Take(&row).Error; {
64 case errors.Is(err, gorm.ErrRecordNotFound):
65 return fmt.Errorf("abono: comp %d: %w", id, ErrNotFound)
66 case err != nil:
67 return err
68 }
69 if row.Status == compCanceled {
70 return nil
71 }
72 if err := tx.Exec(`UPDATE abono_comps SET status = ?, updated_at = ? WHERE id = ?`,
73 compCanceled, s.now(), id).Error; err != nil {
74 return err
75 }
76 return s.changed(ctx, tx, row.Subject)
77 })
78}
79