Skip to content
quarry Artifact [6d77559d75]
Login

Artifact [6d77559d75]

Artifact 6d77559d7509e089ce5635e31f1e4bf95fbaa9687821d2c6dcc60bb9ebb07ef9:


// 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()
}