// Types for lottery Stage 1 user & admin APIs. Kept in a separate file so // future `goctl` regenerations of types.go do not clobber them (same pattern // as internal/types/subscribe.go). // // Field names mirror the shape declared in HIF-3 (Stage 1 spec) and the // architect's PR C brief. Do NOT rename without updating the .api files and // notifying frontend. package types import "encoding/json" // ---- User API --------------------------------------------------------------- // GetLotteryConfigRequest is the query for GET /api/v1/lottery/config. type GetLotteryConfigRequest struct { ActivityId int64 `form:"activity_id" validate:"required"` } // LotteryActivityConfig is the activity snapshot returned to the user. type LotteryActivityConfig struct { Id int64 `json:"id"` Title string `json:"title"` Description string `json:"description"` StartAt int64 `json:"start_at"` EndAt int64 `json:"end_at"` Status string `json:"status"` GridSize int `json:"grid_size"` Prizes []LotteryPrizeConfig `json:"prizes"` } // LotteryPrizeConfig is the slot-facing view of a prize (no weight / stock // exposed — those are admin-only). type LotteryPrizeConfig struct { Slot int `json:"slot"` Id int64 `json:"id"` Type string `json:"type"` Name string `json:"name"` IconUrl string `json:"icon_url"` Config json.RawMessage `json:"config"` SoldOut bool `json:"sold_out"` } // LotteryUserStatus reports whether the user can currently draw. type LotteryUserStatus struct { Eligible bool `json:"eligible"` ChancesRemaining int64 `json:"chances_remaining"` UnmetReasons []LotteryUnmetReason `json:"unmet_reasons"` } // LotteryUnmetReason is a single unmet-rule explanation for frontend display. type LotteryUnmetReason struct { Rule string `json:"rule"` Hint string `json:"hint"` Current int64 `json:"current,omitempty"` Required int64 `json:"required,omitempty"` } // GetLotteryConfigResponse is the envelope for GET /config. type GetLotteryConfigResponse struct { Activity LotteryActivityConfig `json:"activity"` User LotteryUserStatus `json:"user"` } // DrawLotteryRequest is the body for POST /api/v1/lottery/draw. type DrawLotteryRequest struct { ActivityId int64 `json:"activity_id" validate:"required"` ClientNonce string `json:"client_nonce" validate:"required,max=64"` } // DrawnPrize is a slim prize view returned on draw success. type DrawnPrize struct { Slot int `json:"slot"` Id int64 `json:"id"` Type string `json:"type"` Name string `json:"name"` Config json.RawMessage `json:"config"` } // LotteryClaimStatus tells the frontend whether more action is needed. type LotteryClaimStatus struct { Required bool `json:"required"` AutoClaimed bool `json:"auto_claimed"` Message string `json:"message,omitempty"` // ExpiresAt 是领奖窗口截止时间(Unix 秒;0 表示不适用,例如自动奖)。 ExpiresAt int64 `json:"expires_at,omitempty"` // ClaimFormSchema 是人工奖的领奖表单 JSON Schema(前端据此动态渲染)。 // nil 表示不适用(自动奖 / 谢谢参与)。 ClaimFormSchema json.RawMessage `json:"claim_form_schema,omitempty"` } // DrawLotteryResponse is what /draw returns. type DrawLotteryResponse struct { DrawId int64 `json:"draw_id"` IsWin bool `json:"is_win"` Prize *DrawnPrize `json:"prize"` Claim LotteryClaimStatus `json:"claim"` ChancesRemaining int64 `json:"chances_remaining"` } // GetLotteryRecordsRequest paginates over the user's draws. // Page/Size 默认 by logic 层(Page<=0 → 1;Size<=0||>200 → 20);tag 里不设 // default 以避免 staticcheck 与 Gin binding 的语义冲突。 type GetLotteryRecordsRequest struct { ActivityId int64 `form:"activity_id"` Status string `form:"status"` Page int `form:"page"` Size int `form:"size"` } // LotteryRecord is one row in the records list. type LotteryRecord struct { DrawId int64 `json:"draw_id"` ActivityId int64 `json:"activity_id"` IsWin bool `json:"is_win"` Prize *DrawnPrize `json:"prize"` DispatchState string `json:"dispatch_state"` DrawnAt int64 `json:"drawn_at"` // Claim 是人工奖的工单详情;自动奖 / 未中奖时为 nil。Stage 2 新增。 Claim *LotteryRecordClaim `json:"claim,omitempty"` } // LotteryRecordClaim 是 GET /records 里"人工奖工单"的用户视图。 // 状态:pending_claim / reviewing / paying / paid / rejected / expired。 // - 用户在 pending_claim 或 rejected 时可再次提交(前端读 ClaimFormSchema 渲染)。 // - paying / paid / expired 时前端只展示状态与打款/发货结果。 type LotteryRecordClaim struct { Status string `json:"status"` ClaimData json.RawMessage `json:"claim_data,omitempty"` SubmittedAt int64 `json:"submitted_at,omitempty"` ExpiresAt int64 `json:"expires_at"` TxHash string `json:"tx_hash,omitempty"` DeliveryRef string `json:"delivery_ref,omitempty"` RejectReason string `json:"reject_reason,omitempty"` PaidAt int64 `json:"paid_at,omitempty"` // ClaimFormSchema 仅当 status 允许再提交(pending_claim / rejected)时下发。 ClaimFormSchema json.RawMessage `json:"claim_form_schema,omitempty"` } // GetLotteryRecordsResponse is the paginated payload. type GetLotteryRecordsResponse struct { Total int64 `json:"total"` List []LotteryRecord `json:"list"` } // ClaimLotteryPrizeRequest is Stage 2: submit claim data for a manual prize. type ClaimLotteryPrizeRequest struct { DrawId int64 `json:"draw_id" validate:"required"` ClaimData json.RawMessage `json:"claim_data"` // Input 是 Stage 1 骨架里预留的旧字段名,为了不破坏前端契约保留。 // Deprecated: Prefer ClaimData for new callers. Input json.RawMessage `json:"input,omitempty"` } // ClaimLotteryPrizeResponse mirrors Stage 2 shape. type ClaimLotteryPrizeResponse struct { Status string `json:"status"` SubmittedAt int64 `json:"submitted_at"` } // ---- Admin API -------------------------------------------------------------- // AdminLotteryActivity is the full admin view of an activity. type AdminLotteryActivity struct { Id int64 `json:"id"` Title string `json:"title"` Description string `json:"description"` StartAt int64 `json:"start_at"` EndAt int64 `json:"end_at"` Status string `json:"status"` GridSize int `json:"grid_size"` Eligibility json.RawMessage `json:"eligibility"` ChanceSources json.RawMessage `json:"chance_sources"` UnmetAction string `json:"unmet_action"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } // CreateAdminLotteryActivityRequest creates a new activity in "draft" status. type CreateAdminLotteryActivityRequest struct { Title string `json:"title" validate:"required,max=128"` Description string `json:"description"` StartAt int64 `json:"start_at" validate:"required"` EndAt int64 `json:"end_at" validate:"required"` // GridSize:0 由 logic 层兜底为 9(不能在 json tag 里写 default=… —— // staticcheck SA5008 会拒;encoding/json 也不认这个选项)。 GridSize int `json:"grid_size"` Eligibility json.RawMessage `json:"eligibility"` ChanceSources json.RawMessage `json:"chance_sources"` // UnmetAction:空字符串由 logic 层兜底为 "block"。 UnmetAction string `json:"unmet_action"` } // UpdateAdminLotteryActivityRequest updates mutable fields. type UpdateAdminLotteryActivityRequest struct { Id int64 `json:"id" validate:"required"` Title string `json:"title,omitempty"` Description string `json:"description,omitempty"` StartAt int64 `json:"start_at,omitempty"` EndAt int64 `json:"end_at,omitempty"` GridSize int `json:"grid_size,omitempty"` UnmetAction string `json:"unmet_action,omitempty"` } // ListAdminLotteryActivitiesRequest paginates admin listings. // Page/Size 默认 by logic 层。 type ListAdminLotteryActivitiesRequest struct { Page int `form:"page"` Size int `form:"size"` Status string `form:"status,omitempty"` Search string `form:"search,omitempty"` } // ListAdminLotteryActivitiesResponse pages. type ListAdminLotteryActivitiesResponse struct { Total int64 `json:"total"` List []AdminLotteryActivity `json:"list"` } // AdminActivityIdRequest is used by GET /detail, publish, pause, delete. type AdminActivityIdRequest struct { Id int64 `form:"id" json:"id" validate:"required"` } // UpdateAdminLotteryRulesRequest overwrites eligibility / chance_sources / // unmet_action in a single call. Validated against rule caps before persist. type UpdateAdminLotteryRulesRequest struct { Id int64 `json:"id" validate:"required"` Eligibility json.RawMessage `json:"eligibility"` ChanceSources json.RawMessage `json:"chance_sources"` UnmetAction string `json:"unmet_action,omitempty"` } // AdminLotteryPrize is the admin view of a prize (includes weight + stock). type AdminLotteryPrize struct { Id int64 `json:"id"` ActivityId int64 `json:"activity_id"` Slot int `json:"slot"` Type string `json:"type"` Name string `json:"name"` IconUrl string `json:"icon_url"` Config json.RawMessage `json:"config"` Weight int `json:"weight"` TotalStock *int64 `json:"total_stock"` RemainingStock *int64 `json:"remaining_stock"` IsFallback bool `json:"is_fallback"` CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` } // CreateAdminLotteryPrizeRequest is nested under /activities/{id}/prizes. type CreateAdminLotteryPrizeRequest struct { ActivityId int64 `json:"activity_id" validate:"required"` Slot int `json:"slot"` Type string `json:"type" validate:"required,max=32"` Name string `json:"name" validate:"required,max=128"` IconUrl string `json:"icon_url"` Config json.RawMessage `json:"config"` Weight int `json:"weight"` TotalStock *int64 `json:"total_stock,omitempty"` IsFallback bool `json:"is_fallback"` } // UpdateAdminLotteryPrizeRequest updates mutable prize fields. Id comes from // the URL path (PUT /prizes/:id), not the body. type UpdateAdminLotteryPrizeRequest struct { Id int64 `path:"id" json:"-" validate:"required"` Slot *int `json:"slot,omitempty"` Type string `json:"type,omitempty"` Name string `json:"name,omitempty"` IconUrl string `json:"icon_url,omitempty"` Config json.RawMessage `json:"config,omitempty"` Weight *int `json:"weight,omitempty"` TotalStock *int64 `json:"total_stock,omitempty"` IsFallback *bool `json:"is_fallback,omitempty"` } // ListAdminLotteryPrizesRequest lists prizes for an activity. type ListAdminLotteryPrizesRequest struct { ActivityId int64 `form:"activity_id" validate:"required"` } // ListAdminLotteryPrizesResponse pages. type ListAdminLotteryPrizesResponse struct { List []AdminLotteryPrize `json:"list"` } // AdminPrizeIdRequest is used by DELETE /prizes/:id. Id comes from the URL path. type AdminPrizeIdRequest struct { Id int64 `path:"id" form:"id" json:"id" validate:"required"` } // GrantAdminLotteryChanceRequest gives a specified user N chances on an // activity. sourceRef doubles as idempotency key. type GrantAdminLotteryChanceRequest struct { ActivityId int64 `json:"activity_id" validate:"required"` UserId int64 `json:"user_id" validate:"required"` Amount int `json:"amount" validate:"required,min=1"` SourceRef string `json:"source_ref" validate:"required,max=128"` } // ---- Stage 2 Admin Claims -------------------------------------------------- // ListAdminLotteryClaimsRequest lists claims filtered by type/status/activity. // Page/Size 默认 by logic 层(Page<=0 → 1;Size<=0||>200 → 20)。 type ListAdminLotteryClaimsRequest struct { Type string `form:"type,omitempty"` Status string `form:"status,omitempty"` ActivityId int64 `form:"activity_id,omitempty"` UserId int64 `form:"user_id,omitempty"` From int64 `form:"from,omitempty"` // Unix 秒 To int64 `form:"to,omitempty"` // Unix 秒 Page int `form:"page"` Size int `form:"size"` } // AdminLotteryClaim is the admin-facing view of one claim row. type AdminLotteryClaim struct { Id int64 `json:"id"` DrawId int64 `json:"draw_id"` ActivityId int64 `json:"activity_id"` User AdminLotteryClaimUser `json:"user"` Prize AdminLotteryClaimPrize `json:"prize"` Status string `json:"status"` ClaimData json.RawMessage `json:"claim_data,omitempty"` SubmittedAt int64 `json:"submitted_at,omitempty"` ExpiresAt int64 `json:"expires_at"` ReviewedBy int64 `json:"reviewed_by,omitempty"` ReviewedAt int64 `json:"reviewed_at,omitempty"` RejectReason string `json:"reject_reason,omitempty"` TxHash string `json:"tx_hash,omitempty"` DeliveryRef string `json:"delivery_ref,omitempty"` PaidAt int64 `json:"paid_at,omitempty"` CreatedAt int64 `json:"created_at"` } // AdminLotteryClaimUser 是 claim 列表里附带的用户简况。 type AdminLotteryClaimUser struct { Id int64 `json:"id"` Email string `json:"email,omitempty"` } // AdminLotteryClaimPrize 是 claim 列表里附带的奖品简况(含 config,供审核判断)。 type AdminLotteryClaimPrize struct { Type string `json:"type"` Name string `json:"name"` Config json.RawMessage `json:"config"` } // ListAdminLotteryClaimsResponse pages. type ListAdminLotteryClaimsResponse struct { Total int64 `json:"total"` Claims []AdminLotteryClaim `json:"claims"` } // AdminApproveClaimRequest 转 reviewing → paying。 type AdminApproveClaimRequest struct { Id int64 `json:"id" validate:"required"` } // AdminRejectClaimRequest 转 reviewing/paying → rejected,reason 必填。 type AdminRejectClaimRequest struct { Id int64 `json:"id" validate:"required"` Reason string `json:"reason" validate:"required,max=512"` } // AdminMarkPaidClaimRequest 转 paying → paid。 // tx_hash / delivery_ref 至少填一个;crypto 必填 tx_hash,physical 必填 // delivery_ref,manual_other 至少填一个(业务层做类型检查)。 // paid_at 可选,缺省时用服务端 now。 type AdminMarkPaidClaimRequest struct { Id int64 `json:"id" validate:"required"` TxHash string `json:"tx_hash,omitempty" validate:"max=128"` DeliveryRef string `json:"delivery_ref,omitempty" validate:"max=128"` PaidAt int64 `json:"paid_at,omitempty"` // Unix 秒 } // AdminLotteryClaimsSummary 是 /claims/summary 的响应。overdue = 待用户填的 // pending_claim 中已过期的条数(不重叠 status=expired)。 type AdminLotteryClaimsSummary struct { Crypto AdminLotteryClaimsStatusCount `json:"crypto"` Physical AdminLotteryClaimsStatusCount `json:"physical"` ManualOther AdminLotteryClaimsStatusCount `json:"manual_other"` Overdue int64 `json:"overdue"` } // AdminLotteryClaimsStatusCount 是各类型的关键状态计数。 type AdminLotteryClaimsStatusCount struct { Reviewing int64 `json:"reviewing"` Paying int64 `json:"paying"` } // ---- Stage 3 后台抽奖记录(发放流水)-------------------------------------- // ListAdminLotteryDrawsRequest 列出抽奖记录,支持活动/用户/中奖/发放状态/奖品类型/时间窗过滤。 type ListAdminLotteryDrawsRequest struct { ActivityId int64 `form:"activity_id,omitempty"` UserId int64 `form:"user_id,omitempty"` PrizeType string `form:"prize_type,omitempty"` DispatchState string `form:"dispatch_state,omitempty"` Win string `form:"win,omitempty"` // "1"=只看中奖, "0"=只看未中奖, ""=全部 From int64 `form:"from,omitempty"` To int64 `form:"to,omitempty"` Page int `form:"page"` Size int `form:"size"` } // AdminLotteryDrawUser 抽奖记录里附带的用户简况。 type AdminLotteryDrawUser struct { Id int64 `json:"id"` Email string `json:"email,omitempty"` } // AdminLotteryDrawPrize 抽奖记录里的奖品快照(抽奖时刻冻结)。 type AdminLotteryDrawPrize struct { Slot int `json:"slot"` Type string `json:"type"` Name string `json:"name"` Config json.RawMessage `json:"config"` } // AdminLotteryDraw 是后台抽奖记录视图:谁、何时、中了什么、发放状态与结果。 type AdminLotteryDraw struct { DrawId int64 `json:"draw_id"` ActivityId int64 `json:"activity_id"` User AdminLotteryDrawUser `json:"user"` IsWin bool `json:"is_win"` Prize *AdminLotteryDrawPrize `json:"prize,omitempty"` DispatchState string `json:"dispatch_state"` DispatchError string `json:"dispatch_error,omitempty"` // GrantAmount / GrantMessage 来自 lottery_grant_ledger(自动奖发放结果): // vpn_duration=天数,commission=分;message 如"已新建订阅并加 30 天"。 GrantAmount int64 `json:"grant_amount,omitempty"` GrantMessage string `json:"grant_message,omitempty"` DrawnAt int64 `json:"drawn_at"` DispatchedAt int64 `json:"dispatched_at,omitempty"` CreatedAt int64 `json:"created_at"` } // ListAdminLotteryDrawsResponse 分页。 type ListAdminLotteryDrawsResponse struct { Total int64 `json:"total"` List []AdminLotteryDraw `json:"list"` }