thread-master/internal/bootstrap/admin.go

73 lines
2.1 KiB
Go

package bootstrap
import (
"context"
"strings"
app "haixun-backend/internal/library/errors"
"haixun-backend/internal/library/errors/code"
"haixun-backend/internal/model/member/domain/entity"
domrepo "haixun-backend/internal/model/member/domain/repository"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
)
type AdminOptions struct {
TenantID string
Email string
Password string
DisplayName string
}
func EnsureAdminMember(ctx context.Context, repo domrepo.Repository, opts AdminOptions) (*entity.Member, bool, error) {
tenantID := strings.TrimSpace(opts.TenantID)
email := normalizeEmail(opts.Email)
if tenantID == "" || email == "" || opts.Password == "" {
return nil, false, app.For(code.Member).InputMissingRequired("tenant_id, email, and password are required")
}
if len(opts.Password) < 8 {
return nil, false, app.For(code.Member).InputInvalidFormat("password must be at least 8 characters")
}
existing, err := repo.FindByEmail(ctx, tenantID, email)
if err == nil {
if err := repo.SetRoles(ctx, tenantID, existing.UID, []string{"admin"}); err != nil {
return nil, false, err
}
existing.Roles = []string{"admin"}
return existing, false, nil
}
if e := app.FromError(err); e == nil || e.Category() != code.ResNotFound {
return nil, false, err
}
hash, err := bcrypt.GenerateFromPassword([]byte(opts.Password), bcrypt.DefaultCost)
if err != nil {
return nil, false, app.For(code.Member).SysInternal("hash password failed").WithCause(err)
}
displayName := strings.TrimSpace(opts.DisplayName)
if displayName == "" {
displayName = "Admin"
}
member, err := repo.Create(ctx, &entity.Member{
TenantID: tenantID,
UID: uuid.NewString(),
Email: email,
DisplayName: displayName,
Language: "zh-TW",
Status: entity.StatusOpen,
Origin: entity.OriginNative,
PasswordHash: string(hash),
Roles: []string{"admin"},
})
if err != nil {
return nil, false, err
}
return member, true, nil
}
func normalizeEmail(email string) string {
return strings.ToLower(strings.TrimSpace(email))
}