mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
refactor(api): split Provider into ProviderGroup and APIKey models
Restructure the provider management system by separating the monolithic Provider model into two distinct entities: - ProviderGroup: defines shared upstream configuration (type, base_url, google settings, models, status) - APIKey: represents individual credentials within a group (api_key, weight, status, auto_ban, ban settings) This change also updates: - Binding model to reference GroupID instead of RouteGroup string - All CRUD handlers for the new provider-group and api-key endpoints - Sync service to rebuild provider snapshots from joined tables - Model registry to aggregate capabilities across group/key pairs - Access handler to validate namespace existence and subset constraints - Migration importer to handle the new schema structure - All related tests to use the new model relationships BREAKING CHANGE: Provider API endpoints replaced with /provider-groups and /api-keys endpoints; Binding.RouteGroup replaced with Binding.GroupID
This commit is contained in:
@@ -258,14 +258,45 @@ func (i *Importer) importMasters(items []Master, summary *ImportSummary) (map[st
|
||||
}
|
||||
|
||||
func (i *Importer) importProviders(items []Provider, summary *ImportSummary) error {
|
||||
groupCache := make(map[string]model.ProviderGroup)
|
||||
for _, item := range items {
|
||||
name := strings.TrimSpace(item.Name)
|
||||
if name == "" {
|
||||
summary.Warnings = append(summary.Warnings, "skip provider with empty name")
|
||||
groupName := normalizeGroup(item.PrimaryGroup)
|
||||
if strings.TrimSpace(groupName) == "" {
|
||||
groupName = "default"
|
||||
}
|
||||
group, ok := groupCache[groupName]
|
||||
if !ok {
|
||||
var existing model.ProviderGroup
|
||||
err := i.db.Where("name = ?", groupName).First(&existing).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
group = existing
|
||||
} else {
|
||||
group = model.ProviderGroup{
|
||||
Name: groupName,
|
||||
Type: strings.TrimSpace(item.Type),
|
||||
BaseURL: strings.TrimSpace(item.BaseURL),
|
||||
Models: strings.Join(item.Models, ","),
|
||||
Status: normalizeStatus(item.Status, "active"),
|
||||
}
|
||||
if !i.opts.DryRun {
|
||||
if err := i.db.Create(&group).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
groupCache[groupName] = group
|
||||
}
|
||||
|
||||
apiKey := strings.TrimSpace(item.APIKey)
|
||||
if apiKey == "" {
|
||||
summary.Warnings = append(summary.Warnings, "skip api key with empty api_key")
|
||||
continue
|
||||
}
|
||||
var existing model.Provider
|
||||
err := i.db.Where("name = ?", name).First(&existing).Error
|
||||
var existingKey model.APIKey
|
||||
err := i.db.Where("group_id = ? AND api_key = ?", group.ID, apiKey).First(&existingKey).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
@@ -277,16 +308,11 @@ func (i *Importer) importProviders(items []Provider, summary *ImportSummary) err
|
||||
continue
|
||||
}
|
||||
update := map[string]any{
|
||||
"type": strings.TrimSpace(item.Type),
|
||||
"base_url": strings.TrimSpace(item.BaseURL),
|
||||
"api_key": strings.TrimSpace(item.APIKey),
|
||||
"group": normalizeGroup(item.PrimaryGroup),
|
||||
"models": strings.Join(item.Models, ","),
|
||||
"weight": resolveWeight(item.Weight, item.Priority),
|
||||
"status": normalizeProviderStatus(item.Status),
|
||||
"auto_ban": item.AutoBan,
|
||||
}
|
||||
if err := i.db.Model(&existing).Updates(update).Error; err != nil {
|
||||
if err := i.db.Model(&existingKey).Updates(update).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
summary.ProvidersUpdated++
|
||||
@@ -301,18 +327,14 @@ func (i *Importer) importProviders(items []Provider, summary *ImportSummary) err
|
||||
continue
|
||||
}
|
||||
|
||||
provider := model.Provider{
|
||||
Name: name,
|
||||
Type: strings.TrimSpace(item.Type),
|
||||
BaseURL: strings.TrimSpace(item.BaseURL),
|
||||
APIKey: strings.TrimSpace(item.APIKey),
|
||||
Group: normalizeGroup(item.PrimaryGroup),
|
||||
Models: strings.Join(item.Models, ","),
|
||||
key := model.APIKey{
|
||||
GroupID: group.ID,
|
||||
APIKey: apiKey,
|
||||
Weight: resolveWeight(item.Weight, item.Priority),
|
||||
Status: normalizeProviderStatus(item.Status),
|
||||
AutoBan: item.AutoBan,
|
||||
}
|
||||
if err := i.db.Create(&provider).Error; err != nil {
|
||||
if err := i.db.Create(&key).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
summary.ProvidersCreated++
|
||||
@@ -420,8 +442,14 @@ func (i *Importer) importBindings(items []Binding, summary *ImportSummary) error
|
||||
summary.Warnings = append(summary.Warnings, "skip binding with empty model")
|
||||
continue
|
||||
}
|
||||
groupName := normalizeGroup(item.RouteGroup)
|
||||
var group model.ProviderGroup
|
||||
if err := i.db.Where("name = ?", groupName).First(&group).Error; err != nil {
|
||||
summary.Warnings = append(summary.Warnings, "skip binding with missing provider group: "+groupName)
|
||||
continue
|
||||
}
|
||||
var existing model.Binding
|
||||
err := i.db.Where("namespace = ? AND public_model = ?", ns, publicModel).First(&existing).Error
|
||||
err := i.db.Where("namespace = ? AND public_model = ? AND group_id = ?", ns, publicModel, group.ID).First(&existing).Error
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
@@ -433,7 +461,8 @@ func (i *Importer) importBindings(items []Binding, summary *ImportSummary) error
|
||||
continue
|
||||
}
|
||||
update := map[string]any{
|
||||
"route_group": normalizeGroup(item.RouteGroup),
|
||||
"group_id": group.ID,
|
||||
"weight": 1,
|
||||
"selector_type": "exact",
|
||||
"selector_value": publicModel,
|
||||
"status": normalizeStatus(item.Status, "active"),
|
||||
@@ -456,7 +485,8 @@ func (i *Importer) importBindings(items []Binding, summary *ImportSummary) error
|
||||
binding := model.Binding{
|
||||
Namespace: ns,
|
||||
PublicModel: publicModel,
|
||||
RouteGroup: normalizeGroup(item.RouteGroup),
|
||||
GroupID: group.ID,
|
||||
Weight: 1,
|
||||
SelectorType: "exact",
|
||||
SelectorValue: publicModel,
|
||||
Status: normalizeStatus(item.Status, "active"),
|
||||
|
||||
@@ -92,7 +92,7 @@ type Key struct {
|
||||
// Binding represents an EZ-API binding (optional, from abilities).
|
||||
type Binding struct {
|
||||
Namespace string `json:"namespace"`
|
||||
RouteGroup string `json:"route_group"`
|
||||
RouteGroup string `json:"route_group"` // provider group name
|
||||
Model string `json:"model"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user