590 lines
18 KiB
Go
590 lines
18 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
|
|
"gorm.io/datatypes"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
type accountRepository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewAccountRepository(db *gorm.DB) service.AccountRepository {
|
|
return &accountRepository{db: db}
|
|
}
|
|
|
|
func (r *accountRepository) Create(ctx context.Context, account *service.Account) error {
|
|
m := accountModelFromService(account)
|
|
err := r.db.WithContext(ctx).Create(m).Error
|
|
if err == nil {
|
|
applyAccountModelToService(account, m)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (r *accountRepository) GetByID(ctx context.Context, id int64) (*service.Account, error) {
|
|
var m accountModel
|
|
err := r.db.WithContext(ctx).Preload("Proxy").Preload("AccountGroups.Group").First(&m, id).Error
|
|
if err != nil {
|
|
return nil, translatePersistenceError(err, service.ErrAccountNotFound, nil)
|
|
}
|
|
return accountModelToService(&m), nil
|
|
}
|
|
|
|
func (r *accountRepository) GetByCRSAccountID(ctx context.Context, crsAccountID string) (*service.Account, error) {
|
|
if crsAccountID == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
var m accountModel
|
|
err := r.db.WithContext(ctx).Where("extra->>'crs_account_id' = ?", crsAccountID).First(&m).Error
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
return accountModelToService(&m), nil
|
|
}
|
|
|
|
func (r *accountRepository) Update(ctx context.Context, account *service.Account) error {
|
|
m := accountModelFromService(account)
|
|
err := r.db.WithContext(ctx).Save(m).Error
|
|
if err == nil {
|
|
applyAccountModelToService(account, m)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (r *accountRepository) Delete(ctx context.Context, id int64) error {
|
|
if err := r.db.WithContext(ctx).Where("account_id = ?", id).Delete(&accountGroupModel{}).Error; err != nil {
|
|
return err
|
|
}
|
|
return r.db.WithContext(ctx).Delete(&accountModel{}, id).Error
|
|
}
|
|
|
|
func (r *accountRepository) List(ctx context.Context, params pagination.PaginationParams) ([]service.Account, *pagination.PaginationResult, error) {
|
|
return r.ListWithFilters(ctx, params, "", "", "", "")
|
|
}
|
|
|
|
func (r *accountRepository) ListWithFilters(ctx context.Context, params pagination.PaginationParams, platform, accountType, status, search string) ([]service.Account, *pagination.PaginationResult, error) {
|
|
var accounts []accountModel
|
|
var total int64
|
|
|
|
db := r.db.WithContext(ctx).Model(&accountModel{})
|
|
|
|
if platform != "" {
|
|
db = db.Where("platform = ?", platform)
|
|
}
|
|
if accountType != "" {
|
|
db = db.Where("type = ?", accountType)
|
|
}
|
|
if status != "" {
|
|
db = db.Where("status = ?", status)
|
|
}
|
|
if search != "" {
|
|
searchPattern := "%" + search + "%"
|
|
db = db.Where("name ILIKE ?", searchPattern)
|
|
}
|
|
|
|
if err := db.Count(&total).Error; err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if err := db.Preload("Proxy").Preload("AccountGroups.Group").Offset(params.Offset()).Limit(params.Limit()).Order("id DESC").Find(&accounts).Error; err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
outAccounts := make([]service.Account, 0, len(accounts))
|
|
for i := range accounts {
|
|
outAccounts = append(outAccounts, *accountModelToService(&accounts[i]))
|
|
}
|
|
|
|
return outAccounts, paginationResultFromTotal(total, params), nil
|
|
}
|
|
|
|
func (r *accountRepository) ListByGroup(ctx context.Context, groupID int64) ([]service.Account, error) {
|
|
var accounts []accountModel
|
|
err := r.db.WithContext(ctx).
|
|
Joins("JOIN account_groups ON account_groups.account_id = accounts.id").
|
|
Where("account_groups.group_id = ? AND accounts.status = ?", groupID, service.StatusActive).
|
|
Preload("Proxy").
|
|
Order("account_groups.priority ASC, accounts.priority ASC").
|
|
Find(&accounts).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
outAccounts := make([]service.Account, 0, len(accounts))
|
|
for i := range accounts {
|
|
outAccounts = append(outAccounts, *accountModelToService(&accounts[i]))
|
|
}
|
|
return outAccounts, nil
|
|
}
|
|
|
|
func (r *accountRepository) ListActive(ctx context.Context) ([]service.Account, error) {
|
|
var accounts []accountModel
|
|
err := r.db.WithContext(ctx).
|
|
Where("status = ?", service.StatusActive).
|
|
Preload("Proxy").
|
|
Order("priority ASC").
|
|
Find(&accounts).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
outAccounts := make([]service.Account, 0, len(accounts))
|
|
for i := range accounts {
|
|
outAccounts = append(outAccounts, *accountModelToService(&accounts[i]))
|
|
}
|
|
return outAccounts, nil
|
|
}
|
|
|
|
func (r *accountRepository) ListByPlatform(ctx context.Context, platform string) ([]service.Account, error) {
|
|
var accounts []accountModel
|
|
err := r.db.WithContext(ctx).
|
|
Where("platform = ? AND status = ?", platform, service.StatusActive).
|
|
Preload("Proxy").
|
|
Order("priority ASC").
|
|
Find(&accounts).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
outAccounts := make([]service.Account, 0, len(accounts))
|
|
for i := range accounts {
|
|
outAccounts = append(outAccounts, *accountModelToService(&accounts[i]))
|
|
}
|
|
return outAccounts, nil
|
|
}
|
|
|
|
func (r *accountRepository) UpdateLastUsed(ctx context.Context, id int64) error {
|
|
now := time.Now()
|
|
return r.db.WithContext(ctx).Model(&accountModel{}).Where("id = ?", id).Update("last_used_at", now).Error
|
|
}
|
|
|
|
func (r *accountRepository) SetError(ctx context.Context, id int64, errorMsg string) error {
|
|
return r.db.WithContext(ctx).Model(&accountModel{}).Where("id = ?", id).
|
|
Updates(map[string]any{
|
|
"status": service.StatusError,
|
|
"error_message": errorMsg,
|
|
}).Error
|
|
}
|
|
|
|
func (r *accountRepository) AddToGroup(ctx context.Context, accountID, groupID int64, priority int) error {
|
|
ag := &accountGroupModel{
|
|
AccountID: accountID,
|
|
GroupID: groupID,
|
|
Priority: priority,
|
|
}
|
|
return r.db.WithContext(ctx).Create(ag).Error
|
|
}
|
|
|
|
func (r *accountRepository) RemoveFromGroup(ctx context.Context, accountID, groupID int64) error {
|
|
return r.db.WithContext(ctx).Where("account_id = ? AND group_id = ?", accountID, groupID).
|
|
Delete(&accountGroupModel{}).Error
|
|
}
|
|
|
|
func (r *accountRepository) GetGroups(ctx context.Context, accountID int64) ([]service.Group, error) {
|
|
var groups []groupModel
|
|
err := r.db.WithContext(ctx).
|
|
Joins("JOIN account_groups ON account_groups.group_id = groups.id").
|
|
Where("account_groups.account_id = ?", accountID).
|
|
Find(&groups).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
outGroups := make([]service.Group, 0, len(groups))
|
|
for i := range groups {
|
|
outGroups = append(outGroups, *groupModelToService(&groups[i]))
|
|
}
|
|
return outGroups, nil
|
|
}
|
|
|
|
func (r *accountRepository) BindGroups(ctx context.Context, accountID int64, groupIDs []int64) error {
|
|
if err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Delete(&accountGroupModel{}).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(groupIDs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
accountGroups := make([]accountGroupModel, 0, len(groupIDs))
|
|
for i, groupID := range groupIDs {
|
|
accountGroups = append(accountGroups, accountGroupModel{
|
|
AccountID: accountID,
|
|
GroupID: groupID,
|
|
Priority: i + 1,
|
|
})
|
|
}
|
|
return r.db.WithContext(ctx).Create(&accountGroups).Error
|
|
}
|
|
|
|
func (r *accountRepository) ListSchedulable(ctx context.Context) ([]service.Account, error) {
|
|
var accounts []accountModel
|
|
now := time.Now()
|
|
err := r.db.WithContext(ctx).
|
|
Where("status = ? AND schedulable = ?", service.StatusActive, true).
|
|
Where("(overload_until IS NULL OR overload_until <= ?)", now).
|
|
Where("(rate_limit_reset_at IS NULL OR rate_limit_reset_at <= ?)", now).
|
|
Preload("Proxy").
|
|
Order("priority ASC").
|
|
Find(&accounts).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
outAccounts := make([]service.Account, 0, len(accounts))
|
|
for i := range accounts {
|
|
outAccounts = append(outAccounts, *accountModelToService(&accounts[i]))
|
|
}
|
|
return outAccounts, nil
|
|
}
|
|
|
|
func (r *accountRepository) ListSchedulableByGroupID(ctx context.Context, groupID int64) ([]service.Account, error) {
|
|
var accounts []accountModel
|
|
now := time.Now()
|
|
err := r.db.WithContext(ctx).
|
|
Joins("JOIN account_groups ON account_groups.account_id = accounts.id").
|
|
Where("account_groups.group_id = ?", groupID).
|
|
Where("accounts.status = ? AND accounts.schedulable = ?", service.StatusActive, true).
|
|
Where("(accounts.overload_until IS NULL OR accounts.overload_until <= ?)", now).
|
|
Where("(accounts.rate_limit_reset_at IS NULL OR accounts.rate_limit_reset_at <= ?)", now).
|
|
Preload("Proxy").
|
|
Order("account_groups.priority ASC, accounts.priority ASC").
|
|
Find(&accounts).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
outAccounts := make([]service.Account, 0, len(accounts))
|
|
for i := range accounts {
|
|
outAccounts = append(outAccounts, *accountModelToService(&accounts[i]))
|
|
}
|
|
return outAccounts, nil
|
|
}
|
|
|
|
func (r *accountRepository) ListSchedulableByPlatform(ctx context.Context, platform string) ([]service.Account, error) {
|
|
var accounts []accountModel
|
|
now := time.Now()
|
|
err := r.db.WithContext(ctx).
|
|
Where("platform = ?", platform).
|
|
Where("status = ? AND schedulable = ?", service.StatusActive, true).
|
|
Where("(overload_until IS NULL OR overload_until <= ?)", now).
|
|
Where("(rate_limit_reset_at IS NULL OR rate_limit_reset_at <= ?)", now).
|
|
Preload("Proxy").
|
|
Order("priority ASC").
|
|
Find(&accounts).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
outAccounts := make([]service.Account, 0, len(accounts))
|
|
for i := range accounts {
|
|
outAccounts = append(outAccounts, *accountModelToService(&accounts[i]))
|
|
}
|
|
return outAccounts, nil
|
|
}
|
|
|
|
func (r *accountRepository) ListSchedulableByGroupIDAndPlatform(ctx context.Context, groupID int64, platform string) ([]service.Account, error) {
|
|
var accounts []accountModel
|
|
now := time.Now()
|
|
err := r.db.WithContext(ctx).
|
|
Joins("JOIN account_groups ON account_groups.account_id = accounts.id").
|
|
Where("account_groups.group_id = ?", groupID).
|
|
Where("accounts.platform = ?", platform).
|
|
Where("accounts.status = ? AND accounts.schedulable = ?", service.StatusActive, true).
|
|
Where("(accounts.overload_until IS NULL OR accounts.overload_until <= ?)", now).
|
|
Where("(accounts.rate_limit_reset_at IS NULL OR accounts.rate_limit_reset_at <= ?)", now).
|
|
Preload("Proxy").
|
|
Order("account_groups.priority ASC, accounts.priority ASC").
|
|
Find(&accounts).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
outAccounts := make([]service.Account, 0, len(accounts))
|
|
for i := range accounts {
|
|
outAccounts = append(outAccounts, *accountModelToService(&accounts[i]))
|
|
}
|
|
return outAccounts, nil
|
|
}
|
|
|
|
func (r *accountRepository) SetRateLimited(ctx context.Context, id int64, resetAt time.Time) error {
|
|
now := time.Now()
|
|
return r.db.WithContext(ctx).Model(&accountModel{}).Where("id = ?", id).
|
|
Updates(map[string]any{
|
|
"rate_limited_at": now,
|
|
"rate_limit_reset_at": resetAt,
|
|
}).Error
|
|
}
|
|
|
|
func (r *accountRepository) SetOverloaded(ctx context.Context, id int64, until time.Time) error {
|
|
return r.db.WithContext(ctx).Model(&accountModel{}).Where("id = ?", id).
|
|
Update("overload_until", until).Error
|
|
}
|
|
|
|
func (r *accountRepository) ClearRateLimit(ctx context.Context, id int64) error {
|
|
return r.db.WithContext(ctx).Model(&accountModel{}).Where("id = ?", id).
|
|
Updates(map[string]any{
|
|
"rate_limited_at": nil,
|
|
"rate_limit_reset_at": nil,
|
|
"overload_until": nil,
|
|
}).Error
|
|
}
|
|
|
|
func (r *accountRepository) UpdateSessionWindow(ctx context.Context, id int64, start, end *time.Time, status string) error {
|
|
updates := map[string]any{
|
|
"session_window_status": status,
|
|
}
|
|
if start != nil {
|
|
updates["session_window_start"] = start
|
|
}
|
|
if end != nil {
|
|
updates["session_window_end"] = end
|
|
}
|
|
return r.db.WithContext(ctx).Model(&accountModel{}).Where("id = ?", id).Updates(updates).Error
|
|
}
|
|
|
|
func (r *accountRepository) SetSchedulable(ctx context.Context, id int64, schedulable bool) error {
|
|
return r.db.WithContext(ctx).Model(&accountModel{}).Where("id = ?", id).
|
|
Update("schedulable", schedulable).Error
|
|
}
|
|
|
|
func (r *accountRepository) UpdateExtra(ctx context.Context, id int64, updates map[string]any) error {
|
|
if len(updates) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var account accountModel
|
|
if err := r.db.WithContext(ctx).Select("extra").Where("id = ?", id).First(&account).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
if account.Extra == nil {
|
|
account.Extra = datatypes.JSONMap{}
|
|
}
|
|
for k, v := range updates {
|
|
account.Extra[k] = v
|
|
}
|
|
|
|
return r.db.WithContext(ctx).Model(&accountModel{}).Where("id = ?", id).
|
|
Update("extra", account.Extra).Error
|
|
}
|
|
|
|
func (r *accountRepository) BulkUpdate(ctx context.Context, ids []int64, updates service.AccountBulkUpdate) (int64, error) {
|
|
if len(ids) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
updateMap := map[string]any{}
|
|
|
|
if updates.Name != nil {
|
|
updateMap["name"] = *updates.Name
|
|
}
|
|
if updates.ProxyID != nil {
|
|
updateMap["proxy_id"] = updates.ProxyID
|
|
}
|
|
if updates.Concurrency != nil {
|
|
updateMap["concurrency"] = *updates.Concurrency
|
|
}
|
|
if updates.Priority != nil {
|
|
updateMap["priority"] = *updates.Priority
|
|
}
|
|
if updates.Status != nil {
|
|
updateMap["status"] = *updates.Status
|
|
}
|
|
if len(updates.Credentials) > 0 {
|
|
updateMap["credentials"] = gorm.Expr("COALESCE(credentials,'{}') || ?", datatypes.JSONMap(updates.Credentials))
|
|
}
|
|
if len(updates.Extra) > 0 {
|
|
updateMap["extra"] = gorm.Expr("COALESCE(extra,'{}') || ?", datatypes.JSONMap(updates.Extra))
|
|
}
|
|
|
|
if len(updateMap) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
result := r.db.WithContext(ctx).
|
|
Model(&accountModel{}).
|
|
Where("id IN ?", ids).
|
|
Clauses(clause.Returning{}).
|
|
Updates(updateMap)
|
|
|
|
return result.RowsAffected, result.Error
|
|
}
|
|
|
|
type accountModel struct {
|
|
ID int64 `gorm:"primaryKey"`
|
|
Name string `gorm:"size:100;not null"`
|
|
Platform string `gorm:"size:50;not null"`
|
|
Type string `gorm:"size:20;not null"`
|
|
Credentials datatypes.JSONMap `gorm:"type:jsonb;default:'{}'"`
|
|
Extra datatypes.JSONMap `gorm:"type:jsonb;default:'{}'"`
|
|
ProxyID *int64 `gorm:"index"`
|
|
Concurrency int `gorm:"default:3;not null"`
|
|
Priority int `gorm:"default:50;not null"`
|
|
Status string `gorm:"size:20;default:active;not null"`
|
|
ErrorMessage string `gorm:"type:text"`
|
|
LastUsedAt *time.Time `gorm:"index"`
|
|
CreatedAt time.Time `gorm:"not null"`
|
|
UpdatedAt time.Time `gorm:"not null"`
|
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
|
|
Schedulable bool `gorm:"default:true;not null"`
|
|
|
|
RateLimitedAt *time.Time `gorm:"index"`
|
|
RateLimitResetAt *time.Time `gorm:"index"`
|
|
OverloadUntil *time.Time `gorm:"index"`
|
|
|
|
SessionWindowStart *time.Time
|
|
SessionWindowEnd *time.Time
|
|
SessionWindowStatus string `gorm:"size:20"`
|
|
|
|
Proxy *proxyModel `gorm:"foreignKey:ProxyID"`
|
|
AccountGroups []accountGroupModel `gorm:"foreignKey:AccountID"`
|
|
}
|
|
|
|
func (accountModel) TableName() string { return "accounts" }
|
|
|
|
type accountGroupModel struct {
|
|
AccountID int64 `gorm:"primaryKey"`
|
|
GroupID int64 `gorm:"primaryKey"`
|
|
Priority int `gorm:"default:50;not null"`
|
|
CreatedAt time.Time `gorm:"not null"`
|
|
|
|
Account *accountModel `gorm:"foreignKey:AccountID"`
|
|
Group *groupModel `gorm:"foreignKey:GroupID"`
|
|
}
|
|
|
|
func (accountGroupModel) TableName() string { return "account_groups" }
|
|
|
|
func accountGroupModelToService(m *accountGroupModel) *service.AccountGroup {
|
|
if m == nil {
|
|
return nil
|
|
}
|
|
return &service.AccountGroup{
|
|
AccountID: m.AccountID,
|
|
GroupID: m.GroupID,
|
|
Priority: m.Priority,
|
|
CreatedAt: m.CreatedAt,
|
|
Account: accountModelToService(m.Account),
|
|
Group: groupModelToService(m.Group),
|
|
}
|
|
}
|
|
|
|
func accountModelToService(m *accountModel) *service.Account {
|
|
if m == nil {
|
|
return nil
|
|
}
|
|
|
|
var credentials map[string]any
|
|
if m.Credentials != nil {
|
|
credentials = map[string]any(m.Credentials)
|
|
}
|
|
|
|
var extra map[string]any
|
|
if m.Extra != nil {
|
|
extra = map[string]any(m.Extra)
|
|
}
|
|
|
|
account := &service.Account{
|
|
ID: m.ID,
|
|
Name: m.Name,
|
|
Platform: m.Platform,
|
|
Type: m.Type,
|
|
Credentials: credentials,
|
|
Extra: extra,
|
|
ProxyID: m.ProxyID,
|
|
Concurrency: m.Concurrency,
|
|
Priority: m.Priority,
|
|
Status: m.Status,
|
|
ErrorMessage: m.ErrorMessage,
|
|
LastUsedAt: m.LastUsedAt,
|
|
CreatedAt: m.CreatedAt,
|
|
UpdatedAt: m.UpdatedAt,
|
|
Schedulable: m.Schedulable,
|
|
RateLimitedAt: m.RateLimitedAt,
|
|
RateLimitResetAt: m.RateLimitResetAt,
|
|
OverloadUntil: m.OverloadUntil,
|
|
SessionWindowStart: m.SessionWindowStart,
|
|
SessionWindowEnd: m.SessionWindowEnd,
|
|
SessionWindowStatus: m.SessionWindowStatus,
|
|
Proxy: proxyModelToService(m.Proxy),
|
|
}
|
|
|
|
if len(m.AccountGroups) > 0 {
|
|
account.AccountGroups = make([]service.AccountGroup, 0, len(m.AccountGroups))
|
|
account.GroupIDs = make([]int64, 0, len(m.AccountGroups))
|
|
account.Groups = make([]*service.Group, 0, len(m.AccountGroups))
|
|
for i := range m.AccountGroups {
|
|
ag := accountGroupModelToService(&m.AccountGroups[i])
|
|
if ag == nil {
|
|
continue
|
|
}
|
|
account.AccountGroups = append(account.AccountGroups, *ag)
|
|
account.GroupIDs = append(account.GroupIDs, ag.GroupID)
|
|
if ag.Group != nil {
|
|
account.Groups = append(account.Groups, ag.Group)
|
|
}
|
|
}
|
|
}
|
|
|
|
return account
|
|
}
|
|
|
|
func accountModelFromService(a *service.Account) *accountModel {
|
|
if a == nil {
|
|
return nil
|
|
}
|
|
|
|
var credentials datatypes.JSONMap
|
|
if a.Credentials != nil {
|
|
credentials = datatypes.JSONMap(a.Credentials)
|
|
}
|
|
|
|
var extra datatypes.JSONMap
|
|
if a.Extra != nil {
|
|
extra = datatypes.JSONMap(a.Extra)
|
|
}
|
|
|
|
return &accountModel{
|
|
ID: a.ID,
|
|
Name: a.Name,
|
|
Platform: a.Platform,
|
|
Type: a.Type,
|
|
Credentials: credentials,
|
|
Extra: extra,
|
|
ProxyID: a.ProxyID,
|
|
Concurrency: a.Concurrency,
|
|
Priority: a.Priority,
|
|
Status: a.Status,
|
|
ErrorMessage: a.ErrorMessage,
|
|
LastUsedAt: a.LastUsedAt,
|
|
CreatedAt: a.CreatedAt,
|
|
UpdatedAt: a.UpdatedAt,
|
|
Schedulable: a.Schedulable,
|
|
RateLimitedAt: a.RateLimitedAt,
|
|
RateLimitResetAt: a.RateLimitResetAt,
|
|
OverloadUntil: a.OverloadUntil,
|
|
SessionWindowStart: a.SessionWindowStart,
|
|
SessionWindowEnd: a.SessionWindowEnd,
|
|
SessionWindowStatus: a.SessionWindowStatus,
|
|
}
|
|
}
|
|
|
|
func applyAccountModelToService(account *service.Account, m *accountModel) {
|
|
if account == nil || m == nil {
|
|
return
|
|
}
|
|
account.ID = m.ID
|
|
account.CreatedAt = m.CreatedAt
|
|
account.UpdatedAt = m.UpdatedAt
|
|
}
|