diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..81b33a1 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +PRODUCTION="0" +PORT=8080 +APP_DATA_DIR=./data +DB_CONNDVR="sqlite3" +DB_CONNSTR="./db.db" diff --git a/.gitignore b/.gitignore index 5b90e79..ca77a4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,14 @@ -# ---> Go -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# -# 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 +vendor/ go.work go.work.sum - -# env file .env +axiom +db.db diff --git a/database/database.go b/database/database.go new file mode 100644 index 0000000..9cae3a3 --- /dev/null +++ b/database/database.go @@ -0,0 +1,20 @@ +package database + +import ( + "database/sql" + + _ "github.com/mattn/go-sqlite3" +) + +func Init(connDvr, connStr string) (*sql.DB, error) { + db, err := sql.Open(connDvr, connStr) + if err != nil { + return nil, err + } + + if err := db.Ping(); err != nil { + return nil, err + } + + return db, nil +} diff --git a/go.mod b/go.mod index fe93397..9847650 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module git.tavo.one/tavo/axiom -go 1.24.4 +go 1.22.1 + +require github.com/mattn/go-sqlite3 v1.14.28 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..42e5bac --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= +github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/handlers/handler.go b/handlers/handler.go new file mode 100644 index 0000000..1dd7402 --- /dev/null +++ b/handlers/handler.go @@ -0,0 +1,47 @@ +package handlers + +import ( + "database/sql" + "html/template" + "net/http" + + "git.tavo.one/tavo/axiom/storage" +) + +type Handler struct { + cfg Config + mux *http.ServeMux +} + +type Config struct { + Production bool + Views map[string]*template.Template + DB *sql.DB + S3 *storage.Client +} + +func New(cfg Config) *Handler { + return &Handler{ + cfg: cfg, + } +} + +func (h *Handler) Router() *http.ServeMux { + return h.mux +} + +func (h *Handler) Production() bool { + return h.cfg.Production +} + +func (h *Handler) Views() map[string]*template.Template { + return h.cfg.Views +} + +func (h *Handler) DB() *sql.DB { + return h.cfg.DB +} + +func (h *Handler) S3() *storage.Client { + return h.cfg.S3 +} diff --git a/handlers/sampleIndex.go b/handlers/sampleIndex.go new file mode 100644 index 0000000..de85199 --- /dev/null +++ b/handlers/sampleIndex.go @@ -0,0 +1,9 @@ +package handlers + +import ( + "net/http" +) + +func (h *Handler) SampleIndex(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello from Index!")) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..c1d6a99 --- /dev/null +++ b/main.go @@ -0,0 +1,70 @@ +package main + +import ( + "log" + "net/http" + "os" + "os/signal" + "syscall" + + "git.tavo.one/tavo/axiom/database" + "git.tavo.one/tavo/axiom/handlers" + "git.tavo.one/tavo/axiom/storage" +) + +func init() { + needed := []string{ + "PRODUCTION", + "PORT", + "APP_DATA_DIR", + "DB_CONNDVR", + "DB_CONNSTR", + } + + for _, v := range needed { + if os.Getenv(v) == "" { + log.Fatalf("missing environment varialbe: %s", v) + } + } +} + +func main() { + db, err := database.Init(os.Getenv("DB_CONNDVR"), os.Getenv("DB_CONNSTR")) + if err != nil { + log.Fatalf("failed to initialize database: %v", err) + } + defer db.Close() + + s3, err := storage.New(os.Getenv("APP_DATA_DIR"), 4<<20) + if err != nil { + log.Fatalf("failed to initialize storage: %v", err) + } + + handlerConfig := handlers.Config{ + Production: os.Getenv("PRODUCTION") == "1", + Views: nil, + DB: db, + S3: s3, + } + handler := handlers.New(handlerConfig) + + router := http.NewServeMux() + router.HandleFunc("GET /", handler.SampleIndex) + + stop := make(chan os.Signal, 1) + signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + + port := os.Getenv("PORT") + + go func() { + log.Printf("starting on :%s...", port) + + if err := http.ListenAndServe(":"+port, router); err != nil { + log.Fatalf("fatal: failed to start on port %s: %v", port, err) + } + }() + + <-stop + + log.Printf("shutting down...") +} diff --git a/storage/storage.go b/storage/storage.go index 5dbb5af..afcc1b6 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -11,8 +11,8 @@ import ( ) type Client struct { - RootDir string - MaxObjectSize int64 + rootDir string + maxObjectSize int64 } func New(root string, maxObjectSize int64) (*Client, error) { @@ -40,17 +40,17 @@ func New(root string, maxObjectSize int64) (*Client, error) { os.Remove(testFile) return &Client{ - RootDir: root, - MaxObjectSize: maxObjectSize, + rootDir: root, + maxObjectSize: maxObjectSize, }, nil } func (s *Client) getPath(bucket, key string) string { - return filepath.Join(s.RootDir, bucket, key) + return filepath.Join(s.rootDir, bucket, key) } func (s *Client) CreateBucket(bucket string) error { - path := filepath.Join(s.RootDir, bucket) + path := filepath.Join(s.rootDir, bucket) return os.MkdirAll(path, 0755) } @@ -67,15 +67,15 @@ func (s *Client) PutObject(bucket, key string, reader io.Reader) error { } defer f.Close() - limitedReader := io.LimitReader(reader, s.MaxObjectSize+1) + limitedReader := io.LimitReader(reader, s.maxObjectSize+1) written, err := io.Copy(f, limitedReader) if err != nil { return err } - if written > s.MaxObjectSize { + if written > s.maxObjectSize { os.Remove(fullPath) - return fmt.Errorf("object size exceeds maximum allowed: %d bytes", s.MaxObjectSize) + return fmt.Errorf("object size exceeds maximum allowed: %d bytes", s.maxObjectSize) } return nil