package api
// chatidentity.go is which quarry account a bridged chat name is
// (oltp/chatidentity.go): fossil:stephan shown as stephan, because an
// administrator of the repository said so, or because it is the
// bridge's own login and the bridge signs in as it.
import (
"context"
"fmt"
"log"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
)
// ChatIdentityStore is the chat_identity table, satisfied by *oltp.DB.
type ChatIdentityStore interface {
SetChatIdentity(repo, remote, account, by string) error
ChatIdentities(repo string) ([]oltp.ChatIdentity, error)
}
// bridgeLinkBy is the chat_identity.by of a bridge's own login: the
// forge knows it is the person who set the bridge up, because the
// bridge signs in as it with their password.
const bridgeLinkBy = "bridge"
// linkBridgeLogin links a bridge's own login to whoever set it up.
func (d Deps) linkBridgeLogin(repo, login, setter string) {
if d.ChatIdentities == nil || login == "" || setter == "" {
return
}
if err := d.ChatIdentities.SetChatIdentity(repo, "fossil:"+login, setter, bridgeLinkBy); err != nil {
log.Printf("chat bridge %s: linking its login %s to %s: %v", repo, login, setter, err)
}
}
type chatIdentitiesIn struct {
Repo string `json:"repo" validate:"required,max=64"`
}
type chatIdentitiesOut struct {
Repo string `json:"repo"`
Links []oltp.ChatIdentity `json:"links"`
}
type chatIdentitySetIn struct {
Repo string `json:"repo" validate:"required,max=64"`
// Remote is the bridged name as the channel shows it, fossil:<login>.
Remote string `json:"remote" validate:"required,max=140"`
// Account is the quarry account it is; empty removes the link.
Account string `json:"account" validate:"max=64"`
}
func registerChatIdentity(r *ops.Registry, d Deps) error {
if err := ops.Register(r, ops.Op[chatIdentitiesIn, chatIdentitiesOut]{
Name: "chat.identities",
Caps: "C",
Doc: "Which quarry accounts a repository channel's bridged names are: `fossil:<login>` shown as the account, " +
"each link with who made it (an administrator, or `bridge` for the bridge's own login).",
Kind: ops.Query,
Auth: ops.Token,
Route: "GET /api/v1/r/{repo}/chat/identities",
Run: func(ctx context.Context, in chatIdentitiesIn) (chatIdentitiesOut, error) {
if _, err := d.repoFor(ctx, in.Repo); err != nil {
return chatIdentitiesOut{}, err
}
out := chatIdentitiesOut{Repo: in.Repo, Links: []oltp.ChatIdentity{}}
if d.ChatIdentities == nil {
return out, nil
}
links, err := d.ChatIdentities.ChatIdentities(in.Repo)
if err != nil {
return chatIdentitiesOut{}, err
}
if links != nil {
out.Links = links
}
return out, nil
},
}); err != nil {
return err
}
return ops.Register(r, ops.Op[chatIdentitySetIn, chatIdentitiesOut]{
Name: "chat.identity.set",
Caps: "a",
Doc: "Say which quarry account a bridged chat name is, `fossil:<login>`, so the channel shows the account's name " +
"and identicon for it; an empty account removes the link. An administrator's statement, recorded as theirs: " +
"a fossil login proves nothing on its own.",
Kind: ops.Command,
Auth: ops.Token,
Route: "POST /api/v1/r/{repo}/chat/identities",
Run: func(ctx context.Context, in chatIdentitySetIn) (chatIdentitiesOut, error) {
if _, err := d.repoFor(ctx, in.Repo); err != nil {
return chatIdentitiesOut{}, err
}
if d.ChatIdentities == nil {
return chatIdentitiesOut{}, fmt.Errorf("this forge keeps no chat identity links: %w", ops.ErrUnavailable)
}
if err := d.ChatIdentities.SetChatIdentity(in.Repo, in.Remote, in.Account, author(ctx)); err != nil {
return chatIdentitiesOut{}, refusalAsInvalid(err)
}
links, err := d.ChatIdentities.ChatIdentities(in.Repo)
if err != nil {
return chatIdentitiesOut{}, err
}
if links == nil {
links = []oltp.ChatIdentity{}
}
return chatIdentitiesOut{Repo: in.Repo, Links: links}, nil
},
})
}