init
This commit is contained in:
120
server/internal/httpapi/server.go
Normal file
120
server/internal/httpapi/server.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"synctv/server/internal/room"
|
||||
"synctv/server/internal/store"
|
||||
)
|
||||
|
||||
type Rooms interface {
|
||||
Create(ctx context.Context) (room.Room, error)
|
||||
Get(ctx context.Context, code string) (room.Room, error)
|
||||
SetSource(ctx context.Context, code, deviceID string, src room.Source) (room.Room, error)
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
rooms Rooms
|
||||
hub http.Handler
|
||||
proxy http.Handler
|
||||
}
|
||||
|
||||
func New(rooms Rooms, hub http.Handler, proxy http.Handler) *Server {
|
||||
return &Server{rooms: rooms, hub: hub, proxy: proxy}
|
||||
}
|
||||
|
||||
func (s *Server) Routes() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /health", s.health)
|
||||
mux.HandleFunc("POST /api/rooms", s.createRoom)
|
||||
mux.HandleFunc("GET /api/rooms/{code}", s.getRoom)
|
||||
mux.HandleFunc("POST /api/rooms/{code}/source", s.setSource)
|
||||
mux.Handle("GET /api/rooms/{code}/stream", s.proxy)
|
||||
mux.Handle("GET /ws", s.hub)
|
||||
return withCORS(mux)
|
||||
}
|
||||
|
||||
func (s *Server) health(w http.ResponseWriter, _ *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
func (s *Server) createRoom(w http.ResponseWriter, r *http.Request) {
|
||||
rm, err := s.rooms.Create(r.Context())
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, map[string]any{"room": rm})
|
||||
}
|
||||
|
||||
func (s *Server) getRoom(w http.ResponseWriter, r *http.Request) {
|
||||
code := r.PathValue("code")
|
||||
deviceID := r.URL.Query().Get("deviceId")
|
||||
rm, err := s.rooms.Get(r.Context(), code)
|
||||
if err != nil {
|
||||
status := http.StatusInternalServerError
|
||||
if errors.Is(err, store.ErrNotFound) {
|
||||
status = http.StatusNotFound
|
||||
}
|
||||
writeError(w, status, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, room.Snapshot{
|
||||
Room: rm.PublicFor(deviceID),
|
||||
IsOwner: deviceID != "" && rm.OwnerDeviceID == deviceID,
|
||||
DeviceID: deviceID,
|
||||
ServerNow: room.NowMS(),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) setSource(w http.ResponseWriter, r *http.Request) {
|
||||
code := r.PathValue("code")
|
||||
deviceID := r.Header.Get("X-Device-Id")
|
||||
if strings.TrimSpace(deviceID) == "" {
|
||||
writeError(w, http.StatusBadRequest, "X-Device-Id is required")
|
||||
return
|
||||
}
|
||||
var src room.Source
|
||||
if err := json.NewDecoder(r.Body).Decode(&src); err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
rm, err := s.rooms.SetSource(r.Context(), code, deviceID, src)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, room.Snapshot{
|
||||
Room: rm.PublicFor(deviceID),
|
||||
IsOwner: rm.OwnerDeviceID == deviceID,
|
||||
DeviceID: deviceID,
|
||||
ServerNow: room.NowMS(),
|
||||
})
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, v any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
_ = json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, status int, message string) {
|
||||
writeJSON(w, status, map[string]any{"error": message})
|
||||
}
|
||||
|
||||
func withCORS(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-Device-Id")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user