173 lines
4.9 KiB
Go
173 lines
4.9 KiB
Go
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.g3e.fr/H6N/account/internal/snapshot"
|
|
"git.g3e.fr/H6N/account/internal/store"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
func (h *SnapshotHandler) verifyAccountOwner(w http.ResponseWriter, r *http.Request) (int32, int32, bool) {
|
|
ownerID, ok := requireOwner(w, r)
|
|
if !ok {
|
|
return 0, 0, false
|
|
}
|
|
accountID, err := parseID(r)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid account id")
|
|
return 0, 0, false
|
|
}
|
|
if err := h.engine.Store().VerifyAccountOwner(r.Context(), accountID, ownerID); errors.Is(err, pgx.ErrNoRows) {
|
|
writeError(w, http.StatusNotFound, "account not found")
|
|
return 0, 0, false
|
|
} else if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to verify account")
|
|
return 0, 0, false
|
|
}
|
|
return ownerID, accountID, true
|
|
}
|
|
|
|
type SnapshotHandler struct {
|
|
engine *snapshot.Engine
|
|
}
|
|
|
|
func NewSnapshotHandler(e *snapshot.Engine) *SnapshotHandler {
|
|
return &SnapshotHandler{engine: e}
|
|
}
|
|
|
|
func (h *SnapshotHandler) RegisterRoutes(mux *http.ServeMux) {
|
|
mux.HandleFunc("GET /accounts/{id}/snapshots", h.listAccountSnapshots)
|
|
mux.HandleFunc("GET /accounts/{id}/snapshots/positions", h.listPositionSnapshots)
|
|
mux.HandleFunc("POST /accounts/{id}/snapshots/recompute", h.recomputeAccount)
|
|
mux.HandleFunc("GET /snapshots/pending", h.listPending)
|
|
}
|
|
|
|
// parseDateParam parse un paramètre de query YYYY-MM-DD. Retourne time.Time{} si absent.
|
|
func parseDateParam(r *http.Request, key string) (time.Time, error) {
|
|
v := r.URL.Query().Get(key)
|
|
if v == "" {
|
|
return time.Time{}, nil
|
|
}
|
|
return time.Parse("2006-01-02", v)
|
|
}
|
|
|
|
// GET /snapshots/pending
|
|
// Liste les comptes de l'owner ayant une invalidation en attente.
|
|
func (h *SnapshotHandler) listPending(w http.ResponseWriter, r *http.Request) {
|
|
ownerID, ok := requireOwner(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
invalidations, err := h.engine.Store().GetInvalidationsForOwner(r.Context(), ownerID)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
type row struct {
|
|
AccountID int32 `json:"account_id"`
|
|
RecomputeFrom string `json:"recompute_from"`
|
|
}
|
|
out := make([]row, 0, len(invalidations))
|
|
for _, inv := range invalidations {
|
|
out = append(out, row{
|
|
AccountID: inv.AccountID,
|
|
RecomputeFrom: inv.RecomputeFrom.Format("2006-01-02"),
|
|
})
|
|
}
|
|
writeJSON(w, http.StatusOK, out)
|
|
}
|
|
|
|
// GET /accounts/{id}/snapshots?from=YYYY-MM-DD&to=YYYY-MM-DD
|
|
// Liste les snapshots agrégés (valeur totale) d'un compte.
|
|
// from/to optionnels : sans borne → pas de filtre de ce côté.
|
|
func (h *SnapshotHandler) listAccountSnapshots(w http.ResponseWriter, r *http.Request) {
|
|
_, accountID, ok := h.verifyAccountOwner(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
from, err := parseDateParam(r, "from")
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid from date")
|
|
return
|
|
}
|
|
to, err := parseDateParam(r, "to")
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid to date")
|
|
return
|
|
}
|
|
|
|
rows, err := h.engine.Store().ListAccountSnapshots(r.Context(), accountID, from, to)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if rows == nil {
|
|
rows = []store.AccountSnapshotRow{}
|
|
}
|
|
writeJSON(w, http.StatusOK, rows)
|
|
}
|
|
|
|
// GET /accounts/{id}/snapshots/positions?from=YYYY-MM-DD&to=YYYY-MM-DD
|
|
// Liste les positions détaillées (par instrument) d'un compte.
|
|
func (h *SnapshotHandler) listPositionSnapshots(w http.ResponseWriter, r *http.Request) {
|
|
_, accountID, ok := h.verifyAccountOwner(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
from, err := parseDateParam(r, "from")
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid from date")
|
|
return
|
|
}
|
|
to, err := parseDateParam(r, "to")
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid to date")
|
|
return
|
|
}
|
|
|
|
rows, err := h.engine.Store().ListPositionSnapshots(r.Context(), accountID, from, to)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
if rows == nil {
|
|
rows = []store.PositionSnapshotRow{}
|
|
}
|
|
writeJSON(w, http.StatusOK, rows)
|
|
}
|
|
|
|
// POST /accounts/{id}/snapshots/recompute
|
|
// Recalcule les snapshots du compte depuis sa date d'invalidation jusqu'à aujourd'hui.
|
|
// Retourne 204 si aucune invalidation n'est en attente.
|
|
func (h *SnapshotHandler) recomputeAccount(w http.ResponseWriter, r *http.Request) {
|
|
_, accountID, ok := h.verifyAccountOwner(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
from, to, err := h.engine.RecomputeAccount(r.Context(), accountID)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
writeError(w, http.StatusNotFound, "account not found")
|
|
return
|
|
}
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
|
|
if from.IsZero() {
|
|
w.WriteHeader(http.StatusNoContent) // aucune invalidation en attente
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"account_id": accountID,
|
|
"from": from.Format("2006-01-02"),
|
|
"to": to.Format("2006-01-02"),
|
|
"status": "ok",
|
|
})
|
|
}
|