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