prime8dev

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

main.go (2550B)


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