Gitfed
bastien-mrq/gitfed / cmd / gitfed-tui / actions.go
package main

import (
	"fmt"
	"strings"

	"github.com/charmbracelet/bubbles/list"
	tea "github.com/charmbracelet/bubbletea"

	"git.neuromancer.ovh/bastien-mrq/gitfed/internal/store"
)

func (m *model) refreshUsers() {
	users, _ := m.ops.ListUsers()
	items := make([]list.Item, len(users))
	for i, u := range users {
		items[i] = userItem{u}
	}
	m.usersList.SetItems(items)
}

func (m *model) refreshRepos() {
	repos, _ := m.ops.ListRepos()
	items := make([]list.Item, len(repos))
	for i, r := range repos {
		items[i] = repoItem{r}
	}
	m.reposList.SetItems(items)
}

func (m *model) refreshACL() {
	repo, err := m.ops.GetRepo(m.selectedRepo)
	if err != nil {
		m.aclList.SetItems(nil)
		return
	}
	items := []list.Item{
		collaboratorItem{c: store.Collaborator{Principal: repo.Owner, Role: store.RoleAdmin}, isOwner: true},
	}
	a, err := m.ops.GetACL(m.selectedRepo)
	if err == nil {
		for _, c := range a.Collaborators {
			items = append(items, collaboratorItem{c: c})
		}
	}
	m.aclList.SetItems(items)
}

func (m *model) refreshTrust() {
	trusted, _ := m.ops.ListTrustedCAs()
	items := make([]list.Item, len(trusted))
	for i, t := range trusted {
		items[i] = trustItem{t}
	}
	m.trustList.SetItems(items)
}

func (m *model) refreshAudit() {
	events, _ := m.ops.ListAudit(200)
	items := make([]list.Item, len(events))
	for i, e := range events {
		items[i] = auditItem{e}
	}
	m.auditList.SetItems(items)
}

func (m model) openAddUserForm() (tea.Model, tea.Cmd) {
	a := m.ops
	m.activeForm = newForm("Add user",
		[]string{"Username", "Public key", "Password", "Admin (y/n)"},
		[]string{"alice", "ssh-ed25519 AAAA... comment", "min. 8 characters", "n"},
		func(values []string) tea.Msg {
			username := strings.TrimSpace(values[0])
			if username == "" {
				return statusMsg{err: fmt.Errorf("username is required")}
			}
			if err := a.CreateUser(username, values[1]); err != nil {
				return statusMsg{err: err}
			}
			if password := values[2]; password != "" {
				if err := a.SetPassword(username, password); err != nil {
					return statusMsg{err: err}
				}
			}
			if isAdmin := strings.EqualFold(strings.TrimSpace(values[3]), "y"); isAdmin {
				if err := a.SetUserAdmin(username, true); err != nil {
					return statusMsg{err: err}
				}
			}
			return statusMsg{text: "user " + username + " created"}
		},
		func() tea.Msg { return formCancelMsg{} },
	)
	return m, nil
}

func (m model) deleteSelectedUser() (tea.Model, tea.Cmd) {
	item, ok := m.usersList.SelectedItem().(userItem)
	if !ok {
		return m, nil
	}
	if err := m.ops.DeleteUser(item.u.Username); err != nil {
		m.err, m.status = err, ""
	} else {
		m.status, m.err = "deleted user "+item.u.Username, nil
	}
	m.refreshUsers()
	return m, nil
}

func (m model) openAddRepoForm() (tea.Model, tea.Cmd) {
	a := m.ops
	m.activeForm = newForm("Add repo",
		[]string{"Name", "Owner (local username)"},
		[]string{"alice/mon-projet", "alice"},
		func(values []string) tea.Msg {
			name := strings.TrimSpace(values[0])
			owner := strings.TrimSpace(values[1])
			if name == "" || owner == "" {
				return statusMsg{err: fmt.Errorf("name and owner are required")}
			}
			if err := a.CreateRepo(name, owner); err != nil {
				return statusMsg{err: err}
			}
			return statusMsg{text: "repo " + name + " created"}
		},
		func() tea.Msg { return formCancelMsg{} },
	)
	return m, nil
}

func (m model) deleteSelectedRepo() (tea.Model, tea.Cmd) {
	item, ok := m.reposList.SelectedItem().(repoItem)
	if !ok {
		return m, nil
	}
	if err := m.ops.DeleteRepo(item.r.Name); err != nil {
		m.err, m.status = err, ""
	} else {
		m.status, m.err = "deleted repo "+item.r.Name, nil
	}
	m.refreshRepos()
	return m, nil
}

func (m model) openACLForSelectedRepo() (tea.Model, tea.Cmd) {
	item, ok := m.reposList.SelectedItem().(repoItem)
	if !ok {
		return m, nil
	}
	m.selectedRepo = item.r.Name
	m.aclList.Title = "Collaborators — " + item.r.Name
	m.refreshACL()
	m.view = viewACL
	m.status, m.err = "", nil
	return m, nil
}

func (m model) openAddCollaboratorForm() (tea.Model, tea.Cmd) {
	a := m.ops
	repoName := m.selectedRepo
	actor := "admin@" + m.domain
	m.activeForm = newForm("Add collaborator",
		[]string{"Principal", "Role"},
		[]string{"bob@instanceb.example", "read|write|admin"},
		func(values []string) tea.Msg {
			principal := strings.TrimSpace(values[0])
			role := store.Role(strings.ToLower(strings.TrimSpace(values[1])))
			if !role.Valid() {
				return statusMsg{err: fmt.Errorf("role must be read, write or admin")}
			}
			if err := a.GrantCollaborator(repoName, principal, actor, role); err != nil {
				return statusMsg{err: err}
			}
			return statusMsg{text: principal + " granted " + string(role) + " on " + repoName}
		},
		func() tea.Msg { return formCancelMsg{} },
	)
	return m, nil
}

func (m model) deleteSelectedCollaborator() (tea.Model, tea.Cmd) {
	item, ok := m.aclList.SelectedItem().(collaboratorItem)
	if !ok {
		return m, nil
	}
	if item.isOwner {
		m.status, m.err = "", fmt.Errorf("cannot remove the repo owner")
		return m, nil
	}
	if err := m.ops.RevokeCollaborator(m.selectedRepo, item.c.Principal); err != nil {
		m.err, m.status = err, ""
	} else {
		m.status, m.err = "removed "+item.c.Principal, nil
	}
	m.refreshACL()
	return m, nil
}

func (m model) approveSelectedDomain() (tea.Model, tea.Cmd) {
	item, ok := m.trustList.SelectedItem().(trustItem)
	if !ok {
		return m, nil
	}
	if err := m.ops.ApproveDomain(item.t.Domain); err != nil {
		m.err, m.status = err, ""
	} else {
		m.status, m.err = "approved "+item.t.Domain, nil
	}
	m.refreshTrust()
	return m, nil
}