oficina / sheets Public

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

Plain git — no account needed to clone.

Download

Download this file

1package sheets
2
3import (
4 "errors"
5
6 "gorm.io/gorm"
7)
8
9// errLastOwner refuses the change that would leave a sheet with nobody
10// who can share it.
11//
12// A sheet with no owner cannot be shared, cannot be un-shared, and
13// cannot be given an owner — mayShare is false for everybody, including
14// the person who did it, and there is no admin path to repair it. That
15// makes it the one state in this package worth an explicit sentinel
16// rather than a boolean: both doors have to refuse it, and both have to
17// say the same thing to the person.
18//
19// The doors are revoke and grant. Only revoke was guarded at first;
20// grant upserts on (sheet_id, member_id), so re-sharing with an address
21// that already had access changed their role and counted nothing. The
22// sole owner picking "viewer" for their own address orphaned the sheet
23// in one form submission.
24var errLastOwner = errors.New("sheets: a sheet needs an owner")
25
26// lastOwnerGoing reports whether changing this member's role away from
27// owner would take the sheet's last owner with it.
28//
29// It runs inside the caller's transaction, and that is the point rather
30// than tidiness: read-then-write outside one is check-then-act, and two
31// owners demoting each other concurrently both counted two, both passed,
32// and both wrote. SQLite serialises the writes; it does not serialise a
33// read against someone else's later write.
34func lastOwnerGoing(tx *gorm.DB, sheetID, memberID uint64, newRole SheetRole) (bool, error) {
35 if newRole == SheetOwner {
36 return false, nil // not going anywhere
37 }
38 var current Grant
39 err := tx.Where("sheet_id = ? AND member_id = ?", sheetID, memberID).
40 First(&current).Error
41 if errors.Is(err, gorm.ErrRecordNotFound) {
42 return false, nil // holds nothing, so takes nothing with them
43 }
44 if err != nil {
45 return false, err
46 }
47 if SheetRole(current.Role) != SheetOwner {
48 return false, nil
49 }
50 var owners int64
51 if err := tx.Model(&Grant{}).
52 Where("sheet_id = ? AND role = ?", sheetID, string(SheetOwner)).
53 Count(&owners).Error; err != nil {
54 return false, err
55 }
56 return owners <= 1, nil
57}
58