From 345b3621bf4bacc13c185611526d527fdf6eed75 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Sun, 15 Jun 2025 11:14:10 +0000 Subject: [PATCH] appview: add spindle to repo settings form Signed-off-by: oppiliappan --- appview/db/db.go | 8 ++++++++ appview/db/repos.go | 30 +++++++++++++++++++++++------- appview/middleware/middleware.go | 1 + appview/pages/pages.go | 12 +++++++----- appview/repo/repo.go | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- appview/repo/router.go | 1 + appview/reporesolver/resolver.go | 3 +++ appview/pages/templates/repo/settings.html | 39 +++++++++++++++++++++++++++++++++++++-- 8 file(s) changed, 179 insertion(s)(+), 24 deletion(s)(-) diff --git a/appview/db/db.go b/appview/db/db.go --- a/appview/db/db.go +++ b/appview/db/db.go @@ -455,6 +455,14 @@ }) db.Exec("pragma foreign_keys = on;") + // run migrations + runMigration(db, "add-spindle-to-repos", func(tx *sql.Tx) error { + tx.Exec(` + alter table repos add column spindle text; + `) + return nil + }) + return &DB{db}, nil } diff --git a/appview/db/repos.go b/appview/db/repos.go --- a/appview/db/repos.go +++ b/appview/db/repos.go @@ -18,6 +18,7 @@ Created time.Time AtUri string Description string + Spindle string // optionally, populate this when querying for reverse mappings RepoStats *RepoStats @@ -138,21 +139,30 @@ func GetRepo(e Execer, did, name string) (*Repo, error) { var repo Repo - var nullableDescription sql.NullString + var description, spindle sql.NullString - row := e.QueryRow(`select did, name, knot, created, at_uri, description from repos where did = ? and name = ?`, did, name) + row := e.QueryRow(` + select did, name, knot, created, at_uri, description, spindle + from repos + where did = ? and name = ? + `, + did, + name, + ) var createdAt string - if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri, &nullableDescription); err != nil { + if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri, &description, &spindle); err != nil { return nil, err } createdAtTime, _ := time.Parse(time.RFC3339, createdAt) repo.Created = createdAtTime - if nullableDescription.Valid { - repo.Description = nullableDescription.String - } else { - repo.Description = "" + if description.Valid { + repo.Description = description.String + } + + if spindle.Valid { + repo.Spindle = spindle.String } return &repo, nil @@ -302,6 +312,12 @@ func UpdateDescription(e Execer, repoAt, newDescription string) error { _, err := e.Exec( `update repos set description = ? where at_uri = ?`, newDescription, repoAt) + return err +} + +func UpdateSpindle(e Execer, repoAt, spindle string) error { + _, err := e.Exec( + `update repos set spindle = ? where at_uri = ?`, spindle, repoAt) return err } diff --git a/appview/middleware/middleware.go b/appview/middleware/middleware.go --- a/appview/middleware/middleware.go +++ b/appview/middleware/middleware.go @@ -225,6 +225,7 @@ ctx := context.WithValue(req.Context(), "knot", repo.Knot) ctx = context.WithValue(ctx, "repoAt", repo.AtUri) ctx = context.WithValue(ctx, "repoDescription", repo.Description) + ctx = context.WithValue(ctx, "repoSpindle", repo.Spindle) ctx = context.WithValue(ctx, "repoAddedAt", repo.Created.Format(time.RFC3339)) next.ServeHTTP(w, req.WithContext(ctx)) }) diff --git a/appview/pages/pages.go b/appview/pages/pages.go --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -616,11 +616,13 @@ } type RepoSettingsParams struct { - LoggedInUser *oauth.User - RepoInfo repoinfo.RepoInfo - Collaborators []Collaborator - Active string - Branches []types.Branch + LoggedInUser *oauth.User + RepoInfo repoinfo.RepoInfo + Collaborators []Collaborator + Active string + Branches []types.Branch + Spindles []string + CurrentSpindle string // TODO: use repoinfo.roles IsCollaboratorInviteAllowed bool } diff --git a/appview/repo/repo.go b/appview/repo/repo.go --- a/appview/repo/repo.go +++ b/appview/repo/repo.go @@ -367,7 +367,6 @@ }) return case http.MethodPut: - user := rp.oauth.GetUser(r) newDescription := r.FormValue("description") client, err := rp.oauth.AuthorizedClient(r) if err != nil { @@ -753,6 +752,88 @@ return } +// modify the spindle configured for this repo +func (rp *Repo) EditSpindle(w http.ResponseWriter, r *http.Request) { + f, err := rp.repoResolver.Resolve(r) + if err != nil { + log.Println("failed to get repo and knot", err) + w.WriteHeader(http.StatusBadRequest) + return + } + + repoAt := f.RepoAt + rkey := repoAt.RecordKey().String() + if rkey == "" { + log.Println("invalid aturi for repo", err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + user := rp.oauth.GetUser(r) + + newSpindle := r.FormValue("spindle") + client, err := rp.oauth.AuthorizedClient(r) + if err != nil { + log.Println("failed to get client") + rp.pages.Notice(w, "repo-notice", "Failed to configure spindle, try again later.") + return + } + + // ensure that this is a valid spindle for this user + validSpindles, err := rp.enforcer.GetSpindlesForUser(user.Did) + if err != nil { + log.Println("failed to get valid spindles") + rp.pages.Notice(w, "repo-notice", "Failed to configure spindle, try again later.") + return + } + + if !slices.Contains(validSpindles, newSpindle) { + log.Println("newSpindle not present in validSpindles", "newSpindle", newSpindle, "validSpindles", validSpindles) + rp.pages.Notice(w, "repo-notice", "Failed to configure spindle, try again later.") + return + } + + // optimistic update + err = db.UpdateSpindle(rp.db, string(repoAt), newSpindle) + if err != nil { + log.Println("failed to perform update-spindle query", err) + rp.pages.Notice(w, "repo-notice", "Failed to configure spindle, try again later.") + return + } + + ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoNSID, user.Did, rkey) + if err != nil { + // failed to get record + rp.pages.Notice(w, "repo-notice", "Failed to configure spindle, no record found on PDS.") + return + } + _, err = client.RepoPutRecord(r.Context(), &comatproto.RepoPutRecord_Input{ + Collection: tangled.RepoNSID, + Repo: user.Did, + Rkey: rkey, + SwapRecord: ex.Cid, + Record: &lexutil.LexiconTypeDecoder{ + Val: &tangled.Repo{ + Knot: f.Knot, + Name: f.RepoName, + Owner: user.Did, + CreatedAt: f.CreatedAt, + Description: &f.Description, + Spindle: &newSpindle, + }, + }, + }) + + if err != nil { + log.Println("failed to perform update-spindle query", err) + // failed to get record + rp.pages.Notice(w, "repo-notice", "Failed to configure spindle, unable to save to PDS.") + return + } + + w.Write(fmt.Append(nil, "spindle set to: ", newSpindle)) +} + func (rp *Repo) AddCollaborator(w http.ResponseWriter, r *http.Request) { f, err := rp.repoResolver.Resolve(r) if err != nil { @@ -794,14 +875,14 @@ } if ksResp.StatusCode != http.StatusNoContent { - w.Write([]byte(fmt.Sprint("knotserver failed to add collaborator: ", err))) + w.Write(fmt.Append(nil, "knotserver failed to add collaborator: ", err)) return } tx, err := rp.db.BeginTx(r.Context(), nil) if err != nil { log.Println("failed to start tx") - w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err))) + w.Write(fmt.Append(nil, "failed to add collaborator: ", err)) return } defer func() { @@ -814,13 +895,13 @@ err = rp.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.DidSlashRepo()) if err != nil { - w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err))) + w.Write(fmt.Append(nil, "failed to add collaborator: ", err)) return } err = db.AddCollaborator(rp.db, collaboratorIdent.DID.String(), f.OwnerDid(), f.RepoName, f.Knot) if err != nil { - w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err))) + w.Write(fmt.Append(nil, "failed to add collaborator: ", err)) return } @@ -838,7 +919,7 @@ return } - w.Write([]byte(fmt.Sprint("added collaborator: ", collaboratorIdent.Handle.String()))) + w.Write(fmt.Append(nil, "added collaborator: ", collaboratorIdent.Handle.String())) } @@ -897,7 +978,7 @@ tx, err := rp.db.BeginTx(r.Context(), nil) if err != nil { log.Println("failed to start tx") - w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err))) + w.Write(fmt.Append(nil, "failed to add collaborator: ", err)) return } defer func() { @@ -988,7 +1069,7 @@ return } - w.Write([]byte(fmt.Sprint("default branch set to: ", branch))) + w.Write(fmt.Append(nil, "default branch set to: ", branch)) } func (rp *Repo) RepoSettings(w http.ResponseWriter, r *http.Request) { @@ -1027,12 +1108,20 @@ return } + spindles, err := rp.enforcer.GetSpindlesForUser(user.Did) + if err != nil { + log.Println("failed to fetch spindles", err) + return + } + rp.pages.RepoSettings(w, pages.RepoSettingsParams{ LoggedInUser: user, RepoInfo: f.RepoInfo(user), Collaborators: repoCollaborators, IsCollaboratorInviteAllowed: isCollaboratorInviteAllowed, Branches: result.Branches, + Spindles: spindles, + CurrentSpindle: f.Spindle, }) } } @@ -1049,7 +1138,7 @@ case http.MethodPost: secret, err := db.GetRegistrationKey(rp.db, f.Knot) if err != nil { - rp.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %rp.", f.Knot)) + rp.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", f.Knot)) return } @@ -1135,7 +1224,7 @@ } secret, err := db.GetRegistrationKey(rp.db, knot) if err != nil { - rp.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %rp.", knot)) + rp.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", knot)) return } diff --git a/appview/repo/router.go b/appview/repo/router.go --- a/appview/repo/router.go +++ b/appview/repo/router.go @@ -70,6 +70,7 @@ }) r.With(mw.RepoPermissionMiddleware("repo:settings")).Route("/settings", func(r chi.Router) { r.Get("/", rp.RepoSettings) + r.With(mw.RepoPermissionMiddleware("repo:owner")).Post("/spindle", rp.EditSpindle) r.With(mw.RepoPermissionMiddleware("repo:invite")).Put("/collaborator", rp.AddCollaborator) r.With(mw.RepoPermissionMiddleware("repo:delete")).Delete("/delete", rp.DeleteRepo) r.Put("/branches/default", rp.SetDefaultBranch) diff --git a/appview/reporesolver/resolver.go b/appview/reporesolver/resolver.go --- a/appview/reporesolver/resolver.go +++ b/appview/reporesolver/resolver.go @@ -31,6 +31,7 @@ RepoName string RepoAt syntax.ATURI Description string + Spindle string CreatedAt string Ref string CurrentDir string @@ -95,6 +96,7 @@ // pass through values from the middleware description, ok := r.Context().Value("repoDescription").(string) addedAt, ok := r.Context().Value("repoAddedAt").(string) + spindle, ok := r.Context().Value("repoSpindle").(string) return &ResolvedRepo{ Knot: knot, @@ -105,6 +107,7 @@ CreatedAt: addedAt, Ref: ref, CurrentDir: currentDir, + Spindle: spindle, rr: rr, }, nil diff --git a/appview/pages/templates/repo/settings.html b/appview/pages/templates/repo/settings.html --- a/appview/pages/templates/repo/settings.html +++ b/appview/pages/templates/repo/settings.html @@ -81,6 +81,41 @@ + {{ if .RepoInfo.Roles.IsOwner }} +
+ +
+ + +
+
+ {{ end }} + {{ if .RepoInfo.Roles.RepoDeleteAllowed }}
- -- tangled.sh