···
···
comatproto "github.com/bluesky-social/indigo/api/atproto"
lexutil "github.com/bluesky-social/indigo/lex/util"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/go-chi/chi/v5"
···
"tangled.sh/tangled.sh/core/appview/pages"
posthogService "tangled.sh/tangled.sh/core/appview/posthog"
"tangled.sh/tangled.sh/core/appview/reporesolver"
"tangled.sh/tangled.sh/core/eventconsumer"
"tangled.sh/tangled.sh/core/idresolver"
"tangled.sh/tangled.sh/core/jetstream"
···
repoResolver *reporesolver.RepoResolver
knotstream *eventconsumer.Consumer
spindlestream *eventconsumer.Consumer
func Make(ctx context.Context, config *config.Config) (*State, error) {
···
···
user := s.oauth.GetUser(r)
domain := r.FormValue("domain")
s.pages.Notice(w, "repo", "Invalid form submission—missing knot domain.")
repoName := r.FormValue("name")
···
s.pages.Notice(w, "repo", err.Error())
repoName = stripGitExt(repoName)
defaultBranch := r.FormValue("branch")
description := r.FormValue("description")
ok, err := s.enforcer.E.Enforce(user.Did, domain, domain, "repo:create")
s.pages.Notice(w, "repo", "You do not have permission to create a repo in this knot.")
existingRepo, err := db.GetRepo(s.db, user.Did, repoName)
if err == nil && existingRepo != nil {
···
-
client, err := s.oauth.ServiceClient(
-
oauth.WithService(domain),
-
oauth.WithLxm(tangled.RepoCreateNSID),
-
oauth.WithDev(s.config.Core.Dev),
-
s.pages.Notice(w, "repo", "Failed to connect to knot server.")
···
xrpcClient, err := s.oauth.AuthorizedClient(r)
s.pages.Notice(w, "repo", "Failed to write record to PDS.")
···
-
log.Printf("failed to create record: %s", err)
s.pages.Notice(w, "repo", "Failed to announce repository creation.")
-
log.Println("created repo record: ", atresp.Uri)
tx, err := s.db.BeginTx(r.Context(), nil)
s.pages.Notice(w, "repo", "Failed to save repository information.")
-
err = s.enforcer.E.LoadPolicy()
-
log.Println("failed to rollback policies")
xe := tangled.RepoCreate(
···
-
xe, err := xrpcerr.Unmarshal(err.Error())
-
s.pages.Notice(w, "repo", "Failed to create repository on knot server.")
-
log.Println(xe.Error())
-
s.pages.Notice(w, "repo", fmt.Sprintf("Failed to create repository on knot server: %s.", xe.Message))
err = db.AddRepo(tx, repo)
s.pages.Notice(w, "repo", "Failed to save repository information.")
···
p, _ := securejoin.SecureJoin(user.Did, repoName)
err = s.enforcer.AddRepo(user.Did, domain, p)
s.pages.Notice(w, "repo", "Failed to set up repository permissions.")
-
log.Println("failed to commit changes", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
err = s.enforcer.E.SavePolicy()
-
log.Println("failed to update ACLs", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
s.notifier.NewRepo(r.Context(), repo)
-
s.pages.HxLocation(w, fmt.Sprintf("/@%s/%s", user.Handle, repoName))
···
···
comatproto "github.com/bluesky-social/indigo/api/atproto"
+
"github.com/bluesky-social/indigo/atproto/syntax"
lexutil "github.com/bluesky-social/indigo/lex/util"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/go-chi/chi/v5"
···
"tangled.sh/tangled.sh/core/appview/pages"
posthogService "tangled.sh/tangled.sh/core/appview/posthog"
"tangled.sh/tangled.sh/core/appview/reporesolver"
+
xrpcclient "tangled.sh/tangled.sh/core/appview/xrpcclient"
"tangled.sh/tangled.sh/core/eventconsumer"
"tangled.sh/tangled.sh/core/idresolver"
"tangled.sh/tangled.sh/core/jetstream"
···
repoResolver *reporesolver.RepoResolver
knotstream *eventconsumer.Consumer
spindlestream *eventconsumer.Consumer
func Make(ctx context.Context, config *config.Config) (*State, error) {
···
···
+
l := s.logger.With("handler", "NewRepo")
user := s.oauth.GetUser(r)
+
l = l.With("did", user.Did)
+
l = l.With("handle", user.Handle)
domain := r.FormValue("domain")
s.pages.Notice(w, "repo", "Invalid form submission—missing knot domain.")
+
l = l.With("knot", domain)
repoName := r.FormValue("name")
···
s.pages.Notice(w, "repo", err.Error())
repoName = stripGitExt(repoName)
+
l = l.With("repoName", repoName)
defaultBranch := r.FormValue("branch")
+
l = l.With("defaultBranch", defaultBranch)
description := r.FormValue("description")
ok, err := s.enforcer.E.Enforce(user.Did, domain, domain, "repo:create")
s.pages.Notice(w, "repo", "You do not have permission to create a repo in this knot.")
+
// Check for existing repos
existingRepo, err := db.GetRepo(s.db, user.Did, repoName)
if err == nil && existingRepo != nil {
···
+
// create atproto record for this repo
···
xrpcClient, err := s.oauth.AuthorizedClient(r)
+
l.Info("PDS write failed", "err", err)
s.pages.Notice(w, "repo", "Failed to write record to PDS.")
···
+
l.Info("PDS write failed", "err", err)
s.pages.Notice(w, "repo", "Failed to announce repository creation.")
+
l = l.With("aturi", aturi)
tx, err := s.db.BeginTx(r.Context(), nil)
+
l.Info("txn failed", "err", err)
s.pages.Notice(w, "repo", "Failed to save repository information.")
+
// The rollback function reverts a few things on failure:
+
// - the atproto record created
+
err2 := s.enforcer.E.LoadPolicy()
+
err3 := rollbackRecord(context.Background(), aturi, xrpcClient)
+
// ignore txn complete errors, this is okay
+
if errors.Is(err1, sql.ErrTxDone) {
+
if errs := errors.Join(err1, err2, err3); errs != nil {
+
l.Error("failed to rollback changes", "errs", errs)
+
client, err := s.oauth.ServiceClient(
+
oauth.WithService(domain),
+
oauth.WithLxm(tangled.RepoCreateNSID),
+
oauth.WithDev(s.config.Core.Dev),
+
l.Error("service auth failed", "err", err)
+
s.pages.Notice(w, "repo", "Failed to reach PDS.")
xe := tangled.RepoCreate(
···
+
l.Error("xrpc request failed", "err", err)
+
s.pages.Notice(w, "repo", fmt.Sprintf("Failed to create repository on knot server: %s.", err.Error()))
err = db.AddRepo(tx, repo)
+
l.Error("db write failed", "err", err)
s.pages.Notice(w, "repo", "Failed to save repository information.")
···
p, _ := securejoin.SecureJoin(user.Did, repoName)
err = s.enforcer.AddRepo(user.Did, domain, p)
+
l.Error("acl setup failed", "err", err)
s.pages.Notice(w, "repo", "Failed to set up repository permissions.")
+
l.Error("txn commit failed", "err", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
err = s.enforcer.E.SavePolicy()
+
l.Error("acl save failed", "err", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
+
// reset the ATURI because the transaction completed successfully
s.notifier.NewRepo(r.Context(), repo)
+
s.pages.HxLocation(w, fmt.Sprintf("/@%s/%s", user.Handle, repoName))
+
// this is used to rollback changes made to the PDS
+
// it is a no-op if the provided ATURI is empty
+
func rollbackRecord(ctx context.Context, aturi string, xrpcc *xrpcclient.Client) error {
+
parsed := syntax.ATURI(aturi)
+
collection := parsed.Collection().String()
+
repo := parsed.Authority().String()
+
rkey := parsed.RecordKey().String()
+
_, err := xrpcc.RepoDeleteRecord(ctx, &comatproto.RepoDeleteRecord_Input{
+
Collection: collection,