store

package
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
var ErrAdminNotFound = errors.New("admin not found")

ErrAdminNotFound is returned when an admin id matches no row.

View Source
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.

View Source
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.

View Source
var ErrRegistrationPending = errors.New("registration already pending")

ErrRegistrationPending means the chat already has a pending request.

Functions

func Check added in v0.15.0

func Check(path string) error

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

func InspectDB(path string) (users, admins int, secret string, err error)

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

func PeekTimezone(dbPath string) string

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

type ConnectionHit struct {
	UserID int64
	IP     string
	SeenAt int64
	Hits   int64
}

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

type SessionAdmin struct {
	ID                 int64
	Username           string
	Role               string
	MustChangePassword bool
}

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

func Open(path string) (*Store, error)

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

func (s *Store) AbuseUserCountsForDay(day string) (map[int64]int64, error)

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

func (s *Store) ActiveDeviceCounts(since int64) (map[int64]int, error)

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

func (s *Store) AddAbuseMatches(hits []AbuseHit) error

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

func (s *Store) AddBroadcastTargets(broadcastID int64, chatIDs []int64) error

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

func (s *Store) AddConnection(userID int64, ip string, ts int64) error

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

func (s *Store) AddDailyTraffic(userID int64, day string, up, down int64) error

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

func (s *Store) AddDailyTrafficNode(userID, nodeID int64, day string, up, down int64) error

AddDailyTrafficNode adds up/down deltas attributed to a specific node.

func (*Store) AddUsedTraffic added in v1.0.0

func (s *Store) AddUsedTraffic(userID, up, down int64) error

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

func (s *Store) AddUserEvent(ev model.UserEvent) error

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

func (s *Store) BackdateUserForTest(id int64, at time.Time) error

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

func (s *Store) CancelPaymentOrderIfPending(id int64) (bool, error)

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

func (s *Store) Checkpoint() error

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

func (s *Store) ClaimRegistrationRequest(id int64) (bool, error)

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

func (s *Store) ClearUserTelegramChat(userID int64) error

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

func (s *Store) ClearUserTgLinkCode(userID int64) error

ClearUserTgLinkCode burns a user's pending Telegram bind code (after a successful link or on demand).

func (*Store) Close

func (s *Store) Close() error

Close releases the database.

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

func (s *Store) ConsumeJoinToken(raw string) (*model.Node, string, error)

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

func (s *Store) CountAdmins() (int, error)

CountAdmins returns the number of admin accounts.

func (*Store) CountSubscribers added in v1.2.0

func (s *Store) CountSubscribers() (total, reachable int, err error)

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.

Online rides on the device-count join that was already there: a user with at least one IP seen inside DeviceOnlineWindow is connected right now. Deliberately the same window that defines an "active device" everywhere else, so the dashboard and a user's device count can never tell different stories about who is on. It counts the whole fleet, not just this server — a node's sightings land in the same connections table when it syncs.

func (*Store) CountUsersOnPlan added in v0.4.0

func (s *Store) CountUsersOnPlan(planID int64) (int, error)

CountUsersOnPlan returns how many users currently have this plan assigned.

func (*Store) CreateAPIKey added in v0.10.0

func (s *Store) CreateAPIKey(name string) (*model.APIKey, error)

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

func (s *Store) CreateAdmin(username, passwordHash, role string, mustChange bool) (int64, error)

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

func (s *Store) CreateBroadcast(b *model.Broadcast, now int64) (int64, error)

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

func (s *Store) CreateNode(name, host, decoyTemplate string) (*model.Node, error)

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 (s *Store) CreatePaymentOrder(userID, planID int64, amountRub int) (*model.PaymentOrder, error)

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

func (s *Store) CreateSession(adminID int64, ttl time.Duration) (string, error)

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

func (s *Store) CreateWebhook(url string, events []string) (*model.Webhook, error)

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

func (s *Store) DeleteAdmin(id int64) error

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

func (s *Store) DeleteNode(id int64) error

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

func (s *Store) DeleteRegistrationRequest(id int64) error

DeleteRegistrationRequest removes a request (after approval or rejection).

func (*Store) DeleteSession

func (s *Store) DeleteSession(token string) error

DeleteSession revokes a session by its raw token.

func (*Store) DeleteSessionsForAdmin added in v0.4.0

func (s *Store) DeleteSessionsForAdmin(adminID int64) error

DeleteSessionsForAdmin revokes every session for an admin.

func (*Store) DeleteSessionsForAdminExcept

func (s *Store) DeleteSessionsForAdminExcept(adminID int64, keepToken string) error

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

func (s *Store) DeleteSupportGroup(chatID int64) error

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

func (s *Store) DeleteSupportTopic(groupID, chatID int64) error

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 (s *Store) DeleteTariffPlan(id int64) error

func (*Store) DeleteUser

func (s *Store) DeleteUser(id int64) error

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

func (s *Store) DeleteUsers(ids []int64) (int64, error)

DeleteUsers removes many users in one statement, returning how many were deleted.

func (*Store) DeleteWebhook added in v0.10.0

func (s *Store) DeleteWebhook(id int64) error

DeleteWebhook removes an endpoint.

func (*Store) EnabledWebhooksForEvent added in v0.10.0

func (s *Store) EnabledWebhooksForEvent(event string) ([]model.Webhook, error)

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

func (s *Store) ExpiredUsersBefore(cutoff int64) ([]model.User, error)

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

func (s *Store) FinishedBroadcastIDs() ([]int64, error)

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) GetAdmin added in v0.14.0

func (s *Store) GetAdmin(id int64) (model.Admin, error)

GetAdmin returns one admin by id, or ErrAdminNotFound.

func (*Store) GetAdminAuth

func (s *Store) GetAdminAuth(username string) (id int64, hash, role string, err error)

GetAdminAuth returns the id, password hash and role for a username.

func (*Store) GetAdminHash

func (s *Store) GetAdminHash(id int64) (string, error)

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

func (s *Store) GetBroadcast(id int64) (*model.Broadcast, error)

GetBroadcast returns one broadcast with its progress counters.

func (*Store) GetDetachedUserByPrevChat added in v0.11.0

func (s *Store) GetDetachedUserByPrevChat(chatID int64) (*model.User, error)

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

func (s *Store) GetNode(id int64) (*model.Node, error)

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

func (s *Store) GetSettings() (*model.Settings, error)

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) GetUser added in v0.3.0

func (s *Store) GetUser(id int64) (*model.User, error)

GetUser returns one user by id.

func (*Store) GetUserBySubToken

func (s *Store) GetUserBySubToken(token string) (*model.User, error)

GetUserBySubToken resolves a subscription token to its user.

func (*Store) GetUserByTelegramChatID added in v0.3.0

func (s *Store) GetUserByTelegramChatID(chatID int64) (*model.User, error)

GetUserByTelegramChatID resolves a linked Telegram chat to its VPN user.

func (*Store) GetUserByTgLinkCode added in v0.4.0

func (s *Store) GetUserByTgLinkCode(code string) (*model.User, error)

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

func (s *Store) GetWebhook(id int64) (*model.Webhook, error)

GetWebhook returns one endpoint by id.

func (*Store) IssueJoinToken added in v1.0.0

func (s *Store) IssueJoinToken(id int64) (string, error)

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

func (s *Store) ListAPIKeys() ([]model.APIKey, error)

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

func (s *Store) ListAdmins() ([]model.Admin, error)

ListAdmins returns the roster, owner first, then by creation order.

func (*Store) ListBroadcasts added in v1.2.0

func (s *Store) ListBroadcasts(limit int) ([]model.Broadcast, error)

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

func (s *Store) ListNodes() ([]model.Node, error)

ListNodes returns all live (non-tombstoned) nodes, oldest first. RawJoinToken is never populated here.

func (*Store) ListPaymentOrders added in v0.4.0

func (s *Store) ListPaymentOrders(status string, limit int) ([]model.PaymentOrder, error)

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

func (s *Store) ListUserEvents(userID int64, limit int, beforeID int64) ([]model.UserEvent, error)

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) ListUsers

func (s *Store) ListUsers() ([]model.User, error)

ListUsers returns all users, newest first.

func (*Store) ListWebhooks added in v0.10.0

func (s *Store) ListWebhooks() ([]model.Webhook, error)

ListWebhooks returns every configured endpoint, newest first.

func (*Store) LookupAPIKey added in v0.10.0

func (s *Store) LookupAPIKey(raw string) (*model.APIKey, error)

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

func (s *Store) LookupNodeByToken(raw string) (*model.Node, error)

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

func (s *Store) MarkConfigApplied() error

MarkConfigApplied bumps the config revision and clears any prior error.

func (*Store) MarkTarget added in v1.2.0

func (s *Store) MarkTarget(broadcastID, chatID int64, state, errMsg string, at int64) error

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

func (s *Store) MarkWebhookResult(id int64, status int, errStr string) error

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

func (s *Store) NextPendingTargets(broadcastID int64, limit int) ([]int64, error)

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

func (s *Store) NextRunningBroadcast() (*model.Broadcast, error)

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

func (s *Store) NodeNameTaken(name string, excludeID int64) (bool, error)

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

func (s *Store) NodeNames() (map[int64]string, error)

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

func (s *Store) NodeTrafficTotals(userID int64, from, to string) (map[int64][2]int64, error)

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

func (s *Store) PaidSumSince(since int64) (int, error)

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

func (s *Store) PendingTotals() (count, sum int, err error)

PendingTotals returns the count and rouble total of orders awaiting payment.

func (*Store) PruneSupportGroups added in v1.2.0

func (s *Store) PruneSupportGroups(before int64) error

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

func (s *Store) PurgeAbuseMatches(beforeDay string) (int64, error)

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

func (s *Store) PurgeAdminAudit(before int64) (int64, error)

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

func (s *Store) PurgeConnections(before int64) (int64, error)

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

func (s *Store) PurgeDeletedNodes(before int64) (int64, error)

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

func (s *Store) PurgeTrafficDaily(beforeDay string) (int64, error)

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

func (s *Store) PurgeUserEvents(before int64) (int64, error)

PurgeUserEvents drops audit rows older than the cutoff (unix seconds), returning how many were removed. This is the retention sweep.

func (*Store) RecentConnections

func (s *Store) RecentConnections(userID int64, limit int) ([]model.Connection, error)

RecentConnections returns a user's source IPs, most recent first.

func (*Store) ReencryptSensitiveFields added in v0.4.0

func (s *Store) ReencryptSensitiveFields() error

ReencryptSensitiveFields migrates legacy plaintext secrets to enc:v1: at-rest blobs.

func (*Store) RegenJoinToken added in v1.0.0

func (s *Store) RegenJoinToken(id int64) (string, error)

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

func (s *Store) ResetDailyStats() error

ResetDailyStats clears the entire per-day traffic history.

func (*Store) ResetTraffic

func (s *Store) ResetTraffic(id, lastUp, lastDown int64) error

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

func (s *Store) ResetUserQuota(id, now, lastUp, lastDown int64) error

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

func (s *Store) RetryFailedBroadcast(id, now int64) (int, error)

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

func (s *Store) RevokeAPIKey(id int64) error

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

func (s *Store) SaveNodeReality(id int64, priv, pub, shortID, service string) error

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

func (s *Store) SaveNodeWarp(id int64, priv, pub, endpoint, v4, v6, reserved string) error

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

func (s *Store) SeeSupportGroup(chatID int64, title string, isForum bool, now int64) error

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

func (s *Store) SetACMEProvider(provider, eabKID, eabHMAC string) error

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

func (s *Store) SetAPIPath(p string) error

SetAPIPath persists the external-API URL segment (empty disables the surface).

func (*Store) SetAbuseConfig added in v1.3.0

func (s *Store) SetAbuseConfig(enabled bool, categories int64, custom string, alertMin int) error

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

func (s *Store) SetAdminEvents(mask int64) error

SetAdminEvents persists the admin notification bitmask (model.AdminEvent* flags).

func (*Store) SetAdminRole added in v0.14.0

func (s *Store) SetAdminRole(id int64, role string) error

SetAdminRole changes an admin's role.

func (*Store) SetAntiDPI

func (s *Store) SetAntiDPI(tlsFragment, tlsMin13, blockQUIC bool, realityMaxTimeDiff int) error

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 (s *Store) SetBillingSettings(st *model.Settings) error

func (*Store) SetBroadcastMediaFileID added in v1.2.0

func (s *Store) SetBroadcastMediaFileID(id int64, fileID string) error

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

func (s *Store) SetBroadcastStatus(id int64, status string, at int64) error

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

func (s *Store) SetBroadcastStatusIf(id int64, from, to string, at int64) (bool, error)

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

func (s *Store) SetConfigError(msg string) error

SetConfigError records the last failed config-apply error.

func (*Store) SetDecoyTemplate

func (s *Store) SetDecoyTemplate(name string) error

SetDecoyTemplate persists the masquerade (decoy) template slug.

func (*Store) SetFingerprints

func (s *Store) SetFingerprints(vless, trojan, reality string) error

SetFingerprints persists the per-connection uTLS fingerprints used in links.

func (*Store) SetGeoRefresh added in v1.0.0

func (s *Store) SetGeoRefresh(hours int) error

SetGeoRefresh persists the geo auto-refresh cadence in hours (0 ⇒ never).

func (*Store) SetHysteriaPorts

func (s *Store) SetHysteriaPorts(port, hopStart, hopEnd int, interval string) error

SetHysteriaPorts persists the Hysteria2 base port, hop range, and hop interval.

func (*Store) SetIPListRefresh added in v1.1.0

func (s *Store) SetIPListRefresh(hours int) error

SetIPListRefresh persists the iplist auto-refresh cadence (hours; 0 ⇒ never).

func (*Store) SetLocalBackup added in v0.13.0

func (s *Store) SetLocalBackup(cron string, keep int) error

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

func (s *Store) SetMasterLabel(label string) error

SetMasterLabel persists the panel server's display name used in config labels.

func (*Store) SetNodeACME added in v1.0.0

func (s *Store) SetNodeACME(id int64, host, email, provider, eabKID, eabHMAC string) error

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

func (s *Store) SetNodeAPIPath(p string) error

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

func (s *Store) SetNodeDNS(id int64, dns *string) error

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

func (s *Store) SetNodeEnabled(id int64, enabled bool) error

SetNodeEnabled toggles whether a node serves traffic and appears in links.

func (*Store) SetNodeGeoRefresh added in v1.0.0

func (s *Store) SetNodeGeoRefresh(id int64, hours int) error

SetNodeGeoRefresh persists a node's own geo auto-refresh cadence (hours; 0 ⇒ never).

func (*Store) SetNodeProtocols added in v1.0.0

func (s *Store) SetNodeProtocols(id int64, vless, trojan, hysteria, reality bool) error

SetNodeProtocols sets a node's protocol on/off flags explicitly (the node's own — no inheritance).

func (*Store) SetNodeRealityDest added in v1.0.0

func (s *Store) SetNodeRealityDest(id int64, dest string) error

SetNodeRealityDest persists a node's own REALITY masquerade donor (empty ⇒ inherit the panel's).

func (*Store) SetNotifiedExpireAt added in v1.2.0

func (s *Store) SetNotifiedExpireAt(id, expireAt int64) error

SetNotifiedExpireAt records the expiry a "runs out soon" warning was sent for.

func (*Store) SetNotifiedQuotaAt added in v1.2.0

func (s *Store) SetNotifiedQuotaAt(id, at int64) error

SetNotifiedQuotaAt marks (at != 0) or re-arms (0) the traffic warning.

func (*Store) SetNotifiedStatus added in v0.13.0

func (s *Store) SetNotifiedStatus(id int64, status string) error

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

func (s *Store) SetOpera(enabled bool, country string, port int) error

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 (s *Store) SetPanelName(name string) error

func (*Store) SetPanelTheme added in v0.5.0

func (s *Store) SetPanelTheme(themeJSON string) error

func (*Store) SetPaymentOrderProvider added in v0.6.0

func (s *Store) SetPaymentOrderProvider(id int64, provider, providerID, payURL string) error

SetPaymentOrderProvider links an order to an external provider payment.

func (*Store) SetPaymentOrderStatus added in v0.4.0

func (s *Store) SetPaymentOrderStatus(id int64, status string, paidAt int64) error

func (*Store) SetPaymentWebhookSecret added in v0.6.0

func (s *Store) SetPaymentWebhookSecret(secret string) error

SetPaymentWebhookSecret stores the random webhook URL segment.

func (*Store) SetProtocolEnabled

func (s *Store) SetProtocolEnabled(name string, enabled bool) error

SetProtocolEnabled flips a single protocol's on/off toggle.

func (*Store) SetProtocolNames added in v0.11.0

func (s *Store) SetProtocolNames(vless, reality, trojan, hysteria string) error

SetProtocolNames persists the custom per-connection display names (empty ⇒ the default protocol label is used at render time).

func (*Store) SetProxyMode

func (s *Store) SetProxyMode(enabled bool, typ string, port int, user, pass string) error

SetProxyMode persists the forward-proxy inbound configuration.

func (*Store) SetRealityKeys

func (s *Store) SetRealityKeys(priv, pub, shortID, serviceName string) error

SetRealityKeys persists a freshly generated REALITY keypair, shortId, and gRPC service name.

func (*Store) SetRealityPorts

func (s *Store) SetRealityPorts(port int, dest string) error

SetRealityPorts persists the REALITY port and destination (SNI/serverName).

func (*Store) SetResetPeriod

func (s *Store) SetResetPeriod(id int64, period string, now int64) error

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

func (s *Store) SetSecretPath(p string) error

SetSecretPath persists the hidden panel path segment.

func (*Store) SetSetupDone

func (s *Store) SetSetupDone(done bool) error

SetSetupDone marks the first-run wizard as completed.

func (*Store) SetSubSettings

func (s *Store) SetSubSettings(st *model.Settings) error

SetSubSettings persists the subscription delivery settings.

func (*Store) SetSubToken added in v0.3.0

func (s *Store) SetSubToken(id int64, token string) error

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

func (s *Store) SetSubscriberBlocked(chatID, at int64) error

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

func (s *Store) SetSubscriberOptOut(chatID int64, out bool, now int64) error

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

func (s *Store) SetSupportTopic(groupID, chatID, topicID, now int64) error

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) SetTLS

func (s *Store) SetTLS(host, sni, mode, certPath, keyPath string) error

SetTLS persists host/SNI/cert configuration (used on first boot).

func (*Store) SetTLSMode

func (s *Store) SetTLSMode(mode, host, sni, acmeEmail string) error

SetTLSMode persists the TLS mode, domain (host), SNI and ACME e-mail.

func (*Store) SetTelegramBot added in v0.2.0

func (s *Store) SetTelegramBot(enabled bool, token, cron string) error

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

func (s *Store) SetTelegramChats(csv string) error

SetTelegramChats replaces the comma-separated set of authorized chat IDs.

func (*Store) SetTelegramLinkCode added in v0.2.0

func (s *Store) SetTelegramLinkCode(code string) error

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

func (s *Store) SetTelegramUserBot(enabled bool, token, regMode, regCode string) error

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

func (s *Store) SetTimezone(tz string) error

SetTimezone persists the operator's IANA timezone (e.g. "Europe/Moscow").

func (*Store) SetUserAutoDeleteDays added in v0.15.0

func (s *Store) SetUserAutoDeleteDays(days int) error

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

func (s *Store) SetUserEnabled(id int64, enabled bool) error

SetUserEnabled sets the independent manual on/off flag. Expiry/quota are separate and never change this.

func (*Store) SetUserEvents added in v1.2.0

func (s *Store) SetUserEvents(mask int64, expiringDays int) error

SetUserEvents persists the user-facing notification bitmask (model.UserEvent* flags) and how many days ahead the expiry warning goes out.

func (*Store) SetUserLimits

func (s *Store) SetUserLimits(id, dataLimit, expireAt int64, deviceLimit int) error

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

func (s *Store) SetUserName(id int64, name string) error

SetUserName updates a user's display name.

func (*Store) SetUserPlan added in v0.4.0

func (s *Store) SetUserPlan(userID, planID int64, trialUsed bool) error

func (*Store) SetUserTelegramChat added in v0.3.0

func (s *Store) SetUserTelegramChat(userID, chatID int64) error

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

func (s *Store) SetUserTgLinkCode(userID int64, code string) error

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

func (s *Store) SetUsersEnabled(ids []int64, enabled bool) (int64, error)

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) SetWSPath

func (s *Store) SetWSPath(p string) error

SetWSPath persists the Trojan-WS path.

func (*Store) SetWarp

func (s *Store) SetWarp(st *model.Settings) error

SetWarp persists the WARP enabled flag plus the provisioned account fields.

func (*Store) SetXrayDNS

func (s *Store) SetXrayDNS(dns string) error

SetXrayDNS persists the operator's Xray DNS servers.

func (*Store) SkipRemainingTargets added in v1.2.0

func (s *Store) SkipRemainingTargets(broadcastID int64) error

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

func (s *Store) StatsByUser(from, to string) ([]model.UserTotal, error)

StatsByUser returns each user's traffic total over the period (users with no traffic appear with zeros), busiest first.

func (*Store) StatsSeries

func (s *Store) StatsSeries(userID int64, from, to string) ([]model.DailyPoint, error)

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

func (s *Store) StatsSeriesNode(userID, nodeID int64, from, to string) ([]model.DailyPoint, error)

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

func (s *Store) SupportChatByTopic(groupID, topicID int64) (int64, error)

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

func (s *Store) SupportGroupRightsAt(chatID int64) (int64, error)

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

func (s *Store) SupportTopicByChat(groupID, chatID int64) (int64, error)

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

func (s *Store) TouchAdminLogin(id int64) error

TouchAdminLogin records a successful sign-in, so the roster can show who is still actually using their account.

func (*Store) TouchLastSeen

func (s *Store) TouchLastSeen(userID, ts int64) error

TouchLastSeen updates a user's last activity time (used by the poller too).

func (*Store) UpdateAdminPassword

func (s *Store) UpdateAdminPassword(id int64, passwordHash string, mustChange bool) error

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

func (s *Store) UpdateAdminUsername(id int64, username string) error

UpdateAdminUsername changes an admin's login name.

func (*Store) UpdateNode added in v1.0.0

func (s *Store) UpdateNode(id int64, e NodeEdit) error

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

func (s *Store) UpdateTraffic(id, addUp, addDown, lastUp, lastDown int64) error

UpdateTraffic adds deltas to lifetime totals and records the raw counters.

func (*Store) UpdateWebhook added in v0.10.0

func (s *Store) UpdateWebhook(id int64, url string, events []string, enabled bool) error

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

func (s *Store) UserIDs() (map[int64]struct{}, error)

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

func (s *Store) UserIDsOnPlan(planID int64) ([]int64, error)

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 (s *Store) UsersWithExpiredPlan(now int64) ([]model.User, error)

func (*Store) WorkingUsers

func (s *Store) WorkingUsers(now int64) ([]model.User, error)

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

type UserCounts struct {
	Total     int
	Active    int
	Online    int
	TotalUp   int64
	TotalDown int64
}

UserCounts is the dashboard's view of the users table: how many there are, how many are working, how many are connected right now, 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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL