58c346abec
- PUT /admin/lottery/prizes/:id、DELETE /admin/lottery/prizes/:id
- handler 从 c.Param("id") 取 id;types 用 path:"id"
- 同步 apis/admin/lottery.api 定义
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
391 lines
16 KiB
Go
391 lines
16 KiB
Go
// 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"`
|
||
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"`
|
||
}
|