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 }