Commit Graph
258 Commits
Author SHA1 Message Date
nutcas3 71ae1925f1 feat: Add subscriber account and usage recording API endpoints with validation
- Add GET /v1/subscriber/:imsi endpoint to retrieve subscriber account details
- Add POST /v1/usage endpoint to record usage events with session validation
- Remove unused validate_imsi import and dummy validation calls from check_credit
- Add validate_amount call for bytes_used in deduct_credit
- Add balance retrieval logging and fix IP clone in get_balance response
- Add IMSI/session_id validation and error handling for missing fields in record_usage
- Return subscriber
2026-04-25 01:22:12 +03:00
nutcas3 8076d9b79a feat: Migrate web-dashboard CI workflow from npm to pnpm
- Replace npm cache with pnpm setup and store caching in CI workflow
- Add pnpm/action-setup@v4 with version 9
- Configure pnpm store path caching with pnpm-lock.yaml hash key
- Update install command to use pnpm install --frozen-lockfile instead of npm ci
- Update lint and build commands to use pnpm run instead of npm run
2026-04-25 01:19:02 +03:00
nutcas3 ad417defbf feat: Add validation calls and error logging to API, credit management, billing, and sync operations
- Add validate_imsi/validate_session_id/validate_amount/log_error imports to api/credit_management/rating_billing/sync modules
- Add validation calls for IMSI in get_subscriber_account and session_id in record_usage_event
- Add dummy validation calls in check_credit to prevent unused function removal
- Add validate_amount call after cost calculation in calculate_usage_cost
- Add log_error call in check
2026-04-25 01:14:13 +03:00
nutcas3 79f217ff1b feat: Fix formatting, type conversion, and test assertions in middleware and monitoring modules
- Add fmt import and fix X-RateLimit-Limit header conversion from rune to string in ratelimit middleware
- Fix whitespace alignment for rate limiter variable declarations and JSON error responses
- Update mock expectations in redis_ratelimit_test to use explicit argument type matchers
- Add severity validation check in alerting_test rule validation
- Fix health_test assertions for degraded/unhealthy counts
2026-04-25 01:07:52 +03:00
nutcas3 9d3b8428bd feat: Remove unused imports across charging engine modules
- Remove unused IntoResponse/warn/error/ChargingError/validate_* imports from api
- Remove unused AsyncCommands/error/ChargingSession/ErrorContext imports from credit_management
- Remove unused ErrorContext/ChargingError imports from engine
- Remove unused ErrorContext import from rating_billing and prefix unused variables with underscore
- Remove unused ErrorContext import from rating_plans_repo
- Remove unused log_error/validate_amount/validate_im
2026-04-25 01:06:30 +03:00
nutcas3 eb8e7bc48a feat: Add Redis serialization trait implementations for SubscriberAccount and UsageEvent types
- Add redis::FromRedisValue/ToRedisArgs/ToSingleRedisArg imports
- Implement FromRedisValue for SubscriberAccount with JSON deserialization and ParsingError mapping
- Implement ToRedisArgs for SubscriberAccount with JSON serialization
- Implement ToSingleRedisArg for SubscriberAccount
- Implement FromRedisValue for UsageEvent with JSON deserialization and ParsingError mapping
- Implement ToRedisArgs for UsageEvent with JSON serial
2026-04-25 00:52:19 +03:00
nutcas3 2bd7e19f09 feat: Replace context error handling with map_err for ChargingError in RatingPlansRepo Postgres operations
- Replace context with map_err for InternalError on Postgres connection
- Replace context with map_err for RedisOperation on schema migration/fetch/list/upsert/deactivate operations
2026-04-25 00:51:27 +03:00
nutcas3 dd12518786 feat: Refactor rating_billing to use async rating plans from Postgres and multiplexed Redis connections
- Remove RatingPlan import and update get_rating_plan call to async with await
- Update generate_invoice to use get_multiplexed_async_connection instead of get_async_connection
- Replace context error handling with map_err for RedisConnection/RedisOperation errors
- Add explicit redis::AsyncCommands trait qualification for keys/get/set operations
- Update apply_monthly_fees to use async get_rating
2026-04-25 00:51:15 +03:00
nutcas3 c3522cad3a feat: Refactor ChargingEngine to use RatingPlansRepo for Postgres-backed rating plans and multiplexed Redis connections
- Replace in-memory HashMap rating plans with RatingPlansRepo for Postgres-backed storage
- Update constructor to accept RatingPlansRepo and remove hardcoded default plans (basic/premium/enterprise)
- Make redis_client/plans/sync_interval/startup_time fields public(crate) for module access
- Convert get_rating_plan/add_rating_plan/remove_rating_plan/list_rating_plans to async methods using
2026-04-25 00:51:01 +03:00
nutcas3 f0bbbc4459 feat: Refactor credit management to use multiplexed Redis connections and explicit AsyncCommands trait for improved connection pooling
- Update all Redis operations to use get_multiplexed_async_connection instead of get_async_connection for better connection pooling
- Replace context error handling with map_err for RedisConnection/RedisOperation errors
- Add explicit redis::AsyncCommands trait qualification for all Redis operations (get/set/del/incr/decr/expire)
- Add ChargingSession import to types
2026-04-25 00:50:42 +03:00
nutcas3 0fc60e553d feat: Remove operations module from monitoring module declarations
- Remove operations module declaration from monitoring/mod.rs
2026-04-25 00:50:31 +03:00
nutcas3 20bb3239b2 feat: Initialize RatingPlansRepo from DATABASE_URL and seed defaults before creating ChargingEngine
- Add RatingPlansRepo import from charging module
- Add DATABASE_URL environment variable requirement for rating plans
- Connect to Postgres via RatingPlansRepo::connect and seed default rating plans
- Pass plans_repo to ChargingEngine::new constructor
- Add info log for Postgres connection and default rating plans
2026-04-25 00:49:33 +03:00
nutcas3 75d00704b9 feat: Add RatingPlansRepo module and public re-export to charging module
- Add rating_plans_repo module declaration
- Add RatingPlansRepo re-export from rating_plans_repo
2026-04-25 00:49:23 +03:00
nutcas3 49e132cecb feat: Add Clone derive to AppState and cast balance values to i64 in API responses
- Add Clone derive to AppState struct
- Cast remaining_bytes to i64 in CreditCheckResponse
- Cast balance_bytes to i64 in BalanceResponse
2026-04-25 00:48:50 +03:00
nutcas3 181ddf2969 feat: Add public re-exports for ChargingError and helper functions to errors module
- Add ChargingError re-export from types
- Add log_error/validate_amount/validate_bytes/validate_imsi/validate_ip/validate_session_id/ChargingResult/ErrorContext re-exports from helpers
2026-04-25 00:48:41 +03:00
nutcas3 5fa329e4e0 feat: Refactor monitoring sync module to use multiplexed Redis connections and explicit AsyncCommands trait for improved connection pooling
- Update all Redis operations to use get_multiplexed_async_connection instead of get_async_connection for better connection pooling
- Replace context error handling with map_err for RedisConnection/RedisOperation errors
- Add explicit redis::AsyncCommands trait qualification for all Redis operations (keys/get/set/del/ttl/expire)
- Add chrono::Datelike import
2026-04-25 00:48:27 +03:00
nutcas3 1f90b27d34 feat: Add ErrorContext trait implementation for anyhow::Result to enable context error handling with ChargingError conversion
- Add ChargingError import from super::types
- Add ErrorContext<T> implementation for anyhow::Result<T> with with_context method that maps errors to ChargingError::InternalError with formatted context message
2026-04-25 00:48:16 +03:00
nutcas3 5c03c48e22 feat: Remove system statistics and health check methods from monitoring module and update Redis connection handling
- Remove get_system_statistics/get_uptime/health_check/get_memory_usage/get_process_memory/estimate_memory_usage/get_redis_memory_usage methods from ChargingEngine impl
- Remove SystemStats/HealthStatus struct definitions
- Update get_performance_metrics/calculate_rps/calculate_avg_response_time/get_error_statistics/get_last_error/get_redis_latency to use get_multiplexed_async_connection instead of get_async_connection
- Replace context error handling with map
2026-04-25 00:47:45 +03:00
nutcas3 0e41a05cb0 feat: Remove charging-engine monitoring operations module and clean up module declarations
- Remove monitoring/operations.rs with background sync implementation (start_background_sync/perform_sync/cleanup_expired_blocks/update_usage_statistics/check_low_balance_alerts/apply_monthly_fees_if_needed methods)
- Remove charging/mod.rs module declaration with types/engine/credit_management/rating_billing submodules and ChargingEngine re-export
- Remove errors/mod.rs module declaration with types/helpers
2026-04-25 00:47:23 +03:00
nutcas3 758bb8ad15 feat: Add RatingPlansRepo with Postgres persistence for rating plan management shared between charging-engine and api-server
- Add RatingPlansRepo struct with PgPool for Postgres-backed rating plan storage
- Add connect method with max_connections=10 and idempotent schema creation for rating_plans table with plan_id/name/data_rate/voice_rate/sms_rate/monthly_fee/data_limit/voice_limit/sms_limit/is_active/created_at/updated_at columns
- Add seed_defaults with basic/premium/enterprise plans (data
2026-04-24 09:09:05 +03:00
nutcas3 3f19a5135c feat: Remove redundant chrono dependency as it's already included via sqlx features
- Remove standalone chrono 0.4.44 dependency from Cargo.toml
- Chrono functionality remains available through sqlx's chrono feature
2026-04-24 09:06:20 +03:00
nutcas3 eff14e3718 feat: Update workspace dependencies to latest versions and add sqlx/chrono for Postgres persistence
- Update tokio from 1.40 to 1.52.1, async-std from 1.13 to 1.13.2
- Update aya from 0.13 to 0.13.1, aya-ebpf from 0.1 to 0.1.1, aya-log from 0.2 to 0.2.1, aya-log-ebpf to 0.1.0
- Update serde from 1.0 to 1.0.228, serde_json from 1.0 to 1.0.149
- Update redis from 0.27 to 1.2.0 with tokio-comp/connection-manager/cluster-async features
- Add sqlx 0.8.6 with runtime-tokio-rustls/postgres/chrono/uuid/migrate features
2026-04-24 09:02:45 +03:00
nutcas3 4f55835dfa feat: Replace in-memory profile storage with Postgres persistence using GORM driver and add database connection validation
- Add gorm.io/driver/postgres v1.6.0 and gorm.io/gorm v1.31.1 dependencies with transitive dependencies (jackc/pgx/v5, jackc/puddle, jinzhu/inflection, jinzhu/now, golang.org/x/crypto/sync/text)
- Replace NewInMemoryProfileStore with NewPostgresProfileStore using DATABASE_DSN environment variable
- Add fatal error handling for missing DATABASE_DSN and Postgres connection failures
- Add defer
2026-04-24 09:00:10 +03:00
nutcas3 d27eb871e3 feat: Add DatabaseConfig to carrier-connector config and remove InMemoryProfileStore implementation for Postgres migration
- Add DatabaseConfig struct with DSN field for Postgres connection string
- Add package documentation comments for ES2Config/DatabaseConfig/LoadES2Config
- Normalize ES2Config field alignment for BaseURL/APIKey/InsecureSkipVerify/FunctionalityRequesterID
- Remove InMemoryProfileStore struct and all methods (Create/Get/List/UpdateState/Delete) from profile.go
- Remove sync package import
2026-04-24 08:59:44 +03:00
nutcas3 cb8b6c4d96 feat: Add PostgresProfileStore repository with GORM-based persistence for eSIM profile lifecycle management
- Add profileRow struct with ICCID/EID/IMSI/MCC/MNC/ProfileType/State/TenantID/ActivationCode/CreatedAt/UpdatedAt fields mapped to esim_profiles table with indexes on EID/IMSI/State/TenantID
- Add NewPostgresProfileStore with DSN validation, GORM connection setup with Warn logging, and AutoMigrate for esim_profiles schema
- Add Create/Get/List/UpdateState/Delete methods implementing ProfileRepository interface with
2026-04-24 08:59:09 +03:00
nutcas3 c0034b195a feat: Refactor api-server main.go to extract route setup and handlers into separate packages for improved modularity
- Move route registration logic to registerV1Routes with dependency injection pattern
- Extract metrics collector initialization to buildMetricsCollector helper function
- Extract service/handler dependency initialization to buildDeps helper function
- Remove all inline handler implementations (auth/services/monitoring/deployments/plugins/automation/config/chaos/billing routes)
- Remove all metrics collection helper functions (startMetricsCollection/
2026-04-24 08:58:53 +03:00
nutcas3 000640149d feat: Remove package documentation from platform.go and normalize imports formatting
- Remove package-level documentation comment mapping handler files to domains (services/monitoring/deployments/plugins/automation/config/chaos/billing)
- Normalize imports from inline to block format (net/http, strconv, github.com/gin-gonic/gin)
- Retain ErrorResponse struct and shared helper functions
2026-04-24 08:56:31 +03:00
nutcas3 85ed3e1598 feat: Refactor GraphQL resolvers to extract domain methods into separate files and retain only shared helpers
- Add package documentation mapping resolver files to domains (subscriber/usage/billing/mutations/esim/alerts/subscriptions)
- Remove models package import and add blank imports for context/base64 to preserve helper function dependencies
- Remove all resolver method implementations (Subscriber/SubscriberByImsi/Subscribers/SubscriberAccount/UsageEvents/UsageStats/RealTimeUsage/Invoices/Invoice
2026-04-24 08:55:28 +03:00
nutcas3 d99109386b feat: Refactor platform.go to extract domain handlers into separate files and retain only shared helpers
- Add package documentation mapping handler files to domains (services/monitoring/deployments/plugins/automation/config/chaos/billing)
- Remove gorm.io/gorm, models, monitoring, and services package imports
- Remove ServicesHandler struct and all methods (List/Get/Restart/Start/Stop/Scale/Logs/Health/PodStatus/Events)
- Remove MonitoringHandler struct and all methods (Metrics/Alerts/Health/Logs)
2026-04-24 08:54:23 +03:00
nutcas3 cb1d7171e0 feat: Add PaymentController with modular handlers for payment operations, refunds, and webhook processing
- Add PaymentController struct with paymentService/db dependencies and NewPaymentController constructor
- Add SetupRoutes with /payments endpoints (POST /top-up, POST/DELETE/GET /payment-methods, GET /transactions/:id, POST /transactions/:id/refund) and /webhooks/stripe endpoint
- Add ProcessTopUp handler with amount/paymentMethodId validation, subscriberID authentication, and PaymentService.
2026-04-24 08:53:34 +03:00
nutcas3 26731d4390 feat: Extract Stripe gateway types to separate file for improved code organization and maintainability
- Move StripeGateway struct from stripe.go to types.go
- Move PaymentRequest/PaymentResponse/CustomerResponse/RefundResponse/PaymentStatusResponse/WebhookEvent/InvoiceItem/InvoiceResponse structs to types.go
- Remove encoding/json import from stripe.go (moved to types.go)
- Remove PaymentRequest comment from types.go for consistency with other type definitions
2026-04-24 08:51:37 +03:00
nutcas3 1889e78db2 feat: Refactor SubscriberService to split monolithic service into modular files with method organization by domain
- Remove all method implementations from subscriber.go (CreateSubscriber/GetSubscriber/GetSubscriberByIMSI/UpdateSubscriber/SuspendSubscriber/TerminateSubscriber/ListSubscribers/terminateSubscriberSessions/GetAccount/ListInvoices/GetInvoice/ListRatingPlans/GetRatingPlan/ListAlerts/SearchSubscribers/ResolveAlert/CreateAlert and all other methods)
- Update SubscriberService struct comment
2026-04-24 08:51:13 +03:00
nutcas3 24586bbeb4 feat: Refactor carrier-connector main.go to extract route setup and handlers into separate packages for improved modularity
- Move setupRoutes to routes.go with ProfileRepository parameter and handler package integration
- Extract all handler functions (orderProfile/getProfile/listProfiles/deleteProfile/getCarrierInfo/checkConnectivity) to internal/handler package
- Move ProfileOrder/ProfileResponse structs to internal/handler package as shared types
- Move getEnv helper function to handler.Get
2026-04-24 08:50:41 +03:00
nutcas3 d27709222e feat: Add carrier-connector handler package with carrier info, connectivity check, and shared types for GSMA ES2+ profile management
- Add handlers package with GetCarrierInfoHandler returning static carrier name/mcc/mnc/smdpUrl/supported/lastChecked from environment config
- Add CheckConnectivityHandler with ES2+ GetProfileStatus probe using synthetic EID/ICCID and smdpConnected/checkedAt/error response
- Add ProfileOrder struct with EID/ICCID/IMSI/K/OPc/MCC/MNC/ProfileType/ConfirmationCode fields
2026-04-24 08:50:22 +03:00
nutcas3 5f6d58a0b7 feat: Add profile handlers with repository integration for eSIM profile lifecycle management via ES2+ protocol
- Add OrderProfileHandlerWithRepo with ICCID/IMSI/K/OPc validation, ES2+ DownloadProfile call, and Profile creation with ICCID/EID/IMSI/MCC/MNC/ProfileType/State/TenantID persistence
- Add GetProfileHandlerWithRepo with repository.Get/ES2+ GetProfileStatus calls and merged response with state/imsi/tenantId/createdAt/updatedAt/executionStatus/profileState/statusMessage fields
- Add ListProfilesHandlerWithRepo with pagination support, page/limit query
2026-04-24 08:49:54 +03:00
nutcas3 8acba8b1e5 feat: Add ProfileRepository interface with in-memory implementation for eSIM profile storage and CRUD operations
- Add Profile struct with ICCID/EID/IMSI/MCC/MNC/ProfileType/State/TenantID/ActivationCode/CreatedAt/UpdatedAt fields
- Add ListFilter struct with TenantID/State/Limit/Offset fields for query filtering
- Add ProfileRepository interface with Create/Get/List/UpdateState/Delete methods
- Add InMemoryProfileStore with sync.RWMutex for concurrent-safe in-memory storage
- Add NewInMemoryProfileStore constructor
2026-04-24 08:49:18 +03:00
nutcas3 11011f657b feat: Remove ES2Client implementation from main.go and update handlers to use internal/es2 package
- Remove crypto/tls and go-resty/resty/v2 imports
- Remove NewES2Client function and ES2Client.OrderProfile method from main.go
- Update orderProfileHandler to use es2.DownloadProfileRequest with EID/ConfirmationCode and convert GSMA response to ProfileResponse with ExecutionStatus/StatusMessage
- Update all handler function signatures to accept *es2.ES2Client instead of *ES2Client
- Update checkConnectivityHandler to use Get
2026-04-24 07:40:15 +03:00
nutcas3 065fe6727f feat: Refactor carrier-connector to use GSMA ES2+ protocol with dedicated client package and context-aware profile download
- Add context import and config/es2 package imports for GSMA ES2+ protocol implementation
- Remove ES2Client struct definition and move to internal/es2 package
- Update ProfileOrder struct with EID/ConfirmationCode fields for GSMA ES2+ compliance
- Update ProfileResponse struct with ExecutionStatus/StatusMessage fields replacing Status
- Add ES2Config initialization with Base
2026-04-24 07:39:17 +03:00
nutcas3 2fe8678782 feat: Add REST API server with Gin framework and comprehensive eSIM profile management endpoints
- Add Gin router initialization with custom logging formatter and recovery middleware
- Add setupRoutes function with /api/v1 prefix and health check endpoint
- Add eSIM profile endpoints: POST /profiles for ordering, GET /profiles/:profileId for status, GET /profiles for listing with pagination, DELETE /profiles/:profileId for deletion
- Add carrier management endpoints: GET /carrier/info for carrier
2026-04-24 07:35:46 +03:00
nutcas3 d9c2ad1f71 feat: Add structured JSON logging to PerformanceMiddleware slow request handler with comprehensive request metadata
- Replace fmt.Appendf with json.Marshal for structured log output
- Add logData map with level/type/method/path/query/response_time/status/user_agent/ip/timestamp fields
- Update logSlowRequest to output JSON-formatted logs with newline terminator for better log parsing and monitoring integration
2026-04-24 07:35:32 +03:00
nutcas3 3b338b0e20 feat: Add comprehensive ETag middleware with content-based generation, validation, and cache invalidation support
- Add ResponseWriterWrapper struct with Write/Body methods for response body capture
- Add ETagData/ETagGenerator structs with GenerateETag/GenerateWeakETag/GetCachedETag/CacheETag/InvalidateCache methods for sophisticated ETag generation and caching
- Add generateETag/generateContentETag/generateWeakETag helper functions with path/method/content/timestamp/user context hashing
- Add ETagValid
2026-04-24 07:34:14 +03:00
nutcas3 7ee875c544 feat: Remove ETag generation and validation middleware, simplify CacheMiddleware to basic Cache-Control header
- Remove ResponseWriterWrapper struct with Write/Body methods for response body capture
- Remove ETagData/ETagGenerator structs and all ETag generation/validation/caching logic
- Remove generateETag/generateContentETag/generateWeakETag/etagMatches helper functions
- Remove ETagValidationMiddleware/ContentBasedETagMiddleware/CacheInvalidationMiddleware/AdvancedCacheMiddleware handlers
2026-04-24 07:33:07 +03:00
nutcas3 8909126612 feat: Refactor Redis rate limiter to use interface for improved testability and remove mock client replacement pattern
- Add RedisClientInterface with Eval/Del/ZCount methods for Redis operation abstraction
- Update RedisRateLimiter/NewRedisRateLimiter/NewMultiTierRateLimiter to accept RedisClientInterface instead of concrete redis.Client
- Update MockRedisClient.Eval to properly construct call arguments with script and keys
- Update MockRedisClient.Del to accept keys as slice instead of variadic parameters
2026-04-23 03:16:00 +03:00
nutcas3 857ca98382 feat: Add comprehensive Redis rate limiter tests with mock client, middleware validation, and multi-tier support
- Add MockRedisClient struct with Eval/Del/ZCount methods for Redis operation mocking
- Add TestRedisRateLimiter_Allow with under/over limit scenarios and mock Redis response validation
- Add TestRedisRateLimitMiddleware with IP-based rate limiting and empty key bypass tests
- Add TestIPKeyExtractor/TestUserKeyExtractor/TestAPIKeyExtractor with valid/invalid input validation
- Add TestEnd
2026-04-23 03:02:00 +03:00
nutcas3 6aea8af89e feat: Add batch sync mechanism to eBPF packet filter with sync_control flag, sync_buffer map, and stats aggregation
- Add sync_control BPF_MAP_TYPE_ARRAY map with single entry for sync flag control
- Add sync_buffer BPF_MAP_TYPE_PERCPU_ARRAY map with 1024 entries for batched sync data
- Add sync_entry struct with ip/bytes/credit/blocked/valid fields for data transfer
- Update sync_stats XDP program to check sync_control flag and reset after triggering
- Add packet_stats/user_credits/block_list map
2026-04-23 03:01:39 +03:00
nutcas3 316bb76eca feat: Add eBPF manager with XDP program loading, packet stats tracking, credit management, and bidirectional Redis synchronization
- Add EbpfManager struct with bpf/interface fields and PacketStats/SyncEntry/CreditInfo structs for data transfer
- Add new/attach methods for eBPF program loading and XDP attachment with BpfLogger initialization
- Add get_packet_stats/get_credit_info methods for reading packet_stats and user_credits maps
- Add update_user_credit/block_user methods for modifying user
2026-04-23 03:01:09 +03:00
nutcas3 c32d377488 feat: Add build script for eBPF program compilation with cargo build-bpf integration
- Add build.rs with Command execution for cargo build-bpf --release
- Add status check with panic on build failure for eBPF program compilation
2026-04-23 03:00:58 +03:00
nutcas3 3d868580ae feat: Add eBPF manager integration with XDP program loading, bidirectional Redis sync, and graceful shutdown handling
- Add EbpfManager import and initialization with interface parameter and attach method for XDP program loading
- Add initial sync from Redis to eBPF maps on startup with context error handling
- Replace simulated packet processing with sync_batch_to_redis for eBPF-to-Redis data transfer and sync_from_redis for credit updates
- Add periodic stats logging (every 10 iterations) with
2026-04-23 03:00:49 +03:00
nutcas3 85926ee377 feat: Add distributed Redis-based rate limiting with sliding window, token bucket, and multi-tier support
- Add RedisRateLimiter struct with client/prefix/limit/window fields for distributed rate limit tracking
- Add RateLimitResult struct with allowed/remaining/reset_time/total_limit/retry_after fields
- Add NewRedisRateLimiter constructor and Allow method with Lua script for atomic sliding window operations
- Add AllowTokenBucket method with token bucket algorithm using Redis HMSET for refill rate
2026-04-23 03:00:33 +03:00
nutcas3 5a3f08829f feat: Add response body capture to ContentBasedETagMiddleware for accurate content-based ETag generation
- Add ResponseWriterWrapper struct with gin.ResponseWriter and body buffer fields to capture response content
- Add Write method to ResponseWriterWrapper that captures response body while writing to original writer
- Add Body method to ResponseWriterWrapper that returns captured response bytes
- Update ContentBasedETagMiddleware to wrap response writer with ResponseWriterWrapper before calling
2026-04-23 02:59:18 +03:00