Initial open-source release (MIT): image2api AI gateway
Full Go backend + Vue 3 frontend, OpenAI-compatible API, multi-provider account pools, billing/admin, Docker one-command deploy with auto HTTPS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID string `gorm:"primaryKey;size:32"`
|
||||
Email string `gorm:"size:255;uniqueIndex;not null"`
|
||||
Name string `gorm:"size:255"`
|
||||
PasswordHash string `gorm:"size:255"`
|
||||
Role string `gorm:"size:32;index;not null"`
|
||||
Status string `gorm:"size:32;index;not null"`
|
||||
Credits float64 `gorm:"not null;default:0"`
|
||||
Notes string `gorm:"type:text"`
|
||||
InviteCode string `gorm:"size:32;uniqueIndex"`
|
||||
InvitedBy *string `gorm:"size:32;index"`
|
||||
InviteRewardDone bool `gorm:"not null;default:false"`
|
||||
InviteRewardAt *time.Time
|
||||
CheckinLast string `gorm:"size:32"`
|
||||
CheckinStreak int `gorm:"not null;default:0"`
|
||||
LastLoginAt *time.Time
|
||||
LastLoginIP string `gorm:"size:128"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
APIKeys []APIKey `gorm:"foreignKey:UserID"`
|
||||
}
|
||||
|
||||
type APIKey struct {
|
||||
ID string `gorm:"primaryKey;size:32"`
|
||||
UserID string `gorm:"size:32;index;not null"`
|
||||
Name string `gorm:"size:100;not null"`
|
||||
KeyPreview string `gorm:"size:32;not null"`
|
||||
KeyHash string `gorm:"size:255;uniqueIndex;not null"`
|
||||
CreatedAt time.Time
|
||||
LastUsedAt *time.Time
|
||||
}
|
||||
|
||||
type ShowcaseItem struct {
|
||||
ID string `gorm:"primaryKey;size:32"`
|
||||
Kind string `gorm:"size:32;index;not null"`
|
||||
Title string `gorm:"size:255"`
|
||||
Subtitle string `gorm:"size:255"`
|
||||
Prompt string `gorm:"type:text"`
|
||||
Gradient string `gorm:"type:text"`
|
||||
Span string `gorm:"size:100"`
|
||||
Image string `gorm:"size:500;index"`
|
||||
Weight int `gorm:"not null;default:0"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type EventLog struct {
|
||||
ID string `gorm:"primaryKey;size:32"`
|
||||
TS time.Time `gorm:"index;not null"`
|
||||
Kind string `gorm:"size:32;index;not null"`
|
||||
Status string `gorm:"size:32;index;not null"`
|
||||
Model string `gorm:"size:255;index"`
|
||||
Provider string `gorm:"size:100;index"`
|
||||
Prompt string `gorm:"type:text"`
|
||||
Ratio string `gorm:"size:32"`
|
||||
Resolution string `gorm:"size:32"`
|
||||
Duration string `gorm:"size:32"`
|
||||
Refs int `gorm:"not null;default:0"`
|
||||
RefFiles datatypes.JSON `gorm:"type:jsonb"` // relative paths of saved reference images, for回显 on reload
|
||||
Source string `gorm:"size:32;index"`
|
||||
// AccountID is the provider token/account chosen to fulfil this generation,
|
||||
// stamped when the upstream call begins. Drives the accounts view's live
|
||||
// in-flight count (pending events per account) and lets an abandoned-event
|
||||
// purge attribute the failure back to the account it was using.
|
||||
AccountID string `gorm:"size:64;index"`
|
||||
UserID string `gorm:"size:32;index"`
|
||||
Cost float64 `gorm:"not null;default:0"`
|
||||
// Refunded marks that this event's up-front charge has already been credited
|
||||
// back, so the normal failure path and the abandoned-purge sweep can never
|
||||
// double-refund the same generation.
|
||||
Refunded bool `gorm:"not null;default:false"`
|
||||
ElapsedMS int `gorm:"not null;default:0"`
|
||||
File string `gorm:"size:500;index"`
|
||||
Error string `gorm:"type:text"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type ModelConfig struct {
|
||||
ID string `gorm:"primaryKey;size:255"`
|
||||
Type string `gorm:"size:32;index;not null"`
|
||||
Name string `gorm:"size:255;not null"`
|
||||
Provider string `gorm:"size:100;index;not null"`
|
||||
Enabled bool `gorm:"not null;default:true"`
|
||||
Ratios datatypes.JSON `gorm:"type:jsonb"`
|
||||
Prices datatypes.JSONMap `gorm:"type:jsonb"`
|
||||
Resolutions datatypes.JSON `gorm:"type:jsonb"`
|
||||
ImageToImage bool `gorm:"not null;default:false"`
|
||||
DurationPrices datatypes.JSONMap `gorm:"type:jsonb"`
|
||||
// Agent (代理) pricing — optional overlay over Prices/DurationPrices. A tier
|
||||
// left unset here means agent users pay the normal price for that tier; the
|
||||
// set of *supported* tiers is always driven by Prices, not these.
|
||||
PricesAgent datatypes.JSONMap `gorm:"type:jsonb;column:prices_agent"`
|
||||
DurationPricesAgent datatypes.JSONMap `gorm:"type:jsonb;column:duration_prices_agent"`
|
||||
Durations datatypes.JSON `gorm:"type:jsonb"`
|
||||
MaxReferenceImages int `gorm:"not null;default:0"`
|
||||
ReferenceMode string `gorm:"size:32;not null;default:'none'"`
|
||||
// Weight controls display order in the model dropdown / admin list: higher
|
||||
// weight floats to the top (matches ShowcaseItem.Weight semantics). Ties fall
|
||||
// back to created_at desc. Default 0.
|
||||
Weight int `gorm:"not null;default:0;index"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type CDKCode struct {
|
||||
Code string `gorm:"primaryKey;size:32"`
|
||||
Amount int `gorm:"not null"`
|
||||
Status string `gorm:"size:32;index;not null"`
|
||||
Type string `gorm:"size:16;not null;default:normal;index"` // normal | marketing
|
||||
BatchID string `gorm:"size:32;index"` // groups one generate call
|
||||
Note string `gorm:"type:text"`
|
||||
RedeemedBy *string `gorm:"size:32;index"`
|
||||
RedeemedAt *time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type TokenAccount struct {
|
||||
ID string `gorm:"primaryKey;size:64"`
|
||||
Pool string `gorm:"size:64;index;not null"`
|
||||
Value string `gorm:"type:text"`
|
||||
Status string `gorm:"size:32;index;not null"`
|
||||
Fails int `gorm:"not null;default:0"`
|
||||
FailTotal int `gorm:"not null;default:0"`
|
||||
SuccessTotal int `gorm:"not null;default:0"`
|
||||
Dead bool `gorm:"not null;default:false"`
|
||||
Meta datatypes.JSONMap `gorm:"type:jsonb"`
|
||||
AddedAt *time.Time
|
||||
LastUsedAt *time.Time
|
||||
CachedQuotaResetAfter string `gorm:"size:128"`
|
||||
QuotaRecoverAt *time.Time
|
||||
// Adobe quota is tracked separately for image vs video. An account only
|
||||
// enters the shared "quota" waiting status when BOTH are limited; a single
|
||||
// limit leaves the account usable for the other kind. Recovery time is shared
|
||||
// (QuotaRecoverAt / CachedQuotaResetAfter) since Adobe resets both at once.
|
||||
ImageLimited bool `gorm:"not null;default:false"`
|
||||
VideoLimited bool `gorm:"not null;default:false"`
|
||||
AccountEmail string `gorm:"size:255"`
|
||||
AccountDisplayName string `gorm:"size:255"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type RefreshProfile struct {
|
||||
ID string `gorm:"primaryKey;size:64"`
|
||||
Name string `gorm:"size:255;not null"`
|
||||
Pool string `gorm:"size:64;index;not null"`
|
||||
Kind string `gorm:"size:64;index;not null"`
|
||||
Cookie string `gorm:"type:text"`
|
||||
Enabled bool `gorm:"not null;default:true"`
|
||||
IntervalSeconds int `gorm:"not null;default:54000"`
|
||||
ImportedAt *time.Time
|
||||
LastAttemptAt *time.Time
|
||||
LastSuccessAt *time.Time
|
||||
LastError string `gorm:"type:text"`
|
||||
NextRetryAt *time.Time
|
||||
ConsecutiveFailures int `gorm:"not null;default:0"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type SiteSetting struct {
|
||||
Key string `gorm:"primaryKey;size:100"`
|
||||
Value string `gorm:"type:text"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
func AutoMigrateModels() []any {
|
||||
return []any{
|
||||
&User{},
|
||||
&APIKey{},
|
||||
&ShowcaseItem{},
|
||||
&EventLog{},
|
||||
&ModelConfig{},
|
||||
&CDKCode{},
|
||||
&TokenAccount{},
|
||||
&RefreshProfile{},
|
||||
&SiteSetting{},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user