Goit

Simple and lightweight Git web server
Mirror of https://github.com/Jamozed/Goit
git clone http://git.omkov.net/Goit
Log | Tree | Refs | README | Download

AuthorJakob Wakeling <[email protected]>
Date2023-07-19 07:53:59
Commit1828e5fb2957c068734703ded8c0b6144b9bba3d
Parent25ea845fd12009e041cf228fe3225ef0c27f15f7

Enable Git receive-pack service

Diffstat

M main.go | 28 +++++++++++++---------------
M res/repo_log.html | 92 ++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
M res/style.css | 4 +++-
M src/admin.go | 14 +++++++-------
M src/git.go | 27 ++++++++++++++++++---------
M src/goit.go | 54 ++++++++++++++++++++++++++++++++++++++----------------
M src/repo.go | 19 ++++++++++---------
M src/user.go | 16 ++++++++--------
M src/util.go | 4 +++-

9 files changed, 153 insertions, 105 deletions

diff --git a/main.go b/main.go
index 0ef7922..63f947d 100644
--- a/main.go
+++ b/main.go
@@ -16,33 +16,30 @@ import (
 )
 
 func main() {
-	g, err := goit.InitGoit()
-	if err != nil {
+	if err := goit.InitGoit("./goit.json"); err != nil {
 		log.Fatalln("[InitGoit]", err.Error())
-	} else {
-		defer g.Close()
 	}
 
 	h := mux.NewRouter()
 	h.StrictSlash(true)
 
-	h.Path("/").HandlerFunc(g.HandleIndex)
-	h.Path("/user/login").Methods("GET", "POST").HandlerFunc(g.HandleUserLogin)
-	h.Path("/user/logout").Methods("GET", "POST").HandlerFunc(g.HandleUserLogout)
+	h.Path("/").HandlerFunc(goit.HandleIndex)
+	h.Path("/user/login").Methods("GET", "POST").HandlerFunc(goit.HandleUserLogin)
+	h.Path("/user/logout").Methods("GET", "POST").HandlerFunc(goit.HandleUserLogout)
 	// h.Path("/user/settings").Methods("GET").HandlerFunc()
-	h.Path("/repo/create").Methods("GET", "POST").HandlerFunc(g.HandleRepoCreate)
+	h.Path("/repo/create").Methods("GET", "POST").HandlerFunc(goit.HandleRepoCreate)
 	// h.Path("/repo/delete").Methods("POST").HandlerFunc()
 	// h.Path("/admin/settings").Methods("GET").HandlerFunc()
-	h.Path("/admin/user").Methods("GET").HandlerFunc(g.HandleAdminUserIndex)
+	h.Path("/admin/user").Methods("GET").HandlerFunc(goit.HandleAdminUserIndex)
 	// h.Path("/admin/repos").Methods("GET").HandlerFunc()
-	h.Path("/admin/user/create").Methods("GET", "POST").HandlerFunc(g.HandleAdminUserCreate)
+	h.Path("/admin/user/create").Methods("GET", "POST").HandlerFunc(goit.HandleAdminUserCreate)
 	// h.Path("/admin/user/edit").Methods("GET", "POST").HandlerFunc()
 
 	h.Path("/{repo:.+(?:\\.git)$}").Methods(http.MethodGet).HandlerFunc(redirectDotGit)
-	h.Path("/{repo}").Methods(http.MethodGet).HandlerFunc(g.HandleRepoLog)
-	h.Path("/{repo}/log").Methods(http.MethodGet).HandlerFunc(g.HandleRepoLog)
-	h.Path("/{repo}/tree").Methods(http.MethodGet).HandlerFunc(g.HandleRepoTree)
-	h.Path("/{repo}/refs").Methods(http.MethodGet).HandlerFunc(g.HandleRepoRefs)
+	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)
@@ -67,6 +64,7 @@ func main() {
 func logHttp(handler http.Handler) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		log.Println("[HTTP]", r.RemoteAddr, r.Method, r.URL.String())
+		// log.Println("[HTTP]", r.Header)
 		handler.ServeHTTP(w, r)
 	})
 }
@@ -74,7 +72,7 @@ func logHttp(handler http.Handler) http.Handler {
 func handleStyle(w http.ResponseWriter, r *http.Request) {
 	w.Header().Add("Content-Type", "text/css")
 	if _, err := w.Write([]byte(res.Style)); err != nil {
-		log.Println("[handleStyle]", err.Error())
+		log.Println("[Style]", err.Error())
 	}
 }
 
diff --git a/res/repo_log.html b/res/repo_log.html
index 8483e0b..999aa88 100644
--- a/res/repo_log.html
+++ b/res/repo_log.html
@@ -7,44 +7,57 @@
 	<link rel="icon" type="image/png" href="/static/favicon.png">
 </head>
 <body>
-	<table>
-		<tr><td><h1>{{.Name}}</h1></td></tr>
-		<tr><td><span>{{.Description}}</span></td></tr>
-		<tr><td>git clone <a href="{{.Url}}">{{.Url}}</a></td></tr>
-		<tr>
-			<td>
-				<a href="/{{.Name}}/log">Log</a>
-				| <a href="/{{.Name}}/tree">Tree</a>
-				| <a href="/{{.Name}}/refs">Refs</a>
-				{{if .HasReadme}}
-					| <a href="">README</a>
-				{{end}}
-				{{if .HasLicence}}
-					| <a href="">LICENCE</a>
-				{{end}}
-			</td>
-		</tr>
-	</table><hr>
-	{{if .Commits}}
+	<header>
 		<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>
-			{{end}}
-			</tbody>
+			<tr>
+				<td rowspan="2"><a href="/"><img style="max-height: 22px;" src=""></a></td>
+				<td><h1>{{.Name}}</h1></td>
+			</tr>
+			<tr>
+				<td>{{.Description}}</td>
+			</tr>
+			<tr>
+				<td></td>
+				<td>git clone <a href="{{.Url}}">{{.Url}}</a></td>
+			</tr>
+			<tr>
+				<td></td>
+				<td>
+					<a href="/{{.Name}}/log">Log</a>
+					| <a href="/{{.Name}}/tree">Tree</a>
+					| <a href="/{{.Name}}/refs">Refs</a>
+					{{if .HasReadme}}
+						| <a href="">README</a>
+					{{end}}
+					{{if .HasLicence}}
+						| <a href="">LICENCE</a>
+					{{end}}
+				</td>
+			</tr>
 		</table>
-	{{else}}
-		<span>No commits</span>
-	{{end}}
+	</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>
+				{{end}}
+				</tbody>
+			</table>
+		{{else}}
+			<span>No commits</span>
+		{{end}}
+	</main>
 </body>
diff --git a/res/style.css b/res/style.css
index efd7049..9f3f842 100644
--- a/res/style.css
+++ b/res/style.css
@@ -7,4 +7,6 @@ h1, h2 { font-size: 1em; margin: 0; }
 hr { border: 0; height: 1em; margin: 0; }
 
 table td { padding: 0 0.4em; }
-table tr:hover td { background-color: #222222; }
+table td:empty::after { content: "\00a0"; }
+
+main table tr:hover td { background-color: #222222; }
diff --git a/src/admin.go b/src/admin.go
index cb8f762..7d134cf 100644
--- a/src/admin.go
+++ b/src/admin.go
@@ -22,11 +22,11 @@ func init() {
 	adminUserIndex = template.Must(template.New("admin_user_index").Parse(res.AdminUserIndex))
 }
 
-func (g *Goit) HandleAdminUserIndex(w http.ResponseWriter, r *http.Request) {
+func HandleAdminUserIndex(w http.ResponseWriter, r *http.Request) {
 	if ok, uid := AuthHttp(r); !ok {
 		HttpError(w, http.StatusNotFound)
 		return
-	} else if user, err := g.GetUser(uid); err != nil {
+	} else if user, err := GetUser(uid); err != nil {
 		log.Println("[Admin:User:Create:Auth]", err.Error())
 		HttpError(w, http.StatusNotFound)
 		return
@@ -35,7 +35,7 @@ func (g *Goit) HandleAdminUserIndex(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if rows, err := g.db.Query("SELECT id, name, name_full, is_admin FROM users"); err != nil {
+	if rows, err := db.Query("SELECT id, name, name_full, is_admin FROM users"); err != nil {
 		log.Println("[Admin:User:Index:SELECT]", err.Error())
 		HttpError(w, http.StatusInternalServerError)
 	} else {
@@ -63,11 +63,11 @@ func (g *Goit) HandleAdminUserIndex(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-func (g *Goit) HandleAdminUserCreate(w http.ResponseWriter, r *http.Request) {
+func HandleAdminUserCreate(w http.ResponseWriter, r *http.Request) {
 	if ok, uid := AuthHttp(r); !ok {
 		HttpError(w, http.StatusNotFound)
 		return
-	} else if user, err := g.GetUser(uid); err != nil {
+	} else if user, err := GetUser(uid); err != nil {
 		log.Println("[Admin:User:Create:Auth]", err.Error())
 		HttpError(w, http.StatusNotFound)
 		return
@@ -88,7 +88,7 @@ func (g *Goit) HandleAdminUserCreate(w http.ResponseWriter, r *http.Request) {
 			data.Msg = "Username cannot be empty"
 		} else if SliceContains(reserved, username) {
 			data.Msg = "Username \"" + username + "\" is reserved"
-		} else if exists, err := g.UserExists(username); err != nil {
+		} else if exists, err := UserExists(username); err != nil {
 			log.Println("[Admin:User:Create:Exists]", err.Error())
 			HttpError(w, http.StatusInternalServerError)
 			return
@@ -98,7 +98,7 @@ func (g *Goit) HandleAdminUserCreate(w http.ResponseWriter, r *http.Request) {
 			log.Println("[Admin:User:Create:Salt]", err.Error())
 			HttpError(w, http.StatusInternalServerError)
 			return
-		} else if _, err := g.db.Exec(
+		} 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, admin,
 		); err != nil {
diff --git a/src/git.go b/src/git.go
index cd7dcbf..03dc2b2 100644
--- a/src/git.go
+++ b/src/git.go
@@ -27,6 +27,12 @@ type GitCommand struct {
 
 func HandleInfoRefs(w http.ResponseWriter, r *http.Request) {
 	service := r.FormValue("service")
+
+	if service == "git-upload-pack" && r.Header.Get("Git-Protocol") != "version=2" {
+		HttpError(w, http.StatusForbidden)
+		return
+	}
+
 	repo := httpBase(w, r, service)
 	if repo == nil {
 		return
@@ -56,6 +62,11 @@ func HandleInfoRefs(w http.ResponseWriter, r *http.Request) {
 }
 
 func HandleUploadPack(w http.ResponseWriter, r *http.Request) {
+	if r.Header.Get("Git-Protocol") != "version=2" {
+		HttpError(w, http.StatusForbidden)
+		return
+	}
+
 	repo := httpBase(w, r, "git-upload-pack")
 	if repo == nil {
 		return
@@ -87,11 +98,6 @@ func httpBase(w http.ResponseWriter, r *http.Request, service string) *Repo {
 		return nil
 	}
 
-	if r.Header.Get("Git-Protocol") != "version=2" {
-		HttpError(w, http.StatusForbidden)
-		return nil
-	}
-
 	repo, err := GetRepoByName(db, reponame)
 	if err != nil {
 		HttpError(w, http.StatusInternalServerError)
@@ -104,8 +110,8 @@ func httpBase(w http.ResponseWriter, r *http.Request, service string) *Repo {
 	/* Require authentication other than for public pull */
 	if repo.IsPrivate || !isPull {
 		/* TODO authentcate */
-		HttpError(w, http.StatusUnauthorized)
-		return nil
+		// HttpError(w, http.StatusUnauthorized)
+		// return nil
 	}
 
 	return repo
@@ -137,9 +143,12 @@ func serviceRPC(w http.ResponseWriter, r *http.Request, service string, repo *Re
 
 	c := NewCommand(strings.TrimPrefix(service, "git-"), "--stateless-rpc", ".")
 	c.AddEnv(os.Environ()...)
-	c.AddEnv("GIT_PROTOCOL=version=2")
 	c.dir = "./" + repo.Name + ".git"
 
+	if p := r.Header.Get("Git-Protocol"); p == "version=2" {
+		c.AddEnv("GIT_PROTOCOL=version=2")
+	}
+
 	w.Header().Add("Content-Type", "application/x-"+service+"-result")
 	w.WriteHeader(http.StatusOK)
 
diff --git a/src/goit.go b/src/goit.go
index 7c40cdd..7553fc4 100644
--- a/src/goit.go
+++ b/src/goit.go
@@ -6,28 +6,49 @@ package goit
 
 import (
 	"database/sql"
+	"encoding/json"
+	"errors"
 	"fmt"
 	"log"
+	"os"
+	"path"
 
 	_ "github.com/mattn/go-sqlite3"
 )
 
-var db *sql.DB
+type Config struct {
+	DataPath string `json:"data_path"`
+	HttpAddr string `json:"http_addr"`
+	HttpPort string `json:"http_port"`
+	GitPath  string `json:"git_path"`
+}
 
-type Goit struct {
-	db *sql.DB
+var config = Config{
+	DataPath: ".",
+	HttpAddr: "",
+	HttpPort: "8080",
+	GitPath:  "git",
 }
 
+var db *sql.DB
+
 /* Initialise Goit. */
-func InitGoit() (g *Goit, err error) {
-	g = &Goit{}
+func InitGoit(conf string) (err error) {
+	if dat, err := os.ReadFile(conf); err != nil {
+		if !errors.Is(err, os.ErrNotExist) {
+			return fmt.Errorf("[Config] %w", err)
+		}
+	} else if dat != nil {
+		if json.Unmarshal(dat, &config); err != nil {
+			return fmt.Errorf("[Config] %w", err)
+		}
+	}
 
-	if g.db, err = sql.Open("sqlite3", "./goit.db"); err != nil {
-		return nil, fmt.Errorf("[SQL:open] %w", err)
+	if db, err = sql.Open("sqlite3", path.Join(config.DataPath, "goit.db")); err != nil {
+		return fmt.Errorf("[Database] %w", err)
 	}
-	db = g.db
 
-	if _, err = g.db.Exec(
+	if _, err = db.Exec(
 		`CREATE TABLE IF NOT EXISTS users (
 			id INTEGER PRIMARY KEY AUTOINCREMENT,
 			name TEXT UNIQUE NOT NULL,
@@ -38,10 +59,10 @@ func InitGoit() (g *Goit, err error) {
 			is_admin BOOLEAN NOT NULL
 		)`,
 	); err != nil {
-		return nil, fmt.Errorf("[CREATE:users] %w", err)
+		return fmt.Errorf("[CREATE:users] %w", err)
 	}
 
-	if _, err = g.db.Exec(
+	if _, err = db.Exec(
 		`CREATE TABLE IF NOT EXISTS repos (
 			id INTEGER PRIMARY KEY AUTOINCREMENT,
 			owner_id INTEGER NOT NULL,
@@ -52,18 +73,18 @@ func InitGoit() (g *Goit, err error) {
 			is_private BOOLEAN NOT NULL
 		)`,
 	); err != nil {
-		return nil, fmt.Errorf("[CREATE:repos] %w", err)
+		return fmt.Errorf("[CREATE:repos] %w", err)
 	}
 
 	/* Create an admin user if one does not exist */
-	if exists, err := g.UserExists("admin"); err != nil {
+	if exists, err := UserExists("admin"); err != nil {
 		log.Println("[admin:Exists]", err.Error())
 		err = nil /* ignored */
 	} else if !exists {
 		if salt, err := Salt(); err != nil {
 			log.Println("[admin:Salt]", err.Error())
 			err = nil /* ignored */
-		} else if _, err = g.db.Exec(
+		} else if _, err = db.Exec(
 			"INSERT INTO users (id, name, name_full, pass, pass_algo, salt, is_admin) VALUES (?, ?, ?, ?, ?, ?, ?)",
 			0, "admin", "Administrator", Hash("admin", salt), "argon2", salt, true,
 		); err != nil {
@@ -72,9 +93,9 @@ func InitGoit() (g *Goit, err error) {
 		}
 	}
 
-	return g, nil
+	return nil
 }
 
-func (g *Goit) Close() error {
-	return g.db.Close()
+func GetRepoPath(username, reponame string) string {
+	return path.Join(config.DataPath, "repos", username, reponame+".git")
 }
diff --git a/src/repo.go b/src/repo.go
index ec35f3f..400e9d2 100644
--- a/src/repo.go
+++ b/src/repo.go
@@ -38,10 +38,10 @@ var (
 	repoRefs   *template.Template = template.Must(template.New("repo_refs").Parse(res.RepoRefs))
 )
 
-func (g *Goit) HandleIndex(w http.ResponseWriter, r *http.Request) {
+func HandleIndex(w http.ResponseWriter, r *http.Request) {
 	authOk, uid := AuthHttp(r)
 
-	if rows, err := g.db.Query("SELECT id, owner_id, name, description, is_private FROM repos"); err != nil {
+	if rows, err := db.Query("SELECT id, owner_id, name, description, is_private FROM repos"); err != nil {
 		log.Println("[Index:SELECT]", err.Error())
 		HttpError(w, http.StatusInternalServerError)
 	} else {
@@ -56,7 +56,7 @@ func (g *Goit) HandleIndex(w http.ResponseWriter, r *http.Request) {
 			if err := rows.Scan(&r.Id, &r.OwnerId, &r.Name, &r.Description, &r.IsPrivate); err != nil {
 				log.Println("[Index:SELECT:Scan]", err.Error())
 			} else if !r.IsPrivate || (authOk && uid == r.OwnerId) {
-				owner, err := g.GetUser(r.OwnerId)
+				owner, err := GetUser(r.OwnerId)
 				if err != nil {
 					log.Println("[Index:SELECT:UserName]", err.Error())
 				}
@@ -74,14 +74,14 @@ func (g *Goit) HandleIndex(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-func (g *Goit) HandleRepoCreate(w http.ResponseWriter, r *http.Request) {
+func HandleRepoCreate(w http.ResponseWriter, r *http.Request) {
 	if ok, uid := AuthHttp(r); !ok {
 		HttpError(w, http.StatusUnauthorized)
 	} else if r.Method == http.MethodPost {
 		name := r.FormValue("reponame")
 		private := r.FormValue("visibility") == "private"
 
-		if taken, err := RepoExists(g.db, name); err != nil {
+		if taken, err := RepoExists(db, name); err != nil {
 			log.Println("[RepoCreate:RepoExists]", err.Error())
 			HttpError(w, http.StatusInternalServerError)
 		} else if taken {
@@ -89,7 +89,7 @@ func (g *Goit) HandleRepoCreate(w http.ResponseWriter, r *http.Request) {
 		} else if SliceContains[string](reserved, name) {
 			repoCreate.Execute(w, struct{ Msg string }{"Reponame is reserved"})
 		} else {
-			if _, err := g.db.Exec(
+			if _, err := db.Exec(
 				`INSERT INTO repos (
 					owner_id, name, name_lower, description, default_branch, is_private
 				) VALUES (?, ?, ?, ?, ?, ?)`,
@@ -106,7 +106,7 @@ func (g *Goit) HandleRepoCreate(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-func (g *Goit) HandleRepoLog(w http.ResponseWriter, r *http.Request) {
+func HandleRepoLog(w http.ResponseWriter, r *http.Request) {
 	reponame := mux.Vars(r)["repo"]
 
 	repo, err := GetRepoByName(db, reponame)
@@ -153,11 +153,11 @@ func (g *Goit) HandleRepoLog(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
-func (g *Goit) HandleRepoTree(w http.ResponseWriter, r *http.Request) {
+func HandleRepoTree(w http.ResponseWriter, r *http.Request) {
 	HttpError(w, http.StatusNoContent)
 }
 
-func (g *Goit) HandleRepoRefs(w http.ResponseWriter, r *http.Request) {
+func HandleRepoRefs(w http.ResponseWriter, r *http.Request) {
 	reponame := mux.Vars(r)["repo"]
 
 	repo, err := GetRepoByName(db, reponame)
diff --git a/src/user.go b/src/user.go
index 9b2617c..237f942 100644
--- a/src/user.go
+++ b/src/user.go
@@ -35,7 +35,7 @@ var (
 	userCreate *template.Template = template.Must(template.New("user_create").Parse(res.UserCreate))
 )
 
-func (g *Goit) HandleUserLogin(w http.ResponseWriter, r *http.Request) {
+func HandleUserLogin(w http.ResponseWriter, r *http.Request) {
 	if ok, _ := AuthHttp(r); ok {
 		http.Redirect(w, r, "/", http.StatusFound)
 		return
@@ -50,13 +50,13 @@ func (g *Goit) HandleUserLogin(w http.ResponseWriter, r *http.Request) {
 
 		if username == "" {
 			data.Msg = "Username cannot be empty"
-		} else if exists, err := g.UserExists(username); err != nil {
+		} else if exists, err := UserExists(username); err != nil {
 			log.Println("[User:Login:Exists]", err.Error())
 			HttpError(w, http.StatusInternalServerError)
 			return
 		} else if !exists {
 			data.Msg = "Invalid credentials"
-		} else if err := g.db.QueryRow(
+		} else if err := db.QueryRow(
 			"SELECT id, name, pass, pass_algo, salt FROM users WHERE name = ?", username,
 		).Scan(&u.Id, &u.Name, &u.Pass, &u.PassAlgo, &u.Salt); err != nil {
 			log.Println("[User:Login:SELECT]", err.Error())
@@ -81,16 +81,16 @@ func (g *Goit) HandleUserLogin(w http.ResponseWriter, r *http.Request) {
 	userLogin.Execute(w, data)
 }
 
-func (g *Goit) HandleUserLogout(w http.ResponseWriter, r *http.Request) {
+func HandleUserLogout(w http.ResponseWriter, r *http.Request) {
 	EndSession(SessionCookie(r))
 	http.SetCookie(w, &http.Cookie{Name: "session", Path: "/", MaxAge: -1})
 	http.Redirect(w, r, "/", http.StatusFound)
 }
 
-func (g *Goit) GetUser(id uint64) (*User, error) {
+func GetUser(id uint64) (*User, error) {
 	u := User{}
 
-	if err := g.db.QueryRow(
+	if err := db.QueryRow(
 		"SELECT id, name, name_full, is_admin FROM users WHERE id = ?", id,
 	).Scan(&u.Id, &u.Name, &u.NameFull, &u.IsAdmin); err != nil {
 		if !errors.Is(err, sql.ErrNoRows) {
@@ -103,8 +103,8 @@ func (g *Goit) GetUser(id uint64) (*User, error) {
 	}
 }
 
-func (g *Goit) UserExists(name string) (bool, error) {
-	if err := g.db.QueryRow("SELECT name FROM users WHERE name = ?", strings.ToLower(name)).Scan(&name); err != nil {
+func UserExists(name string) (bool, error) {
+	if err := db.QueryRow("SELECT name FROM users WHERE name = ?", strings.ToLower(name)).Scan(&name); err != nil {
 		if !errors.Is(err, sql.ErrNoRows) {
 			return false, err
 		} else {
diff --git a/src/util.go b/src/util.go
index a273c72..3cd9efc 100644
--- a/src/util.go
+++ b/src/util.go
@@ -4,7 +4,9 @@
 
 package goit
 
-import "net/http"
+import (
+	"net/http"
+)
 
 func If[T any](cond bool, a, b T) T {
 	if cond {