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
42 // Pages
43 s.router.Get("/", func(w http.ResponseWriter, r *http.Request) {
44 http.Redirect(w, r, "/library", http.StatusSeeOther)
45 })
46 s.router.Get("/library", s.handler.Library)
47 s.router.Get("/library/{id}", s.handler.LibraryDetail)
48 s.router.Post("/library/scan", s.handler.ScanLibrary)
49 s.router.Post("/library/{id}/delete", s.handler.DeleteMedia)
50
51 s.router.Get("/queue", s.handler.Downloads)
52 s.router.Get("/queue/{id}", s.handler.DownloadDetail)
53 s.router.Post("/queue/{id}/delete", s.handler.DeleteDownload)
54
55 s.router.Get("/download", s.handler.DownloadForm)
56 s.router.Post("/download", s.handler.CreateDownload)
57
58 s.router.Get("/settings", s.handler.Settings)
59 s.router.Post("/settings/presets", s.handler.CreatePreset)
60 s.router.Post("/settings/presets/{id}", s.handler.UpdatePreset)
61 s.router.Post("/settings/presets/{id}/delete", s.handler.DeletePreset)
62 s.router.Post("/settings", s.handler.UpdateSettings)
63
64 s.router.Post("/theme", s.handler.Theme)
65
66 // API endpoints
67 s.router.Get("/api/presets/{id}/flags", s.handler.GetPresetFlags)
68}
69
70func (s *Server) securityHeaders(next http.Handler) http.Handler {
71 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
72 w.Header().Set("X-Content-Type-Options", "nosniff")
73 w.Header().Set("X-Frame-Options", "DENY")
74 w.Header().Set("X-XSS-Protection", "1; mode=block")
75 w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
76
77 if s.cfg.IsHTTPS() {
78 w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
79 w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; media-src 'self' blob:;")
80 }
81
82 next.ServeHTTP(w, r)
83 })
84}
85
86func (s *Server) Start() error {
87 addr := fmt.Sprintf(":%d", s.cfg.Port)
88 fmt.Printf("Starting server on %s\n", addr)
89 if s.cfg.BaseURL != "" {
90 fmt.Printf("Base URL: %s\n", s.cfg.BaseURL)
91 fmt.Printf("HTTPS mode: %v\n", s.cfg.IsHTTPS())
92 }
93 return http.ListenAndServe(addr, s.router)
94}
95