package httpapi import ( "errors" "net/http" "strings" accountsvc "git.118im.com/im_team/im_workbench/internal/accounts" "git.118im.com/im_team/im_workbench/internal/auth" "git.118im.com/im_team/im_workbench/internal/store" ) type accountRegisterRequest struct { Username string `json:"username"` Phone string `json:"phone"` Password string `json:"password"` ReferralCode string `json:"referral_code,omitempty"` PrivacyAccepted bool `json:"privacy_accepted"` } type accountLoginRequest struct { Phone string `json:"phone"` Password string `json:"password"` } func (a *API) accountRegister(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { methodNotAllowed(w, r) return } if !a.validatePublicClient(w, r) { return } var body accountRegisterRequest if !decodeBody(w, r, &body) { return } result, err := a.accounts.Register(r.Context(), accountsvc.RegisterInput{ Username: body.Username, Phone: body.Phone, Password: body.Password, ReferralCode: body.ReferralCode, PrivacyAccepted: body.PrivacyAccepted, DeviceID: strings.TrimSpace(r.Header.Get("X-Device-ID")), }) if err != nil { writeAccountError(w, r, err) return } writeSuccess(w, r, accountResultPayload(result)) } func (a *API) accountLogin(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { methodNotAllowed(w, r) return } if !a.validatePublicClient(w, r) { return } var body accountLoginRequest if !decodeBody(w, r, &body) { return } result, err := a.accounts.Login(r.Context(), accountsvc.LoginInput{ Phone: body.Phone, Password: body.Password, DeviceID: strings.TrimSpace(r.Header.Get("X-Device-ID")), }) if err != nil { writeAccountError(w, r, err) return } writeSuccess(w, r, accountResultPayload(result)) } func (a *API) accountLogout(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { methodNotAllowed(w, r) return } if a.auth == nil { writeError(w, r, http.StatusServiceUnavailable, 90001, "SERVICE_UNAVAILABLE", "service unavailable", nil) return } if err := a.auth.Logout(r); err != nil { switch { case errors.Is(err, auth.ErrAuthRequired): writeError(w, r, http.StatusUnauthorized, 30041, "AUTH_REQUIRED", "authentication required", nil) case errors.Is(err, auth.ErrForbidden): writeError(w, r, http.StatusForbidden, 30003, reasonForbidden, "forbidden", nil) default: writeError(w, r, http.StatusInternalServerError, 90000, reasonInternal, "internal error", nil) } return } writeSuccess(w, r, nil) } func (a *API) validatePublicClient(w http.ResponseWriter, r *http.Request) bool { if a.accounts == nil || a.auth == nil { writeError(w, r, http.StatusServiceUnavailable, 90001, "SERVICE_UNAVAILABLE", "service unavailable", nil) return false } if err := a.auth.ValidateAPIKey(r); err != nil { writeError(w, r, http.StatusForbidden, 30003, reasonForbidden, "valid API key required", nil) return false } return true } func accountResultPayload(result *accountsvc.Result) map[string]any { return map[string]any{ "member_id": result.Account.MemberID, "member_no": result.Account.MemberNo, "username": result.Account.Username, "phone": result.Account.Mobile, "invite_code": result.Account.InviteCode, "token": result.Token, "token_type": "Bearer", "expires_at": result.ExpiresAt, } } func writeAccountError(w http.ResponseWriter, r *http.Request, err error) { switch { case errors.Is(err, accountsvc.ErrUsernameInvalid): writeError(w, r, http.StatusBadRequest, 30000, "USERNAME_INVALID_FORMAT", "validation failed", map[string]any{"field": "username"}) case errors.Is(err, accountsvc.ErrPhoneInvalid): writeError(w, r, http.StatusBadRequest, 30000, "PHONE_INVALID_FORMAT", "validation failed", map[string]any{"field": "phone"}) case errors.Is(err, accountsvc.ErrPasswordInvalid): writeError(w, r, http.StatusBadRequest, 30000, "PASSWORD_INVALID_FORMAT", "validation failed", map[string]any{"field": "password"}) case errors.Is(err, accountsvc.ErrReferralInvalid), errors.Is(err, store.ErrReferralInvalid): writeError(w, r, http.StatusBadRequest, 30000, "REFERRAL_CODE_INVALID", "validation failed", map[string]any{"field": "referral_code"}) case errors.Is(err, accountsvc.ErrPrivacyNotAccepted): writeError(w, r, http.StatusBadRequest, 30000, "PRIVACY_POLICY_NOT_ACCEPTED", "validation failed", map[string]any{"field": "privacy_accepted"}) case errors.Is(err, store.ErrAccountExists): writeError(w, r, http.StatusConflict, 30040, "ACCOUNT_ALREADY_REGISTERED", "account already registered", nil) case errors.Is(err, store.ErrInvalidCredentials): writeError(w, r, http.StatusUnauthorized, 30043, "INVALID_CREDENTIALS", "invalid phone or password", nil) case errors.Is(err, accountsvc.ErrRateLimited): writeError(w, r, http.StatusTooManyRequests, 30020, "RATE_LIMITED", "too many login attempts", nil) case errors.Is(err, accountsvc.ErrUnavailable): writeError(w, r, http.StatusServiceUnavailable, 90001, "SERVICE_UNAVAILABLE", "service unavailable", nil) default: writeError(w, r, http.StatusInternalServerError, 90000, reasonInternal, "internal error", nil) } }