diff --git a/appview/pages/pages.go b/appview/pages/pages.go
index e20d77af..8cfa4d90 100644
--- a/appview/pages/pages.go
+++ b/appview/pages/pages.go
@@ -17,7 +17,6 @@ import (
"sync"
"time"
- "tangled.org/core/api/tangled"
"tangled.org/core/appview/cache"
"tangled.org/core/appview/commitverify"
"tangled.org/core/appview/config"
@@ -675,8 +674,7 @@ func (p *Pages) KnotListing(w io.Writer, params KnotListingParams) error {
type SpindlesParams struct {
BaseParams
- Spindles []models.Spindle
- Tab string
+ Tab string
}
func (p *Pages) Spindles(w io.Writer, params SpindlesParams) error {
@@ -684,27 +682,6 @@ func (p *Pages) Spindles(w io.Writer, params SpindlesParams) error {
return p.execute("spindles/index", w, params)
}
-type SpindleListingParams struct {
- models.Spindle
- Tab string
-}
-
-func (p *Pages) SpindleListing(w io.Writer, params SpindleListingParams) error {
- return p.executePlain("spindles/fragments/spindleListing", w, params)
-}
-
-type SpindleDashboardParams struct {
- BaseParams
- Spindle models.Spindle
- Members []string
- Repos map[string][]models.Repo
- Tab string
-}
-
-func (p *Pages) SpindleDashboard(w io.Writer, params SpindleDashboardParams) error {
- return p.execute("spindles/dashboard", w, params)
-}
-
type NewRepoParams struct {
BaseParams
Knots []string
@@ -1205,25 +1182,6 @@ type Collaborator struct {
Role string
}
-type RepoSettingsParams struct {
- BaseParams
- RepoInfo repoinfo.RepoInfo
- Collaborators []Collaborator
- Active string
- Branches []types.Branch
- Spindles []string
- CurrentSpindle string
- Secrets []*tangled.RepoListSecrets_Secret
-
- // TODO: use repoinfo.roles
- IsCollaboratorInviteAllowed bool
-}
-
-func (p *Pages) RepoSettings(w io.Writer, params RepoSettingsParams) error {
- params.Active = "settings"
- return p.executeRepo("repo/settings", w, params)
-}
-
type RepoGeneralSettingsParams struct {
BaseParams
RepoInfo repoinfo.RepoInfo
diff --git a/appview/pages/templates/repo/new.html b/appview/pages/templates/repo/new.html
index 6a375e6f..42b1cf29 100644
--- a/appview/pages/templates/repo/new.html
+++ b/appview/pages/templates/repo/new.html
@@ -229,7 +229,6 @@
A spindle runs your CI workflows.
- You can also register your own spindle.
{{ end }}
diff --git a/appview/pages/templates/spindles/dashboard.html b/appview/pages/templates/spindles/dashboard.html
deleted file mode 100644
index a79abad5..00000000
--- a/appview/pages/templates/spindles/dashboard.html
+++ /dev/null
@@ -1,133 +0,0 @@
-{{ define "title" }}{{ .Tab | capitalize }} · {{.Spindle.Instance}} · Tangled{{ end }}
-
-{{ define "content" }}
-
-
-
-
- {{ template "user/settings/fragments/sidebar" . }}
-
-
- {{ template "spindleDash" . }}
-
-
-
-{{ end }}
-
-{{ define "spindleDash" }}
-
-
-
{{ .Tab | capitalize }} · {{ .Spindle.Instance }}
-
- {{ $style := "px-2 py-1 rounded flex items-center flex-shrink-0 gap-2" }}
- {{ $isOwner := and .LoggedInUser (eq .LoggedInUser.Did .Spindle.Owner) }}
- {{ if .Spindle.Verified }}
- {{ i "shield-check" "w-4 h-4" }} Verified
- {{ if $isOwner }}
- {{ template "spindles/fragments/addMemberModal" .Spindle }}
- {{ end }}
- {{ else }}
- {{ i "shield-off" "w-4 h-4" }} Unverified
- {{ if $isOwner }}
- {{ block "retryButton" .Spindle }} {{ end }}
- {{ end }}
- {{ end }}
-
- {{ if $isOwner }}
- {{ block "deleteButton" .Spindle }} {{ end }}
- {{ end }}
-
-
-
-
-
-{{ if .Members }}
-
-
- {{ block "member" . }} {{ end }}
-
-
-{{ end }}
-{{ end }}
-
-
-{{ define "member" }}
- {{ range .Members }}
-
-
-
- {{ template "user/fragments/picHandleLink" . }}
-
- {{ if ne $.LoggedInUser.Did . }}
- {{ block "removeMemberButton" (list $ . ) }} {{ end }}
- {{ end }}
-
-
- {{ $repos := index $.Repos . }}
- {{ range $repos }}
-
- {{ else }}
-
- No repositories configured yet.
-
- {{ end }}
-
-
- {{ end }}
-{{ end }}
-
-{{ define "deleteButton" }}
-
-{{ end }}
-
-
-{{ define "retryButton" }}
-
-{{ end }}
-
-
-{{ define "removeMemberButton" }}
- {{ $root := index . 0 }}
- {{ $member := index . 1 }}
-
-{{ end }}
-
diff --git a/appview/pages/templates/spindles/fragments/addMemberModal.html b/appview/pages/templates/spindles/fragments/addMemberModal.html
deleted file mode 100644
index f999bd84..00000000
--- a/appview/pages/templates/spindles/fragments/addMemberModal.html
+++ /dev/null
@@ -1,65 +0,0 @@
-{{ define "spindles/fragments/addMemberModal" }}
-
-
-
- {{ block "addSpindleMemberPopover" . }} {{ end }}
-
-{{ end }}
-
-{{ define "addSpindleMemberPopover" }}
-
-{{ end }}
diff --git a/appview/pages/templates/spindles/fragments/spindleListing.html b/appview/pages/templates/spindles/fragments/spindleListing.html
deleted file mode 100644
index b74f206e..00000000
--- a/appview/pages/templates/spindles/fragments/spindleListing.html
+++ /dev/null
@@ -1,77 +0,0 @@
-{{ define "spindles/fragments/spindleListing" }}
-
- {{ block "spindleLeftSide" . }} {{ end }}
- {{ block "spindleRightSide" . }} {{ end }}
-
-{{ end }}
-
-{{ define "spindleLeftSide" }}
- {{ if .Verified }}
-
- {{ i "hard-drive" "w-4 h-4" }}
-
- {{ .Instance }}
-
-
- {{ template "repo/fragments/shortTimeAgo" .Created }}
-
-
- {{ else }}
-
- {{ i "hard-drive" "w-4 h-4" }}
- {{ .Instance }}
-
- {{ template "repo/fragments/shortTimeAgo" .Created }}
-
-
- {{ end }}
-{{ end }}
-
-{{ define "spindleRightSide" }}
-
- {{ $style := "px-2 py-1 rounded flex items-center flex-shrink-0 gap-2 text-sm" }}
-
- {{ if .NeedsUpgrade }}
- {{ i "shield-alert" "w-4 h-4" }} Needs upgrade
- {{ block "spindleRetryButton" . }} {{ end }}
- {{ else if .Verified }}
- {{ i "shield-check" "w-4 h-4" }} Verified
- {{ template "spindles/fragments/addMemberModal" . }}
- {{ else }}
- {{ i "shield-off" "w-4 h-4" }} Unverified
- {{ block "spindleRetryButton" . }} {{ end }}
- {{ end }}
-
- {{ block "spindleDeleteButton" . }} {{ end }}
-
-{{ end }}
-
-{{ define "spindleDeleteButton" }}
-
-{{ end }}
-
-
-{{ define "spindleRetryButton" }}
-
-{{ end }}
diff --git a/appview/pages/templates/spindles/index.html b/appview/pages/templates/spindles/index.html
index 9f1f9d88..a302ef27 100644
--- a/appview/pages/templates/spindles/index.html
+++ b/appview/pages/templates/spindles/index.html
@@ -20,29 +20,26 @@
Spindle
- {{ block "about" . }} {{ end }}
+
+
+ Spindles are small CI runners.
+
+
- {{ block "list" . }} {{ end }}
- {{ block "register" . }} {{ end }}
{{ end }}
-{{ define "about" }}
-
-
- Spindles are small CI runners.
-
-
-{{ end }}
-
{{ define "list" }}
Your spindles
@@ -58,57 +55,3 @@
{{ end }}
-
-{{ define "register" }}
-
- Register a spindle
- Enter the hostname of your spindle to get started.
-
-
-
-{{ end }}
-
-{{ define "docsButton" }}
-
-{{ i "book" "size-4" }}
-Docs
-
-
-
-{{ end }}
diff --git a/appview/serververify/verify.go b/appview/serververify/verify.go
index f7d2a466..d089659b 100644
--- a/appview/serververify/verify.go
+++ b/appview/serververify/verify.go
@@ -82,50 +82,6 @@ func RunVerification(ctx context.Context, domain, expectedOwner string, dev bool
return nil
}
-// MarkSpindleVerified marks a spindle as verified in the DB and adds the user as its owner
-func MarkSpindleVerified(d *db.DB, e *rbac.Enforcer, instance, owner string) (int64, error) {
- tx, err := d.Begin()
- if err != nil {
- return 0, fmt.Errorf("failed to create txn: %w", err)
- }
- committed := false
- defer func() {
- if committed {
- return
- }
- tx.Rollback()
- e.E.LoadPolicy()
- }()
-
- // mark this spindle as verified in the db
- rowId, err := db.VerifySpindle(
- tx,
- orm.FilterEq("owner", owner),
- orm.FilterEq("instance", instance),
- )
- if err != nil {
- return 0, fmt.Errorf("failed to write to DB: %w", err)
- }
-
- err = e.AddSpindleOwner(instance, owner)
- if err != nil {
- return 0, fmt.Errorf("failed to update ACL: %w", err)
- }
-
- err = tx.Commit()
- if err != nil {
- return 0, fmt.Errorf("failed to commit txn: %w", err)
- }
-
- err = e.E.SavePolicy()
- if err != nil {
- return 0, fmt.Errorf("failed to update ACL: %w", err)
- }
- committed = true
-
- return rowId, nil
-}
-
// MarkKnotVerified marks a knot as verified and sets up ownership/permissions
func MarkKnotVerified(d *db.DB, e *rbac.Enforcer, domain, owner string) error {
tx, err := d.BeginTx(context.Background(), nil)
diff --git a/appview/spindles/spindles.go b/appview/spindles/spindles.go
index 6840fb39..e842beff 100644
--- a/appview/spindles/spindles.go
+++ b/appview/spindles/spindles.go
@@ -1,32 +1,17 @@
package spindles
import (
- "errors"
- "fmt"
"log/slog"
"net/http"
- "slices"
- "strings"
- "time"
"github.com/go-chi/chi/v5"
- "tangled.org/core/api/tangled"
"tangled.org/core/appview/config"
"tangled.org/core/appview/db"
"tangled.org/core/appview/middleware"
- "tangled.org/core/appview/models"
"tangled.org/core/appview/oauth"
"tangled.org/core/appview/pages"
- "tangled.org/core/appview/serververify"
"tangled.org/core/idresolver"
- "tangled.org/core/orm"
"tangled.org/core/rbac"
- "tangled.org/core/tid"
- "tangled.org/core/xrpc/xrpcclient"
-
- comatproto "github.com/bluesky-social/indigo/api/atproto"
- "github.com/bluesky-social/indigo/atproto/syntax"
- lexutil "github.com/bluesky-social/indigo/lex/util"
)
type Spindles struct {
@@ -43,558 +28,13 @@ func (s *Spindles) Router() http.Handler {
r := chi.NewRouter()
r.With(middleware.AuthMiddleware(s.OAuth)).Get("/", s.spindles)
- r.With(middleware.AuthMiddleware(s.OAuth)).Post("/register", s.register)
-
- r.With(middleware.AuthMiddleware(s.OAuth)).Get("/{instance}", s.dashboard)
- r.With(middleware.AuthMiddleware(s.OAuth)).Delete("/{instance}", s.delete)
-
- r.With(middleware.AuthMiddleware(s.OAuth)).Post("/{instance}/retry", s.retry)
- r.With(middleware.AuthMiddleware(s.OAuth)).Post("/{instance}/add", s.addMember)
- r.With(middleware.AuthMiddleware(s.OAuth)).Post("/{instance}/remove", s.removeMember)
return r
}
func (s *Spindles) spindles(w http.ResponseWriter, r *http.Request) {
- user := s.OAuth.GetMultiAccountUser(r)
- all, err := db.GetSpindles(
- r.Context(),
- s.Db,
- orm.FilterEq("owner", user.Did),
- )
- if err != nil {
- s.Logger.Error("failed to fetch spindles", "err", err)
- w.WriteHeader(http.StatusInternalServerError)
- return
- }
-
s.Pages.Spindles(w, pages.SpindlesParams{
BaseParams: pages.BaseParamsFromContext(r.Context()),
- Spindles: all,
- Tab: "spindles",
- })
-}
-
-func (s *Spindles) dashboard(w http.ResponseWriter, r *http.Request) {
- l := s.Logger.With("handler", "dashboard")
-
- user := s.OAuth.GetMultiAccountUser(r)
- l = l.With("user", user.Did)
-
- instance := chi.URLParam(r, "instance")
- if instance == "" {
- return
- }
- l = l.With("instance", instance)
-
- spindles, err := db.GetSpindles(
- r.Context(),
- s.Db,
- orm.FilterEq("instance", instance),
- orm.FilterEq("owner", user.Did),
- orm.FilterIsNot("verified", "null"),
- )
- if err != nil || len(spindles) != 1 {
- l.Error("failed to get spindle", "err", err, "len(spindles)", len(spindles))
- http.Error(w, "Not found", http.StatusNotFound)
- return
- }
-
- spindle := spindles[0]
- members, err := s.Enforcer.GetSpindleUsersByRole("server:member", spindle.Instance)
- if err != nil {
- l.Error("failed to get spindle members", "err", err)
- http.Error(w, "Not found", http.StatusInternalServerError)
- return
- }
- slices.Sort(members)
-
- repos, err := db.GetRepos(
- s.Db,
- orm.FilterEq("spindle", instance),
- )
- if err != nil {
- l.Error("failed to get spindle repos", "err", err)
- http.Error(w, "Not found", http.StatusInternalServerError)
- return
- }
-
- // organize repos by did
- repoMap := make(map[string][]models.Repo)
- for _, r := range repos {
- repoMap[r.Did] = append(repoMap[r.Did], r)
- }
-
- s.Pages.SpindleDashboard(w, pages.SpindleDashboardParams{
- BaseParams: pages.BaseParamsFromContext(r.Context()),
- Spindle: spindle,
- Members: members,
- Repos: repoMap,
Tab: "spindles",
})
}
-
-// this endpoint inserts a record on behalf of the user to register that domain
-//
-// when registered, it also makes a request to see if the spindle declares this users as its owner,
-// and if so, marks the spindle as verified.
-//
-// if the spindle is not up yet, the user is free to retry verification at a later point
-func (s *Spindles) register(w http.ResponseWriter, r *http.Request) {
- user := s.OAuth.GetMultiAccountUser(r)
- l := s.Logger.With("handler", "register")
-
- noticeId := "register-error"
- defaultErr := "Failed to register spindle. Try again later."
- fail := func() {
- s.Pages.Notice(w, noticeId, defaultErr)
- }
-
- instance := r.FormValue("instance")
- // Strip protocol, trailing slashes, and whitespace
- // Rkey cannot contain slashes
- instance = strings.TrimSpace(instance)
- instance = strings.TrimPrefix(instance, "https://")
- instance = strings.TrimPrefix(instance, "http://")
- instance = strings.TrimSuffix(instance, "/")
- if instance == "" {
- s.Pages.Notice(w, noticeId, "Incomplete form.")
- return
- }
- l = l.With("instance", instance)
- l = l.With("user", user.Did)
-
- tx, err := s.Db.Begin()
- if err != nil {
- l.Error("failed to start transaction", "err", err)
- fail()
- return
- }
- defer tx.Rollback()
-
- if err := db.AddSpindle(tx, models.Spindle{
- Owner: syntax.DID(user.Did),
- Instance: instance,
- }); err != nil {
- l.Error("failed to insert", "err", err)
- fail()
- return
- }
-
- client, err := s.OAuth.AuthorizedClient(r)
- if err != nil {
- l.Error("failed to authorize client", "err", err)
- fail()
- return
- }
-
- ex, _ := comatproto.RepoGetRecord(r.Context(), client, "", tangled.SpindleNSID, user.Did, instance)
- var exCid *string
- if ex != nil {
- exCid = ex.Cid
- }
-
- _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
- Collection: tangled.SpindleNSID,
- Repo: user.Did,
- Rkey: instance,
- Record: &lexutil.LexiconTypeDecoder{
- Val: &tangled.Spindle{
- CreatedAt: time.Now().Format(time.RFC3339),
- },
- },
- SwapRecord: exCid,
- })
- if err != nil {
- l.Error("failed to put record", "err", err)
- fail()
- return
- }
-
- if err := tx.Commit(); err != nil {
- l.Error("failed to commit transaction", "err", err)
- fail()
- return
- }
-
- s.Pages.HxRefresh(w)
-}
-
-func (s *Spindles) delete(w http.ResponseWriter, r *http.Request) {
- user := s.OAuth.GetMultiAccountUser(r)
- l := s.Logger.With("handler", "delete")
-
- noticeId := "operation-error"
- defaultErr := "Failed to delete spindle. Try again later."
- fail := func() {
- s.Pages.Notice(w, noticeId, defaultErr)
- }
-
- instance := chi.URLParam(r, "instance")
- if instance == "" {
- l.Error("empty instance")
- fail()
- return
- }
-
- spindles, err := db.GetSpindles(
- r.Context(),
- s.Db,
- orm.FilterEq("owner", user.Did),
- orm.FilterEq("instance", instance),
- )
- if err != nil || len(spindles) != 1 {
- l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles))
- fail()
- return
- }
-
- if string(spindles[0].Owner) != user.Did {
- l.Error("unauthorized", "user", user.Did, "owner", spindles[0].Owner)
- s.Pages.Notice(w, noticeId, "Failed to delete spindle, unauthorized deletion attempt.")
- return
- }
-
- tx, err := s.Db.Begin()
- if err != nil {
- l.Error("failed to start txn", "err", err)
- fail()
- return
- }
- defer func() {
- tx.Rollback()
- s.Enforcer.E.LoadPolicy()
- }()
-
- // remove spindle members first
- err = db.RemoveSpindleMember(
- tx,
- orm.FilterEq("did", user.Did),
- orm.FilterEq("instance", instance),
- )
- if err != nil {
- l.Error("failed to remove spindle members", "err", err)
- fail()
- return
- }
-
- err = db.DeleteSpindle(
- tx,
- orm.FilterEq("owner", user.Did),
- orm.FilterEq("instance", instance),
- )
- if err != nil {
- l.Error("failed to delete spindle", "err", err)
- fail()
- return
- }
-
- // delete from enforcer
- if spindles[0].Verified != nil {
- err = s.Enforcer.RemoveSpindle(instance)
- if err != nil {
- l.Error("failed to update ACL", "err", err)
- fail()
- return
- }
- }
-
- client, err := s.OAuth.AuthorizedClient(r)
- if err != nil {
- l.Error("failed to authorize client", "err", err)
- fail()
- return
- }
-
- _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
- Collection: tangled.SpindleNSID,
- Repo: user.Did,
- Rkey: instance,
- })
- if err != nil {
- // non-fatal
- l.Error("failed to delete record", "err", err)
- }
-
- err = tx.Commit()
- if err != nil {
- l.Error("failed to delete spindle", "err", err)
- fail()
- return
- }
-
- err = s.Enforcer.E.SavePolicy()
- if err != nil {
- l.Error("failed to update ACL", "err", err)
- s.Pages.HxRefresh(w)
- return
- }
-
- shouldRedirect := r.Header.Get("shouldRedirect")
- if shouldRedirect == "true" {
- s.Pages.HxRedirect(w, "/settings/spindles")
- return
- }
-
- w.Write([]byte{})
-}
-
-func (s *Spindles) retry(w http.ResponseWriter, r *http.Request) {
- user := s.OAuth.GetMultiAccountUser(r)
- l := s.Logger.With("handler", "retry")
-
- noticeId := "operation-error"
- defaultErr := "Failed to verify spindle. Try again later."
- fail := func() {
- s.Pages.Notice(w, noticeId, defaultErr)
- }
-
- instance := chi.URLParam(r, "instance")
- if instance == "" {
- l.Error("empty instance")
- fail()
- return
- }
- l = l.With("instance", instance)
- l = l.With("user", user.Did)
-
- spindles, err := db.GetSpindles(
- r.Context(),
- s.Db,
- orm.FilterEq("owner", user.Did),
- orm.FilterEq("instance", instance),
- )
- if err != nil || len(spindles) != 1 {
- l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles))
- fail()
- return
- }
-
- if string(spindles[0].Owner) != user.Did {
- l.Error("unauthorized", "user", user.Did, "owner", spindles[0].Owner)
- s.Pages.Notice(w, noticeId, "Failed to verify spindle, unauthorized verification attempt.")
- return
- }
-
- // begin verification
- err = serververify.RunVerification(r.Context(), instance, user.Did, s.Config.Core.Dev)
- if err != nil {
- l.Error("verification failed", "err", err)
-
- if errors.Is(err, xrpcclient.ErrXrpcUnsupported) {
- s.Pages.Notice(w, noticeId, "Failed to verify spindle, XRPC queries are unsupported on this spindle, consider upgrading!")
- return
- }
-
- if e, ok := err.(*serververify.OwnerMismatch); ok {
- s.Pages.Notice(w, noticeId, e.Error())
- return
- }
-
- fail()
- return
- }
-
- rowId, err := serververify.MarkSpindleVerified(s.Db, s.Enforcer, instance, user.Did)
- if err != nil {
- l.Error("failed to mark verified", "err", err)
- s.Pages.Notice(w, noticeId, err.Error())
- return
- }
-
- verifiedSpindle, err := db.GetSpindles(
- r.Context(),
- s.Db,
- orm.FilterEq("id", rowId),
- )
- if err != nil || len(verifiedSpindle) != 1 {
- l.Error("failed get new spindle", "err", err)
- s.Pages.HxRefresh(w)
- return
- }
-
- shouldRefresh := r.Header.Get("shouldRefresh")
- if shouldRefresh == "true" {
- s.Pages.HxRefresh(w)
- return
- }
-
- w.Header().Set("HX-Reswap", "outerHTML")
- s.Pages.SpindleListing(w, pages.SpindleListingParams{Spindle: verifiedSpindle[0]})
-}
-
-func (s *Spindles) addMember(w http.ResponseWriter, r *http.Request) {
- user := s.OAuth.GetMultiAccountUser(r)
- l := s.Logger.With("handler", "addMember")
-
- instance := chi.URLParam(r, "instance")
- if instance == "" {
- l.Error("empty instance")
- http.Error(w, "Not found", http.StatusNotFound)
- return
- }
- l = l.With("instance", instance)
- l = l.With("user", user.Did)
-
- spindles, err := db.GetSpindles(
- r.Context(),
- s.Db,
- orm.FilterEq("owner", user.Did),
- orm.FilterEq("instance", instance),
- )
- if err != nil || len(spindles) != 1 {
- l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles))
- http.Error(w, "Not found", http.StatusNotFound)
- return
- }
-
- noticeId := fmt.Sprintf("add-member-error-%d", spindles[0].Id)
- defaultErr := "Failed to add member. Try again later."
- fail := func() {
- s.Pages.Notice(w, noticeId, defaultErr)
- }
-
- if string(spindles[0].Owner) != user.Did {
- l.Error("unauthorized", "user", user.Did, "owner", spindles[0].Owner)
- s.Pages.Notice(w, noticeId, "Failed to add member, unauthorized attempt.")
- return
- }
-
- member := r.FormValue("member")
- member = strings.TrimPrefix(member, "@")
- if member == "" {
- l.Error("empty member")
- s.Pages.Notice(w, noticeId, "Failed to add member, empty form.")
- return
- }
- l = l.With("member", member)
-
- memberId, err := s.IdResolver.ResolveIdent(r.Context(), member)
- if err != nil {
- l.Error("failed to resolve member identity to handle", "err", err)
- s.Pages.Notice(w, noticeId, "Failed to add member, identity resolution failed.")
- return
- }
- if memberId.Handle.IsInvalidHandle() {
- l.Error("failed to resolve member identity to handle")
- s.Pages.Notice(w, noticeId, "Failed to add member, identity resolution failed.")
- return
- }
-
- // write to pds
- client, err := s.OAuth.AuthorizedClient(r)
- if err != nil {
- l.Error("failed to authorize client", "err", err)
- fail()
- return
- }
-
- rkey := tid.TID()
-
- _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{
- Collection: tangled.SpindleMemberNSID,
- Repo: user.Did,
- Rkey: rkey,
- Record: &lexutil.LexiconTypeDecoder{
- Val: &tangled.SpindleMember{
- CreatedAt: time.Now().Format(time.RFC3339),
- Instance: instance,
- Subject: memberId.DID.String(),
- },
- },
- })
- if err != nil {
- l.Error("failed to add record to PDS", "err", err)
- s.Pages.Notice(w, noticeId, "Failed to add record to PDS, try again later.")
- return
- }
-
- // success
- s.Pages.HxRedirect(w, fmt.Sprintf("/settings/spindles/%s", instance))
-}
-
-func (s *Spindles) removeMember(w http.ResponseWriter, r *http.Request) {
- user := s.OAuth.GetMultiAccountUser(r)
- l := s.Logger.With("handler", "removeMember")
-
- noticeId := "operation-error"
- defaultErr := "Failed to remove member. Try again later."
- fail := func() {
- s.Pages.Notice(w, noticeId, defaultErr)
- }
-
- instance := chi.URLParam(r, "instance")
- if instance == "" {
- l.Error("empty instance")
- fail()
- return
- }
- l = l.With("instance", instance)
- l = l.With("user", user.Did)
-
- spindles, err := db.GetSpindles(
- r.Context(),
- s.Db,
- orm.FilterEq("owner", user.Did),
- orm.FilterEq("instance", instance),
- )
- if err != nil || len(spindles) != 1 {
- l.Error("failed to retrieve instance", "err", err, "len(spindles)", len(spindles))
- fail()
- return
- }
-
- if string(spindles[0].Owner) != user.Did {
- l.Error("unauthorized", "user", user.Did, "owner", spindles[0].Owner)
- s.Pages.Notice(w, noticeId, "Failed to remove member, unauthorized attempt.")
- return
- }
-
- member := r.FormValue("member")
- member = strings.TrimPrefix(member, "@")
- if member == "" {
- l.Error("empty member")
- s.Pages.Notice(w, noticeId, "Failed to remove member, empty form.")
- return
- }
- l = l.With("member", member)
-
- memberId, err := s.IdResolver.ResolveIdent(r.Context(), member)
- if err != nil {
- l.Error("failed to resolve member identity to handle", "err", err)
- s.Pages.Notice(w, noticeId, "Failed to remove member, identity resolution failed.")
- return
- }
-
- members, err := db.GetSpindleMembers(
- s.Db,
- orm.FilterEq("did", user.Did),
- orm.FilterEq("instance", instance),
- orm.FilterEq("subject", memberId.DID),
- )
- if err != nil || len(members) != 1 {
- l.Error("failed to get member", "err", err)
- fail()
- return
- }
-
- client, err := s.OAuth.AuthorizedClient(r)
- if err != nil {
- l.Error("failed to authorize client", "err", err)
- fail()
- return
- }
-
- _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{
- Collection: tangled.SpindleMemberNSID,
- Repo: user.Did,
- Rkey: members[0].Rkey,
- })
- if err != nil {
- l.Error("failed to delete record", "err", err)
- fail()
- return
- }
-
- s.Pages.HxRefresh(w)
-}