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:
@@ -42,7 +42,8 @@ func (s *order) ListUserOrders(ctx context.Context, status string) ([]user_dto.O
|
||||
|
||||
var data []user_dto.Order
|
||||
for _, v := range list {
|
||||
data = append(data, s.toUserOrderDTO(v))
|
||||
dto, _ := s.composeOrderDTO(ctx, v)
|
||||
data = append(data, dto)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
@@ -64,7 +65,10 @@ func (s *order) GetUserOrder(ctx context.Context, id string) (*user_dto.Order, e
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
dto := s.toUserOrderDTO(item)
|
||||
dto, err := s.composeOrderDTO(ctx, item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto, nil
|
||||
}
|
||||
|
||||
@@ -87,6 +91,8 @@ func (s *order) Create(ctx context.Context, form *transaction_dto.OrderCreateFor
|
||||
|
||||
price, err := models.ContentPriceQuery.WithContext(ctx).Where(models.ContentPriceQuery.ContentID.Eq(cid)).First()
|
||||
if err != nil {
|
||||
// If price missing, treat as error? Or maybe 0?
|
||||
// Better to require price record.
|
||||
return nil, errorx.ErrDataCorrupted.WithCause(err).WithMsg("价格信息缺失")
|
||||
}
|
||||
|
||||
@@ -96,12 +102,12 @@ func (s *order) Create(ctx context.Context, form *transaction_dto.OrderCreateFor
|
||||
UserID: uid,
|
||||
Type: consts.OrderTypeContentPurchase,
|
||||
Status: consts.OrderStatusCreated,
|
||||
Currency: consts.Currency(price.Currency), // price.Currency is consts.Currency in DB? Yes.
|
||||
Currency: consts.Currency(price.Currency),
|
||||
AmountOriginal: price.PriceAmount,
|
||||
AmountDiscount: 0, // Calculate discount if needed
|
||||
AmountPaid: price.PriceAmount, // Expected to pay
|
||||
IdempotencyKey: uuid.NewString(), // Should be from client ideally
|
||||
Snapshot: types.NewJSONType(fields.OrdersSnapshot{}), // Populate details
|
||||
AmountDiscount: 0,
|
||||
AmountPaid: price.PriceAmount,
|
||||
IdempotencyKey: uuid.NewString(),
|
||||
Snapshot: types.NewJSONType(fields.OrdersSnapshot{}),
|
||||
}
|
||||
|
||||
if err := models.OrderQuery.WithContext(ctx).Create(order); err != nil {
|
||||
@@ -147,13 +153,13 @@ func (s *order) Pay(ctx context.Context, id string, form *transaction_dto.OrderP
|
||||
return s.payWithBalance(ctx, o)
|
||||
}
|
||||
|
||||
// External payment (mock)
|
||||
return &transaction_dto.OrderPayResponse{
|
||||
PayParams: "mock_pay_params",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transaction_dto.OrderPayResponse, error) {
|
||||
var tenantOwnerID int64
|
||||
err := models.Q.Transaction(func(tx *models.Query) error {
|
||||
// 1. Deduct User Balance
|
||||
info, err := tx.User.WithContext(ctx).
|
||||
@@ -179,6 +185,12 @@ func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transacti
|
||||
// 3. Grant Content Access
|
||||
items, _ := tx.OrderItem.WithContext(ctx).Where(tx.OrderItem.OrderID.Eq(o.ID)).Find()
|
||||
for _, item := range items {
|
||||
// Check if access already exists (idempotency)
|
||||
exists, _ := tx.ContentAccess.WithContext(ctx).Where(tx.ContentAccess.UserID.Eq(o.UserID), tx.ContentAccess.ContentID.Eq(item.ContentID)).Exists()
|
||||
if exists {
|
||||
continue
|
||||
}
|
||||
|
||||
access := &models.ContentAccess{
|
||||
TenantID: item.TenantID,
|
||||
UserID: o.UserID,
|
||||
@@ -196,6 +208,7 @@ func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transacti
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tenantOwnerID = t.UserID
|
||||
|
||||
ledger := &models.TenantLedger{
|
||||
TenantID: o.TenantID,
|
||||
@@ -224,16 +237,74 @@ func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transacti
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
if Notification != nil {
|
||||
_ = Notification.Send(ctx, o.UserID, "order", "支付成功", "订单已支付,您可以查看已购内容。")
|
||||
if tenantOwnerID > 0 {
|
||||
_ = Notification.Send(ctx, tenantOwnerID, "order", "新的订单", "您的店铺有新的订单,收入已入账。")
|
||||
}
|
||||
}
|
||||
|
||||
return &transaction_dto.OrderPayResponse{
|
||||
PayParams: "balance_paid",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *order) Status(ctx context.Context, id string) (*transaction_dto.OrderStatusResponse, error) {
|
||||
// ... check status ...
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *order) composeOrderDTO(ctx context.Context, o *models.Order) (user_dto.Order, error) {
|
||||
dto := user_dto.Order{
|
||||
ID: cast.ToString(o.ID),
|
||||
Status: string(o.Status),
|
||||
Amount: float64(o.AmountPaid) / 100.0,
|
||||
CreateTime: o.CreatedAt.Format(time.RFC3339),
|
||||
TenantID: cast.ToString(o.TenantID),
|
||||
}
|
||||
|
||||
// Fetch Tenant Name
|
||||
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(o.TenantID)).First()
|
||||
if err == nil {
|
||||
dto.TenantName = t.Name
|
||||
}
|
||||
|
||||
// Fetch Items
|
||||
items, err := models.OrderItemQuery.WithContext(ctx).Where(models.OrderItemQuery.OrderID.Eq(o.ID)).Find()
|
||||
if err == nil {
|
||||
dto.Quantity = len(items)
|
||||
for _, item := range items {
|
||||
// Fetch Content
|
||||
var c models.Content
|
||||
err := models.ContentQuery.WithContext(ctx).
|
||||
Where(models.ContentQuery.ID.Eq(item.ContentID)).
|
||||
UnderlyingDB().
|
||||
Preload("ContentAssets").
|
||||
Preload("ContentAssets.Asset").
|
||||
First(&c).Error
|
||||
|
||||
if err == nil {
|
||||
ci := transaction_dto.ContentItem{
|
||||
ID: cast.ToString(c.ID),
|
||||
Title: c.Title,
|
||||
Genre: c.Genre,
|
||||
AuthorID: cast.ToString(c.UserID),
|
||||
Price: float64(item.AmountPaid) / 100.0, // Use actual paid amount
|
||||
}
|
||||
// Cover logic (simplified from content service)
|
||||
for _, asset := range c.ContentAssets {
|
||||
if asset.Role == consts.ContentAssetRoleCover && asset.Asset != nil {
|
||||
ci.Cover = Common.GetAssetURL(asset.Asset.ObjectKey)
|
||||
break
|
||||
}
|
||||
}
|
||||
dto.Items = append(dto.Items, ci)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dto, nil
|
||||
}
|
||||
|
||||
func (s *order) toUserOrderDTO(o *models.Order) user_dto.Order {
|
||||
return user_dto.Order{
|
||||
ID: cast.ToString(o.ID),
|
||||
|
||||
Reference in New Issue
Block a user