Implement initial database setup and web routing
DDavid committed on 2026-06-27 23:45 commit 6209d88
.gitignore +2 −1
.env*
-docs+docs
+app.db
cmd/server/main.go +23 −3
-package server
+package main
-import "fmt"
+import (
+ "gitgud/internal/infra/config"
+ "gitgud/internal/infra/persistence/sqlite"
+ "gitgud/internal/interface/web"
+ "log"
+ "net/http"
+)
func main() {
- fmt.Println("sup, nerds")
+ cfg, err := config.Load()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ db, err := sqlite.Open(cfg.DBPath())
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer db.Close()
+
+ handler := web.NewRouter(cfg)
+
+ log.Printf("listening on %s", cfg.Addr)
+ log.Fatal(http.ListenAndServe(cfg.Addr, handler))
}
go.mod +6 −1
module gitgud
go 1.25.0
-require github.com/joho/godotenv v1.5.1
+require (
+ github.com/go-chi/chi v1.5.5
+ github.com/joho/godotenv v1.5.1
+)
+
+require github.com/mattn/go-sqlite3 v1.14.47
go.sum +4 −0
+github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE=
+github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/mattn/go-sqlite3 v1.14.47 h1:jOBI62gS7nKeZv+as1oGEy0+1qISgXwH/QBlR6KbfIo=
+github.com/mattn/go-sqlite3 v1.14.47/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w=
internal/infra/persistence/sqlite/db.go +44 −0
+package sqlite
+
+import (
+ "database/sql"
+ "embed"
+
+ _ "github.com/mattn/go-sqlite3"
+)
+
+var migrationsFS embed.FS
+
+func Open(dbPath string) (*sql.DB, error) {
+ db, err := sql.Open("sqlite3", dbPath+"?_pragma=foreign_keys(1)&_pragma=busy_timeout(5000)")
+ if err != nil {
+ return nil, err
+ }
+ if err := db.Ping(); err != nil {
+ return nil, err
+ }
+ if err := migrate(db); err != nil {
+ return nil, err
+ }
+ return db, nil
+}
+
+func migrate(db *sql.DB) error {
+ migrations, err := migrationsFS.ReadDir(".")
+ if err != nil {
+ return err
+ }
+ for _, migration := range migrations {
+ if !migration.IsDir() {
+ content, err := migrationsFS.ReadFile(migration.Name())
+ if err != nil {
+ return err
+ }
+ _, err = db.Exec(string(content))
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
internal/infra/persistence/sqlite/migrations/0001_init.sql +60 −0
+CREATE TABLE users (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ username TEXT NOT NULL UNIQUE,
+ email TEXT NOT NULL,
+ password_hash TEXT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE repositories (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ owner_id INTEGER NOT NULL REFERENCES users(id),
+ name TEXT NOT NULL,
+ description TEXT NOT NULL DEFAULT '',
+ is_private INTEGER NOT NULL DEFAULT 0,
+ default_branch TEXT NOT NULL DEFAULT 'main',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE(owner_id, name)
+);
+
+CREATE TABLE issues (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ repo_id INTEGER NOT NULL REFERENCES repositories(id),
+ number INTEGER NOT NULL, -- per-repo sequential (#1, #2 …)
+ author_id INTEGER NOT NULL REFERENCES users(id),
+ title TEXT NOT NULL,
+ body TEXT NOT NULL DEFAULT '',
+ state TEXT NOT NULL DEFAULT 'open', -- 'open' | 'closed'
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE(repo_id, number)
+);
+
+CREATE TABLE issue_comments (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ issue_id INTEGER NOT NULL REFERENCES issues(id),
+ author_id INTEGER NOT NULL REFERENCES users(id),
+ body TEXT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE pull_requests (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ repo_id INTEGER NOT NULL REFERENCES repositories(id),
+ number INTEGER NOT NULL,
+ author_id INTEGER NOT NULL REFERENCES users(id),
+ title TEXT NOT NULL,
+ body TEXT NOT NULL DEFAULT '',
+ base_branch TEXT NOT NULL,
+ head_branch TEXT NOT NULL,
+ state TEXT NOT NULL DEFAULT 'open', -- 'open' | 'merged' | 'closed'
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE(repo_id, number)
+);
+
+CREATE TABLE pr_comments (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ pr_id INTEGER NOT NULL REFERENCES pull_requests(id),
+ author_id INTEGER NOT NULL REFERENCES users(id),
+ body TEXT NOT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
internal/interface/web/router.go +33 −0
+package web
+
+import (
+ "embed"
+ "gitgud/internal/infra/config"
+ "net/http"
+
+ "github.com/go-chi/chi"
+ "github.com/go-chi/chi/middleware"
+)
+
+var templatesFS embed.FS
+
+var staticFS embed.FS
+
+func NewRouter(cfg config.Config) http.Handler {
+ r := chi.NewRouter()
+
+ r.Use(middleware.Logger, middleware.Recoverer)
+
+ r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte("OK"))
+ })
+ r.Handle("/static/*", http.StripPrefix("/static", http.FileServer((http.FS(staticFS)))))
+ r.Get("/", homeHandler)
+ return r
+}
+
+func homeHandler(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte("Welcome to GitGud!"))
+}