dana-medical-site/internal/api/onec_webhook.go

82 lines
2.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package api
import (
"crypto/sha256"
"crypto/subtle"
"encoding/json"
"net/http"
"strings"
"time"
)
const maxOnecEventBody = 8 << 10
func (a *API) WithOneCWebhook(key string) { a.onecHook = strings.TrimSpace(key) }
func (a *API) onecAutonomousEnabled() bool { return a.onecHook != "" }
type onecEvent struct {
Event string `json:"event"`
DoctorID string `json:"doctor_id"`
AppointmentID string `json:"appointment_id"`
Status string `json:"status"`
BranchID string `json:"branch_id"`
Start *time.Time `json:"start"`
End *time.Time `json:"end"`
}
func sameKey(got, want string) bool {
g, w := sha256.Sum256([]byte(got)), sha256.Sum256([]byte(want))
return subtle.ConstantTimeCompare(g[:], w[:]) == 1
}
func (a *API) onecEvents(w http.ResponseWriter, r *http.Request) {
if !a.onecAutonomousEnabled() {
fail(w, 404, "not_found", "Не найдено")
return
}
ip := clientIP(r)
if a.tooMany(w, "onec-ev:"+ip, 300, time.Minute) {
return
}
if !a.checkOnecKey(r) {
if a.tooMany(w, "onec-bad:"+ip, 20, 10*time.Minute) {
return
}
fail(w, 401, "unauthorized", "Неверный ключ")
return
}
var ev onecEvent
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxOnecEventBody)).Decode(&ev); err != nil {
fail(w, 400, "bad_json", "Не удалось разобрать событие")
return
}
switch ev.Event {
case "schedule_changed", "directory_changed":
if err := a.store.RequestOnecSnapshot(r.Context()); err != nil {
handleErr(w, err)
return
}
writeJSON(w, http.StatusAccepted, envelope{Data: map[string]any{"ok": true, "queued": "push"}})
case "appointment_status":
if ev.AppointmentID == "" || (ev.Status != "booked" && ev.Status != "arrived" && ev.Status != "cancelled") {
fail(w, 400, "validation", "Нужны appointment_id и status: booked, arrived или cancelled")
return
}
if (ev.Start == nil) != (ev.End == nil) || (ev.Start != nil && !ev.Start.Before(*ev.End)) {
fail(w, 400, "validation", "Некорректный интервал события")
return
}
_, _, known, err := a.store.ApplyOnecAppointmentEvent(r.Context(), ev.AppointmentID, ev.Status, ev.DoctorID, ev.BranchID, ev.Start, ev.End)
if err != nil {
handleErr(w, err)
return
}
if err = a.store.RequestOnecSnapshot(r.Context()); err != nil {
handleErr(w, err)
return
}
ok(w, map[string]any{"ok": true, "known": known}, nil)
default:
fail(w, 400, "validation", "Неизвестное событие: допустимы schedule_changed, directory_changed, appointment_status")
}
}