Author | Jakob Wakeling <[email protected]> |
Date | 2023-11-22 10:25:37 |
Commit | 463cf58b6afaa428962644e198ac283f0d8ddd9c |
Parent | 7dd44593815e8cb2019e0757ac1900a3da470755 |
Update user and repo admin pages
Diffstat
M | res/admin/repo_edit.html | | | 55 | ++++++++++++++++++++++++++++++++++++++----------------- |
M | res/admin/user_edit.html | | | 47 | +++++++++++++++++++++++++++++++++-------------- |
A | src/admin/admin.go | | | 25 | +++++++++++++++++++++++++ |
A | src/admin/repos.go | | | 151 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/admin/users.go | | | 189 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | src/goit/admin.go | | | 321 | -------------------------------------------------------------------------------- |
M | src/goit/repo.go | | | 26 | ++++++++++++++++++++++++++ |
M | src/goit/user.go | | | 41 | +++++++++++++++++++++++++++++++++++++++-- |
M | src/main.go | | | 13 | +++++++------ |
M | src/repo/edit.go | | | 2 | ++ |
M | src/user/edit.go | | | 2 | +- |
11 files changed, 511 insertions, 361 deletions
diff --git a/res/admin/repo_edit.html b/res/admin/repo_edit.html index 4cb6fdb..76ddf3e 100644 --- a/res/admin/repo_edit.html +++ b/res/admin/repo_edit.html @@ -1,24 +1,45 @@ <!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> + </td></tr> + </table> + </header><hr> <main> - <h1>{{.Title}}</h1> - <form action="/admin/repo/edit?repo={{.Id}}" method="post"> - <label for="id">ID:</label> - <input type="text" name="id" value="{{.Id}}" disabled><br> - <label for="id">Owner:</label> - <input type="text" name="owner" value="{{.Owner}}" disabled><br> - <label for="reponame">Name:</label> - <input type="text" name="reponame" value="{{.Name}}"><br> - <label for="description">Description:</label> - <input type="text" name="description" value="{{.Description}}"><br> - <label for="visibility">Visibility:</label> - <select name="visibility"> - <option value="public">Public</option> - <option value="private" {{if .IsPrivate}}selected{{end}}>Private</option> - </select><br> - <input type="submit" value="Update"> + <h1>{{.Title}}</h1><hr> + <form action="/admin/repo/edit?repo={{.Form.Id}}" method="post"> + <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> + <select name="visibility"> + <option value="public">Public</option> + <option value="private" {{if .Form.IsPrivate}}selected{{end}}>Private</option> + </select> + </td></tr> + <tr><td> + <input type="submit" name="submit" value="Update"> + <a href="/admin/repos" style="color: inherit;">Cancel</a> + </td></tr> + <tr><td><span style="color: #AA0000">{{.Message}}</span></td></tr> + </table> </form> - <span>{{.Message}}</span> </main> </body> diff --git a/res/admin/user_edit.html b/res/admin/user_edit.html index 43badf4..3424289 100644 --- a/res/admin/user_edit.html +++ b/res/admin/user_edit.html @@ -1,21 +1,40 @@ <!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> + </td></tr> + </table> + </header><hr> <main> - <h1>{{.Title}}</h1> - <form action="/admin/user/edit?user={{.Id}}" method="post"> - <label for="id">ID:</label> - <input type="text" name="id" value="{{.Id}}" disabled><br> - <label for="username">Username:</label> - <input type="text" name="username" value="{{.Name}}"><br> - <label for="fullname">Full Name:</label> - <input type="text" name="fullname" value="{{.FullName}}"><br> - <label for="password">Password:</label> - <input type="password" name="password" placeholder="Unchanged"><br> - <label for="admin">Admin:</label> - <input type="checkbox" name="admin" value="true" {{if .IsAdmin}}checked{{end}}><br> - <input type="submit" value="Update"> + <h1>{{.Title}}</h1><hr> + <form action="/admin/user/edit?user={{.Form.Id}}" method="post"> + <table> + <tr><td><label for="id">ID</label></td></tr> + <tr><td><input type="text" name="id" value="{{.Form.Id}}" spellcheck="false" disabled></td></tr> + <tr><td><label for="username">Username</label></td></tr> + <tr><td><input type="text" name="username" value="{{.Form.Name}}" spellcheck="false"></td></tr> + <tr><td><label for="fullname">Full Name</label></td></tr> + <tr><td><input type="text" name="fullname" value="{{.Form.FullName}}" spellcheck="false"></td></tr> + <tr><td><label for="password">Password</label></td></tr> + <tr><td><input type="password" name="password" placeholder="unchanged"></td></tr> + <tr><td><label for="admin">Admin</label></td></tr> + <tr><td><input type="checkbox" name="admin" value="true" {{if .Form.IsAdmin}}checked{{end}}></td></tr> + <tr><td> + <input type="submit" name="submit" value="Update"> + <a href="/admin/users" style="color: inherit;">Cancel</a> + <span style="color: #AA0000">{{.Message}}</span> + </td></tr> + </table> </form> - <span>{{.Message}}</span> </main> </body> diff --git a/src/admin/admin.go b/src/admin/admin.go new file mode 100644 index 0000000..bff2609 --- /dev/null +++ b/src/admin/admin.go @@ -0,0 +1,25 @@ +package admin + +import ( + "log" + "net/http" + + "github.com/Jamozed/Goit/src/goit" +) + +func HandleIndex(w http.ResponseWriter, r *http.Request) { + auth, user, err := goit.Auth(w, r, true) + if err != nil { + log.Println("[admin]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + } + + if !auth || !user.IsAdmin { + goit.HttpError(w, http.StatusNotFound) + return + } + + if err := goit.Tmpl.ExecuteTemplate(w, "admin/index", struct{ Title string }{"Admin"}); err != nil { + log.Println("[/admin/index]", err.Error()) + } +} diff --git a/src/admin/repos.go b/src/admin/repos.go new file mode 100644 index 0000000..811d83f --- /dev/null +++ b/src/admin/repos.go @@ -0,0 +1,151 @@ +package admin + +import ( + "fmt" + "log" + "net/http" + "slices" + "strconv" + + "github.com/Jamozed/Goit/src/goit" + "github.com/Jamozed/Goit/src/util" + "github.com/dustin/go-humanize" +) + +func HandleRepos(w http.ResponseWriter, r *http.Request) { + 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 + } + + type row struct{ Id, Owner, Name, Visibility, Size string } + data := struct { + Title string + Repos []row + }{Title: "Admin - Repositories"} + + repos, err := goit.GetRepos() + if err != nil { + log.Println("[/admin/repos]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + for _, r := range repos { + u, err := goit.GetUser(r.OwnerId) + if err != nil { + log.Println("[/admin/repos]", err.Error()) + u = &goit.User{} + } + + size, err := util.DirSize(goit.RepoPath(r.Name, true)) + if err != nil { + log.Println("[/admin/repos]", err.Error()) + } + + data.Repos = append(data.Repos, row{ + fmt.Sprint(r.Id), u.Name, r.Name, util.If(r.IsPrivate, "private", "public"), humanize.IBytes(size), + }) + } + + if err := goit.Tmpl.ExecuteTemplate(w, "admin/repos", data); err != nil { + log.Println("[/admin/repos]", err.Error()) + } +} + +func HandleRepoEdit(w http.ResponseWriter, r *http.Request) { + 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 + } + + id, err := strconv.ParseInt(r.URL.Query().Get("repo"), 10, 64) + if err != nil { + goit.HttpError(w, http.StatusNotFound) + return + } + + repo, err := goit.GetRepo(id) + if err != nil { + log.Println("[/admin/repo/edit]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } else if repo == nil { + goit.HttpError(w, http.StatusNotFound) + return + } + + owner, err := goit.GetUser(repo.OwnerId) + if err != nil { + log.Println("[/admin/repo/edit]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } else if owner == nil { + log.Println("[/admin/repo/edit]", repo.Id, "is owned by a nonexistent user") + /* TODO have admin adopt the orphaned repository */ + owner = &goit.User{} + } + + data := struct { + Title, Message string + + Form struct { + Id, Owner, Name, Description string + IsPrivate bool + } + }{ + Title: "Admin - Edit Repository", + } + + 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.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" + } + } + + if err := goit.Tmpl.ExecuteTemplate(w, "admin/repo/edit", data); err != nil { + log.Println("[/admin/repo/edit]", err.Error()) + } +} diff --git a/src/admin/users.go b/src/admin/users.go new file mode 100644 index 0000000..eddad46 --- /dev/null +++ b/src/admin/users.go @@ -0,0 +1,189 @@ +package admin + +import ( + "fmt" + "log" + "net/http" + "slices" + "strconv" + "strings" + + "github.com/Jamozed/Goit/src/goit" + "github.com/Jamozed/Goit/src/util" +) + +func HandleUsers(w http.ResponseWriter, r *http.Request) { + 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 + } + + type row struct{ Id, Name, FullName, IsAdmin string } + data := struct { + Title string + Users []row + }{Title: "Admin - Users"} + + users, err := goit.GetUsers() + if err != nil { + log.Println("[admin/users]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + for _, u := range users { + data.Users = append(data.Users, row{ + fmt.Sprint(u.Id), u.Name, u.FullName, util.If(u.IsAdmin, "true", "false"), + }) + } + + if err := goit.Tmpl.ExecuteTemplate(w, "admin/users", data); err != nil { + log.Println("[/admin/users]", err.Error()) + } +} + +func HandleUserCreate(w http.ResponseWriter, r *http.Request) { + 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 + } + + data := struct{ Title, Message string }{"Admin - Create User", ""} + + if r.Method == http.MethodPost { + username := strings.ToLower(r.FormValue("username")) + fullname := r.FormValue("fullname") + password := r.FormValue("password") + isAdmin := r.FormValue("admin") == "true" + + if username == "" { + data.Message = "Username cannot be empty" + } else if slices.Contains(goit.Reserved, username) { + data.Message = "Username \"" + username + "\" is reserved" + } else if exists, err := goit.UserExists(username); err != nil { + log.Println("[/admin/user/create]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } else if exists { + data.Message = "Username \"" + username + "\" is taken" + } else if salt, err := goit.Salt(); err != nil { + log.Println("[/admin/user/create]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } else if err := goit.CreateUser(goit.User{ + Name: username, FullName: fullname, Pass: goit.Hash(password, salt), PassAlgo: "argon2", Salt: salt, + IsAdmin: isAdmin, + }); err != nil { + log.Println("[/admin/user/create]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } else { + data.Message = "User \"" + username + "\" created successfully" + } + } + + if err := goit.Tmpl.ExecuteTemplate(w, "admin/user/create", data); err != nil { + log.Println("[/admin/user/create]", err.Error()) + } +} + +func HandleUserEdit(w http.ResponseWriter, r *http.Request) { + 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 + } + + uid, err := strconv.ParseInt(r.URL.Query().Get("user"), 10, 64) + if err != nil { + goit.HttpError(w, http.StatusNotFound) + return + } + + u, err := goit.GetUser(uid) + if err != nil { + log.Println("[/admin/user/edit]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } else if u == nil { + goit.HttpError(w, http.StatusNotFound) + return + } + + data := struct { + Title, Message string + + Form struct { + Id, Name, FullName string + IsAdmin bool + } + }{ + Title: "Admin - Edit User", + } + + data.Form.Id = fmt.Sprint(u.Id) + data.Form.Name = u.Name + data.Form.FullName = u.FullName + data.Form.IsAdmin = u.IsAdmin + + if r.Method == http.MethodPost { + data.Form.Name = strings.ToLower(r.FormValue("username")) + data.Form.FullName = r.FormValue("fullname") + password := r.FormValue("password") + data.Form.IsAdmin = r.FormValue("admin") == "true" + + if data.Form.Name == "" { + data.Message = "Username cannot be empty" + } else if slices.Contains(goit.Reserved, data.Form.Name) { + data.Message = "Username \"" + data.Form.Name + "\" is reserved" + } else if exists, err := goit.UserExists(data.Form.Name); err != nil { + log.Println("[/admin/user/edit]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } else if exists && data.Form.Name != u.Name { + data.Message = "Username \"" + data.Form.Name + "\" is taken" + } else { + if err := goit.UpdateUser(u.Id, goit.User{ + Name: data.Form.Name, FullName: data.Form.FullName, IsAdmin: data.Form.IsAdmin, + }); err != nil { + log.Println("[/admin/user/edit]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + if password != "" { + if err := goit.UpdatePassword(u.Id, password); err != nil { + log.Println("[/admin/user/edit]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + } + + data.Message = "User \"" + u.Name + "\" updated successfully" + } + } + + if err := goit.Tmpl.ExecuteTemplate(w, "admin/user/edit", data); err != nil { + log.Println("[/admin/user/edit]", err.Error()) + } +} diff --git a/src/goit/admin.go b/src/goit/admin.go deleted file mode 100644 index 9804e38..0000000 --- a/src/goit/admin.go +++ /dev/null @@ -1,321 +0,0 @@ -// admin.go -// Copyright (C) 2023, Jakob Wakeling -// All rights reserved. - -package goit - -import ( - "fmt" - "log" - "net/http" - "slices" - "strconv" - "strings" - - "github.com/Jamozed/Goit/src/util" - "github.com/dustin/go-humanize" -) - -func HandleAdminIndex(w http.ResponseWriter, r *http.Request) { - if _, admin, _ := AuthCookieAdmin(w, r, true); !admin { - HttpError(w, http.StatusNotFound) - return - } - - if err := Tmpl.ExecuteTemplate(w, "admin/index", struct{ Title string }{"Admin"}); err != nil { - log.Println("[/admin/index]", err.Error()) - } -} - -func HandleAdminUsers(w http.ResponseWriter, r *http.Request) { - if _, admin, _ := AuthCookieAdmin(w, r, true); !admin { - HttpError(w, http.StatusNotFound) - return - } - - rows, err := db.Query("SELECT id, name, name_full, is_admin FROM users") - if err != nil { - log.Println("[/admin/users]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } - - defer rows.Close() - - type row struct{ Id, Name, FullName, IsAdmin string } - data := struct { - Title string - Users []row - }{Title: "Admin - Users"} - - for rows.Next() { - d := User{} - if err := rows.Scan(&d.Id, &d.Name, &d.FullName, &d.IsAdmin); err != nil { - log.Println("[/admin/users]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } - - data.Users = append(data.Users, row{ - fmt.Sprint(d.Id), d.Name, d.FullName, util.If(d.IsAdmin, "true", "false"), - }) - } - - if err := rows.Err(); err != nil { - log.Println("[/admin/users]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } - - if err := Tmpl.ExecuteTemplate(w, "admin/users", data); err != nil { - log.Println("[/admin/users]", err.Error()) - } -} - -func HandleAdminUserCreate(w http.ResponseWriter, r *http.Request) { - if _, admin, _ := AuthCookieAdmin(w, r, true); !admin { - HttpError(w, http.StatusNotFound) - return - } - - data := struct{ Title, Message string }{"Admin - Create User", ""} - - if r.Method == http.MethodPost { - username := strings.ToLower(r.FormValue("username")) - fullname := r.FormValue("fullname") - password := r.FormValue("password") - isAdmin := r.FormValue("admin") == "true" - - if username == "" { - data.Message = "Username cannot be empty" - } else if slices.Contains(Reserved, username) { - data.Message = "Username \"" + username + "\" is reserved" - } else if exists, err := UserExists(username); err != nil { - log.Println("[/admin/user/create]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else if exists { - data.Message = "Username \"" + username + "\" is taken" - } else if salt, err := Salt(); err != nil { - log.Println("[/admin/user/create]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else if _, err := db.Exec( - "INSERT INTO users (name, name_full, pass, pass_algo, salt, is_admin) VALUES (?, ?, ?, ?, ?, ?)", - username, fullname, Hash(password, salt), "argon2", salt, isAdmin, - ); err != nil { - log.Println("[/admin/user/create]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else { - data.Message = "User \"" + username + "\" created successfully" - } - } - - if err := Tmpl.ExecuteTemplate(w, "admin/user/create", data); err != nil { - log.Println("[/admin/user/create]", err.Error()) - } -} - -func HandleAdminUserEdit(w http.ResponseWriter, r *http.Request) { - if _, admin, _ := AuthCookieAdmin(w, r, true); !admin { - HttpError(w, http.StatusNotFound) - return - } - - id, err := strconv.ParseInt(r.URL.Query().Get("user"), 10, 64) - if err != nil { - HttpError(w, http.StatusNotFound) - return - } - - user, err := GetUser(id) - if err != nil { - log.Println("[/admin/user/edit]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else if user == nil { - HttpError(w, http.StatusNotFound) - return - } - - data := struct { - Title, Id, Name, FullName, Message string - IsAdmin bool - }{ - Title: "Admin - Edit User", Id: fmt.Sprint(user.Id), Name: user.Name, FullName: user.FullName, - IsAdmin: user.IsAdmin, - } - - if r.Method == http.MethodPost { - data.Name = strings.ToLower(r.FormValue("username")) - data.FullName = r.FormValue("fullname") - password := r.FormValue("password") - data.IsAdmin = r.FormValue("admin") == "true" - - if data.Name == "" { - data.Message = "Username cannot be empty" - } else if slices.Contains(Reserved, data.Name) { - data.Message = "Username \"" + data.Name + "\" is reserved" - } else if exists, err := UserExists(data.Name); err != nil { - log.Println("[/admin/user/edit]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else if exists && data.Name != user.Name { - data.Message = "Username \"" + data.Name + "\" is taken" - } else if salt, err := Salt(); err != nil { - log.Println("[/admin/user/edit]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else { - if password == "" { - _, err = db.Exec( - "UPDATE users SET name = ?, name_full = ?, is_admin = ? WHERE id = ?", - data.Name, data.FullName, data.IsAdmin, user.Id, - ) - } else { - _, err = db.Exec( - "UPDATE users SET name = ?, name_full = ?, pass = ?, salt = ?, is_admin = ? WHERE id = ?", - data.Name, data.FullName, Hash(password, salt), salt, data.IsAdmin, user.Id, - ) - } - - if err != nil { - log.Println("[/admin/user/edit]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else { - data.Message = "User \"" + user.Name + "\" updated successfully" - } - } - } - - if err := Tmpl.ExecuteTemplate(w, "admin/user/edit", data); err != nil { - log.Println("[/admin/user/edit]", err.Error()) - } -} - -func HandleAdminRepos(w http.ResponseWriter, r *http.Request) { - if _, admin, _ := AuthCookieAdmin(w, r, true); !admin { - HttpError(w, http.StatusNotFound) - return - } - - rows, err := db.Query("SELECT id, owner_id, name, is_private FROM repos") - if err != nil { - log.Println("[/admin/repos]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } - - defer rows.Close() - - type row struct{ Id, Owner, Name, Visibility, Size string } - data := struct { - Title string - Repos []row - }{Title: "Admin - Repositories"} - - for rows.Next() { - d := Repo{} - if err := rows.Scan(&d.Id, &d.OwnerId, &d.Name, &d.IsPrivate); err != nil { - log.Println("[/admin/repos]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } - - user, err := GetUser(d.OwnerId) - if err != nil { - log.Println("[/admin/repos]", err.Error()) - } - - size, err := util.DirSize(RepoPath(d.Name, true)) - if err != nil { - log.Println("[/admin/repos]", err.Error()) - } - - data.Repos = append(data.Repos, row{ - fmt.Sprint(d.Id), user.Name, d.Name, util.If(d.IsPrivate, "private", "public"), humanize.IBytes(size), - }) - } - - if err := rows.Err(); err != nil { - log.Println("[/admin/repos]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } - - if err := Tmpl.ExecuteTemplate(w, "admin/repos", data); err != nil { - log.Println("[/admin/repos]", err.Error()) - } -} - -func HandleAdminRepoEdit(w http.ResponseWriter, r *http.Request) { - if _, admin, _ := AuthCookieAdmin(w, r, true); !admin { - HttpError(w, http.StatusNotFound) - return - } - - id, err := strconv.ParseInt(r.URL.Query().Get("repo"), 10, 64) - if err != nil { - HttpError(w, http.StatusNotFound) - return - } - - repo, err := GetRepo(id) - if err != nil { - log.Println("[/admin/repo/edit]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else if repo == nil { - HttpError(w, http.StatusNotFound) - return - } - - data := struct { - Title, Id, Owner, Name, Description, Message string - IsPrivate bool - }{ - Title: "Admin - Edit Repository", Id: fmt.Sprint(repo.Id), Name: repo.Name, Description: repo.Description, - IsPrivate: repo.IsPrivate, - } - - owner, err := GetUser(repo.OwnerId) - if err != nil { - log.Println("[/admin/repo/edit]", err.Error()) - data.Owner = fmt.Sprint(repo.OwnerId) - } else { - data.Owner = owner.Name - } - - if r.Method == http.MethodPost { - data.Name = r.FormValue("reponame") - data.Description = r.FormValue("description") - data.IsPrivate = r.FormValue("visibility") == "private" - - if data.Name == "" { - data.Message = "Name cannot be empty" - } else if slices.Contains(Reserved, data.Name) { - data.Message = "Name \"" + data.Name + "\" is reserved" - } else if exists, err := RepoExists(data.Name); err != nil { - log.Println("[/admin/repo/edit]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else if exists && data.Name != repo.Name { - data.Message = "Name \"" + data.Name + "\" is taken" - } else if _, err := db.Exec( - "UPDATE repos SET name = ?, name_lower = ?, description = ?, is_private = ? WHERE id = ?", - data.Name, strings.ToLower(data.Name), data.Description, data.IsPrivate, repo.Id, - ); err != nil { - log.Println("[/admin/repo/edit]", err.Error()) - HttpError(w, http.StatusInternalServerError) - return - } else { - data.Message = "Repository \"" + repo.Name + "\" updated successfully" - } - } - - if err := Tmpl.ExecuteTemplate(w, "admin/repo/edit", data); err != nil { - log.Println("[/admin/repo/edit]", err.Error()) - } -} diff --git a/src/goit/repo.go b/src/goit/repo.go index 32c95a0..13b2f52 100644 --- a/src/goit/repo.go +++ b/src/goit/repo.go @@ -22,6 +22,32 @@ type Repo struct { IsPrivate bool `json:"is_private"` } +func GetRepos() ([]Repo, error) { + repos := []Repo{} + + rows, err := db.Query("SELECT id, owner_id, name, description, is_private FROM repos") + if err != nil { + return nil, err + } + + defer rows.Close() + + for rows.Next() { + r := Repo{} + if err := rows.Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.IsPrivate); err != nil { + return nil, err + } + + repos = append(repos, r) + } + + if rows.Err() != nil { + return nil, err + } + + return repos, nil +} + func GetRepo(rid int64) (*Repo, error) { r := &Repo{} diff --git a/src/goit/user.go b/src/goit/user.go index cbf110c..16c26d5 100644 --- a/src/goit/user.go +++ b/src/goit/user.go @@ -29,6 +29,32 @@ func HandleUserLogout(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusFound) } +func GetUsers() ([]User, error) { + users := []User{} + + rows, err := db.Query("SELECT id, name, name_full, pass, pass_algo, salt, is_admin FROM users") + if err != nil { + return nil, err + } + + defer rows.Close() + + for rows.Next() { + u := User{} + if err := rows.Scan(&u.Id, &u.Name, &u.FullName, &u.Pass, &u.PassAlgo, &u.Salt, &u.IsAdmin); err != nil { + return nil, err + } + + users = append(users, u) + } + + if rows.Err() != nil { + return nil, err + } + + return users, nil +} + func GetUser(id int64) (*User, error) { u := User{} @@ -72,10 +98,21 @@ func UserExists(name string) (bool, error) { } } +func CreateUser(user User) error { + if _, err := db.Exec( + "INSERT INTO users (name, name_full, pass, pass_algo, salt, is_admin) VALUES (?, ?, ?, ?, ?, ?)", + user.Name, user.FullName, user.Pass, user.PassAlgo, user.Salt, user.IsAdmin, + ); err != nil { + return err + } + + return nil +} + func UpdateUser(uid int64, user User) error { if _, err := db.Exec( - "UPDATE users SET name = ?, name_full = ? WHERE id = ?", - user.Name, user.FullName, uid, + "UPDATE users SET name = ?, name_full = ?, is_admin = ? WHERE id = ?", + user.Name, user.FullName, user.IsAdmin, uid, ); err != nil { return err } diff --git a/src/main.go b/src/main.go index fd36b89..3d96181 100644 --- a/src/main.go +++ b/src/main.go @@ -19,6 +19,7 @@ import ( "time" "github.com/Jamozed/Goit/res" + "github.com/Jamozed/Goit/src/admin" "github.com/Jamozed/Goit/src/goit" "github.com/Jamozed/Goit/src/repo" "github.com/Jamozed/Goit/src/user" @@ -85,12 +86,12 @@ 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("/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) - h.Path("/admin/user/edit").Methods("GET", "POST").HandlerFunc(goit.HandleAdminUserEdit) - h.Path("/admin/repos").Methods("GET").HandlerFunc(goit.HandleAdminRepos) - h.Path("/admin/repo/edit").Methods("GET", "POST").HandlerFunc(goit.HandleAdminRepoEdit) + h.Path("/admin").Methods("GET").HandlerFunc(admin.HandleIndex) + h.Path("/admin/users").Methods("GET").HandlerFunc(admin.HandleUsers) + h.Path("/admin/user/create").Methods("GET", "POST").HandlerFunc(admin.HandleUserCreate) + h.Path("/admin/user/edit").Methods("GET", "POST").HandlerFunc(admin.HandleUserEdit) + h.Path("/admin/repos").Methods("GET").HandlerFunc(admin.HandleRepos) + h.Path("/admin/repo/edit").Methods("GET", "POST").HandlerFunc(admin.HandleRepoEdit) h.Path("/{repo:.+(?:\\.git)$}").Methods("GET").HandlerFunc(redirectDotGit) h.Path("/{repo}").Methods("GET").HandlerFunc(repo.HandleLog) diff --git a/src/repo/edit.go b/src/repo/edit.go index 2afa940..d423a30 100644 --- a/src/repo/edit.go +++ b/src/repo/edit.go @@ -106,6 +106,8 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) { 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 { diff --git a/src/user/edit.go b/src/user/edit.go index 1420054..7ef2739 100644 --- a/src/user/edit.go +++ b/src/user/edit.go @@ -56,7 +56,7 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) { } else if exists && data.Form.Name != user.Name { data.MessageA = "Username \"" + data.Form.Name + "\" is taken" } else if err := goit.UpdateUser(user.Id, goit.User{ - Name: data.Form.Name, FullName: data.Form.FullName, + Name: data.Form.Name, FullName: data.Form.FullName, IsAdmin: user.IsAdmin, }); err != nil { log.Println("[/user/edit]", err.Error()) goit.HttpError(w, http.StatusInternalServerError)