mood/inspo boards

Compare changes

Choose any two refs to compare.

Changed files
+90 -37
cmd
internal
+3 -1
.gitignore
··· 1 1 build 2 - .env 2 + .env 3 + *.db* 4 + *.shm
+3 -2
Dockerfile
··· 1 + # syntax=docker/dockerfile:1 2 + 1 3 FROM golang:1.25-alpine AS builder 2 4 3 5 ENV CGO_ENABLED=1 \ 4 - GOOS=linux \ 5 - GOARCH=amd64 6 + GOOS=linux 6 7 7 8 RUN apk add --no-cache \ 8 9 gcc \
+9 -11
cmd/main.go
··· 11 11 "tangled.org/dane.is.extraordinarily.cool/pallet/internal/server" 12 12 ) 13 13 14 - 15 14 func main() { 16 15 if err := run(os.Args); err != nil { 17 16 log.Fatal(err) ··· 21 20 22 21 func run(args []string) error { 23 22 app := &cli.Command{ 24 - Name: "pallet", 23 + Name: "pallet", 25 24 Usage: "pallet app server", 26 25 } 27 26 28 27 app.Flags = []cli.Flag{ 29 28 &cli.StringFlag{ 30 - Name: "log-level", 31 - Usage: "log verbosity level (eg: warn, info, debug)", 29 + Name: "log-level", 30 + Usage: "log verbosity level (eg: warn, info, debug)", 32 31 Sources: cli.EnvVars("GO_LOG_LEVEL", "LOG_LEVEL"), 33 32 }, 34 33 } 35 34 36 35 app.Commands = []*cli.Command{ 37 36 &cli.Command{ 38 - Name: "start", 39 - Usage: "start the pallet app server", 37 + Name: "start", 38 + Usage: "start the pallet app server", 40 39 Action: runServer, 41 40 Flags: []cli.Flag{ 42 41 &cli.StringFlag{ 43 - Name: "port", 44 - Usage: "Port to bind the HTTP server to", 45 - Value: ":8080", 42 + Name: "port", 43 + Usage: "Port to bind the HTTP server to", 44 + Value: ":8080", 46 45 Sources: cli.EnvVars("PORT"), 47 46 }, 48 47 }, 49 48 }, 50 49 } 51 - 52 50 53 51 return app.Run(context.Background(), args) 54 52 } ··· 68 66 } 69 67 70 68 return nil 71 - } 69 + }
+43 -2
internal/db/db.go
··· 7 7 _ "github.com/mattn/go-sqlite3" 8 8 ) 9 9 10 - 11 10 func NewSQLiteDB() (*sql.DB, error) { 12 11 db, err := sql.Open("sqlite3", "./pallet.db") 12 + _, err = db.Exec(` 13 + pragma foreign_keys = on; 14 + pragma journal_mode = wal; 15 + 16 + create table if not exists boards ( 17 + uri text primary key, 18 + cid text not null, 19 + rkey text not null, 20 + owner text not null, 21 + name text not null, 22 + description text, 23 + created_at datetime default current_timestamp, 24 + indexed_at text not null 25 + ); 26 + 27 + create table if not exists board_item ( 28 + uri text primary key, 29 + cid text not null, 30 + rkey text not null, 31 + owner text not null, 32 + board_id text not null, 33 + type text not null check(type in ('text', 'image')), 34 + text text, 35 + 36 + blob_cid text, 37 + mime_type text, 38 + width integer, 39 + height integer, 40 + alt_text text, 41 + 42 + position integer, 43 + created_at datetime default current_timestamp, 44 + indexed_at text not null, 45 + 46 + foreign key(board_id) references boards(rkey) on delete cascade, 47 + 48 + check ( 49 + (type = 'text' and text is not null and blob_cid is null) or 50 + (type = 'image' and blob_cid is not null and text is null) 51 + ) 52 + ); 53 + `) 13 54 14 55 if err != nil { 15 56 log.Fatal("error initializing database", err) ··· 17 58 18 59 defer db.Close() 19 60 return db, nil 20 - } 61 + }
+4 -4
internal/server/handlers.go
··· 3 3 import "net/http" 4 4 5 5 func (s *Server) handleHomeRoute(w http.ResponseWriter, r *http.Request) { 6 - w.WriteHeader(http.StatusOK) 7 - w.Write([]byte("hello world")) 6 + w.WriteHeader(http.StatusOK) 7 + w.Write([]byte("hello world")) 8 8 } 9 9 10 10 func (s *Server) handleHealthCheckRoute(w http.ResponseWriter, r *http.Request) { 11 - w.WriteHeader(http.StatusOK); 11 + w.WriteHeader(http.StatusOK) 12 12 w.Write([]byte("reporting for duty")) 13 - } 13 + }
+26 -15
internal/server/server.go
··· 1 1 package server 2 2 3 3 import ( 4 + "context" 4 5 "database/sql" 5 6 "fmt" 6 7 "net/http" ··· 11 12 ) 12 13 13 14 type Server struct { 14 - addr string 15 - db *sql.DB 15 + addr string 16 + db *sql.DB 16 17 logger *log.Logger 17 18 } 18 19 ··· 31 32 32 33 func (s *Server) Start() error { 33 34 router := mux.NewRouter() 35 + router.Use(s.requestLoggerMiddleware) 36 + router.Use(s.contextWithTimeoutMiddleware) 34 37 server := &http.Server{ 35 - Addr: s.addr, 38 + Addr: s.addr, 36 39 WriteTimeout: 5 * time.Second, 37 - ReadTimeout: 10 * time.Second, 38 - IdleTimeout: 30 * time.Second, 39 - Handler: requestLoggerMiddleware(s.logger, router), 40 + ReadTimeout: 10 * time.Second, 41 + IdleTimeout: 30 * time.Second, 42 + Handler: router, 40 43 } 41 44 42 45 router.HandleFunc("/healthcheck", s.handleHomeRoute) ··· 46 49 return server.ListenAndServe() 47 50 } 48 51 49 - func requestLoggerMiddleware(logger *log.Logger, next http.Handler) http.Handler { 52 + func (s *Server) contextWithTimeoutMiddleware(next http.Handler) http.Handler { 53 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 54 + ctx, cancel := context.WithTimeout(r.Context(), time.Second*30) 55 + defer cancel() 56 + r = r.WithContext(ctx) 57 + next.ServeHTTP(w, r) 58 + }) 59 + } 60 + 61 + func (s *Server) requestLoggerMiddleware(next http.Handler) http.Handler { 50 62 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 51 63 recorder := statusRecorder{ 52 64 ResponseWriter: w, 53 - status: http.StatusOK, 65 + status: http.StatusOK, 54 66 } 55 67 56 68 next.ServeHTTP(recorder, r) 57 69 var ( 58 - ip = r.RemoteAddr 59 - method = r.Method 60 - url = r.URL.String() 61 - proto = r.Proto 70 + ip = r.RemoteAddr 71 + method = r.Method 72 + url = r.URL.String() 73 + proto = r.Proto 62 74 timestamp = time.Now().Format(time.RFC850) 63 75 ) 64 76 65 - 66 - logger.Info(fmt.Sprintf("%s [%s] %s %s %s %d", ip, timestamp, method, url, proto, recorder.status)) 77 + s.logger.Info(fmt.Sprintf("%s [%s] %s %s %s %d", ip, timestamp, method, url, proto, recorder.status)) 67 78 }) 68 - } 79 + }
+2 -2
justfile
··· 5 5 go run cmd/*.go start 6 6 7 7 build: 8 - CGO_ENABLED=0 go build -o build/pallet ./cmd/main.go 8 + CGO_ENABLED=1 go build -o build/pallet ./cmd/main.go 9 9 10 10 clean: 11 - rm -rf build 11 + rm -rf build