232 lines
7.7 KiB
Go
232 lines
7.7 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.118im.com/im_team/olons_server/internal/auth"
|
|
"git.118im.com/im_team/olons_server/internal/store"
|
|
"git.118im.com/im_team/olons_server/internal/tasks"
|
|
)
|
|
|
|
type taskClaimRequest struct {
|
|
HallID int64 `json:"hall_id"`
|
|
}
|
|
|
|
func (a *API) taskHalls(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
methodNotAllowed(w, r, http.MethodGet)
|
|
return
|
|
}
|
|
principal, ok := a.authenticateTaskMember(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
result, err := a.tasks.Halls(r.Context(), principal.TenantID, principal.MemberID)
|
|
if err != nil {
|
|
writeTaskError(w, r, err)
|
|
return
|
|
}
|
|
writeSuccess(w, r, result)
|
|
}
|
|
|
|
func (a *API) taskEligibility(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
methodNotAllowed(w, r, http.MethodGet)
|
|
return
|
|
}
|
|
principal, ok := a.authenticateTaskMember(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
hallID, err := strconv.ParseInt(strings.TrimSpace(r.URL.Query().Get("hall_id")), 10, 64)
|
|
if err != nil {
|
|
writeTaskError(w, r, tasks.ErrHallIDInvalid)
|
|
return
|
|
}
|
|
result, err := a.tasks.Eligibility(r.Context(), principal.TenantID, principal.MemberID, hallID)
|
|
if err != nil {
|
|
writeTaskError(w, r, err)
|
|
return
|
|
}
|
|
writeSuccess(w, r, result)
|
|
}
|
|
|
|
func (a *API) taskStats(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
methodNotAllowed(w, r, http.MethodGet)
|
|
return
|
|
}
|
|
principal, ok := a.authenticateTaskMember(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
result, err := a.tasks.Stats(r.Context(), principal.TenantID, principal.MemberID)
|
|
if err != nil {
|
|
writeTaskError(w, r, err)
|
|
return
|
|
}
|
|
writeSuccess(w, r, result)
|
|
}
|
|
|
|
func (a *API) taskClaim(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
methodNotAllowed(w, r, http.MethodPost)
|
|
return
|
|
}
|
|
principal, ok := a.authenticateTaskMember(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
var body taskClaimRequest
|
|
if !decodeBody(w, r, &body) {
|
|
return
|
|
}
|
|
result, err := a.tasks.Claim(r.Context(), principal.TenantID, principal.MemberID, body.HallID)
|
|
if err != nil {
|
|
writeTaskError(w, r, err)
|
|
return
|
|
}
|
|
writeSuccess(w, r, result)
|
|
}
|
|
|
|
func (a *API) taskOrders(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
methodNotAllowed(w, r, http.MethodGet)
|
|
return
|
|
}
|
|
principal, ok := a.authenticateTaskMember(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
page, ok := optionalPositiveInt(w, r, "page")
|
|
if !ok {
|
|
writeTaskError(w, r, tasks.ErrPageInvalid)
|
|
return
|
|
}
|
|
pageSize, ok := optionalPositiveInt(w, r, "page_size")
|
|
if !ok {
|
|
writeTaskError(w, r, tasks.ErrPageSizeInvalid)
|
|
return
|
|
}
|
|
result, err := a.tasks.Orders(r.Context(), principal.TenantID, principal.MemberID, page, pageSize, r.URL.Query().Get("status"))
|
|
if err != nil {
|
|
writeTaskError(w, r, err)
|
|
return
|
|
}
|
|
writeSuccess(w, r, result)
|
|
}
|
|
|
|
func (a *API) taskOrderResource(w http.ResponseWriter, r *http.Request) {
|
|
path := strings.Trim(strings.TrimPrefix(r.URL.Path, "/v1/task-orders/"), "/")
|
|
parts := strings.Split(path, "/")
|
|
if len(parts) < 1 || len(parts) > 2 || parts[0] == "" {
|
|
writeTaskError(w, r, tasks.ErrOrderIDInvalid)
|
|
return
|
|
}
|
|
orderID, err := strconv.ParseInt(parts[0], 10, 64)
|
|
if err != nil || orderID <= 0 {
|
|
writeTaskError(w, r, tasks.ErrOrderIDInvalid)
|
|
return
|
|
}
|
|
principal, ok := a.authenticateTaskMember(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
if len(parts) == 1 {
|
|
if r.Method != http.MethodGet {
|
|
methodNotAllowed(w, r, http.MethodGet)
|
|
return
|
|
}
|
|
result, err := a.tasks.Order(r.Context(), principal.TenantID, principal.MemberID, orderID)
|
|
if err != nil {
|
|
writeTaskError(w, r, err)
|
|
return
|
|
}
|
|
writeSuccess(w, r, result)
|
|
return
|
|
}
|
|
if parts[1] != "submissions" {
|
|
writeTaskError(w, r, store.ErrOrderNotFound)
|
|
return
|
|
}
|
|
if r.Method != http.MethodPost {
|
|
methodNotAllowed(w, r, http.MethodPost)
|
|
return
|
|
}
|
|
var body tasks.SubmitInput
|
|
if !decodeBody(w, r, &body) {
|
|
return
|
|
}
|
|
result, err := a.tasks.Submit(r.Context(), principal.TenantID, principal.MemberID, orderID, body)
|
|
if err != nil {
|
|
writeTaskError(w, r, err)
|
|
return
|
|
}
|
|
writeSuccess(w, r, result)
|
|
}
|
|
|
|
func (a *API) authenticateTaskMember(w http.ResponseWriter, r *http.Request) (*auth.Principal, bool) {
|
|
if a.tasks == nil {
|
|
writeError(w, r, http.StatusServiceUnavailable, 90001, "SERVICE_UNAVAILABLE", "service unavailable", nil)
|
|
return nil, false
|
|
}
|
|
principal, ok := a.authenticateMember(w, r)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return principal, true
|
|
}
|
|
|
|
func optionalPositiveInt(w http.ResponseWriter, r *http.Request, name string) (int, bool) {
|
|
value := strings.TrimSpace(r.URL.Query().Get(name))
|
|
if value == "" {
|
|
return 0, true
|
|
}
|
|
parsed, err := strconv.Atoi(value)
|
|
return parsed, err == nil && parsed > 0
|
|
}
|
|
|
|
func writeTaskError(w http.ResponseWriter, r *http.Request, err error) {
|
|
switch {
|
|
case errors.Is(err, tasks.ErrHallIDInvalid):
|
|
writeError(w, r, http.StatusBadRequest, 30000, "HALL_ID_INVALID", "validation failed", map[string]any{"field": "hall_id"})
|
|
case errors.Is(err, tasks.ErrOrderIDInvalid):
|
|
writeError(w, r, http.StatusBadRequest, 30000, "ORDER_ID_INVALID", "validation failed", map[string]any{"field": "order_id"})
|
|
case errors.Is(err, tasks.ErrPageInvalid):
|
|
writeError(w, r, http.StatusBadRequest, 30000, "PAGE_INVALID", "validation failed", map[string]any{"field": "page"})
|
|
case errors.Is(err, tasks.ErrPageSizeInvalid):
|
|
writeError(w, r, http.StatusBadRequest, 30000, "PAGE_SIZE_INVALID", "validation failed", map[string]any{"field": "page_size"})
|
|
case errors.Is(err, tasks.ErrStatusInvalid):
|
|
writeError(w, r, http.StatusBadRequest, 30000, "ORDER_STATUS_INVALID", "validation failed", map[string]any{"field": "status"})
|
|
case errors.Is(err, tasks.ErrEvidenceInvalid), errors.Is(err, store.ErrEvidenceInvalid):
|
|
writeError(w, r, http.StatusBadRequest, 30000, "EVIDENCE_INVALID", "validation failed", map[string]any{"field": "evidence"})
|
|
case errors.Is(err, tasks.ErrTaskDisabled), errors.Is(err, store.ErrTaskDisabled):
|
|
writeError(w, r, http.StatusUnprocessableEntity, 30022, "TASK_DISABLED", "task feature is disabled", nil)
|
|
case errors.Is(err, tasks.ErrOutsideBusinessHour), errors.Is(err, store.ErrOutsideBusinessHours):
|
|
writeError(w, r, http.StatusUnprocessableEntity, 30022, "OUTSIDE_BUSINESS_HOURS", "outside business hours", nil)
|
|
case errors.Is(err, store.ErrInsufficientBalance):
|
|
writeError(w, r, http.StatusUnprocessableEntity, 30022, "INSUFFICIENT_BALANCE", "insufficient available balance", nil)
|
|
case errors.Is(err, store.ErrDailyLimitReached):
|
|
writeError(w, r, http.StatusUnprocessableEntity, 30022, "DAILY_LIMIT_REACHED", "daily claim limit reached", nil)
|
|
case errors.Is(err, store.ErrLevelNotEligible), errors.Is(err, store.ErrTaskHallNotFound):
|
|
writeError(w, r, http.StatusUnprocessableEntity, 30022, "LEVEL_NOT_ELIGIBLE", "member level is not eligible", nil)
|
|
case errors.Is(err, store.ErrAccountNotFound), errors.Is(err, store.ErrAccountRestricted):
|
|
writeError(w, r, http.StatusUnprocessableEntity, 30022, "ACCOUNT_RESTRICTED", "account is restricted", nil)
|
|
case errors.Is(err, store.ErrPendingOrderExists):
|
|
writeError(w, r, http.StatusConflict, 30040, "PENDING_ORDER_EXISTS", "unfinished order exists", nil)
|
|
case errors.Is(err, store.ErrNoTaskAvailable):
|
|
writeError(w, r, http.StatusServiceUnavailable, 30053, "NO_TASK_AVAILABLE", "no task is currently available", nil)
|
|
case errors.Is(err, store.ErrOrderNotFound):
|
|
writeError(w, r, http.StatusNotFound, 30044, "ORDER_NOT_FOUND", "task order not found", nil)
|
|
case errors.Is(err, store.ErrOrderState):
|
|
writeError(w, r, http.StatusConflict, 30040, "ORDER_STATE_INVALID", "task order state does not allow this operation", nil)
|
|
case errors.Is(err, store.ErrOrderExpired):
|
|
writeError(w, r, http.StatusConflict, 30040, "ORDER_EXPIRED", "task order has expired", nil)
|
|
default:
|
|
writeError(w, r, http.StatusInternalServerError, 90000, reasonInternal, "internal error", nil)
|
|
}
|
|
}
|