Author | Jakob Wakeling <[email protected]> |
Date | 2023-11-27 08:07:45 |
Commit | 3c55d1d0149283f0387e3111a86d10a5b18a0acf |
Parent | e3bf6a71be5c3e7af5d6f5fa12c3f89fa55324cd |
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 }