diff --git a/appview/pulls/pulls.go b/appview/pulls/pulls.go index 30ed20a7..a37d28e4 100644 --- a/appview/pulls/pulls.go +++ b/appview/pulls/pulls.go @@ -2,10 +2,8 @@ package pulls import ( "database/sql" - "encoding/json" "errors" "fmt" - "io" "log" "net/http" "sort" @@ -96,7 +94,7 @@ func (s *Pulls) PullActions(w http.ResponseWriter, r *http.Request) { return } - mergeCheckResponse := s.mergeCheck(f, pull, stack) + mergeCheckResponse := s.mergeCheck(r, f, pull, stack) resubmitResult := pages.Unknown if user.Did == pull.OwnerDid { resubmitResult = s.resubmitCheck(f, pull, stack) @@ -151,7 +149,7 @@ func (s *Pulls) RepoSinglePull(w http.ResponseWriter, r *http.Request) { } } - mergeCheckResponse := s.mergeCheck(f, pull, stack) + mergeCheckResponse := s.mergeCheck(r, f, pull, stack) resubmitResult := pages.Unknown if user != nil && user.Did == pull.OwnerDid { resubmitResult = s.resubmitCheck(f, pull, stack) @@ -215,24 +213,21 @@ func (s *Pulls) RepoSinglePull(w http.ResponseWriter, r *http.Request) { }) } -func (s *Pulls) mergeCheck(f *reporesolver.ResolvedRepo, pull *db.Pull, stack db.Stack) types.MergeCheckResponse { +func (s *Pulls) mergeCheck(r *http.Request, f *reporesolver.ResolvedRepo, pull *db.Pull, stack db.Stack) types.MergeCheckResponse { if pull.State == db.PullMerged { return types.MergeCheckResponse{} } - secret, err := db.GetRegistrationKey(s.db, f.Knot) - if err != nil { - log.Printf("failed to get registration key: %v", err) - return types.MergeCheckResponse{ - Error: "failed to check merge status: this knot is unregistered", - } - } - - ksClient, err := knotclient.NewSignedClient(f.Knot, secret, s.config.Core.Dev) + client, err := s.oauth.ServiceClient( + r, + oauth.WithService(f.Knot), + oauth.WithLxm(tangled.RepoMergeCheckNSID), + oauth.WithDev(s.config.Core.Dev), + ) if err != nil { - log.Printf("failed to setup signed client for %s; ignoring: %v", f.Knot, err) + log.Printf("failed to connect to knot server: %v", err) return types.MergeCheckResponse{ - Error: "failed to check merge status", + Error: "failed to check merge status: could not connect to knot server", } } @@ -246,43 +241,46 @@ func (s *Pulls) mergeCheck(f *reporesolver.ResolvedRepo, pull *db.Pull, stack db patch = mergeable.CombinedPatch() } - resp, err := ksClient.MergeCheck([]byte(patch), f.OwnerDid(), f.Name, pull.TargetBranch) - if err != nil { - log.Println("failed to check for mergeability:", err) + resp, xe := tangled.RepoMergeCheck( + r.Context(), + &xrpcc, + &tangled.RepoMergeCheck_Input{ + Did: f.OwnerDid(), + Name: f.Name, + Branch: pull.TargetBranch, + Patch: patch, + }, + ) + if err := xrpcclient.HandleXrpcErr(xe); err != nil { + log.Println("failed to check for mergeability", "err", err) return types.MergeCheckResponse{ - Error: "failed to check merge status", + Error: fmt.Sprintf("failed to check merge status: %s", err.Error()), } } - switch resp.StatusCode { - case 404: - return types.MergeCheckResponse{ - Error: "failed to check merge status: this knot does not support PRs", - } - case 400: - return types.MergeCheckResponse{ - Error: "failed to check merge status: does this knot support PRs?", + + // convert xrpc response to internal types + conflicts := make([]types.ConflictInfo, len(resp.Conflicts)) + for i, conflict := range resp.Conflicts { + conflicts[i] = types.ConflictInfo{ + Filename: conflict.Filename, + Reason: conflict.Reason, } } - respBody, err := io.ReadAll(resp.Body) - if err != nil { - log.Println("failed to read merge check response body") - return types.MergeCheckResponse{ - Error: "failed to check merge status: knot is not speaking the right language", - } + result := types.MergeCheckResponse{ + IsConflicted: resp.Is_conflicted, + Conflicts: conflicts, } - defer resp.Body.Close() - var mergeCheckResponse types.MergeCheckResponse - err = json.Unmarshal(respBody, &mergeCheckResponse) - if err != nil { - log.Println("failed to unmarshal merge check response", err) - return types.MergeCheckResponse{ - Error: "failed to check merge status: knot is not speaking the right language", - } + if resp.Message != nil { + result.Message = *resp.Message } - return mergeCheckResponse + if resp.Error != nil { + result.Error = *resp.Error + } + + return result } func (s *Pulls) resubmitCheck(f *reporesolver.ResolvedRepo, pull *db.Pull, stack db.Stack) pages.ResubmitResult { @@ -867,16 +865,14 @@ func (s *Pulls) handleForkBasedPull(w http.ResponseWriter, r *http.Request, f *r return } - secret, err := db.GetRegistrationKey(s.db, fork.Knot) - if err != nil { - log.Println("failed to fetch registration key:", err) - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") - return - } - - sc, err := knotclient.NewSignedClient(fork.Knot, secret, s.config.Core.Dev) + client, err := s.oauth.ServiceClient( + r, + oauth.WithService(fork.Knot), + oauth.WithLxm(tangled.RepoHiddenRefNSID), + oauth.WithDev(s.config.Core.Dev), + ) if err != nil { - log.Println("failed to create signed client:", err) + log.Printf("failed to connect to knot server: %v", err) s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") return } @@ -888,17 +884,37 @@ func (s *Pulls) handleForkBasedPull(w http.ResponseWriter, r *http.Request, f *r return } - resp, err := sc.NewHiddenRef(user.Did, fork.Name, sourceBranch, targetBranch) + resp, err := tangled.RepoHiddenRef( + r.Context(), + client, + &tangled.RepoHiddenRef_Input{ + ForkRef: sourceBranch, + RemoteRef: targetBranch, + Repo: fork.RepoAt().String(), + }, + ) if err != nil { - log.Println("failed to create hidden ref:", err, resp.StatusCode) - s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") + xe, parseErr := xrpcerr.Unmarshal(err.Error()) + if parseErr != nil { + log.Printf("failed to create hidden ref: %v", err) + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") + } else { + log.Printf("failed to create hidden ref: %s", xe.Error()) + if xe.Tag == "AccessControl" { + s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.") + } else { + s.pages.Notice(w, "pull", fmt.Sprintf("Failed to create pull request: %s", xe.Message)) + } + } return } - switch resp.StatusCode { - case 404: - case 400: - s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.") + if !resp.Success { + errorMsg := "Failed to create pull request" + if resp.Error != nil { + errorMsg = fmt.Sprintf("Failed to create pull request: %s", *resp.Error) + } + s.pages.Notice(w, "pull", errorMsg) return } @@ -1464,24 +1480,33 @@ func (s *Pulls) resubmitFork(w http.ResponseWriter, r *http.Request) { return } - secret, err := db.GetRegistrationKey(s.db, forkRepo.Knot) - if err != nil { - log.Printf("failed to get registration key for %s: %s", forkRepo.Knot, err) - s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") - return - } - // update the hidden tracking branch to latest - signedClient, err := knotclient.NewSignedClient(forkRepo.Knot, secret, s.config.Core.Dev) + client, err := s.oauth.ServiceClient( + r, + oauth.WithService(forkRepo.Knot), + oauth.WithLxm(tangled.RepoHiddenRefNSID), + oauth.WithDev(s.config.Core.Dev), + ) if err != nil { - log.Printf("failed to create signed client for %s: %s", forkRepo.Knot, err) - s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") + log.Printf("failed to connect to knot server: %v", err) return } - resp, err := signedClient.NewHiddenRef(forkRepo.Did, forkRepo.Name, pull.PullSource.Branch, pull.TargetBranch) - if err != nil || resp.StatusCode != http.StatusNoContent { - log.Printf("failed to update tracking branch: %s", err) + resp, err := tangled.RepoHiddenRef( + r.Context(), + client, + &tangled.RepoHiddenRef_Input{ + ForkRef: pull.PullSource.Branch, + RemoteRef: pull.TargetBranch, + Repo: forkRepo.RepoAt().String(), + }, + ) + if err != nil || !resp.Success { + if err != nil { + log.Printf("failed to update tracking branch: %s", err) + } else { + log.Printf("failed to update tracking branch: success=false") + } s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") return } @@ -1908,9 +1933,14 @@ func (s *Pulls) MergePull(w http.ResponseWriter, r *http.Request) { patch := pullsToMerge.CombinedPatch() - secret, err := db.GetRegistrationKey(s.db, f.Knot) + client, err := s.oauth.ServiceClient( + r, + oauth.WithService(f.Knot), + oauth.WithLxm(tangled.RepoMergeNSID), + oauth.WithDev(s.config.Core.Dev), + ) if err != nil { - log.Printf("no registration key found for domain %s: %s\n", f.Knot, err) + log.Printf("failed to connect to knot server: %v", err) s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") return } @@ -1927,24 +1957,39 @@ func (s *Pulls) MergePull(w http.ResponseWriter, r *http.Request) { log.Printf("failed to get primary email: %s", err) } - ksClient, err := knotclient.NewSignedClient(f.Knot, secret, s.config.Core.Dev) - if err != nil { - log.Printf("failed to create signed client for %s: %s", f.Knot, err) - s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") - return + authorName := ident.Handle.String() + mergeInput := &tangled.RepoMerge_Input{ + Did: f.OwnerDid(), + Name: f.Name, + Branch: pull.TargetBranch, + Patch: patch, + CommitMessage: &pull.Title, + AuthorName: &authorName, + } + + if pull.Body != "" { + mergeInput.CommitBody = &pull.Body } - // Merge the pull request - resp, err := ksClient.Merge([]byte(patch), f.OwnerDid(), f.Name, pull.TargetBranch, pull.Title, pull.Body, ident.Handle.String(), email.Address) + if email.Address != "" { + mergeInput.AuthorEmail = &email.Address + } + + client, err := s.oauth.ServiceClient( + r, + oauth.WithService(f.Knot), + oauth.WithLxm(tangled.RepoMergeNSID), + oauth.WithDev(s.config.Core.Dev), + ) if err != nil { - log.Printf("failed to merge pull request: %s", err) + log.Printf("failed to connect to knot server: %v", err) s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") return } - if resp.StatusCode != http.StatusOK { - log.Printf("knotserver returned non-OK status code for merge: %d", resp.StatusCode) - s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") + err = tangled.RepoMerge(r.Context(), client, mergeInput) + if err := xrpcclient.HandleXrpcErr(err); err != nil { + s.pages.Notice(w, "pull-merge-error", err.Error()) return } diff --git a/appview/repo/index.go b/appview/repo/index.go index e6dc16b5..2de4f796 100644 --- a/appview/repo/index.go +++ b/appview/repo/index.go @@ -119,7 +119,7 @@ func (rp *Repo) RepoIndex(w http.ResponseWriter, r *http.Request) { var forkInfo *types.ForkInfo if user != nil && (repoInfo.Roles.IsOwner() || repoInfo.Roles.IsCollaborator()) { - forkInfo, err = getForkInfo(repoInfo, rp, f, result.Ref, user, signedClient) + forkInfo, err = getForkInfo(r, repoInfo, rp, f, result.Ref, user, signedClient) if err != nil { log.Printf("Failed to fetch fork information: %v", err) return @@ -233,6 +233,7 @@ func (rp *Repo) getLanguageInfo( } func getForkInfo( + r *http.Request, repoInfo repoinfo.RepoInfo, rp *Repo, f *reporesolver.ResolvedRepo, @@ -273,12 +274,35 @@ func getForkInfo( return &forkInfo, nil } - newHiddenRefResp, err := signedClient.NewHiddenRef(user.Did, repoInfo.Name, currentRef, currentRef) - if err != nil || newHiddenRefResp.StatusCode != http.StatusNoContent { - log.Printf("failed to update tracking branch: %s", err) + client, err := rp.oauth.ServiceClient( + r, + oauth.WithService(f.Knot), + oauth.WithLxm(tangled.RepoHiddenRefNSID), + oauth.WithDev(rp.config.Core.Dev), + ) + if err != nil { + log.Printf("failed to connect to knot server: %v", err) return nil, err } + resp, err := tangled.RepoHiddenRef( + r.Context(), + client, + &tangled.RepoHiddenRef_Input{ + ForkRef: currentRef, + RemoteRef: currentRef, + Repo: f.RepoAt().String(), + }, + ) + if err != nil || !resp.Success { + if err != nil { + log.Printf("failed to update tracking branch: %s", err) + } else { + log.Printf("failed to update tracking branch: success=false") + } + return nil, fmt.Errorf("failed to update tracking branch") + } + hiddenRef := fmt.Sprintf("hidden/%s/%s", currentRef, currentRef) var status types.AncestorCheckResponse diff --git a/appview/repo/repo.go b/appview/repo/repo.go index 48e33eb8..62e33a63 100644 --- a/appview/repo/repo.go +++ b/appview/repo/repo.go @@ -17,6 +17,8 @@ import ( "strings" "time" + comatproto "github.com/bluesky-social/indigo/api/atproto" + lexutil "github.com/bluesky-social/indigo/lex/util" "tangled.sh/tangled.sh/core/api/tangled" "tangled.sh/tangled.sh/core/appview/commitverify" "tangled.sh/tangled.sh/core/appview/config" @@ -33,14 +35,13 @@ import ( "tangled.sh/tangled.sh/core/rbac" "tangled.sh/tangled.sh/core/tid" "tangled.sh/tangled.sh/core/types" + "tangled.sh/tangled.sh/core/xrpc/serviceauth" securejoin "github.com/cyphar/filepath-securejoin" "github.com/go-chi/chi/v5" "github.com/go-git/go-git/v5/plumbing" - comatproto "github.com/bluesky-social/indigo/api/atproto" "github.com/bluesky-social/indigo/atproto/syntax" - lexutil "github.com/bluesky-social/indigo/lex/util" ) type Repo struct { @@ -54,6 +55,7 @@ type Repo struct { enforcer *rbac.Enforcer notifier notify.Notifier logger *slog.Logger + serviceAuth *serviceauth.ServiceAuth } func New( @@ -960,26 +962,33 @@ func (rp *Repo) DeleteRepo(w http.ResponseWriter, r *http.Request) { } log.Println("removed repo record ", f.RepoAt().String()) - secret, err := db.GetRegistrationKey(rp.db, f.Knot) - if err != nil { - log.Printf("no key found for domain %s: %s\n", f.Knot, err) - return - } - - ksClient, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev) + client, err := rp.oauth.ServiceClient( + r, + oauth.WithService(f.Knot), + oauth.WithLxm(tangled.RepoDeleteNSID), + oauth.WithDev(rp.config.Core.Dev), + ) if err != nil { - log.Println("failed to create client to ", f.Knot) + log.Println("failed to connect to knot server:", err) return } - ksResp, err := ksClient.RemoveRepo(f.OwnerDid(), f.Name) + err = tangled.RepoDelete( + r.Context(), + client, + &tangled.RepoDelete_Input{ + Did: f.OwnerDid(), + Name: f.Name, + }, + ) if err != nil { - log.Printf("failed to make request to %s: %s", f.Knot, err) - return - } - - if ksResp.StatusCode != http.StatusNoContent { - log.Println("failed to remove repo from knot, continuing anyway ", f.Knot) + xe, parseErr := xrpcerr.Unmarshal(err.Error()) + if parseErr != nil { + log.Printf("failed to delete repo from knot %s: %s", f.Knot, err) + } else { + log.Printf("failed to delete repo from knot %s: %s", f.Knot, xe.Error()) + } + // Continue anyway since we want to clean up local state } else { log.Println("removed repo from knot ", f.Knot) } @@ -1055,26 +1064,29 @@ func (rp *Repo) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { return } - secret, err := db.GetRegistrationKey(rp.db, f.Knot) - if err != nil { - log.Printf("no key found for domain %s: %s\n", f.Knot, err) - return - } - - ksClient, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev) - if err != nil { - log.Println("failed to create client to ", f.Knot) - return - } - - ksResp, err := ksClient.SetDefaultBranch(f.OwnerDid(), f.Name, branch) + client, err := rp.oauth.ServiceClient( + r, + oauth.WithService(f.Knot), + oauth.WithLxm(tangled.RepoSetDefaultBranchNSID), + oauth.WithDev(rp.config.Core.Dev), + ) if err != nil { - log.Printf("failed to make request to %s: %s", f.Knot, err) + log.Println("failed to connect to knot server:", err) + rp.pages.Notice(w, noticeId, "Failed to connect to knot server.") return } - if ksResp.StatusCode != http.StatusNoContent { - rp.pages.Notice(w, "repo-settings", "Failed to set default branch. Try again later.") + xe := tangled.RepoSetDefaultBranch( + r.Context(), + client, + &tangled.RepoSetDefaultBranch_Input{ + Repo: f.RepoAt().String(), + DefaultBranch: branch, + }, + ) + if err := xrpcclient.HandleXrpcErr(xe); err != nil { + log.Println("xrpc failed", "err", xe) + rp.pages.Notice(w, noticeId, err.Error()) return } @@ -1373,30 +1385,42 @@ func (rp *Repo) SyncRepoFork(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPost: - secret, err := db.GetRegistrationKey(rp.db, f.Knot) + client, err := rp.oauth.ServiceClient( + r, + oauth.WithService(f.Knot), + oauth.WithLxm(tangled.RepoForkSyncNSID), + oauth.WithDev(rp.config.Core.Dev), + ) if err != nil { - rp.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", f.Knot)) + rp.pages.Notice(w, "repo", "Failed to connect to knot server.") return } - client, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev) - if err != nil { - rp.pages.Notice(w, "repo", "Failed to reach knot server.") + repoInfo := f.RepoInfo(user) + if repoInfo.Source == nil { + rp.pages.Notice(w, "repo", "This repository is not a fork.") return } - var uri string - if rp.config.Core.Dev { - uri = "http" - } else { - uri = "https" - } - forkName := fmt.Sprintf("%s", f.Name) - forkSourceUrl := fmt.Sprintf("%s://%s/%s/%s", uri, f.Knot, f.OwnerDid(), f.Repo.Name) - - _, err = client.SyncRepoFork(user.Did, forkSourceUrl, forkName, ref) + err = tangled.RepoForkSync( + r.Context(), + client, + &tangled.RepoForkSync_Input{ + Did: user.Did, + Name: f.Name, + Source: repoInfo.Source.RepoAt().String(), + Branch: ref, + }, + ) if err != nil { - rp.pages.Notice(w, "repo", "Failed to sync repository fork.") + xe, parseErr := xrpcerr.Unmarshal(err.Error()) + if parseErr != nil { + log.Printf("failed to sync repository fork: %s", err) + rp.pages.Notice(w, "repo", "Failed to sync repository fork.") + } else { + log.Printf("failed to sync repository fork: %s", xe.Error()) + rp.pages.Notice(w, "repo", fmt.Sprintf("Failed to sync repository fork: %s", xe.Message)) + } return } @@ -1459,15 +1483,16 @@ func (rp *Repo) ForkRepo(w http.ResponseWriter, r *http.Request) { // repo with this name already exists, append random string forkName = fmt.Sprintf("%s-%s", forkName, randomString(3)) } - secret, err := db.GetRegistrationKey(rp.db, knot) - if err != nil { - rp.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", knot)) - return - } + client, err := rp.oauth.ServiceClient( + r, + oauth.WithService(knot), + oauth.WithLxm(tangled.RepoForkNSID), + oauth.WithDev(rp.config.Core.Dev), + ) - client, err := knotclient.NewSignedClient(knot, secret, rp.config.Core.Dev) if err != nil { - rp.pages.Notice(w, "repo", "Failed to reach knot server.") + log.Printf("error creating client for knot server: %v", err) + rp.pages.Notice(w, "repo", "Failed to connect to knot server.") return } @@ -1503,20 +1528,27 @@ func (rp *Repo) ForkRepo(w http.ResponseWriter, r *http.Request) { } }() - resp, err := client.ForkRepo(user.Did, forkSourceUrl, forkName) + err = tangled.RepoFork( + r.Context(), + client, + &tangled.RepoFork_Input{ + Did: user.Did, + Name: &forkName, + Source: forkSourceUrl, + }, + ) + if err != nil { - rp.pages.Notice(w, "repo", "Failed to create repository on knot server.") - return - } + xe, err := xrpcerr.Unmarshal(err.Error()) + if err != nil { + log.Println(err) + rp.pages.Notice(w, "repo", "Failed to create repository on knot server.") + return + } - switch resp.StatusCode { - case http.StatusConflict: - rp.pages.Notice(w, "repo", "A repository with that name already exists.") + log.Println(xe.Error()) + rp.pages.Notice(w, "repo", fmt.Sprintf("Failed to create repository on knot server: %s.", xe.Message)) return - case http.StatusInternalServerError: - rp.pages.Notice(w, "repo", "Failed to create repository on knot. Try again later.") - case http.StatusNoContent: - // continue } xrpcClient, err := rp.oauth.AuthorizedClient(r) diff --git a/appview/state/state.go b/appview/state/state.go index 96f4c537..3edc2c08 100644 --- a/appview/state/state.go +++ b/appview/state/state.go @@ -28,10 +28,10 @@ import ( "tangled.sh/tangled.sh/core/eventconsumer" "tangled.sh/tangled.sh/core/idresolver" "tangled.sh/tangled.sh/core/jetstream" - "tangled.sh/tangled.sh/core/knotclient" tlog "tangled.sh/tangled.sh/core/log" "tangled.sh/tangled.sh/core/rbac" "tangled.sh/tangled.sh/core/tid" + // xrpcerr "tangled.sh/tangled.sh/core/xrpc/errors" ) type State struct { @@ -327,17 +327,18 @@ func (s *State) NewRepo(w http.ResponseWriter, r *http.Request) { existingRepo, err := db.GetRepo(s.db, user.Did, repoName) if err == nil && existingRepo != nil { - s.pages.Notice(w, "repo", fmt.Sprintf("A repo by this name already exists on %s", existingRepo.Knot)) + l.Info("repo exists") + s.pages.Notice(w, "repo", fmt.Sprintf("You already have a repository by this name on %s", existingRepo.Knot)) return } - secret, err := db.GetRegistrationKey(s.db, domain) - if err != nil { - s.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", domain)) - return - } + client, err := s.oauth.ServiceClient( + r, + oauth.WithService(domain), + oauth.WithLxm(tangled.RepoCreateNSID), + oauth.WithDev(s.config.Core.Dev), + ) - client, err := knotclient.NewSignedClient(domain, secret, s.config.Core.Dev) if err != nil { s.pages.Notice(w, "repo", "Failed to connect to knot server.") return @@ -392,20 +393,24 @@ func (s *State) NewRepo(w http.ResponseWriter, r *http.Request) { } }() - resp, err := client.NewRepo(user.Did, repoName, defaultBranch) + xe := tangled.RepoCreate( + r.Context(), + client, + &tangled.RepoCreate_Input{ + Rkey: rkey, + }, + ) if err != nil { - s.pages.Notice(w, "repo", "Failed to create repository on knot server.") - return - } + xe, err := xrpcerr.Unmarshal(err.Error()) + if err != nil { + log.Println(err) + s.pages.Notice(w, "repo", "Failed to create repository on knot server.") + return + } - switch resp.StatusCode { - case http.StatusConflict: - s.pages.Notice(w, "repo", "A repository with that name already exists.") + log.Println(xe.Error()) + s.pages.Notice(w, "repo", fmt.Sprintf("Failed to create repository on knot server: %s.", xe.Message)) return - case http.StatusInternalServerError: - s.pages.Notice(w, "repo", "Failed to create repository on knot. Try again later.") - case http.StatusNoContent: - // continue } err = db.AddRepo(tx, repo)