server.go
⎇
Raw
1package server
2
3import (
4 "fmt"
5 "io/fs"
6 "net/http"
7
8 "github.com/go-chi/chi/v5"
9 "github.com/go-chi/chi/v5/middleware"
10
11 "vidarchive"
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 s.router.Use(middleware.Logger)
34 s.router.Use(middleware.Recoverer)
35 s.router.Use(s.securityHeaders)
36
37 // Serve embedded static assets (fs.Sub strips the web/static prefix). The
38 // error is only possible for an invalid constant path, so it can't occur here.
39 staticFS, _ := fs.Sub(vidarchive.StaticFS, "web/static")
40 s.router.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
41
42 s.router.Get("/media/item/*", s.handler.ServeMediaItem)
43
44 s.router.Get("/", func(w http.ResponseWriter, r *http.Request) {
45 http.Redirect(w, r, "/library", http.StatusSeeOther)
46 })
47 s.router.Get("/library", s.handler.Library)
48 s.router.Get("/library/item/*", s.handler.LibraryItem)
49 s.router.Post("/library/item/*", s.handler.LibraryItem)
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 s.router.Post("/queue/clear", s.handler.ClearAllDownloads)
55
56 s.router.Get("/download", s.handler.DownloadForm)
57 s.router.Post("/download", s.handler.CreateDownload)
58
59 s.router.Get("/subscriptions", s.handler.Subscriptions)
60 s.router.Post("/subscriptions", s.handler.CreateSubscription)
61 s.router.Post("/subscriptions/{id}", s.handler.UpdateSubscription)
62 s.router.Post("/subscriptions/{id}/delete", s.handler.DeleteSubscription)
63 s.router.Post("/subscriptions/{id}/toggle", s.handler.ToggleSubscription)
64 s.router.Post("/subscriptions/{id}/run", s.handler.RunSubscription)
65
66 s.router.Get("/settings", s.handler.Settings)
67 s.router.Post("/settings/presets", s.handler.CreatePreset)
68 s.router.Post("/settings/presets/{id}", s.handler.UpdatePreset)
69 s.router.Post("/settings/presets/{id}/delete", s.handler.DeletePreset)
70 s.router.Post("/settings", s.handler.UpdateSettings)
71
72 s.router.Post("/theme", s.handler.Theme)
73
74 s.router.Get("/api/presets/{id}/flags", s.handler.GetPresetFlags)
75}
76
77func (s *Server) securityHeaders(next http.Handler) http.Handler {
78 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
79 w.Header().Set("X-Content-Type-Options", "nosniff")
80 w.Header().Set("X-Frame-Options", "DENY")
81 w.Header().Set("X-XSS-Protection", "1; mode=block")
82 w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
83
84 if s.cfg.IsHTTPS() {
85 w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
86 w.Header().Set("Content-Security-Policy", "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; media-src 'self' blob:;")
87 }
88
89 next.ServeHTTP(w, r)
90 })
91}
92
93func (s *Server) Router() http.Handler {
94 return s.router
95}
96
97func (s *Server) Start() error {
98 addr := fmt.Sprintf(":%d", s.cfg.Port)
99 fmt.Printf("Starting server on %s\n", addr)
100 if s.cfg.BaseURL != "" {
101 fmt.Printf("Base URL: %s\n", s.cfg.BaseURL)
102 fmt.Printf("HTTPS mode: %v\n", s.cfg.IsHTTPS())
103 }
104 return http.ListenAndServe(addr, s.router)
105}
106