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:
Max 2025-10-13 16:16:40 +08:00
parent 15f0750253
commit 87465379be
6 changed files with 264 additions and 147 deletions

File diff suppressed because one or more lines are too long

View file

@ -116,7 +116,7 @@ var (
// DefaultTypeFields contains basic type fields
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",
"price_yearly", "credits_monthly", "created_at", "updated_at",
}
@ -124,7 +124,7 @@ var (
// DefaultTypeDetailFields contains all type fields including configuration and metadata
DefaultTypeDetailFields = []interface{}{
"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",
"credits_monthly", "introduction", "sale_type", "sale_link", "sale_price_label",
"sale_description", "created_at", "updated_at",

View file

@ -294,11 +294,12 @@ func (u *DefaultUser) SetTypeConfiguration(ctx context.Context, typeID string, c
// GetTypePricing retrieves pricing information for a type
func (u *DefaultUser) GetTypePricing(ctx context.Context, typeID string) (maps.MapStrAny, error) {
m := model.Select(u.typeModel)
types, err := m.Get(model.QueryParam{
Select: []interface{}{
"type_id", "name", "price_daily", "price_monthly", "price_yearly",
"credits_monthly", "introduction", "sale_type", "sale_link",
"sale_price_label", "sale_description", "status",
"sale_price_label", "sale_description", "status", "locale",
},
Wheres: []model.QueryWhere{
{Column: "type_id", Value: typeID},
@ -317,8 +318,8 @@ func (u *DefaultUser) GetTypePricing(ctx context.Context, typeID string) (maps.M
return types[0], nil
}
// GetPublishedTypes retrieves all published types with pricing information
func (u *DefaultUser) GetPublishedTypes(ctx context.Context, param model.QueryParam) ([]maps.MapStr, error) {
// GetPublishedTypes retrieves all published types with pricing information, optionally filtered by locale
func (u *DefaultUser) GetPublishedTypes(ctx context.Context, param model.QueryParam, locale ...string) ([]maps.MapStr, error) {
// Add published status filter
param.Wheres = append(param.Wheres, model.QueryWhere{
Column: "status",
@ -331,12 +332,20 @@ func (u *DefaultUser) GetPublishedTypes(ctx context.Context, param model.QueryPa
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
if param.Select == nil {
param.Select = []interface{}{
"type_id", "name", "description", "price_daily", "price_monthly", "price_yearly",
"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",
}
}

View file

@ -20,6 +20,7 @@ type TestTypeData struct {
IsDefault bool `json:"is_default"`
SortOrder int `json:"sort_order"`
Status string `json:"status"`
Locale string `json:"locale"`
DefaultRoleID string `json:"default_role_id"`
MaxSessions *int `json:"max_sessions"`
SessionTimeout int `json:"session_timeout"`
@ -832,6 +833,7 @@ func TestTypePricingOperations(t *testing.T) {
testTypes := []struct {
TypeID string
Name string
Locale string
PriceDaily int
PriceMonthly int
PriceYearly int
@ -846,6 +848,7 @@ func TestTypePricingOperations(t *testing.T) {
{
TypeID: "free_" + testUUID,
Name: "Free Plan",
Locale: "en-us",
PriceDaily: 0,
PriceMonthly: 0,
PriceYearly: 0,
@ -858,6 +861,7 @@ func TestTypePricingOperations(t *testing.T) {
{
TypeID: "pro_" + testUUID,
Name: "Pro Plan",
Locale: "en-us",
PriceDaily: 100,
PriceMonthly: 2900,
PriceYearly: 29900,
@ -870,6 +874,7 @@ func TestTypePricingOperations(t *testing.T) {
{
TypeID: "enterprise_" + testUUID,
Name: "Enterprise Plan",
Locale: "en-us",
PriceDaily: 0,
PriceMonthly: 0,
PriceYearly: 0,
@ -884,6 +889,7 @@ func TestTypePricingOperations(t *testing.T) {
{
TypeID: "beta_" + testUUID,
Name: "Beta Plan",
Locale: "en-us",
PriceDaily: 50,
PriceMonthly: 1500,
PriceYearly: 15000,
@ -899,6 +905,7 @@ func TestTypePricingOperations(t *testing.T) {
typeData := maps.MapStrAny{
"type_id": testType.TypeID,
"name": testType.Name,
"locale": testType.Locale,
"price_daily": testType.PriceDaily,
"price_monthly": testType.PriceMonthly,
"price_yearly": testType.PriceYearly,
@ -1123,4 +1130,90 @@ func TestTypePricingOperations(t *testing.T) {
assert.Error(t, err)
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
})
}

View file

@ -247,6 +247,12 @@ type UserProvider interface {
GetTypeConfiguration(ctx context.Context, typeID string) (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
// ============================================================================

View file

@ -107,6 +107,15 @@
"default": "draft",
"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