Goit

Simple and lightweight Git web server
git clone http://git.omkov.net/Goit
Log | Tree | Refs | README | Download

Goit/src/admin/repos.go (239 lines, 7.0 KiB) -rw-r--r-- blame download

0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
// Copyright (C) 2023, Jakob Wakeling
// All rights reserved.

package admin

import (
	"fmt"
	"html/template"
	"log"
	"net/http"
	"slices"
	"strconv"
	"strings"

	"github.com/Jamozed/Goit/src/cron"
	"github.com/Jamozed/Goit/src/goit"
	"github.com/Jamozed/Goit/src/util"
	"github.com/dustin/go-humanize"
	"github.com/gorilla/csrf"
)

func HandleRepos(w http.ResponseWriter, r *http.Request) {
	auth, user, err := goit.Auth(w, r, true)
	if err != nil {
		log.Println("[admin/repos]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	}

	if !auth || !user.IsAdmin {
		goit.HttpError(w, http.StatusNotFound)
		return
	}

	type row struct{ Id, Owner, Name, Visibility, Size string }
	data := struct {
		Title string
		Repos []row
	}{Title: "Admin - Repositories"}

	repos, err := goit.GetRepos()
	if err != nil {
		log.Println("[/admin/repos]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	}

	for _, r := range repos {
		u, err := goit.GetUser(r.OwnerId)
		if err != nil {
			log.Println("[/admin/repos]", err.Error())
			u = &goit.User{}
		}

		size, err := util.DirSize(goit.RepoPath(r.Name, true))
		if err != nil {
			log.Println("[/admin/repos]", err.Error())
		}

		data.Repos = append(data.Repos, row{
			fmt.Sprint(r.Id), u.Name, r.Name, r.Visibility.String(), humanize.IBytes(size),
		})
	}

	if err := goit.Tmpl.ExecuteTemplate(w, "admin/repos", data); err != nil {
		log.Println("[/admin/repos]", err.Error())
	}
}

func HandleRepoEdit(w http.ResponseWriter, r *http.Request) {
	auth, user, err := goit.Auth(w, r, true)
	if err != nil {
		log.Println("[/admin/repo/edit]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	}

	if !auth || !user.IsAdmin {
		goit.HttpError(w, http.StatusNotFound)
		return
	}

	id, err := strconv.ParseInt(r.FormValue("repo"), 10, 64)
	if err != nil {
		goit.HttpError(w, http.StatusNotFound)
		return
	}

	repo, err := goit.GetRepo(id)
	if err != nil {
		log.Println("[/admin/repo/edit]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	} else if repo == nil {
		goit.HttpError(w, http.StatusNotFound)
		return
	}

	owner, err := goit.GetUser(repo.OwnerId)
	if err != nil {
		log.Println("[/admin/repo/edit]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	} else if owner == nil {
		log.Println("[/admin/repo/edit]", repo.Id, "is owned by a nonexistent user")
		owner = &goit.User{}
	}

	data := struct {
		Title, Name string

		Edit struct {
			Id, Owner, Name, Description        string
			DefaultBranch, Upstream, Visibility string
			IsMirror                            bool
			Message                             string
		}

		Transfer struct{ Owner, Message string }
		Delete   struct{ Message string }

		CsrfField template.HTML
	}{
		Title: "Admin - Edit Repository",
		Name:  repo.Name,

		CsrfField: csrf.TemplateField(r),
	}

	data.Edit.Id = fmt.Sprint(repo.Id)
	data.Edit.Owner = owner.FullName + " (" + owner.Name + ")[" + fmt.Sprint(owner.Id) + "]"
	data.Edit.Name = repo.Name
	data.Edit.Description = repo.Description
	data.Edit.DefaultBranch = repo.DefaultBranch
	data.Edit.Upstream = repo.Upstream
	data.Edit.Visibility = repo.Visibility.String()
	data.Edit.IsMirror = repo.IsMirror

	if r.Method == http.MethodPost {
		switch r.FormValue("action") {
		case "edit":
			data.Edit.Name = r.FormValue("reponame")
			data.Edit.Description = r.FormValue("description")
			data.Edit.DefaultBranch = util.If(r.FormValue("branch") == "", "master", r.FormValue("branch"))
			data.Edit.Upstream = r.FormValue("upstream")
			data.Edit.Visibility = r.FormValue("visibility")
			data.Edit.IsMirror = r.FormValue("mirror") == "mirror"

			if data.Edit.Name == "" {
				data.Edit.Message = "Name cannot be empty"
			} else if slices.Contains(goit.Reserved, data.Edit.Name) || !goit.IsLegal(data.Name) {
				data.Edit.Message = "Name \"" + data.Edit.Name + "\" is illegal"
			} else if exists, err := goit.RepoExists(data.Edit.Name); err != nil {
				log.Println("[/admin/repo/edit]", err.Error())
				goit.HttpError(w, http.StatusInternalServerError)
				return
			} else if exists && !strings.EqualFold(data.Edit.Name, repo.Name) {
				data.Edit.Message = "Name \"" + data.Edit.Name + "\" is taken"
			} else if len(data.Edit.Description) > 256 {
				data.Edit.Message = "Description cannot exceed 256 characters"
			} else if visibility := goit.VisibilityFromString(data.Edit.Visibility); visibility == -1 {
				data.Edit.Message = "Visibility \"" + data.Edit.Visibility + "\" is invalid"
			} else if err := goit.UpdateRepo(repo.Id, goit.Repo{
				Name: data.Edit.Name, Description: data.Edit.Description, DefaultBranch: data.Edit.DefaultBranch,
				Upstream: data.Edit.Upstream, Visibility: visibility, IsMirror: data.Edit.IsMirror,
			}); err != nil {
				log.Println("[/admin/repo/edit]", err.Error())
				goit.HttpError(w, http.StatusInternalServerError)
				return
			} else {
				if (data.Edit.Upstream == "" && repo.Upstream != "") || !data.Edit.IsMirror {
					goit.Cron.RemoveFor(repo.Id)
					goit.Cron.Update()
				} else if data.Edit.Upstream != "" && data.Edit.IsMirror &&
					(data.Edit.Upstream != repo.Upstream || !repo.IsMirror) {
					goit.Cron.RemoveFor(repo.Id)

					goit.Cron.Add(repo.Id, cron.Immediate, func() {
						if err := goit.Pull(repo.Id); err != nil {
							log.Println("[cron:import]", err.Error())
						}
						log.Println("[cron:import] imported", data.Name)
					})

					goit.Cron.Add(repo.Id, cron.Daily, func() {
						if err := goit.Pull(repo.Id); err != nil {
							log.Println("[cron:mirror]", err.Error())
						}
						log.Println("[cron:mirror] updated", data.Edit.Name)
					})

					goit.Cron.Update()
				}

				data.Edit.Message = "Repository \"" + repo.Name + "\" updated successfully"
			}

		case "transfer":
			data.Transfer.Owner = r.FormValue("owner")

			if data.Transfer.Owner == "" {
				data.Transfer.Message = "New owner cannot be empty"
			} else if u, err := goit.GetUserByName(data.Transfer.Owner); err != nil {
				log.Println("[/admin/repo/edit]", err.Error())
				goit.HttpError(w, http.StatusInternalServerError)
				return
			} else if u == nil {
				data.Transfer.Message = "User \"" + data.Transfer.Owner + "\" does not exist"
			} else if err := goit.ChownRepo(repo.Id, u.Id); err != nil {
				log.Println("[/admin/repo/edit]", err.Error())
				goit.HttpError(w, http.StatusInternalServerError)
				return
			} else {
				log.Println("User", user.Id, "transferred repo", repo.Id, "ownership to", u.Id)
				http.Redirect(w, r, "/admin/repo/edit?repo="+data.Edit.Id, http.StatusFound)
				return
			}

		case "delete":
			var reponame = r.FormValue("reponame")

			if reponame != repo.Name {
				data.Delete.Message = "Input does not match the repository name"
			} else if err := goit.DelRepo(repo.Id); err != nil {
				log.Println("[/admin/repo/edit]", err.Error())
				goit.HttpError(w, http.StatusInternalServerError)
				return
			} else {
				http.Redirect(w, r, "/admin/repos", http.StatusFound)
				return
			}
		}
	}

	if err := goit.Tmpl.ExecuteTemplate(w, "admin/repo/edit", data); err != nil {
		log.Println("[/admin/repo/edit]", err.Error())
	}
}