53 lines
2.5 KiB
Go
53 lines
2.5 KiB
Go
package model
|
|
|
|
import "time"
|
|
|
|
// Feedback is a user-submitted message — bug report, feature request,
|
|
// or general comment — captured by the /feedback page. Surfaced to
|
|
// admins both via the /stats page and via an email fan-out on insert.
|
|
//
|
|
// UserID is a pointer so the model can carry anonymous submissions
|
|
// (logged-out visitors) and orphaned rows (the submitter has since
|
|
// deleted their account — the FK is set null). ContactEmail is the
|
|
// optional reply-to filled in by anonymous submitters.
|
|
//
|
|
// AuthorHandle / AuthorName / AuthorEmail are populated by joins in
|
|
// the repo's lookup helpers (used by the admin email template and the
|
|
// /stats page). They're nil for anonymous rows.
|
|
type Feedback struct {
|
|
ID string `json:"id"`
|
|
UserID *string `json:"user_id,omitempty"`
|
|
Category string `json:"category"`
|
|
Message string `json:"message"`
|
|
ContactEmail *string `json:"contact_email,omitempty"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
// AcknowledgedAt is set when an admin presses "Acknowledge" on the
|
|
// /stats page — the team has seen it and (if it needs work) the devs
|
|
// have started. NULL until then. One-way like ResolvedAt.
|
|
AcknowledgedAt *time.Time `json:"acknowledged_at,omitempty"`
|
|
// ResolvedAt is set the moment an admin presses "Mark resolved" on
|
|
// the /stats page. NULL while open. The action is one-way — the
|
|
// repo's MarkResolved guards on `resolved_at IS NULL` so a second
|
|
// resolve is a no-op.
|
|
ResolvedAt *time.Time `json:"resolved_at,omitempty"`
|
|
|
|
// ReplyMessage is the admin's written reply to the submitter, sent
|
|
// (and stored) from the admin panel. NULL until an admin replies.
|
|
// RepliedAt stamps when it was sent. Surfaced to the submitter on the
|
|
// "My feedback" page as the team's response.
|
|
ReplyMessage *string `json:"reply_message,omitempty"`
|
|
RepliedAt *time.Time `json:"replied_at,omitempty"`
|
|
|
|
// HasContactEmail reports *whether* an anonymous submitter left a reply-to
|
|
// address, without carrying the address itself. Populated only by
|
|
// Recent(), whose sole consumer is the PUBLIC /stats page — that page has
|
|
// to distinguish "anonymous, but we can reply" from "anonymous dead end"
|
|
// while never handling the email. Admins see the real address in the
|
|
// admin panel, which is auth-gated.
|
|
HasContactEmail bool `json:"-"`
|
|
|
|
AuthorHandle *string `json:"author_handle,omitempty"`
|
|
AuthorName *string `json:"author_name,omitempty"`
|
|
AuthorEmail *string `json:"-"` // server-side only; never serialised
|
|
}
|