feat(feedback): capture the browser UTC offset; Filed time in three zones
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 55s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m20s
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 55s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m20s
The account time zone defaults to UTC until a player saves a profile, so a report's Filed time could only render in UTC even for a player clearly in another zone. Capture the client's detected "±HH:MM" offset (browser_tz) with each submission and show the Filed time in three zones in the operator console — UTC, the browser offset detected at submit, and the sender's saved profile zone — each shown "N/A" when not known, so the operator can tell what is certainly known from what is merely defaulted. - Thread browser_tz through the fbs envelope (+ Go/TS codegen), the gateway transcode + backend client, and the backend feedback service/store; add the column via migration 00004 (additive, image-rollback-safe). - Fix fmtTimeIn to resolve "±HH:MM" offsets via account.ResolveZone; time.LoadLocation alone silently fell back to UTC for offset zones, which is the second reason a "+02:00" sender showed only UTC. - Update ARCHITECTURE/FUNCTIONAL(+ru) docs and the feedback integration test.
This commit is contained in:
@@ -1151,8 +1151,8 @@ Players reach the operators through a **Feedback** screen (Settings → Info, re
|
||||
only). A message (≤1024 runes) plus an optional single attachment is stored in
|
||||
`feedback_messages`; the sender's IP (gateway-forwarded, as for chat), the submitting
|
||||
**channel** (telegram/ios/android/web, client-reported and validated), the **client app version**
|
||||
(`__APP_VERSION__`, the build a report was sent from) and a snapshot of the sender's interface
|
||||
language are recorded. The domain is `internal/feedback` (store + service), modelled on the admin
|
||||
(`__APP_VERSION__`, the build a report was sent from), the client's **detected UTC offset** at
|
||||
submit (`browser_tz`, `±HH:MM`) and a snapshot of the sender's interface language are recorded. The domain is `internal/feedback` (store + service), modelled on the admin
|
||||
chat-moderation surface.
|
||||
|
||||
**Anti-spam.** A player with an unreviewed message (`read_at IS NULL`) cannot submit another; the
|
||||
@@ -1162,7 +1162,8 @@ rate limit — there is no separate per-user feedback limiter.
|
||||
**Operator review** happens in the server-rendered console (`/_gm/feedback`): an
|
||||
unread / read / archived queue with per-user search (the `/users` glob masks), a detail card
|
||||
(user content rendered as auto-escaped `html/template` text; it shows the channel, interface
|
||||
language and app version, and the filed time in UTC plus the sender's time zone), and the read /
|
||||
language and app version, and the filed time in three zones — UTC, the browser offset detected at
|
||||
submit, and the sender's saved profile zone, each `N/A` when not known), and the read /
|
||||
reply / archive / delete / delete-all actions — each marks the message read; merely opening the
|
||||
detail does not.
|
||||
The attachment is served from `/_gm/feedback/:id/attachment` with `X-Content-Type-Options:
|
||||
|
||||
+3
-2
@@ -346,8 +346,9 @@ over-grant cannot be reversed there.
|
||||
The console works a **feedback** queue too (`/_gm/feedback`): the messages players sent, filtered
|
||||
**unread / read / archived** with per-user search, each shown with its sender, source, channel
|
||||
(with the bot language — en/ru — for a Telegram message), the sender's interface
|
||||
language, the **app version** it was sent from, IP, the filed time (in UTC and the sender's time
|
||||
zone) and any attachment. The operator can mark a message read, **reply** to the player (delivered
|
||||
language, the **app version** it was sent from, IP, the filed time (in three zones — UTC, the
|
||||
browser zone detected at submit, and the sender's saved zone, each shown `N/A` when not known) and
|
||||
any attachment. The operator can mark a message read, **reply** to the player (delivered
|
||||
in-app), archive it, delete it, or delete every message from that player — and, alongside a delete,
|
||||
**bar the player from feedback** (a `feedback_banned` role, distinct from a full account block: it
|
||||
stops only feedback submission). Roles are listed and granted/revoked on the user card. Opening a
|
||||
|
||||
@@ -356,8 +356,8 @@ high-rate флага. С карточки пользователя операт
|
||||
Консоль ведёт и очередь **обратной связи** (`/_gm/feedback`): присланные игроками сообщения с фильтром
|
||||
**непрочитанные / прочитанные / архив** и поиском по пользователю, каждое — с отправителем, источником,
|
||||
каналом (и языком бота — en/ru — для сообщения из Telegram), языком интерфейса отправителя,
|
||||
**версией приложения**, с которой отправлено, IP, временем подачи (в UTC и в зоне отправителя)
|
||||
и вложением. Оператор может пометить сообщение прочитанным, **ответить** игроку (доставка
|
||||
**версией приложения**, с которой отправлено, IP, временем подачи (в трёх зонах — UTC, зоне браузера
|
||||
на момент отправки и сохранённой зоне отправителя, каждая — «N/A», если неизвестна) и вложением. Оператор может пометить сообщение прочитанным, **ответить** игроку (доставка
|
||||
в приложение), отправить в архив, удалить или удалить все сообщения этого игрока — и вместе с удалением
|
||||
**запретить игроку обратную связь** (роль `feedback_banned`, отличная от полной блокировки аккаунта:
|
||||
останавливает только отправку обратной связи). Роли перечислены и выдаются/снимаются на карточке
|
||||
|
||||
Reference in New Issue
Block a user