Goit

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

AuthorJakob Wakeling <[email protected]>
Date2023-10-23 10:05:22
Commit988a70896b863d3c930c5d6cd78b6f2df4c63d35
Parent5166d87cbc742c010bc82fbab661b3edad09c35b

Add transfer ownership and delete to repo edit

Diffstat

M main.go | 1 -
M res/repo/edit.html | 50 ++++++++++++++++++++++++++++++++++++++++++--------
M src/repo.go | 24 ++++++++++++++++++------
M src/repo/edit.go | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
M src/repo/repo.go | 34 ----------------------------------

5 files changed, 130 insertions, 78 deletions

diff --git a/main.go b/main.go
index 94a1342..61921ad 100644
--- a/main.go
+++ b/main.go
@@ -37,7 +37,6 @@ func main() {
 	h.Path("/user/sessions").Methods("GET", "POST").HandlerFunc(user.HandleSessions)
 	h.Path("/user/edit").Methods("GET", "POST").HandlerFunc(user.HandleEdit)
 	h.Path("/repo/create").Methods("GET", "POST").HandlerFunc(repo.HandleCreate)
-	h.Path("/repo/delete").Methods("DELETE").HandlerFunc(repo.HandleDelete)
 	h.Path("/admin").Methods("GET").HandlerFunc(goit.HandleAdminIndex)
 	h.Path("/admin/users").Methods("GET").HandlerFunc(goit.HandleAdminUsers)
 	h.Path("/admin/user/create").Methods("GET", "POST").HandlerFunc(goit.HandleAdminUserCreate)
diff --git a/res/repo/edit.html b/res/repo/edit.html
index f76b1d7..2cf5088 100644
--- a/res/repo/edit.html
+++ b/res/repo/edit.html
@@ -5,21 +5,30 @@
 	<main>
 		<h1>{{.Title}}</h1><hr>
 		<form action="/{{.Name}}/edit" method="post">
+			<input type="hidden" name="action" value="edit">
 			<table>
+				<tr>
+					<td style="text-align: right;"><span>ID</span></td>
+					<td><span>{{.Edit.Id}}</span></td>
+				</tr>
+				<tr>
+					<td style="text-align: right;"><span>Owner</span></td>
+					<td><span>{{.Edit.Owner}}</span></td>
+				</tr>
 				<tr>
 					<td style="text-align: right;"><label for="reponame">Name</label></td>
-					<td><input type="text" name="reponame" value="{{.Form.Name}}" spellcheck="false"></td>
+					<td><input type="text" name="reponame" value="{{.Edit.Name}}" spellcheck="false"></td>
 				</tr>
 				<tr>
 					<td style="text-align: right; vertical-align: top;"><label for="description">Description</label></td>
-					<td><textarea name="description" spellcheck="false">{{.Form.Description}}</textarea></td>
+					<td><textarea name="description" spellcheck="false">{{.Edit.Description}}</textarea></td>
 				</tr>
 				<tr>
 					<td style="text-align: right;"><label for="visibility">Visibility</label></td>
 					<td>
 						<select name="visibility">
 							<option value="public">Public</option>
-							<option value="private" {{if .Form.IsPrivate}}selected{{end}}>Private</option>
+							<option value="private" {{if .Edit.IsPrivate}}selected{{end}}>Private</option>
 						</select>
 					</td>
 				</tr>
@@ -32,13 +41,38 @@
 				</tr>
 				<tr>
 					<td></td>
-					<td style="color: #AA0000">{{.Message}}</td>
+					<td style="color: #AA0000">{{.Edit.Message}}</td>
 				</tr>
 			</table>
 		</form>
-		<table>
-			<tr><td style="text-align: right;"><span>ID:</span></td><td><span>{{.Form.Id}}</span></td></tr>
-			<tr><td style="text-align: right;"><span>Owner:</Span></td><td><span>{{.Form.Owner}}</span></td></tr>
-		</table>
+		<br><h2>Transfer Ownership</h2><hr>
+		<span>- You will lose access to this repository if it is not public.</span><br><br>
+		<form action="/{{.Name}}/edit" method="post">
+			<input type="hidden" name="action" value="transfer">
+			<table>
+				<tr><td><label for="owner">New Owner</label></td></tr>
+				<tr><td><input type="text" name="owner" value="{{.Transfer.Owner}}" spellcheck="false"></td></tr>
+				<tr><td>
+					<input type="submit" value="Transfer">
+					<a href="/{{.Name}}" style="color: inherit;">Cancel</a>
+				</td></tr>
+				<tr><td style="color: #AA0000">{{.Transfer.Message}}</td></tr>
+			</table>
+		</form>
+		<br><h2>Delete Repository</h2><hr>
+		<span>- This operation <b>CANNOT</b> be undone.</span><br>
+		<span>- This operation will permanently delete the {{.Name}} repository and all associated data.</span><br><br>
+		<form action="/{{.Name}}/edit" method="post">
+			<input type="hidden" name="action" value="delete">
+			<table>
+				<tr><td><label for="reponame">To confirm, type "{{.Name}}" in the box below</label></td></tr>
+				<tr><td><input type="text" name="reponame" spellcheck="false"></td></tr>
+				<tr><td>
+					<input type="submit" value="Delete">
+					<a href="/{{.Name}}" style="color: inherit;">Cancel</a>
+				</td></tr>
+				<tr><td style="color: #AA0000">{{.Delete.Message}}</td></tr>
+			</table>
+		</form>
 	</main>
 </body>
diff --git a/src/repo.go b/src/repo.go
index 2662f42..15457ff 100644
--- a/src/repo.go
+++ b/src/repo.go
@@ -22,11 +22,11 @@ type Repo struct {
 	IsPrivate   bool
 }
 
-func GetRepo(id int64) (*Repo, error) {
+func GetRepo(rid int64) (*Repo, error) {
 	r := &Repo{}
 
 	if err := db.QueryRow(
-		"SELECT id, owner_id, name, description, is_private FROM repos WHERE id = ?", id,
+		"SELECT id, owner_id, name, description, is_private FROM repos WHERE id = ?", rid,
 	).Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.IsPrivate); err != nil {
 		if !errors.Is(err, sql.ErrNoRows) {
 			return nil, err
@@ -82,12 +82,17 @@ func CreateRepo(repo Repo) error {
 	return nil
 }
 
-func DelRepo(name string) error {
-	if err := os.RemoveAll(RepoPath(name)); err != nil {
+func DelRepo(rid int64) error {
+	repo, err := GetRepo(rid)
+	if err != nil {
 		return err
 	}
 
-	if _, err := db.Exec("DELETE FROM repos WHERE name = ?", name); err != nil {
+	if err := os.RemoveAll(RepoPath(repo.Name)); err != nil {
+		return err
+	}
+
+	if _, err := db.Exec("DELETE FROM repos WHERE id = ?", rid); err != nil {
 		return err
 	}
 
@@ -128,7 +133,6 @@ func UpdateRepo(rid int64, repo Repo) error {
 	}
 
 	if repo.Name != old.Name {
-		/* TODO use a mutex lock or something to make sure this doesn't break */
 		if err := os.Rename(RepoPath(old.Name), RepoPath(repo.Name)); err != nil {
 			tx.Rollback()
 			return err
@@ -143,3 +147,11 @@ func UpdateRepo(rid int64, repo Repo) error {
 
 	return nil
 }
+
+func ChownRepo(rid int64, uid int64) error {
+	if _, err := db.Exec("UPDATE repos SET owner_id = ? WHERE id = ?", uid, rid); err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/src/repo/edit.go b/src/repo/edit.go
index 3ae2ab0..6a2e4e7 100644
--- a/src/repo/edit.go
+++ b/src/repo/edit.go
@@ -41,13 +41,17 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) {
 
 	data := struct {
 		Title, Name, Description, Url string
-		Readme, Licence, Message      string
+		Readme, Licence               string
 		Editable                      bool
 
-		Form struct {
+		Edit struct {
 			Id, Owner, Name, Description string
 			IsPrivate                    bool
+			Message                      string
 		}
+
+		Transfer struct{ Owner, Message string }
+		Delete   struct{ Message string }
 	}{
 		Title:       "Repository - Edit",
 		Name:        repo.Name,
@@ -56,11 +60,11 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) {
 		Editable:    (auth && repo.OwnerId == uid),
 	}
 
-	data.Form.Id = fmt.Sprint(repo.Id)
-	data.Form.Owner = owner.FullName + " (" + owner.Name + ")[" + fmt.Sprint(owner.Id) + "]"
-	data.Form.Name = repo.Name
-	data.Form.Description = repo.Description
-	data.Form.IsPrivate = repo.IsPrivate
+	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.IsPrivate = repo.IsPrivate
 
 	gr, err := git.PlainOpen(goit.RepoPath(repo.Name))
 	if err != nil {
@@ -86,29 +90,66 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) {
 	}
 
 	if r.Method == http.MethodPost {
-		data.Form.Name = r.FormValue("reponame")
-		data.Form.Description = r.FormValue("description")
-		data.Form.IsPrivate = r.FormValue("visibility") == "private"
-
-		if data.Form.Name == "" {
-			data.Message = "Name cannot be empty"
-		} else if slices.Contains(reserved, data.Form.Name) {
-			data.Message = "Name \"" + data.Form.Name + "\" is reserved"
-		} else if exists, err := goit.RepoExists(data.Form.Name); err != nil {
-			log.Println("[/repo/edit]", err.Error())
-			goit.HttpError(w, http.StatusInternalServerError)
-			return
-		} else if exists && data.Form.Name != repo.Name {
-			data.Message = "Name \"" + data.Form.Name + "\" is taken"
-		} else if err := goit.UpdateRepo(repo.Id, goit.Repo{
-			Name: data.Form.Name, Description: data.Form.Description, IsPrivate: data.Form.IsPrivate,
-		}); err != nil {
-			log.Println("[/repo/edit]", err.Error())
-			goit.HttpError(w, http.StatusInternalServerError)
-			return
-		} else {
-			http.Redirect(w, r, "/"+data.Form.Name+"/edit", http.StatusFound)
-			return
+		switch r.FormValue("action") {
+		case "edit":
+			data.Edit.Name = r.FormValue("reponame")
+			data.Edit.Description = r.FormValue("description")
+			data.Edit.IsPrivate = r.FormValue("visibility") == "private"
+
+			if data.Edit.Name == "" {
+				data.Edit.Message = "Name cannot be empty"
+			} else if slices.Contains(reserved, data.Edit.Name) {
+				data.Edit.Message = "Name \"" + data.Edit.Name + "\" is reserved"
+			} else if exists, err := goit.RepoExists(data.Edit.Name); err != nil {
+				log.Println("[/repo/edit]", err.Error())
+				goit.HttpError(w, http.StatusInternalServerError)
+				return
+			} else if exists && data.Edit.Name != repo.Name {
+				data.Edit.Message = "Name \"" + data.Edit.Name + "\" is taken"
+			} else if err := goit.UpdateRepo(repo.Id, goit.Repo{
+				Name: data.Edit.Name, Description: data.Edit.Description, IsPrivate: data.Edit.IsPrivate,
+			}); err != nil {
+				log.Println("[/repo/edit]", err.Error())
+				goit.HttpError(w, http.StatusInternalServerError)
+				return
+			} else {
+				http.Redirect(w, r, "/"+data.Edit.Name+"/edit", http.StatusFound)
+				return
+			}
+
+		case "transfer":
+			data.Transfer.Owner = r.FormValue("owner")
+
+			if data.Transfer.Owner == "" {
+				data.Transfer.Message = "New owner cannot be empty"
+			} else if user, err := goit.GetUserByName(data.Transfer.Owner); err != nil {
+				log.Println("[/repo/edit]", err.Error())
+				goit.HttpError(w, http.StatusInternalServerError)
+				return
+			} else if user == nil {
+				data.Transfer.Message = "User \"" + data.Transfer.Owner + "\" does not exist"
+			} else if err := goit.ChownRepo(repo.Id, user.Id); err != nil {
+				log.Println("[/repo/edit]", err.Error())
+				goit.HttpError(w, http.StatusInternalServerError)
+				return
+			} else {
+				http.Redirect(w, r, "/"+data.Edit.Name, 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("[/repo/edit]", err.Error())
+				goit.HttpError(w, http.StatusInternalServerError)
+				return
+			} else {
+				http.Redirect(w, r, "/", http.StatusFound)
+				return
+			}
 		}
 	}
 
diff --git a/src/repo/repo.go b/src/repo/repo.go
index cac89f8..984020d 100644
--- a/src/repo/repo.go
+++ b/src/repo/repo.go
@@ -1,12 +1,8 @@
 package repo
 
 import (
-	"log"
-	"net/http"
 	"regexp"
-	"strconv"
 
-	goit "github.com/Jamozed/Goit/src"
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/object"
@@ -21,36 +17,6 @@ type HeaderFields struct {
 var readmePattern = regexp.MustCompile(`(?i)^readme(?:\.?(?:md|txt))?$`)
 var licencePattern = regexp.MustCompile(`(?i)^licence(?:\.?(?:md|txt))?$`)
 
-func HandleDelete(w http.ResponseWriter, r *http.Request) {
-	auth, admin, uid := goit.AuthCookieAdmin(w, r, true)
-
-	rid, err := strconv.ParseInt(r.URL.Query().Get("repo"), 10, 64)
-	if err != nil {
-		goit.HttpError(w, http.StatusNotFound)
-		return
-	}
-
-	repo, err := goit.GetRepo(rid)
-	if err != nil {
-		log.Println("[/repo/delete]", err.Error())
-		goit.HttpError(w, http.StatusInternalServerError)
-		return
-	}
-
-	if !auth || (uid != repo.OwnerId && !admin) {
-		goit.HttpError(w, http.StatusUnauthorized)
-		return
-	}
-
-	if err := goit.DelRepo(repo.Name); err != nil {
-		log.Println("[/repo/delete]", err.Error())
-		goit.HttpError(w, http.StatusInternalServerError)
-		return
-	}
-
-	http.Redirect(w, r, "/", http.StatusFound)
-}
-
 func findReadme(gr *git.Repository, ref *plumbing.Reference) (string, error) {
 	commit, err := gr.CommitObject(ref.Hash())
 	if err != nil {