server.go
⎇
Raw
1package server
2
3import (
4 "fmt"
5 "net/http"
6
7 "github.com/go-chi/chi/v5"
8 "github.com/go-chi/chi/v5/middleware"
9
10 "vidarchive/internal/config"
11 "vidarchive/internal/handler"
12)
13
14type Server struct {
15 router *chi.Mux
16 handler *handler.Handler
17 cfg *config.Config
18}
19
20func New(cfg *config.Config, h *handler.Handler) *Server {
21 s := &Server{
22 router: chi.NewRouter(),
23 handler: h,
24 cfg: cfg,
25 }
26 s.setupRoutes()
27 return s
28}
29
30func (s *Server) setupRoutes() {
31 // Middleware
32 s.router.Use(middleware.Logger)
33 s.router.Use(middleware.Recoverer)
34 s.router.Use(s.securityHeaders)
35
36 // Static files
37 s.router.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))))
38
39 // Media serving (with range request support via http.ServeFile)
40 s.router.Get("/media/{id}", s.handler.ServeMedia)
41 s.router.Get("/media/{id}/thumbnail", s.handler.ServeThumbnail)
42 s.router.Get("/media/{id}/subtitles/{lang}", s.handler.ServeSubtitles)
43
44 // Pages
45 s.router.Get("/", func(w http.ResponseWriter, r *http.Request) {
46 http.Redirect(w, r, "/library", http.StatusSeeOther)
47 })
48 s.router.Get("/library", s.handler.Library)
49 s.router.Get("/library/{id}", s.handler.LibraryDetail)
50 s.router.Post("/library/scan", s.handler.ScanLibrary)
51 s.router.Post("/library/{id}/delete", s.handler.DeleteMedia)
52
53 s.router.Get("/queue", s.handler.Downloads)
54 s.router.Get("/queue/{id}", s.handler.DownloadDetail)
55 s.router.Post("/queue/{id}/delete", s.handler.DeleteDownload)
56 s.router.Post("/queue/clear", s.handler.ClearAllDownloads)
57
58 s.router.Get("/download", s.handler.DownloadForm)
59 s.router.Post("/download", s.handler.CreateDownload)
60
61 s.router.Get("/settings", s.handler.Settings)
62 s.router.Post("/settings/presets", s.handler.CreatePreset)
63 s.router.Post("/settings/presets/{id}", s.handler.UpdatePreset)
64 s.router.Post("/settings/presets/{id}/delete", s.handler.DeletePreset)
65 s.router.Post("/settings", s.handler.UpdateSettings)
66
67 s.router.Post("/theme", s.handler.Theme)
68
69 // API endpoints
70 s.router.Get("/api/presets/{id}/flags", s.handler.GetPresetFlags)
71}
72
73func (s *Server) securityHeaders(next http.Handler) http.Handler {
74 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
75 w.Header().Set("X-Content-Type-Options", "nosniff")
76 w.Header().Set("X-Frame-Options", "DENY")
77 w.Header().Set("X-XSS-Protection", "1; mode=block")
78 w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
79
80 if s.cfg.IsHTTPS() {
81 w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
82 w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; media-src 'self' blob:;")
83 }
84
85 next.ServeHTTP(w, r)
86 })
87}
88
89func (s *Server) Start() error {
90 addr := fmt.Sprintf(":%d", s.cfg.Port)
91 fmt.Printf("Starting server on %s\n", addr)
92 if s.cfg.BaseURL != "" {
93 fmt.Printf("Base URL: %s\n", s.cfg.BaseURL)
94 fmt.Printf("HTTPS mode: %v\n", s.cfg.IsHTTPS())
95 }
96 return http.ListenAndServe(addr, s.router)
97}
98