238 lines
8.0 KiB
Go
238 lines
8.0 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
apimw "github.com/Silo-Server/silo-server/internal/api/middleware"
|
|
"github.com/Silo-Server/silo-server/internal/watchsync"
|
|
)
|
|
|
|
type WatchProviderService interface {
|
|
ListProviders() []watchsync.ProviderSummary
|
|
StartDeviceAuth(ctx context.Context, userID int, profileID string, providerKey string) (watchsync.DeviceAuthSession, error)
|
|
PollDeviceAuth(ctx context.Context, userID int, profileID string, providerKey string, sessionID string) (watchsync.Connection, error)
|
|
ConnectAPIKey(ctx context.Context, userID int, profileID string, providerKey string, apiKey string) (watchsync.Connection, error)
|
|
GetConnectionStatus(ctx context.Context, userID int, profileID string, provider string) (watchsync.ConnectionStatus, error)
|
|
UpdateConnection(ctx context.Context, userID int, profileID string, provider string, update watchsync.ConnectionUpdate) (watchsync.ConnectionStatus, error)
|
|
DeleteConnection(ctx context.Context, userID int, profileID string, provider string) error
|
|
RequestManualSync(ctx context.Context, userID int, profileID string, provider string) (watchsync.ManualSyncResult, error)
|
|
ListSyncRuns(ctx context.Context, userID int, profileID string, provider string, limit int) ([]watchsync.SyncRun, error)
|
|
}
|
|
|
|
type WatchProviderHandler struct {
|
|
service WatchProviderService
|
|
}
|
|
|
|
func NewWatchProviderHandler(service WatchProviderService) *WatchProviderHandler {
|
|
return &WatchProviderHandler{service: service}
|
|
}
|
|
|
|
func (h *WatchProviderHandler) HandleListProviders(w http.ResponseWriter, r *http.Request) {
|
|
if h == nil || h.service == nil {
|
|
writeError(w, http.StatusServiceUnavailable, "unavailable", "Watch provider service is not configured")
|
|
return
|
|
}
|
|
|
|
providers := h.service.ListProviders()
|
|
if providers == nil {
|
|
providers = []watchsync.ProviderSummary{}
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, struct {
|
|
Providers []watchsync.ProviderSummary `json:"providers"`
|
|
}{
|
|
Providers: providers,
|
|
})
|
|
}
|
|
|
|
func (h *WatchProviderHandler) HandleGetConnection(w http.ResponseWriter, r *http.Request) {
|
|
status, ok := h.connectionStatus(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, status)
|
|
}
|
|
|
|
func (h *WatchProviderHandler) HandleUpdateConnection(w http.ResponseWriter, r *http.Request) {
|
|
userID, profileID, provider, ok := watchProviderRequestScope(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
var update watchsync.ConnectionUpdate
|
|
if err := json.NewDecoder(r.Body).Decode(&update); err != nil {
|
|
writeError(w, http.StatusBadRequest, "bad_request", "Invalid request body")
|
|
return
|
|
}
|
|
status, err := h.service.UpdateConnection(r.Context(), userID, profileID, provider, update)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "watch_provider_error", err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, status)
|
|
}
|
|
|
|
func (h *WatchProviderHandler) HandleDeleteConnection(w http.ResponseWriter, r *http.Request) {
|
|
userID, profileID, provider, ok := watchProviderRequestScope(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := h.service.DeleteConnection(r.Context(), userID, profileID, provider); err != nil {
|
|
writeError(w, http.StatusBadRequest, "watch_provider_error", err.Error())
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *WatchProviderHandler) HandleStartDeviceAuth(w http.ResponseWriter, r *http.Request) {
|
|
userID, profileID, provider, ok := watchProviderRequestScope(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
session, err := h.service.StartDeviceAuth(r.Context(), userID, profileID, provider)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "watch_provider_error", err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, session)
|
|
}
|
|
|
|
func (h *WatchProviderHandler) HandlePollDeviceAuth(w http.ResponseWriter, r *http.Request) {
|
|
userID, profileID, provider, ok := watchProviderRequestScope(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
var req struct {
|
|
AuthSessionID string `json:"auth_session_id"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "bad_request", "Invalid request body")
|
|
return
|
|
}
|
|
if _, err := h.service.PollDeviceAuth(r.Context(), userID, profileID, provider, req.AuthSessionID); err != nil {
|
|
writeError(w, http.StatusBadRequest, "watch_provider_error", err.Error())
|
|
return
|
|
}
|
|
status, err := h.service.GetConnectionStatus(r.Context(), userID, profileID, provider)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "watch_provider_error", err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, status)
|
|
}
|
|
|
|
func (h *WatchProviderHandler) HandleConnectAPIKey(w http.ResponseWriter, r *http.Request) {
|
|
userID, profileID, provider, ok := watchProviderRequestScope(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
var req struct {
|
|
APIKey string `json:"api_key"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "bad_request", "Invalid request body")
|
|
return
|
|
}
|
|
if _, err := h.service.ConnectAPIKey(r.Context(), userID, profileID, provider, req.APIKey); err != nil {
|
|
writeError(w, http.StatusBadRequest, "watch_provider_error", err.Error())
|
|
return
|
|
}
|
|
status, err := h.service.GetConnectionStatus(r.Context(), userID, profileID, provider)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "watch_provider_error", err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, status)
|
|
}
|
|
|
|
func (h *WatchProviderHandler) HandleManualSync(w http.ResponseWriter, r *http.Request) {
|
|
userID, profileID, provider, ok := watchProviderRequestScope(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
result, err := h.service.RequestManualSync(r.Context(), userID, profileID, provider)
|
|
if err != nil {
|
|
var cooldown watchsync.SyncCooldownError
|
|
if errors.As(err, &cooldown) {
|
|
w.Header().Set("Retry-After", strconv.Itoa(cooldown.RetryAfterSeconds))
|
|
writeJSON(w, http.StatusTooManyRequests, struct {
|
|
Error string `json:"error"`
|
|
Message string `json:"message"`
|
|
RetryAfterSeconds int `json:"retry_after_seconds"`
|
|
}{
|
|
Error: "sync_cooldown",
|
|
Message: "Watch provider sync recently ran. Try again later.",
|
|
RetryAfterSeconds: cooldown.RetryAfterSeconds,
|
|
})
|
|
return
|
|
}
|
|
writeError(w, http.StatusBadRequest, "watch_provider_error", err.Error())
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusAccepted, result)
|
|
}
|
|
|
|
func (h *WatchProviderHandler) HandleListSyncRuns(w http.ResponseWriter, r *http.Request) {
|
|
userID, profileID, provider, ok := watchProviderRequestScope(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
limit := 10
|
|
if raw := r.URL.Query().Get("limit"); raw != "" {
|
|
parsed, err := strconv.Atoi(raw)
|
|
if err != nil || parsed <= 0 {
|
|
writeError(w, http.StatusBadRequest, "bad_request", "Invalid limit")
|
|
return
|
|
}
|
|
limit = parsed
|
|
}
|
|
runs, err := h.service.ListSyncRuns(r.Context(), userID, profileID, provider, limit)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "watch_provider_error", err.Error())
|
|
return
|
|
}
|
|
if runs == nil {
|
|
runs = []watchsync.SyncRun{}
|
|
}
|
|
writeJSON(w, http.StatusOK, struct {
|
|
Runs []watchsync.SyncRun `json:"runs"`
|
|
}{Runs: runs})
|
|
}
|
|
|
|
func (h *WatchProviderHandler) connectionStatus(w http.ResponseWriter, r *http.Request) (watchsync.ConnectionStatus, bool) {
|
|
userID, profileID, provider, ok := watchProviderRequestScope(w, r)
|
|
if !ok {
|
|
return watchsync.ConnectionStatus{}, false
|
|
}
|
|
status, err := h.service.GetConnectionStatus(r.Context(), userID, profileID, provider)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "watch_provider_error", err.Error())
|
|
return watchsync.ConnectionStatus{}, false
|
|
}
|
|
return status, true
|
|
}
|
|
|
|
func watchProviderRequestScope(w http.ResponseWriter, r *http.Request) (int, string, string, bool) {
|
|
userID := apimw.GetUserID(r.Context())
|
|
profileID := apimw.GetProfileID(r.Context())
|
|
provider := chi.URLParam(r, "provider")
|
|
if userID == 0 {
|
|
writeError(w, http.StatusUnauthorized, "unauthorized", "Authentication required")
|
|
return 0, "", "", false
|
|
}
|
|
if profileID == "" {
|
|
writeError(w, http.StatusBadRequest, "profile_required", "Profile is required")
|
|
return 0, "", "", false
|
|
}
|
|
if provider == "" {
|
|
writeError(w, http.StatusBadRequest, "bad_request", "Provider is required")
|
|
return 0, "", "", false
|
|
}
|
|
return userID, profileID, provider, true
|
|
}
|