2021-02-09 11:53:33 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
2021-03-10 15:44:46 +00:00
|
|
|
package roomdb
|
2021-02-09 11:39:57 +00:00
|
|
|
|
2021-02-11 15:43:19 +00:00
|
|
|
import (
|
|
|
|
"database/sql/driver"
|
2021-02-15 09:40:43 +00:00
|
|
|
"errors"
|
2021-02-11 15:43:19 +00:00
|
|
|
"fmt"
|
2021-02-23 19:23:50 +00:00
|
|
|
"sort"
|
2021-03-16 16:13:01 +00:00
|
|
|
"time"
|
2021-02-11 15:43:19 +00:00
|
|
|
|
|
|
|
refs "go.mindeco.de/ssb-refs"
|
|
|
|
)
|
|
|
|
|
2021-02-15 09:40:43 +00:00
|
|
|
// ErrNotFound is returned by the admin db if an object couldn't be found.
|
2021-03-10 15:44:46 +00:00
|
|
|
var ErrNotFound = errors.New("roomdb: object not found")
|
2021-02-15 09:40:43 +00:00
|
|
|
|
2021-03-11 16:57:55 +00:00
|
|
|
// Alias is how the roomdb stores an alias.
|
|
|
|
type Alias struct {
|
|
|
|
ID int64
|
|
|
|
|
|
|
|
Name string // or "alias string" as the docs call it
|
|
|
|
|
|
|
|
Feed refs.FeedRef // the ssb identity that belongs to the user
|
|
|
|
|
|
|
|
Signature []byte
|
|
|
|
}
|
|
|
|
|
2021-03-18 16:49:52 +00:00
|
|
|
// Member holds all the information an internal user of the room has.
|
|
|
|
type Member struct {
|
2021-03-29 08:23:03 +00:00
|
|
|
ID int64
|
|
|
|
Role Role
|
|
|
|
PubKey refs.FeedRef
|
2021-02-09 11:39:57 +00:00
|
|
|
}
|
2021-02-11 15:43:19 +00:00
|
|
|
|
2021-03-24 09:58:32 +00:00
|
|
|
//go:generate go run golang.org/x/tools/cmd/stringer -type=PrivacyMode
|
|
|
|
|
|
|
|
type PrivacyMode uint
|
|
|
|
|
|
|
|
func (pm PrivacyMode) IsValid() error {
|
|
|
|
if pm == ModeUnknown || pm > ModeRestricted {
|
|
|
|
return errors.New("No such privacy mode")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParsePrivacyMode(val string) PrivacyMode {
|
|
|
|
switch val {
|
|
|
|
case "open":
|
|
|
|
return ModeOpen
|
|
|
|
case "community":
|
|
|
|
return ModeCommunity
|
|
|
|
case "restricted":
|
|
|
|
return ModeRestricted
|
|
|
|
default:
|
|
|
|
return ModeUnknown
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// PrivacyMode describes the access mode the room server is currently running under.
|
|
|
|
// ModeOpen allows anyone to create an room invite
|
|
|
|
// ModeCommunity restricts invite creation to pre-existing room members (i.e. "internal users")
|
|
|
|
// ModeRestricted only allows admins and moderators to create room invitations
|
|
|
|
const (
|
|
|
|
ModeUnknown PrivacyMode = iota
|
|
|
|
ModeOpen
|
|
|
|
ModeCommunity
|
|
|
|
ModeRestricted
|
|
|
|
)
|
|
|
|
|
2021-03-29 13:42:09 +00:00
|
|
|
//go:generate go run golang.org/x/tools/cmd/stringer -type=Role
|
2021-03-18 16:49:52 +00:00
|
|
|
|
|
|
|
// Role describes the authorization level of an internal user (or member).
|
|
|
|
// Valid roles are Member, Moderator or Admin.
|
|
|
|
// The zero value Uknown is used to detect missing initializion while not falling into a bad default.
|
|
|
|
type Role uint
|
|
|
|
|
|
|
|
func (r Role) IsValid() error {
|
|
|
|
if r == RoleUnknown {
|
2021-03-24 09:58:32 +00:00
|
|
|
return errors.New("unknown member role")
|
2021-03-18 16:49:52 +00:00
|
|
|
}
|
|
|
|
if r > RoleAdmin {
|
|
|
|
return errors.New("invalid member role")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
RoleUnknown Role = iota
|
|
|
|
RoleMember
|
|
|
|
RoleModerator
|
|
|
|
RoleAdmin
|
|
|
|
)
|
|
|
|
|
2021-03-22 09:32:49 +00:00
|
|
|
func (r *Role) UnmarshalText(text []byte) error {
|
|
|
|
roleStr := string(text)
|
|
|
|
switch roleStr {
|
|
|
|
|
|
|
|
case RoleAdmin.String():
|
|
|
|
*r = RoleAdmin
|
|
|
|
|
|
|
|
case RoleModerator.String():
|
|
|
|
*r = RoleModerator
|
|
|
|
|
|
|
|
case RoleMember.String():
|
|
|
|
*r = RoleMember
|
|
|
|
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("unknown member role: %q", roleStr)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-02-15 16:21:06 +00:00
|
|
|
type ErrAlreadyAdded struct {
|
|
|
|
Ref refs.FeedRef
|
|
|
|
}
|
|
|
|
|
|
|
|
func (aa ErrAlreadyAdded) Error() string {
|
2021-03-10 15:44:46 +00:00
|
|
|
return fmt.Sprintf("roomdb: the item (%s) is already on the list", aa.Ref.Ref())
|
2021-02-15 16:21:06 +00:00
|
|
|
}
|
|
|
|
|
2021-03-02 16:14:02 +00:00
|
|
|
// Invite is a combination of an invite id, who created it and an (optional) alias suggestion.
|
|
|
|
// The token itself is only visible from the db.Create function and stored hashed in the database
|
|
|
|
type Invite struct {
|
|
|
|
ID int64
|
|
|
|
|
2021-03-18 16:49:52 +00:00
|
|
|
CreatedBy Member
|
2021-03-16 16:13:01 +00:00
|
|
|
CreatedAt time.Time
|
2021-03-02 16:14:02 +00:00
|
|
|
}
|
|
|
|
|
2021-03-18 16:49:52 +00:00
|
|
|
// ListEntry values are returned by the DenyListServices
|
2021-02-15 09:40:43 +00:00
|
|
|
type ListEntry struct {
|
|
|
|
ID int64
|
|
|
|
PubKey refs.FeedRef
|
2021-03-19 11:28:14 +00:00
|
|
|
|
|
|
|
CreatedAt time.Time
|
|
|
|
Comment string
|
2021-02-15 09:40:43 +00:00
|
|
|
}
|
|
|
|
|
2021-02-11 15:43:19 +00:00
|
|
|
// DBFeedRef wraps a feed reference and implements the SQL marshaling interfaces.
|
|
|
|
type DBFeedRef struct{ refs.FeedRef }
|
|
|
|
|
|
|
|
// Scan implements https://pkg.go.dev/database/sql#Scanner to read strings into feed references.
|
|
|
|
func (r *DBFeedRef) Scan(src interface{}) error {
|
|
|
|
str, ok := src.(string)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("unexpected type: %T", src)
|
|
|
|
}
|
|
|
|
|
|
|
|
fr, err := refs.ParseFeedRef(str)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
r.FeedRef = *fr
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Value returns feed references as strings to the database.
|
|
|
|
// https://pkg.go.dev/database/sql/driver#Valuer
|
|
|
|
func (r DBFeedRef) Value() (driver.Value, error) {
|
|
|
|
return driver.Value(r.Ref()), nil
|
|
|
|
}
|
2021-02-22 16:55:12 +00:00
|
|
|
|
|
|
|
// PinnedNoticeName holds a name of a well known part of the page with a fixed location.
|
|
|
|
// These also double as the i18n labels.
|
|
|
|
type PinnedNoticeName string
|
|
|
|
|
2021-02-25 09:27:26 +00:00
|
|
|
func (n PinnedNoticeName) String() string {
|
|
|
|
return string(n)
|
|
|
|
}
|
|
|
|
|
2021-02-22 16:55:12 +00:00
|
|
|
// These are the well known names that the room page will display
|
|
|
|
const (
|
|
|
|
NoticeDescription PinnedNoticeName = "NoticeDescription"
|
|
|
|
NoticeNews PinnedNoticeName = "NoticeNews"
|
|
|
|
NoticePrivacyPolicy PinnedNoticeName = "NoticePrivacyPolicy"
|
|
|
|
NoticeCodeOfConduct PinnedNoticeName = "NoticeCodeOfConduct"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Valid returns true if the page name is well known.
|
2021-02-25 09:27:26 +00:00
|
|
|
func (n PinnedNoticeName) Valid() bool {
|
|
|
|
return n == NoticeNews ||
|
|
|
|
n == NoticeDescription ||
|
|
|
|
n == NoticePrivacyPolicy ||
|
|
|
|
n == NoticeCodeOfConduct
|
2021-02-22 16:55:12 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 19:23:50 +00:00
|
|
|
type PinnedNotices map[PinnedNoticeName][]Notice
|
|
|
|
|
2021-02-22 16:55:12 +00:00
|
|
|
// Notice holds the title and content of a page that is user generated
|
|
|
|
type Notice struct {
|
|
|
|
ID int64
|
|
|
|
Title string
|
|
|
|
Content string
|
|
|
|
Language string
|
|
|
|
}
|
2021-02-23 19:23:50 +00:00
|
|
|
|
|
|
|
type PinnedNotice struct {
|
|
|
|
Name PinnedNoticeName
|
|
|
|
Notices []Notice
|
|
|
|
}
|
|
|
|
|
|
|
|
type SortedPinnedNotices []PinnedNotice
|
|
|
|
|
|
|
|
// Sorted returns a sorted list of the map, by the key names
|
|
|
|
func (pn PinnedNotices) Sorted() SortedPinnedNotices {
|
|
|
|
|
|
|
|
lst := make(SortedPinnedNotices, 0, len(pn))
|
|
|
|
|
|
|
|
for name, notices := range pn {
|
|
|
|
lst = append(lst, PinnedNotice{
|
|
|
|
Name: name,
|
|
|
|
Notices: notices,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Sort(lst)
|
|
|
|
return lst
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ sort.Interface = (SortedPinnedNotices)(nil)
|
|
|
|
|
|
|
|
func (byName SortedPinnedNotices) Len() int { return len(byName) }
|
|
|
|
|
|
|
|
func (byName SortedPinnedNotices) Less(i, j int) bool {
|
|
|
|
return byName[i].Name < byName[j].Name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (byName SortedPinnedNotices) Swap(i, j int) {
|
|
|
|
byName[i], byName[j] = byName[j], byName[i]
|
|
|
|
}
|