Enhance user type management with locale support
- Added 'locale' field to user type structure for language localization. - Updated API methods to include locale in pricing and published types retrieval. - Enhanced tests to validate locale handling in user type operations, ensuring robust support for multiple languages.
This commit is contained in:
parent
15f0750253
commit
87465379be
6 changed files with 264 additions and 147 deletions
282
data/bindata.go
282
data/bindata.go
File diff suppressed because one or more lines are too long
|
|
@ -116,7 +116,7 @@ var (
|
||||||
|
|
||||||
// DefaultTypeFields contains basic type fields
|
// DefaultTypeFields contains basic type fields
|
||||||
DefaultTypeFields = []interface{}{
|
DefaultTypeFields = []interface{}{
|
||||||
"id", "type_id", "name", "description", "is_active", "is_default", "sort_order", "status",
|
"id", "type_id", "name", "description", "is_active", "is_default", "sort_order", "status", "locale",
|
||||||
"default_role_id", "max_sessions", "session_timeout", "price_daily", "price_monthly",
|
"default_role_id", "max_sessions", "session_timeout", "price_daily", "price_monthly",
|
||||||
"price_yearly", "credits_monthly", "created_at", "updated_at",
|
"price_yearly", "credits_monthly", "created_at", "updated_at",
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +124,7 @@ var (
|
||||||
// DefaultTypeDetailFields contains all type fields including configuration and metadata
|
// DefaultTypeDetailFields contains all type fields including configuration and metadata
|
||||||
DefaultTypeDetailFields = []interface{}{
|
DefaultTypeDetailFields = []interface{}{
|
||||||
"id", "type_id", "name", "description", "default_role_id", "schema", "metadata",
|
"id", "type_id", "name", "description", "default_role_id", "schema", "metadata",
|
||||||
"is_active", "is_default", "sort_order", "status", "max_sessions", "session_timeout",
|
"is_active", "is_default", "sort_order", "status", "locale", "max_sessions", "session_timeout",
|
||||||
"password_policy", "features", "limits", "price_daily", "price_monthly", "price_yearly",
|
"password_policy", "features", "limits", "price_daily", "price_monthly", "price_yearly",
|
||||||
"credits_monthly", "introduction", "sale_type", "sale_link", "sale_price_label",
|
"credits_monthly", "introduction", "sale_type", "sale_link", "sale_price_label",
|
||||||
"sale_description", "created_at", "updated_at",
|
"sale_description", "created_at", "updated_at",
|
||||||
|
|
|
||||||
|
|
@ -294,11 +294,12 @@ func (u *DefaultUser) SetTypeConfiguration(ctx context.Context, typeID string, c
|
||||||
// GetTypePricing retrieves pricing information for a type
|
// GetTypePricing retrieves pricing information for a type
|
||||||
func (u *DefaultUser) GetTypePricing(ctx context.Context, typeID string) (maps.MapStrAny, error) {
|
func (u *DefaultUser) GetTypePricing(ctx context.Context, typeID string) (maps.MapStrAny, error) {
|
||||||
m := model.Select(u.typeModel)
|
m := model.Select(u.typeModel)
|
||||||
|
|
||||||
types, err := m.Get(model.QueryParam{
|
types, err := m.Get(model.QueryParam{
|
||||||
Select: []interface{}{
|
Select: []interface{}{
|
||||||
"type_id", "name", "price_daily", "price_monthly", "price_yearly",
|
"type_id", "name", "price_daily", "price_monthly", "price_yearly",
|
||||||
"credits_monthly", "introduction", "sale_type", "sale_link",
|
"credits_monthly", "introduction", "sale_type", "sale_link",
|
||||||
"sale_price_label", "sale_description", "status",
|
"sale_price_label", "sale_description", "status", "locale",
|
||||||
},
|
},
|
||||||
Wheres: []model.QueryWhere{
|
Wheres: []model.QueryWhere{
|
||||||
{Column: "type_id", Value: typeID},
|
{Column: "type_id", Value: typeID},
|
||||||
|
|
@ -317,8 +318,8 @@ func (u *DefaultUser) GetTypePricing(ctx context.Context, typeID string) (maps.M
|
||||||
return types[0], nil
|
return types[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublishedTypes retrieves all published types with pricing information
|
// GetPublishedTypes retrieves all published types with pricing information, optionally filtered by locale
|
||||||
func (u *DefaultUser) GetPublishedTypes(ctx context.Context, param model.QueryParam) ([]maps.MapStr, error) {
|
func (u *DefaultUser) GetPublishedTypes(ctx context.Context, param model.QueryParam, locale ...string) ([]maps.MapStr, error) {
|
||||||
// Add published status filter
|
// Add published status filter
|
||||||
param.Wheres = append(param.Wheres, model.QueryWhere{
|
param.Wheres = append(param.Wheres, model.QueryWhere{
|
||||||
Column: "status",
|
Column: "status",
|
||||||
|
|
@ -331,12 +332,20 @@ func (u *DefaultUser) GetPublishedTypes(ctx context.Context, param model.QueryPa
|
||||||
Value: true,
|
Value: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Add locale filter if provided
|
||||||
|
if len(locale) > 0 && locale[0] != "" {
|
||||||
|
param.Wheres = append(param.Wheres, model.QueryWhere{
|
||||||
|
Column: "locale",
|
||||||
|
Value: locale[0],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Set default select fields if not provided
|
// Set default select fields if not provided
|
||||||
if param.Select == nil {
|
if param.Select == nil {
|
||||||
param.Select = []interface{}{
|
param.Select = []interface{}{
|
||||||
"type_id", "name", "description", "price_daily", "price_monthly", "price_yearly",
|
"type_id", "name", "description", "price_daily", "price_monthly", "price_yearly",
|
||||||
"credits_monthly", "introduction", "sale_type", "sale_link",
|
"credits_monthly", "introduction", "sale_type", "sale_link",
|
||||||
"sale_price_label", "sale_description", "sort_order", "status", "is_active", "features", "limits",
|
"sale_price_label", "sale_description", "sort_order", "status", "locale", "is_active", "features", "limits",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ type TestTypeData struct {
|
||||||
IsDefault bool `json:"is_default"`
|
IsDefault bool `json:"is_default"`
|
||||||
SortOrder int `json:"sort_order"`
|
SortOrder int `json:"sort_order"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Locale string `json:"locale"`
|
||||||
DefaultRoleID string `json:"default_role_id"`
|
DefaultRoleID string `json:"default_role_id"`
|
||||||
MaxSessions *int `json:"max_sessions"`
|
MaxSessions *int `json:"max_sessions"`
|
||||||
SessionTimeout int `json:"session_timeout"`
|
SessionTimeout int `json:"session_timeout"`
|
||||||
|
|
@ -832,6 +833,7 @@ func TestTypePricingOperations(t *testing.T) {
|
||||||
testTypes := []struct {
|
testTypes := []struct {
|
||||||
TypeID string
|
TypeID string
|
||||||
Name string
|
Name string
|
||||||
|
Locale string
|
||||||
PriceDaily int
|
PriceDaily int
|
||||||
PriceMonthly int
|
PriceMonthly int
|
||||||
PriceYearly int
|
PriceYearly int
|
||||||
|
|
@ -846,6 +848,7 @@ func TestTypePricingOperations(t *testing.T) {
|
||||||
{
|
{
|
||||||
TypeID: "free_" + testUUID,
|
TypeID: "free_" + testUUID,
|
||||||
Name: "Free Plan",
|
Name: "Free Plan",
|
||||||
|
Locale: "en-us",
|
||||||
PriceDaily: 0,
|
PriceDaily: 0,
|
||||||
PriceMonthly: 0,
|
PriceMonthly: 0,
|
||||||
PriceYearly: 0,
|
PriceYearly: 0,
|
||||||
|
|
@ -858,6 +861,7 @@ func TestTypePricingOperations(t *testing.T) {
|
||||||
{
|
{
|
||||||
TypeID: "pro_" + testUUID,
|
TypeID: "pro_" + testUUID,
|
||||||
Name: "Pro Plan",
|
Name: "Pro Plan",
|
||||||
|
Locale: "en-us",
|
||||||
PriceDaily: 100,
|
PriceDaily: 100,
|
||||||
PriceMonthly: 2900,
|
PriceMonthly: 2900,
|
||||||
PriceYearly: 29900,
|
PriceYearly: 29900,
|
||||||
|
|
@ -870,6 +874,7 @@ func TestTypePricingOperations(t *testing.T) {
|
||||||
{
|
{
|
||||||
TypeID: "enterprise_" + testUUID,
|
TypeID: "enterprise_" + testUUID,
|
||||||
Name: "Enterprise Plan",
|
Name: "Enterprise Plan",
|
||||||
|
Locale: "en-us",
|
||||||
PriceDaily: 0,
|
PriceDaily: 0,
|
||||||
PriceMonthly: 0,
|
PriceMonthly: 0,
|
||||||
PriceYearly: 0,
|
PriceYearly: 0,
|
||||||
|
|
@ -884,6 +889,7 @@ func TestTypePricingOperations(t *testing.T) {
|
||||||
{
|
{
|
||||||
TypeID: "beta_" + testUUID,
|
TypeID: "beta_" + testUUID,
|
||||||
Name: "Beta Plan",
|
Name: "Beta Plan",
|
||||||
|
Locale: "en-us",
|
||||||
PriceDaily: 50,
|
PriceDaily: 50,
|
||||||
PriceMonthly: 1500,
|
PriceMonthly: 1500,
|
||||||
PriceYearly: 15000,
|
PriceYearly: 15000,
|
||||||
|
|
@ -899,6 +905,7 @@ func TestTypePricingOperations(t *testing.T) {
|
||||||
typeData := maps.MapStrAny{
|
typeData := maps.MapStrAny{
|
||||||
"type_id": testType.TypeID,
|
"type_id": testType.TypeID,
|
||||||
"name": testType.Name,
|
"name": testType.Name,
|
||||||
|
"locale": testType.Locale,
|
||||||
"price_daily": testType.PriceDaily,
|
"price_daily": testType.PriceDaily,
|
||||||
"price_monthly": testType.PriceMonthly,
|
"price_monthly": testType.PriceMonthly,
|
||||||
"price_yearly": testType.PriceYearly,
|
"price_yearly": testType.PriceYearly,
|
||||||
|
|
@ -1123,4 +1130,90 @@ func TestTypePricingOperations(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "type not found")
|
assert.Contains(t, err.Error(), "type not found")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Test locale support
|
||||||
|
t.Run("GetTypePricing_WithLocale", func(t *testing.T) {
|
||||||
|
// Create a zh-cn version of pro plan with unique type_id
|
||||||
|
zhCNTypeData := maps.MapStrAny{
|
||||||
|
"type_id": "pro_zh_" + testUUID,
|
||||||
|
"name": "专业版",
|
||||||
|
"locale": "zh-cn",
|
||||||
|
"price_daily": 7,
|
||||||
|
"price_monthly": 199,
|
||||||
|
"price_yearly": 1990,
|
||||||
|
"credits_monthly": 10000,
|
||||||
|
"introduction": "适合专业用户和团队协作",
|
||||||
|
"sale_type": "online",
|
||||||
|
"status": "published",
|
||||||
|
"is_active": true,
|
||||||
|
}
|
||||||
|
_, err := testProvider.CreateType(ctx, zhCNTypeData)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Get pricing for English version
|
||||||
|
pricingEN, err := testProvider.GetTypePricing(ctx, "pro_"+testUUID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "Pro Plan", pricingEN["name"])
|
||||||
|
assert.Equal(t, "en-us", pricingEN["locale"])
|
||||||
|
|
||||||
|
// Get pricing for Chinese version
|
||||||
|
pricingCN, err := testProvider.GetTypePricing(ctx, "pro_zh_"+testUUID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "专业版", pricingCN["name"])
|
||||||
|
assert.Equal(t, "zh-cn", pricingCN["locale"])
|
||||||
|
|
||||||
|
// Verify different prices (Note: EN price was updated to 3900 in SetTypePricing test)
|
||||||
|
priceMonthlyEN := pricingEN["price_monthly"]
|
||||||
|
switch v := priceMonthlyEN.(type) {
|
||||||
|
case int:
|
||||||
|
assert.Equal(t, 3900, v) // Updated price from SetTypePricing test
|
||||||
|
case int32:
|
||||||
|
assert.Equal(t, int32(3900), v)
|
||||||
|
case int64:
|
||||||
|
assert.Equal(t, int64(3900), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
priceMonthlyCN := pricingCN["price_monthly"]
|
||||||
|
switch v := priceMonthlyCN.(type) {
|
||||||
|
case int:
|
||||||
|
assert.Equal(t, 199, v)
|
||||||
|
case int32:
|
||||||
|
assert.Equal(t, int32(199), v)
|
||||||
|
case int64:
|
||||||
|
assert.Equal(t, int64(199), v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test GetPublishedTypes with locale filter
|
||||||
|
t.Run("GetPublishedTypes_WithLocale", func(t *testing.T) {
|
||||||
|
param := model.QueryParam{}
|
||||||
|
|
||||||
|
// Get English versions
|
||||||
|
typesEN, err := testProvider.GetPublishedTypes(ctx, param, "en-us")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.GreaterOrEqual(t, len(typesEN), 3) // At least free, pro, enterprise
|
||||||
|
|
||||||
|
enCount := 0
|
||||||
|
for _, typeRecord := range typesEN {
|
||||||
|
if strings.Contains(typeRecord["type_id"].(string), testUUID) {
|
||||||
|
assert.Equal(t, "en-us", typeRecord["locale"])
|
||||||
|
enCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.GreaterOrEqual(t, enCount, 3) // free, pro, enterprise
|
||||||
|
|
||||||
|
// Get Chinese version
|
||||||
|
typesCN, err := testProvider.GetPublishedTypes(ctx, param, "zh-cn")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.GreaterOrEqual(t, len(typesCN), 1) // At least pro_zh
|
||||||
|
|
||||||
|
cnCount := 0
|
||||||
|
for _, typeRecord := range typesCN {
|
||||||
|
if strings.Contains(typeRecord["type_id"].(string), testUUID) {
|
||||||
|
assert.Equal(t, "zh-cn", typeRecord["locale"])
|
||||||
|
cnCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.GreaterOrEqual(t, cnCount, 1) // pro_zh
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,12 @@ type UserProvider interface {
|
||||||
GetTypeConfiguration(ctx context.Context, typeID string) (maps.MapStrAny, error)
|
GetTypeConfiguration(ctx context.Context, typeID string) (maps.MapStrAny, error)
|
||||||
SetTypeConfiguration(ctx context.Context, typeID string, config maps.MapStrAny) error
|
SetTypeConfiguration(ctx context.Context, typeID string, config maps.MapStrAny) error
|
||||||
|
|
||||||
|
// Type Pricing and Status Management
|
||||||
|
GetTypePricing(ctx context.Context, typeID string) (maps.MapStrAny, error)
|
||||||
|
SetTypePricing(ctx context.Context, typeID string, pricing maps.MapStrAny) error
|
||||||
|
GetPublishedTypes(ctx context.Context, param model.QueryParam, locale ...string) ([]maps.MapStr, error)
|
||||||
|
UpdateTypeStatus(ctx context.Context, typeID string, status string) error
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Team Resource
|
// Team Resource
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,15 @@
|
||||||
"default": "draft",
|
"default": "draft",
|
||||||
"index": true
|
"index": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "locale",
|
||||||
|
"type": "string",
|
||||||
|
"label": "Locale",
|
||||||
|
"comment": "Language locale (e.g., en-us, zh-cn)",
|
||||||
|
"length": 10,
|
||||||
|
"default": "en-us",
|
||||||
|
"index": true
|
||||||
|
},
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Pricing & Subscription Fields
|
// Pricing & Subscription Fields
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue