121 lines
4.2 KiB
Go
121 lines
4.2 KiB
Go
package usecase
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
app "haixun-backend/internal/library/errors"
|
|
"haixun-backend/internal/library/errors/code"
|
|
authusecase "haixun-backend/internal/model/auth/domain/usecase"
|
|
"haixun-backend/internal/model/member/domain/entity"
|
|
domrepo "haixun-backend/internal/model/member/domain/repository"
|
|
domusecase "haixun-backend/internal/model/member/domain/usecase"
|
|
|
|
"github.com/google/uuid"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
type memberUseCase struct {
|
|
repo domrepo.Repository
|
|
tokens authusecase.TokenUseCase
|
|
}
|
|
|
|
func NewUseCase(repo domrepo.Repository, tokens authusecase.TokenUseCase) domusecase.UseCase {
|
|
return &memberUseCase{repo: repo, tokens: tokens}
|
|
}
|
|
|
|
func (u *memberUseCase) Register(ctx context.Context, req domusecase.RegisterRequest) (*entity.Member, *domusecase.AuthToken, error) {
|
|
tenantID := strings.TrimSpace(req.TenantID)
|
|
email := normalizeEmail(req.Email)
|
|
if tenantID == "" || email == "" || req.Password == "" {
|
|
return nil, nil, app.For(code.Member).InputMissingRequired("tenant_id, email, and password are required")
|
|
}
|
|
if len(req.Password) < 8 {
|
|
return nil, nil, app.For(code.Member).InputInvalidFormat("password must be at least 8 characters")
|
|
}
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return nil, nil, app.For(code.Member).SysInternal("hash password failed").WithCause(err)
|
|
}
|
|
member, err := u.repo.Create(ctx, &entity.Member{
|
|
TenantID: tenantID,
|
|
UID: uuid.NewString(),
|
|
Email: email,
|
|
DisplayName: strings.TrimSpace(req.DisplayName),
|
|
Language: strings.TrimSpace(req.Language),
|
|
Status: entity.StatusOpen,
|
|
Origin: entity.OriginNative,
|
|
PasswordHash: string(hash),
|
|
Roles: []string{"user"},
|
|
})
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
token, err := u.issue(ctx, member)
|
|
return member, token, err
|
|
}
|
|
|
|
func (u *memberUseCase) Login(ctx context.Context, req domusecase.LoginRequest) (*entity.Member, *domusecase.AuthToken, error) {
|
|
tenantID := strings.TrimSpace(req.TenantID)
|
|
email := normalizeEmail(req.Email)
|
|
if tenantID == "" || email == "" || req.Password == "" {
|
|
return nil, nil, app.For(code.Auth).InputMissingRequired("tenant_id, email, and password are required")
|
|
}
|
|
member, err := u.repo.FindByEmail(ctx, tenantID, email)
|
|
if err != nil {
|
|
return nil, nil, app.For(code.Auth).AuthUnauthorized("invalid email or password").WithCause(err)
|
|
}
|
|
if member.Status != entity.StatusOpen {
|
|
return nil, nil, app.For(code.Auth).AuthForbidden("member is not active")
|
|
}
|
|
if err := bcrypt.CompareHashAndPassword([]byte(member.PasswordHash), []byte(req.Password)); err != nil {
|
|
return nil, nil, app.For(code.Auth).AuthUnauthorized("invalid email or password")
|
|
}
|
|
token, err := u.issue(ctx, member)
|
|
return member, token, err
|
|
}
|
|
|
|
func (u *memberUseCase) GetByUID(ctx context.Context, tenantID, uid string) (*entity.Member, error) {
|
|
if tenantID == "" || uid == "" {
|
|
return nil, app.For(code.Member).InputMissingRequired("tenant_id and uid are required")
|
|
}
|
|
return u.repo.FindByUID(ctx, tenantID, uid)
|
|
}
|
|
|
|
func (u *memberUseCase) UpdateProfile(ctx context.Context, req domusecase.UpdateProfileRequest) (*entity.Member, error) {
|
|
if req.TenantID == "" || req.UID == "" {
|
|
return nil, app.For(code.Member).InputMissingRequired("tenant_id and uid are required")
|
|
}
|
|
return u.repo.UpdateProfile(ctx, req.TenantID, req.UID, domrepo.ProfileUpdate{
|
|
DisplayName: req.DisplayName,
|
|
Avatar: req.Avatar,
|
|
Language: req.Language,
|
|
Currency: req.Currency,
|
|
Phone: req.Phone,
|
|
})
|
|
}
|
|
|
|
func (u *memberUseCase) issue(ctx context.Context, member *entity.Member) (*domusecase.AuthToken, error) {
|
|
if u.tokens == nil {
|
|
return nil, app.For(code.Auth).SysNotImplemented("token usecase is not configured")
|
|
}
|
|
pair, err := u.tokens.IssuePair(ctx, authusecase.IssuePairRequest{
|
|
TenantID: member.TenantID,
|
|
UID: member.UID,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &domusecase.AuthToken{
|
|
AccessToken: pair.AccessToken,
|
|
RefreshToken: pair.RefreshToken,
|
|
ExpiresIn: pair.ExpiresIn,
|
|
UID: member.UID,
|
|
TokenType: pair.TokenType,
|
|
}, nil
|
|
}
|
|
|
|
func normalizeEmail(email string) string {
|
|
return strings.ToLower(strings.TrimSpace(email))
|
|
}
|