diff --git a/bookmarkActionHandlers.go b/bookmarkActionHandlers.go index 6610e8a..3938df2 100644 --- a/bookmarkActionHandlers.go +++ b/bookmarkActionHandlers.go @@ -1,6 +1,7 @@ package gobookmarks import ( + "errors" "fmt" "github.com/gorilla/sessions" "golang.org/x/oauth2" @@ -30,7 +31,25 @@ func BookmarksEditSaveAction(w http.ResponseWriter, r *http.Request) error { return fmt.Errorf("bookmark modified concurrently") } + repoName := RepoName + if r.PostFormValue("repoName") != "" { + repoName = r.PostFormValue("repoName") + } + if r.PostFormValue("createRepo") == "1" { + if err := ActiveProvider.CreateRepo(r.Context(), login, token, repoName); err != nil { + return renderCreateRepoPrompt(w, r, repoName, text, branch, ref, sha, err) + } + RepoName = repoName + if err := CreateBookmarks(r.Context(), login, token, branch, text); err != nil { + return fmt.Errorf("createBookmark error: %w", err) + } + return nil + } + if err := UpdateBookmarks(r.Context(), login, token, ref, branch, text, curSha); err != nil { + if errors.Is(err, ErrRepoNotFound) { + return renderCreateRepoPrompt(w, r, repoName, text, branch, ref, sha, nil) + } return fmt.Errorf("updateBookmark error: %w", err) } return nil @@ -90,3 +109,29 @@ func CategoryEditSaveAction(w http.ResponseWriter, r *http.Request) error { } return nil } + +func renderCreateRepoPrompt(w http.ResponseWriter, r *http.Request, repoName, text, branch, ref, sha string, err error) error { + data := struct { + *CoreData + RepoName string + Text string + Branch string + Ref string + Sha string + Error string + }{ + CoreData: r.Context().Value(ContextValues("coreData")).(*CoreData), + RepoName: repoName, + Text: text, + Branch: branch, + Ref: ref, + Sha: sha, + } + if err != nil { + data.Error = err.Error() + } + if tplErr := GetCompiledTemplates(NewFuncs(r)).ExecuteTemplate(w, "createRepo.gohtml", data); tplErr != nil { + return fmt.Errorf("template: %w", tplErr) + } + return ErrHandled +} diff --git a/cmd/gobookmarks/main.go b/cmd/gobookmarks/main.go index 1fb6859..c50e7df 100644 --- a/cmd/gobookmarks/main.go +++ b/cmd/gobookmarks/main.go @@ -9,6 +9,7 @@ import ( "crypto/x509/pkix" "encoding/json" "encoding/pem" + "errors" "flag" "fmt" . "github.com/arran4/gobookmarks" @@ -136,6 +137,7 @@ func main() { UseCssColumns = cfg.CssColumns Namespace = cfg.Namespace + RepoName = GetBookmarksRepoName() SiteTitle = cfg.Title if cfg.GitServer != "" { GitServer = cfg.GitServer @@ -206,6 +208,7 @@ func main() { log.Printf("gobookmarks: %s, commit %s, built at %s", version, commit, date) SetVersion(version, commit, date) + RepoName = GetBookmarksRepoName() log.Printf("Redirect URL configured to: %s", redirectUrl) log.Println("Server started on http://localhost:8080") log.Println("Server started on https://localhost:8443") @@ -342,6 +345,9 @@ func runHandlerChain(chain ...any) func(http.ResponseWriter, *http.Request) { each(w, r) case func(http.ResponseWriter, *http.Request) error: if err := each(w, r); err != nil { + if errors.Is(err, ErrHandled) { + return + } type ErrorData struct { *CoreData Error string diff --git a/data_test.go b/data_test.go index 89afba0..1eeeb0c 100644 --- a/data_test.go +++ b/data_test.go @@ -140,6 +140,15 @@ func TestExecuteTemplates(t *testing.T) { *CoreData Error string }{baseData.CoreData, "boom"}}, + {"createRepo", "createRepo.gohtml", struct { + *CoreData + RepoName string + Text string + Branch string + Ref string + Sha string + Error string + }{baseData.CoreData, "MyBookmarks", "text", "main", "ref", "sha", ""}}, } for _, tt := range pages { diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..5708e79 --- /dev/null +++ b/errors.go @@ -0,0 +1,10 @@ +package gobookmarks + +import "errors" + +// ErrRepoNotFound indicates that the bookmarks repository does not exist. +var ErrRepoNotFound = errors.New("repository not found") + +// ErrHandled is returned by handlers when they have already written +// a response and no further handlers should run. +var ErrHandled = errors.New("handled") diff --git a/provider.go b/provider.go index 3d0058b..7a60fb6 100644 --- a/provider.go +++ b/provider.go @@ -35,8 +35,9 @@ type Provider interface { GetCommits(ctx context.Context, user string, token *oauth2.Token) ([]*Commit, error) GetBookmarks(ctx context.Context, user, ref string, token *oauth2.Token) (string, string, error) UpdateBookmarks(ctx context.Context, user string, token *oauth2.Token, sourceRef, branch, text, expectSHA string) error - CreateBookmarks(ctx context.Context, user string, token *oauth2.Token, branch, text string) error - DefaultServer() string + CreateBookmarks(ctx context.Context, user string, token *oauth2.Token, branch, text string) error + CreateRepo(ctx context.Context, user string, token *oauth2.Token, name string) error + DefaultServer() string } var providers = map[string]Provider{} diff --git a/provider_github.go b/provider_github.go index 0240d1f..9b9483b 100644 --- a/provider_github.go +++ b/provider_github.go @@ -141,35 +141,31 @@ func (p GitHubProvider) GetBookmarks(ctx context.Context, user, ref string, toke var commitAuthor = &github.CommitAuthor{Name: SP("Gobookmarks"), Email: SP("Gobookmarks@arran.net.au")} -func (p GitHubProvider) getDefaultBranch(ctx context.Context, user string, client *github.Client, branch string) (string, bool, error) { +func (p GitHubProvider) getDefaultBranch(ctx context.Context, user string, client *github.Client, branch string) (string, error) { rep, resp, err := client.Repositories.Get(ctx, user, RepoName) - created := false if resp != nil && resp.StatusCode == 404 { - rep, err = p.createRepo(ctx, user, client) - if err != nil { - log.Printf("github createRepo: %v", err) - return "", created, err - } - created = true + return "", ErrRepoNotFound } if err != nil { log.Printf("github getDefaultBranch: %v", err) - return "", created, fmt.Errorf("Repositories.Get: %w", err) + return "", fmt.Errorf("Repositories.Get: %w", err) } if rep.DefaultBranch != nil { branch = *rep.DefaultBranch } else { branch = "main" } - return branch, created, nil + return branch, nil } -func (p GitHubProvider) createRepo(ctx context.Context, user string, client *github.Client) (*github.Repository, error) { +func (p GitHubProvider) CreateRepo(ctx context.Context, user string, token *oauth2.Token, name string) error { + client := p.client(ctx, token) + RepoName = name rep := &github.Repository{Name: &RepoName, Description: SP("Personal bookmarks"), Private: BP(true)} rep, _, err := client.Repositories.Create(ctx, "", rep) if err != nil { log.Printf("github createRepo: %v", err) - return nil, fmt.Errorf("Repositories.Create: %w", err) + return fmt.Errorf("Repositories.Create: %w", err) } _, _, err = client.Repositories.CreateFile(ctx, user, RepoName, "readme.md", &github.RepositoryContentFileOptions{ Message: SP("Auto create from web"), @@ -180,9 +176,10 @@ See . https://github.com/arran4/gobookmarks `), }) if err != nil { log.Printf("github createRepo readme: %v", err) - return nil, fmt.Errorf("CreateReadme: %w", err) + return fmt.Errorf("CreateReadme: %w", err) } - return rep, nil + _ = rep + return nil } func (p GitHubProvider) createRef(ctx context.Context, user string, client *github.Client, sourceRef, branchRef string) error { @@ -204,7 +201,7 @@ func (p GitHubProvider) createRef(ctx context.Context, user string, client *gith func (p GitHubProvider) UpdateBookmarks(ctx context.Context, user string, token *oauth2.Token, sourceRef, branch, text, expectSHA string) error { client := p.client(ctx, token) - defaultBranch, created, err := p.getDefaultBranch(ctx, user, client, branch) + defaultBranch, err := p.getDefaultBranch(ctx, user, client, branch) if err != nil { return err } @@ -215,9 +212,6 @@ func (p GitHubProvider) UpdateBookmarks(ctx context.Context, user string, token if sourceRef == "" { sourceRef = branchRef } - if created { - return p.CreateBookmarks(ctx, user, token, branch, text) - } _, grefResp, err := client.Git.GetRef(ctx, user, RepoName, branchRef) if err != nil && grefResp.StatusCode != 404 { log.Printf("github UpdateBookmarks getRef: %v", err) @@ -231,11 +225,7 @@ func (p GitHubProvider) UpdateBookmarks(ctx context.Context, user string, token } contents, _, resp, err := client.Repositories.GetContents(ctx, user, RepoName, "bookmarks.txt", &github.RepositoryContentGetOptions{Ref: branchRef}) if resp != nil && resp.StatusCode == 404 { - if _, err := p.createRepo(ctx, user, client); err != nil { - log.Printf("github UpdateBookmarks create repo: %v", err) - return fmt.Errorf("CreateRepo: %w", err) - } - return p.CreateBookmarks(ctx, user, token, branch, text) + return ErrRepoNotFound } if err != nil { log.Printf("github UpdateBookmarks get contents: %v", err) @@ -266,7 +256,7 @@ func (p GitHubProvider) CreateBookmarks(ctx context.Context, user string, token client := p.client(ctx, token) if branch == "" { var err error - branch, _, err = p.getDefaultBranch(ctx, user, client, branch) + branch, err = p.getDefaultBranch(ctx, user, client, branch) if err != nil { log.Printf("github CreateBookmarks default branch: %v", err) return err diff --git a/provider_gitlab.go b/provider_gitlab.go index 91f32bf..6afc653 100644 --- a/provider_gitlab.go +++ b/provider_gitlab.go @@ -167,6 +167,9 @@ func (GitLabProvider) UpdateBookmarks(ctx context.Context, user string, token *o } _, _, err = c.RepositoryFiles.UpdateFile(user+"/"+RepoName, "bookmarks.txt", opt) if err != nil { + if respErr, ok := err.(*gitlab.ErrorResponse); ok && respErr.Response != nil && respErr.Response.StatusCode == http.StatusNotFound { + return ErrRepoNotFound + } log.Printf("gitlab UpdateBookmarks: %v", err) return err } @@ -188,7 +191,27 @@ func (GitLabProvider) CreateBookmarks(ctx context.Context, user string, token *o } _, _, err = c.RepositoryFiles.CreateFile(user+"/"+RepoName, "bookmarks.txt", opt) if err != nil { - log.Printf("gitlab CreateBookmarks: %v", err) + if respErr, ok := err.(*gitlab.ErrorResponse); ok && respErr.Response != nil && respErr.Response.StatusCode == http.StatusNotFound { + return ErrRepoNotFound + } + if err != nil { + log.Printf("gitlab CreateBookmarks: %v", err) + } + } + return err +} + +func (p GitLabProvider) CreateRepo(ctx context.Context, user string, token *oauth2.Token, name string) error { + c, err := GitLabProvider{}.client(token) + if err != nil { + return err } + RepoName = name + _, _, err = c.Projects.CreateProject(&gitlab.CreateProjectOptions{ + Name: gitlab.String(RepoName), + Description: gitlab.String("Personal bookmarks"), + Visibility: gitlab.Visibility(gitlab.PrivateVisibility), + InitializeWithReadme: gitlab.Ptr(true), + }) return err } diff --git a/repo.go b/repo.go index d72a87f..7bff814 100644 --- a/repo.go +++ b/repo.go @@ -2,9 +2,16 @@ package gobookmarks var RepoName = GetBookmarksRepoName() +// GetBookmarksRepoName returns the repository name based on the current +// configuration and build mode. When running a development build the name is +// suffixed with "-dev". The Namespace value is appended if supplied. func GetBookmarksRepoName() string { + name := "MyBookmarks" + if version == "dev" { + name += "-dev" + } if Namespace != "" { - return "MyBookmarks-" + Namespace + name += "-" + Namespace } - return "MyBookmarks" + return name } diff --git a/templates/createRepo.gohtml b/templates/createRepo.gohtml new file mode 100644 index 0000000..3bb2b33 --- /dev/null +++ b/templates/createRepo.gohtml @@ -0,0 +1,24 @@ +{{ template "head" $ }} + {{ if $.Error }} +
Error: {{ $.Error }}
+ {{ end }} + + +{{ template "tail" $ }} +