package db import ( "database/sql" "errors" "fmt" ) // Permission is the access level granted by a share. type Permission string const ( PermRead Permission = "read" PermWrite Permission = "write" ) // ErrShareNotFound is returned when revoking a share that doesn't exist. var ErrShareNotFound = errors.New("share not found") // CalendarShare describes a grant of access to owner's calendar to another // user. type CalendarShare struct { Owner string CalendarName string SharedWith string Permission Permission } // ShareCalendar grants sharedWith access (read or write) to owner's // calendar calName. Calling it again for the same (owner, calName, // sharedWith) updates the permission. func (d *DB) ShareCalendar(owner, calName, sharedWith string, perm Permission) error { if perm != PermRead && perm != PermWrite { return fmt.Errorf("invalid permission %q", perm) } _, err := d.conn.Exec(` INSERT INTO calendar_shares (owner, calendar_name, shared_with, permission) VALUES (?, ?, ?, ?) ON CONFLICT (owner, calendar_name, shared_with) DO UPDATE SET permission = excluded.permission`, owner, calName, sharedWith, string(perm)) if err != nil { return fmt.Errorf("sharing calendar: %w", err) } return nil } // UnshareCalendar revokes sharedWith's access to owner's calendar calName. func (d *DB) UnshareCalendar(owner, calName, sharedWith string) error { res, err := d.conn.Exec(` DELETE FROM calendar_shares WHERE owner = ? AND calendar_name = ? AND shared_with = ?`, owner, calName, sharedWith) if err != nil { return fmt.Errorf("unsharing calendar: %w", err) } n, err := res.RowsAffected() if err != nil { return fmt.Errorf("unsharing calendar: %w", err) } if n == 0 { return ErrShareNotFound } return nil } // SharesOfCalendar lists everyone owner's calendar calName has been shared // with. func (d *DB) SharesOfCalendar(owner, calName string) ([]CalendarShare, error) { rows, err := d.conn.Query(` SELECT owner, calendar_name, shared_with, permission FROM calendar_shares WHERE owner = ? AND calendar_name = ? ORDER BY shared_with`, owner, calName) if err != nil { return nil, fmt.Errorf("listing calendar shares: %w", err) } defer rows.Close() return scanCalendarShares(rows) } // CalendarsSharedWith lists all calendars (from any owner) that have been // shared with user. func (d *DB) CalendarsSharedWith(user string) ([]CalendarShare, error) { rows, err := d.conn.Query(` SELECT owner, calendar_name, shared_with, permission FROM calendar_shares WHERE shared_with = ? ORDER BY owner, calendar_name`, user) if err != nil { return nil, fmt.Errorf("listing calendars shared with user: %w", err) } defer rows.Close() return scanCalendarShares(rows) } // CalendarShare looks up the share record granting user access to // owner's calendar calName, if any. func (d *DB) CalendarShareFor(owner, calName, user string) (*CalendarShare, error) { row := d.conn.QueryRow(` SELECT owner, calendar_name, shared_with, permission FROM calendar_shares WHERE owner = ? AND calendar_name = ? AND shared_with = ?`, owner, calName, user) var s CalendarShare var perm string if err := row.Scan(&s.Owner, &s.CalendarName, &s.SharedWith, &perm); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrShareNotFound } return nil, fmt.Errorf("looking up calendar share: %w", err) } s.Permission = Permission(perm) return &s, nil } func scanCalendarShares(rows *sql.Rows) ([]CalendarShare, error) { var shares []CalendarShare for rows.Next() { var s CalendarShare var perm string if err := rows.Scan(&s.Owner, &s.CalendarName, &s.SharedWith, &perm); err != nil { return nil, fmt.Errorf("scanning calendar share: %w", err) } s.Permission = Permission(perm) shares = append(shares, s) } return shares, rows.Err() } // AddressBookShare describes a grant of access to owner's address book to // another user. type AddressBookShare struct { Owner string AddressBookName string SharedWith string Permission Permission } // ShareAddressBook grants sharedWith access (read or write) to owner's // address book bookName. func (d *DB) ShareAddressBook(owner, bookName, sharedWith string, perm Permission) error { if perm != PermRead && perm != PermWrite { return fmt.Errorf("invalid permission %q", perm) } _, err := d.conn.Exec(` INSERT INTO addressbook_shares (owner, addressbook_name, shared_with, permission) VALUES (?, ?, ?, ?) ON CONFLICT (owner, addressbook_name, shared_with) DO UPDATE SET permission = excluded.permission`, owner, bookName, sharedWith, string(perm)) if err != nil { return fmt.Errorf("sharing address book: %w", err) } return nil } // UnshareAddressBook revokes sharedWith's access to owner's address book // bookName. func (d *DB) UnshareAddressBook(owner, bookName, sharedWith string) error { res, err := d.conn.Exec(` DELETE FROM addressbook_shares WHERE owner = ? AND addressbook_name = ? AND shared_with = ?`, owner, bookName, sharedWith) if err != nil { return fmt.Errorf("unsharing address book: %w", err) } n, err := res.RowsAffected() if err != nil { return fmt.Errorf("unsharing address book: %w", err) } if n == 0 { return ErrShareNotFound } return nil } // AddressBooksSharedWith lists all address books (from any owner) that // have been shared with user. func (d *DB) AddressBooksSharedWith(user string) ([]AddressBookShare, error) { rows, err := d.conn.Query(` SELECT owner, addressbook_name, shared_with, permission FROM addressbook_shares WHERE shared_with = ? ORDER BY owner, addressbook_name`, user) if err != nil { return nil, fmt.Errorf("listing address books shared with user: %w", err) } defer rows.Close() var shares []AddressBookShare for rows.Next() { var s AddressBookShare var perm string if err := rows.Scan(&s.Owner, &s.AddressBookName, &s.SharedWith, &perm); err != nil { return nil, fmt.Errorf("scanning address book share: %w", err) } s.Permission = Permission(perm) shares = append(shares, s) } return shares, rows.Err() } // AddressBookShareFor looks up the share record granting user access to // owner's address book bookName, if any. func (d *DB) AddressBookShareFor(owner, bookName, user string) (*AddressBookShare, error) { row := d.conn.QueryRow(` SELECT owner, addressbook_name, shared_with, permission FROM addressbook_shares WHERE owner = ? AND addressbook_name = ? AND shared_with = ?`, owner, bookName, user) var s AddressBookShare var perm string if err := row.Scan(&s.Owner, &s.AddressBookName, &s.SharedWith, &perm); err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrShareNotFound } return nil, fmt.Errorf("looking up address book share: %w", err) } s.Permission = Permission(perm) return &s, nil }