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.
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
185
backend/app/services/user_test.go
Normal file
185
backend/app/services/user_test.go
Normal file
@@ -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")
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -11,4 +11,22 @@ imports:
|
||||
- quyun/v2/pkg/consts
|
||||
- quyun/v2/database/fields
|
||||
field_type:
|
||||
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:
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"quyun/v2/pkg/consts"
|
||||
|
||||
"go.ipao.vip/gen"
|
||||
)
|
||||
|
||||
@@ -19,9 +21,9 @@ type ContentPrice struct {
|
||||
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"`
|
||||
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 string `gorm:"column:discount_type;type:character varying(16);default:none" json:"discount_type"`
|
||||
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"`
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"quyun/v2/pkg/consts"
|
||||
|
||||
"go.ipao.vip/gen"
|
||||
"go.ipao.vip/gen/types"
|
||||
"gorm.io/gorm"
|
||||
@@ -22,8 +24,8 @@ type Content struct {
|
||||
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"`
|
||||
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"`
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"quyun/v2/pkg/consts"
|
||||
|
||||
"go.ipao.vip/gen"
|
||||
"go.ipao.vip/gen/types"
|
||||
)
|
||||
@@ -19,8 +21,8 @@ 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"`
|
||||
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"`
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"quyun/v2/pkg/consts"
|
||||
|
||||
"go.ipao.vip/gen"
|
||||
"go.ipao.vip/gen/types"
|
||||
)
|
||||
@@ -19,7 +21,7 @@ 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"`
|
||||
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"`
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"quyun/v2/pkg/consts"
|
||||
|
||||
"go.ipao.vip/gen"
|
||||
"go.ipao.vip/gen/types"
|
||||
)
|
||||
@@ -21,7 +23,7 @@ type Tenant struct {
|
||||
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"`
|
||||
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"`
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"quyun/v2/pkg/consts"
|
||||
|
||||
"go.ipao.vip/gen"
|
||||
"go.ipao.vip/gen/types"
|
||||
"gorm.io/gorm"
|
||||
@@ -20,7 +22,7 @@ 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"`
|
||||
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"`
|
||||
@@ -28,7 +30,7 @@ type User struct {
|
||||
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"`
|
||||
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"`
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user