prime8dev

prime8dev
git clone https://git.prime8.dev/prime8dev.git
Log | Files | Refs | README | LICENSE

main.go (2602B)


      1 package main
      2 
      3 import (
      4 	"context"
      5 	"errors"
      6 	"log/slog"
      7 	"net/http"
      8 	"os"
      9 	"time"
     10 
     11 	"git.prime8.dev/moncheeta/prime8dev/cmd/server/handlers"
     12 	"git.prime8.dev/moncheeta/prime8dev/internal/config"
     13 	sshserver "git.prime8.dev/moncheeta/prime8dev/internal/ssh"
     14 	"github.com/go-chi/chi/v5"
     15 	"github.com/go-chi/chi/v5/middleware"
     16 	"github.com/joho/godotenv"
     17 )
     18 
     19 func main() {
     20 	_ = godotenv.Load()
     21 	configureLogger()
     22 
     23 	if err := run(); err != nil {
     24 		slog.Error("server stopped", "error", err)
     25 	}
     26 }
     27 
     28 func run() error {
     29 	h, err := handlers.NewHandler()
     30 	if err != nil {
     31 		return err
     32 	}
     33 
     34 	r := chi.NewRouter()
     35 	r.Use(requestLoggerMiddleware(slog.Default()))
     36 	r.Use(middleware.Recoverer)
     37 	r.Use(middleware.Timeout(30 * time.Second))
     38 
     39 	fs := http.FileServer(http.Dir("./static"))
     40 	r.Handle("/static/*", http.StripPrefix("/static/", fs))
     41 	serveStaticFile := func(file string) http.HandlerFunc {
     42 		return func(w http.ResponseWriter, r *http.Request) {
     43 			http.ServeFile(w, r, "./static/"+file)
     44 		}
     45 	}
     46 	r.Get("/robots.txt", serveStaticFile("robots.txt"))
     47 	r.Get("/favicon.png", serveStaticFile("favicon.png"))
     48 	r.Get("/site.webmanifest", serveStaticFile("site.webmanifest"))
     49 
     50 	r.Get("/", h.HomeHandler)
     51 	r.Get("/projects", h.ProjectsPageHandler)
     52 	r.Get("/projects/{id}", h.ProjectsPageHandler)
     53 	r.Get("/projects/{id}/raw/*", h.ProjectRawHandler)
     54 	r.Get("/experience", h.ExperienceListHandler)
     55 	r.Get("/experience/{id}", h.ExperiencePageHandler)
     56 	r.Get("/sitemap.xml", h.SitemapHandler)
     57 
     58 	r.Get("/api/projects/{id}", h.ProjectDetailHandler)
     59 	r.Get("/api/fs", h.FSHandler)
     60 	if config.WakaTimeUser != "" {
     61 		r.Get("/api/wakatime", h.WakaTimeHandler)
     62 	}
     63 
     64 	port := os.Getenv("WEB_PORT")
     65 	if port == "" {
     66 		port = "3000"
     67 	}
     68 
     69 	sshCtx, cancelSSH := context.WithCancel(context.Background())
     70 	defer cancelSSH()
     71 	sshErrCh := make(chan error, 1)
     72 	go func() {
     73 		if err := sshserver.Run(sshCtx, slog.Default()); err != nil {
     74 			sshErrCh <- err
     75 		}
     76 	}()
     77 
     78 	httpServer := &http.Server{
     79 		Addr:    ":" + port,
     80 		Handler: r,
     81 	}
     82 	defer func() {
     83 		shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
     84 		defer cancel()
     85 		if err := httpServer.Shutdown(shutdownCtx); err != nil && !errors.Is(err, http.ErrServerClosed) {
     86 			slog.Error("http server shutdown error", "error", err)
     87 		}
     88 	}()
     89 
     90 	httpErrCh := make(chan error, 1)
     91 	go func() {
     92 		slog.Info("http server starting", "addr", httpServer.Addr)
     93 		if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
     94 			httpErrCh <- err
     95 		}
     96 	}()
     97 
     98 	select {
     99 	case err := <-sshErrCh:
    100 		return err
    101 	case err := <-httpErrCh:
    102 		return err
    103 	}
    104 }