Author | Jakob Wakeling <[email protected]> |
Date | 2023-08-19 00:11:35 |
Commit | ceeeb5229f041cde3b96a1ff27f91b403b205e98 |
Parent | 4a22b95d4b1a44bde5c239387430575ee063f9bb |
Implement repository edit page
Diffstat
M | main.go | | | 1 | + |
A | res/repo/edit.html | | | 44 | ++++++++++++++++++++++++++++++++++++++++++++ |
M | res/repo/header.html | | | 3 | +++ |
M | res/res.go | | | 3 | +++ |
M | res/style.css | | | 12 | ++++++------ |
M | src/goit.go | | | 1 | - |
M | src/http.go | | | 1 | + |
M | src/repo.go | | | 64 | ++++++++++++++++++++++++++++++++++++++++++++++++++++------------ |
M | src/repo/commit.go | | | 4 | +++- |
M | src/repo/edit.go | | | 71 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- |
M | src/repo/file.go | | | 8 | +++++--- |
M | src/repo/log.go | | | 4 | +++- |
M | src/repo/refs.go | | | 4 | +++- |
M | src/repo/repo.go | | | 5 | +++++ |
M | src/repo/tree.go | | | 8 | +++++--- |
15 files changed, 200 insertions, 33 deletions
diff --git a/main.go b/main.go index 6daa6f0..153a80b 100644 --- a/main.go +++ b/main.go @@ -46,6 +46,7 @@ func main() { h.Path("/{repo}/tree/{path:.*}").Methods("GET").HandlerFunc(repo.HandleTree) h.Path("/{repo}/file/{path:.*}").Methods("GET").HandlerFunc(repo.HandleFile) h.Path("/{repo}/refs").Methods("GET").HandlerFunc(repo.HandleRefs) + h.Path("/{repo}/edit").Methods("GET", "POST").HandlerFunc(repo.HandleEdit) h.Path("/{repo}/info/refs").Methods("GET").HandlerFunc(goit.HandleInfoRefs) h.Path("/{repo}/git-upload-pack").Methods("POST").HandlerFunc(goit.HandleUploadPack) h.Path("/{repo}/git-receive-pack").Methods("POST").HandlerFunc(goit.HandleReceivePack) diff --git a/res/repo/edit.html b/res/repo/edit.html new file mode 100644 index 0000000..f76b1d7 --- /dev/null +++ b/res/repo/edit.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<head lang="en-GB">{{template "base/head" .}}</head> +<body> + <header>{{template "repo/header" .}}</header><hr> + <main> + <h1>{{.Title}}</h1><hr> + <form action="/{{.Name}}/edit" method="post"> + <table> + <tr> + <td style="text-align: right;"><label for="reponame">Name</label></td> + <td><input type="text" name="reponame" value="{{.Form.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> + </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> + </select> + </td> + </tr> + <tr> + <td></td> + <td> + <input type="submit" value="Update"> + <a href="/{{.Name}}" style="color: inherit;">Cancel</a> + </td> + </tr> + <tr> + <td></td> + <td style="color: #AA0000">{{.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> + </main> +</body> diff --git a/res/repo/header.html b/res/repo/header.html index c59fbae..8391023 100644 --- a/res/repo/header.html +++ b/res/repo/header.html @@ -24,6 +24,9 @@ {{if .Licence}} | <a href="{{.Licence}}">LICENCE</a> {{end}} + {{if .Editable}} + | <a href="/{{.Name}}/edit">Edit</a> + {{end}} </td> </tr> </table> diff --git a/res/res.go b/res/res.go index 1e106ac..fbf0647 100644 --- a/res/res.go +++ b/res/res.go @@ -44,6 +44,9 @@ var RepoHeader string //go:embed repo/create.html var RepoCreate string +//go:embed repo/edit.html +var RepoEdit string + //go:embed repo/log.html var RepoLog string diff --git a/res/style.css b/res/style.css index 2c00a7f..cb7f3a6 100644 --- a/res/style.css +++ b/res/style.css @@ -16,16 +16,16 @@ table td.lnum a { color: inherit; display: block; padding: 0 0.4rem 0 0.8rem; } table td.lnum a:hover { text-decoration: none; } table td.line { tab-size: 4; white-space: pre; } -form table input { border: 2px solid #333333; border-radius: 3px; background-color: #111111; padding: 2px; } -form table input[type="text"] { color: #888888; width: 24em; } -form table input[type="password"] { color: #888888; width: 24em; } -form table input[type="submit"] { color: #FF7E00; width: 6em; } +table input { border: 2px solid #333333; border-radius: 3px; background-color: #111111; padding: 2px; } +table input[type="text"] { color: #888888; width: 24em; } +table input[type="password"] { color: #888888; width: 24em; } +table input[type="submit"] { color: #FF7E00; width: 6em; } -form table select { +table select { border: 2px solid #333333; border-radius: 3px; background-color: #111111; color: #888888; padding: 2px; width: 24em; } -form table textarea { +table textarea { border: 2px solid #333333; border-radius: 3px; background-color: #111111; color: #888888; max-height: 18rem; min-height: 6rem; padding: 2px; resize: vertical; width: 24em; } diff --git a/src/goit.go b/src/goit.go index f9ff03d..b150df5 100644 --- a/src/goit.go +++ b/src/goit.go @@ -85,7 +85,6 @@ func Goit(conf string) (err error) { name TEXT UNIQUE NOT NULL, name_lower TEXT UNIQUE NOT NULL, description TEXT NOT NULL, - default_branch TEXT NOT NULL, is_private BOOLEAN NOT NULL )`, ); err != nil { diff --git a/src/http.go b/src/http.go index d4e1137..8955ca3 100644 --- a/src/http.go +++ b/src/http.go @@ -31,6 +31,7 @@ func init() { template.Must(Tmpl.New("repo/header").Parse(res.RepoHeader)) template.Must(Tmpl.New("repo/create").Parse(res.RepoCreate)) + template.Must(Tmpl.New("repo/edit").Parse(res.RepoEdit)) template.Must(Tmpl.New("repo/log").Parse(res.RepoLog)) template.Must(Tmpl.New("repo/commit").Parse(res.RepoCommit)) diff --git a/src/repo.go b/src/repo.go index afe986a..e523dd7 100644 --- a/src/repo.go +++ b/src/repo.go @@ -7,6 +7,7 @@ package goit import ( "database/sql" "errors" + "log" "os" "strings" @@ -14,20 +15,19 @@ import ( ) type Repo struct { - Id int64 - OwnerId int64 - Name string - Description string - DefaultBranch string - IsPrivate bool + Id int64 + OwnerId int64 + Name string + Description string + IsPrivate bool } func GetRepo(id int64) (*Repo, error) { r := &Repo{} if err := db.QueryRow( - "SELECT id, owner_id, name, description, default_branch, is_private FROM repos WHERE id = ?", id, - ).Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.DefaultBranch, &r.IsPrivate); err != nil { + "SELECT id, owner_id, name, description, is_private FROM repos WHERE id = ?", id, + ).Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.IsPrivate); err != nil { if !errors.Is(err, sql.ErrNoRows) { return nil, err } @@ -42,8 +42,8 @@ func GetRepoByName(name string) (*Repo, error) { r := &Repo{} if err := db.QueryRow( - "SELECT id, owner_id, name, description, default_branch, is_private FROM repos WHERE name = ?", name, - ).Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.DefaultBranch, &r.IsPrivate); err != nil { + "SELECT id, owner_id, name, description, is_private FROM repos WHERE name = ?", name, + ).Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.IsPrivate); err != nil { if !errors.Is(err, sql.ErrNoRows) { return nil, err } @@ -61,9 +61,9 @@ func CreateRepo(repo Repo) error { } if _, err := tx.Exec( - `INSERT INTO repos (owner_id, name, name_lower, description, default_branch, is_private) + `INSERT INTO repos (owner_id, name, name_lower, description, is_private) VALUES (?, ?, ?, ?, ?, ?)`, - repo.OwnerId, repo.Name, strings.ToLower(repo.Name), repo.Description, repo.DefaultBranch, repo.IsPrivate, + repo.OwnerId, repo.Name, strings.ToLower(repo.Name), repo.Description, repo.IsPrivate, ); err != nil { tx.Rollback() return err @@ -107,3 +107,43 @@ func RepoExists(name string) (bool, error) { return true, nil } } + +func UpdateRepo(rid int64, repo Repo) error { + old, err := GetRepo(rid) + if err != nil { + return err + } + + tx, err := db.Begin() + if err != nil { + return err + } + + if _, err := tx.Exec( + "UPDATE repos SET name = ?, name_lower = ?, description = ?, is_private = ? WHERE id = ?", + repo.Name, strings.ToLower(repo.Name), repo.Description, repo.IsPrivate, rid, + ); err != nil { + tx.Rollback() + return err + } + + 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 + } + } + + if err := tx.Commit(); err != nil { + os.Rename(RepoPath(repo.Name), RepoPath(old.Name)) + log.Println("[repo/update]", "error while renaming, check repo \""+old.Name+"\"/\""+repo.Name+"\"") + return err + } + + if _, err := db.Exec(""); err != nil { + return err + } + + return nil +} diff --git a/src/repo/commit.go b/src/repo/commit.go index 1058d3b..9ee34c7 100644 --- a/src/repo/commit.go +++ b/src/repo/commit.go @@ -45,9 +45,11 @@ func HandleCommit(w http.ResponseWriter, r *http.Request) { Stats []stat Summary string Diff template.HTML + Editable bool }{ Title: repo.Name + " - Log", Name: repo.Name, Description: repo.Description, - Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, + Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, + Editable: (auth && repo.OwnerId == uid), } gr, err := git.PlainOpen(goit.RepoPath(repo.Name)) diff --git a/src/repo/edit.go b/src/repo/edit.go index b259065..2af4bce 100644 --- a/src/repo/edit.go +++ b/src/repo/edit.go @@ -1,10 +1,12 @@ package repo import ( + "fmt" "log" "net/http" goit "github.com/Jamozed/Goit/src" + "github.com/Jamozed/Goit/src/util" "github.com/gorilla/mux" ) @@ -21,9 +23,68 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) { return } - // data := struct { - // Title string - // }{ - // Title: "Repository - Edit", - // } + owner, err := goit.GetUser(repo.OwnerId) + if err != nil { + log.Println("[/repo/edit]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } else if owner == nil { + log.Println("[/repo/edit]", repo.Id, "is owned by a nonexistent user") + /* TODO have admin adopt the orphaned repository */ + owner = &goit.User{} + } + + data := struct { + Title, Name, Description, Url string + Readme, Licence, Message string + Editable bool + + Form struct { + Id, Owner, Name, Description string + IsPrivate bool + } + }{ + Title: "Repository - Edit", + Name: repo.Name, + Description: repo.Description, + Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, + 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 + + 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.Name == "" { + data.Message = "Name cannot be empty" + } else if util.SliceContains(reserved, data.Name) { + data.Message = "Name \"" + data.Name + "\" is reserved" + } else if exists, err := goit.RepoExists(data.Name); err != nil { + log.Println("[/repo/edit]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } else if exists && data.Name != repo.Name { + data.Message = "Name \"" + data.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 + } + } + + if err := goit.Tmpl.ExecuteTemplate(w, "repo/edit", data); err != nil { + log.Println("[/repo/edit]", err.Error()) + } } diff --git a/src/repo/file.go b/src/repo/file.go index 09e3979..21eeecf 100644 --- a/src/repo/file.go +++ b/src/repo/file.go @@ -18,7 +18,7 @@ import ( ) func HandleFile(w http.ResponseWriter, r *http.Request) { - _, uid := goit.AuthCookie(w, r, true) + auth, uid := goit.AuthCookie(w, r, true) treepath := mux.Vars(r)["path"] // if treepath == "" { @@ -30,7 +30,7 @@ func HandleFile(w http.ResponseWriter, r *http.Request) { if err != nil { goit.HttpError(w, http.StatusInternalServerError) return - } else if repo == nil || (repo.IsPrivate && repo.OwnerId != uid) { + } else if repo == nil || (repo.IsPrivate && (!auth || repo.OwnerId != uid)) { goit.HttpError(w, http.StatusNotFound) return } @@ -40,9 +40,11 @@ func HandleFile(w http.ResponseWriter, r *http.Request) { Readme, Licence string Mode, File, Size string Lines []string + Editable bool }{ Title: repo.Name + " - File", Name: repo.Name, Description: repo.Description, - Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, + Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, + Editable: (auth && repo.OwnerId == uid), } gr, err := git.PlainOpen(goit.RepoPath(repo.Name)) diff --git a/src/repo/log.go b/src/repo/log.go index 03e46d0..1f667a3 100644 --- a/src/repo/log.go +++ b/src/repo/log.go @@ -44,9 +44,11 @@ func HandleLog(w http.ResponseWriter, r *http.Request) { Title, Name, Description, Url string Readme, Licence string Commits []row + Editable bool }{ Title: repo.Name + " - Log", Name: repo.Name, Description: repo.Description, - Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, + Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, + Editable: (auth && repo.OwnerId == uid), } gr, err := git.PlainOpen(goit.RepoPath(repo.Name)) diff --git a/src/repo/refs.go b/src/repo/refs.go index 6bff474..164a51d 100644 --- a/src/repo/refs.go +++ b/src/repo/refs.go @@ -32,9 +32,11 @@ func HandleRefs(w http.ResponseWriter, r *http.Request) { Title, Name, Description, Url string Readme, Licence string Branches, Tags []row + Editable bool }{ Title: repo.Name + " - References", Name: repo.Name, Description: repo.Description, - Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, + Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, + Editable: (auth && repo.OwnerId == uid), } gr, err := git.PlainOpen(goit.RepoPath(repo.Name)) diff --git a/src/repo/repo.go b/src/repo/repo.go index f118a21..cac89f8 100644 --- a/src/repo/repo.go +++ b/src/repo/repo.go @@ -13,6 +13,11 @@ import ( "github.com/go-git/go-git/v5/plumbing/storer" ) +type HeaderFields struct { + Name, Description, Url, Readme, Licence string + Editable bool +} + var readmePattern = regexp.MustCompile(`(?i)^readme(?:\.?(?:md|txt))?$`) var licencePattern = regexp.MustCompile(`(?i)^licence(?:\.?(?:md|txt))?$`) diff --git a/src/repo/tree.go b/src/repo/tree.go index 8709225..7fd9c27 100644 --- a/src/repo/tree.go +++ b/src/repo/tree.go @@ -18,14 +18,14 @@ import ( ) func HandleTree(w http.ResponseWriter, r *http.Request) { - _, uid := goit.AuthCookie(w, r, true) + auth, uid := goit.AuthCookie(w, r, true) treepath := mux.Vars(r)["path"] repo, err := goit.GetRepoByName(mux.Vars(r)["repo"]) if err != nil { goit.HttpError(w, http.StatusInternalServerError) return - } else if repo == nil || (repo.IsPrivate && repo.OwnerId != uid) { + } else if repo == nil || (repo.IsPrivate && (!auth || repo.OwnerId != uid)) { goit.HttpError(w, http.StatusNotFound) return } @@ -38,9 +38,11 @@ func HandleTree(w http.ResponseWriter, r *http.Request) { Title, Name, Description, Url string Readme, Licence string Files []row + Editable bool }{ Title: repo.Name + " - Tree", Name: repo.Name, Description: repo.Description, - Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, + Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, + Editable: (auth && repo.OwnerId == uid), } gr, err := git.PlainOpen(goit.RepoPath(repo.Name))