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", }) }