// chatidentity.go links a bridged chat name ("fossil:stephan") to a
// quarry account, per repository, so the channel shows the account's
// name and identicon for what that person writes on the upstream fossil.
//
// The forge claims only what someone stands behind. A bridge's own login
// is linked to whoever set the bridge up, since the bridge signs in as
// it with their password; any other name is linked by an administrator
// of the repository, who knows it is the same person. Each row says
// who made it, and removing it puts the name back to fossil:<login>.
// A fossil login proves nothing on its own: anybody can register any
// name on any fossil, and matching names would let them post here as
// somebody else.
package oltp
import (
"fmt"
"strings"
"time"
)
const chatIdentitySchema = `
CREATE TABLE IF NOT EXISTS chat_identity (
repo TEXT NOT NULL,
remote TEXT NOT NULL, -- the bridged name as the channel holds it: fossil:<login>
account TEXT NOT NULL, -- the quarry account it is
by TEXT NOT NULL, -- who linked it: an administrator's login, or "bridge" for its own login
at TEXT NOT NULL,
PRIMARY KEY (repo, remote)
);`
// ChatIdentity is one link from a bridged name to an account.
type ChatIdentity struct {
Remote string `json:"remote"`
Account string `json:"account"`
By string `json:"by"`
At string `json:"at"`
}
// SetChatIdentity links remote (fossil:<login>) to account in repo's
// channel, or removes the link when account is empty. by is who stands
// behind it.
func (d *DB) SetChatIdentity(repo, remote, account, by string) error {
if !strings.HasPrefix(remote, "fossil:") || len(remote) == len("fossil:") {
return Refusal{fmt.Sprintf("%q is not a bridged name: give it as fossil:<login>", remote)}
}
if account == "" {
_, err := d.w.Exec(`DELETE FROM chat_identity WHERE repo = ? AND remote = ?`, repo, remote)
return err
}
if _, ok := d.AccountByName(account); !ok {
return Refusal{fmt.Sprintf("there is no account %q on this forge", account)}
}
_, err := d.w.Exec(`INSERT INTO chat_identity (repo, remote, account, by, at) VALUES (?, ?, ?, ?, ?)
ON CONFLICT (repo, remote) DO UPDATE SET account = excluded.account, by = excluded.by, at = excluded.at`,
repo, remote, account, by, time.Now().UTC().Format(time.RFC3339))
return err
}
// ChatIdentities lists repo's links, by bridged name.
func (d *DB) ChatIdentities(repo string) ([]ChatIdentity, error) {
rows, err := d.db.Query(`SELECT remote, account, by, at FROM chat_identity WHERE repo = ? ORDER BY remote`, repo)
if err != nil {
return nil, err
}
defer rows.Close()
var out []ChatIdentity
for rows.Next() {
var c ChatIdentity
if err := rows.Scan(&c.Remote, &c.Account, &c.By, &c.At); err != nil {
return nil, err
}
out = append(out, c)
}
return out, rows.Err()
}