generated from VLADIMIR/template
112 lines
4.4 KiB
Go
112 lines
4.4 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/subtle"
|
|
"encoding/json"
|
|
"errors"
|
|
"evening_detective_server/internal/services/team_service"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// TeamRegistrationHandler принимает вебхук Яндекс Форм отдельно от пользовательского JWT.
|
|
func TeamRegistrationHandler(token string, register func(context.Context, int, string, team_service.Registration) (int, error)) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
fail := func(code int, message string) {
|
|
w.WriteHeader(code)
|
|
_ = json.NewEncoder(w).Encode(map[string]string{"error": message})
|
|
}
|
|
if token == "" {
|
|
fail(http.StatusServiceUnavailable, "Интеграция регистрации не настроена")
|
|
return
|
|
}
|
|
if subtle.ConstantTimeCompare([]byte(r.Header.Get("Authorization")), []byte("Bearer "+token)) != 1 {
|
|
fail(http.StatusUnauthorized, "Неверный токен интеграции")
|
|
return
|
|
}
|
|
gameID, err := strconv.ParseInt(r.PathValue("id"), 10, 32)
|
|
deliveryID := r.Header.Get("X-Delivery-Id")
|
|
if err != nil || gameID <= 0 || deliveryID == "" || len(deliveryID) > 255 {
|
|
fail(http.StatusBadRequest, "Требуются ID игры и заголовок X-Delivery-Id (до 255 байт)")
|
|
return
|
|
}
|
|
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<10))
|
|
var payload json.RawMessage
|
|
if err := decoder.Decode(&payload); err != nil {
|
|
fail(http.StatusBadRequest, fmt.Sprintf("Некорректный JSON регистрации: %v", err))
|
|
return
|
|
}
|
|
var extra any
|
|
if err := decoder.Decode(&extra); err != io.EOF {
|
|
if err == nil {
|
|
fail(http.StatusBadRequest, "Некорректный JSON регистрации: после объекта обнаружены лишние данные")
|
|
} else {
|
|
fail(http.StatusBadRequest, fmt.Sprintf("Некорректный JSON регистрации: после объекта обнаружены лишние данные: %v", err))
|
|
}
|
|
return
|
|
}
|
|
registration, rpcID, isRPC, err := decodeRegistrationPayload(payload)
|
|
if err != nil {
|
|
fail(http.StatusBadRequest, fmt.Sprintf("Некорректный JSON регистрации: %v", err))
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(r.Context(), 4*time.Second)
|
|
defer cancel()
|
|
id, err := register(ctx, int(gameID), deliveryID, registration)
|
|
switch {
|
|
case errors.Is(err, team_service.ErrInvalidRegistration):
|
|
fail(http.StatusBadRequest, err.Error())
|
|
case errors.Is(err, team_service.ErrRegistrationClosed):
|
|
fail(http.StatusConflict, err.Error())
|
|
case err != nil:
|
|
fail(http.StatusInternalServerError, "Не удалось зарегистрировать команду")
|
|
default:
|
|
if isRPC {
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"jsonrpc": "2.0", "result": map[string]int{"id": id}, "id": rpcID})
|
|
} else {
|
|
_ = json.NewEncoder(w).Encode(map[string]int{"id": id})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func decodeRegistrationPayload(payload json.RawMessage) (team_service.Registration, json.RawMessage, bool, error) {
|
|
var fields map[string]json.RawMessage
|
|
if err := json.Unmarshal(payload, &fields); err != nil {
|
|
return team_service.Registration{}, nil, false, err
|
|
}
|
|
if rawRPC, ok := fields["jsonrpc"]; ok {
|
|
var version string
|
|
if err := json.Unmarshal(rawRPC, &version); err != nil || version != "2.0" {
|
|
return team_service.Registration{}, nil, true, errors.New(`поле "jsonrpc" должно быть равно "2.0"`)
|
|
}
|
|
params, ok := fields["params"]
|
|
if !ok {
|
|
return team_service.Registration{}, nil, true, errors.New(`в JSON-RPC запросе отсутствует поле "params"`)
|
|
}
|
|
var registration team_service.Registration
|
|
if err := decodeStrict(params, ®istration); err != nil {
|
|
return team_service.Registration{}, nil, true, fmt.Errorf("поле params: %w", err)
|
|
}
|
|
return registration, fields["id"], true, nil
|
|
}
|
|
var registration team_service.Registration
|
|
if err := decodeStrict(payload, ®istration); err != nil {
|
|
return team_service.Registration{}, nil, false, err
|
|
}
|
|
return registration, nil, false, nil
|
|
}
|
|
|
|
func decodeStrict(data []byte, target any) error {
|
|
decoder := json.NewDecoder(bytes.NewReader(data))
|
|
decoder.DisallowUnknownFields()
|
|
return decoder.Decode(target)
|
|
}
|