Files
olons_server/internal/httpapi/accounts.go
T

145 lines
5.1 KiB
Go

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_mask": result.Account.MobileMask,
"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)
}
}