Goit

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

AuthorJakob Wakeling <[email protected]>
Date2023-11-27 08:07:45
Commit3c55d1d0149283f0387e3111a86d10a5b18a0acf
Parente3bf6a71be5c3e7af5d6f5fa12c3f89fa55324cd

Add transfer and delete to admin repo edit page

Diffstat

A res/admin/header.html | 13 +++++++++++++
M res/admin/repo_edit.html | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
M res/admin/repos.html | 2 +-
M res/admin/users.html | 2 +-
M res/repo/edit.html | 2 +-
M res/res.go | 3 +++
M src/admin/repos.go | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
M src/goit/http.go | 1 +
M src/repo/edit.go | 23 +++++++++++++++++------

9 files changed, 174 insertions, 72 deletions

diff --git a/res/admin/header.html b/res/admin/header.html
new file mode 100644
index 0000000..ed6a382
--- /dev/null
+++ b/res/admin/header.html
@@ -0,0 +1,13 @@
+<table>
+	<tr>
+		<td rowspan="2">
+			<a href="/"><img style="max-height: 24px;" src="/static/favicon.png"></a>
+		</td>
+		<td><h1>{{.Title}}</h1></td>
+	</tr>
+	<tr><td>
+		<a href="/admin/users">Users</a>
+		| <a href="/admin/repos">Repositories</a>
+		| <a href="/admin/user/create">Create User</a>
+	</td></tr>
+</table>
diff --git a/res/admin/repo_edit.html b/res/admin/repo_edit.html
index 0a9a132..1b852a6 100644
--- a/res/admin/repo_edit.html
+++ b/res/admin/repo_edit.html
@@ -1,45 +1,77 @@
 <!DOCTYPE html>
 <head lang="en-GB">{{template "base/head" .}}</head>
 <body>
-	<header>
-		<table>
-			<tr>
-				<td rowspan="2">
-					<a href="/"><img src="/static/favicon.png" style="max-height: 24px"></a>
-				</td>
-				<td><h1>{{.Title}}</h1></td>
-			</tr>
-			<tr><td>
-				<a href="/admin/users">Users</a>
-				| <a href="/admin/repos">Repositories</a>
-				| <a href="/admin/user/create">Create User</a>
-			</td></tr>
-		</table>
-	</header><hr>
+	<header>{{template "admin/header" .}}</header><hr>
 	<main>
 		<h1>{{.Title}}</h1><hr>
-		<form action="/admin/repo/edit?repo={{.Form.Id}}" method="post">
+		<form action="/admin/repo/edit?repo={{.Edit.Id}}" method="post">
+			<input type="hidden" name="action" value="edit">
 			<table>
-				<tr><td><label for="id">ID</label></td></tr>
-				<tr><td><span>{{.Form.Id}}</span></td></tr>
-				<tr><td><label for="id">Owner</label></td></tr>
-				<tr><td><span>{{.Form.Owner}}</span></td></tr>
-				<tr><td><label for="reponame">Name</label></td></tr>
-				<tr><td><input type="text" name="reponame" value="{{.Form.Name}}" spellcheck="false"></td></tr>
-				<tr><td><label for="description">Description</label></td></tr>
-				<tr><td><textarea name="description" spellcheck="false">{{.Form.Description}}</textarea></td></tr>
-				<tr><td><label for="visibility">Visibility:</label></td></tr>
+				<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="{{.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">{{.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 .Edit.IsPrivate}}selected{{end}}>Private</option>
+						</select>
+					</td>
+				</tr>
+				<tr>
+					<td></td>
+					<td>
+						<input type="submit" value="Update">
+						<a href="/admin/repos" style="color: inherit;">Cancel</a>
+					</td>
+				</tr>
+				<tr>
+					<td></td>
+					<td><span style="color: #AA0000">{{.Edit.Message}}</span></td>
+				</tr>
+			</table>
+		</form>
+		<br><h2>Transfer Ownership</h2><hr>
+		<span>- You will lose access to this repository if it is not public.</span><br><br>
+		<form action="/admin/repo/edit?repo={{.Edit.Id}}" 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>
-					<select name="visibility">
-						<option value="public">Public</option>
-						<option value="private" {{if .Form.IsPrivate}}selected{{end}}>Private</option>
-					</select>
+					<input type="submit" value="Transfer">
+					<a href="/admin/repos" 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="/admin/repo/edit?repo={{.Edit.Id}}" 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" name="submit" value="Update">
+					<input type="submit" value="Delete">
 					<a href="/admin/repos" style="color: inherit;">Cancel</a>
 				</td></tr>
-				<tr><td><span style="color: #AA0000">{{.Message}}</span></td></tr>
+				<tr><td style="color: #AA0000">{{.Delete.Message}}</td></tr>
 			</table>
 		</form>
 	</main>
diff --git a/res/admin/repos.html b/res/admin/repos.html
index b8f66eb..ec30841 100644
--- a/res/admin/repos.html
+++ b/res/admin/repos.html
@@ -17,7 +17,7 @@
 		</table>
 	</header><hr>
 	<main>
-		<table>
+		<table class="highlight-row">
 			<thead>
 				<tr>
 					<td><b>ID</b></td>
diff --git a/res/admin/users.html b/res/admin/users.html
index 29ae963..ec963ac 100644
--- a/res/admin/users.html
+++ b/res/admin/users.html
@@ -17,7 +17,7 @@
 		</table>
 	</header><hr>
 	<main>
-		<table>
+		<table class="highlight-row">
 			<thead>
 				<tr>
 					<td><b>ID</b></td>
diff --git a/res/repo/edit.html b/res/repo/edit.html
index 2cf5088..768c864 100644
--- a/res/repo/edit.html
+++ b/res/repo/edit.html
@@ -20,7 +20,7 @@
 					<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 style="text-align:right; vertical-align:top;"><label for="description">Description</label></td>
 					<td><textarea name="description" spellcheck="false">{{.Edit.Description}}</textarea></td>
 				</tr>
 				<tr>
diff --git a/res/res.go b/res/res.go
index b2dde74..d257f88 100644
--- a/res/res.go
+++ b/res/res.go
@@ -13,6 +13,9 @@ var Index string
 //go:embed base/head.html
 var BaseHead string
 
+//go:embed admin/header.html
+var AdminHeader string
+
 //go:embed admin/index.html
 var AdminIndex string
 
diff --git a/src/admin/repos.go b/src/admin/repos.go
index 811d83f..01d1d66 100644
--- a/src/admin/repos.go
+++ b/src/admin/repos.go
@@ -101,47 +101,89 @@ func HandleRepoEdit(w http.ResponseWriter, r *http.Request) {
 	}
 
 	data := struct {
-		Title, Message string
+		Title, Name string
 
-		Form struct {
-			Id, Owner, Name, Description string
-			IsPrivate                    bool
+		Edit struct {
+			Id, Owner, Name, Description, Message string
+			IsPrivate                             bool
 		}
+
+		Transfer struct{ Owner, Message string }
+		Delete   struct{ Message string }
 	}{
 		Title: "Admin - Edit Repository",
+		Name:  repo.Name,
 	}
 
-	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
 
 	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(goit.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("[/admin/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 len(data.Form.Description) > 256 {
-			data.Message = "Description cannot exceed 256 characters"
-		} 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("[/admin/repo/edit]", err.Error())
-			goit.HttpError(w, http.StatusInternalServerError)
-			return
-		} else {
-			data.Message = "Repository \"" + repo.Name + "\" updated successfully"
+		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(goit.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("[/admin/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 len(data.Edit.Description) > 256 {
+				data.Edit.Message = "Description cannot exceed 256 characters"
+			} 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("[/admin/repo/edit]", err.Error())
+				goit.HttpError(w, http.StatusInternalServerError)
+				return
+			} else {
+				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
+			}
 		}
 	}
 
diff --git a/src/goit/http.go b/src/goit/http.go
index ebda9e2..af7d7d6 100644
--- a/src/goit/http.go
+++ b/src/goit/http.go
@@ -18,6 +18,7 @@ func init() {
 	template.Must(Tmpl.New("index").Parse(res.Index))
 	template.Must(Tmpl.New("base/head").Parse(res.BaseHead))
 
+	template.Must(Tmpl.New("admin/header").Parse(res.AdminHeader))
 	template.Must(Tmpl.New("admin/index").Parse(res.AdminIndex))
 	template.Must(Tmpl.New("admin/users").Parse(res.AdminUsers))
 	template.Must(Tmpl.New("admin/user/create").Parse(res.AdminUserCreate))
diff --git a/src/repo/edit.go b/src/repo/edit.go
index d423a30..eb98841 100644
--- a/src/repo/edit.go
+++ b/src/repo/edit.go
@@ -16,14 +16,24 @@ import (
 )
 
 func HandleEdit(w http.ResponseWriter, r *http.Request) {
-	auth, uid := goit.AuthCookie(w, r, true)
+	auth, user, err := goit.Auth(w, r, true)
+	if err != nil {
+		log.Println("[admin/users]", err.Error())
+		goit.HttpError(w, http.StatusInternalServerError)
+		return
+	}
+
+	if !auth || !user.IsAdmin {
+		goit.HttpError(w, http.StatusNotFound)
+		return
+	}
 
 	repo, err := goit.GetRepoByName(mux.Vars(r)["repo"])
 	if err != nil {
 		log.Println("[/repo/edit]", err.Error())
 		goit.HttpError(w, http.StatusInternalServerError)
 		return
-	} else if repo == nil || (!auth || repo.OwnerId != uid) {
+	} else if repo == nil || (!auth || repo.OwnerId != user.Id) {
 		goit.HttpError(w, http.StatusNotFound)
 		return
 	}
@@ -57,7 +67,7 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) {
 		Name:        repo.Name,
 		Description: repo.Description,
 		Url:         util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name,
-		Editable:    (auth && repo.OwnerId == uid),
+		Editable:    (auth && repo.OwnerId == user.Id),
 	}
 
 	data.Edit.Id = fmt.Sprint(repo.Id)
@@ -124,17 +134,18 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) {
 
 			if data.Transfer.Owner == "" {
 				data.Transfer.Message = "New owner cannot be empty"
-			} else if user, err := goit.GetUserByName(data.Transfer.Owner); err != nil {
+			} else if u, err := goit.GetUserByName(data.Transfer.Owner); err != nil {
 				log.Println("[/repo/edit]", err.Error())
 				goit.HttpError(w, http.StatusInternalServerError)
 				return
-			} else if user == nil {
+			} else if u == nil {
 				data.Transfer.Message = "User \"" + data.Transfer.Owner + "\" does not exist"
-			} else if err := goit.ChownRepo(repo.Id, user.Id); err != nil {
+			} else if err := goit.ChownRepo(repo.Id, u.Id); err != nil {
 				log.Println("[/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, "/"+data.Edit.Name, http.StatusFound)
 				return
 			}