diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-17 15:25:52 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-17 15:25:52 +0300 |
| commit | 914bd7cd6aa14e839332a98d91c30b19865b0cf2 (patch) | |
| tree | 02b11537237a204048589e14ed24938b805e42f7 /player-server/internal | |
| parent | 3b24f0e1be832584d6550e8cc3e5d24329f66f90 (diff) | |
Restructure repo: move Go server into player-server/
Diffstat (limited to 'player-server/internal')
113 files changed, 26651 insertions, 0 deletions
diff --git a/player-server/internal/api/handlers.go b/player-server/internal/api/handlers.go new file mode 100644 index 0000000..b276b10 --- /dev/null +++ b/player-server/internal/api/handlers.go @@ -0,0 +1,193 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "net/http" + "strconv" + + "codeberg.org/snonux/player/internal/model" + "codeberg.org/snonux/player/internal/service" +) + +// ------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------ + +func writeJSON(w http.ResponseWriter, status int, data interface{}) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + if err := json.NewEncoder(w).Encode(data); err != nil { + slog.Error("encode json", "err", err) + } +} + +func writeError(w http.ResponseWriter, status int, message string) { + writeJSON(w, status, map[string]string{"error": message}) +} + +func badRequest(w http.ResponseWriter, message string) { + writeError(w, http.StatusBadRequest, message) +} + +func notFound(w http.ResponseWriter) { + writeError(w, http.StatusNotFound, "not found") +} + +func forbidden(w http.ResponseWriter, message string) { + writeError(w, http.StatusForbidden, message) +} + +// handleError maps service sentinel errors to the appropriate HTTP status +// and writes a JSON error response. It falls back to 500 for unknown errors. +func handleError(w http.ResponseWriter, err error) { + switch { + case errors.Is(err, service.ErrNotFound), + errors.Is(err, service.ErrShareNotFound), + errors.Is(err, service.ErrMediaNotFound): + notFound(w) + case errors.Is(err, service.ErrForbidden): + forbidden(w, "forbidden") + case errors.Is(err, service.ErrAlreadyBootstrapped): + forbidden(w, "bootstrap already complete") + case errors.Is(err, service.ErrInvalidCredentials): + writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid credentials"}) + case errors.Is(err, service.ErrUnsupportedExtension), + errors.Is(err, service.ErrInvalidFeed), + errors.Is(err, service.ErrCannotDeleteSelf): + badRequest(w, err.Error()) + default: + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + } +} + +func readJSON(r *http.Request, dst interface{}) error { + if r.Body == nil { + return errors.New("missing body") + } + defer r.Body.Close() + return json.NewDecoder(r.Body).Decode(dst) +} + +func pathID(r *http.Request, name string) int64 { + id, _ := strconv.ParseInt(r.PathValue(name), 10, 64) + return id +} + +func userIDFromContext(r *http.Request) int64 { + sess, _ := r.Context().Value(sessionCtxKey).(*model.Session) + if sess == nil { + return 0 + } + return sess.UserID +} + +func sessionIDFromContext(r *http.Request) string { + sess, _ := r.Context().Value(sessionCtxKey).(*model.Session) + if sess == nil { + return "" + } + return sess.ID +} + +func stringPtr(s string) *string { return &s } + +func floatPtr(f float64) *float64 { return &f } + +func intPtr(i int64) *int64 { return &i } + +func requireService(w http.ResponseWriter, svc any) bool { + if svc == nil { + writeJSON(w, http.StatusNotImplemented, map[string]string{"error": "not implemented"}) + return false + } + return true +} + +// ------------------------------------------------------------------ +// Static pages +// ------------------------------------------------------------------ + +func (s *Server) serveFile(w http.ResponseWriter, r *http.Request, filename string) { + f, err := s.staticFS.Open(filename) + if err != nil { + http.Error(w, "not found", http.StatusNotFound) + return + } + defer f.Close() + stat, err := f.Stat() + if err != nil { + http.Error(w, "not found", http.StatusNotFound) + return + } + rs, ok := f.(io.ReadSeeker) + if !ok { + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + http.ServeContent(w, r, filename, stat.ModTime(), rs) +} + +func (s *Server) serveIndex(w http.ResponseWriter, r *http.Request) { + s.serveFile(w, r, "index.html") +} + +func (s *Server) serveLogin(w http.ResponseWriter, r *http.Request) { + s.serveFile(w, r, "login.html") +} + +func (s *Server) serveBootstrap(w http.ResponseWriter, r *http.Request) { + s.serveFile(w, r, "bootstrap.html") +} + +func (s *Server) serveDetach(w http.ResponseWriter, r *http.Request) { + s.serveFile(w, r, "detach.html") +} + +// ------------------------------------------------------------------ +// File serving helpers +// ------------------------------------------------------------------ |
