forked from tangled.org/core
this repo has no description

appview/{pulls,repo,state}: use xrpc + service auth for knot requests

This now covers: Fork, ForkSync, NewHiddenRef, Merge, MergeCheck,
and RepoCreate/Delete.

Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.sh>

Changed files
+281 -175
appview
pulls
repo
state
+130 -85
appview/pulls/pulls.go
···
import (
"database/sql"
-
"encoding/json"
"errors"
"fmt"
-
"io"
"log"
"net/http"
"sort"
···
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)
···
}
}
-
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)
···
})
}
-
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",
}
}
···
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,
+
}
+
+
if resp.Message != nil {
+
result.Message = *resp.Message
}
-
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.Error != nil {
+
result.Error = *resp.Error
}
-
return mergeCheckResponse
+
return result
}
func (s *Pulls) resubmitCheck(f *reporesolver.ResolvedRepo, pull *db.Pull, stack db.Stack) pages.ResubmitResult {
···
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
}
···
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
}
···
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
···
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
···
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,
-
// 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 pull.Body != "" {
+
mergeInput.CommitBody = &pull.Body
+
}
+
+
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
+28 -4
appview/repo/index.go
···
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
···
}
func getForkInfo(
+
r *http.Request,
repoInfo repoinfo.RepoInfo,
rp *Repo,
f *reporesolver.ResolvedRepo,
···
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)
+99 -67
appview/repo/repo.go
···
"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"
···
"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 {
···
enforcer *rbac.Enforcer
notifier notify.Notifier
logger *slog.Logger
+
serviceAuth *serviceauth.ServiceAuth
}
func New(
···
}
log.Println("removed repo record ", f.RepoAt().String())
-
secret, err := db.GetRegistrationKey(rp.db, f.Knot)
+
client, err := rp.oauth.ServiceClient(
+
r,
+
oauth.WithService(f.Knot),
+
oauth.WithLxm(tangled.RepoDeleteNSID),
+
oauth.WithDev(rp.config.Core.Dev),
+
)
if err != nil {
-
log.Printf("no key found for domain %s: %s\n", f.Knot, err)
+
log.Println("failed to connect to knot server:", err)
return
}
-
ksClient, err := knotclient.NewSignedClient(f.Knot, secret, rp.config.Core.Dev)
+
err = tangled.RepoDelete(
+
r.Context(),
+
client,
+
&tangled.RepoDelete_Input{
+
Did: f.OwnerDid(),
+
Name: f.Name,
+
},
+
)
if err != nil {
-
log.Println("failed to create client to ", f.Knot)
-
return
-
}
-
-
ksResp, err := ksClient.RemoveRepo(f.OwnerDid(), 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)
}
···
return
-
secret, err := db.GetRegistrationKey(rp.db, f.Knot)
+
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("no key found for domain %s: %s\n", f.Knot, err)
+
log.Println("failed to connect to knot server:", err)
+
rp.pages.Notice(w, noticeId, "Failed to connect to knot server.")
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)
-
if err != nil {
-
log.Printf("failed to make request to %s: %s", f.Knot, err)
-
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
···
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
···
// 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
···
}()
-
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)
+24 -19
appview/state/state.go
···
"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 {
···
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
···
}
}()
-
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)