1507ceb793
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 21s
CI / ui (pull_request) Successful in 1m15s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 2m0s
Let an operator disable purchases live from the admin — a whole rail/channel or one account — and show the user a localized reason on their next attempt, so a provider outage or a misconfig is explained instead of a silent dead button. - rail kill switch (payments.rail_status, per rail direct:web / direct:android / vk / telegram): enabled + a per-language message, edited on the catalog page. Fail-open — a rail with no row stays enabled, so payments are never accidentally killed. The intake gate (CanPurchase in handleWalletOrder, before the order) returns payment_unavailable + the localized message, orthogonal to the security gates. - per-account override (payments.account_payment_override, a row only for non-default): allow / deny / default, edited on the user card. "allow" bypasses ONLY the ops rail switch, never the security gates (trusted platform, the email anchor, the VK-iOS freeze, the min client version). - wire: an additive ExecuteResponse.message envelope field (frozen-contract-safe); the gateway forwards a backend domain-error message; the client shows it on a payment_unavailable buy attempt. - admin: rail toggles on the catalog page, the override control on the user card. - tests: the pure gate (unit, TDD), the store + gate + override end-to-end (integration, migration 00016), the client (svelte-check / vitest). - docs: PAYMENTS (+ru), the decisions log (D45/D46). Fiscalization stays cabinet-side (owner decision) — no itemized-receipt code. Contour-safe: additive migration (two new tables, no wipe), the wire add is additive, and fail-open so nothing is disabled until an operator acts.
767 lines
24 KiB
Go
767 lines
24 KiB
Go
package adminconsole
|
|
|
|
import "html/template"
|
|
|
|
// The *View types are the display models the gin handlers fill and the templates
|
|
// render. Time values are pre-formatted to strings by the handlers so the
|
|
// templates stay logic-free.
|
|
|
|
// Pager is the shared list pagination state.
|
|
type Pager struct {
|
|
Page int
|
|
PageSize int
|
|
Total int
|
|
HasPrev bool
|
|
HasNext bool
|
|
PrevPage int
|
|
NextPage int
|
|
}
|
|
|
|
// NewPager builds the pagination state for a 1-based page of pageSize over total
|
|
// items.
|
|
func NewPager(page, pageSize, total int) Pager {
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
p := Pager{Page: page, PageSize: pageSize, Total: total, PrevPage: page - 1, NextPage: page + 1}
|
|
p.HasPrev = page > 1
|
|
p.HasNext = page*pageSize < total
|
|
return p
|
|
}
|
|
|
|
// VariantVersions lists the dictionary versions resident for one variant.
|
|
type VariantVersions struct {
|
|
Variant string
|
|
Latest string
|
|
Versions []string
|
|
}
|
|
|
|
// DashboardView is the landing-page summary.
|
|
type DashboardView struct {
|
|
Accounts int
|
|
Games int
|
|
ActiveGames int
|
|
OpenComplaints int
|
|
OpenFeedback int
|
|
PendingChanges int
|
|
// ActiveVersion is the dictionary version new games pin (the persisted active
|
|
// version), distinct from the per-variant resident versions.
|
|
ActiveVersion string
|
|
Variants []VariantVersions
|
|
}
|
|
|
|
// UsersView is the paginated account list.
|
|
type UsersView struct {
|
|
Items []UserRow
|
|
Pager Pager
|
|
// Robots is the active people/robots toggle; NameMask/ExternalIDMask are the current
|
|
// glob filters; FilterQuery is those URL-encoded for the pager links. It is an
|
|
// already-escaped query fragment (url.Values.Encode), so it is typed template.URL to
|
|
// be emitted verbatim — interpolated as a plain string it would have its "=" and "&"
|
|
// percent-encoded again by the contextual escaper.
|
|
Robots bool
|
|
Deleted bool
|
|
NameMask string
|
|
ExternalIDMask string
|
|
EmailExact string
|
|
FilterQuery template.URL
|
|
}
|
|
|
|
// UserRow is one account row in the list. MoveMin/Avg/Max are the account's
|
|
// pre-formatted move-duration summary (empty when it has no timed move);
|
|
// FlaggedHighRate marks the soft high-rate badge.
|
|
type UserRow struct {
|
|
ID string
|
|
DisplayName string
|
|
Kind string
|
|
Language string
|
|
Guest bool
|
|
Deleted bool
|
|
FlaggedHighRate bool
|
|
CreatedAt string
|
|
HasMoveStats bool
|
|
MoveMin string
|
|
MoveAvg string
|
|
MoveMax string
|
|
}
|
|
|
|
// MessagesView is the paginated chat-message moderation list. NameMask/ExtMask are the
|
|
// current sender glob filters; GameID/UserID pin the list to one game / sender (set from a
|
|
// game or user card); FilterQuery is the active filters URL-encoded for the pager and CSV
|
|
// links — an already-escaped query fragment, hence template.URL so it is not re-encoded
|
|
// inside the link (see UsersView.FilterQuery).
|
|
type MessagesView struct {
|
|
Items []MessageRow
|
|
Pager Pager
|
|
NameMask string
|
|
ExtMask string
|
|
GameID string
|
|
UserID string
|
|
UnreadOnly bool
|
|
FilterQuery template.URL
|
|
}
|
|
|
|
// MessageRow is one chat message in the moderation list: its sender (linked to the user
|
|
// card), source, IP, body, game (linked to the game card), time, and whether it is still
|
|
// unread by at least one recipient.
|
|
type MessageRow struct {
|
|
ID string
|
|
SenderID string
|
|
SenderName string
|
|
Source string
|
|
IP string
|
|
Body string
|
|
GameID string
|
|
CreatedAt string
|
|
Unread bool
|
|
}
|
|
|
|
// ChatMessageDetailView is one chat message with its per-seat read breakdown, for the
|
|
// console message card.
|
|
type ChatMessageDetailView struct {
|
|
ID string
|
|
GameID string
|
|
SenderID string
|
|
SenderName string
|
|
Source string
|
|
Kind string
|
|
Body string
|
|
IP string
|
|
CreatedAt string
|
|
Unread bool
|
|
Seats []ChatSeatStatusRow
|
|
}
|
|
|
|
// ChatSeatStatusRow is one seat's read status on the message card: the seat index, the
|
|
// occupant (linked to the user card) and its role ("sender", "read" or "unread").
|
|
type ChatSeatStatusRow struct {
|
|
Seat int
|
|
AccountID string
|
|
DisplayName string
|
|
Role string
|
|
}
|
|
|
|
// UserDetailView is one account with its stats, identities and recent games.
|
|
type UserDetailView struct {
|
|
ID string
|
|
DisplayName string
|
|
Language string
|
|
TimeZone string
|
|
Guest bool
|
|
NotificationsInAppOnly bool
|
|
// MergedInto is the primary account id when this account has been retired by a
|
|
// merge, or empty for a live account.
|
|
MergedInto string
|
|
// The account-deletion dossier. Deleted marks a tombstoned account; DeletedAt and
|
|
// DeletedName are its deletion time and retained real name; LastLoginAt/IP are the
|
|
// last cold-load stamp (shown for any account); Retained is the credential journal.
|
|
Deleted bool
|
|
DeletedAt string
|
|
DeletedName string
|
|
LastLoginAt string
|
|
LastLoginIP string
|
|
Retained []RetainedRow
|
|
// FlaggedHighRateAt is the pre-formatted soft high-rate marker timestamp,
|
|
// empty for an unflagged account; the card shows it with the Clear action.
|
|
FlaggedHighRateAt string
|
|
CreatedAt string
|
|
HasStats bool
|
|
Stats StatsRow
|
|
Identities []IdentityRow
|
|
// HasEmail gates the "Erase email" action; set when the account carries an email identity.
|
|
HasEmail bool
|
|
Games []GameRow
|
|
// TelegramID and VKID are the account's platform external ids (empty when absent).
|
|
// TelegramID gates the "Send Telegram message" operator action; VKID surfaces the VK
|
|
// user id with a link to the VK profile (there is no VK messaging to drive).
|
|
TelegramID string
|
|
VKID string
|
|
ConnectorEnabled bool
|
|
// MoveChart is the pre-rendered inline SVG of the account's per-move-number think
|
|
// time (min/mean/max), empty when the account has no timed move.
|
|
MoveChart template.HTML
|
|
// Suspension is the account's current manual-block state, shown with the block/unblock
|
|
// form; Reasons is the operator-editable reason picklist offered in the block form.
|
|
Suspension SuspensionView
|
|
Reasons []ReasonOption
|
|
// Roles is the account's current roles (each revocable); KnownRoles is the set the
|
|
// grant form offers. The first role is the feedback ban (see internal/account).
|
|
Roles []string
|
|
KnownRoles []string
|
|
// Blocks, BlockedBy and Friends are the social graph on the card: who this account has
|
|
// blocked, who currently blocks it, and its mutual friendships — each cross-linked to the
|
|
// other account with the date it happened. They are the full truth; the asymmetric block
|
|
// suppression that hides relationships from players never applies to the console.
|
|
Blocks []RelationRow
|
|
BlockedBy []RelationRow
|
|
Friends []RelationRow
|
|
// Finance is the account's payments picture (balances, benefits, refund risk, ledger). Present
|
|
// is false when the payments domain is unwired.
|
|
Finance FinanceView
|
|
// PurchaseOverride is the account's per-account purchase override ("default"/"allow"/"deny"),
|
|
// shown in and edited from the user card's payment-override control.
|
|
PurchaseOverride string
|
|
// Grant is the admin-grant panel (origin picker + grantable products). Present is false when the
|
|
// payments domain is unwired.
|
|
Grant GrantFormView
|
|
}
|
|
|
|
// FinanceView is the account's payments picture on the user card: chip balances per funding
|
|
// segment, benefits per origin, the recorded refund risk, and the append-only ledger history
|
|
// (newest first). Present is false when the payments domain is unwired.
|
|
type FinanceView struct {
|
|
Present bool
|
|
Segments []SegmentRow
|
|
Benefits []BenefitRow
|
|
// Abuse is the refund abuse flag; Loss is the unrecoverable chip loss from floor-0 refunds.
|
|
Abuse bool
|
|
Loss int
|
|
Ledger []LedgerRow
|
|
}
|
|
|
|
// SegmentRow is one funding segment's chip balance.
|
|
type SegmentRow struct {
|
|
Source string
|
|
Chips int
|
|
}
|
|
|
|
// BenefitRow is one origin's benefit: the hint wallet, the ad-free expiry (pre-formatted, empty
|
|
// when none) and the lifetime ad-free flag.
|
|
type BenefitRow struct {
|
|
Origin string
|
|
Hints int
|
|
AdsUntil string
|
|
Forever bool
|
|
}
|
|
|
|
// LedgerRow is one append-only ledger entry: its kind, funding source / benefit origin, signed chip
|
|
// delta, the product / order / provider / direct-rail shop it references (empty when none), the raw
|
|
// snapshot JSON and the pre-formatted time.
|
|
type LedgerRow struct {
|
|
Kind string
|
|
Source string
|
|
Origin string
|
|
ChipsDelta int
|
|
Product string
|
|
Order string
|
|
Provider string
|
|
Shop string
|
|
Snapshot string
|
|
At string
|
|
}
|
|
|
|
// RelationRow is one cross-linked account in the user card's blocks / blocked-by / friends
|
|
// lists: the other account's id (the link target), its display name, and the pre-formatted date.
|
|
type RelationRow struct {
|
|
AccountID string
|
|
DisplayName string
|
|
Date string
|
|
}
|
|
|
|
// SuspensionView is an account's current manual-block state shown on the user card: whether it
|
|
// is blocked, whether the block is permanent, the pre-formatted expiry (empty when permanent),
|
|
// when it was applied, and the reason snapshot in both languages (empty when none was cited).
|
|
type SuspensionView struct {
|
|
Blocked bool
|
|
Permanent bool
|
|
Until string
|
|
BlockedAt string
|
|
ReasonEn string
|
|
ReasonRu string
|
|
}
|
|
|
|
// ReasonOption is one suspension-reason picklist entry offered in the block form's dropdown.
|
|
type ReasonOption struct {
|
|
ID string
|
|
TextEn string
|
|
TextRu string
|
|
}
|
|
|
|
// StatsRow is an account's lifetime statistics.
|
|
type StatsRow struct {
|
|
Wins int
|
|
Losses int
|
|
Draws int
|
|
MaxGamePoints int
|
|
MaxWordPoints int
|
|
Moves int
|
|
HintsUsed int
|
|
}
|
|
|
|
// IdentityRow is one platform/email identity of an account.
|
|
type IdentityRow struct {
|
|
Kind string
|
|
ExternalID string
|
|
Confirmed bool
|
|
CreatedAt string
|
|
}
|
|
|
|
// RetainedRow is one credential in the account-deletion retention journal (the legal
|
|
// dossier of detached credentials): what was detached, when, and why.
|
|
type RetainedRow struct {
|
|
Kind string
|
|
ExternalID string
|
|
Reason string
|
|
Confirmed bool
|
|
LinkedAt string
|
|
DetachedAt string
|
|
}
|
|
|
|
// GameRow is one game row in a list.
|
|
type GameRow struct {
|
|
ID string
|
|
Variant string
|
|
Status string
|
|
Players int
|
|
UpdatedAt string
|
|
// VsAI marks an honest-AI game (rendered as 🤖 in the list's AI column).
|
|
VsAI bool
|
|
// Kind is the game's origin tag label: vs_ai / random / friends / unknown.
|
|
Kind string
|
|
}
|
|
|
|
// GamesView is the paginated games list, optionally filtered by status.
|
|
type GamesView struct {
|
|
Items []GameRow
|
|
Status string
|
|
Pager Pager
|
|
}
|
|
|
|
// GameLimitsView is the per-tier, per-kind active-game limit form: each field is a cap where -1
|
|
// is unlimited, 0 blocks the kind, and a positive value caps concurrent games of that kind.
|
|
type GameLimitsView struct {
|
|
GuestVsAI int
|
|
GuestRandom int
|
|
GuestFriends int
|
|
DurableVsAI int
|
|
DurableRandom int
|
|
DurableFriends int
|
|
}
|
|
|
|
// GameDetailView is one game with its seats.
|
|
type GameDetailView struct {
|
|
ID string
|
|
Variant string
|
|
DictVersion string
|
|
Status string
|
|
Players int
|
|
ToMove int
|
|
EndReason string
|
|
MoveCount int
|
|
CreatedAt string
|
|
UpdatedAt string
|
|
FinishedAt string
|
|
// VsAI marks an honest-AI game (shown as a 🤖 flag in the summary).
|
|
VsAI bool
|
|
// MultipleWordsPerTurn is the game's cross-word rule: true = standard Scrabble (every cross-word
|
|
// is validated and scored), false = the single-word rule (only the main word along the play
|
|
// direction counts). Shown in the summary so an operator can tell the rule at a glance.
|
|
MultipleWordsPerTurn bool
|
|
Seats []SeatRow
|
|
// HasRobot is true when any seat is a robot, gating the robot-target caption;
|
|
// RobotTargetPct is the configured global play-to-win rate, in percent.
|
|
HasRobot bool
|
|
RobotTargetPct int
|
|
// ReplayJSON is the game-replay payload (board, seats, per-step racks/scores/bag) the
|
|
// game_detail page feeds to its vanilla-JS stepper; HasReplay gates the replay section.
|
|
ReplayJSON template.JS
|
|
HasReplay bool
|
|
// SetupDraws is the first-move draw — one row per tile drawn (docs/ARCHITECTURE.md §6) —
|
|
// and FirstMover is the resolved name of the seat-0 player the draw elected.
|
|
SetupDraws []SetupDrawRow
|
|
FirstMover string
|
|
}
|
|
|
|
// SetupDrawRow is one tile drawn in the first-move seeding (docs/ARCHITECTURE.md §6): the
|
|
// round, the player (Name/AccountID, or "(opponent)" with an empty AccountID for an
|
|
// auto-match synthetic draw not yet back-filled), the drawn letter (upper-cased; "?" for a
|
|
// blank) and its draw rank.
|
|
type SetupDrawRow struct {
|
|
Round int
|
|
Name string
|
|
AccountID string
|
|
Letter string
|
|
Blank bool
|
|
Rank int
|
|
}
|
|
|
|
// SeatRow is one seat of a game. For a robot seat (IsRobot) RobotIntent is the game's
|
|
// deterministic play-to-win decision ("play to win"/"play to lose"), and NextMove is the
|
|
// scheduled next-move ETA shown only while it is that robot's turn in an active game.
|
|
type SeatRow struct {
|
|
Seat int
|
|
DisplayName string
|
|
AccountID string
|
|
Score int
|
|
HintsUsed int
|
|
Winner bool
|
|
IsRobot bool
|
|
RobotIntent string
|
|
NextMove string
|
|
}
|
|
|
|
// ComplaintsView is the paginated complaint review queue.
|
|
type ComplaintsView struct {
|
|
Items []ComplaintRow
|
|
Status string
|
|
Pager Pager
|
|
}
|
|
|
|
// ComplaintRow is one complaint row in the queue.
|
|
type ComplaintRow struct {
|
|
ID string
|
|
Word string
|
|
Variant string
|
|
WasValid bool
|
|
Status string
|
|
Disposition string
|
|
CreatedAt string
|
|
}
|
|
|
|
// ComplaintDetailView is one complaint with its resolution state and form.
|
|
type ComplaintDetailView struct {
|
|
ID string
|
|
Word string
|
|
Variant string
|
|
DictVersion string
|
|
WasValid bool
|
|
Note string
|
|
Status string
|
|
Disposition string
|
|
ResolutionNote string
|
|
CreatedAt string
|
|
ResolvedAt string
|
|
GameID string
|
|
Resolved bool
|
|
}
|
|
|
|
// DictionaryView lists the resident versions per variant, the active version new
|
|
// games pin, and the pending wordlist changes from accepted complaints.
|
|
type DictionaryView struct {
|
|
// ActiveVersion is the dictionary version new games pin; the update form sets it.
|
|
ActiveVersion string
|
|
Variants []VariantVersions
|
|
Changes []DictChangeRow
|
|
}
|
|
|
|
// DictChangeRow is one pending wordlist edit.
|
|
type DictChangeRow struct {
|
|
Variant string
|
|
Word string
|
|
Action string
|
|
ResolvedAt string
|
|
}
|
|
|
|
// DictionaryPreviewView is the second step of a dictionary update: the version
|
|
// parsed from the uploaded archive (editable before confirming), the staging token
|
|
// that names the uploaded files on disk, the active version the diff is against, and
|
|
// the per-variant word diff.
|
|
type DictionaryPreviewView struct {
|
|
Version string
|
|
Token string
|
|
ActiveVersion string
|
|
Variants []VariantDiffRow
|
|
}
|
|
|
|
// VariantDiffRow summarises one variant's word diff in the update preview.
|
|
// AddedCount/RemovedCount are the full totals; AddedSample/RemovedSample are the
|
|
// first words shown (capped); AddedTruncated/RemovedTruncated mark a capped list;
|
|
// LargeRemoval flags a removal large enough to warrant caution before confirming.
|
|
type VariantDiffRow struct {
|
|
Variant string
|
|
AddedCount int
|
|
RemovedCount int
|
|
AddedSample []string
|
|
RemovedSample []string
|
|
AddedTruncated bool
|
|
RemovedTruncated bool
|
|
LargeRemoval bool
|
|
}
|
|
|
|
// BroadcastView is the operator-broadcast form page.
|
|
type BroadcastView struct {
|
|
ConnectorEnabled bool
|
|
}
|
|
|
|
// ThrottledView is the rate-limit observability page: the temporary IP bans the
|
|
// gateway is currently enforcing, the recent gateway-reported throttle episodes
|
|
// (in-memory, reset on restart) and the accounts currently carrying the high-rate
|
|
// flag. FlagThreshold and FlagWindow caption the active auto-flag tuning.
|
|
type ThrottledView struct {
|
|
Bans []BanRow
|
|
Episodes []ThrottleEpisodeRow
|
|
Flagged []FlaggedAccountRow
|
|
FlagThreshold int
|
|
FlagWindow string
|
|
}
|
|
|
|
// BanRow is one temporary IP ban the gateway is enforcing, with its reason and its
|
|
// since/expiry timestamps; the row carries an unban action.
|
|
type BanRow struct {
|
|
IP string
|
|
Reason string
|
|
Since string
|
|
Expires string
|
|
}
|
|
|
|
// ThrottleEpisodeRow is one recently throttled limiter key. UserID links to the
|
|
// user card and is set only for the user class (the other classes key by IP).
|
|
type ThrottleEpisodeRow struct {
|
|
Class string
|
|
Key string
|
|
UserID string
|
|
Rejected int
|
|
FirstSeen string
|
|
LastSeen string
|
|
}
|
|
|
|
// FlaggedAccountRow is one account carrying the high-rate flag.
|
|
type FlaggedAccountRow struct {
|
|
ID string
|
|
DisplayName string
|
|
FlaggedAt string
|
|
}
|
|
|
|
// ReasonsView is the suspension-reason picklist management page: every editable reason entry.
|
|
type ReasonsView struct {
|
|
Items []ReasonRow
|
|
}
|
|
|
|
// ReasonRow is one editable suspension-reason entry, with its English and Russian text.
|
|
type ReasonRow struct {
|
|
ID string
|
|
TextEn string
|
|
TextRu string
|
|
CreatedAt string
|
|
}
|
|
|
|
// MessageView is the result page shown after a POST action.
|
|
type MessageView struct {
|
|
Heading string
|
|
Body string
|
|
Back string
|
|
}
|
|
|
|
// BannersView is the advertising-campaign list page.
|
|
type BannersView struct {
|
|
Items []BannerCampaignRow
|
|
}
|
|
|
|
// BannerCampaignRow is one campaign in the list. Window is a human-readable
|
|
// validity window ("perpetual" for the default); ActiveNow reports whether it
|
|
// would rotate right now (enabled and within its window).
|
|
type BannerCampaignRow struct {
|
|
ID string
|
|
Name string
|
|
Weight int
|
|
IsDefault bool
|
|
Enabled bool
|
|
Window string
|
|
Messages int
|
|
ActiveNow bool
|
|
}
|
|
|
|
// BannerDetailView is the campaign detail/edit page. StartsAt/EndsAt are the
|
|
// "YYYY-MM-DDTHH:MM" (UTC) values for the datetime-local inputs, empty when open.
|
|
//
|
|
// The colour-override and urgent fields drive the non-default campaign's editor:
|
|
// OverrideAllOn/OverrideDarkOn report whether each colour set is active, and the
|
|
// six *Bg/*Fg/*Link values seed the native colour inputs — the stored override
|
|
// when a set is on, otherwise the neutral theme token so the picker starts from a
|
|
// sensible colour and the live preview shows the real fallback.
|
|
type BannerDetailView struct {
|
|
ID string
|
|
Name string
|
|
Weight int
|
|
IsDefault bool
|
|
Enabled bool
|
|
StartsAt string
|
|
EndsAt string
|
|
Urgent bool
|
|
OverrideAllOn bool
|
|
AllBg string
|
|
AllFg string
|
|
AllLink string
|
|
OverrideDarkOn bool
|
|
DarkBg string
|
|
DarkFg string
|
|
DarkLink string
|
|
Messages []BannerMessageRow
|
|
}
|
|
|
|
// BannerMessageRow is one bilingual message of a campaign. First/Last drive the
|
|
// reorder buttons (disabled at the ends).
|
|
type BannerMessageRow struct {
|
|
ID string
|
|
BodyEn string
|
|
BodyRu string
|
|
First bool
|
|
Last bool
|
|
}
|
|
|
|
// BannerSettingsView is the global display-timings form.
|
|
type BannerSettingsView struct {
|
|
HoldMs int
|
|
EdgePauseMs int
|
|
ScrollPxPerSec int
|
|
FadeOutMs int
|
|
GapMs int
|
|
FadeInMs int
|
|
}
|
|
|
|
// FeedbackView is the paginated user-feedback queue. Status is the active
|
|
// unread/read/archived filter; NameMask/ExtMask are the sender glob filters;
|
|
// UserID pins the list to one account (the per-user link from /users);
|
|
// FilterQuery is the active filters URL-encoded for the pager links (already
|
|
// escaped, hence template.URL — see UsersView.FilterQuery).
|
|
type FeedbackView struct {
|
|
Items []FeedbackRow
|
|
Status string
|
|
NameMask string
|
|
ExtMask string
|
|
UserID string
|
|
Pager Pager
|
|
FilterQuery template.URL
|
|
}
|
|
|
|
// FeedbackRow is one feedback message in the queue: its sender (linked to the user
|
|
// card), source, channel, whether it has an attachment / a reply, its state and
|
|
// time.
|
|
type FeedbackRow struct {
|
|
ID string
|
|
AccountID string
|
|
SenderName string
|
|
Source string
|
|
Channel string
|
|
HasAttachment bool
|
|
Read bool
|
|
Replied bool
|
|
Archived bool
|
|
CreatedAt string
|
|
}
|
|
|
|
// FeedbackDetailView is one feedback message with its body, attachment, state and
|
|
// the reply / archive / delete forms. Body, AttachmentName, SenderName and IP are
|
|
// user-controlled and rendered as plain auto-escaped text. IsImage gates the inline
|
|
// <img> preview; Banned shows whether the sender already holds the feedback ban.
|
|
type FeedbackDetailView struct {
|
|
ID string
|
|
AccountID string
|
|
SenderName string
|
|
Source string
|
|
Channel string
|
|
// InterfaceLanguage is the sender's interface language (account preference).
|
|
InterfaceLanguage string
|
|
IP string
|
|
Body string
|
|
HasAttachment bool
|
|
AttachmentName string
|
|
IsImage bool
|
|
Read bool
|
|
Archived bool
|
|
Replied bool
|
|
ReplyBody string
|
|
RepliedAt string
|
|
CreatedAt string
|
|
// Version is the client app build the report was sent from (empty for rows that predate it).
|
|
Version string
|
|
// The Filed time is shown in three zones so the operator can tell what is certainly known from
|
|
// what is merely defaulted. CreatedAt is the authoritative UTC time. CreatedAtBrowser is that
|
|
// instant in the client's UTC offset detected at submit (BrowserTZ its "±HH:MM" label), empty
|
|
// when the client reported none (an older build). CreatedAtUser is that instant in the sender's
|
|
// saved profile zone (UserTZ its label), empty when the account has no zone beyond the UTC
|
|
// default — the template then shows "N/A" so the missing datum is explicit.
|
|
CreatedAtBrowser string
|
|
BrowserTZ string
|
|
CreatedAtUser string
|
|
UserTZ string
|
|
Banned bool
|
|
}
|
|
|
|
// CatalogView is the product-catalog list page.
|
|
type CatalogView struct {
|
|
Products []ProductRow
|
|
// ShowAll reports whether archived products are listed alongside active ones (the active/all
|
|
// toggle); it drives the toggle link and the list heading.
|
|
ShowAll bool
|
|
// RewardPayout / RewardDailyCap / RewardHourlyCap are the rewarded-video config (chips earned per
|
|
// view and the per-day / per-hour anti-abuse caps), shown in and edited from the page's
|
|
// rewarded-ads form. A 0 payout means rewarded is inert.
|
|
RewardPayout int
|
|
RewardDailyCap int
|
|
RewardHourlyCap int
|
|
// Rails is the per-rail operational kill-switch state (enabled + per-language off-message), shown
|
|
// in and edited from the page's payment-availability form.
|
|
Rails []RailStatusRow
|
|
}
|
|
|
|
// RailStatusRow is one payment rail's operational availability in the kill-switch editor: the rail
|
|
// key, whether purchases are enabled, and the operator's per-language off-message shown to the user.
|
|
type RailStatusRow struct {
|
|
Rail string
|
|
Enabled bool
|
|
MessageRU string
|
|
MessageEN string
|
|
}
|
|
|
|
// ProductRow is one product in the catalog list: its composition, prices, the archived flag
|
|
// (Active) and the transacted flag (which forbids a hard delete).
|
|
type ProductRow struct {
|
|
ID string
|
|
Title string
|
|
Active bool
|
|
Atoms []AtomRow
|
|
Prices []PriceRow
|
|
Transacted bool
|
|
}
|
|
|
|
// AtomRow is one atom line of a product row.
|
|
type AtomRow struct {
|
|
Atom string
|
|
Quantity int
|
|
}
|
|
|
|
// PriceRow is one price of a product: the method ("" for a value's CHIP price), the currency, and
|
|
// the amount in that currency's minor units.
|
|
type PriceRow struct {
|
|
Method string
|
|
Currency string
|
|
Amount int64
|
|
}
|
|
|
|
// ProductFormView is the product edit form, pre-filled from the current composition. Atom quantities
|
|
// and prices are flattened to the fixed fields the form offers (0 = absent); Transacted disables the
|
|
// delete action.
|
|
type ProductFormView struct {
|
|
ID string
|
|
Title string
|
|
Active bool
|
|
Chips int
|
|
Hints int
|
|
NoAds int
|
|
Tournament int
|
|
PriceRUB int64
|
|
PriceVote int64
|
|
PriceStar int64
|
|
PriceChip int64
|
|
Transacted bool
|
|
}
|
|
|
|
// GrantFormView is the admin-grant panel on the user card: the origin picker and the grantable
|
|
// products (value bundles — hints / no-ads days — including archived ones; chips and tournament
|
|
// products are excluded). Present is false when the payments domain is unwired.
|
|
type GrantFormView struct {
|
|
Present bool
|
|
Origins []string
|
|
Products []GrantProductOption
|
|
}
|
|
|
|
// GrantProductOption is one grantable product in the by-product picker: its id, title, an atom
|
|
// summary, and whether it is archived (the common case for a non-public reward bundle).
|
|
type GrantProductOption struct {
|
|
ID string
|
|
Title string
|
|
Summary string
|
|
Archived bool
|
|
}
|