package api import ( "net/http" "strconv" "strings" "reanimator/internal/repository/timeline" ) func writeTimelineResponse(w http.ResponseWriter, r *http.Request, repo *timeline.EventRepository, subjectType string, subjectID int64) { limit := 50 if raw := r.URL.Query().Get("limit"); raw != "" { if parsed, err := strconv.Atoi(raw); err == nil { limit = parsed } else { writeError(w, http.StatusBadRequest, "invalid limit") return } } var cursor *timeline.Cursor if raw := r.URL.Query().Get("cursor"); raw != "" { decoded, err := timeline.DecodeCursor(raw) if err != nil { writeError(w, http.StatusBadRequest, "invalid cursor") return } cursor = &decoded } items, next, err := repo.List(r.Context(), subjectType, subjectID, limit, cursor) if err != nil { writeError(w, http.StatusInternalServerError, "list timeline failed") return } var nextCursor *string if next != nil { value := timeline.EncodeCursor(*next) nextCursor = &value } writeJSON(w, http.StatusOK, map[string]any{ "items": items, "next_cursor": nextCursor, }) } func parseTimelineID(path, prefix string) int64 { if !strings.HasPrefix(path, prefix) || !strings.HasSuffix(path, "/timeline") { return 0 } trimmed := strings.TrimPrefix(path, prefix) trimmed = strings.TrimSuffix(trimmed, "/timeline") if trimmed == "" || strings.Contains(trimmed, "/") { return 0 } id, err := strconv.ParseInt(trimmed, 10, 64) if err != nil || id <= 0 { return 0 } return id }