package service import ( "context" "fmt" "strconv" "time" "git.nobla.cn/golang/aeus-admin/internal/logic" "git.nobla.cn/golang/aeus-admin/models" "git.nobla.cn/golang/aeus-admin/pb" "git.nobla.cn/golang/aeus-admin/pkg/dbcache" "git.nobla.cn/golang/aeus/middleware/auth" "git.nobla.cn/golang/aeus/pkg/cache" "git.nobla.cn/golang/aeus/pkg/errors" "git.nobla.cn/golang/rest/types" "gorm.io/gorm" ) type ( userOptions struct { db *gorm.DB cache cache.Cache } UserOption func(o *userOptions) UserService struct { user *logic.User menu *logic.Menu role *logic.Role department *logic.Department opts *userOptions } ) func WithUserDB(db *gorm.DB) UserOption { return func(o *userOptions) { o.db = db } } func WithUserCache(cache cache.Cache) UserOption { return func(o *userOptions) { o.cache = cache } } func (s *UserService) getUidFromContext(ctx context.Context) (string, error) { if claims, ok := auth.FromContext(ctx); !ok { return "", errors.ErrAccessDenied } else { return claims.GetSubject() } } func (s *UserService) hasPermission(menuName string, permissions []*models.Permission) bool { for _, permission := range permissions { if permission.Menu == menuName { return true } } return false } func (s *UserService) getPermissions(menuName string, permissions []*models.Permission) []*pb.PermissionItem { ss := make([]*pb.PermissionItem, 0, 10) for _, permission := range permissions { if permission.Menu == menuName { ss = append(ss, &pb.PermissionItem{ Value: permission.Permission, Label: permission.Label, }) } } return ss } func (s *UserService) recursiveNestedMenu(ctx context.Context, parent string, perm bool, menus []*models.Menu, permissions []*models.Permission) []*pb.MenuItem { values := make([]*pb.MenuItem, 0) for _, row := range menus { if row.Parent == parent { if !row.Public && !s.hasPermission(row.Name, permissions) { continue } v := &pb.MenuItem{ Label: row.Label, Name: row.Name, Icon: row.Icon, Hidden: row.Hidden, Route: row.Uri, Public: row.Public, View: row.ViewPath, Children: s.recursiveNestedMenu(ctx, row.Name, perm, menus, permissions), } if perm { v.Permissions = s.getPermissions(row.Name, permissions) } values = append(values, v) } } return values } func (s *UserService) GetMenus(ctx context.Context, req *pb.GetMenuRequest) (res *pb.GetMenuResponse, err error) { var ( uid string permissions []*models.Permission ) if uid, err = s.getUidFromContext(ctx); err != nil { return } res = &pb.GetMenuResponse{} res.Data, err = dbcache.TryCache(ctx, fmt.Sprintf("user:menu:%s:%v", uid, req.Permission), func(tx *gorm.DB) ([]*pb.MenuItem, error) { var ( userModel *models.User menus []*models.Menu ) userModel = &models.User{} if err = tx.Where("uid=?", uid).First(userModel).Error; err != nil { return nil, err } if menus, err = s.menu.GetMenus(ctx); err != nil { return nil, err } roleName := userModel.Role if userModel.Admin { roleName = "" } if permissions, err = s.role.GetPermissions(ctx, roleName); err != nil { return nil, err } return s.recursiveNestedMenu(ctx, "", req.Permission, menus, permissions), nil }, dbcache.WithDB(s.opts.db), dbcache.WithCacheDuration(time.Second*10), ) return } func (s *UserService) GetProfile(ctx context.Context, req *pb.GetProfileRequest) (res *pb.GetProfileResponse, err error) { if req.Uid == "" { if req.Uid, err = s.getUidFromContext(ctx); err != nil { return } } res, err = dbcache.TryCache(ctx, fmt.Sprintf("user:profile:%s", req.Uid), func(tx *gorm.DB) (*pb.GetProfileResponse, error) { profile := &pb.GetProfileResponse{} err = tx.Table("users AS u"). Select("u.uid as uid", "u.username", "u.avatar", "u.email", "u.description", "u.role", "u.admin"). Where("u.uid=? ", req.Uid). First(profile).Error return profile, err }, dbcache.WithDB(s.opts.db), dbcache.WithDependency(dbcache.NewSqlDependency("SELECT `updated_at` FROM users WHERE `uid`=?", req.Uid)), ) return } func (s *UserService) UpdateProfile(ctx context.Context, req *pb.UpdateProfileRequest) (res *pb.UpdateProfileResponse, err error) { if req.Uid == "" { if req.Uid, err = s.getUidFromContext(ctx); err != nil { return } } userModel := &models.User{} tx := s.opts.db.WithContext(ctx) if err = tx.Where("uid=?", req.Uid).First(userModel).Error; err != nil { return } if req.Avatar != "" { userModel.Avatar = req.Avatar } if req.Email != "" { userModel.Email = req.Email } if req.Username != "" { userModel.Username = req.Username } if req.Description != "" { userModel.Description = req.Description } if err = tx.Save(userModel).Error; err == nil { res = &pb.UpdateProfileResponse{ Uid: userModel.Uid, } } return } func (s *UserService) ResetPassword(ctx context.Context, req *pb.ResetPasswordRequest) (res *pb.ResetPasswordResponse, err error) { if req.Uid == "" { if req.Uid, err = s.getUidFromContext(ctx); err != nil { return } } userModel := &models.User{} tx := s.opts.db.WithContext(ctx) if err = tx.Where("uid=?", req.Uid).First(userModel).Error; err != nil { return } if userModel.Password == req.OldPassword { if err = tx.Where("uid=?", req.Uid).Model(&models.User{}).UpdateColumn("password", req.NewPassword).Error; err == nil { res = &pb.ResetPasswordResponse{ Uid: userModel.Uid, } } } else { err = errors.Format(errors.AccessDenied, "invalid old password") } return } func (s *UserService) GetPermissions(ctx context.Context, req *pb.GetPermissionRequest) (res *pb.GetPermissionResponse, err error) { if req.Uid == "" { if req.Uid, err = s.getUidFromContext(ctx); err != nil { return } } res = &pb.GetPermissionResponse{ Uid: req.Uid, } res.Permissions, err = s.user.GetPermissions(ctx, req.Uid) return } func (s *UserService) GetUserLabels(ctx context.Context, req *pb.GetUserLabelRequest) (res *pb.GetUserLabelResponse, err error) { res = &pb.GetUserLabelResponse{} var values []*types.TypeValue[string] if values, err = s.user.GetLabels(ctx); err == nil { res.Data = make([]*pb.LabelValue, 0, len(values)) for _, row := range values { res.Data = append(res.Data, &pb.LabelValue{ Label: row.Label, Value: row.Value, }) } } return } func (s *UserService) DepartmentUserNested(ctx context.Context) []*types.NestedValue[string] { var ( err error users []*models.User departments []*models.Department ) if departments, err = s.department.GetDepartments(ctx); err != nil { return nil } if users, err = s.user.GeUsers(ctx); err != nil { return nil } depts := s.department.RecursiveDepartment(ctx, 0, 0, departments) values := make([]*types.NestedValue[string], 0) for _, dept := range depts { v := &types.NestedValue[string]{ Label: dept.Label, Value: strconv.FormatInt(dept.Value, 10), Children: make([]*types.NestedValue[string], 0), } for _, user := range users { if strconv.FormatInt(user.DeptId, 10) == v.Value { v.Children = append(v.Children, &types.NestedValue[string]{ Label: fmt.Sprintf("%s(%s)", user.Username, user.Uid), Value: user.Uid, }) } } values = append(values, v) } return values } func (s *UserService) GetUserTags(ctx context.Context, req *pb.GetUserTagRequest) (res *pb.GetUserTagResponse, err error) { res = &pb.GetUserTagResponse{} res.Data, err = dbcache.TryCache(ctx, fmt.Sprintf("user:tags"), func(tx *gorm.DB) ([]*pb.LabelValue, error) { values := make([]*models.User, 0) if err = tx.Select("DISTINCT(`tag`) AS `tag`").Find(&values).Error; err == nil { items := make([]*pb.LabelValue, 0, len(values)) for _, v := range values { if v.Tag == "" { continue } items = append(items, &pb.LabelValue{ Label: v.Tag, Value: v.Tag, }) } return items, nil } else { return nil, err } }, dbcache.WithDB(s.opts.db), dbcache.WithDependency(dbcache.NewSqlDependency("SELECT MAX(`updated_at`) FROM users")), ) return } func NewUserService(cbs ...UserOption) *UserService { opts := &userOptions{} for _, cb := range cbs { cb(opts) } return &UserService{ opts: opts, user: logic.NewUserLogic(opts.db, opts.cache), role: logic.NewRoleLogic(opts.db, opts.cache), menu: logic.NewMenuLogic(opts.db, opts.cache), department: logic.NewDepartmentLogic(opts.db, opts.cache), } }