package router import ( "fmt" "io" "net/http" "net/url" "strings" "go.jolheiser.com/cabinet/internal/static" "go.jolheiser.com/cabinet/internal/workspace" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/rs/zerolog/log" ) type Cabinet interface { Meta(string) (workspace.Meta, error) Read(string) (io.ReadCloser, error) Add(io.Reader, workspace.Meta) (string, error) Token(string) (workspace.Token, error) IsProtected() (bool, error) } func New(domain string, c Cabinet, limit *Limit) *chi.Mux { m := chi.NewMux() m.Use(middleware.StripSlashes) m.Use(middleware.Logger) m.Use(middleware.Recoverer) middleware.DefaultLogger = middleware.RequestLogger(&middleware.DefaultLogFormatter{ Logger: &log.Logger, NoColor: true, }) s := Store{ domain: domain, c: c, } m.Get("/", index(domain)) m.Mount("/css/", http.StripPrefix("/css/", http.FileServer(http.FS(static.CSS)))) m.Get("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "image/x-icon") _, _ = w.Write(static.Favicon) }) m.Route("/r", func(r chi.Router) { r.Get("/{id}", s.GetRedirect) r.With(limit.redirect, tokenMiddleware(c, workspace.TokenRedirect)).Post("/", s.AddRedirect) }) m.Route("/f", func(r chi.Router) { r.Get("/{id}", s.GetFile) r.With(limit.file, tokenMiddleware(c, workspace.TokenFile)).Post("/", s.AddFile) }) return m } func index(domain string) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { if err := static.Index.Execute(w, domain); err != nil { log.Err(err).Msg("could not execute index template") } } } type Store struct { domain string c Cabinet } func (s *Store) AddRedirect(w http.ResponseWriter, r *http.Request) { u := r.FormValue("url") if u == "" { http.Error(w, "Expected a url", http.StatusBadRequest) return } _, err := url.Parse(u) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } id, err := s.c.Add(strings.NewReader(u), workspace.Meta{ Type: workspace.TypeRedirect, Name: u, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if _, err := w.Write([]byte(fmt.Sprintf("%s/r/%s", s.domain, id))); err != nil { log.Err(err).Msg("could not write response") } } func (s *Store) GetRedirect(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") meta, err := s.c.Meta(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } if meta.Type != workspace.TypeRedirect { http.Error(w, err.Error(), http.StatusNotFound) return } rc, err := s.c.Read(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } defer rc.Close() redirect, err := io.ReadAll(rc) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, string(redirect), http.StatusFound) } func (s *Store) AddFile(w http.ResponseWriter, r *http.Request) { fi, fh, err := r.FormFile("file") if err != nil { http.Error(w, "Could not open form file", http.StatusBadRequest) return } defer fi.Close() id, err := s.c.Add(fi, workspace.Meta{ Type: workspace.TypeFile, Name: fh.Filename, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if _, err := w.Write([]byte(fmt.Sprintf("%s/f/%s", s.domain, id))); err != nil { log.Err(err).Msg("could not write response") } } func (s *Store) GetFile(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") meta, err := s.c.Meta(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } if meta.Type != workspace.TypeFile { http.Error(w, "could not find file", http.StatusNotFound) return } rc, err := s.c.Read(id) if err != nil { http.Error(w, "could not read file", http.StatusInternalServerError) return } defer rc.Close() w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, meta.Name)) if _, err := io.Copy(w, rc); err != nil { log.Err(err).Msg("could not write response") } }