cabinet/internal/router/router.go

180 lines
4.1 KiB
Go

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")
}
}