kv-netflix/backend/cmd/server/main.go
vndangkhoa 056824cfa8 Refactor: Clean up codebase and improve project structure
- Remove debug files, binaries, and temp outputs from repo
- Update .gitignore to exclude cache, logs, and build artifacts
- Fix CI/CD workflow for Go backend (was referencing Python)
- Add graceful shutdown and config module to backend
- Add SSRF protection with URL validation
- Refactor handlers to reduce code duplication
- Add React ErrorBoundary component
- Add lazy loading for frontend routes
- Setup Vitest for frontend testing
- Update Dockerfile to use Go 1.23
2026-02-18 19:00:22 +07:00

124 lines
3.2 KiB
Go

package main
import (
"context"
"log"
"mime"
"net/http"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
"streamflow-backend/internal/api"
"streamflow-backend/internal/config"
"streamflow-backend/internal/database"
"streamflow-backend/internal/scraper"
"streamflow-backend/internal/service"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
)
func main() {
mime.AddExtensionType(".apk", "application/vnd.android.package-archive")
cfg := config.Load()
database.InitDB(cfg.DatabaseURL)
videoRepo := database.NewVideoRepository(database.DB)
ophimService := scraper.NewOphimScraper()
phimMoiService := scraper.NewPhimMoiChillScraper()
tmdbService := service.NewTMDBService()
extractorService := service.NewVideoExtractor()
imageService := service.NewImageService()
providers := []scraper.MovieProvider{ophimService, phimMoiService}
handler := api.NewHandler(videoRepo, providers, tmdbService, extractorService, imageService)
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.RequestID)
r.Use(cors.Handler(cors.Options{
AllowedOrigins: cfg.AllowedOrigins,
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "X-Request-ID"},
ExposedHeaders: []string{"Link", "X-Request-ID"},
AllowCredentials: true,
MaxAge: 300,
}))
r.Route("/api", func(r chi.Router) {
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"status":"healthy", "version":"v3.6-go"}`))
})
api.RegisterRoutes(r, handler)
})
workDir, _ := os.Getwd()
frontendDir := filepath.Join(workDir, "dist")
if _, err := os.Stat(frontendDir); os.IsNotExist(err) {
frontendDir = filepath.Join(workDir, "..", "frontend-react", "dist")
}
if _, err := os.Stat(frontendDir); os.IsNotExist(err) {
log.Println("Frontend build not found at", frontendDir)
} else {
log.Println("Serving frontend from", frontendDir)
FileServer(r, "/", http.Dir(frontendDir))
}
srv := &http.Server{
Addr: ":" + cfg.Port,
Handler: r,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
go func() {
log.Printf("Server starting on port %s", cfg.Port)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server exited")
}
func FileServer(r chi.Router, path string, root http.FileSystem) {
if path != "/" && path[len(path)-1] != '/' {
r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
path += "/"
}
path += "*"
r.Get(path, func(w http.ResponseWriter, r *http.Request) {
rctx := chi.RouteContext(r.Context())
pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
fs := http.StripPrefix(pathPrefix, http.FileServer(root))
fs.ServeHTTP(w, r)
})
}