226 lines
5.5 KiB
Go
226 lines
5.5 KiB
Go
// SPDX-FileCopyrightText: 2021 The NGI Pointer Secure-Scuttlebutt Team of 2020/2021
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
|
|
"github.com/friendsofgo/errors"
|
|
"github.com/mattn/go-sqlite3"
|
|
refs "github.com/ssbc/go-ssb-refs"
|
|
"github.com/ssbc/go-ssb-room/v2/roomdb"
|
|
"github.com/ssbc/go-ssb-room/v2/roomdb/sqlite/models"
|
|
"github.com/volatiletech/sqlboiler/v4/boil"
|
|
"github.com/volatiletech/sqlboiler/v4/queries/qm"
|
|
)
|
|
|
|
// compiler assertion to ensure the struct fullfills the interface
|
|
var _ roomdb.MembersService = (*Members)(nil)
|
|
|
|
type Members struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// getAliases returns the JOIN-ed aliases for a member as a the wanted roomdb type.
|
|
func (Members) getAliases(mEntry *models.Member) []roomdb.Alias {
|
|
if mEntry.R == nil || mEntry.R.Aliases == nil {
|
|
return make([]roomdb.Alias, 0)
|
|
}
|
|
var aliases = make([]roomdb.Alias, len(mEntry.R.Aliases))
|
|
for j, aEntry := range mEntry.R.Aliases {
|
|
aliases[j].ID = aEntry.ID
|
|
aliases[j].Feed = mEntry.PubKey.FeedRef
|
|
aliases[j].Name = aEntry.Name
|
|
aliases[j].Signature = aEntry.Signature
|
|
}
|
|
return aliases
|
|
}
|
|
|
|
func (m Members) Add(ctx context.Context, pubKey refs.FeedRef, role roomdb.Role) (int64, error) {
|
|
var newID int64
|
|
err := transact(m.db, func(tx *sql.Tx) error {
|
|
var err error
|
|
newID, err = m.add(ctx, tx, pubKey, role)
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return -1, err
|
|
}
|
|
return newID, nil
|
|
}
|
|
|
|
// no receiver name because it needs to use the passed transaction
|
|
func (Members) add(ctx context.Context, tx *sql.Tx, pubKey refs.FeedRef, role roomdb.Role) (int64, error) {
|
|
if err := role.IsValid(); err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
if _, err := refs.ParseFeedRef(pubKey.String()); err != nil {
|
|
return -1, err
|
|
}
|
|
|
|
var newMember models.Member
|
|
newMember.PubKey = roomdb.DBFeedRef{FeedRef: pubKey}
|
|
newMember.Role = int64(role)
|
|
|
|
err := newMember.Insert(ctx, tx, boil.Infer())
|
|
if err != nil {
|
|
var sqlErr sqlite3.Error
|
|
if errors.As(err, &sqlErr) && sqlErr.ExtendedCode == sqlite3.ErrConstraintUnique {
|
|
return -1, roomdb.ErrAlreadyAdded{Ref: pubKey}
|
|
}
|
|
return -1, fmt.Errorf("members: failed to insert new user: %w", err)
|
|
}
|
|
|
|
return newMember.ID, nil
|
|
}
|
|
|
|
func (m Members) GetByID(ctx context.Context, mid int64) (roomdb.Member, error) {
|
|
entry, err := models.FindMember(ctx, m.db, mid)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return roomdb.Member{}, roomdb.ErrNotFound
|
|
}
|
|
return roomdb.Member{}, err
|
|
}
|
|
|
|
// resolve alias relation
|
|
entry.R = entry.R.NewStruct()
|
|
entry.R.Aliases, err = entry.Aliases().All(ctx, m.db)
|
|
if err != nil {
|
|
return roomdb.Member{}, err
|
|
}
|
|
|
|
return roomdb.Member{
|
|
ID: entry.ID,
|
|
Role: roomdb.Role(entry.Role),
|
|
PubKey: entry.PubKey.FeedRef,
|
|
Aliases: m.getAliases(entry),
|
|
}, nil
|
|
}
|
|
|
|
// GetByFeed returns the member if it exists
|
|
func (m Members) GetByFeed(ctx context.Context, h refs.FeedRef) (roomdb.Member, error) {
|
|
entry, err := models.Members(qm.Where("pub_key = ?", h.String())).One(ctx, m.db)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return roomdb.Member{}, roomdb.ErrNotFound
|
|
}
|
|
return roomdb.Member{}, err
|
|
}
|
|
|
|
// resolve alias relation
|
|
entry.R = entry.R.NewStruct()
|
|
entry.R.Aliases, err = entry.Aliases().All(ctx, m.db)
|
|
if err != nil {
|
|
return roomdb.Member{}, err
|
|
}
|
|
|
|
return roomdb.Member{
|
|
ID: entry.ID,
|
|
Role: roomdb.Role(entry.Role),
|
|
PubKey: entry.PubKey.FeedRef,
|
|
Aliases: m.getAliases(entry),
|
|
}, nil
|
|
}
|
|
|
|
// List returns a list of all the feeds.
|
|
func (m Members) List(ctx context.Context) ([]roomdb.Member, error) {
|
|
all, err := models.Members(qm.Load("Aliases")).All(ctx, m.db)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var members = make([]roomdb.Member, len(all))
|
|
for i, entry := range all {
|
|
members[i].ID = entry.ID
|
|
members[i].Role = roomdb.Role(entry.Role)
|
|
members[i].PubKey = entry.PubKey.FeedRef
|
|
members[i].Aliases = m.getAliases(entry)
|
|
}
|
|
|
|
return members, nil
|
|
}
|
|
|
|
func (m Members) Count(ctx context.Context) (uint, error) {
|
|
count, err := models.Members().Count(ctx, m.db)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return uint(count), nil
|
|
}
|
|
|
|
// RemoveFeed removes the feed from the list.
|
|
func (m Members) RemoveFeed(ctx context.Context, r refs.FeedRef) error {
|
|
entry, err := models.Members(qm.Where("pub_key = ?", r.String())).One(ctx, m.db)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return roomdb.ErrNotFound
|
|
}
|
|
return err
|
|
}
|
|
|
|
_, err = entry.Delete(ctx, m.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveID removes the feed from the list.
|
|
func (m Members) RemoveID(ctx context.Context, id int64) error {
|
|
entry, err := models.FindMember(ctx, m.db, id)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return roomdb.ErrNotFound
|
|
}
|
|
return err
|
|
}
|
|
|
|
_, err = entry.Delete(ctx, m.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetRole updates the role r of the passed memberID.
|
|
func (m Members) SetRole(ctx context.Context, id int64, r roomdb.Role) error {
|
|
if err := r.IsValid(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return transact(m.db, func(tx *sql.Tx) error {
|
|
m, err := models.FindMember(ctx, tx, id)
|
|
if err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return roomdb.ErrNotFound
|
|
}
|
|
return err
|
|
}
|
|
|
|
// find the number of other admins
|
|
admins, err := models.Members(
|
|
qm.Where("id != ?", id),
|
|
qm.Where("role = ?", roomdb.RoleAdmin),
|
|
).Count(ctx, tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if admins < 1 {
|
|
return fmt.Errorf("need at least one other admin")
|
|
}
|
|
|
|
m.Role = int64(r)
|
|
_, err = m.Update(ctx, tx, boil.Whitelist("role"))
|
|
return err
|
|
})
|
|
}
|