feat: Implement notification service and integrate with user interactions

- Added notification service to handle sending and listing notifications.
- Integrated notification sending on user follow and order payment events.
- Updated user service to include fetching followed tenants.
- Enhanced content service to manage access control for content assets.
- Implemented logic for listing content topics based on genre.
- Updated creator service to manage content updates and pricing.
- Improved order service to include detailed order information and notifications.
- Added tests for notification CRUD operations and order details.
This commit is contained in:
2025-12-30 09:57:12 +08:00
parent 9ef9642965
commit 5cf2295f91
14 changed files with 741 additions and 52 deletions

View File

@@ -67,14 +67,28 @@ func (s *creator) Dashboard(ctx context.Context) (*creator_dto.DashboardStats, e
return nil, err
}
// Mock stats for now or query
// Followers: count tenant_users
followers, _ := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(tid)).Count()
// Revenue: sum tenant_ledgers (income)
var revenue float64
// GORM doesn't have a direct Sum method in Gen yet easily accessible without raw SQL or result mapping
// But we can use underlying DB
models.TenantLedgerQuery.WithContext(ctx).UnderlyingDB().
Model(&models.TenantLedger{}).
Where("tenant_id = ? AND type = ?", tid, consts.TenantLedgerTypeDebitPurchase).
Select("COALESCE(SUM(amount), 0)").
Scan(&revenue)
// Pending Refunds: count orders in refunding
pendingRefunds, _ := models.OrderQuery.WithContext(ctx).
Where(models.OrderQuery.TenantID.Eq(tid), models.OrderQuery.Status.Eq(consts.OrderStatusRefunding)).
Count()
stats := &creator_dto.DashboardStats{
TotalFollowers: creator_dto.IntStatItem{Value: int(followers)},
TotalRevenue: creator_dto.FloatStatItem{Value: 0},
PendingRefunds: 0,
TotalRevenue: creator_dto.FloatStatItem{Value: revenue / 100.0},
PendingRefunds: int(pendingRefunds),
NewMessages: 0,
}
return stats, nil
@@ -173,7 +187,73 @@ func (s *creator) CreateContent(ctx context.Context, form *creator_dto.ContentCr
}
func (s *creator) UpdateContent(ctx context.Context, id string, form *creator_dto.ContentUpdateForm) error {
return nil
tid, err := s.getTenantID(ctx)
if err != nil {
return err
}
cid := cast.ToInt64(id)
uid := cast.ToInt64(ctx.Value(consts.CtxKeyUser))
return models.Q.Transaction(func(tx *models.Query) error {
// 1. Check Ownership
c, err := tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(cid), tx.Content.TenantID.Eq(tid)).First()
if err != nil {
return errorx.ErrRecordNotFound
}
// 2. Update Content
_, err = tx.Content.WithContext(ctx).Where(tx.Content.ID.Eq(cid)).Updates(&models.Content{
Title: form.Title,
Genre: form.Genre,
})
if err != nil {
return err
}
// 3. Update Price
// Check if price exists
count, _ := tx.ContentPrice.WithContext(ctx).Where(tx.ContentPrice.ContentID.Eq(cid)).Count()
newPrice := int64(form.Price * 100)
if count > 0 {
_, err = tx.ContentPrice.WithContext(ctx).Where(tx.ContentPrice.ContentID.Eq(cid)).UpdateSimple(tx.ContentPrice.PriceAmount.Value(newPrice))
} else {
err = tx.ContentPrice.WithContext(ctx).Create(&models.ContentPrice{
TenantID: tid,
UserID: c.UserID,
ContentID: cid,
PriceAmount: newPrice,
Currency: consts.CurrencyCNY,
})
}
if err != nil {
return err
}
// 4. Update Assets (Full replacement strategy)
if len(form.MediaIDs) > 0 {
_, err = tx.ContentAsset.WithContext(ctx).Where(tx.ContentAsset.ContentID.Eq(cid)).Delete()
if err != nil {
return err
}
var assets []*models.ContentAsset
for i, mid := range form.MediaIDs {
assets = append(assets, &models.ContentAsset{
TenantID: tid,
UserID: uid,
ContentID: cid,
AssetID: cast.ToInt64(mid),
Sort: int32(i),
Role: consts.ContentAssetRoleMain, // Default to main
})
}
if err := tx.ContentAsset.WithContext(ctx).Create(assets...); err != nil {
return err
}
}
return nil
})
}
func (s *creator) DeleteContent(ctx context.Context, id string) error {