feat: configure OpenAI-compatible AI services and discover models
douyin-release-gate / verify (push) Failing after 19m29s

This commit is contained in:
2026-10-07 17:17:33 +08:00
parent 51091cf0c5
commit f345e368df
19 changed files with 1135 additions and 408 deletions
@@ -0,0 +1,5 @@
ALTER TABLE creator_settings
ADD COLUMN ai_base_url TEXT NOT NULL DEFAULT '',
ADD COLUMN ai_api_key TEXT NOT NULL DEFAULT '',
DROP COLUMN ai_provider,
DROP COLUMN ai_configured;
@@ -0,0 +1,86 @@
package environment
import (
"context"
"database/sql"
"fmt"
"net/url"
"os"
"testing"
"time"
)
func TestOpenAISettingsSchemaFreshUpgradeAndRestart(t *testing.T) {
databaseURL := os.Getenv("CREATORHUB_POSTGRES_TEST_URL")
if databaseURL == "" {
t.Skip("set CREATORHUB_POSTGRES_TEST_URL to run migration tests")
}
ctx := context.Background()
admin, err := sql.Open("pgx", databaseURL)
if err != nil {
t.Fatal(err)
}
defer admin.Close()
schema := fmt.Sprintf("openai_upgrade_%d", time.Now().UnixNano())
if _, err := admin.ExecContext(ctx, "CREATE SCHEMA "+schema); err != nil {
t.Fatal(err)
}
defer func() {
if _, err := admin.ExecContext(ctx, "DROP SCHEMA "+schema+" CASCADE"); err != nil {
t.Errorf("cleanup: %v", err)
}
}()
parsed, err := url.Parse(databaseURL)
if err != nil {
t.Fatal(err)
}
query := parsed.Query()
query.Set("search_path", schema)
parsed.RawQuery = query.Encode()
store, err := Open(ctx, parsed.String())
if err != nil {
t.Fatal(err)
}
defer store.Close()
assertColumns := func() {
t.Helper()
var columns int
if err := store.db.QueryRowContext(ctx, `SELECT count(*) FROM information_schema.columns WHERE table_schema=current_schema() AND table_name='creator_settings' AND column_name IN ('ai_base_url','ai_api_key','ai_model')`).Scan(&columns); err != nil || columns != 3 {
t.Fatalf("AI columns=%d err=%v", columns, err)
}
if err := store.db.QueryRowContext(ctx, `SELECT count(*) FROM information_schema.columns WHERE table_schema=current_schema() AND table_name='creator_settings' AND column_name IN ('ai_provider','ai_configured')`).Scan(&columns); err != nil || columns != 0 {
t.Fatalf("legacy columns=%d err=%v", columns, err)
}
}
assertColumns()
// Recreate the previous schema only in this isolated database, preserving its settings row.
if _, err := store.db.ExecContext(ctx, `ALTER TABLE creator_settings DROP COLUMN ai_base_url,DROP COLUMN ai_api_key,ADD COLUMN ai_provider text NOT NULL DEFAULT '',ADD COLUMN ai_configured boolean NOT NULL DEFAULT false;
UPDATE creator_settings SET lookback_days=12,ai_model='legacy-model',ai_provider='bailian',ai_configured=true;
DELETE FROM schema_migration WHERE version=1057`); err != nil {
t.Fatal(err)
}
if err := store.migrate(ctx); err != nil {
t.Fatalf("upgrade: %v", err)
}
assertColumns()
var days int
var model, base, key string
if err := store.db.QueryRowContext(ctx, `SELECT lookback_days,ai_model,ai_base_url,ai_api_key FROM creator_settings`).Scan(&days, &model, &base, &key); err != nil || days != 12 || model != "legacy-model" || base != "" || key != "" {
t.Fatalf("preserved settings=%d/%s/%s/%s err=%v", days, model, base, key, err)
}
if _, err := store.db.ExecContext(ctx, `UPDATE creator_settings SET ai_base_url='https://example.com/v1',ai_api_key='saved-key',ai_model='chosen-model'`); err != nil {
t.Fatal(err)
}
for i := 0; i < 2; i++ {
if err := store.migrate(ctx); err != nil {
t.Fatalf("repeat startup: %v", err)
}
}
if err := store.db.QueryRowContext(ctx, `SELECT lookback_days,ai_model,ai_base_url,ai_api_key FROM creator_settings`).Scan(&days, &model, &base, &key); err != nil || days != 12 || model != "chosen-model" || base != "https://example.com/v1" || key != "saved-key" {
t.Fatalf("restart changed settings: %d/%s/%s/%s err=%v", days, model, base, key, err)
}
var applied int
if err := store.db.QueryRowContext(ctx, `SELECT count(*) FROM schema_migration WHERE version=1057`).Scan(&applied); err != nil || applied != 1 {
t.Fatalf("applied=%d err=%v", applied, err)
}
}
+4 -1
View File
@@ -209,6 +209,9 @@ var migration1055 string
//go:embed migrations/1056_event_details.sql
var migration1056 string
//go:embed migrations/1057_openai_settings.sql
var migration1057 string
var (
ErrConflict = errors.New("resource conflicts with existing state")
ErrInvalid = errors.New("invalid environment input")
@@ -351,7 +354,7 @@ func (s *Store) migrate(ctx context.Context) error {
{1029, migration1029}, {1030, migration1030}, {1031, migration1031}, {1032, migration1032}, {1033, migration1033}, {1034, migration1034},
{1035, migration1035}, {1036, migration1036}, {1037, migration1037}, {1038, migration1038}, {1039, migration1039}, {1040, migration1040},
{1041, migration1041}, {1042, migration1042}, {1043, migration1043}, {1044, migration1044}, {1045, migration1045}, {1046, migration1046},
{43, migration043}, {44, migration044}, {1047, migration1047}, {1048, migration1048}, {1049, migration1049}, {1050, migration1050}, {1051, migration1051}, {1052, migration1052}, {1053, migration1053}, {1054, migration1054}, {1055, migration1055}, {1056, migration1056}} {
{43, migration043}, {44, migration044}, {1047, migration1047}, {1048, migration1048}, {1049, migration1049}, {1050, migration1050}, {1051, migration1051}, {1052, migration1052}, {1053, migration1053}, {1054, migration1054}, {1055, migration1055}, {1056, migration1056}, {1057, migration1057}} {
var applied bool
if err := tx.QueryRowContext(ctx, `SELECT EXISTS (SELECT 1 FROM schema_migration WHERE version = $1)`, migration.version).Scan(&applied); err != nil {
return errors.New("read environment schema migration state")