SetWebhook returning OK is not sufficient proof the webhook is live —
the URL can end up unset if DeleteWebhook races in or an intermediary
rewrites the request, after which inbound updates are silently dropped.
Read back getWebhookInfo to confirm Telegram stored the expected URL,
and on any verification failure fall back to polling when an update
handler is configured so the bot keeps receiving updates.
Replace direct uses of sub.CurrentPeriodStart/End with
subscription.ResolveTrafficPeriod across the quota cache, forward usage
endpoints, and DTOs. For calendar_month plans these windows diverge,
causing the node hub real-time enforcer to query usage over the wrong
window after a calendar reset and forward usage endpoints to return
mis-aligned traffic figures. SubscriptionDTO and DashboardSubscriptionDTO
now expose currentTrafficCycleStart/End plus the upload/download
breakdown, so the frontend pairs figures with the matching window and
skips a redundant traffic-stats request.
Also demote context.Canceled / DeadlineExceeded inside Errorw/Error to
WARN. Repository read paths previously logged client cancellations as
ERRORs (e.g. dashboard 30s timeout produced an ERROR per repo on the
call chain), which polluted alerts.
calculatePeriodTraffic reimplements period resolution locally and was not
updated alongside ResolveTrafficPeriod, so the Subscription-Userinfo
header still applied calendar-month boundaries to lifetime subscriptions.
Clients like clash/v2ray consequently saw the used-traffic counter reset
on the 1st of each month instead of accumulating from the subscription
start. Thread BillingCycle through SubscriptionValidationResult and
short-circuit lifetime subscriptions to the full subscription period,
mirroring the domain's ResolveTrafficPeriod.
When a forward agent is used as an intermediate or exit hop in a rule
but has no publicAddress/tunnelAddress configured, the entry agent
receives an empty NextHopAddress and config sync silently produces an
unusable configuration. The hub already observes each agent's remote
address at WebSocket handshake, so record it on AgentHubConn and use
it as a best-effort fallback in both rule sync converters, emitting a
warning so operators can see when auto-detection takes effect.
Align admin node update with the other three node usecases: an empty
server address is a placeholder and must not participate in
(address, port) uniqueness, otherwise two placeholder nodes sharing a
port would wrongly collide on save.
Lifetime subscriptions should accumulate traffic from their start date
without being reset at calendar-month boundaries. ResolveTrafficPeriod
now treats lifetime billing cycle as billing_cycle mode regardless of
the plan's reset mode setting.
Also exempt lifetime subscriptions from the automatic usage reset
triggered when a plan's traffic reset mode changes, since their traffic
period is already handled correctly by the domain logic.
Add NodeConfigChangeNotifier interface and wire it into forward rule
CRUD use cases (create/update/delete/enable/disable) so that target
nodes are notified to reload their routing configuration when per-rule
routes are added, modified, or removed.
Also populate ForwardRuleRoutes in NodeConfigData via the shared
mergeForwardRuleRoutesCore helper, enabling the WS hub sync path to
include per-forward-rule routing in node config pushes.
ExitAgentRequest.Weight was uint16, causing weight=0 to be treated as
"unset" and replaced with default (50). Changed to *uint16 so nil means
default and explicit 0 is preserved as backup agent designation.
The Updates map in ForwardRuleRepositoryImpl.Update was missing the
address_preference field, causing address preference changes to be
silently dropped during persistence despite being correctly applied
in the domain layer.
When a plan's traffic reset mode changes (e.g. calendar_month to
billing_cycle), reset usage for all active/trialing/suspended
subscriptions to avoid unfair traffic accumulation under the new mode.
RuleSyncData was missing the AddressPreference field, causing the WS hub
full/incremental sync path to always send an empty value. This made
ruleConfigChanged comparison ineffective when only the address preference
changed but the resolved NextHopAddress remained the same.
Forward rule mutations (create/update/delete/enable/disable) now trigger
async subscription sync to affected nodes, ensuring nodes pick up rule
changes immediately without waiting for the next polling cycle.
Replace full-entity loading with lightweight database queries across
multiple domains: node/agent offline detection and counting for telegram
summaries, notification unread filtering and bulk mark-as-read, and
active user ID retrieval. Fix subscription pricing IsActive field to use
*bool for proper nil/false distinction.
Allow forward rules to specify how the next-hop agent address is
resolved (auto/public/tunnel). This gives users control over whether
chain and entry rules connect via public or tunnel addresses instead
of always using the default auto-detection order.
Adds AddressPreference value object, integrates it across domain,
application, infrastructure, and interface layers with migration.
Traffic reset was not taking effect because multiple enforcement paths
ignored the subscription's currentPeriodStart when calculating usage:
- ResolveTrafficPeriod now uses currentPeriodStart as a floor for
calendar_month mode, so manual resets exclude pre-reset traffic
- Node and forward enforcement services now query traffic within the
resolved period instead of from time zero
- NodeSubscriptionUsageReaderAdapter Redis query respects periodStart
Also refactors node subscription generation and token validation into
infrastructure layer, adds include_counts support for admin subscription
list endpoint, and extracts shared nodeutil package.
- Extract routing value objects (RouteConfig, RouteRule, CustomOutbound,
OutboundType, RuleSetEntry) from node/valueobjects to shared/routing
package for reuse across node and forward domains
- Add RouteConfig field to ForwardRule domain model with persistence
(JSON column), validation, and mutation support
- Merge per-forward-rule routes into NodeConfigResponse with inbound
filtering, namespaced custom outbounds, and rule_set deduplication
- Add migration 057 for forward_rule route_config column
- Update agent SDK types with ForwardRuleRoute, RuleSetEntry, and
RouteRule.Inbound field
- Switch all telegram notifications from MarkdownV1 to HTML format
with consistent html.EscapeString on all dynamic content including
timeStr and amountStr for defense-in-depth
- Add silent message support (SendMessageSilent) for info-type
notifications while keeping offline alerts audible
- Add /lang command for user language preference management
- Add SendMessageDraft support for typing-bubble UX feedback
- Deduplicate TelegramMessageSender interface using type alias in
admin/serviceddd.go referencing canonical definition in usecases
- Fix error handling in GetBindingLanguageByTelegramID to distinguish
ErrBindingNotFound from infrastructure errors (DB timeout etc.)
- Add ErrBindingNotFound sentinel in infrastructure/telegram with
domain-to-local error translation in ServiceAdapter
- Normalize logger injection: rename shadowed param to log, move
logger to last constructor parameter per project conventions
- Add debug logging for SendMessageDraft nil-check paths
- Remove empty messages.go, consolidate EscapeHTML into escape.go
- Replace blockquote formatting for improved Telegram rendering
Pass TrafficLimitOverride and TrafficUsedAdjustment from the
subscription model through to the subscription generation use case,
so per-subscription traffic quota overrides are reflected in the
Subscription-Userinfo response header.
Add overflow guard for uint64 -> int64 conversion when applying
traffic used adjustment.
The list subscription endpoints (user and admin) were returning
dataUsedBytes as 0 because ListUserSubscriptionsUseCase lacked
QuotaService integration. Added GetSubscriptionQuotaPreloaded to
the QuotaService interface to avoid redundant DB lookups when
subscription and plan are already fetched, and wired it into the
list use case to populate traffic usage data in DTOs.
- Add JWT signing key strength validation at startup (reject default key in non-debug mode)
- Fix stored XSS in announcements/notifications by switching to ToHTMLSanitized() and restricting URL schemes to http/https/mailto
- Fix admin role persistence after demotion by reading fresh role from database in auth middleware and token refresh
- Add SSRF protection for SMTP host configuration (reject private/reserved IPs)
- Fix command injection in install script generation with URL validation and shell quoting
- Strengthen password policy to 12+ chars with uppercase/lowercase/number/special requirements and common password blacklist
- Add rate limiting to verify-email and reset-password endpoints
- Fix SSTI by switching notification templates from text/template to html/template
- Add node status field to hub config sync data
Add per-subscription traffic limit override and usage adjustment fields,
allowing admins to override plan-level traffic limits and adjust displayed
usage for individual subscriptions. Includes PATCH /admin/subscriptions/:id
endpoint, migration, and domain/infrastructure/application layer changes.
Also switch protocol config and plan repositories from soft delete to hard
delete, and add HardDelete for external forward rules.
- Implement Redis-based online device tracking using sorted sets with
IP-level granularity and automatic stale entry cleanup
- Add online subscription count per node to dashboard, node list, node
detail, and traffic stats APIs
- Propagate plan device limits to node agents during subscription sync,
full sync, and plan feature changes
- Add OnlineDeviceCounter and NodeOnlineSubscriptionCounter interfaces
to decouple application layer from infrastructure
- Populate onlineDeviceCount and deviceLimit in subscription API responses
(get, list, create, reset-link)
- Handle multi-IP per subscription in online subscription reports with
IP validation and deduplication
- Add plan change notification to re-sync affected nodes when plan
features (e.g. device_limit) are updated
- Add RuleSetEntry value object for remote rule-set sources in sing-box
route configuration, replacing inline geoip/geosite with rule_set references
- Add rule-set entry serialization in DTO and persistence mapper layers
- Add username/version fields to OutboundDTO for SOCKS/HTTP auth support
- Update nginx reverse proxy examples with WebSocket upgrade headers
- Fix GitHub OAuth to fall back to login when display name is empty
- Redesign OAuth success/error HTML pages with dark mode support,
cookie-based auth flow, and auto-close with manual fallback
Add DnsConfig value object with servers, rules, and strategy options
compatible with sing-box DNS configuration format. Implement full
CRUD support across all layers: domain aggregate, persistence mapper
with JSON serialization, application use cases (create/update/get),
config sync service, and HTTP handler endpoints. Include database
migration for the dns_config column and comprehensive domain tests.
Allow users to define custom sing-box outbound configurations (custom_xxx)
in route rules, in addition to existing preset types and node references.
- Add CustomOutbound value object with validation (tag, protocol, server, port, settings)
- Extend OutboundType to support custom_xxx references
- Add custom outbounds to RouteConfig with referential integrity validation
- Add CustomOutboundDTO and DTO conversion functions
- Flatten custom outbounds into agent outbounds list for sing-box consumption
- Agent responses only include custom outbounds in the outbounds array, not in route
- Add persistence support via RouteConfigJSON/CustomOutboundJSON in mapper
Split oversized files across multiple layers into smaller, focused files:
- domain/forward: extract chain, mutations, traffic, and validation logic
- domain/node: extract lifecycle, mutations, and status logic
- infrastructure/repository: extract list, ops, and query methods
- application/telegram/admin: extract notification methods to serviceddd_notify.go
- infrastructure/telegram: extract adapter and handler from pollingservice
No behavioral changes; pure structural refactoring for maintainability.
The AnyTLS protocol branch was missing from ToNodeConfigData() in
hubdto.go, causing empty Protocol field in WebSocket config_sync
messages. Also add AnyTLS to node-to-node forwarding password
generation in both GetNodeSubscriptions and SubscriptionSyncService,
which would have caused forwarding auth failures for AnyTLS targets.
Implement end-to-end AnyTLS protocol support including domain value
objects, repository persistence, use case handling, subscription
formatting (Base64/Clash/Surge), and HTTP handler bindings. Add
migration script for the anytls_configs table and wire protocol-specific
config loading in parallel with existing protocols.
Also add PlanType filter to plan repository list query.
- Add admin dashboard API (GET /admin/dashboard) with concurrent errgroup
queries aggregating users, subscriptions, nodes, forward agents, and
today's traffic metrics
- Implement Double Submit Cookie CSRF middleware with token generation,
cookie lifecycle management, and path-based exemptions for public and
machine-to-machine endpoints
- Downgrade routine Info logs to Debug and validation Error logs to Warn
across ~60 use cases to reduce production log noise
- Replace logger.NewLogger() calls with injected logger.Interface in
migration, middleware (logging, recovery, nodeowner, nodequota), and
CLI commands for consistent DI
- Simplify TrafficOverviewResponse to traffic-only fields (counts moved
to new admin dashboard)
- Restrict forward rule bindIP to exit-role agents only
- Default cookie Secure to true, fix SameSite call ordering, redact
Cookie header in panic recovery
- Add CountByLastSeenAfter to node and forward agent repositories,
CreatedAfter filter to user ListFilter
- Fix logger.Error to logger.Errorw in SystemSettingRepository
Move mimetype, gocron/v2, and gobreaker/v2 from indirect to direct
dependencies to reflect actual import usage. Remove unused indirect
dependency check.v1.
seedAdminUser runs on server startup but fails on first install because
the users table does not exist yet (migrations are applied separately
via docker exec). Adding a container restart after migrations ensures
the admin credentials configured during installation take effect.
Add remember_me flag to sessions to control cookie persistence and
session duration. Password login respects user preference (session
cookie vs persistent cookie), while passkey and OAuth default to
persistent sessions as trusted authentication methods.
- Add RememberMe field to Session domain model and persistence layer
- Use session cookies (cleared on browser close) when rememberMe=false
- Use persistent cookies with RememberExpDays when rememberMe=true
- Inject SessionConfig into auth and passkey handlers for cookie control
- Extend session duration on refresh based on remember me preference
- Add migration 053 to add remember_me column to sessions table
When resource group membership, forward rules, status, or deletion
changes occur, push subscription updates to all affected node agents.
This ensures nodes promptly reflect the latest subscription state
without waiting for the next periodic sync.
- Add NodeSubscriptionSyncer interface with shared collect/sync helpers
- Add GetIDsByGroupID to NodeRepository for efficient node lookup
- Add SyncSubscriptionsToNode adapter on SubscriptionSyncService
- Wire syncer into 4 resource group use cases via setter injection
- Extract all telegram notification messages into i18n package with ZH/EN support
- Auto-detect user language from Telegram client and persist per-binding preference
- Add language column to telegram_bindings and admin_telegram_bindings tables
- Add circuit breaker (gobreaker) and retry logic for Telegram Bot API calls
- Handle 429 rate limiting with retry_after, skip 403 bot-blocked errors gracefully
- Add message splitting for messages exceeding Telegram's 4096 char limit
- Add deep link support (t.me/bot?start=bind_<code>) for one-click binding
- Add concurrent worker pool for polling update processing with user affinity
- Persist polling offset to Redis for crash-resilient restart
- Add traffic_reset_mode plan feature (calendar_month/billing_cycle) for flexible
traffic period calculation via ResolveTrafficPeriod domain function
- Add SendChatAction ("typing") indicator before long operations
- Add admin /adminunbind and /adminstatus commands in polling mode
- Fix error handling: use errors.Is for sentinel errors, return error on dual
data source failure in quota service
- Fix PlanFeatures.Clone to deep-copy slices, use reflect.DeepEqual in Equals
- Fix NormalizeLimits to prioritize standard keys over legacy aliases
- Fix CanNotifyResourceExpiring to use business timezone for date comparison
- Remove unused SubscriptionUsageRepository dependency from ProcessReminderUseCase
Allow each admin telegram binding to configure its own schedule preferences:
- daily_summary_hour: hour to send daily summary (0-23, business timezone)
- weekly_summary_hour: hour to send weekly summary (0-23, business timezone)
- weekly_summary_weekday: weekday to send weekly summary (0=Sunday..6=Saturday)
- offline_check_interval_minutes: repeat notification interval for offline resources (1-30 min)
Scheduler cron jobs now trigger hourly and delegate filtering to the use case
layer, which matches bindings by their configured hour/weekday. Summary dedup
switches from elapsed-time to calendar-based comparison to avoid timing races.
Offline check now supports repeat notifications using per-binding intervals.
Two fixes for traffic statistics consistency:
1. Use calendar month boundaries (in business timezone) instead of
per-subscription billing period for dashboard and subscription
config traffic display, matching checkTrafficLimit enforcement logic.
2. Replace sliding 24h window with day-aligned boundary for the
MySQL/Redis Lambda architecture split. MySQL now covers complete
days before yesterday, Redis covers yesterday + today. This aligns
with the daily batch aggregation schedule and the 48h Redis TTL.
Also fix lifetime subscription end date to avoid year overflow when
converting to eastern timezones (9999-12-31 23:59:59 -> 9999-01-01).
Nodes referenced as target_node_id in system forward rules now also
receive subscriptions from the corresponding resource groups. This
fixes cases where a resource group contains only forward rules but
the target nodes were not getting subscriptions delivered.
Consolidate duplicated handler code into reusable utils functions:
- ParseSIDParam for Stripe-style ID parsing
- GetUserIDFromContext/GetSubscriptionIDFromContext for safe context extraction
- ParsePagination/ParsePaginationWithLimits for query parameter parsing
- ListSuccessResponse for paginated list responses
- Move pagination utilities from trafficstatsutil to shared/utils
- Add batch update methods for resource group membership changes (nodes, agents, rules)
to reduce database round-trips during bulk operations
- Add billing_cycle field to SubscriptionDTO to expose subscription's billing cycle
- Support all billing cycles (weekly, monthly, quarterly, semi_annual, yearly, lifetime)
in subscription creation and renewal
- Return empty subscription instead of error when no nodes are available
- Default to monthly billing cycle for legacy subscriptions without billing_cycle set
- Optimize protocol config queries with explicit column selection
Previously, forward rules required both the rule itself and its target_node
to be in the subscription plan's resource groups. This change relaxes this
constraint - rules are now delivered based solely on their own resource group
membership, regardless of where the target node resides.
Changes:
- Add ListSystemRulesByGroupIDs repository method to query rules by group membership
- Modify getForwardedNodes to query rules first, then fetch target nodes separately
- Fix GetBySubscriptionToken and getHybridPlanNodes to handle cases where
resource groups contain only forward rules without any nodes
- Add target_node_id IS NOT NULL filter for query optimization
Redirect all read commands to /dev/tty to allow user input when the
script is executed via curl pipe (curl ... | bash). Without this fix,
read commands fail because stdin is consumed by the piped script content.
- Add XOR-based handshake obfuscation to reduce DPI fingerprint in tunnel_ping
- Change WebSocket tunnel URL from /tunnel to /ws
- Switch handshake message format from TextMessage to BinaryMessage
- Replace renewal_amount with cost_label for flexible pricing display
- Add expiring notification preferences with configurable advance days
- Implement scheduled expiring check for forward agents and nodes
- Add Telegram alerts for expiring resources with user preferences
Add expires_at, renewal_amount, and is_expired fields to forward agents
and nodes to support expiration tracking and renewal management.
Changes:
- Add expires_at (datetime), renewal_amount (decimal), is_expired (computed)
fields to ForwardAgent and Node domain entities
- Add handler validation for expires_at (ISO8601 format, must be future time)
and renewal_amount (non-negative, 0 clears the value)
- Update DTOs, mappers, models, and repositories for both entities
- Rename Node's group_ids JSON field to group_sids for consistency
- Add database migration 048_add_agent_expiry_and_renewal.sql
- Change ForwardAgent.GroupID (single) to GroupIDs (array) to support
multi-group membership
- Add migration script 047 to convert group_id column to group_ids array
- Update repository layer with array-based queries using ANY operator
- Update ResourceGroupRepository to handle array-based forward agent
group associations
- Update all related use cases and DTOs to work with group arrays
- Fix payment repository to use explicit column selection
- Update handlers and tests to reflect new multi-group structure
Add GroupSID field to CreateForwardAgentRequest and GroupSIDs field to
CreateNodeRequest, allowing resource group assignment during entity creation.
This eliminates the need for a separate update call after creation.
- Forward agent: accepts single group_sid (optional)
- Node: accepts array of group_sids (optional, supports multiple groups)
- Both use cases now inject resourceGroupRepo to resolve SIDs to internal IDs
- Duplicate and empty SIDs are handled gracefully for nodes
Add LoadBalanceStrategy field to support two load balancing modes for
entry rules with multiple exit agents:
- failover: Priority-based selection by weight (highest first), with
weight=0 agents serving as backup only when all others unavailable
- weighted: Traffic distribution based on weight ratios
Changes:
- Add LoadBalanceStrategy value object with validation
- Add load_balance_strategy column to forward_rules table (default: failover)
- Update ForwardRule domain entity with strategy field and validation
- Add validation: weighted strategy requires at least one non-backup agent
- Update DTOs, handlers, and usecases to support the new field
- Sync SDK with LoadBalanceStrategy type and fields
- Update all test files to include the new parameter
- Add ExitAgents field to ForwardRule for multiple exit agent configuration
- Implement AgentWeight value object for weighted load balancing
- Add load balance mode support (random/round_robin/weighted)
- Update repository, mappers, and models to persist exit agents config
- Add database migration for exit_agents and load_balance_mode columns
- Extend DTOs and converters to handle multi-exit agent scenarios
- Add tunnel health handler for managing agent health status
- Update all usecases to work with new exit agents structure
- Modify affected agents finder to include all exit agents
- Add AlertStateManager with Normal/Firing state machine for proper alert lifecycle
- Implement recovery notifications when nodes/agents transition from Firing to Normal
- Clear alert state on resource deletion to prevent stale recovery notifications
- Use batch fetching for plans, users, and subscriptions to reduce N+1 queries
- Add GetTotalBySubscriptionIDsGrouped for efficient per-subscription usage retrieval
- Remove real-time offline notifications; use scheduled check for threshold-based alerting