package service import ( "context" "fmt" "time" "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/errors" "gorm.io/gorm" ) type ( userOptions struct { db *gorm.DB } UserOption func(o *userOptions) UserService struct { opts *userOptions } ) func WithUserDB(db *gorm.DB) UserOption { return func(o *userOptions) { o.db = db } } 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) getRolePermissions(db *gorm.DB, role string) (permissions []*models.Permission, err error) { permissions = make([]*models.Permission, 0) err = db.Where("permission IN (SELECT permission FROM role_permissions WHERE role = ?)", role).Find(&permissions).Error return } 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) { userModel := &models.User{} if err = tx.Where("uid=?", uid).First(userModel).Error; err != nil { return nil, err } items := make([]*models.Menu, 0) if err = tx.Find(&items).Error; err != nil { return nil, err } if userModel.Admin { permissions = make([]*models.Permission, 0) if err = tx.Find(&permissions).Error; err != nil { return nil, err } } else { if permissions, err = s.getRolePermissions(tx, userModel.Role); err != nil { return nil, err } } return s.recursiveNestedMenu(ctx, "", req.Permission, items, permissions), nil }, dbcache.WithDB(s.opts.db), dbcache.WithDependency(dbcache.NewSqlDependency("SELECT `updated_at` FROM users WHERE `uid`=?", uid)), ) 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)), // dbcache.WithDepend("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 = dbcache.TryCache(ctx, fmt.Sprintf("user:permissions:%s", req.Uid), func(tx *gorm.DB) ([]string, error) { var ss []string err = tx.Select("permission").Model(&models.RolePermission{}). Joins("LEFT JOIN users on users.role = role_permissions.role"). Where("users.uid = ?", req.Uid). Pluck("permission", &ss). Error return ss, err }, dbcache.WithCacheDuration(time.Minute), dbcache.WithDB(s.opts.db)) return } func (s *UserService) GetUserLabels(ctx context.Context, req *pb.GetUserLabelRequest) (res *pb.GetUserLabelResponse, err error) { res = &pb.GetUserLabelResponse{} res.Data, err = dbcache.TryCache(ctx, fmt.Sprintf("user:labels"), func(tx *gorm.DB) ([]*pb.LabelValue, error) { values := make([]*models.User, 0) if err = tx.Find(&values).Error; err == nil { items := make([]*pb.LabelValue, 0, len(values)) for _, v := range values { items = append(items, &pb.LabelValue{ Label: v.Username, Value: v.Uid, }) } return items, nil } else { return nil, err } }, dbcache.WithDB(s.opts.db), dbcache.WithDependency(dbcache.NewSqlDependency("SELECT MAX(`updated_at`) FROM users")), ) return } 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, } }