Author | Jakob Wakeling <[email protected]> |
Date | 2023-10-23 10:05:22 |
Commit | 988a70896b863d3c930c5d6cd78b6f2df4c63d35 |
Parent | 5166d87cbc742c010bc82fbab661b3edad09c35b |
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 {