216 lines
5.4 KiB
Go
216 lines
5.4 KiB
Go
package repository
|
|
|
|
import (
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"hyapp-admin-server/internal/appctx"
|
|
"hyapp-admin-server/internal/model"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
type MoneyAccess struct {
|
|
UserID uint
|
|
All bool
|
|
Scopes []model.UserMoneyScope
|
|
}
|
|
|
|
func (access MoneyAccess) Allows(appCode string, regionID int64) bool {
|
|
if access.All {
|
|
return true
|
|
}
|
|
appCode = appctx.Normalize(appCode)
|
|
for _, scope := range access.Scopes {
|
|
if appctx.Normalize(scope.AppCode) != appCode {
|
|
continue
|
|
}
|
|
if scope.RegionID == 0 || scope.RegionID == regionID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (access MoneyAccess) AppCodes() []string {
|
|
if access.All {
|
|
return nil
|
|
}
|
|
set := map[string]struct{}{}
|
|
for _, scope := range access.Scopes {
|
|
appCode := appctx.Normalize(scope.AppCode)
|
|
if appCode != "" {
|
|
set[appCode] = struct{}{}
|
|
}
|
|
}
|
|
out := make([]string, 0, len(set))
|
|
for appCode := range set {
|
|
out = append(out, appCode)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (s *Store) MoneyAccessForUser(userID uint) (MoneyAccess, error) {
|
|
if userID == 0 {
|
|
return MoneyAccess{All: true}, nil
|
|
}
|
|
var user model.User
|
|
if err := s.db.Preload("Roles").First(&user, userID).Error; err != nil {
|
|
return MoneyAccess{}, err
|
|
}
|
|
scopes, err := s.ListUserMoneyScopes(userID)
|
|
if err != nil {
|
|
return MoneyAccess{}, err
|
|
}
|
|
if len(scopes) == 0 && userHasRole(user, "platform-admin") {
|
|
return MoneyAccess{UserID: userID, All: true}, nil
|
|
}
|
|
return MoneyAccess{UserID: userID, Scopes: scopes}, nil
|
|
}
|
|
|
|
func (s *Store) ListUserMoneyScopes(userID uint) ([]model.UserMoneyScope, error) {
|
|
var scopes []model.UserMoneyScope
|
|
err := s.db.Where("user_id = ?", userID).Order("app_code ASC, region_id ASC").Find(&scopes).Error
|
|
return scopes, err
|
|
}
|
|
|
|
func (s *Store) ReplaceUserMoneyScopes(userID uint, scopes []model.UserMoneyScope) error {
|
|
if userID == 0 {
|
|
return errors.New("user id is required")
|
|
}
|
|
normalized, err := normalizeMoneyScopes(userID, scopes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.db.Transaction(func(tx *gorm.DB) error {
|
|
var user model.User
|
|
if err := tx.First(&user, userID).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Where("user_id = ?", userID).Delete(&model.UserMoneyScope{}).Error; err != nil {
|
|
return err
|
|
}
|
|
if len(normalized) == 0 {
|
|
return nil
|
|
}
|
|
return tx.Create(&normalized).Error
|
|
})
|
|
}
|
|
|
|
func (s *Store) RecordTemporaryPaymentLinkOwner(owner model.TemporaryPaymentLinkOwner) error {
|
|
owner.AppCode = appctx.Normalize(owner.AppCode)
|
|
owner.OrderID = strings.TrimSpace(owner.OrderID)
|
|
if owner.AppCode == "" || owner.OrderID == "" || owner.AdminUserID == 0 || owner.RegionID <= 0 {
|
|
return errors.New("temporary payment link owner is incomplete")
|
|
}
|
|
nowMS := time.Now().UTC().UnixMilli()
|
|
if owner.CreatedAtMS == 0 {
|
|
owner.CreatedAtMS = nowMS
|
|
}
|
|
owner.UpdatedAtMS = nowMS
|
|
return s.db.Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{{Name: "app_code"}, {Name: "order_id"}},
|
|
DoUpdates: clause.AssignmentColumns([]string{
|
|
"admin_user_id",
|
|
"region_id",
|
|
"updated_at_ms",
|
|
}),
|
|
}).Create(&owner).Error
|
|
}
|
|
|
|
func (s *Store) TemporaryPaymentLinkOwners(appCode string, orderIDs []string) (map[string]model.TemporaryPaymentLinkOwner, error) {
|
|
appCode = appctx.Normalize(appCode)
|
|
ids := uniqueStrings(orderIDs)
|
|
out := make(map[string]model.TemporaryPaymentLinkOwner, len(ids))
|
|
if appCode == "" || len(ids) == 0 {
|
|
return out, nil
|
|
}
|
|
var owners []model.TemporaryPaymentLinkOwner
|
|
if err := s.db.Where("app_code = ? AND order_id IN ?", appCode, ids).Find(&owners).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
for _, owner := range owners {
|
|
out[owner.OrderID] = owner
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *Store) FindUsersByIDs(ids []uint) (map[uint]model.User, error) {
|
|
uniqueIDs := uniqueUints(ids)
|
|
out := make(map[uint]model.User, len(uniqueIDs))
|
|
if len(uniqueIDs) == 0 {
|
|
return out, nil
|
|
}
|
|
var users []model.User
|
|
if err := s.db.Where("id IN ?", uniqueIDs).Find(&users).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
for _, user := range users {
|
|
out[user.ID] = user
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func normalizeMoneyScopes(userID uint, scopes []model.UserMoneyScope) ([]model.UserMoneyScope, error) {
|
|
seen := map[string]struct{}{}
|
|
out := make([]model.UserMoneyScope, 0, len(scopes))
|
|
for _, scope := range scopes {
|
|
appCode := appctx.Normalize(scope.AppCode)
|
|
if appCode == "" || scope.RegionID < 0 {
|
|
return nil, errors.New("money scope app code and region id are required")
|
|
}
|
|
key := appCode + ":" + strconv.FormatInt(scope.RegionID, 10)
|
|
if _, ok := seen[key]; ok {
|
|
continue
|
|
}
|
|
seen[key] = struct{}{}
|
|
out = append(out, model.UserMoneyScope{UserID: userID, AppCode: appCode, RegionID: scope.RegionID})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func userHasRole(user model.User, code string) bool {
|
|
for _, role := range user.Roles {
|
|
if role.Code == code {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func uniqueStrings(values []string) []string {
|
|
set := map[string]struct{}{}
|
|
out := make([]string, 0, len(values))
|
|
for _, value := range values {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
continue
|
|
}
|
|
if _, ok := set[value]; ok {
|
|
continue
|
|
}
|
|
set[value] = struct{}{}
|
|
out = append(out, value)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func uniqueUints(values []uint) []uint {
|
|
set := map[uint]struct{}{}
|
|
out := make([]uint, 0, len(values))
|
|
for _, value := range values {
|
|
if value == 0 {
|
|
continue
|
|
}
|
|
if _, ok := set[value]; ok {
|
|
continue
|
|
}
|
|
set[value] = struct{}{}
|
|
out = append(out, value)
|
|
}
|
|
return out
|
|
}
|