Documentation
¶
Overview ¶
Package store is the SQLite-backed persistence layer. The DB is the single source of truth; the Xray config is always derived from it.
Index ¶
- Variables
- func Check(path string) error
- func InspectDB(path string) (users, admins int, secret string, err error)
- func PeekTimezone(dbPath string) string
- type AbuseHit
- type AbuseMatch
- type AdminAuditFilter
- type ConnectionHit
- type NodeEdit
- type SessionAdmin
- type Store
- func (s *Store) AbuseByUser(userID int64, limit int) ([]AbuseMatch, error)
- func (s *Store) AbuseRecent(limit int) ([]AbuseMatch, error)
- func (s *Store) AbuseUserCountsForDay(day string) (map[int64]int64, error)
- func (s *Store) ActiveDeviceCounts(since int64) (map[int64]int, error)
- func (s *Store) AddAbuseMatches(hits []AbuseHit) error
- func (s *Store) AddAdminAudit(ev model.AdminAudit) error
- func (s *Store) AddBroadcastTargets(broadcastID int64, chatIDs []int64) error
- func (s *Store) AddConnection(userID int64, ip string, ts int64) error
- func (s *Store) AddConnections(hits []ConnectionHit) error
- func (s *Store) AddDailyTraffic(userID int64, day string, up, down int64) error
- func (s *Store) AddDailyTrafficNode(userID, nodeID int64, day string, up, down int64) error
- func (s *Store) AddUsedTraffic(userID, up, down int64) error
- func (s *Store) AddUserEvent(ev model.UserEvent) error
- func (s *Store) ApplyNodeReport(nodeID, reportID int64, deltas []TrafficDelta) (bool, error)
- func (s *Store) ApplyTrafficDeltas(deltas []TrafficDelta) error
- func (s *Store) ApplyUserPlan(p UserPlanWrite) error
- func (s *Store) BackdateUserForTest(id int64, at time.Time) error
- func (s *Store) CancelPaymentOrderIfPending(id int64) (bool, error)
- func (s *Store) Checkpoint() error
- func (s *Store) ClaimRegistrationRequest(id int64) (bool, error)
- func (s *Store) ClearUserTelegramChat(userID int64) error
- func (s *Store) ClearUserTgLinkCode(userID int64) error
- func (s *Store) Close() error
- func (s *Store) ConfirmPaymentOrder(orderID, paidAt int64, p UserPlanWrite) (bool, error)
- func (s *Store) ConsumeJoinToken(raw string) (*model.Node, string, error)
- func (s *Store) CountAdmins() (int, error)
- func (s *Store) CountSubscribers() (total, reachable int, err error)
- func (s *Store) CountUsers(now int64) (UserCounts, error)
- func (s *Store) CountUsersOnPlan(planID int64) (int, error)
- func (s *Store) CreateAPIKey(name string) (*model.APIKey, error)
- func (s *Store) CreateAdmin(username, passwordHash, role string, mustChange bool) (int64, error)
- func (s *Store) CreateBroadcast(b *model.Broadcast, now int64) (int64, error)
- func (s *Store) CreateNode(name, host, decoyTemplate string) (*model.Node, error)
- func (s *Store) CreatePaymentOrder(userID, planID int64, amountRub int) (*model.PaymentOrder, error)
- func (s *Store) CreateRegistrationRequest(chatID int64, name string, now int64) (*model.RegistrationRequest, error)
- func (s *Store) CreateSession(adminID int64, ttl time.Duration) (string, error)
- func (s *Store) CreateUser(name, uuid, password, subToken string, dataLimit, expireAt int64, ...) (*model.User, error)
- func (s *Store) CreateWebhook(url string, events []string) (*model.Webhook, error)
- func (s *Store) DeleteAdmin(id int64) error
- func (s *Store) DeleteNode(id int64) error
- func (s *Store) DeleteRegistrationRequest(id int64) error
- func (s *Store) DeleteSession(token string) error
- func (s *Store) DeleteSessionsForAdmin(adminID int64) error
- func (s *Store) DeleteSessionsForAdminExcept(adminID int64, keepToken string) error
- func (s *Store) DeleteSupportGroup(chatID int64) error
- func (s *Store) DeleteSupportTopic(groupID, chatID int64) error
- func (s *Store) DeleteTariffPlan(id int64) error
- func (s *Store) DeleteUser(id int64) error
- func (s *Store) DeleteUsers(ids []int64) (int64, error)
- func (s *Store) DeleteWebhook(id int64) error
- func (s *Store) EnabledWebhooksForEvent(event string) ([]model.Webhook, error)
- func (s *Store) ExpiredUsersBefore(cutoff int64) ([]model.User, error)
- func (s *Store) FinishedBroadcastIDs() ([]int64, error)
- func (s *Store) GetAdmin(id int64) (model.Admin, error)
- func (s *Store) GetAdminAuth(username string) (id int64, hash, role string, err error)
- func (s *Store) GetAdminHash(id int64) (string, error)
- func (s *Store) GetBroadcast(id int64) (*model.Broadcast, error)
- func (s *Store) GetDetachedUserByPrevChat(chatID int64) (*model.User, error)
- func (s *Store) GetNode(id int64) (*model.Node, error)
- func (s *Store) GetPaymentOrder(id int64) (*model.PaymentOrder, error)
- func (s *Store) GetPaymentOrderByProvider(provider, providerID string) (*model.PaymentOrder, error)
- func (s *Store) GetPaymentProvider(key string) (model.PaymentProvider, error)
- func (s *Store) GetRegistrationRequest(id int64) (*model.RegistrationRequest, error)
- func (s *Store) GetRegistrationRequestByChat(chatID int64) (*model.RegistrationRequest, error)
- func (s *Store) GetSettings() (*model.Settings, error)
- func (s *Store) GetTariffPlan(id int64) (*model.TariffPlan, error)
- func (s *Store) GetUser(id int64) (*model.User, error)
- func (s *Store) GetUserBySubToken(token string) (*model.User, error)
- func (s *Store) GetUserByTelegramChatID(chatID int64) (*model.User, error)
- func (s *Store) GetUserByTgLinkCode(code string) (*model.User, error)
- func (s *Store) GetWebhook(id int64) (*model.Webhook, error)
- func (s *Store) IssueJoinToken(id int64) (string, error)
- func (s *Store) LatestPendingManualOrder(userID, planID int64) (*model.PaymentOrder, error)
- func (s *Store) LatestPendingProviderOrder(userID int64) (*model.PaymentOrder, error)
- func (s *Store) LatestPendingProviderOrderForPlan(userID, planID int64, provider string) (*model.PaymentOrder, error)
- func (s *Store) ListAPIKeys() ([]model.APIKey, error)
- func (s *Store) ListAdminAudit(f AdminAuditFilter) ([]model.AdminAudit, error)
- func (s *Store) ListAdmins() ([]model.Admin, error)
- func (s *Store) ListBroadcasts(limit int) ([]model.Broadcast, error)
- func (s *Store) ListEvents(f UserEventFilter) ([]model.UserEvent, error)
- func (s *Store) ListNodes() ([]model.Node, error)
- func (s *Store) ListPaymentOrders(status string, limit int) ([]model.PaymentOrder, error)
- func (s *Store) ListPaymentProviders() (map[string]model.PaymentProvider, error)
- func (s *Store) ListReachableSubscribers() ([]model.Subscriber, error)
- func (s *Store) ListRegistrationRequests() ([]model.RegistrationRequest, error)
- func (s *Store) ListSupportGroups() ([]model.SupportGroup, error)
- func (s *Store) ListTariffPlans(includeDisabled bool) ([]model.TariffPlan, error)
- func (s *Store) ListUserEvents(userID int64, limit int, beforeID int64) ([]model.UserEvent, error)
- func (s *Store) ListUsers() ([]model.User, error)
- func (s *Store) ListWebhooks() ([]model.Webhook, error)
- func (s *Store) LookupAPIKey(raw string) (*model.APIKey, error)
- func (s *Store) LookupNodeByToken(raw string) (*model.Node, error)
- func (s *Store) LookupSession(token string) (SessionAdmin, bool)
- func (s *Store) MarkConfigApplied() error
- func (s *Store) MarkTarget(broadcastID, chatID int64, state, errMsg string, at int64) error
- func (s *Store) MarkWebhookResult(id int64, status int, errStr string) error
- func (s *Store) NextPendingTargets(broadcastID int64, limit int) ([]int64, error)
- func (s *Store) NextRunningBroadcast() (*model.Broadcast, error)
- func (s *Store) NodeNameTaken(name string, excludeID int64) (bool, error)
- func (s *Store) NodeNames() (map[int64]string, error)
- func (s *Store) NodeTrafficTotals(userID int64, from, to string) (map[int64][2]int64, error)
- func (s *Store) PaidByProvider() ([]model.ProviderStat, error)
- func (s *Store) PaidSumSince(since int64) (int, error)
- func (s *Store) PendingProviderOrders(limit int) ([]model.PaymentOrder, error)
- func (s *Store) PendingTotals() (count, sum int, err error)
- func (s *Store) PruneSupportGroups(before int64) error
- func (s *Store) PurgeAbuseMatches(beforeDay string) (int64, error)
- func (s *Store) PurgeAdminAudit(before int64) (int64, error)
- func (s *Store) PurgeConnections(before int64) (int64, error)
- func (s *Store) PurgeDeletedNodes(before int64) (int64, error)
- func (s *Store) PurgeTrafficDaily(beforeDay string) (int64, error)
- func (s *Store) PurgeUserEvents(before int64) (int64, error)
- func (s *Store) RecentConnections(userID int64, limit int) ([]model.Connection, error)
- func (s *Store) ReencryptSensitiveFields() error
- func (s *Store) RegenJoinToken(id int64) (string, error)
- func (s *Store) ResetDailyStats() error
- func (s *Store) ResetTraffic(id, lastUp, lastDown int64) error
- func (s *Store) ResetUserQuota(id, now, lastUp, lastDown int64) error
- func (s *Store) RetryFailedBroadcast(id, now int64) (int, error)
- func (s *Store) RevokeAPIKey(id int64) error
- func (s *Store) SaveNodeReality(id int64, priv, pub, shortID, service string) error
- func (s *Store) SaveNodeWarp(id int64, priv, pub, endpoint, v4, v6, reserved string) error
- func (s *Store) SavePaymentProvider(p model.PaymentProvider) error
- func (s *Store) SaveTariffPlan(p *model.TariffPlan) error
- func (s *Store) SeeSupportGroup(chatID int64, title string, isForum bool, now int64) error
- func (s *Store) SetACMEProvider(provider, eabKID, eabHMAC string) error
- func (s *Store) SetAPIPath(p string) error
- func (s *Store) SetAbuseConfig(enabled bool, categories int64, custom string, alertMin int) error
- func (s *Store) SetAdminEvents(mask int64) error
- func (s *Store) SetAdminRole(id int64, role string) error
- func (s *Store) SetAntiDPI(tlsFragment, tlsMin13, blockQUIC bool, realityMaxTimeDiff int) error
- func (s *Store) SetBillingSettings(st *model.Settings) error
- func (s *Store) SetBroadcastMediaFileID(id int64, fileID string) error
- func (s *Store) SetBroadcastStatus(id int64, status string, at int64) error
- func (s *Store) SetBroadcastStatusIf(id int64, from, to string, at int64) (bool, error)
- func (s *Store) SetConfigError(msg string) error
- func (s *Store) SetDecoyTemplate(name string) error
- func (s *Store) SetFingerprints(vless, trojan, reality string) error
- func (s *Store) SetGeoRefresh(hours int) error
- func (s *Store) SetHysteriaPorts(port, hopStart, hopEnd int, interval string) error
- func (s *Store) SetIPListRefresh(hours int) error
- func (s *Store) SetLocalBackup(cron string, keep int) error
- func (s *Store) SetMasterLabel(label string) error
- func (s *Store) SetNodeACME(id int64, host, email, provider, eabKID, eabHMAC string) error
- func (s *Store) SetNodeAPIPath(p string) error
- func (s *Store) SetNodeConnections(id int64, c *model.NodeConnections) error
- func (s *Store) SetNodeDNS(id int64, dns *string) error
- func (s *Store) SetNodeEnabled(id int64, enabled bool) error
- func (s *Store) SetNodeGeoRefresh(id int64, hours int) error
- func (s *Store) SetNodeProtocols(id int64, vless, trojan, hysteria, reality bool) error
- func (s *Store) SetNodeRealityDest(id int64, dest string) error
- func (s *Store) SetNotifiedExpireAt(id, expireAt int64) error
- func (s *Store) SetNotifiedQuotaAt(id, at int64) error
- func (s *Store) SetNotifiedStatus(id int64, status string) error
- func (s *Store) SetOpera(enabled bool, country string, port int) error
- func (s *Store) SetPanelName(name string) error
- func (s *Store) SetPanelTheme(themeJSON string) error
- func (s *Store) SetPaymentOrderProvider(id int64, provider, providerID, payURL string) error
- func (s *Store) SetPaymentOrderStatus(id int64, status string, paidAt int64) error
- func (s *Store) SetPaymentWebhookSecret(secret string) error
- func (s *Store) SetProtocolEnabled(name string, enabled bool) error
- func (s *Store) SetProtocolNames(vless, reality, trojan, hysteria string) error
- func (s *Store) SetProxyMode(enabled bool, typ string, port int, user, pass string) error
- func (s *Store) SetRealityKeys(priv, pub, shortID, serviceName string) error
- func (s *Store) SetRealityPorts(port int, dest string) error
- func (s *Store) SetResetPeriod(id int64, period string, now int64) error
- func (s *Store) SetRoutingConfig(cfg model.RoutingConfig) error
- func (s *Store) SetSecretPath(p string) error
- func (s *Store) SetSetupDone(done bool) error
- func (s *Store) SetSubSettings(st *model.Settings) error
- func (s *Store) SetSubToken(id int64, token string) error
- func (s *Store) SetSubscriberBlocked(chatID, at int64) error
- func (s *Store) SetSubscriberOptOut(chatID int64, out bool, now int64) error
- func (s *Store) SetSupportTopic(groupID, chatID, topicID, now int64) error
- func (s *Store) SetTLS(host, sni, mode, certPath, keyPath string) error
- func (s *Store) SetTLSMode(mode, host, sni, acmeEmail string) error
- func (s *Store) SetTelegramBot(enabled bool, token, cron string) error
- func (s *Store) SetTelegramChats(csv string) error
- func (s *Store) SetTelegramLinkCode(code string) error
- func (s *Store) SetTelegramSupport(enabled bool, token, username string, groupID int64, greeting string) error
- func (s *Store) SetTelegramUserBot(enabled bool, token, regMode, regCode string) error
- func (s *Store) SetTimezone(tz string) error
- func (s *Store) SetUserAutoDeleteDays(days int) error
- func (s *Store) SetUserEnabled(id int64, enabled bool) error
- func (s *Store) SetUserEvents(mask int64, expiringDays int) error
- func (s *Store) SetUserLimits(id, dataLimit, expireAt int64, deviceLimit int) error
- func (s *Store) SetUserName(id int64, name string) error
- func (s *Store) SetUserPlan(userID, planID int64, trialUsed bool) error
- func (s *Store) SetUserTelegramChat(userID, chatID int64) error
- func (s *Store) SetUserTgLinkCode(userID int64, code string) error
- func (s *Store) SetUsersEnabled(ids []int64, enabled bool) (int64, error)
- func (s *Store) SetWSPath(p string) error
- func (s *Store) SetWarp(st *model.Settings) error
- func (s *Store) SetXrayDNS(dns string) error
- func (s *Store) SkipRemainingTargets(broadcastID int64) error
- func (s *Store) StatsByUser(from, to string) ([]model.UserTotal, error)
- func (s *Store) StatsSeries(userID int64, from, to string) ([]model.DailyPoint, error)
- func (s *Store) StatsSeriesNode(userID, nodeID int64, from, to string) ([]model.DailyPoint, error)
- func (s *Store) SubscriberByChat(chatID int64) (*model.Subscriber, error)
- func (s *Store) SupportChatByTopic(groupID, topicID int64) (int64, error)
- func (s *Store) SupportGroupRightsAt(chatID int64) (int64, error)
- func (s *Store) SupportTopicByChat(groupID, chatID int64) (int64, error)
- func (s *Store) TouchAdminLogin(id int64) error
- func (s *Store) TouchLastSeen(userID, ts int64) error
- func (s *Store) UpdateAdminPassword(id int64, passwordHash string, mustChange bool) error
- func (s *Store) UpdateAdminUsername(id int64, username string) error
- func (s *Store) UpdateNode(id int64, e NodeEdit) error
- func (s *Store) UpdateNodeStatus(id int64, st model.NodeStatusUpdate) error
- func (s *Store) UpdateTraffic(id, addUp, addDown, lastUp, lastDown int64) error
- func (s *Store) UpdateWebhook(id int64, url string, events []string, enabled bool) error
- func (s *Store) UpsertSubscriber(chatID, userID int64, username, firstName, lang string, now int64) error
- func (s *Store) UpsertSupportGroup(chatID int64, title string, isForum, isAdmin bool, now int64) error
- func (s *Store) UserIDs() (map[int64]struct{}, error)
- func (s *Store) UserIDsOnPlan(planID int64) ([]int64, error)
- func (s *Store) UsersWithExpiredPlan(now int64) ([]model.User, error)
- func (s *Store) WorkingUsers(now int64) ([]model.User, error)
- type TrafficBaseline
- type TrafficDelta
- type UserCounts
- type UserEventFilter
- type UserPlanWrite
Constants ¶
This section is empty.
Variables ¶
var ErrAdminNotFound = errors.New("admin not found")
ErrAdminNotFound is returned when an admin id matches no row.
var ErrCorrupt = errors.New("database is corrupt")
ErrCorrupt reports a database file that exists but SQLite cannot use: a torn page, a truncated header ("file is not a database"), or a failed integrity check. It is the one failure a caller can act on — the file is unusable, so the only way back is a restore. Everything else from Open is a plain error.
var ErrNodeNameTaken = errors.New("node name already in use")
ErrNodeNameTaken is returned when a node write violates the live-node name unique index (the app-level NodeNameTaken check lost a race). The manager maps it to a user-facing validation error.
var ErrRegistrationPending = errors.New("registration already pending")
ErrRegistrationPending means the chat already has a pending request.
Functions ¶
func Check ¶ added in v0.15.0
Check reports whether the database file at path is usable, without opening the panel's full Store (no migrations, no writes). A missing file is fine — that's a fresh install — so it returns nil. A damaged one returns ErrCorrupt.
This runs at boot before anything touches the data dir, because the alternative is a panel that crash-loops on a corrupt file with no idea it should restore.
func InspectDB ¶
InspectDB opens the database file at path read-only and reports whether it's a usable rospanel database, along with its user/admin counts and configured secret path. A non-nil error means the file isn't a valid panel DB (missing, corrupt, or — the classic empty-backup case — a header with no tables because the data was left in an unbacked-up WAL).
func PeekTimezone ¶ added in v0.13.0
PeekTimezone reads the operator's configured IANA timezone straight from the DB file, without opening the full store (no migrations, no encryption key needed — the zone isn't a secret). It exists so main() can stamp log lines in the operator's zone from the very FIRST line: the real store isn't open that early, and setting the zone later would leave the opening boot lines in the server's system zone while everything after them used the operator's — timestamps jumping an hour mid-boot.
Returns "" for a fresh install (no DB / no row yet), which the caller reads as "use server-local".
Types ¶
type AbuseHit ¶ added in v1.3.0
type AbuseHit struct {
UserID int64
NodeID int64
Domain string
Category string
Day string
Count int64
SeenAt int64
}
AbuseHit is one pre-aggregated blocklist match, as the flush hands it over.
type AbuseMatch ¶ added in v1.3.0
type AbuseMatch struct {
UserID int64 `json:"user_id"`
UserName string `json:"user_name,omitempty"`
NodeID int64 `json:"node_id"`
Domain string `json:"domain"`
Category string `json:"category"`
Day string `json:"day"`
Count int64 `json:"count"`
LastSeen int64 `json:"last_seen"`
}
AbuseMatch is a stored match, for the panel's views.
type AdminAuditFilter ¶ added in v0.14.0
type AdminAuditFilter struct {
// Actions matches any one of these action keys — a category filter expands to the
// keys in that category (model.AdminAuditActionsIn). Empty means every action.
Actions []string
Actor string // actor_name, exact
BeforeID int64 // page backwards from this id
Limit int
}
AdminAuditFilter narrows the trail. A zero field means "no filter".
type ConnectionHit ¶ added in v1.2.0
ConnectionHit is one user+IP sighting, with however many times it was seen folded into Hits.
type NodeEdit ¶ added in v1.0.0
type NodeEdit struct {
Name string
Host string
DecoyTemplate string
VLESS *bool
Trojan *bool
Hysteria *bool
Reality *bool
Routing *model.RoutingConfig // nil ⇒ inherit global routing
XrayDNS *string // nil ⇒ inherit global DNS
// Per-node egress toggles (independent of the master). WARP keys are provisioned
// separately (SaveNodeWarp) when WarpEnabled flips on.
WarpEnabled bool
OperaEnabled bool
OperaCountry string
}
NodeEdit carries the operator-editable fields of a node. Pointer fields left nil mean "inherit the global setting" for protocol/DNS toggles and routing.
type SessionAdmin ¶ added in v0.14.0
SessionAdmin is who a session belongs to — resolved fresh on every authenticated request, so a role change or a raised password gate takes effect on the admin's next request rather than on their next login.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store wraps the SQLite connection pool.
func Open ¶
Open opens (creating if needed) the SQLite database at path, applies pragmas, verifies the file's integrity, and runs pending migrations. A file SQLite can't read, or one that fails the integrity check, comes back wrapped in ErrCorrupt.
func (*Store) AbuseByUser ¶ added in v1.3.0
func (s *Store) AbuseByUser(userID int64, limit int) ([]AbuseMatch, error)
AbuseByUser returns a user's matches, most recent first.
func (*Store) AbuseRecent ¶ added in v1.3.0
func (s *Store) AbuseRecent(limit int) ([]AbuseMatch, error)
AbuseRecent returns the fleet's matches, most recent first, with user names so the operator can act without a second lookup per row.
func (*Store) AbuseUserCountsForDay ¶ added in v1.3.0
AbuseUserCountsForDay returns each user's match count on one day, keyed by user id.
Scoped to a day, not the whole retention window, because the alert it feeds fires once per user per day: a window-wide sum would re-alert today about matches that happened last week, which is the opposite of "this account is a problem right now".
func (*Store) ActiveDeviceCounts ¶ added in v0.3.0
ActiveDeviceCounts returns how many distinct source IPs were seen per user since the given unix timestamp (typically now - DeviceOnlineWindow). INDEXED BY is deliberate. Left alone, SQLite picks the (user_id, ip) primary key so GROUP BY needs no sort, and scans the whole table — which grows a row per source IP per user. `since` is only DeviceOnlineWindow (120s) back, so the rows we want are a tiny slice of that: seeking them on last_seen and sorting the slice beats scanning everything, and the planner's row estimates (we never ANALYZE) don't know it. The clause also fails loudly if a migration ever drops the index.
func (*Store) AddAbuseMatches ¶ added in v1.3.0
AddAbuseMatches folds a batch of matches into the daily rollup, in one transaction.
The EXISTS guard is there for the same reason AddConnections has one: user ids come out of the Xray access log, so a deleted user with a still-live session keeps being named, and without the guard that one ghost would void everyone else's matches in the batch on every flush.
func (*Store) AddAdminAudit ¶ added in v0.14.0
func (s *Store) AddAdminAudit(ev model.AdminAudit) error
AddAdminAudit appends one row to the admin trail. Details that won't marshal are dropped rather than failing the write — a row with no details still beats losing the event.
func (*Store) AddBroadcastTargets ¶ added in v1.2.0
AddBroadcastTargets materialises the audience snapshot. Inserted in one transaction so a broadcast is never left with a half-built recipient list that the worker would happily treat as complete.
func (*Store) AddConnection ¶
AddConnection records activity from a source IP for a user (upserting the per-IP row) and bumps the user's last_seen.
func (*Store) AddConnections ¶ added in v1.2.0
func (s *Store) AddConnections(hits []ConnectionHit) error
AddConnections records a batch of sightings in one transaction.
The access-log tap is the panel's highest-frequency write source — it fires per user per source IP — and it used to do two separate statements per sighting. Folding a few seconds' worth into one commit is what stops the write rate from scaling with the number of connected devices.
func (*Store) AddDailyTraffic ¶
AddDailyTraffic adds up/down deltas to a user's row on the local server (node 0) for the given day.
func (*Store) AddDailyTrafficNode ¶ added in v1.0.0
AddDailyTrafficNode adds up/down deltas attributed to a specific node.
func (*Store) AddUsedTraffic ¶ added in v1.0.0
AddUsedTraffic bumps a user's lifetime totals WITHOUT touching last_up/last_down (the raw Xray counter baseline for the local poller). Remote-node traffic ingest uses this: the node already computed the delta, so the panel just accumulates.
func (*Store) AddUserEvent ¶ added in v0.13.0
AddUserEvent appends one audit row. details may be nil (stored as ""); a value that won't marshal is dropped rather than failing the write — an audit row with no details still beats losing the event.
func (*Store) ApplyNodeReport ¶ added in v1.2.0
func (s *Store) ApplyNodeReport(nodeID, reportID int64, deltas []TrafficDelta) (bool, error)
ApplyNodeReport claims a node's traffic-ingest watermark AND books the batch it covers, in one transaction. It reports whether this call won the claim; false means the report was already counted and nothing was written.
The two halves must commit together, for the same reason a payment's claim does (see ConfirmPaymentOrder). The watermark is what makes ingest idempotent, so once it advances the node's resend of that batch is rejected as a duplicate — if the traffic writes then failed or the process died, that batch is gone for good and no retry can bring it back. Rolled back together, the node simply resends and the traffic is counted on the next attempt.
func (*Store) ApplyTrafficDeltas ¶ added in v1.2.0
func (s *Store) ApplyTrafficDeltas(deltas []TrafficDelta) error
ApplyTrafficDeltas books a whole poll cycle's traffic in one transaction.
This is the panel's hottest write path: it used to run three separate statements per active user, each its own implicit transaction, each paying its own fsync on a single-connection pool. One commit for the batch turns a per-user cost into a per-cycle one — on the reference box that is the difference between ~70 users/sec and the whole cycle landing in a few milliseconds.
func (*Store) ApplyUserPlan ¶ added in v1.2.0
func (s *Store) ApplyUserPlan(p UserPlanWrite) error
ApplyUserPlan writes a plan assignment atomically.
func (*Store) BackdateUserForTest ¶ added in v1.2.0
BackdateUserForTest moves a user's creation date. Test-only: the audience filters floor "never connected" by account age, and there is no other way to exercise the difference between a long-dormant account and one registered this morning.
func (*Store) CancelPaymentOrderIfPending ¶ added in v0.11.0
CancelPaymentOrderIfPending cancels an order only while it's still pending, so a stale-order sweep or a provider "canceled" status can't clobber an order a concurrent webhook just marked paid. It reports whether THIS call performed the cancellation — a caller that logs or notifies must not do so for an order someone else already resolved.
func (*Store) Checkpoint ¶
Checkpoint flushes the write-ahead log into the main database file and truncates it, so an on-disk copy (e.g. a backup) captures all committed data. Without this a live backup would copy a near-empty .db with everything still in the uncheckpointed .db-wal (which backups intentionally exclude).
func (*Store) ClaimRegistrationRequest ¶ added in v1.0.0
ClaimRegistrationRequest atomically deletes a request and reports whether THIS call removed it. With MaxOpenConns(1) serialising writes, exactly one of several concurrent approvers/rejecters wins — so a double-click (or two admins) can't create the account twice or send contradictory notifications.
func (*Store) ClearUserTelegramChat ¶ added in v0.3.0
ClearUserTelegramChat unlinks a VPN user's Telegram chat, remembering the chat in tg_prev_chat_id so the same chat can restore this exact account (keeping its plan and consumed trial) by registering again — instead of getting a brand-new trial user. Only overwrites tg_prev_chat_id when actually detaching a chat, so a redundant clear can't wipe a prior pointer.
func (*Store) ClearUserTgLinkCode ¶ added in v0.4.0
ClearUserTgLinkCode burns a user's pending Telegram bind code (after a successful link or on demand).
func (*Store) ConfirmPaymentOrder ¶ added in v1.2.0
func (s *Store) ConfirmPaymentOrder(orderID, paidAt int64, p UserPlanWrite) (bool, error)
ConfirmPaymentOrder claims the pending→paid transition AND applies the plan that payment bought, in a single transaction. It reports whether this caller won the claim — false means a concurrent webhook/poll already handled the order, and the plan was NOT applied again.
The atomicity is the whole point, and it is specifically about crashes rather than concurrency (the CAS alone already handles concurrency). Done as separate statements, the terminal status commits FIRST and the plan LAST — so a process killed in between leaves an order marked paid whose plan was never granted. That state is unrecoverable by design: every retry path here keys off status = 'pending', which the claim has already overwritten, and a redelivered webhook no-ops on the non-pending status. Money captured, nothing delivered, nothing that can notice. In one transaction the pair is all-or-nothing: either the user has what they paid for, or the order is still pending and the 25s poll re-confirms it from the provider.
func (*Store) ConsumeJoinToken ¶ added in v1.0.0
ConsumeJoinToken exchanges a valid, unexpired one-time join token for a fresh permanent bearer token: the join token is cleared (single use) and the permanent token's hash is stored. The raw permanent token is returned exactly once. Returns (nil, "", nil) when the token is unknown or expired.
func (*Store) CountAdmins ¶
CountAdmins returns the number of admin accounts.
func (*Store) CountSubscribers ¶ added in v1.2.0
CountSubscribers reports how many chats are known in total and how many a broadcast would currently reach (neither blocked nor opted out).
func (*Store) CountUsers ¶ added in v1.2.0
func (s *Store) CountUsers(now int64) (UserCounts, error)
CountUsers computes UserCounts in one aggregate query.
The dashboard used to get these four numbers by loading every user row into Go and folding over the slice — on a 2s stream tick, per connected admin, which also meant decrypting every user's stored password (see decField) just to count them. This does the same arithmetic in SQLite and returns four scalars, so the cost stops scaling with the number of users AND the number of open panel tabs.
The WHERE clause is deriveStatus's "active" case spelled out in SQL, in the same order: enabled, not expired, inside the quota, inside the device cap. The two must agree — TestCountUsersMatchesDeriveStatus holds them to it.
func (*Store) CountUsersOnPlan ¶ added in v0.4.0
CountUsersOnPlan returns how many users currently have this plan assigned.
func (*Store) CreateAPIKey ¶ added in v0.10.0
CreateAPIKey mints a new named key, stores only its HMAC hash, and returns the model record with RawKey populated (shown to the operator exactly once).
func (*Store) CreateAdmin ¶
CreateAdmin inserts an admin with the given username, password hash and role. mustChange gates the account on a password change at first login — set for every account created with a password someone else picked.
func (*Store) CreateBroadcast ¶ added in v1.2.0
CreateBroadcast inserts a broadcast, paused. It is started separately, once the caller has finished putting everything in place — an attachment is written to disk after the row exists (it is named by id), and a worker that saw the row as running in between would find no file and stop the run.
func (*Store) CreateNode ¶ added in v1.0.0
CreateNode inserts a node with a fresh REALITY identity, a random decoy, and a one-time join token. The returned Node has RawJoinToken populated (shown to the operator exactly once, inside the install command); only its hash is stored.
func (*Store) CreatePaymentOrder ¶ added in v0.4.0
func (*Store) CreateRegistrationRequest ¶ added in v1.0.0
func (s *Store) CreateRegistrationRequest(chatID int64, name string, now int64) (*model.RegistrationRequest, error)
CreateRegistrationRequest inserts a pending request for a chat. chat_id is unique, so a chat with an existing request keeps the first one (ErrRegistrationPending).
func (*Store) CreateSession ¶
CreateSession issues a new opaque session token for an admin and stores only its HMAC hash. The raw token is returned to set as a cookie.
func (*Store) CreateUser ¶
func (s *Store) CreateUser(name, uuid, password, subToken string, dataLimit, expireAt int64, deviceLimit int) (*model.User, error)
CreateUser inserts a user with one credential set (UUID for VLESS, password for Trojan + Hysteria2), a subscription token, and optional quota/expiry.
func (*Store) CreateWebhook ¶ added in v0.10.0
CreateWebhook stores a new endpoint, generating a random HMAC signing secret (encrypted at rest) and returning the record with the plaintext secret.
func (*Store) DeleteAdmin ¶ added in v0.14.0
DeleteAdmin removes an admin. Their sessions go with them: admin_sessions has ON DELETE CASCADE, so a deleted admin's live cookies stop resolving immediately.
func (*Store) DeleteNode ¶ added in v1.0.0
DeleteNode soft-deletes a node: it tombstones the row (keeping the token) and disables it, so the node's next sync is answered Revoked=true and it stops serving — a hard delete would drop the token and leave the node running its last config with live user credentials (it would read the resulting decoy response as "panel unreachable" and keep serving). PurgeDeletedNodes reclaims the row later. Traffic history in traffic_daily is kept (rows carry the numeric node_id).
func (*Store) DeleteRegistrationRequest ¶ added in v1.0.0
DeleteRegistrationRequest removes a request (after approval or rejection).
func (*Store) DeleteSession ¶
DeleteSession revokes a session by its raw token.
func (*Store) DeleteSessionsForAdmin ¶ added in v0.4.0
DeleteSessionsForAdmin revokes every session for an admin.
func (*Store) DeleteSessionsForAdminExcept ¶
DeleteSessionsForAdminExcept revokes every session belonging to an admin except the one identified by keepToken — used after a credential change so a previously stolen cookie can't outlive the change, while the admin doing the change stays logged in.
func (*Store) DeleteSupportGroup ¶ added in v1.2.0
DeleteSupportGroup drops a group the bot was removed from, so the picker doesn't keep offering somewhere it can no longer post.
func (*Store) DeleteSupportTopic ¶ added in v1.2.0
DeleteSupportTopic drops a mapping whose topic no longer exists, so the next message opens a fresh thread instead of failing forever.
func (*Store) DeleteTariffPlan ¶ added in v0.4.0
func (*Store) DeleteUser ¶
DeleteUser removes a user and detaches them from the broadcast audience.
The subscriber row survives on purpose — someone whose account was deleted is still in the bot, and reaching them is exactly what the "без аккаунта" audience is for — but it must stop naming an account that no longer exists, or the audience filters read a missing user's zero values as facts about a real one.
func (*Store) DeleteUsers ¶ added in v0.7.0
DeleteUsers removes many users in one statement, returning how many were deleted.
func (*Store) DeleteWebhook ¶ added in v0.10.0
DeleteWebhook removes an endpoint.
func (*Store) EnabledWebhooksForEvent ¶ added in v0.10.0
EnabledWebhooksForEvent returns the enabled endpoints subscribed to event — the dispatcher's fan-out set. Subscription is filtered in Go (the events CSV is tiny) so "empty = all" stays in one place (model.Webhook.Subscribed).
func (*Store) ExpiredUsersBefore ¶ added in v0.15.0
ExpiredUsersBefore returns users whose expiry date is older than cutoff (unix seconds) — the candidates for the auto-delete sweep.
It keys off expire_at rather than the `status` column on purpose. status is a derived value that a reset or a plan change can flip back to active, and a user who is active again must never be deleted because they were expired last week. expire_at is the fact: a date in the past that nobody extended. Users with no expiry (expire_at = 0) are excluded — there is nothing for them to be past.
func (*Store) FinishedBroadcastIDs ¶ added in v1.2.0
FinishedBroadcastIDs lists runs that will never send again. The worker uses it to drop their attachments: removeMedia on the "done" path alone leaks a file for every run that was cancelled, paused for good, or died between creation and start.
func (*Store) GetAdminAuth ¶
GetAdminAuth returns the id, password hash and role for a username.
func (*Store) GetAdminHash ¶
GetAdminHash returns an admin's current password hash by id (for re-authenticating a credential change against the current password).
func (*Store) GetBroadcast ¶ added in v1.2.0
GetBroadcast returns one broadcast with its progress counters.
func (*Store) GetDetachedUserByPrevChat ¶ added in v0.11.0
GetDetachedUserByPrevChat finds an account this chat was previously unlinked from and that is still detached (no active chat), so registration can restore it instead of creating a new trial user. Returns sql.ErrNoRows when none.
func (*Store) GetNode ¶ added in v1.0.0
GetNode returns one live node by id, or (nil, nil) if it doesn't exist or was deleted. (A tombstoned node is invisible to the operator; only the token lookup still finds it, so its next sync can be answered Revoked.)
func (*Store) GetPaymentOrder ¶ added in v0.4.0
func (s *Store) GetPaymentOrder(id int64) (*model.PaymentOrder, error)
func (*Store) GetPaymentOrderByProvider ¶ added in v0.6.0
func (s *Store) GetPaymentOrderByProvider(provider, providerID string) (*model.PaymentOrder, error)
GetPaymentOrderByProvider finds a pending-or-any order by its external id.
func (*Store) GetPaymentProvider ¶ added in v1.0.0
func (s *Store) GetPaymentProvider(key string) (model.PaymentProvider, error)
GetPaymentProvider returns one provider's row. A provider that was never saved comes back as a zero-value row (disabled, empty config) — not an error.
func (*Store) GetRegistrationRequest ¶ added in v1.0.0
func (s *Store) GetRegistrationRequest(id int64) (*model.RegistrationRequest, error)
GetRegistrationRequest returns one request by id.
func (*Store) GetRegistrationRequestByChat ¶ added in v1.0.0
func (s *Store) GetRegistrationRequestByChat(chatID int64) (*model.RegistrationRequest, error)
GetRegistrationRequestByChat returns a chat's pending request, or nil when none.
func (*Store) GetSettings ¶
GetSettings returns the singleton settings row.
func (*Store) GetTariffPlan ¶ added in v0.4.0
func (s *Store) GetTariffPlan(id int64) (*model.TariffPlan, error)
func (*Store) GetUserBySubToken ¶
GetUserBySubToken resolves a subscription token to its user.
func (*Store) GetUserByTelegramChatID ¶ added in v0.3.0
GetUserByTelegramChatID resolves a linked Telegram chat to its VPN user.
func (*Store) GetUserByTgLinkCode ¶ added in v0.4.0
GetUserByTgLinkCode resolves a pending one-time Telegram bind code to its user, rejecting codes that are blank, unknown, or expired.
func (*Store) GetWebhook ¶ added in v0.10.0
GetWebhook returns one endpoint by id.
func (*Store) IssueJoinToken ¶ added in v1.0.0
IssueJoinToken issues a fresh one-time join token WITHOUT revoking the node's current permanent token. Used for SSH re-provisioning: if the install fails the live node keeps working on its old token, and a successful re-join replaces the token via ConsumeJoinToken. The raw join token is returned once.
func (*Store) LatestPendingManualOrder ¶ added in v0.11.0
func (s *Store) LatestPendingManualOrder(userID, planID int64) (*model.PaymentOrder, error)
LatestPendingManualOrder returns the newest still-pending manual order (no provider set) for a user+plan, or sql.ErrNoRows. Lets callers reuse an order instead of piling up duplicates when the user re-taps "Pay".
func (*Store) LatestPendingProviderOrder ¶ added in v0.11.0
func (s *Store) LatestPendingProviderOrder(userID int64) (*model.PaymentOrder, error)
LatestPendingProviderOrder returns the newest still-pending order that went through an automatic provider for a user (or sql.ErrNoRows). Used by the subscription page to show a "payment processing" state after the user returns from the provider until the webhook/poll confirms it.
func (*Store) LatestPendingProviderOrderForPlan ¶ added in v0.11.0
func (s *Store) LatestPendingProviderOrderForPlan(userID, planID int64, provider string) (*model.PaymentOrder, error)
LatestPendingProviderOrderForPlan returns the newest pending order for a user+plan+provider (or sql.ErrNoRows). Lets the pay flow reuse a fresh order instead of creating duplicates on repeated taps.
func (*Store) ListAPIKeys ¶ added in v0.10.0
ListAPIKeys returns all keys (active and revoked), newest first. RawKey is never populated here — it exists only in the CreateAPIKey response.
func (*Store) ListAdminAudit ¶ added in v0.14.0
func (s *Store) ListAdminAudit(f AdminAuditFilter) ([]model.AdminAudit, error)
ListAdminAudit returns the admin trail, newest first, filtered per f.
func (*Store) ListAdmins ¶ added in v0.14.0
ListAdmins returns the roster, owner first, then by creation order.
func (*Store) ListBroadcasts ¶ added in v1.2.0
ListBroadcasts returns the newest broadcasts with their progress counters.
func (*Store) ListEvents ¶ added in v0.13.0
func (s *Store) ListEvents(f UserEventFilter) ([]model.UserEvent, error)
ListEvents returns the global audit trail, newest first, filtered per f.
func (*Store) ListNodes ¶ added in v1.0.0
ListNodes returns all live (non-tombstoned) nodes, oldest first. RawJoinToken is never populated here.
func (*Store) ListPaymentOrders ¶ added in v0.4.0
func (*Store) ListPaymentProviders ¶ added in v1.0.0
func (s *Store) ListPaymentProviders() (map[string]model.PaymentProvider, error)
ListPaymentProviders returns every saved provider row, keyed by provider key. Providers the operator never touched simply have no row.
func (*Store) ListReachableSubscribers ¶ added in v1.2.0
func (s *Store) ListReachableSubscribers() ([]model.Subscriber, error)
ListReachableSubscribers returns every chat a broadcast may address: not blocked, not opted out. Audience narrowing beyond that happens in the manager, because the user statuses it filters on are derived on read and don't exist as columns.
func (*Store) ListRegistrationRequests ¶ added in v1.0.0
func (s *Store) ListRegistrationRequests() ([]model.RegistrationRequest, error)
ListRegistrationRequests returns all pending requests, oldest first.
func (*Store) ListSupportGroups ¶ added in v1.2.0
func (s *Store) ListSupportGroups() ([]model.SupportGroup, error)
ListSupportGroups returns the known candidates: usable ones first, then most recently seen.
Usable-first is not cosmetic. The bot is public, so anyone can add it to groups and every such row carries a fresh seen_at; ordering by recency alone let seeded junk evict the operator's own group past the cap — the exact confusion the cap was meant to prevent.
func (*Store) ListTariffPlans ¶ added in v0.4.0
func (s *Store) ListTariffPlans(includeDisabled bool) ([]model.TariffPlan, error)
ListTariffPlans returns plans sorted for display.
func (*Store) ListUserEvents ¶ added in v0.13.0
ListUserEvents returns one user's audit trail, newest first. beforeID > 0 pages backwards from a previously-returned id (the rows are id-ordered, so the id is a stable cursor even as new events land at the top).
func (*Store) ListWebhooks ¶ added in v0.10.0
ListWebhooks returns every configured endpoint, newest first.
func (*Store) LookupAPIKey ¶ added in v0.10.0
LookupAPIKey resolves a raw key to its record, ignoring revoked keys. The lookup is by HMAC hash (a UNIQUE-indexed column) so the DB does the comparison; the raw key never touches storage. last_used_at is bumped on a successful, non-revoked match. Returns (nil, nil) when no active key matches.
func (*Store) LookupNodeByToken ¶ added in v1.0.0
LookupNodeByToken resolves a raw permanent token to its (enabled or disabled) node, by HMAC hash. Returns (nil, nil) when nothing matches. The caller decides what a disabled node means (it is told to stop serving, not deauthenticated).
func (*Store) LookupSession ¶
func (s *Store) LookupSession(token string) (SessionAdmin, bool)
LookupSession resolves a raw session token to its admin. Expired sessions are deleted and treated as invalid.
func (*Store) MarkConfigApplied ¶
MarkConfigApplied bumps the config revision and clears any prior error.
func (*Store) MarkTarget ¶ added in v1.2.0
MarkTarget records one delivery outcome. The state moves off 'pending' whatever happened, so a recipient is attempted once per run and a resume can't repeat it.
func (*Store) MarkWebhookResult ¶ added in v0.10.0
MarkWebhookResult records the outcome of a delivery attempt (status is the HTTP code, 0 on a connection-level failure; errStr is "" on success).
func (*Store) NextPendingTargets ¶ added in v1.2.0
NextPendingTargets returns up to limit recipients still awaiting delivery, excluding anyone who has unsubscribed or blocked the bot since the audience was frozen. The snapshot fixes WHO was in scope; it must not override a decision the person made afterwards — least of all one the bot confirmed to them.
func (*Store) NextRunningBroadcast ¶ added in v1.2.0
NextRunningBroadcast returns the oldest broadcast still being delivered, or nil. Oldest-first so a queue drains in the order it was launched.
func (*Store) NodeNameTaken ¶ added in v1.0.0
NodeNameTaken reports whether a live node other than excludeID already uses the given name (case-insensitively). Node names must be unique because they become Clash proxy names / sing-box outbound tags in multi-node subscriptions, which a client rejects if duplicated.
func (*Store) NodeNames ¶ added in v1.2.0
NodeNames maps every node id to its display name, tombstoned nodes included. Traffic history outlives a node (traffic_daily rows carry the numeric id), so a breakdown covering past days still has to be able to name a server that has since been deleted.
func (*Store) NodeTrafficTotals ¶ added in v1.0.0
NodeTrafficTotals returns each node's total up+down over the period, keyed by node_id (0 = local server). Used by the Nodes UI. userID 0 aggregates across all users, matching StatsSeries.
func (*Store) PaidByProvider ¶ added in v0.10.0
func (s *Store) PaidByProvider() ([]model.ProviderStat, error)
PaidByProvider returns paid-order revenue grouped by provider ("" = manual), highest-earning first. Queried against payment_orders directly (no user join) so revenue from since-deleted users is still counted.
func (*Store) PaidSumSince ¶ added in v0.10.0
PaidSumSince returns the total paid revenue whose paid_at is at or after since.
func (*Store) PendingProviderOrders ¶ added in v0.6.0
func (s *Store) PendingProviderOrders(limit int) ([]model.PaymentOrder, error)
PendingProviderOrders returns pending orders that were started through a payment provider (for the polling fallback). Stale ones (older than maxAge seconds) are skipped — the caller marks them cancelled.
func (*Store) PendingTotals ¶ added in v0.10.0
PendingTotals returns the count and rouble total of orders awaiting payment.
func (*Store) PruneSupportGroups ¶ added in v1.2.0
PruneSupportGroups forgets candidates not seen for a while, so a table anyone can write to doesn't grow forever.
func (*Store) PurgeAbuseMatches ¶ added in v1.3.0
PurgeAbuseMatches drops rows older than the cutoff day ('YYYY-MM-DD'), returning how many went.
Batched by rowid for the same reason every other sweep here is: the pool is a single connection, so one unbounded DELETE would hold it for the whole statement and stall every request behind it.
func (*Store) PurgeAdminAudit ¶ added in v0.14.0
PurgeAdminAudit drops trail rows older than the cutoff (unix seconds), returning how many were removed. Batched for the same reason as the user journal's sweep: the pool is a single connection, so an unbounded delete would stall every request behind it.
func (*Store) PurgeConnections ¶ added in v0.13.0
PurgeConnections drops connection rows not seen since the cutoff (unix seconds), returning how many were removed. Batched for the same reason PurgeUserEvents is: the pool is a single connection, so one unbounded DELETE would stall every query behind it. connections has no surrogate key, so this sweeps by rowid.
func (*Store) PurgeDeletedNodes ¶ added in v1.0.0
PurgeDeletedNodes hard-deletes tombstoned nodes whose deletion is older than the cutoff, once a decommissioned node has had ample time to receive its revocation.
func (*Store) PurgeTrafficDaily ¶ added in v1.2.0
PurgeTrafficDaily drops per-day traffic rows older than beforeDay (exclusive, 'YYYY-MM-DD'), returning how many were removed. The cutoff is a calendar day and not a timestamp because that is what the rows are keyed on — see AddDailyTraffic, which writes the operator's local day. Batched for the same reason the other sweeps are: one unbounded DELETE would hold the single connection for the whole statement and stall every request behind it.
func (*Store) PurgeUserEvents ¶ added in v0.13.0
PurgeUserEvents drops audit rows older than the cutoff (unix seconds), returning how many were removed. This is the retention sweep.
func (*Store) RecentConnections ¶
RecentConnections returns a user's source IPs, most recent first.
func (*Store) ReencryptSensitiveFields ¶ added in v0.4.0
ReencryptSensitiveFields migrates legacy plaintext secrets to enc:v1: at-rest blobs.
func (*Store) RegenJoinToken ¶ added in v1.0.0
RegenJoinToken issues a new one-time join token for a node (e.g. to re-install it) and revokes its current permanent token so the old install can't keep syncing. The raw join token is returned once.
func (*Store) ResetDailyStats ¶
ResetDailyStats clears the entire per-day traffic history.
func (*Store) ResetTraffic ¶
ResetTraffic zeroes a user's usage (so a "limited" user works again) and re-baselines the raw counters to the supplied live Xray values, so the next stats poll measures the delta from now. Passing 0/0 would make the poll re-add the user's whole lifetime Xray total straight back onto the freshly-zeroed usage. Does not touch enabled or expiry — an expired user stays expired.
func (*Store) ResetUserQuota ¶
ResetUserQuota zeroes a user's usage and records the reset time (automatic reset scheduler). It re-baselines the raw counters to the supplied live Xray values so the next stats poll measures the delta from now — passing 0/0 would make the poll re-add the whole lifetime total back. enabled/expiry untouched; status is derived on read.
func (*Store) RetryFailedBroadcast ¶ added in v1.2.0
RetryFailedBroadcast puts failed recipients back in the queue and resumes the run. 'blocked' rows are left alone: Telegram will refuse them again for the same reason. Both statements run in one transaction, and the re-open is conditional on the run still being 'done'. Split across two writes, a crash in between would leave a 'done' broadcast holding pending rows — a state no API can move, because the controls refuse a finished run and a second retry finds nothing failed.
func (*Store) RevokeAPIKey ¶ added in v0.10.0
RevokeAPIKey marks a key revoked (idempotent). The row is kept so a revoked key still shows in the UI with its metadata; the hash stays so the same raw key can never be silently re-lived.
func (*Store) SaveNodeReality ¶ added in v1.0.0
SaveNodeReality replaces a node's REALITY keypair/shortId/service (regeneration). The private key is encrypted at rest.
func (*Store) SaveNodeWarp ¶ added in v1.0.0
SaveNodeWarp stores a node's WARP registration (WireGuard identity). The private key is encrypted at rest.
func (*Store) SavePaymentProvider ¶ added in v1.0.0
func (s *Store) SavePaymentProvider(p model.PaymentProvider) error
SavePaymentProvider upserts a provider's row. Config is stored encrypted.
func (*Store) SaveTariffPlan ¶ added in v0.4.0
func (s *Store) SaveTariffPlan(p *model.TariffPlan) error
func (*Store) SeeSupportGroup ¶ added in v1.2.0
SeeSupportGroup refreshes what is known about a group WITHOUT touching is_admin. Used when the bot has seen the group but hasn't confirmed its own rights: writing a speculative "not an admin" would overwrite a verified true and send the operator to grant a permission the bot already holds.
func (*Store) SetACMEProvider ¶
SetACMEProvider persists the ACME CA selection and (for ZeroSSL) the External Account Binding credentials. An empty provider defaults to "letsencrypt".
func (*Store) SetAPIPath ¶ added in v0.10.0
SetAPIPath persists the external-API URL segment (empty disables the surface).
func (*Store) SetAbuseConfig ¶ added in v1.3.0
SetAbuseConfig persists the abuse/blocklist config: master switch, the active category bitmask (model.AbuseCat* flags), the operator's custom list, and the daily alert threshold.
func (*Store) SetAdminEvents ¶ added in v0.7.0
SetAdminEvents persists the admin notification bitmask (model.AdminEvent* flags).
func (*Store) SetAdminRole ¶ added in v0.14.0
SetAdminRole changes an admin's role.
func (*Store) SetAntiDPI ¶
SetAntiDPI persists the anti-DPI transport-hardening settings (Settings → Подключения): client-config shaping (TLS fragmentation, QUIC block) and the server-inbound knobs (TLS 1.3 floor, REALITY anti-replay window + donor port).
func (*Store) SetBillingSettings ¶ added in v0.4.0
func (*Store) SetBroadcastMediaFileID ¶ added in v1.2.0
SetBroadcastMediaFileID caches the file_id Telegram assigned on the first upload, so the remaining recipients are served by id instead of re-uploading the file.
func (*Store) SetBroadcastStatus ¶ added in v1.2.0
SetBroadcastStatus moves a broadcast between running/paused/done/cancelled. finishedAt is stamped only for the terminal states; started_at is stamped on the first move to running and never overwritten, so resuming keeps the original launch time rather than pretending the run began at the resume.
func (*Store) SetBroadcastStatusIf ¶ added in v1.2.0
SetBroadcastStatusIf moves a broadcast only if it is still in the state the caller last saw, and reports whether it applied. The worker uses it for every status write it makes: between deciding to write and writing, the operator may have paused or cancelled, and an unconditional UPDATE would silently revert that decision — with the run then resumable through the normal controls.
func (*Store) SetConfigError ¶
SetConfigError records the last failed config-apply error.
func (*Store) SetDecoyTemplate ¶
SetDecoyTemplate persists the masquerade (decoy) template slug.
func (*Store) SetFingerprints ¶
SetFingerprints persists the per-connection uTLS fingerprints used in links.
func (*Store) SetGeoRefresh ¶ added in v1.0.0
SetGeoRefresh persists the geo auto-refresh cadence in hours (0 ⇒ never).
func (*Store) SetHysteriaPorts ¶
SetHysteriaPorts persists the Hysteria2 base port, hop range, and hop interval.
func (*Store) SetIPListRefresh ¶ added in v1.1.0
SetIPListRefresh persists the iplist auto-refresh cadence (hours; 0 ⇒ never).
func (*Store) SetLocalBackup ¶ added in v0.13.0
SetLocalBackup persists the local backup schedule (a 5-field cron expression in the operator timezone; empty disables it) and how many archives to retain.
func (*Store) SetMasterLabel ¶ added in v1.0.0
SetMasterLabel persists the panel server's display name used in config labels.
func (*Store) SetNodeACME ¶ added in v1.0.0
SetNodeACME persists a node's own ACME config: host (target), e-mail, provider and ZeroSSL EAB. The HMAC is encrypted at rest.
func (*Store) SetNodeAPIPath ¶ added in v1.0.0
SetNodeAPIPath persists the node sync API URL segment (generated on first node).
func (*Store) SetNodeConnections ¶ added in v1.0.0
func (s *Store) SetNodeConnections(id int64, c *model.NodeConnections) error
SetNodeConnections persists a node's own transport config (JSON blob). A nil config clears it (the node reverts to inheriting the master's transport).
func (*Store) SetNodeDNS ¶ added in v1.0.0
SetNodeDNS persists a node's own DNS override, touching only the xray_dns column (nil ⇒ inherit the panel's). Kept separate from UpdateNode so the DNS tab saves without rewriting routing/egress.
func (*Store) SetNodeEnabled ¶ added in v1.0.0
SetNodeEnabled toggles whether a node serves traffic and appears in links.
func (*Store) SetNodeGeoRefresh ¶ added in v1.0.0
SetNodeGeoRefresh persists a node's own geo auto-refresh cadence (hours; 0 ⇒ never).
func (*Store) SetNodeProtocols ¶ added in v1.0.0
SetNodeProtocols sets a node's protocol on/off flags explicitly (the node's own — no inheritance).
func (*Store) SetNodeRealityDest ¶ added in v1.0.0
SetNodeRealityDest persists a node's own REALITY masquerade donor (empty ⇒ inherit the panel's).
func (*Store) SetNotifiedExpireAt ¶ added in v1.2.0
SetNotifiedExpireAt records the expiry a "runs out soon" warning was sent for.
func (*Store) SetNotifiedQuotaAt ¶ added in v1.2.0
SetNotifiedQuotaAt marks (at != 0) or re-arms (0) the traffic warning.
func (*Store) SetNotifiedStatus ¶ added in v0.13.0
SetNotifiedStatus records the status a user was last alerted about, so the transition detector's comparison survives a panel restart (see the 0020 migration).
func (*Store) SetOpera ¶
SetOpera persists the Opera VPN egress settings (enable flag, region, and the local proxy port the opera-proxy helper listens on).
func (*Store) SetPanelName ¶ added in v0.5.0
func (*Store) SetPanelTheme ¶ added in v0.5.0
func (*Store) SetPaymentOrderProvider ¶ added in v0.6.0
SetPaymentOrderProvider links an order to an external provider payment.
func (*Store) SetPaymentOrderStatus ¶ added in v0.4.0
func (*Store) SetPaymentWebhookSecret ¶ added in v0.6.0
SetPaymentWebhookSecret stores the random webhook URL segment.
func (*Store) SetProtocolEnabled ¶
SetProtocolEnabled flips a single protocol's on/off toggle.
func (*Store) SetProtocolNames ¶ added in v0.11.0
SetProtocolNames persists the custom per-connection display names (empty ⇒ the default protocol label is used at render time).
func (*Store) SetProxyMode ¶
SetProxyMode persists the forward-proxy inbound configuration.
func (*Store) SetRealityKeys ¶
SetRealityKeys persists a freshly generated REALITY keypair, shortId, and gRPC service name.
func (*Store) SetRealityPorts ¶
SetRealityPorts persists the REALITY port and destination (SNI/serverName).
func (*Store) SetResetPeriod ¶
SetResetPeriod sets a user's automatic quota-reset period and anchors the cycle at now.
func (*Store) SetRoutingConfig ¶
func (s *Store) SetRoutingConfig(cfg model.RoutingConfig) error
SetRoutingConfig persists the structured routing configuration as JSON.
func (*Store) SetSecretPath ¶
SetSecretPath persists the hidden panel path segment.
func (*Store) SetSetupDone ¶
SetSetupDone marks the first-run wizard as completed.
func (*Store) SetSubSettings ¶
SetSubSettings persists the subscription delivery settings.
func (*Store) SetSubToken ¶ added in v0.3.0
SetSubToken replaces a user's subscription capability token. The old URL stops working immediately; protocol credentials (UUID/password) are unchanged.
func (*Store) SetSubscriberBlocked ¶ added in v1.2.0
SetSubscriberBlocked marks a chat undeliverable after Telegram refused permanently (blocked the bot, deactivated account). Rows are kept rather than deleted: the person may unblock, and the next message they send re-activates them.
func (*Store) SetSubscriberOptOut ¶ added in v1.2.0
SetSubscriberOptOut records the /mailing choice. It inserts when the chat isn't known yet, so an opt-out is never lost to a missing row.
func (*Store) SetSupportTopic ¶ added in v1.2.0
SetSupportTopic records (or re-points, after the old topic was deleted) the topic belonging to a chat.
Any stale row already holding this (group, thread) is dropped first. Without that, the unique index rejects the insert and the caller is left having created a topic in Telegram that nothing can address — and since it retries on the next message, one unreachable topic per message, with that user's support dead for good.
func (*Store) SetTLSMode ¶
SetTLSMode persists the TLS mode, domain (host), SNI and ACME e-mail.
func (*Store) SetTelegramBot ¶ added in v0.2.0
SetTelegramBot persists the bot's enable flag, token, and backup schedule (a 5-field cron expression in the operator timezone; empty disables scheduling).
func (*Store) SetTelegramChats ¶ added in v0.2.0
SetTelegramChats replaces the comma-separated set of authorized chat IDs.
func (*Store) SetTelegramLinkCode ¶ added in v0.2.0
SetTelegramLinkCode stores (or clears, with "") the pending one-time linking code.
func (*Store) SetTelegramSupport ¶ added in v1.2.0
func (s *Store) SetTelegramSupport(enabled bool, token, username string, groupID int64, greeting string) error
SetTelegramSupport persists the support relay configuration: the enable flag, the dedicated support bot's token, its resolved @username (cached so the user bot can render a t.me link without a getMe on every menu draw), the forum supergroup id admins answer in, and the greeting shown on /start.
func (*Store) SetTelegramUserBot ¶ added in v0.3.0
SetTelegramUserBot persists the public user bot's enable flag, token, and the self-registration mode + invite code. tg_user_reg_enabled is kept as a derived mirror (mode != off) for any legacy reader.
func (*Store) SetTimezone ¶
SetTimezone persists the operator's IANA timezone (e.g. "Europe/Moscow").
func (*Store) SetUserAutoDeleteDays ¶ added in v0.15.0
SetUserAutoDeleteDays persists the grace period (in days, past a user's expiry date) after which the retention sweep deletes them. 0 disables deletion.
func (*Store) SetUserEnabled ¶
SetUserEnabled sets the independent manual on/off flag. Expiry/quota are separate and never change this.
func (*Store) SetUserEvents ¶ added in v1.2.0
SetUserEvents persists the user-facing notification bitmask (model.UserEvent* flags) and how many days ahead the expiry warning goes out.
func (*Store) SetUserLimits ¶
SetUserLimits sets the data limit (bytes), expiry (unix, 0 = none), and the simultaneous device cap (0 = unlimited). Does not touch the manual enabled flag; status is derived on read.
func (*Store) SetUserName ¶
SetUserName updates a user's display name.
func (*Store) SetUserPlan ¶ added in v0.4.0
func (*Store) SetUserTelegramChat ¶ added in v0.3.0
SetUserTelegramChat links a Telegram chat to a VPN user, first detaching the chat from any other user (one chat ⇒ at most one account).
func (*Store) SetUserTgLinkCode ¶ added in v0.4.0
SetUserTgLinkCode stores (or clears, with "") a user's pending Telegram bind code, stamping the issue time so it can expire.
func (*Store) SetUsersEnabled ¶ added in v0.7.0
SetUsersEnabled flips the manual enabled flag for many users in one statement, returning how many rows changed. Empty ids is a no-op.
func (*Store) SetXrayDNS ¶
SetXrayDNS persists the operator's Xray DNS servers.
func (*Store) SkipRemainingTargets ¶ added in v1.2.0
SkipRemainingTargets closes out the recipients a finished run never reached because they unsubscribed mid-flight. Without it they would sit 'pending' forever against a completed run, and the progress bar would never reach its total.
func (*Store) StatsByUser ¶
StatsByUser returns each user's traffic total over the period (users with no traffic appear with zeros), busiest first.
func (*Store) StatsSeries ¶
StatsSeries returns per-day totals between from and to (inclusive, YYYY-MM-DD). userID == 0 aggregates across all users.
func (*Store) StatsSeriesNode ¶ added in v1.0.0
StatsSeriesNode returns per-day totals for a single node (nodeID, including 0 for the local server) between from and to. userID 0 aggregates across users.
func (*Store) SubscriberByChat ¶ added in v1.2.0
func (s *Store) SubscriberByChat(chatID int64) (*model.Subscriber, error)
SubscriberByChat returns one subscriber, or nil when the chat is unknown.
func (*Store) SupportChatByTopic ¶ added in v1.2.0
SupportChatByTopic resolves an admin's reply back to the user who owns the topic, or 0 when the thread isn't one of ours (a topic opened by hand, or a leftover from a group support no longer points at).
func (*Store) SupportGroupRightsAt ¶ added in v1.2.0
SupportGroupRightsAt reports when the bot's rights in a group were last checked (0 = never). Debouncing against this rather than against last-seen is what keeps a busy group from being checked once and never again, and a quiet one from being checked on every message.
func (*Store) SupportTopicByChat ¶ added in v1.2.0
SupportTopicByChat returns the topic opened for a chat IN THIS GROUP, or 0 when none exists yet (the caller then creates one). A mapping made in another group does not match: thread ids are only unique within a chat, so honouring one across a group switch would address an unrelated conversation.
func (*Store) TouchAdminLogin ¶ added in v0.14.0
TouchAdminLogin records a successful sign-in, so the roster can show who is still actually using their account.
func (*Store) TouchLastSeen ¶
TouchLastSeen updates a user's last activity time (used by the poller too).
func (*Store) UpdateAdminPassword ¶
UpdateAdminPassword replaces an admin's password hash and sets or clears the forced-change gate in the same statement: the two always move together — an admin choosing their own password clears it, an owner assigning one raises it.
func (*Store) UpdateAdminUsername ¶
UpdateAdminUsername changes an admin's login name.
func (*Store) UpdateNode ¶ added in v1.0.0
UpdateNode persists the operator-editable fields. Identity, tokens and reported status are untouched.
func (*Store) UpdateNodeStatus ¶ added in v1.0.0
func (s *Store) UpdateNodeStatus(id int64, st model.NodeStatusUpdate) error
UpdateNodeStatus records what a node reported on a sync: liveness, versions, live cert fingerprint, and the desired-state hash it has applied.
func (*Store) UpdateTraffic ¶
UpdateTraffic adds deltas to lifetime totals and records the raw counters.
func (*Store) UpdateWebhook ¶ added in v0.10.0
UpdateWebhook replaces the mutable fields (url, events, enabled) of an endpoint.
func (*Store) UpsertSubscriber ¶ added in v1.2.0
func (s *Store) UpsertSubscriber(chatID, userID int64, username, firstName, lang string, now int64) error
UpsertSubscriber records a chat that just interacted with the bot, refreshing the profile fields and the linked account.
Two rules the ON CONFLICT clause encodes. Contact re-activates: a message proves Telegram will deliver to this chat again, so an earlier block is stale. Opt-out is never touched: it is the person's own decision, and a broadcast quietly resuming because they typed /start is the behaviour that makes people block the bot instead.
func (*Store) UpsertSupportGroup ¶ added in v1.2.0
func (s *Store) UpsertSupportGroup(chatID int64, title string, isForum, isAdmin bool, now int64) error
UpsertSupportGroup records a group the bot is in, refreshing what is known about it (the operator may enable topics or grant admin after adding the bot).
func (*Store) UserIDs ¶ added in v1.3.0
UserIDs returns the set of existing user ids.
For callers that only need to know whether an id is real — validating what a node reported, say. ListUsers would answer the same question by building every column of every row, which is a lot of work to throw away.
func (*Store) UserIDsOnPlan ¶ added in v0.11.0
UserIDsOnPlan returns the ids of users currently assigned to a plan (used to migrate them when a plan is retired).
func (*Store) UsersWithExpiredPlan ¶ added in v0.4.0
func (*Store) WorkingUsers ¶
WorkingUsers returns users that should be in the proxy config right now: manually enabled AND not expired AND within their data limit AND within their device limit. enabled is an independent manual flag — expiry/quota/devices never change it, they just exclude the user from the config here.
type TrafficBaseline ¶ added in v1.2.0
type TrafficBaseline struct{ Up, Down int64 }
TrafficBaseline is the pair of raw Xray counters a delta was measured against.
type TrafficDelta ¶ added in v1.2.0
type TrafficDelta struct {
UserID int64
NodeID int64
Day string // operator-local calendar day the traffic is booked against
AddUp int64 // delta to add to the lifetime totals
AddDown int64
// Baseline is the raw counters to remember as the next poll's reference point.
// Only the LOCAL poller has them: it reads Xray's cumulative counters and
// subtracts, so it must record where it read. A remote node subtracts on its own
// side and ships the delta already computed, so it leaves this nil — and
// last_up/last_down, which track the master's own Xray, stay untouched. Writing
// a node's numbers there would corrupt the next local poll's arithmetic.
Baseline *TrafficBaseline
SeenAt int64 // stamp last_seen with this; 0 leaves it alone
}
TrafficDelta is one user's accounted traffic from a single reporting cycle.
type UserCounts ¶ added in v1.2.0
UserCounts is the dashboard's view of the users table: how many there are, how many are working, and the lifetime traffic totals.
type UserEventFilter ¶ added in v0.13.0
type UserEventFilter struct {
Action string
ActorKind string
UserID int64
BeforeID int64 // page backwards from this id
Limit int
}
UserEventFilter narrows the global journal. A zero field means "no filter".
type UserPlanWrite ¶ added in v1.2.0
type UserPlanWrite struct {
UserID int64
DataLimit int64
ExpireAt int64
DeviceLimit int
ResetPeriod string
ResetAnchor int64 // last_reset_at: when the rolling quota cycle starts counting
PlanID int64
TrialUsed bool
}
UserPlanWrite is every users column a plan assignment touches. It exists so a caller can compute the whole target state up front and hand it over in one piece, because the three UPDATEs behind it have to land together: a user left with the new limits but the old plan_id (or vice versa) is a state nothing reconciles.