145 lines
5.1 KiB
Go
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": 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)
|
|
}
|
|
}
|