feat: Add player_configs feature and migrate user preferences
- Implemented player_configs table to store multiple player configurations per user. - Migrated existing player settings from user_preferences to player_configs. - Removed player-related columns from user_preferences. - Added referral state fields to user for tracking referral rewards. - Created migration scripts for database changes and data migration. - Added test cases for app services and usage helpers. - Introduced video job service interfaces and implementations.
This commit is contained in:
110
internal/database/model/common.go
Normal file
110
internal/database/model/common.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// BoolPtr returns a pointer to the given bool value
|
||||
func BoolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
// StringPtr returns a pointer to the given string value
|
||||
func StringPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
// StringValue returns the string value or empty string if nil
|
||||
func StringValue(s *string) string {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
return *s
|
||||
}
|
||||
|
||||
// BoolValue returns the bool value or false if nil
|
||||
func BoolValue(b *bool) bool {
|
||||
if b == nil {
|
||||
return false
|
||||
}
|
||||
return *b
|
||||
}
|
||||
|
||||
// Int64Ptr returns a pointer to the given int64 value
|
||||
func Int64Ptr(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
|
||||
// Float64Ptr returns a pointer to the given float64 value
|
||||
func Float64Ptr(f float64) *float64 {
|
||||
return &f
|
||||
}
|
||||
|
||||
// TimePtr returns a pointer to the given time.Time value
|
||||
func TimePtr(t time.Time) *time.Time {
|
||||
return &t
|
||||
}
|
||||
|
||||
// FindOrCreateUserPreference finds or creates a user preference record
|
||||
func FindOrCreateUserPreference(ctx context.Context, db *gorm.DB, userID string) (*UserPreference, error) {
|
||||
pref := &UserPreference{}
|
||||
err := db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Attrs(&UserPreference{
|
||||
Language: StringPtr("en"),
|
||||
Locale: StringPtr("en"),
|
||||
EmailNotifications: BoolPtr(true),
|
||||
PushNotifications: BoolPtr(true),
|
||||
MarketingNotifications: false,
|
||||
TelegramNotifications: false,
|
||||
}).
|
||||
FirstOrCreate(pref).Error
|
||||
|
||||
// Handle race condition: if duplicate key error, fetch the existing record
|
||||
if err != nil && strings.Contains(err.Error(), "duplicate key") {
|
||||
err = db.WithContext(ctx).Where("user_id = ?", userID).First(pref).Error
|
||||
}
|
||||
|
||||
return pref, err
|
||||
}
|
||||
|
||||
// GetWalletBalance calculates the current wallet balance for a user
|
||||
func GetWalletBalance(ctx context.Context, db *gorm.DB, userID string) (float64, error) {
|
||||
var balance float64
|
||||
if err := db.WithContext(ctx).
|
||||
Model(&WalletTransaction{}).
|
||||
Where("user_id = ?", userID).
|
||||
Select("COALESCE(SUM(amount), 0)").
|
||||
Scan(&balance).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return balance, nil
|
||||
}
|
||||
|
||||
// GetLatestPlanSubscription finds the latest plan subscription for a user
|
||||
func GetLatestPlanSubscription(ctx context.Context, db *gorm.DB, userID string) (*PlanSubscription, error) {
|
||||
sub := &PlanSubscription{}
|
||||
err := db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Order("expires_at DESC").
|
||||
First(sub).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sub, nil
|
||||
}
|
||||
|
||||
// IsSubscriptionExpiringSoon checks if subscription expires within threshold days
|
||||
func IsSubscriptionExpiringSoon(sub *PlanSubscription, thresholdDays int) bool {
|
||||
if sub == nil {
|
||||
return false
|
||||
}
|
||||
now := time.Now()
|
||||
hoursUntilExpiry := sub.ExpiresAt.Sub(now).Hours()
|
||||
thresholdHours := float64(thresholdDays) * 24
|
||||
return hoursUntilExpiry > 0 && hoursUntilExpiry <= thresholdHours
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPreferenceLanguage = "en"
|
||||
defaultPreferenceLocale = "en"
|
||||
)
|
||||
|
||||
func DefaultUserPreference(userID string) *UserPreference {
|
||||
return &UserPreference{
|
||||
UserID: userID,
|
||||
Language: StringPtr(defaultPreferenceLanguage),
|
||||
Locale: StringPtr(defaultPreferenceLocale),
|
||||
EmailNotifications: BoolPtr(true),
|
||||
PushNotifications: BoolPtr(true),
|
||||
MarketingNotifications: false,
|
||||
TelegramNotifications: false,
|
||||
Autoplay: false,
|
||||
Loop: false,
|
||||
Muted: false,
|
||||
ShowControls: BoolPtr(true),
|
||||
Pip: BoolPtr(true),
|
||||
Airplay: BoolPtr(true),
|
||||
Chromecast: BoolPtr(true),
|
||||
}
|
||||
}
|
||||
|
||||
func FindOrCreateUserPreference(ctx context.Context, db *gorm.DB, userID string) (*UserPreference, error) {
|
||||
var pref UserPreference
|
||||
if err := db.WithContext(ctx).Where("user_id = ?", userID).First(&pref).Error; err == nil {
|
||||
normalizeUserPreferenceDefaults(&pref)
|
||||
return &pref, nil
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pref = *DefaultUserPreference(userID)
|
||||
if err := db.WithContext(ctx).Create(&pref).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pref, nil
|
||||
}
|
||||
|
||||
func GetWalletBalance(ctx context.Context, db *gorm.DB, userID string) (float64, error) {
|
||||
var balance float64
|
||||
if err := db.WithContext(ctx).
|
||||
Model(&WalletTransaction{}).
|
||||
Where("user_id = ?", userID).
|
||||
Select("COALESCE(SUM(amount), 0)").
|
||||
Scan(&balance).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return balance, nil
|
||||
}
|
||||
|
||||
func GetLatestPlanSubscription(ctx context.Context, db *gorm.DB, userID string) (*PlanSubscription, error) {
|
||||
userID = strings.TrimSpace(userID)
|
||||
if userID == "" {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
var subscription PlanSubscription
|
||||
if err := db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Order("created_at DESC").
|
||||
Order("id DESC").
|
||||
First(&subscription).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &subscription, nil
|
||||
}
|
||||
|
||||
func IsSubscriptionExpiringSoon(expiresAt time.Time, now time.Time) bool {
|
||||
if expiresAt.IsZero() || !expiresAt.After(now) {
|
||||
return false
|
||||
}
|
||||
return expiresAt.Sub(now) <= 7*24*time.Hour
|
||||
}
|
||||
|
||||
func normalizeUserPreferenceDefaults(pref *UserPreference) {
|
||||
if pref == nil {
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(StringValue(pref.Language)) == "" {
|
||||
pref.Language = StringPtr(defaultPreferenceLanguage)
|
||||
}
|
||||
if strings.TrimSpace(StringValue(pref.Locale)) == "" {
|
||||
locale := StringValue(pref.Language)
|
||||
if strings.TrimSpace(locale) == "" {
|
||||
locale = defaultPreferenceLocale
|
||||
}
|
||||
pref.Locale = StringPtr(locale)
|
||||
}
|
||||
if pref.EmailNotifications == nil {
|
||||
pref.EmailNotifications = BoolPtr(true)
|
||||
}
|
||||
if pref.PushNotifications == nil {
|
||||
pref.PushNotifications = BoolPtr(true)
|
||||
}
|
||||
if pref.ShowControls == nil {
|
||||
pref.ShowControls = BoolPtr(true)
|
||||
}
|
||||
if pref.Pip == nil {
|
||||
pref.Pip = BoolPtr(true)
|
||||
}
|
||||
if pref.Airplay == nil {
|
||||
pref.Airplay = BoolPtr(true)
|
||||
}
|
||||
if pref.Chromecast == nil {
|
||||
pref.Chromecast = BoolPtr(true)
|
||||
}
|
||||
}
|
||||
|
||||
func StringPtr(value string) *string {
|
||||
v := value
|
||||
return &v
|
||||
}
|
||||
|
||||
func BoolPtr(value bool) *bool {
|
||||
v := value
|
||||
return &v
|
||||
}
|
||||
|
||||
func StringValue(value *string) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
return *value
|
||||
}
|
||||
38
internal/database/model/player_configs.gen.go
Normal file
38
internal/database/model/player_configs.gen.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const TableNamePlayerConfig = "player_configs"
|
||||
|
||||
// PlayerConfig mapped from table <player_configs>
|
||||
type PlayerConfig struct {
|
||||
ID string `gorm:"column:id;type:uuid;primaryKey;default:gen_random_uuid()" json:"id"`
|
||||
UserID string `gorm:"column:user_id;type:uuid;not null;uniqueIndex:idx_player_configs_one_default_per_user,priority:1;index:idx_player_configs_user_default,priority:2;index:idx_player_configs_user_id,priority:1" json:"user_id"`
|
||||
Name string `gorm:"column:name;type:text;not null" json:"name"`
|
||||
Description *string `gorm:"column:description;type:text" json:"description"`
|
||||
Autoplay bool `gorm:"column:autoplay;type:boolean;not null" json:"autoplay"`
|
||||
Loop bool `gorm:"column:loop;type:boolean;not null" json:"loop"`
|
||||
Muted bool `gorm:"column:muted;type:boolean;not null" json:"muted"`
|
||||
ShowControls *bool `gorm:"column:show_controls;type:boolean;not null;default:true" json:"show_controls"`
|
||||
Pip *bool `gorm:"column:pip;type:boolean;not null;default:true" json:"pip"`
|
||||
Airplay *bool `gorm:"column:airplay;type:boolean;not null;default:true" json:"airplay"`
|
||||
Chromecast *bool `gorm:"column:chromecast;type:boolean;not null;default:true" json:"chromecast"`
|
||||
IsActive *bool `gorm:"column:is_active;type:boolean;not null;default:true" json:"is_active"`
|
||||
IsDefault bool `gorm:"column:is_default;type:boolean;not null;index:idx_player_configs_is_default,priority:1;index:idx_player_configs_user_default,priority:1" json:"is_default"`
|
||||
CreatedAt *time.Time `gorm:"column:created_at;type:timestamp(3) without time zone;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp(3) without time zone;not null" json:"updated_at"`
|
||||
Version *int64 `gorm:"column:version;type:bigint;not null;default:1;version" json:"-"`
|
||||
EncrytionM3u8 *bool `gorm:"column:encrytion_m3u8;type:boolean;not null;default:true" json:"encrytion_m3u8"`
|
||||
LogoURL *string `gorm:"column:logo_url;type:character varying(500)" json:"logo_url"`
|
||||
}
|
||||
|
||||
// TableName PlayerConfig's table name
|
||||
func (*PlayerConfig) TableName() string {
|
||||
return TableNamePlayerConfig
|
||||
}
|
||||
@@ -12,18 +12,25 @@ const TableNameUser = "user"
|
||||
|
||||
// User mapped from table <user>
|
||||
type User struct {
|
||||
ID string `gorm:"column:id;type:uuid;primaryKey;default:gen_random_uuid()" json:"id"`
|
||||
Email string `gorm:"column:email;type:text;not null;uniqueIndex:user_email_key,priority:1" json:"email"`
|
||||
Password *string `gorm:"column:password;type:text" json:"-"`
|
||||
Username *string `gorm:"column:username;type:text" json:"username"`
|
||||
Avatar *string `gorm:"column:avatar;type:text" json:"avatar"`
|
||||
Role *string `gorm:"column:role;type:character varying(20);not null;default:USER" json:"role"`
|
||||
GoogleID *string `gorm:"column:google_id;type:text;uniqueIndex:user_google_id_key,priority:1" json:"google_id"`
|
||||
StorageUsed int64 `gorm:"column:storage_used;type:bigint;not null" json:"storage_used"`
|
||||
PlanID *string `gorm:"column:plan_id;type:uuid" json:"plan_id"`
|
||||
CreatedAt *time.Time `gorm:"column:created_at;type:timestamp(3) without time zone;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp(3) without time zone;not null" json:"updated_at"`
|
||||
Version *int64 `gorm:"column:version;type:bigint;not null;default:1;version" json:"-"`
|
||||
ID string `gorm:"column:id;type:uuid;primaryKey;default:gen_random_uuid()" json:"id"`
|
||||
Email string `gorm:"column:email;type:text;not null;uniqueIndex:user_email_key,priority:1" json:"email"`
|
||||
Password *string `gorm:"column:password;type:text" json:"-"`
|
||||
Username *string `gorm:"column:username;type:text" json:"username"`
|
||||
Avatar *string `gorm:"column:avatar;type:text" json:"avatar"`
|
||||
Role *string `gorm:"column:role;type:character varying(20);not null;default:USER" json:"role"`
|
||||
GoogleID *string `gorm:"column:google_id;type:text;uniqueIndex:user_google_id_key,priority:1" json:"google_id"`
|
||||
StorageUsed int64 `gorm:"column:storage_used;type:bigint;not null" json:"storage_used"`
|
||||
PlanID *string `gorm:"column:plan_id;type:uuid" json:"plan_id"`
|
||||
ReferredByUserID *string `gorm:"column:referred_by_user_id;type:uuid;index:idx_user_referred_by_user_id,priority:1" json:"referred_by_user_id"`
|
||||
ReferralEligible *bool `gorm:"column:referral_eligible;type:boolean;not null;default:true" json:"referral_eligible"`
|
||||
ReferralRewardBps *int32 `gorm:"column:referral_reward_bps;type:integer" json:"referral_reward_bps"`
|
||||
ReferralRewardGrantedAt *time.Time `gorm:"column:referral_reward_granted_at;type:timestamp with time zone" json:"referral_reward_granted_at"`
|
||||
ReferralRewardPaymentID *string `gorm:"column:referral_reward_payment_id;type:uuid" json:"referral_reward_payment_id"`
|
||||
ReferralRewardAmount *float64 `gorm:"column:referral_reward_amount;type:numeric(65,30)" json:"referral_reward_amount"`
|
||||
CreatedAt *time.Time `gorm:"column:created_at;type:timestamp(3) without time zone;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp(3) without time zone;not null" json:"updated_at"`
|
||||
Version *int64 `gorm:"column:version;type:bigint;not null;default:1;version" json:"-"`
|
||||
TelegramID *string `gorm:"column:telegram_id;type:character varying" json:"telegram_id"`
|
||||
}
|
||||
|
||||
// TableName User's table name
|
||||
|
||||
@@ -19,16 +19,8 @@ type UserPreference struct {
|
||||
PushNotifications *bool `gorm:"column:push_notifications;type:boolean;not null;default:true" json:"push_notifications"`
|
||||
MarketingNotifications bool `gorm:"column:marketing_notifications;type:boolean;not null" json:"marketing_notifications"`
|
||||
TelegramNotifications bool `gorm:"column:telegram_notifications;type:boolean;not null" json:"telegram_notifications"`
|
||||
Autoplay bool `gorm:"column:autoplay;type:boolean;not null" json:"autoplay"`
|
||||
Loop bool `gorm:"column:loop;type:boolean;not null" json:"loop"`
|
||||
Muted bool `gorm:"column:muted;type:boolean;not null" json:"muted"`
|
||||
ShowControls *bool `gorm:"column:show_controls;type:boolean;not null;default:true" json:"show_controls"`
|
||||
Pip *bool `gorm:"column:pip;type:boolean;not null;default:true" json:"pip"`
|
||||
Airplay *bool `gorm:"column:airplay;type:boolean;not null;default:true" json:"airplay"`
|
||||
Chromecast *bool `gorm:"column:chromecast;type:boolean;not null;default:true" json:"chromecast"`
|
||||
CreatedAt *time.Time `gorm:"column:created_at;type:timestamp with time zone" json:"created_at"`
|
||||
UpdatedAt *time.Time `gorm:"column:updated_at;type:timestamp with time zone" json:"updated_at"`
|
||||
EncrytionM3u8 bool `gorm:"column:encrytion_m3u8;type:boolean;not null" json:"encrytion_m3u8"`
|
||||
Version *int64 `gorm:"column:version;type:bigint;not null;default:1;version" json:"-"`
|
||||
}
|
||||
|
||||
|
||||
@@ -6,31 +6,35 @@ package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
const TableNameVideo = "video"
|
||||
|
||||
// Video mapped from table <video>
|
||||
type Video struct {
|
||||
ID string `gorm:"column:id;type:uuid;primaryKey;default:gen_random_uuid()" json:"id"`
|
||||
Name string `gorm:"column:name;type:text;not null" json:"name"`
|
||||
Title string `gorm:"column:title;type:text;not null" json:"title"`
|
||||
Description *string `gorm:"column:description;type:text" json:"description"`
|
||||
URL string `gorm:"column:url;type:text;not null" json:"url"`
|
||||
Thumbnail *string `gorm:"column:thumbnail;type:text" json:"thumbnail"`
|
||||
HlsToken *string `gorm:"column:hls_token;type:text" json:"hls_token"`
|
||||
HlsPath *string `gorm:"column:hls_path;type:text" json:"hls_path"`
|
||||
Duration int32 `gorm:"column:duration;type:integer;not null" json:"duration"`
|
||||
Size int64 `gorm:"column:size;type:bigint;not null" json:"size"`
|
||||
StorageType *string `gorm:"column:storage_type;type:character varying(20);not null;default:tiktok_avatar" json:"storage_type"`
|
||||
Format string `gorm:"column:format;type:text;not null" json:"format"`
|
||||
Status *string `gorm:"column:status;type:character varying(20);not null;default:PUBLIC" json:"status"`
|
||||
ProcessingStatus *string `gorm:"column:processing_status;type:character varying(20);not null;default:PENDING" json:"processing_status"`
|
||||
Views int32 `gorm:"column:views;type:integer;not null" json:"views"`
|
||||
UserID string `gorm:"column:user_id;type:uuid;not null" json:"user_id"`
|
||||
CreatedAt *time.Time `gorm:"column:created_at;type:timestamp(3) without time zone;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp(3) without time zone;not null" json:"updated_at"`
|
||||
Version *int64 `gorm:"column:version;type:bigint;not null;default:1;version" json:"-"`
|
||||
ID string `gorm:"column:id;type:uuid;primaryKey;default:gen_random_uuid()" json:"id"`
|
||||
Name string `gorm:"column:name;type:text;not null" json:"name"`
|
||||
Title string `gorm:"column:title;type:text;not null" json:"title"`
|
||||
Description *string `gorm:"column:description;type:text" json:"description"`
|
||||
URL string `gorm:"column:url;type:text;not null" json:"url"`
|
||||
Thumbnail *string `gorm:"column:thumbnail;type:text" json:"thumbnail"`
|
||||
HlsToken *string `gorm:"column:hls_token;type:text" json:"hls_token"`
|
||||
HlsPath *string `gorm:"column:hls_path;type:text" json:"hls_path"`
|
||||
Duration int32 `gorm:"column:duration;type:integer;not null" json:"duration"`
|
||||
Size int64 `gorm:"column:size;type:bigint;not null" json:"size"`
|
||||
StorageType *string `gorm:"column:storage_type;type:character varying(20);not null;default:tiktok_avatar" json:"storage_type"`
|
||||
Format string `gorm:"column:format;type:text;not null" json:"format"`
|
||||
Status *string `gorm:"column:status;type:character varying(20);not null;default:PUBLIC" json:"status"`
|
||||
ProcessingStatus *string `gorm:"column:processing_status;type:character varying(20);not null;default:PENDING" json:"processing_status"`
|
||||
Views int32 `gorm:"column:views;type:integer;not null" json:"views"`
|
||||
UserID string `gorm:"column:user_id;type:uuid;not null" json:"user_id"`
|
||||
CreatedAt *time.Time `gorm:"column:created_at;type:timestamp(3) without time zone;not null;default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp(3) without time zone;not null" json:"updated_at"`
|
||||
Version *int64 `gorm:"column:version;type:bigint;not null;default:1;version" json:"-"`
|
||||
AdID *string `gorm:"column:ad_id;type:uuid" json:"ad_id"`
|
||||
Metadata *datatypes.JSON `gorm:"column:metadata;type:jsonb" json:"metadata"`
|
||||
}
|
||||
|
||||
// TableName Video's table name
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
// Code generated by gorm.io/gen. DO NOT EDIT.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const TableNameVideoAdConfig = "video_ad_configs"
|
||||
|
||||
// VideoAdConfig mapped from table <video_ad_configs>
|
||||
type VideoAdConfig struct {
|
||||
VideoID string `gorm:"column:video_id;type:uuid;primaryKey" json:"video_id"`
|
||||
UserID string `gorm:"column:user_id;type:uuid;not null;index:idx_video_ad_configs_user_id,priority:1" json:"user_id"`
|
||||
AdTemplateID string `gorm:"column:ad_template_id;type:uuid;not null" json:"ad_template_id"`
|
||||
VastTagURL string `gorm:"column:vast_tag_url;type:text;not null" json:"vast_tag_url"`
|
||||
AdFormat *string `gorm:"column:ad_format;type:character varying(50);not null;default:pre-roll" json:"ad_format"`
|
||||
Duration *int64 `gorm:"column:duration;type:bigint" json:"duration"`
|
||||
CreatedAt *time.Time `gorm:"column:created_at;type:timestamp with time zone" json:"created_at"`
|
||||
UpdatedAt *time.Time `gorm:"column:updated_at;type:timestamp with time zone" json:"updated_at"`
|
||||
Version *int64 `gorm:"column:version;type:bigint;not null;default:1;version" json:"-"`
|
||||
}
|
||||
|
||||
// TableName VideoAdConfig's table name
|
||||
func (*VideoAdConfig) TableName() string {
|
||||
return TableNameVideoAdConfig
|
||||
}
|
||||
Reference in New Issue
Block a user