batman
Brijesh Wawdhane brijesh@wawdhane.com
Thu, 14 Nov 2024 21:57:02 +0530
11 files changed,
396 insertions(+),
0 deletions(-)
A
core/.air.toml
@@ -0,0 +1,46 @@
+root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./main" + cmd = "make build" + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false + keep_scroll = true
A
core/.gitignore
@@ -0,0 +1,33 @@
+# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with "go test -c" +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +tmp/ + +# IDE specific files +.vscode +.idea + +# .env file +.env + +# Project build +main +*templ.go + +# OS X generated file +.DS_Store
A
core/Makefile
@@ -0,0 +1,32 @@
+# Build the application +build: + @echo "Building..." + @go build -o main cmd/api/main.go + +# Run the application +run: + @go run cmd/api/main.go + +# Clean the binary +clean: + @echo "Cleaning..." + @rm -f main + +# Live Reload +watch: + @if command -v air > /dev/null; then \ + air; \ + echo "Watching...";\ + else \ + read -p "Go's 'air' is not installed on your machine. Do you want to install it? [Y/n] " choice; \ + if [ "$$choice" != "n" ] && [ "$$choice" != "N" ]; then \ + go install github.com/air-verse/air@latest; \ + air; \ + echo "Watching...";\ + else \ + echo "You chose not to install air. Exiting..."; \ + exit 1; \ + fi; \ + fi + +.PHONY: build run clean watch
A
core/cmd/api/main.go
@@ -0,0 +1,48 @@
+package main + +import ( + "context" + "fmt" + "log" + "net/http" + "os/signal" + "syscall" + "time" + + "core/internal/server" +) + +func main() { + server := server.NewServer() + + done := make(chan bool, 1) + + go gracefulShutdown(server, done) + + err := server.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + panic(fmt.Sprintf("http server error: %s", err)) + } + + <-done + log.Println("Graceful shutdown complete.") +} + +func gracefulShutdown(apiServer *http.Server, done chan bool) { + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + <-ctx.Done() + + log.Println("shutting down gracefully, press Ctrl+C again to force") + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := apiServer.Shutdown(ctx); err != nil { + log.Printf("Server forced to shutdown with error: %v", err) + } + + log.Println("Server exiting") + + done <- true +}
A
core/go.mod
@@ -0,0 +1,22 @@
+module core + +go 1.23.0 + +require ( + github.com/go-chi/chi/v5 v5.1.0 + github.com/go-webauthn/webauthn v0.11.2 + github.com/joho/godotenv v1.5.1 + github.com/mattn/go-sqlite3 v1.14.24 +) + +require ( + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-webauthn/x v0.1.14 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/go-tpm v0.9.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/sys v0.23.0 // indirect +)
A
core/go.sum
@@ -0,0 +1,34 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-webauthn/webauthn v0.11.2 h1:Fgx0/wlmkClTKlnOsdOQ+K5HcHDsDcYIvtYmfhEOSUc= +github.com/go-webauthn/webauthn v0.11.2/go.mod h1:aOtudaF94pM71g3jRwTYYwQTG1KyTILTcZqN1srkmD0= +github.com/go-webauthn/x v0.1.14 h1:1wrB8jzXAofojJPAaRxnZhRgagvLGnLjhCAwg3kTpT0= +github.com/go-webauthn/x v0.1.14/go.mod h1:UuVvFZ8/NbOnkDz3y1NaxtUN87pmtpC1PQ+/5BBQRdc= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM= +github.com/google/go-tpm v0.9.1/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
A
core/internal/database/create-tables.go
@@ -0,0 +1,37 @@
+package database + +import ( + "context" + "database/sql" + + _ "github.com/mattn/go-sqlite3" +) + +func (s *service) CreateTables(ctx context.Context) error { + return s.withTransaction(ctx, func(tx *sql.Tx) error { + _, err := tx.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + display_name TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + );`) + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS credentials ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL, + public_key BLOB NOT NULL, + credential_id BLOB NOT NULL, + sign_count INTEGER NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) + );`) + if err != nil { + return err + } + + return nil + }) +}
A
core/internal/database/database.go
@@ -0,0 +1,44 @@
+package database + +import ( + "database/sql" + "log" + "os" + + _ "github.com/joho/godotenv/autoload" + _ "github.com/mattn/go-sqlite3" +) + +type Service interface { + Close() error +} + +type service struct { + db *sql.DB +} + +var ( + dburl = os.Getenv("BLUEPRINT_DB_URL") + dbInstance *service +) + +func New() Service { + if dbInstance != nil { + return dbInstance + } + + db, err := sql.Open("sqlite3", dburl) + if err != nil { + log.Fatal(err) + } + + dbInstance = &service{ + db: db, + } + return dbInstance +} + +func (s *service) Close() error { + log.Printf("Disconnected from database: %s", dburl) + return s.db.Close() +}
A
core/internal/database/transaction.go
@@ -0,0 +1,23 @@
+package database + +import ( + "context" + "database/sql" + + _ "github.com/mattn/go-sqlite3" +) + +func (s *service) withTransaction(ctx context.Context, fn func(*sql.Tx) error) error { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + + defer tx.Rollback() + + if err := fn(tx); err != nil { + return err + } + + return tx.Commit() +}
A
core/internal/server/routes.go
@@ -0,0 +1,38 @@
+package server + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" +) + +func (s *Server) RegisterRoutes() http.Handler { + r := chi.NewRouter() + r.Use(middleware.Logger) + + r.Get("/", s.HelloWorldHandler) + + r.Get("/health", s.healthHandler) + + return r +} + +func (s *Server) HelloWorldHandler(w http.ResponseWriter, r *http.Request) { + resp := make(map[string]string) + resp["message"] = "Hello World" + + jsonResp, err := json.Marshal(resp) + if err != nil { + log.Fatalf("error handling JSON marshal. Err: %v", err) + } + + _, _ = w.Write(jsonResp) +} + +func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) { + jsonResp, _ := json.Marshal(s.db.Health()) + _, _ = w.Write(jsonResp) +}
A
core/internal/server/server.go
@@ -0,0 +1,39 @@
+package server + +import ( + "fmt" + "net/http" + "os" + "strconv" + "time" + + _ "github.com/joho/godotenv/autoload" + + "core/internal/database" +) + +type Server struct { + port int + + db database.Service +} + +func NewServer() *http.Server { + port, _ := strconv.Atoi(os.Getenv("PORT")) + NewServer := &Server{ + port: port, + + db: database.New(), + } + + // Declare Server config + server := &http.Server{ + Addr: fmt.Sprintf(":%d", NewServer.port), + Handler: NewServer.RegisterRoutes(), + IdleTimeout: time.Minute, + ReadTimeout: 10 * time.Second, + WriteTimeout: 30 * time.Second, + } + + return server +}