0.2.45 - Server side git commits
This commit is contained in:
@@ -26,8 +26,8 @@ const adminCookieName = "memby_admin"
|
||||
// a valid session, so the only thing it can extend is its own sign-in.
|
||||
const adminActivityHeader = "X-Memby-Admin-Active"
|
||||
|
||||
// adminRoutes is the operator interface: library imports, the maintenance switch, and
|
||||
// row engagement. Disabled entirely when MEMBY_ADMIN_TOKEN is unset, so it cannot be
|
||||
// adminRoutes is the operator interface: library imports, maintenance and analytics.
|
||||
// Disabled entirely when MEMBY_ADMIN_TOKEN is unset, so it cannot be
|
||||
// left exposed by accident.
|
||||
func (s *Server) adminRoutes() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
@@ -57,6 +57,7 @@ func (s *Server) adminRoutes() http.Handler {
|
||||
mux.Handle("PUT /admin/api/accounts/{userID}/themes", s.adminAuth(s.handleAdminUserThemes))
|
||||
mux.Handle("GET /admin/api/recommendations", s.adminAuth(s.handleAdminRecommendations))
|
||||
mux.Handle("GET /admin/api/analytics", s.adminAuth(s.handleAdminAnalytics))
|
||||
mux.Handle("GET /admin/api/journeys", s.adminAuth(s.handleAdminJourneys))
|
||||
mux.Handle("GET /admin/api/searches", s.adminAuth(s.handleAdminSearches))
|
||||
mux.Handle("GET /admin/api/events", s.adminAuth(s.handleAdminEvents))
|
||||
mux.Handle("GET /admin/api/runtime", s.adminAuth(s.handleAdminRuntime))
|
||||
@@ -720,6 +721,8 @@ func (s *Server) handleAdminAnalytics(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusInternalServerError, "could not read analytics")
|
||||
return
|
||||
}
|
||||
// Keep the journey fields on this older endpoint for scripts written before journeys
|
||||
// gained their own page and endpoint. The console itself only reads rows from here.
|
||||
users, err := s.store.AnalyticsUsers(r.Context(), since)
|
||||
if err != nil {
|
||||
s.loggerFor(r.Context()).Error("user analytics failed", "error", err)
|
||||
@@ -733,7 +736,7 @@ func (s *Server) handleAdminAnalytics(w http.ResponseWriter, r *http.Request) {
|
||||
paths, pathErr := s.store.UserPaths(r.Context(), userID, since)
|
||||
events, eventErr := s.store.UserJourneyEvents(r.Context(), userID, since, 1000)
|
||||
if featureErr != nil || pathErr != nil || eventErr != nil {
|
||||
s.loggerFor(r.Context()).Error("user journey read failed", "user_id", userID)
|
||||
s.loggerFor(r.Context()).Error("legacy user journey read failed", "user_id", userID)
|
||||
writeError(w, http.StatusInternalServerError, "could not read user journey")
|
||||
return
|
||||
}
|
||||
@@ -745,6 +748,42 @@ func (s *Server) handleAdminAnalytics(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, payload)
|
||||
}
|
||||
|
||||
func (s *Server) handleAdminJourneys(w http.ResponseWriter, r *http.Request) {
|
||||
days := queryInt(r, "days", 30, 90)
|
||||
since := time.Now().UTC().AddDate(0, 0, -days)
|
||||
userID := strings.TrimSpace(r.URL.Query().Get("userId"))
|
||||
users, err := s.store.AnalyticsUsers(r.Context(), since)
|
||||
if err != nil {
|
||||
s.loggerFor(r.Context()).Error("journey users failed", "error", err)
|
||||
writeError(w, http.StatusInternalServerError, "could not read journeys")
|
||||
return
|
||||
}
|
||||
stats, statsErr := s.store.JourneyStats(r.Context(), userID, since)
|
||||
features, featureErr := s.store.UserFeatureStats(r.Context(), userID, since)
|
||||
paths, pathErr := s.store.UserPaths(r.Context(), userID, since)
|
||||
actions, actionErr := s.store.JourneyActionStats(r.Context(), userID, since)
|
||||
if statsErr != nil || featureErr != nil || pathErr != nil || actionErr != nil {
|
||||
s.loggerFor(r.Context()).Error("journey analytics read failed", "user_id", userID)
|
||||
writeError(w, http.StatusInternalServerError, "could not read journeys")
|
||||
return
|
||||
}
|
||||
payload := map[string]any{
|
||||
"days": days, "retentionDays": int(s.cfg.AnalyticsRetention / (24 * time.Hour)),
|
||||
"users": users, "stats": stats, "features": features, "paths": paths, "actions": actions,
|
||||
}
|
||||
if userID != "" {
|
||||
events, eventErr := s.store.UserJourneyEvents(r.Context(), userID, since, 1000)
|
||||
if eventErr != nil {
|
||||
s.loggerFor(r.Context()).Error("user journey read failed", "user_id", userID)
|
||||
writeError(w, http.StatusInternalServerError, "could not read user journey")
|
||||
return
|
||||
}
|
||||
payload["userId"] = userID
|
||||
payload["events"] = events
|
||||
}
|
||||
writeJSON(w, http.StatusOK, payload)
|
||||
}
|
||||
|
||||
// syncerHandle is the slice of the syncer the API needs, so api does not depend on the
|
||||
// concrete type for testing.
|
||||
type syncerHandle interface {
|
||||
|
||||
Reference in New Issue
Block a user