mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-04 07:19:35 +00:00
234 lines
7.1 KiB
Go
234 lines
7.1 KiB
Go
package model
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/QuantumNous/new-api/constant"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
type TaskPluginChannelRef struct {
|
|
Id int `json:"id"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func GetTaskPluginUsage(key string) ([]TaskPluginChannelRef, int64, error) {
|
|
var channels []Channel
|
|
if err := DB.Where("type = ? AND status = ?", constant.ChannelTypeTaskPlugin, common.ChannelStatusEnabled).Find(&channels).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
refs := make([]TaskPluginChannelRef, 0)
|
|
for _, channel := range channels {
|
|
if channel.GetSetting().TaskPluginKey == key {
|
|
refs = append(refs, TaskPluginChannelRef{Id: channel.Id, Name: channel.Name})
|
|
}
|
|
}
|
|
var inFlight int64
|
|
err := DB.Model(&Task{}).Where("platform = ? AND status NOT IN ?", key, []TaskStatus{TaskStatusSuccess, TaskStatusFailure}).Count(&inFlight).Error
|
|
return refs, inFlight, err
|
|
}
|
|
|
|
type TaskPlugin struct {
|
|
Id int64 `json:"id"`
|
|
Key string `json:"key" gorm:"size:128;not null;uniqueIndex:uk_task_plugin_key_version,priority:1"`
|
|
APIVersion int `json:"api_version" gorm:"not null"`
|
|
Version string `json:"version" gorm:"size:64;not null;uniqueIndex:uk_task_plugin_key_version,priority:2"`
|
|
Source string `json:"source" gorm:"type:text;not null"`
|
|
SourceHash string `json:"source_hash" gorm:"size:64;not null"`
|
|
Enabled bool `json:"enabled" gorm:"not null"`
|
|
Active bool `json:"active" gorm:"not null;index"`
|
|
CreatedAt int64 `json:"created_at" gorm:"not null"`
|
|
Remark string `json:"remark" gorm:"type:text"`
|
|
}
|
|
|
|
func SaveTaskPlugin(plugin *TaskPlugin) error {
|
|
return DB.Transaction(func(tx *gorm.DB) error {
|
|
var existing TaskPlugin
|
|
err := tx.Where(&TaskPlugin{Key: plugin.Key, Version: plugin.Version}).First(&existing).Error
|
|
if err == nil {
|
|
if existing.SourceHash != plugin.SourceHash {
|
|
return errors.New("plugin key and version already exist with different source")
|
|
}
|
|
if err = tx.Model(&existing).Updates(map[string]any{"enabled": plugin.Enabled, "remark": plugin.Remark}).Error; err != nil {
|
|
return err
|
|
}
|
|
existing.Enabled = plugin.Enabled
|
|
existing.Remark = plugin.Remark
|
|
*plugin = existing
|
|
return nil
|
|
}
|
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return err
|
|
}
|
|
plugin.CreatedAt = time.Now().Unix()
|
|
var count int64
|
|
if err = tx.Model(&TaskPlugin{}).Where(&TaskPlugin{Key: plugin.Key, Active: true}).Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
plugin.Active = count == 0
|
|
return tx.Create(plugin).Error
|
|
})
|
|
}
|
|
|
|
func ListTaskPluginVersions(key string) ([]TaskPlugin, error) {
|
|
var plugins []TaskPlugin
|
|
err := DB.Where(&TaskPlugin{Key: key}).Order("created_at DESC, id DESC").Find(&plugins).Error
|
|
return plugins, err
|
|
}
|
|
|
|
func ListTaskPlugins() ([]TaskPlugin, error) {
|
|
var plugins []TaskPlugin
|
|
err := DB.
|
|
Order(clause.OrderByColumn{Column: clause.Column{Name: "key"}}).
|
|
Order(clause.OrderByColumn{Column: clause.Column{Name: "created_at"}, Desc: true}).
|
|
Order(clause.OrderByColumn{Column: clause.Column{Name: "id"}, Desc: true}).
|
|
Find(&plugins).Error
|
|
return plugins, err
|
|
}
|
|
|
|
func GetTaskPluginVersion(key, version string) (*TaskPlugin, error) {
|
|
var plugin TaskPlugin
|
|
query := DB.Where(&TaskPlugin{Key: key})
|
|
if version == "" {
|
|
query = query.Where(&TaskPlugin{Active: true})
|
|
} else {
|
|
query = query.Where(&TaskPlugin{Version: version})
|
|
}
|
|
if err := query.First(&plugin).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &plugin, nil
|
|
}
|
|
|
|
func ListActiveTaskPlugins() ([]TaskPlugin, error) {
|
|
snapshot, err := GetTaskPluginSyncSnapshot()
|
|
return snapshot.Plugins, err
|
|
}
|
|
|
|
type TaskPluginSyncSnapshot struct {
|
|
Plugins []TaskPlugin
|
|
Revision string
|
|
}
|
|
|
|
// GetTaskPluginSyncSnapshot returns the enabled override set together with a
|
|
// deterministic revision of every active database override. Nodes can compare
|
|
// the revision even though their local routing-generation counters differ.
|
|
func GetTaskPluginSyncSnapshot() (TaskPluginSyncSnapshot, error) {
|
|
var activePlugins []TaskPlugin
|
|
if err := DB.Where(&TaskPlugin{Active: true}).
|
|
Order(clause.OrderByColumn{Column: clause.Column{Name: "key"}}).
|
|
Order(clause.OrderByColumn{Column: clause.Column{Name: "version"}}).
|
|
Order(clause.OrderByColumn{Column: clause.Column{Name: "id"}}).
|
|
Find(&activePlugins).Error; err != nil {
|
|
return TaskPluginSyncSnapshot{}, err
|
|
}
|
|
|
|
type revisionEntry struct {
|
|
Key string `json:"key"`
|
|
APIVersion int `json:"api_version"`
|
|
Version string `json:"version"`
|
|
SourceHash string `json:"source_hash"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
entries := make([]revisionEntry, 0, len(activePlugins))
|
|
enabledPlugins := make([]TaskPlugin, 0, len(activePlugins))
|
|
for _, plugin := range activePlugins {
|
|
entries = append(entries, revisionEntry{
|
|
Key: plugin.Key,
|
|
APIVersion: plugin.APIVersion,
|
|
Version: plugin.Version,
|
|
SourceHash: plugin.SourceHash,
|
|
Enabled: plugin.Enabled,
|
|
})
|
|
if plugin.Enabled {
|
|
enabledPlugins = append(enabledPlugins, plugin)
|
|
}
|
|
}
|
|
sort.Slice(entries, func(i, j int) bool {
|
|
if entries[i].Key != entries[j].Key {
|
|
return entries[i].Key < entries[j].Key
|
|
}
|
|
return entries[i].Version < entries[j].Version
|
|
})
|
|
payload, err := common.Marshal(entries)
|
|
if err != nil {
|
|
return TaskPluginSyncSnapshot{}, err
|
|
}
|
|
digest := sha256.Sum256(payload)
|
|
return TaskPluginSyncSnapshot{
|
|
Plugins: enabledPlugins,
|
|
Revision: hex.EncodeToString(digest[:]),
|
|
}, nil
|
|
}
|
|
|
|
func ActivateTaskPlugin(key, version string) error {
|
|
return DB.Transaction(func(tx *gorm.DB) error {
|
|
var target TaskPlugin
|
|
if err := tx.Where(&TaskPlugin{Key: key, Version: version}).First(&target).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Model(&TaskPlugin{}).Where(&TaskPlugin{Key: key}).Update("active", false).Error; err != nil {
|
|
return err
|
|
}
|
|
return tx.Model(&target).Updates(map[string]any{"active": true, "enabled": true}).Error
|
|
})
|
|
}
|
|
|
|
func SetTaskPluginEnabled(key string, enabled bool) error {
|
|
result := DB.Model(&TaskPlugin{}).Where(&TaskPlugin{Key: key, Active: true}).Update("enabled", enabled)
|
|
if result.Error != nil {
|
|
return result.Error
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type TaskPluginDeleteResult struct {
|
|
DeletedActive bool
|
|
Promoted *TaskPlugin
|
|
}
|
|
|
|
func DeleteTaskPluginVersion(key, version string) (TaskPluginDeleteResult, error) {
|
|
result := TaskPluginDeleteResult{}
|
|
err := DB.Transaction(func(tx *gorm.DB) error {
|
|
var plugin TaskPlugin
|
|
if err := lockForUpdate(tx).Where(&TaskPlugin{Key: key, Version: version}).First(&plugin).Error; err != nil {
|
|
return err
|
|
}
|
|
result.DeletedActive = plugin.Active
|
|
if err := tx.Delete(&plugin).Error; err != nil {
|
|
return err
|
|
}
|
|
if !plugin.Active {
|
|
return nil
|
|
}
|
|
|
|
var promoted TaskPlugin
|
|
err := lockForUpdate(tx).
|
|
Where(&TaskPlugin{Key: key}).
|
|
Order("created_at DESC, id DESC").
|
|
First(&promoted).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = tx.Model(&promoted).Update("active", true).Error; err != nil {
|
|
return err
|
|
}
|
|
promoted.Active = true
|
|
result.Promoted = &promoted
|
|
return nil
|
|
})
|
|
return result, err
|
|
}
|