From d6550e9e1ad3872b618e247e8389be9bdae47641 Mon Sep 17 00:00:00 2001 From: Rogee Date: Sun, 8 Feb 2026 18:29:28 +0800 Subject: [PATCH] feat: expand seed data with diverse test entities Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus --- backend/app/commands/seed/seed.go | 602 ++++++++++++++++++++++++++++++ 1 file changed, 602 insertions(+) diff --git a/backend/app/commands/seed/seed.go b/backend/app/commands/seed/seed.go index d4f20d0..cf1e5f1 100644 --- a/backend/app/commands/seed/seed.go +++ b/backend/app/commands/seed/seed.go @@ -60,6 +60,7 @@ func Serve(_ *cobra.Command, _ []string) error { "system_configs", "notification_templates", "notifications", + "recharge_codes", "tenant_invites", "tenant_join_requests", "content_access", @@ -596,6 +597,607 @@ func Serve(_ *cobra.Command, _ []string) error { fmt.Printf("Create negative user failed: %v\n", err) } + reviewUser := &models.User{ + Username: "review_user", + Phone: "13800009997", + Nickname: "待审核用户", + Avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=ReviewUser", + Balance: 1200, + Status: consts.UserStatusPendingVerify, + Roles: types.Array[consts.Role]{consts.RoleUser}, + } + if err := models.UserQuery.WithContext(ctx).Create(reviewUser); err != nil { + fmt.Printf("Create review user failed: %v\n", err) + } + + bannedUser := &models.User{ + Username: "banned_user", + Phone: "13800009996", + Nickname: "封禁用户", + Avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=BannedUser", + Balance: 200, + Status: consts.UserStatusBanned, + Roles: types.Array[consts.Role]{consts.RoleUser}, + } + if err := models.UserQuery.WithContext(ctx).Create(bannedUser); err != nil { + fmt.Printf("Create banned user failed: %v\n", err) + } + + inactiveUser := &models.User{ + Username: "inactive_user", + Phone: "13800009995", + Nickname: "停用用户", + Avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=InactiveUser", + Balance: 300, + Status: consts.UserStatusInactive, + Roles: types.Array[consts.Role]{consts.RoleUser}, + } + if err := models.UserQuery.WithContext(ctx).Create(inactiveUser); err != nil { + fmt.Printf("Create inactive user failed: %v\n", err) + } + + creatorApplicant := &models.User{ + Username: "creator_apply_user", + Phone: "13800009994", + Nickname: "申请创作者用户", + Avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=CreatorApplyUser", + Balance: 888, + Status: consts.UserStatusVerified, + Roles: types.Array[consts.Role]{consts.RoleUser, consts.RoleCreator}, + } + if err := models.UserQuery.WithContext(ctx).Create(creatorApplicant); err != nil { + fmt.Printf("Create creator applicant user failed: %v\n", err) + } + + if reviewUser != nil { + if err := models.TenantUserQuery.WithContext(ctx).Create(&models.TenantUser{ + TenantID: tenant.ID, + UserID: reviewUser.ID, + Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember}, + Status: consts.UserStatusPendingVerify, + }); err != nil { + fmt.Printf("Create pending tenant user failed: %v\n", err) + } + } + if bannedUser != nil { + if err := models.TenantUserQuery.WithContext(ctx).Create(&models.TenantUser{ + TenantID: tenant.ID, + UserID: bannedUser.ID, + Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember}, + Status: consts.UserStatusBanned, + }); err != nil { + fmt.Printf("Create banned tenant user failed: %v\n", err) + } + } + + emptyTenant := &models.Tenant{ + UserID: creator.ID, + Name: "空态租户", + Code: "empty_" + tenantCodeSuffix, + UUID: types.UUID(uuid.New()), + Status: consts.TenantStatusVerified, + } + if err := models.TenantQuery.WithContext(ctx).Create(emptyTenant); err != nil { + fmt.Printf("Create empty tenant failed: %v\n", err) + } + + pendingTenant := &models.Tenant{ + UserID: creator.ID, + Name: "待审核租户", + Code: "pending_" + tenantCodeSuffix, + UUID: types.UUID(uuid.New()), + Status: consts.TenantStatusPendingVerify, + } + if err := models.TenantQuery.WithContext(ctx).Create(pendingTenant); err != nil { + fmt.Printf("Create pending tenant failed: %v\n", err) + } + + bannedTenant := &models.Tenant{ + UserID: creator.ID, + Name: "封禁租户", + Code: "banned_" + tenantCodeSuffix, + UUID: types.UUID(uuid.New()), + Status: consts.TenantStatusBanned, + } + if err := models.TenantQuery.WithContext(ctx).Create(bannedTenant); err != nil { + fmt.Printf("Create banned tenant failed: %v\n", err) + } + + if creatorApplicant != nil { + if err := models.TenantJoinRequestQuery.WithContext(ctx).Create(&models.TenantJoinRequest{ + TenantID: pendingTenant.ID, + UserID: creatorApplicant.ID, + Status: string(consts.TenantJoinRequestStatusRejected), + Reason: "资料不完整", + DecidedAt: time.Now().Add(-30 * time.Minute), + DecidedOperatorUserID: creator.ID, + DecidedReason: "资料不完整,驳回", + }); err != nil { + fmt.Printf("Create rejected join request failed: %v\n", err) + } + } + + disabledInviteSuffix, err := randomIntWithLimit(100000) + if err != nil { + return fmt.Errorf("generate disabled invite code: %w", err) + } + if err := models.TenantInviteQuery.WithContext(ctx).Create(&models.TenantInvite{ + TenantID: tenant.ID, + UserID: creator.ID, + Code: "disabled" + cast.ToString(disabledInviteSuffix), + Status: string(consts.TenantInviteStatusDisabled), + MaxUses: 3, + UsedCount: 1, + DisabledAt: time.Now().Add(-2 * time.Hour), + DisabledOperatorUserID: superAdmin.ID, + Remark: "seed disabled invite", + ExpiresAt: time.Now().Add(3 * 24 * time.Hour), + }); err != nil { + fmt.Printf("Create disabled invite failed: %v\n", err) + } + + expiredInviteSuffix, err := randomIntWithLimit(100000) + if err != nil { + return fmt.Errorf("generate expired invite code: %w", err) + } + if err := models.TenantInviteQuery.WithContext(ctx).Create(&models.TenantInvite{ + TenantID: tenant.ID, + UserID: creator.ID, + Code: "expired" + cast.ToString(expiredInviteSuffix), + Status: string(consts.TenantInviteStatusExpired), + MaxUses: 2, + UsedCount: 2, + Remark: "seed expired invite", + ExpiresAt: time.Now().Add(-2 * time.Hour), + }); err != nil { + fmt.Printf("Create expired invite failed: %v\n", err) + } + + draftContent := &models.Content{ + TenantID: tenant.ID, + UserID: creator.ID, + Title: "草稿内容(待完善)", + Description: "用于测试草稿态按钮与筛选", + Genre: "京剧", + Status: consts.ContentStatusDraft, + Visibility: consts.ContentVisibilityPrivate, + Views: 0, + Likes: 0, + } + if err := models.ContentQuery.WithContext(ctx).Create(draftContent); err != nil { + fmt.Printf("Create draft content failed: %v\n", err) + } + + reviewingContent := &models.Content{ + TenantID: tenant.ID, + UserID: creator.ID, + Title: "审核中内容(待审核)", + Description: "用于测试审核流操作", + Genre: "越剧", + Status: consts.ContentStatusReviewing, + Visibility: consts.ContentVisibilityTenantOnly, + Views: 18, + Likes: 2, + } + if err := models.ContentQuery.WithContext(ctx).Create(reviewingContent); err != nil { + fmt.Printf("Create reviewing content failed: %v\n", err) + } + + unpublishedContent := &models.Content{ + TenantID: tenant.ID, + UserID: creator.ID, + Title: "下架内容(可重发)", + Description: "用于测试下架态展示", + Genre: "黄梅戏", + Status: consts.ContentStatusUnpublished, + Visibility: consts.ContentVisibilityTenantOnly, + Views: 87, + Likes: 9, + } + if err := models.ContentQuery.WithContext(ctx).Create(unpublishedContent); err != nil { + fmt.Printf("Create unpublished content failed: %v\n", err) + } + + blockedContent := &models.Content{ + TenantID: tenant.ID, + UserID: creator.ID, + Title: "封禁内容(违规)", + Description: "用于测试封禁态展示", + Genre: "昆曲", + Status: consts.ContentStatusBlocked, + Visibility: consts.ContentVisibilityPrivate, + Views: 120, + Likes: 0, + } + if err := models.ContentQuery.WithContext(ctx).Create(blockedContent); err != nil { + fmt.Printf("Create blocked content failed: %v\n", err) + } + + if reviewingContent != nil { + if err := models.ContentReportQuery.WithContext(ctx).Create(&models.ContentReport{ + TenantID: tenant.ID, + ContentID: reviewingContent.ID, + ReporterID: buyer.ID, + Reason: "quality", + Detail: "音画不同步", + Status: string(consts.TenantJoinRequestStatusApproved), + HandledBy: superAdmin.ID, + HandledAction: "approve", + HandledReason: "确认问题,已处理", + HandledAt: time.Now().Add(-90 * time.Minute), + }); err != nil { + fmt.Printf("Create approved content report failed: %v\n", err) + } + } + if blockedContent != nil { + if err := models.ContentReportQuery.WithContext(ctx).Create(&models.ContentReport{ + TenantID: tenant.ID, + ContentID: blockedContent.ID, + ReporterID: buyer.ID, + Reason: "abuse", + Detail: "标题涉嫌误导", + Status: string(consts.TenantJoinRequestStatusRejected), + HandledBy: superAdmin.ID, + HandledAction: "reject", + HandledReason: "证据不足,驳回", + HandledAt: time.Now().Add(-45 * time.Minute), + }); err != nil { + fmt.Printf("Create rejected content report failed: %v\n", err) + } + } + + processingAsset := &models.MediaAsset{ + TenantID: tenant.ID, + UserID: creator.ID, + Type: consts.MediaAssetTypeVideo, + Status: consts.MediaAssetStatusProcessing, + Provider: "mock", + ObjectKey: "seed/video-processing.mp4", + Meta: types.NewJSONType(fields.MediaAssetMeta{ + Size: 4096, + }), + UpdatedAt: time.Now().Add(-8 * time.Hour), + } + if err := models.MediaAssetQuery.WithContext(ctx).Create(processingAsset); err != nil { + fmt.Printf("Create processing asset failed: %v\n", err) + } + + failedAsset := &models.MediaAsset{ + TenantID: tenant.ID, + UserID: creator.ID, + Type: consts.MediaAssetTypeVideo, + Status: consts.MediaAssetStatusFailed, + Provider: "mock", + ObjectKey: "seed/video-failed.mp4", + Meta: types.NewJSONType(fields.MediaAssetMeta{ + Size: 5120, + }), + } + if err := models.MediaAssetQuery.WithContext(ctx).Create(failedAsset); err != nil { + fmt.Printf("Create failed asset failed: %v\n", err) + } + + expiredCoupon := &models.Coupon{ + TenantID: tenant.ID, + Title: "过期券", + Type: consts.CouponTypeFixAmount, + Value: 200, + MinOrderAmount: 500, + TotalQuantity: 2, + UsedQuantity: 2, + StartAt: time.Now().Add(-10 * 24 * time.Hour), + EndAt: time.Now().Add(-24 * time.Hour), + } + if err := models.CouponQuery.WithContext(ctx).Create(expiredCoupon); err != nil { + fmt.Printf("Create expired coupon failed: %v\n", err) + } + + upcomingCoupon := &models.Coupon{ + TenantID: tenant.ID, + Title: "未生效券", + Type: consts.CouponTypeDiscount, + Value: 85, + MaxDiscount: 300, + MinOrderAmount: 1000, + TotalQuantity: 50, + UsedQuantity: 0, + StartAt: time.Now().Add(48 * time.Hour), + EndAt: time.Now().Add(30 * 24 * time.Hour), + } + if err := models.CouponQuery.WithContext(ctx).Create(upcomingCoupon); err != nil { + fmt.Printf("Create upcoming coupon failed: %v\n", err) + } + + exhaustedCoupon := &models.Coupon{ + TenantID: tenant.ID, + Title: "已领完券", + Type: consts.CouponTypeFixAmount, + Value: 300, + MinOrderAmount: 600, + TotalQuantity: 1, + UsedQuantity: 1, + StartAt: time.Now().Add(-24 * time.Hour), + EndAt: time.Now().Add(3 * 24 * time.Hour), + } + if err := models.CouponQuery.WithContext(ctx).Create(exhaustedCoupon); err != nil { + fmt.Printf("Create exhausted coupon failed: %v\n", err) + } + + if expiredCoupon != nil { + if err := models.UserCouponQuery.WithContext(ctx).Create(&models.UserCoupon{ + UserID: buyer.ID, + CouponID: expiredCoupon.ID, + Status: consts.UserCouponStatusExpired, + }); err != nil { + fmt.Printf("Create expired user coupon failed: %v\n", err) + } + } + if cp1 != nil { + if err := models.UserCouponQuery.WithContext(ctx).Create(&models.UserCoupon{ + UserID: buyer.ID, + CouponID: cp1.ID, + OrderID: 0, + Status: consts.UserCouponStatusUsed, + UsedAt: time.Now().Add(-3 * time.Hour), + }); err != nil { + fmt.Printf("Create used user coupon failed: %v\n", err) + } + } + + purchaseCreated := &models.Order{ + TenantID: tenant.ID, + UserID: buyer.ID, + Type: consts.OrderTypeContentPurchase, + Status: consts.OrderStatusCreated, + Currency: consts.CurrencyCNY, + AmountOriginal: 1100, + AmountDiscount: 0, + AmountPaid: 1100, + IdempotencyKey: uuid.NewString(), + } + if err := models.OrderQuery.WithContext(ctx).Create(purchaseCreated); err != nil { + fmt.Printf("Create created purchase order failed: %v\n", err) + } + + purchaseRefunding := &models.Order{ + TenantID: tenant.ID, + UserID: buyer.ID, + Type: consts.OrderTypeContentPurchase, + Status: consts.OrderStatusRefunding, + Currency: consts.CurrencyCNY, + AmountOriginal: 1300, + AmountDiscount: 100, + AmountPaid: 1200, + IdempotencyKey: uuid.NewString(), + PaidAt: time.Now().Add(-5 * time.Hour), + } + if err := models.OrderQuery.WithContext(ctx).Create(purchaseRefunding); err != nil { + fmt.Printf("Create refunding purchase order failed: %v\n", err) + } + + purchaseCanceled := &models.Order{ + TenantID: tenant.ID, + UserID: buyer.ID, + Type: consts.OrderTypeContentPurchase, + Status: consts.OrderStatusCanceled, + Currency: consts.CurrencyCNY, + AmountOriginal: 900, + AmountDiscount: 0, + AmountPaid: 0, + IdempotencyKey: uuid.NewString(), + } + if err := models.OrderQuery.WithContext(ctx).Create(purchaseCanceled); err != nil { + fmt.Printf("Create canceled purchase order failed: %v\n", err) + } + + purchaseFailed := &models.Order{ + TenantID: tenant.ID, + UserID: buyer.ID, + Type: consts.OrderTypeContentPurchase, + Status: consts.OrderStatusFailed, + Currency: consts.CurrencyCNY, + AmountOriginal: 1400, + AmountDiscount: 0, + AmountPaid: 1400, + IdempotencyKey: uuid.NewString(), + } + if err := models.OrderQuery.WithContext(ctx).Create(purchaseFailed); err != nil { + fmt.Printf("Create failed purchase order failed: %v\n", err) + } + + orderSeedContentID := int64(0) + if len(seededContents) > 1 { + orderSeedContentID = seededContents[1].ID + } + if orderSeedContentID > 0 { + if purchaseCreated != nil { + if err := models.OrderItemQuery.WithContext(ctx).Create(&models.OrderItem{ + TenantID: tenant.ID, + UserID: buyer.ID, + OrderID: purchaseCreated.ID, + ContentID: orderSeedContentID, + ContentUserID: creator.ID, + AmountPaid: purchaseCreated.AmountPaid, + }); err != nil { + fmt.Printf("Create order item for created order failed: %v\n", err) + } + } + if purchaseRefunding != nil { + if err := models.OrderItemQuery.WithContext(ctx).Create(&models.OrderItem{ + TenantID: tenant.ID, + UserID: buyer.ID, + OrderID: purchaseRefunding.ID, + ContentID: orderSeedContentID, + ContentUserID: creator.ID, + AmountPaid: purchaseRefunding.AmountPaid, + }); err != nil { + fmt.Printf("Create order item for refunding order failed: %v\n", err) + } + } + } + + if len(seededContents) > 2 { + if err := models.ContentAccessQuery.WithContext(ctx).Create(&models.ContentAccess{ + TenantID: tenant.ID, + UserID: buyer.ID, + ContentID: seededContents[2].ID, + OrderID: purchaseRefunding.ID, + Status: consts.ContentAccessStatusRevoked, + RevokedAt: time.Now().Add(-40 * time.Minute), + }); err != nil { + fmt.Printf("Create revoked content access failed: %v\n", err) + } + } + if len(seededContents) > 3 { + if err := models.ContentAccessQuery.WithContext(ctx).Create(&models.ContentAccess{ + TenantID: tenant.ID, + UserID: buyer.ID, + ContentID: seededContents[3].ID, + OrderID: purchaseRefunding.ID, + Status: consts.ContentAccessStatusExpired, + RevokedAt: time.Now().Add(-24 * time.Hour), + }); err != nil { + fmt.Printf("Create expired content access failed: %v\n", err) + } + } + + if err := models.PayoutAccountQuery.WithContext(ctx).Create(&models.PayoutAccount{ + TenantID: tenant.ID, + UserID: creator.ID, + Type: consts.PayoutAccountTypeBank, + Name: "招商银行", + Account: "6222000000000001", + Realname: "梅派传人小林", + Status: consts.PayoutAccountStatusPending, + }); err != nil { + fmt.Printf("Create pending payout account failed: %v\n", err) + } + + if err := models.PayoutAccountQuery.WithContext(ctx).Create(&models.PayoutAccount{ + TenantID: tenant.ID, + UserID: creator.ID, + Type: consts.PayoutAccountTypeAlipay, + Name: "支付宝(驳回样例)", + Account: "creator-rejected@example.com", + Realname: "梅派传人小林", + Status: consts.PayoutAccountStatusRejected, + ReviewedBy: superAdmin.ID, + ReviewedAt: time.Now().Add(-2 * time.Hour), + ReviewReason: "账户实名不匹配", + }); err != nil { + fmt.Printf("Create rejected payout account failed: %v\n", err) + } + + if err := models.NotificationQuery.WithContext(ctx).Create(&models.Notification{ + UserID: buyer.ID, + TenantID: tenant.ID, + Type: string(consts.NotificationTypeOrder), + Title: "订单支付成功", + Content: "您有一笔订单已支付成功。", + IsRead: true, + }); err != nil { + fmt.Printf("Create read notification failed: %v\n", err) + } + if err := models.NotificationQuery.WithContext(ctx).Create(&models.Notification{ + UserID: buyer.ID, + TenantID: tenant.ID, + Type: string(consts.NotificationTypeAudit), + Title: "内容审核提醒", + Content: "您提交的内容正在审核中。", + IsRead: false, + }); err != nil { + fmt.Printf("Create unread audit notification failed: %v\n", err) + } + + inactiveTemplate := &models.NotificationTemplate{ + TenantID: 0, + Name: "已停用模板", + Type: consts.NotificationTypeSystem, + Title: "停用模板", + Content: "该模板用于测试停用状态。", + IsActive: true, + } + if err := models.NotificationTemplateQuery.WithContext(ctx).Create(inactiveTemplate); err != nil { + fmt.Printf("Create inactive notification template failed: %v\n", err) + } else { + if _, err := models.NotificationTemplateQuery.WithContext(ctx). + Where(models.NotificationTemplateQuery.ID.Eq(inactiveTemplate.ID)). + UpdateSimple(models.NotificationTemplateQuery.IsActive.Value(false)); err != nil { + fmt.Printf("Update inactive notification template failed: %v\n", err) + } + } + + redeemedRechargeOrder := &models.Order{ + TenantID: 0, + UserID: buyer.ID, + Type: consts.OrderTypeRecharge, + Status: consts.OrderStatusPaid, + Currency: consts.CurrencyCNY, + AmountOriginal: 3000, + AmountDiscount: 0, + AmountPaid: 3000, + IdempotencyKey: uuid.NewString(), + PaidAt: time.Now().Add(-90 * time.Minute), + } + if err := models.OrderQuery.WithContext(ctx).Create(redeemedRechargeOrder); err != nil { + fmt.Printf("Create redeemed recharge order failed: %v\n", err) + } + + if err := models.RechargeCodeQuery.WithContext(ctx).Create(&models.RechargeCode{ + Code: "RC_ACTIVE_1000", + Amount: 100000, + Status: "active", + ActivatedBy: superAdmin.ID, + ActivatedAt: time.Now().Add(-30 * time.Minute), + Remark: "seed active recharge code", + }); err != nil { + fmt.Printf("Create active recharge code failed: %v\n", err) + } + + if err := models.RechargeCodeQuery.WithContext(ctx).Create(&models.RechargeCode{ + Code: "RC_REDEEMED_3000", + Amount: 300000, + Status: "redeemed", + ActivatedBy: superAdmin.ID, + ActivatedAt: time.Now().Add(-4 * time.Hour), + RedeemedBy: buyer.ID, + RedeemedAt: time.Now().Add(-2 * time.Hour), + RedeemedOrderID: redeemedRechargeOrder.ID, + Remark: "seed redeemed recharge code", + }); err != nil { + fmt.Printf("Create redeemed recharge code failed: %v\n", err) + } + + if err := models.RechargeCodeQuery.WithContext(ctx).Create(&models.RechargeCode{ + Code: "RC_INACTIVE_500", + Amount: 50000, + Status: "inactive", + ActivatedBy: 0, + Remark: "seed inactive recharge code", + }); err != nil { + fmt.Printf("Create inactive recharge code failed: %v\n", err) + } + + for i := 0; i < 12; i++ { + models.NotificationQuery.WithContext(ctx).Create(&models.Notification{ + UserID: buyer.ID, + TenantID: tenant.ID, + Type: string(consts.NotificationTypeSystem), + Title: fmt.Sprintf("系统通知 #%d", i+1), + Content: "分页测试通知", + IsRead: i%2 == 0, + }) + } + for i := 0; i < 12; i++ { + models.AuditLogQuery.WithContext(ctx).Create(&models.AuditLog{ + TenantID: tenant.ID, + OperatorID: superAdmin.ID, + Action: "seed_extra", + TargetID: fmt.Sprintf("extra:%d", i+1), + Detail: "extra audit log for pagination", + }) + } + fmt.Println("Seed done.") return nil