Files
scrabble-game/gateway/internal/backendclient/api_feedback.go
T
Ilia Denisov 004aca4e97
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
feat(feedback): capture the browser UTC offset; Filed time in three zones
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.
2026-06-22 18:05:39 +02:00

59 lines
2.1 KiB
Go

package backendclient
import (
"context"
"encoding/base64"
"net/http"
)
// FeedbackReplyResp is the operator reply shown back to the player.
type FeedbackReplyResp struct {
Body string `json:"body"`
RepliedAtUnix int64 `json:"replied_at_unix"`
}
// FeedbackStateResp is the player's feedback screen state. Reply is nil when there
// is none to show.
type FeedbackStateResp struct {
CanSend bool `json:"can_send"`
BlockedReason string `json:"blocked_reason"`
Reply *FeedbackReplyResp `json:"reply,omitempty"`
}
// FeedbackUnreadResp reports whether the player has an undelivered operator reply.
type FeedbackUnreadResp struct {
ReplyUnread bool `json:"reply_unread"`
}
// FeedbackSubmit posts a feedback message. The attachment bytes are base64-encoded
// into the JSON body for the internal hop; clientIP rides X-Forwarded-For.
func (c *Client) FeedbackSubmit(ctx context.Context, userID, body string, attachment []byte, attachmentName, channel, version, browserTz, clientIP string) error {
payload := map[string]string{
"body": body,
"attachment": "",
"attachment_name": attachmentName,
"channel": channel,
"version": version,
"browser_tz": browserTz,
}
if len(attachment) > 0 {
payload["attachment"] = base64.StdEncoding.EncodeToString(attachment)
}
return c.do(ctx, http.MethodPost, "/api/v1/user/feedback", userID, clientIP, payload, nil)
}
// FeedbackGet returns the player's feedback screen state, marking any pending reply
// delivered (clearing the badge).
func (c *Client) FeedbackGet(ctx context.Context, userID string) (FeedbackStateResp, error) {
var out FeedbackStateResp
err := c.do(ctx, http.MethodGet, "/api/v1/user/feedback", userID, "", nil, &out)
return out, err
}
// FeedbackUnread reports whether the player has an undelivered operator reply.
func (c *Client) FeedbackUnread(ctx context.Context, userID string) (FeedbackUnreadResp, error) {
var out FeedbackUnreadResp
err := c.do(ctx, http.MethodGet, "/api/v1/user/feedback/unread", userID, "", nil, &out)
return out, err
}