Goit

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

AuthorJakob Wakeling <[email protected]>
Date2023-07-24 09:56:07
Commit64718e1cdc44c70854777e66c08401558ff0999c
Parent583a0660cb480978c9a329575d7ee8f78504f647

Refine repository log page

Diffstat

M main.go | 22 ++++++++++++----------
M res/admin/index.html | 2 +-
M res/repo/log.html | 48 +++++++++++++++++++++++++++---------------------
M src/admin.go | 12 ++++++------
M src/goit.go | 2 ++
M src/http.go | 36 ++++++++++++++++++------------------
M src/repo.go | 87 ++++++++++++++++++++++---------------------------------------------------------
A src/repo/log.go | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A src/repo/repo.go | 39 +++++++++++++++++++++++++++++++++++++++
M src/user.go | 4 ++--

10 files changed, 233 insertions, 120 deletions

diff --git a/main.go b/main.go
index 4783b59..9e714e5 100644
--- a/main.go
+++ b/main.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/Jamozed/Goit/res"
 	goit "github.com/Jamozed/Goit/src"
+	"github.com/Jamozed/Goit/src/repo"
 	"github.com/gorilla/mux"
 )
 
@@ -29,6 +30,7 @@ func main() {
 	h.Path("/user/logout").Methods("GET", "POST").HandlerFunc(goit.HandleUserLogout)
 	h.Path("/user/sessions").Methods("GET", "POST").HandlerFunc(goit.HandleUserSessions)
 	h.Path("/repo/create").Methods("GET", "POST").HandlerFunc(goit.HandleRepoCreate)
+	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)
@@ -36,17 +38,17 @@ func main() {
 	h.Path("/admin/repos").Methods("GET").HandlerFunc(goit.HandleAdminRepos)
 	h.Path("/admin/repo/edit").Methods("GET", "POST").HandlerFunc(goit.HandleAdminRepoEdit)
 
-	h.Path("/{repo:.+(?:\\.git)$}").Methods(http.MethodGet).HandlerFunc(redirectDotGit)
-	h.Path("/{repo}").Methods(http.MethodGet).HandlerFunc(goit.HandleRepoLog)
-	h.Path("/{repo}/log").Methods(http.MethodGet).HandlerFunc(goit.HandleRepoLog)
-	h.Path("/{repo}/tree").Methods(http.MethodGet).HandlerFunc(goit.HandleRepoTree)
-	h.Path("/{repo}/refs").Methods(http.MethodGet).HandlerFunc(goit.HandleRepoRefs)
-	h.Path("/{repo}/info/refs").Methods(http.MethodGet).HandlerFunc(goit.HandleInfoRefs)
-	h.Path("/{repo}/git-upload-pack").Methods(http.MethodPost).HandlerFunc(goit.HandleUploadPack)
-	h.Path("/{repo}/git-receive-pack").Methods(http.MethodPost).HandlerFunc(goit.HandleReceivePack)
+	h.Path("/{repo:.+(?:\\.git)$}").Methods("GET").HandlerFunc(redirectDotGit)
+	h.Path("/{repo}").Methods("GET").HandlerFunc(repo.HandleLog)
+	h.Path("/{repo}/log").Methods("GET").HandlerFunc(repo.HandleLog)
+	h.Path("/{repo}/tree").Methods("GET").HandlerFunc(goit.HandleRepoTree)
+	h.Path("/{repo}/refs").Methods("GET").HandlerFunc(goit.HandleRepoRefs)
+	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)
 
-	h.Path("/static/style.css").Methods(http.MethodGet).HandlerFunc(handleStyle)
-	h.Path("/static/favicon.png").Methods(http.MethodGet).HandlerFunc(handleFavicon)
+	h.Path("/static/style.css").Methods("GET").HandlerFunc(handleStyle)
+	h.Path("/static/favicon.png").Methods("GET").HandlerFunc(handleFavicon)
 
 	h.PathPrefix("/").HandlerFunc(goit.HttpNotFound)
 
diff --git a/res/admin/index.html b/res/admin/index.html
index 257aed7..c2f95de 100644
--- a/res/admin/index.html
+++ b/res/admin/index.html
@@ -10,7 +10,7 @@
 		</tr>
 		<tr><td>
 			<a href="/admin/users">Users</a>
-			| <a href="/admin/repos">Repos</a>
+			| <a href="/admin/repos">Repositories</a>
 		</td></tr>
 	</table>
 </body>
diff --git a/res/repo/log.html b/res/repo/log.html
index 155a31b..6dad3e7 100644
--- a/res/repo/log.html
+++ b/res/repo/log.html
@@ -3,27 +3,33 @@
 <body>
 	<header>{{template "repo/header" .}}</header><hr>
 	<main>
-		{{if .Commits}}
-			<table>
-				<thead>
-					<tr>
-						<td><b>Date</b></td>
-						<td><b>Message</b></td>
-						<td><b>Author</b></td>
-					</tr>
-				</thead>
-				<tbody>
-				{{range .Commits}}
-					<tr>
-						<td>{{.Date}}</a></td>
-						<td>{{.Message}}</td>
-						<td>{{.Author}}</td>
-					</tr>
+		<table>
+			<thead>
+				<tr>
+					<td><b>Date</b></td>
+					<td><b>Message</b></td>
+					<td><b>Author</b></td>
+					<td><b>Files</b></td>
+					<td><b>+</b></td>
+					<td><b>-</b></td>
+				</tr>
+			</thead>
+			<tbody>
+				{{if .Commits}}
+					{{range .Commits}}
+						<tr>
+							<td>{{.Date}}</a></td>
+							<td><a href="/{{$.Name}}/commit/{{.Hash}}">{{.Message}}</a></td>
+							<td>{{.Author}}</td>
+							<td align="right">{{.Files}}</td>
+							<td align="right" style="color: #008800">{{.Additions}}</td>
+							<td align="right" style="color: #AA0000">{{.Deletions}}</td>
+						</tr>
+					{{end}}
+				{{else}}
+					<tr><td colspan="6">No commits</td></tr>
 				{{end}}
-				</tbody>
-			</table>
-		{{else}}
-			<span>No commits</span>
-		{{end}}
+			</tbody>
+		</table>
 	</main>
 </body>
diff --git a/src/admin.go b/src/admin.go
index 9b98ca7..3af6822 100644
--- a/src/admin.go
+++ b/src/admin.go
@@ -21,7 +21,7 @@ func HandleAdminIndex(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if err := tmpl.ExecuteTemplate(w, "admin/index", struct{ Title string }{"Admin"}); err != nil {
+	if err := Tmpl.ExecuteTemplate(w, "admin/index", struct{ Title string }{"Admin"}); err != nil {
 		log.Println("[/admin/index]", err.Error())
 	}
 }
@@ -66,7 +66,7 @@ func HandleAdminUsers(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if err := tmpl.ExecuteTemplate(w, "admin/users", data); err != nil {
+	if err := Tmpl.ExecuteTemplate(w, "admin/users", data); err != nil {
 		log.Println("[/admin/users]", err.Error())
 	}
 }
@@ -111,7 +111,7 @@ func HandleAdminUserCreate(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	if err := tmpl.ExecuteTemplate(w, "admin/user/create", data); err != nil {
+	if err := Tmpl.ExecuteTemplate(w, "admin/user/create", data); err != nil {
 		log.Println("[/admin/user/create]", err.Error())
 	}
 }
@@ -189,7 +189,7 @@ func HandleAdminUserEdit(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	if err := tmpl.ExecuteTemplate(w, "admin/user/edit", data); err != nil {
+	if err := Tmpl.ExecuteTemplate(w, "admin/user/edit", data); err != nil {
 		log.Println("[/admin/user/edit]", err.Error())
 	}
 }
@@ -244,7 +244,7 @@ func HandleAdminRepos(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if err := tmpl.ExecuteTemplate(w, "admin/repos", data); err != nil {
+	if err := Tmpl.ExecuteTemplate(w, "admin/repos", data); err != nil {
 		log.Println("[/admin/repos]", err.Error())
 	}
 }
@@ -314,7 +314,7 @@ func HandleAdminRepoEdit(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	if err := tmpl.ExecuteTemplate(w, "admin/repo/edit", data); err != nil {
+	if err := Tmpl.ExecuteTemplate(w, "admin/repo/edit", data); err != nil {
 		log.Println("[/admin/repo/edit]", err.Error())
 	}
 }
diff --git a/src/goit.go b/src/goit.go
index b71c9a1..f9ff03d 100644
--- a/src/goit.go
+++ b/src/goit.go
@@ -23,6 +23,7 @@ type Config struct {
 	HttpPort   string `json:"http_port"`
 	GitPath    string `json:"git_path"`
 	IpSessions bool   `json:"ip_sessions"`
+	UsesHttps  bool   `json:"uses_https"`
 }
 
 var Conf = Config{
@@ -31,6 +32,7 @@ var Conf = Config{
 	HttpPort:   "8080",
 	GitPath:    "git",
 	IpSessions: true,
+	UsesHttps:  false,
 }
 
 var db *sql.DB
diff --git a/src/http.go b/src/http.go
index 4fc6e78..76722f5 100644
--- a/src/http.go
+++ b/src/http.go
@@ -12,35 +12,35 @@ import (
 	"github.com/Jamozed/Goit/res"
 )
 
-var tmpl = template.Must(template.New("error").Parse(res.Error))
+var Tmpl = template.Must(template.New("error").Parse(res.Error))
 
 func init() {
-	template.Must(tmpl.New("index").Parse(res.Index))
-	template.Must(tmpl.New("base/head").Parse(res.BaseHead))
-
-	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))
-	template.Must(tmpl.New("admin/user/edit").Parse(res.AdminUserEdit))
-	template.Must(tmpl.New("admin/repos").Parse(res.AdminRepos))
-	template.Must(tmpl.New("admin/repo/edit").Parse(res.AdminRepoEdit))
-
-	template.Must(tmpl.New("user/header").Parse(res.UserHeader))
-	template.Must(tmpl.New("user/login").Parse(res.UserLogin))
-	template.Must(tmpl.New("user/sessions").Parse(res.UserSessions))
-
-	template.Must(tmpl.New("repo/header").Parse(res.RepoHeader))
-	template.Must(tmpl.New("repo/create").Parse(res.RepoCreate))
-
-	template.Must(tmpl.New("repo/log").Parse(res.RepoLog))
-	template.Must(tmpl.New("repo/tree").Parse(res.RepoTree))
-	template.Must(tmpl.New("repo/refs").Parse(res.RepoRefs))
+	template.Must(Tmpl.New("index").Parse(res.Index))
+	template.Must(Tmpl.New("base/head").Parse(res.BaseHead))
+
+	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))
+	template.Must(Tmpl.New("admin/user/edit").Parse(res.AdminUserEdit))
+	template.Must(Tmpl.New("admin/repos").Parse(res.AdminRepos))
+	template.Must(Tmpl.New("admin/repo/edit").Parse(res.AdminRepoEdit))
+
+	template.Must(Tmpl.New("user/header").Parse(res.UserHeader))
+	template.Must(Tmpl.New("user/login").Parse(res.UserLogin))
+	template.Must(Tmpl.New("user/sessions").Parse(res.UserSessions))
+
+	template.Must(Tmpl.New("repo/header").Parse(res.RepoHeader))
+	template.Must(Tmpl.New("repo/create").Parse(res.RepoCreate))
+
+	template.Must(Tmpl.New("repo/log").Parse(res.RepoLog))
+	template.Must(Tmpl.New("repo/tree").Parse(res.RepoTree))
+	template.Must(Tmpl.New("repo/refs").Parse(res.RepoRefs))
 }
 
 func HttpError(w http.ResponseWriter, code int) {
 	w.WriteHeader(code)
 	s := fmt.Sprint(code) + " " + http.StatusText(code)
-	tmpl.ExecuteTemplate(w, "error", struct{ Status string }{s})
+	Tmpl.ExecuteTemplate(w, "error", struct{ Status string }{s})
 }
 
 func HttpNotFound(w http.ResponseWriter, r *http.Request) {
diff --git a/src/repo.go b/src/repo.go
index 67b5a03..28c6169 100644
--- a/src/repo.go
+++ b/src/repo.go
@@ -12,12 +12,10 @@ import (
 	"os"
 	"path/filepath"
 	"strings"
-	"time"
 
 	"github.com/Jamozed/Goit/src/util"
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/plumbing"
-	"github.com/go-git/go-git/v5/plumbing/object"
 	"github.com/gorilla/mux"
 )
 
@@ -79,7 +77,7 @@ func HandleIndex(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 
-		if err := tmpl.ExecuteTemplate(w, "index", data); err != nil {
+		if err := Tmpl.ExecuteTemplate(w, "index", data); err != nil {
 			log.Println("[/]", err.Error())
 		}
 	}
@@ -123,60 +121,11 @@ func HandleRepoCreate(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	if err := tmpl.ExecuteTemplate(w, "repo/create", data); err != nil {
+	if err := Tmpl.ExecuteTemplate(w, "repo/create", data); err != nil {
 		log.Println("[/repo/create]", err.Error())
 	}
 }
 
-func HandleRepoLog(w http.ResponseWriter, r *http.Request) {
-	reponame := mux.Vars(r)["repo"]
-
-	repo, err := GetRepoByName(reponame)
-	if err != nil {
-		HttpError(w, http.StatusInternalServerError)
-		return
-	} else if repo == nil {
-		HttpError(w, http.StatusNotFound)
-		return
-	}
-
-	type row struct{ Date, Message, Author string }
-	commits := []row{}
-
-	if gr, err := git.PlainOpen(RepoPath(reponame)); err != nil {
-		log.Println("[Repo:Log]", err.Error())
-		HttpError(w, http.StatusInternalServerError)
-		return
-	} else if ref, err := gr.Head(); err != nil {
-		if !errors.Is(err, plumbing.ErrReferenceNotFound) {
-			log.Println("[Repo:Log]", err.Error())
-			HttpError(w, http.StatusInternalServerError)
-			return
-		}
-	} else if iter, err := gr.Log(&git.LogOptions{From: ref.Hash()}); err != nil {
-		log.Println("[Repo:Log]", err.Error())
-		HttpError(w, http.StatusInternalServerError)
-		return
-	} else if err := iter.ForEach(func(c *object.Commit) error {
-		commits = append(commits, row{c.Author.When.UTC().Format(time.DateTime), strings.SplitN(c.Message, "\n", 2)[0], c.Author.Name})
-		return nil
-	}); err != nil {
-		log.Println("[Repo:Log]", err.Error())
-		HttpError(w, http.StatusInternalServerError)
-		return
-	}
-
-	if err := tmpl.ExecuteTemplate(w, "repo/log", struct {
-		Title, Name, Description, Url string
-		Readme, Licence               string
-		Commits                       []row
-	}{
-		"Log", reponame, repo.Description, r.URL.Host + "/" + repo.Name + ".git", "", "", commits,
-	}); err != nil {
-		log.Println("[Repo:Log]", err.Error())
-	}
-}
-
 func HandleRepoTree(w http.ResponseWriter, r *http.Request) {
 	HttpError(w, http.StatusNoContent)
 }
@@ -226,13 +175,14 @@ func HandleRepoRefs(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if err := tmpl.ExecuteTemplate(w, "repo/refs", struct {
+	if err := Tmpl.ExecuteTemplate(w, "repo/refs", struct {
 		Title, Name, Description, Url string
 		Readme, Licence               string
 		Branches                      []bra
 		Tags                          []tag
 	}{
-		"Refs", reponame, repo.Description, r.URL.Host + "/" + repo.Name + ".git", "", "", bras, tags,
+		"Refs", reponame, repo.Description, util.If(Conf.UsesHttps, "https://", "http://") + r.Host + r.URL.Path, "",
+		"", bras, tags,
 	}); err != nil {
 		log.Println("[Repo:Refs]", err.Error())
 	}
@@ -246,9 +196,9 @@ func GetRepo(id int64) (*Repo, error) {
 	).Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.DefaultBranch, &r.IsPrivate); err != nil {
 		if !errors.Is(err, sql.ErrNoRows) {
 			return nil, err
-		} else {
-			return nil, nil
 		}
+
+		return nil, nil
 	} else {
 		return r, nil
 	}
@@ -257,13 +207,14 @@ func GetRepo(id int64) (*Repo, error) {
 func GetRepoByName(name string) (*Repo, error) {
 	r := &Repo{}
 
-	err := db.QueryRow(
+	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)
-	if errors.Is(err, sql.ErrNoRows) {
+	).Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.DefaultBranch, &r.IsPrivate); err != nil {
+		if !errors.Is(err, sql.ErrNoRows) {
+			return nil, err
+		}
+
 		return nil, nil
-	} else if err != nil {
-		return nil, err
 	}
 
 	return r, nil
@@ -314,3 +265,15 @@ func RepoSize(name string) (uint64, error) {
 
 	return uint64(size), err
 }
+
+func DelRepo(name string) error {
+	if err := os.RemoveAll(RepoPath(name)); err != nil {
+		return err
+	}
+
+	if _, err := db.Exec("DELETE FROM repos WHERE name = ?", name); err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/src/repo/log.go b/src/repo/log.go
new file mode 100644
index 0000000..d2a98a1
--- /dev/null
+++ b/src/repo/log.go
@@ -0,0 +1,101 @@
+package repo
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+	"strconv"
+	"strings"
+	"time"
+
+	goit "github.com/Jamozed/Goit/src"
+	"github.com/Jamozed/Goit/src/util"
+	"github.com/go-git/go-git/v5"
+	"github.com/go-git/go-git/v5/plumbing/object"
+	"github.com/gorilla/mux"
+)
+
+func HandleLog(w http.ResponseWriter, r *http.Request) {
+	_, uid := goit.AuthCookie(w, r, true)
+
+	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) {
+		goit.HttpError(w, http.StatusNotFound)
+		return
+	}
+
+	var offset uint64 = 0
+	if o := r.URL.Query().Get("o"); o != "" {
+		if i, err := strconv.ParseUint(o, 10, 64); err != nil {
+			goit.HttpError(w, http.StatusBadRequest)
+			return
+		} else {
+			offset = i
+		}
+	}
+
+	offset += 1
+
+	type row struct{ Hash, Date, Message, Author, Files, Additions, Deletions string }
+	data := struct {
+		Title, Name, Description, Url string
+		Readme, Licence               string
+		Commits                       []row
+	}{
+		Title: repo.Name + " - Log", Name: repo.Name,
+		Description: repo.Description, Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + r.URL.Path,
+	}
+
+	gr, err := git.PlainOpen(goit.RepoPath(repo.Name))
+	if err != nil {
+		log.Println("[/repo/log]", err.Error())
+		goit.HttpError(w, http.StatusInternalServerError)
+		return
+	}
+
+	ref, err := gr.Head()
+	if err != nil {
+		log.Println("[/repo/log]", err.Error())
+		goit.HttpError(w, http.StatusInternalServerError)
+		return
+	}
+
+	iter, err := gr.Log(&git.LogOptions{From: ref.Hash()})
+	if err != nil {
+		log.Println("[/repo/log]", err.Error())
+		goit.HttpError(w, http.StatusInternalServerError)
+		return
+	}
+
+	if err := iter.ForEach(func(c *object.Commit) error {
+		var files, additions, deletions int
+
+		if stats, err := c.Stats(); err != nil {
+			log.Println("[/repo/log]", err.Error())
+		} else {
+			files = len(stats)
+			for _, s := range stats {
+				additions += s.Addition
+				deletions += s.Deletion
+			}
+		}
+
+		data.Commits = append(data.Commits, row{
+			c.Hash.String(), c.Author.When.UTC().Format(time.DateTime), strings.SplitN(c.Message, "\n", 2)[0],
+			c.Author.Name, fmt.Sprint(files), "+" + fmt.Sprint(additions), "-" + fmt.Sprint(deletions),
+		})
+
+		return nil
+	}); err != nil {
+		log.Println("[/repo/log]", err.Error())
+		goit.HttpError(w, http.StatusInternalServerError)
+		return
+	}
+
+	if err := goit.Tmpl.ExecuteTemplate(w, "repo/log", data); err != nil {
+		log.Println("[/repo/log]", err.Error())
+	}
+}
diff --git a/src/repo/repo.go b/src/repo/repo.go
new file mode 100644
index 0000000..3299296
--- /dev/null
+++ b/src/repo/repo.go
@@ -0,0 +1,39 @@
+package repo
+
+import (
+	"log"
+	"net/http"
+	"strconv"
+
+	goit "github.com/Jamozed/Goit/src"
+)
+
+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)
+}
diff --git a/src/user.go b/src/user.go
index fc967c8..4d0ee24 100644
--- a/src/user.go
+++ b/src/user.go
@@ -73,7 +73,7 @@ func HandleUserLogin(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	if err := tmpl.ExecuteTemplate(w, "user/login", data); err != nil {
+	if err := Tmpl.ExecuteTemplate(w, "user/login", data); err != nil {
 		log.Println("[/user/login]", err.Error())
 	}
 }
@@ -107,7 +107,7 @@ func HandleUserSessions(w http.ResponseWriter, r *http.Request) {
 		})
 	}
 
-	if err := tmpl.ExecuteTemplate(w, "user/sessions", data); err != nil {
+	if err := Tmpl.ExecuteTemplate(w, "user/sessions", data); err != nil {
 		log.Println("[/user/login]", err.Error())
 	}
 }