Nik Afiq 8f7024edfa
All checks were successful
CI / changes (push) Successful in 19s
CI / test (push) Successful in 24s
CI / build-ai-gateway (push) Successful in 1m6s
CI / build-ha-gateway (push) Successful in 1m3s
CI / build-discord-bot (push) Successful in 1m4s
CI / build-alexa-bridge (push) Successful in 1m15s
CI / build-tts-gateway (push) Successful in 1m5s
CI / build-tts-sidecar (push) Has been skipped
CI / build-tts-model (push) Has been skipped
feat(climate): add SetTemperature method to ClimateService
- Implemented SetTemperature in ClimateService for setting an absolute target temperature.
- Updated ClimateServiceClient and ClimateServiceServer interfaces to include SetTemperature.
- Added corresponding handler and tests for SetTemperature in ClimateGRPC.
- Modified ClimateApp to handle SetTemperature requests without clamping.
- Updated climate.proto to define SetTemperatureRequest message.
- Adjusted Dockerfiles to include alexa-bridge dependencies.
2026-07-25 12:08:24 +09:00

81 lines
2.3 KiB
Go

package alexa
import (
"context"
"encoding/json"
"io"
"net/http"
"gitea.nik4nao.com/nik/home-services/alexa-bridge/internal/logger"
)
// Dispatcher handles one already-validated Alexa request and produces a
// response. internal/directive.Router implements this.
type Dispatcher interface {
Dispatch(ctx context.Context, req Request) (Response, error)
}
// Handler is the HTTP entrypoint Alexa's servers call into: it verifies the
// request signature, timestamp, and skill ID (in that order — cheapest and
// most identity-establishing checks first), then hands the decoded envelope
// to a Dispatcher.
type Handler struct {
validator Validator
skillID string
dispatcher Dispatcher
}
// NewHandler constructs the HTTP handler for the Alexa endpoint.
func NewHandler(validator Validator, skillID string, dispatcher Dispatcher) *Handler {
return &Handler{validator: validator, skillID: skillID, dispatcher: dispatcher}
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
log := logger.FromContext(ctx)
body, err := io.ReadAll(r.Body)
if err != nil {
log.Warn("read request body failed", "err", err)
http.Error(w, "bad request", http.StatusBadRequest)
return
}
if err := h.validator.Validate(ctx, r, body); err != nil {
log.Warn("signature validation failed", "err", err)
http.Error(w, "signature verification failed", http.StatusUnauthorized)
return
}
var req Request
if err := json.Unmarshal(body, &req); err != nil {
log.Warn("decode request body failed", "err", err)
http.Error(w, "bad request", http.StatusBadRequest)
return
}
if err := ValidateTimestamp(req.Request.Timestamp); err != nil {
log.Warn("timestamp validation failed", "err", err)
http.Error(w, "request timestamp outside tolerance", http.StatusUnauthorized)
return
}
if err := ValidateApplicationID(&req, h.skillID); err != nil {
log.Warn("application id validation failed", "err", err)
http.Error(w, "application id mismatch", http.StatusUnauthorized)
return
}
resp, err := h.dispatcher.Dispatch(ctx, req)
if err != nil {
log.Error("dispatch failed", "err", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(resp); err != nil {
log.Error("encode response failed", "err", err)
}
}