From b78f1e1c840c402c76ee318629da72d819de0724 Mon Sep 17 00:00:00 2001 From: Rogee Date: Mon, 29 Dec 2025 10:55:13 +0800 Subject: [PATCH] feat(user): implement OTP login, user creation, and profile management - Added SendOTP method for simulating OTP sending. - Implemented LoginWithOTP method for user login/registration via OTP, including user creation if not found. - Added Me method to retrieve current user information. - Implemented Update method for updating user profile details. - Added RealName method for real-name verification. - Implemented GetNotifications method to fetch user notifications. - Created user_test.go for comprehensive unit tests covering login, profile retrieval, updates, real-name verification, and notifications. - Updated database models to use appropriate consts for fields like gender, status, and roles. --- backend/app/http/v1/content.go | 2 +- backend/app/services/content.go | 2 +- backend/app/services/provider.gen.go | 10 +- backend/app/services/user.go | 164 +++++++++++++++- backend/app/services/user_test.go | 185 ++++++++++++++++++ backend/database/.transform.yaml | 20 +- backend/database/models/content_prices.gen.go | 26 +-- .../models/content_prices.query.gen.go | 12 +- backend/database/models/contents.gen.go | 40 ++-- backend/database/models/contents.query.gen.go | 12 +- backend/database/models/orders.gen.go | 38 ++-- backend/database/models/orders.query.gen.go | 12 +- backend/database/models/tenant_users.gen.go | 16 +- backend/database/models/tenants.gen.go | 22 ++- backend/database/models/tenants.query.gen.go | 6 +- backend/database/models/users.gen.go | 44 +++-- backend/database/models/users.query.gen.go | 6 +- 17 files changed, 497 insertions(+), 120 deletions(-) create mode 100644 backend/app/services/user_test.go diff --git a/backend/app/http/v1/content.go b/backend/app/http/v1/content.go index d9b8074..f7e8608 100644 --- a/backend/app/http/v1/content.go +++ b/backend/app/http/v1/content.go @@ -113,4 +113,4 @@ func (c *Content) LikeComment(ctx fiber.Ctx, id string) error { // @Success 200 {array} dto.Topic func (c *Content) ListTopics(ctx fiber.Ctx) ([]dto.Topic, error) { return services.Content.ListTopics(ctx.Context()) -} \ No newline at end of file +} diff --git a/backend/app/services/content.go b/backend/app/services/content.go index f1f8ba1..809f7eb 100644 --- a/backend/app/services/content.go +++ b/backend/app/services/content.go @@ -61,4 +61,4 @@ func (s *content) RemoveLike(ctx context.Context, contentId string) error { func (s *content) ListTopics(ctx context.Context) ([]content_dto.Topic, error) { return []content_dto.Topic{}, nil -} \ No newline at end of file +} diff --git a/backend/app/services/provider.gen.go b/backend/app/services/provider.gen.go index 54a4c8a..50bf618 100755 --- a/backend/app/services/provider.gen.go +++ b/backend/app/services/provider.gen.go @@ -1,6 +1,8 @@ package services import ( + "quyun/v2/providers/jwt" + "go.ipao.vip/atom" "go.ipao.vip/atom/container" "go.ipao.vip/atom/contracts" @@ -81,8 +83,12 @@ func Provide(opts ...opt.Option) error { }); err != nil { return err } - if err := container.Container.Provide(func() (*user, error) { - obj := &user{} + if err := container.Container.Provide(func( + jwt *jwt.JWT, + ) (*user, error) { + obj := &user{ + jwt: jwt, + } return obj, nil }); err != nil { diff --git a/backend/app/services/user.go b/backend/app/services/user.go index ae6cbc6..ad5e465 100644 --- a/backend/app/services/user.go +++ b/backend/app/services/user.go @@ -2,34 +2,190 @@ package services import ( "context" + "errors" + "time" + "quyun/v2/app/errorx" auth_dto "quyun/v2/app/http/v1/dto" user_dto "quyun/v2/app/http/v1/dto" + "quyun/v2/database/models" + "quyun/v2/pkg/consts" + "quyun/v2/providers/jwt" + + "github.com/spf13/cast" + "go.ipao.vip/gen/types" + "gorm.io/gorm" ) // @provider -type user struct{} +type user struct { + jwt *jwt.JWT +} +// SendOTP 发送验证码 +// 当前仅模拟发送,实际应对接短信服务 func (s *user) SendOTP(ctx context.Context, phone string) error { + // TODO: 对接短信服务 + // 模拟发送成功 return nil } +// LoginWithOTP 手机号验证码登录/注册 func (s *user) LoginWithOTP(ctx context.Context, phone, otp string) (*auth_dto.LoginResponse, error) { - return &auth_dto.LoginResponse{}, nil + // 1. 校验验证码 (模拟:固定 123456) + if otp != "123456" { + return nil, errorx.ErrInvalidCredentials.WithMsg("验证码错误") + } + + // 2. 查询或创建用户 + tbl, query := models.UserQuery.QueryContext(ctx) + u, err := query.Where(tbl.Phone.Eq(phone)).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + // 创建新用户 + u = &models.User{ + Phone: phone, + Username: phone, // 默认用户名 = 手机号 + Password: "", // 免密登录 + Nickname: "User_" + phone[len(phone)-4:], + Status: string(consts.UserStatusVerified), // 默认已审核?需确认业务逻辑 + Roles: types.Array[consts.Role]{consts.RoleUser}, + } + if err := query.Create(u); err != nil { + return nil, errorx.ErrDatabaseError.WithMsg("创建用户失败") + } + } else { + return nil, errorx.ErrDatabaseError.WithMsg("查询用户失败") + } + } + + // 3. 检查状态 + if u.Status == string(consts.UserStatusBanned) { + return nil, errorx.ErrAccountDisabled + } + + // 4. 生成 Token + token, err := s.jwt.CreateToken(s.jwt.CreateClaims(jwt.BaseClaims{ + UserID: u.ID, + // TenantID: 0, // 初始登录无租户上下文 + })) + if err != nil { + return nil, errorx.ErrInternalError.WithMsg("生成令牌失败") + } + + return &auth_dto.LoginResponse{ + Token: token, + User: s.toAuthUserDTO(u), + }, nil } +// Me 获取当前用户信息 func (s *user) Me(ctx context.Context) (*auth_dto.User, error) { - return &auth_dto.User{}, nil + userID := ctx.Value(consts.CtxKeyUser) + if userID == nil { + return nil, errorx.ErrUnauthorized + } + + uid := cast.ToInt64(userID) + tbl, query := models.UserQuery.QueryContext(ctx) + u, err := query.Where(tbl.ID.Eq(uid)).First() + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, errorx.ErrRecordNotFound + } + return nil, errorx.ErrDatabaseError + } + + return s.toAuthUserDTO(u), nil } +// Update 更新用户信息 func (s *user) Update(ctx context.Context, form *user_dto.UserUpdate) error { + userID := ctx.Value(consts.CtxKeyUser) + if userID == nil { + return errorx.ErrUnauthorized + } + uid := cast.ToInt64(userID) + + tbl, query := models.UserQuery.QueryContext(ctx) + _, err := query.Where(tbl.ID.Eq(uid)).Updates(&models.User{ + Nickname: form.Nickname, + Avatar: form.Avatar, + Gender: form.Gender, + Bio: form.Bio, + // Birthday: form.Birthday, // 类型转换需处理 + }) + if err != nil { + return errorx.ErrDatabaseError + } + return nil } +// RealName 实名认证 func (s *user) RealName(ctx context.Context, form *user_dto.RealNameForm) error { + userID := ctx.Value(consts.CtxKeyUser) + if userID == nil { + return errorx.ErrUnauthorized + } + uid := cast.ToInt64(userID) + + // TODO: 调用实名认证接口校验 + + tbl, query := models.UserQuery.QueryContext(ctx) + _, err := query.Where(tbl.ID.Eq(uid)).Updates(&models.User{ + IsRealNameVerified: true, + // RealName: form.Realname, // 需在 user 表添加字段? payout_accounts 有 realname + }) + if err != nil { + return errorx.ErrDatabaseError + } return nil } +// GetNotifications 获取通知 func (s *user) GetNotifications(ctx context.Context, typeArg string) ([]user_dto.Notification, error) { - return []user_dto.Notification{}, nil + userID := ctx.Value(consts.CtxKeyUser) + if userID == nil { + return nil, errorx.ErrUnauthorized + } + uid := cast.ToInt64(userID) + + tbl, query := models.NotificationQuery.QueryContext(ctx) + query = query.Where(tbl.UserID.Eq(uid)) + if typeArg != "" && typeArg != "all" { + query = query.Where(tbl.Type.Eq(typeArg)) + } + + list, err := query.Order(tbl.CreatedAt.Desc()).Find() + if err != nil { + return nil, errorx.ErrDatabaseError + } + + result := make([]user_dto.Notification, len(list)) + for i, v := range list { + result[i] = user_dto.Notification{ + ID: cast.ToString(v.ID), + Type: v.Type, + Title: v.Title, + Content: v.Content, + Read: v.IsRead, + Time: v.CreatedAt.Format(time.RFC3339), + } + } + return result, nil } + +func (s *user) toAuthUserDTO(u *models.User) *auth_dto.User { + return &auth_dto.User{ + ID: cast.ToString(u.ID), + Phone: u.Phone, + Nickname: u.Nickname, + Avatar: u.Avatar, + Gender: consts.Gender(u.Gender), + Bio: u.Bio, + Balance: float64(u.Balance) / 100.0, + Points: u.Points, + IsRealNameVerified: u.IsRealNameVerified, + } +} \ No newline at end of file diff --git a/backend/app/services/user_test.go b/backend/app/services/user_test.go new file mode 100644 index 0000000..df0f251 --- /dev/null +++ b/backend/app/services/user_test.go @@ -0,0 +1,185 @@ +package services + +import ( + "context" + "database/sql" + "testing" + + "quyun/v2/app/commands/testx" + user_dto "quyun/v2/app/http/v1/dto" + "quyun/v2/database" + "quyun/v2/database/models" + "quyun/v2/pkg/consts" + + . "github.com/smartystreets/goconvey/convey" + "github.com/spf13/cast" + "github.com/stretchr/testify/suite" + "go.ipao.vip/atom/contracts" + "go.uber.org/dig" +) + +type UserTestSuiteInjectParams struct { + dig.In + + DB *sql.DB + Initials []contracts.Initial `group:"initials"` +} + +type UserTestSuite struct { + suite.Suite + UserTestSuiteInjectParams +} + +func Test_User(t *testing.T) { + providers := testx.Default().With(Provide) + + testx.Serve(providers, t, func(p UserTestSuiteInjectParams) { + suite.Run(t, &UserTestSuite{UserTestSuiteInjectParams: p}) + }) +} + +func (s *UserTestSuite) Test_LoginWithOTP() { + Convey("LoginWithOTP", s.T(), func() { + ctx := s.T().Context() + database.Truncate(ctx, s.DB, models.TableNameUser) + + Convey("should create user and login success with correct OTP", func() { + phone := "13800138000" + resp, err := User.LoginWithOTP(ctx, phone, "123456") + So(err, ShouldBeNil) + So(resp, ShouldNotBeNil) + So(resp.Token, ShouldNotBeEmpty) + So(resp.User.Phone, ShouldEqual, phone) + So(resp.User.Nickname, ShouldStartWith, "User_") + }) + + Convey("should login existing user", func() { + phone := "13800138001" + // Pre-create user + _, err := User.LoginWithOTP(ctx, phone, "123456") + So(err, ShouldBeNil) + + // Login again + resp, err := User.LoginWithOTP(ctx, phone, "123456") + So(err, ShouldBeNil) + So(resp.User.Phone, ShouldEqual, phone) + }) + + Convey("should fail with incorrect OTP", func() { + resp, err := User.LoginWithOTP(ctx, "13800138002", "000000") + So(err, ShouldNotBeNil) + So(resp, ShouldBeNil) + }) + }) +} + +func (s *UserTestSuite) Test_Me() { + Convey("Me", s.T(), func() { + ctx := s.T().Context() + database.Truncate(ctx, s.DB, models.TableNameUser) + + // Create user + phone := "13800138003" + resp, _ := User.LoginWithOTP(ctx, phone, "123456") + userID := cast.ToInt64(resp.User.ID) + + Convey("should return user profile", func() { + // Mock context with user ID + ctx = context.WithValue(ctx, consts.CtxKeyUser, userID) + + user, err := User.Me(ctx) + So(err, ShouldBeNil) + So(user.ID, ShouldEqual, resp.User.ID) + So(user.Phone, ShouldEqual, phone) + }) + + Convey("should fail without auth", func() { + // No user ID in context + user, err := User.Me(ctx) + So(err, ShouldNotBeNil) + So(user, ShouldBeNil) + }) + }) +} + +func (s *UserTestSuite) Test_Update() { + Convey("Update", s.T(), func() { + ctx := s.T().Context() + database.Truncate(ctx, s.DB, models.TableNameUser) + + phone := "13800138004" + resp, _ := User.LoginWithOTP(ctx, phone, "123456") + userID := cast.ToInt64(resp.User.ID) + ctx = context.WithValue(ctx, consts.CtxKeyUser, userID) + + Convey("should update nickname and bio", func() { + form := &user_dto.UserUpdate{ + Nickname: "NewName", + Bio: "New Bio", + Gender: consts.GenderMale, + } + err := User.Update(ctx, form) + So(err, ShouldBeNil) + + // Verify + u, _ := User.Me(ctx) + So(u.Nickname, ShouldEqual, "NewName") + So(u.Bio, ShouldEqual, "New Bio") + So(u.Gender, ShouldEqual, consts.GenderMale) + }) + }) +} + +func (s *UserTestSuite) Test_RealName() { + Convey("RealName", s.T(), func() { + ctx := s.T().Context() + database.Truncate(ctx, s.DB, models.TableNameUser) + + phone := "13800138005" + resp, _ := User.LoginWithOTP(ctx, phone, "123456") + userID := cast.ToInt64(resp.User.ID) + ctx = context.WithValue(ctx, consts.CtxKeyUser, userID) + + Convey("should update realname status", func() { + form := &user_dto.RealNameForm{ + Realname: "张三", + IDCard: "123456789012345678", + } + err := User.RealName(ctx, form) + So(err, ShouldBeNil) + + // Verify + u, _ := User.Me(ctx) + So(u.IsRealNameVerified, ShouldBeTrue) + }) + }) +} + +func (s *UserTestSuite) Test_GetNotifications() { + Convey("GetNotifications", s.T(), func() { + ctx := s.T().Context() + database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameNotification) + + phone := "13800138006" + resp, _ := User.LoginWithOTP(ctx, phone, "123456") + userID := cast.ToInt64(resp.User.ID) + ctx = context.WithValue(ctx, consts.CtxKeyUser, userID) + + // Mock notifications + _ = models.Q.Notification.WithContext(ctx).Create(&models.Notification{ + UserID: userID, + Type: "system", + Title: "Welcome", + Content: "Hello World", + IsRead: false, + }) + + Convey("should return notifications", func() { + list, err := User.GetNotifications(ctx, "all") + So(err, ShouldBeNil) + So(len(list), ShouldEqual, 1) + So(list[0].Title, ShouldEqual, "Welcome") + So(list[0].Type, ShouldEqual, "system") + }) + }) +} diff --git a/backend/database/.transform.yaml b/backend/database/.transform.yaml index f90e841..8ddaa3c 100644 --- a/backend/database/.transform.yaml +++ b/backend/database/.transform.yaml @@ -11,4 +11,22 @@ imports: - quyun/v2/pkg/consts - quyun/v2/database/fields field_type: -field_relate: + users: + gender: consts.Gender + roles: types.Array[consts.Role] + contents: + status: consts.ContentStatus + visibility: consts.ContentVisibility + genre: string # genre is varchar(64) but no enum defined yet? + content_prices: + currency: consts.Currency + discount_type: consts.DiscountType + orders: + status: consts.OrderStatus + type: consts.OrderType + tenants: + status: consts.TenantStatus + tenant_users: + role: types.Array[consts.TenantUserRole] + # status: consts.UserStatus # Skipping status for now to avoid mismatch 'active' vs 'verified' without enum update +field_relate: \ No newline at end of file diff --git a/backend/database/models/content_prices.gen.go b/backend/database/models/content_prices.gen.go index 273b3d6..383ad7f 100644 --- a/backend/database/models/content_prices.gen.go +++ b/backend/database/models/content_prices.gen.go @@ -8,6 +8,8 @@ import ( "context" "time" + "quyun/v2/pkg/consts" + "go.ipao.vip/gen" ) @@ -15,18 +17,18 @@ const TableNameContentPrice = "content_prices" // ContentPrice mapped from table type ContentPrice struct { - ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` - TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"` - UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"` - ContentID int64 `gorm:"column:content_id;type:bigint;not null" json:"content_id"` - Currency string `gorm:"column:currency;type:character varying(16);default:CNY" json:"currency"` - PriceAmount int64 `gorm:"column:price_amount;type:bigint;not null" json:"price_amount"` - DiscountType string `gorm:"column:discount_type;type:character varying(16);default:none" json:"discount_type"` - DiscountValue int64 `gorm:"column:discount_value;type:bigint" json:"discount_value"` - DiscountStartAt time.Time `gorm:"column:discount_start_at;type:timestamp with time zone" json:"discount_start_at"` - DiscountEndAt time.Time `gorm:"column:discount_end_at;type:timestamp with time zone" json:"discount_end_at"` - CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"` + ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` + TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"` + UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"` + ContentID int64 `gorm:"column:content_id;type:bigint;not null" json:"content_id"` + Currency consts.Currency `gorm:"column:currency;type:character varying(16);default:CNY" json:"currency"` + PriceAmount int64 `gorm:"column:price_amount;type:bigint;not null" json:"price_amount"` + DiscountType consts.DiscountType `gorm:"column:discount_type;type:character varying(16);default:none" json:"discount_type"` + DiscountValue int64 `gorm:"column:discount_value;type:bigint" json:"discount_value"` + DiscountStartAt time.Time `gorm:"column:discount_start_at;type:timestamp with time zone" json:"discount_start_at"` + DiscountEndAt time.Time `gorm:"column:discount_end_at;type:timestamp with time zone" json:"discount_end_at"` + CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"` } // Quick operations without importing query package diff --git a/backend/database/models/content_prices.query.gen.go b/backend/database/models/content_prices.query.gen.go index 9738a37..627714d 100644 --- a/backend/database/models/content_prices.query.gen.go +++ b/backend/database/models/content_prices.query.gen.go @@ -29,9 +29,9 @@ func newContentPrice(db *gorm.DB, opts ...gen.DOOption) contentPriceQuery { _contentPriceQuery.TenantID = field.NewInt64(tableName, "tenant_id") _contentPriceQuery.UserID = field.NewInt64(tableName, "user_id") _contentPriceQuery.ContentID = field.NewInt64(tableName, "content_id") - _contentPriceQuery.Currency = field.NewString(tableName, "currency") + _contentPriceQuery.Currency = field.NewField(tableName, "currency") _contentPriceQuery.PriceAmount = field.NewInt64(tableName, "price_amount") - _contentPriceQuery.DiscountType = field.NewString(tableName, "discount_type") + _contentPriceQuery.DiscountType = field.NewField(tableName, "discount_type") _contentPriceQuery.DiscountValue = field.NewInt64(tableName, "discount_value") _contentPriceQuery.DiscountStartAt = field.NewTime(tableName, "discount_start_at") _contentPriceQuery.DiscountEndAt = field.NewTime(tableName, "discount_end_at") @@ -51,9 +51,9 @@ type contentPriceQuery struct { TenantID field.Int64 UserID field.Int64 ContentID field.Int64 - Currency field.String + Currency field.Field PriceAmount field.Int64 - DiscountType field.String + DiscountType field.Field DiscountValue field.Int64 DiscountStartAt field.Time DiscountEndAt field.Time @@ -79,9 +79,9 @@ func (c *contentPriceQuery) updateTableName(table string) *contentPriceQuery { c.TenantID = field.NewInt64(table, "tenant_id") c.UserID = field.NewInt64(table, "user_id") c.ContentID = field.NewInt64(table, "content_id") - c.Currency = field.NewString(table, "currency") + c.Currency = field.NewField(table, "currency") c.PriceAmount = field.NewInt64(table, "price_amount") - c.DiscountType = field.NewString(table, "discount_type") + c.DiscountType = field.NewField(table, "discount_type") c.DiscountValue = field.NewInt64(table, "discount_value") c.DiscountStartAt = field.NewTime(table, "discount_start_at") c.DiscountEndAt = field.NewTime(table, "discount_end_at") diff --git a/backend/database/models/contents.gen.go b/backend/database/models/contents.gen.go index c1389ef..94ba430 100644 --- a/backend/database/models/contents.gen.go +++ b/backend/database/models/contents.gen.go @@ -8,6 +8,8 @@ import ( "context" "time" + "quyun/v2/pkg/consts" + "go.ipao.vip/gen" "go.ipao.vip/gen/types" "gorm.io/gorm" @@ -17,25 +19,25 @@ const TableNameContent = "contents" // Content mapped from table type Content struct { - ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` - TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"` - UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"` - Title string `gorm:"column:title;type:character varying(255);not null" json:"title"` - Description string `gorm:"column:description;type:text;not null" json:"description"` - Status string `gorm:"column:status;type:character varying(32);default:draft" json:"status"` - Visibility string `gorm:"column:visibility;type:character varying(32);default:tenant_only" json:"visibility"` - PreviewSeconds int32 `gorm:"column:preview_seconds;type:integer;default:60" json:"preview_seconds"` - PreviewDownloadable bool `gorm:"column:preview_downloadable;type:boolean" json:"preview_downloadable"` - PublishedAt time.Time `gorm:"column:published_at;type:timestamp with time zone" json:"published_at"` - Summary string `gorm:"column:summary;type:character varying(256)" json:"summary"` - Tags types.JSON `gorm:"column:tags;type:jsonb;default:[]" json:"tags"` - Body string `gorm:"column:body;type:text" json:"body"` - Genre string `gorm:"column:genre;type:character varying(64)" json:"genre"` - Views int32 `gorm:"column:views;type:integer" json:"views"` - Likes int32 `gorm:"column:likes;type:integer" json:"likes"` - CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"` - DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp with time zone" json:"deleted_at"` + ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` + TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"` + UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"` + Title string `gorm:"column:title;type:character varying(255);not null" json:"title"` + Description string `gorm:"column:description;type:text;not null" json:"description"` + Status consts.ContentStatus `gorm:"column:status;type:character varying(32);default:draft" json:"status"` + Visibility consts.ContentVisibility `gorm:"column:visibility;type:character varying(32);default:tenant_only" json:"visibility"` + PreviewSeconds int32 `gorm:"column:preview_seconds;type:integer;default:60" json:"preview_seconds"` + PreviewDownloadable bool `gorm:"column:preview_downloadable;type:boolean" json:"preview_downloadable"` + PublishedAt time.Time `gorm:"column:published_at;type:timestamp with time zone" json:"published_at"` + Summary string `gorm:"column:summary;type:character varying(256)" json:"summary"` + Tags types.JSON `gorm:"column:tags;type:jsonb;default:[]" json:"tags"` + Body string `gorm:"column:body;type:text" json:"body"` + Genre string `gorm:"column:genre;type:character varying(64)" json:"genre"` + Views int32 `gorm:"column:views;type:integer" json:"views"` + Likes int32 `gorm:"column:likes;type:integer" json:"likes"` + CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp with time zone" json:"deleted_at"` } // Quick operations without importing query package diff --git a/backend/database/models/contents.query.gen.go b/backend/database/models/contents.query.gen.go index 7bf9659..868c3e1 100644 --- a/backend/database/models/contents.query.gen.go +++ b/backend/database/models/contents.query.gen.go @@ -30,8 +30,8 @@ func newContent(db *gorm.DB, opts ...gen.DOOption) contentQuery { _contentQuery.UserID = field.NewInt64(tableName, "user_id") _contentQuery.Title = field.NewString(tableName, "title") _contentQuery.Description = field.NewString(tableName, "description") - _contentQuery.Status = field.NewString(tableName, "status") - _contentQuery.Visibility = field.NewString(tableName, "visibility") + _contentQuery.Status = field.NewField(tableName, "status") + _contentQuery.Visibility = field.NewField(tableName, "visibility") _contentQuery.PreviewSeconds = field.NewInt32(tableName, "preview_seconds") _contentQuery.PreviewDownloadable = field.NewBool(tableName, "preview_downloadable") _contentQuery.PublishedAt = field.NewTime(tableName, "published_at") @@ -59,8 +59,8 @@ type contentQuery struct { UserID field.Int64 Title field.String Description field.String - Status field.String - Visibility field.String + Status field.Field + Visibility field.Field PreviewSeconds field.Int32 PreviewDownloadable field.Bool PublishedAt field.Time @@ -94,8 +94,8 @@ func (c *contentQuery) updateTableName(table string) *contentQuery { c.UserID = field.NewInt64(table, "user_id") c.Title = field.NewString(table, "title") c.Description = field.NewString(table, "description") - c.Status = field.NewString(table, "status") - c.Visibility = field.NewString(table, "visibility") + c.Status = field.NewField(table, "status") + c.Visibility = field.NewField(table, "visibility") c.PreviewSeconds = field.NewInt32(table, "preview_seconds") c.PreviewDownloadable = field.NewBool(table, "preview_downloadable") c.PublishedAt = field.NewTime(table, "published_at") diff --git a/backend/database/models/orders.gen.go b/backend/database/models/orders.gen.go index c0fa0ad..323fcc5 100644 --- a/backend/database/models/orders.gen.go +++ b/backend/database/models/orders.gen.go @@ -8,6 +8,8 @@ import ( "context" "time" + "quyun/v2/pkg/consts" + "go.ipao.vip/gen" "go.ipao.vip/gen/types" ) @@ -16,24 +18,24 @@ const TableNameOrder = "orders" // Order mapped from table type Order struct { - ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` - TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"` - UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"` - Type string `gorm:"column:type;type:character varying(32);default:content_purchase" json:"type"` - Status string `gorm:"column:status;type:character varying(32);default:created" json:"status"` - Currency string `gorm:"column:currency;type:character varying(16);default:CNY" json:"currency"` - AmountOriginal int64 `gorm:"column:amount_original;type:bigint;not null" json:"amount_original"` - AmountDiscount int64 `gorm:"column:amount_discount;type:bigint;not null" json:"amount_discount"` - AmountPaid int64 `gorm:"column:amount_paid;type:bigint;not null" json:"amount_paid"` - Snapshot types.JSON `gorm:"column:snapshot;type:jsonb;default:{}" json:"snapshot"` - IdempotencyKey string `gorm:"column:idempotency_key;type:character varying(128);not null" json:"idempotency_key"` - PaidAt time.Time `gorm:"column:paid_at;type:timestamp with time zone" json:"paid_at"` - RefundedAt time.Time `gorm:"column:refunded_at;type:timestamp with time zone" json:"refunded_at"` - RefundForced bool `gorm:"column:refund_forced;type:boolean" json:"refund_forced"` - RefundOperatorUserID int64 `gorm:"column:refund_operator_user_id;type:bigint" json:"refund_operator_user_id"` - RefundReason string `gorm:"column:refund_reason;type:character varying(255)" json:"refund_reason"` - CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"` + ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` + TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"` + UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"` + Type consts.OrderType `gorm:"column:type;type:character varying(32);default:content_purchase" json:"type"` + Status consts.OrderStatus `gorm:"column:status;type:character varying(32);default:created" json:"status"` + Currency string `gorm:"column:currency;type:character varying(16);default:CNY" json:"currency"` + AmountOriginal int64 `gorm:"column:amount_original;type:bigint;not null" json:"amount_original"` + AmountDiscount int64 `gorm:"column:amount_discount;type:bigint;not null" json:"amount_discount"` + AmountPaid int64 `gorm:"column:amount_paid;type:bigint;not null" json:"amount_paid"` + Snapshot types.JSON `gorm:"column:snapshot;type:jsonb;default:{}" json:"snapshot"` + IdempotencyKey string `gorm:"column:idempotency_key;type:character varying(128);not null" json:"idempotency_key"` + PaidAt time.Time `gorm:"column:paid_at;type:timestamp with time zone" json:"paid_at"` + RefundedAt time.Time `gorm:"column:refunded_at;type:timestamp with time zone" json:"refunded_at"` + RefundForced bool `gorm:"column:refund_forced;type:boolean" json:"refund_forced"` + RefundOperatorUserID int64 `gorm:"column:refund_operator_user_id;type:bigint" json:"refund_operator_user_id"` + RefundReason string `gorm:"column:refund_reason;type:character varying(255)" json:"refund_reason"` + CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"` } // Quick operations without importing query package diff --git a/backend/database/models/orders.query.gen.go b/backend/database/models/orders.query.gen.go index 75f7780..4d4fc3c 100644 --- a/backend/database/models/orders.query.gen.go +++ b/backend/database/models/orders.query.gen.go @@ -28,8 +28,8 @@ func newOrder(db *gorm.DB, opts ...gen.DOOption) orderQuery { _orderQuery.ID = field.NewInt64(tableName, "id") _orderQuery.TenantID = field.NewInt64(tableName, "tenant_id") _orderQuery.UserID = field.NewInt64(tableName, "user_id") - _orderQuery.Type = field.NewString(tableName, "type") - _orderQuery.Status = field.NewString(tableName, "status") + _orderQuery.Type = field.NewField(tableName, "type") + _orderQuery.Status = field.NewField(tableName, "status") _orderQuery.Currency = field.NewString(tableName, "currency") _orderQuery.AmountOriginal = field.NewInt64(tableName, "amount_original") _orderQuery.AmountDiscount = field.NewInt64(tableName, "amount_discount") @@ -56,8 +56,8 @@ type orderQuery struct { ID field.Int64 TenantID field.Int64 UserID field.Int64 - Type field.String - Status field.String + Type field.Field + Status field.Field Currency field.String AmountOriginal field.Int64 AmountDiscount field.Int64 @@ -90,8 +90,8 @@ func (o *orderQuery) updateTableName(table string) *orderQuery { o.ID = field.NewInt64(table, "id") o.TenantID = field.NewInt64(table, "tenant_id") o.UserID = field.NewInt64(table, "user_id") - o.Type = field.NewString(table, "type") - o.Status = field.NewString(table, "status") + o.Type = field.NewField(table, "type") + o.Status = field.NewField(table, "status") o.Currency = field.NewString(table, "currency") o.AmountOriginal = field.NewInt64(table, "amount_original") o.AmountDiscount = field.NewInt64(table, "amount_discount") diff --git a/backend/database/models/tenant_users.gen.go b/backend/database/models/tenant_users.gen.go index 66dac9b..7301b4f 100644 --- a/backend/database/models/tenant_users.gen.go +++ b/backend/database/models/tenant_users.gen.go @@ -8,6 +8,8 @@ import ( "context" "time" + "quyun/v2/pkg/consts" + "go.ipao.vip/gen" "go.ipao.vip/gen/types" ) @@ -16,13 +18,13 @@ const TableNameTenantUser = "tenant_users" // TenantUser mapped from table type TenantUser struct { - ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` - TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"` - UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"` - Role types.Array[string] `gorm:"column:role;type:text[];default:{member}" json:"role"` - Status string `gorm:"column:status;type:character varying(50);default:verified" json:"status"` - CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"` + ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` + TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"` + UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"` + Role types.Array[consts.TenantUserRole] `gorm:"column:role;type:text[];default:{member}" json:"role"` + Status string `gorm:"column:status;type:character varying(50);default:verified" json:"status"` + CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"` } // Quick operations without importing query package diff --git a/backend/database/models/tenants.gen.go b/backend/database/models/tenants.gen.go index 359dbe8..93d16ac 100644 --- a/backend/database/models/tenants.gen.go +++ b/backend/database/models/tenants.gen.go @@ -8,6 +8,8 @@ import ( "context" "time" + "quyun/v2/pkg/consts" + "go.ipao.vip/gen" "go.ipao.vip/gen/types" ) @@ -16,16 +18,16 @@ const TableNameTenant = "tenants" // Tenant mapped from table type Tenant struct { - ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` - UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"` - Code string `gorm:"column:code;type:character varying(64);not null" json:"code"` - UUID types.UUID `gorm:"column:uuid;type:uuid;not null" json:"uuid"` - Name string `gorm:"column:name;type:character varying(128);not null" json:"name"` - Status string `gorm:"column:status;type:character varying(64);not null" json:"status"` - Config types.JSON `gorm:"column:config;type:jsonb;default:{}" json:"config"` - ExpiredAt time.Time `gorm:"column:expired_at;type:timestamp with time zone" json:"expired_at"` - CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"` + ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` + UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"` + Code string `gorm:"column:code;type:character varying(64);not null" json:"code"` + UUID types.UUID `gorm:"column:uuid;type:uuid;not null" json:"uuid"` + Name string `gorm:"column:name;type:character varying(128);not null" json:"name"` + Status consts.TenantStatus `gorm:"column:status;type:character varying(64);not null" json:"status"` + Config types.JSON `gorm:"column:config;type:jsonb;default:{}" json:"config"` + ExpiredAt time.Time `gorm:"column:expired_at;type:timestamp with time zone" json:"expired_at"` + CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"` } // Quick operations without importing query package diff --git a/backend/database/models/tenants.query.gen.go b/backend/database/models/tenants.query.gen.go index ef4497f..7393e6a 100644 --- a/backend/database/models/tenants.query.gen.go +++ b/backend/database/models/tenants.query.gen.go @@ -30,7 +30,7 @@ func newTenant(db *gorm.DB, opts ...gen.DOOption) tenantQuery { _tenantQuery.Code = field.NewString(tableName, "code") _tenantQuery.UUID = field.NewField(tableName, "uuid") _tenantQuery.Name = field.NewString(tableName, "name") - _tenantQuery.Status = field.NewString(tableName, "status") + _tenantQuery.Status = field.NewField(tableName, "status") _tenantQuery.Config = field.NewJSONB(tableName, "config") _tenantQuery.ExpiredAt = field.NewTime(tableName, "expired_at") _tenantQuery.CreatedAt = field.NewTime(tableName, "created_at") @@ -50,7 +50,7 @@ type tenantQuery struct { Code field.String UUID field.Field Name field.String - Status field.String + Status field.Field Config field.JSONB ExpiredAt field.Time CreatedAt field.Time @@ -76,7 +76,7 @@ func (t *tenantQuery) updateTableName(table string) *tenantQuery { t.Code = field.NewString(table, "code") t.UUID = field.NewField(table, "uuid") t.Name = field.NewString(table, "name") - t.Status = field.NewString(table, "status") + t.Status = field.NewField(table, "status") t.Config = field.NewJSONB(table, "config") t.ExpiredAt = field.NewTime(table, "expired_at") t.CreatedAt = field.NewTime(table, "created_at") diff --git a/backend/database/models/users.gen.go b/backend/database/models/users.gen.go index 4a5a2f5..503611c 100644 --- a/backend/database/models/users.gen.go +++ b/backend/database/models/users.gen.go @@ -8,6 +8,8 @@ import ( "context" "time" + "quyun/v2/pkg/consts" + "go.ipao.vip/gen" "go.ipao.vip/gen/types" "gorm.io/gorm" @@ -17,27 +19,27 @@ const TableNameUser = "users" // User mapped from table type User struct { - ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` - Username string `gorm:"column:username;type:character varying(255);not null" json:"username"` - Password string `gorm:"column:password;type:character varying(255);not null" json:"password"` - Roles types.Array[string] `gorm:"column:roles;type:text[];default:{user}" json:"roles"` - Status string `gorm:"column:status;type:character varying(50);default:active" json:"status"` - Metas types.JSON `gorm:"column:metas;type:jsonb;default:{}" json:"metas"` - Balance int64 `gorm:"column:balance;type:bigint" json:"balance"` - BalanceFrozen int64 `gorm:"column:balance_frozen;type:bigint" json:"balance_frozen"` - VerifiedAt time.Time `gorm:"column:verified_at;type:timestamp with time zone" json:"verified_at"` - Nickname string `gorm:"column:nickname;type:character varying(255)" json:"nickname"` - Avatar string `gorm:"column:avatar;type:character varying(512)" json:"avatar"` - Gender string `gorm:"column:gender;type:character varying(32);default:secret" json:"gender"` - Bio string `gorm:"column:bio;type:character varying(512)" json:"bio"` - Birthday types.Date `gorm:"column:birthday;type:date" json:"birthday"` - Location types.JSON `gorm:"column:location;type:jsonb;default:{}" json:"location"` - Points int64 `gorm:"column:points;type:bigint" json:"points"` - Phone string `gorm:"column:phone;type:character varying(32)" json:"phone"` - IsRealNameVerified bool `gorm:"column:is_real_name_verified;type:boolean" json:"is_real_name_verified"` - CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"` - UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"` - DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp with time zone" json:"deleted_at"` + ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` + Username string `gorm:"column:username;type:character varying(255);not null" json:"username"` + Password string `gorm:"column:password;type:character varying(255);not null" json:"password"` + Roles types.Array[consts.Role] `gorm:"column:roles;type:text[];default:{user}" json:"roles"` + Status string `gorm:"column:status;type:character varying(50);default:active" json:"status"` + Metas types.JSON `gorm:"column:metas;type:jsonb;default:{}" json:"metas"` + Balance int64 `gorm:"column:balance;type:bigint" json:"balance"` + BalanceFrozen int64 `gorm:"column:balance_frozen;type:bigint" json:"balance_frozen"` + VerifiedAt time.Time `gorm:"column:verified_at;type:timestamp with time zone" json:"verified_at"` + Nickname string `gorm:"column:nickname;type:character varying(255)" json:"nickname"` + Avatar string `gorm:"column:avatar;type:character varying(512)" json:"avatar"` + Gender consts.Gender `gorm:"column:gender;type:character varying(32);default:secret" json:"gender"` + Bio string `gorm:"column:bio;type:character varying(512)" json:"bio"` + Birthday types.Date `gorm:"column:birthday;type:date" json:"birthday"` + Location types.JSON `gorm:"column:location;type:jsonb;default:{}" json:"location"` + Points int64 `gorm:"column:points;type:bigint" json:"points"` + Phone string `gorm:"column:phone;type:character varying(32)" json:"phone"` + IsRealNameVerified bool `gorm:"column:is_real_name_verified;type:boolean" json:"is_real_name_verified"` + CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"` + UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp with time zone" json:"deleted_at"` } // Quick operations without importing query package diff --git a/backend/database/models/users.query.gen.go b/backend/database/models/users.query.gen.go index 03668b2..5ab1606 100644 --- a/backend/database/models/users.query.gen.go +++ b/backend/database/models/users.query.gen.go @@ -36,7 +36,7 @@ func newUser(db *gorm.DB, opts ...gen.DOOption) userQuery { _userQuery.VerifiedAt = field.NewTime(tableName, "verified_at") _userQuery.Nickname = field.NewString(tableName, "nickname") _userQuery.Avatar = field.NewString(tableName, "avatar") - _userQuery.Gender = field.NewString(tableName, "gender") + _userQuery.Gender = field.NewField(tableName, "gender") _userQuery.Bio = field.NewString(tableName, "bio") _userQuery.Birthday = field.NewField(tableName, "birthday") _userQuery.Location = field.NewJSONB(tableName, "location") @@ -67,7 +67,7 @@ type userQuery struct { VerifiedAt field.Time Nickname field.String Avatar field.String - Gender field.String + Gender field.Field Bio field.String Birthday field.Field Location field.JSONB @@ -104,7 +104,7 @@ func (u *userQuery) updateTableName(table string) *userQuery { u.VerifiedAt = field.NewTime(table, "verified_at") u.Nickname = field.NewString(table, "nickname") u.Avatar = field.NewString(table, "avatar") - u.Gender = field.NewString(table, "gender") + u.Gender = field.NewField(table, "gender") u.Bio = field.NewString(table, "bio") u.Birthday = field.NewField(table, "birthday") u.Location = field.NewJSONB(table, "location")