574bc91c2e459aa8afd9f4c77698b3417ce47883 / internal/web/repo_settings.go · 4684 bytes · raw
package web
import (
"errors"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"git.josie-c.com/josie/simplegit/internal/db"
)
type repoSettingsData struct {
navData
CloneURL string
Error string
}
// ownerRepo resolves a repo page and requires the caller to be its owner,
// regardless of visibility. On failure it has written the response.
func (s *Server) ownerRepo(w http.ResponseWriter, r *http.Request) (db.Repo, string, *db.User, bool) {
repo, repoPath, user, ok := s.resolveRepo(w, r)
if !ok {
return db.Repo{}, "", nil, false
}
if !isOwner(user, repo) {
if user == nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
} else {
http.NotFound(w, r)
}
return db.Repo{}, "", nil, false
}
return repo, repoPath, user, true
}
func (s *Server) repoView(r *http.Request, repo db.Repo, user *db.User) repoSettingsData {
return repoSettingsData{
navData: nav(r, repo, user, "settings"),
CloneURL: cloneURL(s.cfg.BaseURL, r.PathValue("user"), repo.Name),
}
}
// handleRepoSettings renders the per-repo settings page.
func (s *Server) handleRepoSettings(w http.ResponseWriter, r *http.Request) {
repo, _, user, ok := s.ownerRepo(w, r)
if !ok {
return
}
s.render(w, "repo_settings.html", http.StatusOK, s.repoView(r, repo, user))
}
// handleRepoVisibility toggles a repo between public and private.
func (s *Server) handleRepoVisibility(w http.ResponseWriter, r *http.Request) {
repo, _, _, ok := s.ownerRepo(w, r)
if !ok {
return
}
r.Body = http.MaxBytesReader(w, r.Body, loginFormMax)
if err := r.ParseForm(); err != nil {
http.Error(w, "bad form", http.StatusBadRequest)
return
}
visibility := r.FormValue("visibility")
if visibility != visibilityPublic {
visibility = visibilityPrivate
}
if err := db.UpdateRepoVisibility(s.database, repo.ID, visibility); err != nil {
s.internalError(w, r, err)
return
}
http.Redirect(w, r, repoBasePath(r)+"/settings", http.StatusSeeOther)
}
// handleRepoRename moves the bare directory and updates the row.
func (s *Server) handleRepoRename(w http.ResponseWriter, r *http.Request) {
repo, repoPath, user, ok := s.ownerRepo(w, r)
if !ok {
return
}
r.Body = http.MaxBytesReader(w, r.Body, loginFormMax)
if err := r.ParseForm(); err != nil {
http.Error(w, "bad form", http.StatusBadRequest)
return
}
owner := r.PathValue("user")
name := formText(r, "name", maxNameLen+1)
fail := func(msg string) {
data := s.repoView(r, repo, user)
data.Error = msg
s.render(w, "repo_settings.html", http.StatusUnprocessableEntity, data)
}
if name == repo.Name {
http.Redirect(w, r, repoBasePath(r)+"/settings", http.StatusSeeOther)
return
}
if len(name) > maxNameLen || !repoNamePattern.MatchString(name) {
fail(fmt.Sprintf("invalid repository name %q", name))
return
}
if _, err := db.GetRepoByName(s.database, owner, name); err == nil {
fail("you already have a repository with that name")
return
} else if !errors.Is(err, db.ErrNotFound) {
s.internalError(w, r, err)
return
}
newPath := filepath.Join(s.cfg.DataDir, "repos", owner, name+".git")
if err := os.Rename(repoPath, newPath); err != nil {
log.Printf("web: rename repo %s: %v", repoPath, err)
fail("could not move the repository on disk")
return
}
if err := db.RenameRepo(s.database, repo.ID, name); err != nil {
log.Printf("web: rename repo row %d: %v", repo.ID, err)
if rollbackErr := os.Rename(newPath, repoPath); rollbackErr != nil {
log.Printf("web: rename rollback %s: %v", newPath, rollbackErr)
}
s.internalError(w, r, err)
return
}
http.Redirect(w, r, "/"+owner+"/"+name+"/settings", http.StatusSeeOther)
}
// handleRepoDelete removes the row, the bare directory, and any release
// asset uploads (the DB rows cascade, but files on disk do not).
func (s *Server) handleRepoDelete(w http.ResponseWriter, r *http.Request) {
repo, repoPath, _, ok := s.ownerRepo(w, r)
if !ok {
return
}
releases, err := db.ListReleases(s.database, repo.ID)
if err != nil {
log.Printf("web: list releases for delete %d: %v", repo.ID, err)
}
if err := db.DeleteRepo(s.database, repo.ID); err != nil {
s.internalError(w, r, err)
return
}
if err := os.RemoveAll(repoPath); err != nil {
log.Printf("web: remove repo dir %s: %v", repoPath, err)
}
for _, release := range releases {
if err := os.RemoveAll(s.releaseUploadDir(release.ID)); err != nil {
log.Printf("web: remove release uploads %d: %v", release.ID, err)
}
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// repoBasePath is the canonical /{owner}/{repo} for the request.
func repoBasePath(r *http.Request) string {
return "/" + r.PathValue("user") + "/" + r.PathValue("repo")
}