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 }