feat: sync deployed v1.0.0-rc.38-custom.42
Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
This commit is contained in:
1 parent
aa6c3f50f4
commit
ceddfd3d6b
828 files changed
+122253
-13702
No files matched your search
@@ -0,0 +1,229 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// DetectionRun is both the durable task and wallet ledger. Public projections
|
||||
// are built explicitly; this record must never be serialized to anonymous users.
|
||||
type DetectionRun struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(64)"`
|
||||
UserID int `gorm:"index"`
|
||||
RequestKey string `gorm:"uniqueIndex;type:varchar(128)"`
|
||||
ChannelID int `gorm:"index"`
|
||||
GroupName string
|
||||
Kind string `gorm:"type:varchar(24)"`
|
||||
Status string `gorm:"type:varchar(24);index"`
|
||||
CreatedAt int64 `gorm:"autoCreateTime:false;index"`
|
||||
FinishedAt int64
|
||||
Deadline int64
|
||||
Reserved int
|
||||
Charged int
|
||||
Price string `gorm:"type:text"`
|
||||
RemoteID string
|
||||
Quality string
|
||||
HTML string `gorm:"type:text"`
|
||||
Result string `gorm:"type:text"`
|
||||
Error string
|
||||
Settled bool
|
||||
LogJSON string `gorm:"type:text"`
|
||||
LogDelivered bool `gorm:"index"`
|
||||
}
|
||||
|
||||
type DetectionCall struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(64)"`
|
||||
RunID string `gorm:"index;type:varchar(64)"`
|
||||
SecretHash string `gorm:"uniqueIndex;type:varchar(64)"`
|
||||
ExpiresAt int64
|
||||
StartedAt int64
|
||||
FinishedAt int64
|
||||
State string `gorm:"type:varchar(24)"`
|
||||
Usage string `gorm:"type:text"`
|
||||
Sample string `gorm:"type:text"`
|
||||
Quota int
|
||||
Available *bool
|
||||
Error string
|
||||
}
|
||||
|
||||
func DetectionSecretHash(secret string) string {
|
||||
sum := sha256.Sum256([]byte(secret))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func CreateDetection(run *DetectionRun) error {
|
||||
if run.Reserved < 0 || run.Reserved > common.MaxWalletQuota {
|
||||
return errors.New("invalid reservation")
|
||||
}
|
||||
original := *run
|
||||
err := relayAttemptTransaction(func(tx *gorm.DB) error {
|
||||
*run = original
|
||||
if run.UserID > 0 {
|
||||
var user User
|
||||
if err := lockForUpdate(tx).First(&user, run.UserID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var previous DetectionRun
|
||||
if err := tx.Where("request_key = ?", run.RequestKey).First(&previous).Error; err == nil {
|
||||
if previous.UserID != run.UserID || previous.GroupName != run.GroupName || previous.Kind != run.Kind {
|
||||
return errors.New("idempotency key conflict")
|
||||
}
|
||||
*run = previous
|
||||
return nil
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
var active int64
|
||||
if err := tx.Model(&DetectionRun{}).Where("user_id = ? AND settled = ?", run.UserID, false).Count(&active).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if active > 0 {
|
||||
return errors.New("a detection is already running")
|
||||
}
|
||||
if err := tx.Model(&DetectionRun{}).Where("user_id = ? AND group_name = ? AND kind = ? AND created_at > ?", run.UserID, run.GroupName, run.Kind, time.Now().Unix()-300).Count(&active).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if active > 0 {
|
||||
return errors.New("please wait five minutes between detections")
|
||||
}
|
||||
if user.Status != common.UserStatusEnabled || user.Quota < run.Reserved {
|
||||
return errors.New("insufficient wallet balance")
|
||||
}
|
||||
if err := tx.Model(&User{}).Where("id = ?", run.UserID).Update("quota", gorm.Expr("quota - ?", run.Reserved)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Create(run).Error
|
||||
})
|
||||
if err == nil && run.UserID > 0 {
|
||||
_ = invalidateUserCache(run.UserID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ClaimDetectionCall(hash string) (*DetectionCall, error) {
|
||||
var call DetectionCall
|
||||
err := relayAttemptTransaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("secret_hash = ?", hash).First(&call).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
var run DetectionRun
|
||||
if err := lockForUpdate(tx).First(&run, "id = ?", call.RunID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := lockForUpdate(tx).Where("id = ?", call.ID).First(&call).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
if run.Kind != "pelican" || call.State != "ready" || call.ExpiresAt <= now || run.Settled || run.Status == "failed" || run.Status == "succeeded" || run.Deadline-10 <= now {
|
||||
return errors.New("expired or consumed credential")
|
||||
}
|
||||
update := tx.Model(&DetectionCall{}).Where("id = ? AND state = ?", call.ID, "ready").Updates(map[string]any{"state": "running", "started_at": now})
|
||||
if update.Error != nil {
|
||||
return update.Error
|
||||
}
|
||||
if update.RowsAffected != 1 {
|
||||
return errors.New("credential already consumed")
|
||||
}
|
||||
call.State = "running"
|
||||
call.StartedAt = now
|
||||
return nil
|
||||
})
|
||||
return &call, err
|
||||
}
|
||||
|
||||
func FinishDetectionCall(call *DetectionCall) error {
|
||||
if call.Quota < 0 || call.Quota > common.MaxWalletQuota {
|
||||
return errors.New("invalid charge")
|
||||
}
|
||||
return DB.Model(&DetectionCall{}).Where("id = ? AND state = ?", call.ID, "running").Updates(map[string]any{
|
||||
"state": "finished", "finished_at": time.Now().Unix(), "usage": call.Usage, "sample": call.Sample, "quota": call.Quota, "available": call.Available, "error": call.Error,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func SettleDetection(id string) error {
|
||||
var owner int
|
||||
err := relayAttemptTransaction(func(tx *gorm.DB) error {
|
||||
var run DetectionRun
|
||||
if err := lockForUpdate(tx).First(&run, "id = ?", id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
owner = run.UserID
|
||||
if run.Settled {
|
||||
return nil
|
||||
}
|
||||
var calls []DetectionCall
|
||||
if err := tx.Where("run_id = ?", id).Find(&calls).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
charge := 0
|
||||
for _, call := range calls {
|
||||
if call.State == "running" {
|
||||
return errors.New("generation still running")
|
||||
}
|
||||
if call.Quota < 0 || call.Quota > run.Reserved-charge {
|
||||
return errors.New("charge exceeds reservation")
|
||||
}
|
||||
charge += call.Quota
|
||||
}
|
||||
logJSON := ""
|
||||
if owner > 0 {
|
||||
var user User
|
||||
if err := lockForUpdate(tx).Unscoped().First(&user, owner).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if user.Quota > common.MaxWalletQuota-(run.Reserved-charge) || user.UsedQuota > common.MaxWalletQuota-charge {
|
||||
return errors.New("wallet settlement exceeds quota limit")
|
||||
}
|
||||
if err := tx.Unscoped().Model(&User{}).Where("id = ?", owner).Updates(map[string]any{"quota": gorm.Expr("quota + ?", run.Reserved-charge), "used_quota": gorm.Expr("used_quota + ?", charge)}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
key := "detection:" + id
|
||||
other, _ := common.Marshal(map[string]any{"billing_source": "wallet", "detection_id": id, "detection_kind": run.Kind, "reserved": run.Reserved, "refunded": run.Reserved - charge})
|
||||
entry := Log{AttemptKey: &key, UserId: owner, Username: user.Username, CreatedAt: time.Now().Unix(), Type: LogTypeSystem, Content: fmt.Sprintf("%s detection: charged %d quota; refunded %d quota", run.Kind, charge, run.Reserved-charge), Quota: charge, Group: run.GroupName, ModelName: "gpt-6-astra", RequestId: id, Other: string(other)}
|
||||
encoded, err := common.Marshal(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logJSON = string(encoded)
|
||||
} else {
|
||||
charge = 0
|
||||
}
|
||||
if err := tx.Model(&DetectionCall{}).Where("run_id = ? AND state = ?", id, "ready").Update("state", "revoked").Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&DetectionRun{}).Where("id = ?", id).Updates(map[string]any{"settled": true, "charged": charge, "finished_at": time.Now().Unix(), "log_json": logJSON}).Error
|
||||
})
|
||||
if err == nil && owner > 0 {
|
||||
_ = invalidateUserCache(owner)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Detection ledger delivery is idempotent and separate from request metrics.
|
||||
func DeliverDetectionLogs() error {
|
||||
var runs []DetectionRun
|
||||
if err := DB.Where("settled = ? AND log_delivered = ? AND log_json <> ?", true, false, "").Limit(100).Find(&runs).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, run := range runs {
|
||||
var entry Log
|
||||
if err := common.UnmarshalJsonStr(run.LogJSON, &entry); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := LOG_DB.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "attempt_key"}}, DoNothing: true}).Create(&entry).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := DB.Model(&DetectionRun{}).Where("id = ?", run.ID).Update("log_delivered", true).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user