From 77f23b4423edd515fa2ed45e829083cf28aa0843 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 08:53:41 +0800 Subject: [PATCH 1/9] chore(deps): bump gorm.io/gorm from 1.30.1 to 1.30.2 in /spx-backend (#2050) Bumps [gorm.io/gorm](https://github.com/go-gorm/gorm) from 1.30.1 to 1.30.2. - [Release notes](https://github.com/go-gorm/gorm/releases) - [Commits](https://github.com/go-gorm/gorm/compare/v1.30.1...v1.30.2) --- updated-dependencies: - dependency-name: gorm.io/gorm dependency-version: 1.30.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- spx-backend/go.mod | 2 +- spx-backend/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spx-backend/go.mod b/spx-backend/go.mod index 192cc5468..327f1cda3 100644 --- a/spx-backend/go.mod +++ b/spx-backend/go.mod @@ -18,7 +18,7 @@ require ( github.com/stretchr/testify v1.11.1 gocloud.dev v0.36.0 // indirect gorm.io/driver/mysql v1.6.0 - gorm.io/gorm v1.30.1 + gorm.io/gorm v1.30.2 ) require ( diff --git a/spx-backend/go.sum b/spx-backend/go.sum index b54f206c6..2df8d8d25 100644 --- a/spx-backend/go.sum +++ b/spx-backend/go.sum @@ -337,8 +337,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4= -gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +gorm.io/gorm v1.30.2 h1:f7bevlVoVe4Byu3pmbWPVHnPsLoWaMjEb7/clyr9Ivs= +gorm.io/gorm v1.30.2/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= modernc.org/fileutil v1.0.0 h1:Z1AFLZwl6BO8A5NldQg/xTSjGLetp+1Ubvl4alfGx8w= From 26ecd684dfb77ada46486d5fe95cea506e30c642 Mon Sep 17 00:00:00 2001 From: Aofei Sheng Date: Mon, 1 Sep 2025 09:06:49 +0800 Subject: [PATCH 2/9] feat(spx-backend): implement Database Migration System (#2001) Fixes #1958 Signed-off-by: Aofei Sheng --- .../validate-database-migrations.yml | 18 ++ .github/workflows/validate.yml | 4 - scripts/validate-database-migrations.sh | 283 ++++++++++++++++++ spx-backend/.env.dev | 4 +- spx-backend/cmd/spx-backend/main.yap | 9 + spx-backend/cmd/spx-backend/xgo_autogen.go | 97 +++--- spx-backend/go.mod | 16 +- spx-backend/go.sum | 97 ++++-- spx-backend/internal/config/config.go | 13 +- spx-backend/internal/config/loader.go | 25 +- spx-backend/internal/config/loader_test.go | 68 ++++- .../internal/controller/controller_test.go | 2 +- spx-backend/internal/migration/migration.go | 109 +++++++ .../migrations/001_initial_schema.down.sql | 46 +++ .../migrations/001_initial_schema.up.sql | 211 +++++++++++++ spx-backend/internal/model/model.go | 9 +- 16 files changed, 917 insertions(+), 94 deletions(-) create mode 100644 .github/workflows/validate-database-migrations.yml create mode 100755 scripts/validate-database-migrations.sh create mode 100644 spx-backend/internal/migration/migration.go create mode 100644 spx-backend/internal/migration/migrations/001_initial_schema.down.sql create mode 100644 spx-backend/internal/migration/migrations/001_initial_schema.up.sql diff --git a/.github/workflows/validate-database-migrations.yml b/.github/workflows/validate-database-migrations.yml new file mode 100644 index 000000000..094374f5c --- /dev/null +++ b/.github/workflows/validate-database-migrations.yml @@ -0,0 +1,18 @@ +name: Validate database migrations +on: + push: + paths: + - spx-backend/internal/migration/migrations/** + - scripts/validate-database-migrations.sh + pull_request: + paths: + - spx-backend/internal/migration/migrations/** + - scripts/validate-database-migrations.sh +jobs: + validate-database-migrations: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v5 + - name: Run database migration validation + run: ./scripts/validate-database-migrations.sh diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 198eb1775..7ec707f5f 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -2,11 +2,7 @@ name: Validate (lint, test & ...) on: push: - branches: - - '**' pull_request: - branches: - - '**' jobs: spx-gui-lint: diff --git a/scripts/validate-database-migrations.sh b/scripts/validate-database-migrations.sh new file mode 100755 index 000000000..f556d81df --- /dev/null +++ b/scripts/validate-database-migrations.sh @@ -0,0 +1,283 @@ +#!/bin/bash + +set -euo pipefail + +# Environment variable configuration +MYSQL_VERSION="${MYSQL_VERSION:-5.7}" +MIGRATE_VERSION="${MIGRATE_VERSION:-4}" + +# Internal configuration +MYSQL_CONTAINER_NAME="xbuilder-database-migration-test-$(date +%s)-$$" +MYSQL_PORT=$(shuf -i 13306-19999 -n 1) +MYSQL_HOST="127.0.0.1" +MYSQL_USER="root" +MYSQL_PASSWORD="root" +MYSQL_DATABASE="xbuilder" +MIGRATIONS_TABLE_NAME="schema_migration" +MIGRATIONS_DIR="spx-backend/internal/migration/migrations" +TMPDIR="$(mktemp -d)" +export TMPDIR +SNAPSHOTS_DIR=$(mktemp -d) + +# Logging functions +log_error() { + echo "[ERROR] $1" +} +log_error_and_exit() { + log_error "$1" + exit 1 +} +log_info() { + echo "[INFO] $1" +} +log_ok() { + echo "[OK] $1" +} + +# Set up cleanup trap +cleanup() { + log_info "Cleaning up..." + + # Remove temporary container + if docker ps -q -f name="${MYSQL_CONTAINER_NAME}" | grep -q .; then + log_info "Stopping MySQL container: ${MYSQL_CONTAINER_NAME}" + docker rm -f "${MYSQL_CONTAINER_NAME}" &>/dev/null || true + fi + + # Remove temporary directory + rm -rf "${TMPDIR}" +} +trap cleanup EXIT INT TERM + +# Display initial information +log_info "MySQL version: ${MYSQL_VERSION}" +log_info "Migrate version: ${MIGRATE_VERSION}" + +echo +echo "=========================================================================" +echo + +# Check if Docker is available +command -v docker &> /dev/null || log_error_and_exit "Docker not found. Please install Docker and try again." + +# Pull necessary Docker images +log_info "Pulling necessary Docker images..." +docker pull --platform linux/amd64 mysql:${MYSQL_VERSION} >/dev/null || log_error_and_exit "Failed to pull MySQL Docker image" +docker pull --platform linux/amd64 migrate/migrate:${MIGRATE_VERSION} >/dev/null || log_error_and_exit "Failed to pull migrate Docker image" + +echo +echo "=========================================================================" +echo + +# Start new MySQL container +log_info "Creating temporary MySQL container (port: ${MYSQL_PORT})" +docker run \ + -d \ + --platform linux/amd64 \ + --name "${MYSQL_CONTAINER_NAME}" \ + -e MYSQL_ROOT_PASSWORD="${MYSQL_PASSWORD}" \ + -e MYSQL_DATABASE="${MYSQL_DATABASE}" \ + -p "${MYSQL_PORT}:3306" \ + mysql:${MYSQL_VERSION} >/dev/null || log_error_and_exit "Failed to start MySQL container" +log_ok "Container started: ${MYSQL_CONTAINER_NAME}" + +# Construct MySQL DSN +MYSQL_DSN="mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@tcp(${MYSQL_HOST}:${MYSQL_PORT})/${MYSQL_DATABASE}?charset=utf8mb4&parseTime=True&loc=UTC&multiStatements=true&x-migrations-table=${MIGRATIONS_TABLE_NAME}" + +# MySQL command runner +run_mysql_command() { + docker run --rm --platform linux/amd64 --network host mysql:${MYSQL_VERSION} \ + "$@" \ + -h "${MYSQL_HOST}" -P "${MYSQL_PORT}" -u "${MYSQL_USER}" -p"${MYSQL_PASSWORD}" +} + +log_info "Waiting for MySQL to be ready..." +for i in {1..60}; do + if run_mysql_command mysqladmin ping --silent 2>/dev/null; then + log_ok "MySQL is ready" + break + fi + + if [[ $i -eq 60 ]]; then + docker logs "${MYSQL_CONTAINER_NAME}" 2>/dev/null || true + log_error_and_exit "MySQL failed to start within 120 seconds" + fi + + if [[ $((i % 10)) -eq 0 ]]; then + log_info "Still waiting... (${i}/60)" + fi + + sleep 2 +done + +echo +echo "=========================================================================" +echo + +# Function to dump database schema +dump_schema() { + local output_file="$1" + local description="$2" + + log_info "Creating schema snapshot: ${description}" + run_mysql_command mysqldump \ + --no-data \ + --skip-comments \ + --skip-add-locks \ + --skip-add-drop-table \ + --compact \ + --single-transaction \ + --ignore-table="${MYSQL_DATABASE}.${MIGRATIONS_TABLE_NAME}" \ + "${MYSQL_DATABASE}" > "${output_file}" 2>/dev/null || { + # Handle empty database case + touch "${output_file}" + } + + # Normalize schema for comparison + # Remove AUTO_INCREMENT values and sort for consistent comparison + if [[ -s "${output_file}" ]]; then + sed -i.bak 's/AUTO_INCREMENT=[0-9]*//' "${output_file}" && rm "${output_file}.bak" + sort "${output_file}" -o "${output_file}" + fi +} + +# Function to check if database is empty (excluding migration tracking table) +is_database_empty() { + local table_count + table_count=$(run_mysql_command mysql -e "SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema='${MYSQL_DATABASE}' AND table_name != '${MIGRATIONS_TABLE_NAME}'" -s -N 2>/dev/null || echo "0") + [[ "${table_count}" -eq 0 ]] +} + +# Function to get migration version from filename +get_version() { + local filename="$1" + echo "${filename}" | grep -oE '^[0-9]+' || echo "000" +} + +# Function to compare schemas +compare_schemas() { + local expected_file="$1" + local actual_file="$2" + local version="$3" + + if ! diff -u "${expected_file}" "${actual_file}" > "${SNAPSHOTS_DIR}/diff_${version}.txt"; then + log_error "Schema mismatch detected at version ${version}" + log_info "Expected schema: ${expected_file}" + log_info "Actual schema: ${actual_file}" + log_info "Diff saved to: ${SNAPSHOTS_DIR}/diff_${version}.txt" + echo + echo "Differences:" + head -20 "${SNAPSHOTS_DIR}/diff_${version}.txt" + return 1 + else + log_ok "Schema matches perfectly for version ${version}" + return 0 + fi +} + +log_info "Phase 1: Validating UP migrations" + +# Verify we start with an empty database +is_database_empty || log_error_and_exit "Database is not empty at start" + +# Create initial empty schema snapshot +dump_schema "${SNAPSHOTS_DIR}/schema_000.sql" "Initial empty database" +log_ok "Confirmed database is empty" + +# Get all UP migration files and sort by version +up_migrations=($(find "${MIGRATIONS_DIR}" -name "*.up.sql" | sort)) + +[[ ${#up_migrations[@]} -eq 0 ]] && log_error_and_exit "No UP migration files found in ${MIGRATIONS_DIR}" + +log_info "Found ${#up_migrations[@]} UP migration files" + +# Execute UP migrations +for migration_file in "${up_migrations[@]}"; do + filename=$(basename "${migration_file}") + version=$(get_version "${filename}") + + log_info "Executing UP migration: ${filename}" + + # Execute migration using Docker + if docker run --rm -v "$(pwd)/${MIGRATIONS_DIR}:/migrations" --network host migrate/migrate:${MIGRATE_VERSION} -path /migrations -database "${MYSQL_DSN}" up 1; then + log_ok "UP migration ${filename} executed successfully" + else + log_error_and_exit "UP migration ${filename} failed" + fi + + # Create schema snapshot + dump_schema "${SNAPSHOTS_DIR}/schema_${version}.sql" "After UP migration ${filename}" +done + +log_ok "All UP migrations executed successfully" + +echo +echo "=========================================================================" +echo + +log_info "Phase 2: Validating DOWN migrations" + +# Get all DOWN migration files and sort by version (descending) +down_migrations=($(find "${MIGRATIONS_DIR}" -name "*.down.sql" | sort -r)) + +[[ ${#down_migrations[@]} -eq 0 ]] && log_error_and_exit "No DOWN migration files found in ${MIGRATIONS_DIR}" + +log_info "Found ${#down_migrations[@]} DOWN migration files" + +# Execute DOWN migrations and compare schemas +for migration_file in "${down_migrations[@]}"; do + filename=$(basename "${migration_file}") + version=$(get_version "${filename}") + + # Calculate expected version (previous version) + expected_version=$(printf "%03d" $((10#${version} - 1))) + + log_info "Executing DOWN migration: ${filename}" + + # Execute migration using Docker + if docker run --rm -v "$(pwd)/${MIGRATIONS_DIR}:/migrations" --network host migrate/migrate:${MIGRATE_VERSION} -path /migrations -database "${MYSQL_DSN}" down 1; then + log_ok "DOWN migration ${filename} executed successfully" + else + log_error_and_exit "DOWN migration ${filename} failed" + fi + + # Create current schema snapshot + dump_schema "${SNAPSHOTS_DIR}/schema_after_down_${version}.sql" "After DOWN migration ${filename}" + + # Compare with expected schema + expected_schema="${SNAPSHOTS_DIR}/schema_${expected_version}.sql" + actual_schema="${SNAPSHOTS_DIR}/schema_after_down_${version}.sql" + + if [[ -f "${expected_schema}" ]]; then + if compare_schemas "${expected_schema}" "${actual_schema}" "${version}"; then + log_ok "Schema symmetry verified for migration ${version}" + else + log_error_and_exit "Schema symmetry FAILED for migration ${version}" + fi + else + log_error_and_exit "Expected schema file not found: ${expected_schema}" + fi +done + +# Final check: ensure database is empty +if is_database_empty; then + log_ok "Database is empty after all DOWN migrations" +else + echo "Remaining tables:" + run_mysql_command mysql -e "SELECT table_name FROM information_schema.tables WHERE table_schema='${MYSQL_DATABASE}' AND table_name != '${MIGRATIONS_TABLE_NAME}'" -s -N "${MYSQL_DATABASE}" 2>/dev/null || true + log_error_and_exit "Database is not empty after all DOWN migrations" +fi + +echo +echo "=========================================================================" +echo + +log_ok "All migration validation tests passed" +log_ok "UP migrations execute without errors" +log_ok "DOWN migrations execute without errors" +log_ok "Migration symmetry is perfect" +log_ok "Database returns to empty state" + +echo +echo "=========================================================================" +echo diff --git a/spx-backend/.env.dev b/spx-backend/.env.dev index 907a90944..a304966ee 100644 --- a/spx-backend/.env.dev +++ b/spx-backend/.env.dev @@ -1,7 +1,9 @@ PORT=:8080 ALLOWED_ORIGIN=* # Use local DB by default for dev -GOP_SPX_DSN=root:123456@tcp(127.0.0.1:3306)/builder?charset=utf8mb4&parseTime=True&loc=UTC +GOP_SPX_DSN=root:123456@tcp(127.0.0.1:3306)/builder?charset=utf8mb4&parseTime=True&loc=UTC&multiStatements=true +GOP_SPX_AUTO_MIGRATE=true +GOP_SPX_MIGRATION_TIMEOUT=10m # AIGC Service AIGC_ENDPOINT=http://36.213.14.15:8888 diff --git a/spx-backend/cmd/spx-backend/main.yap b/spx-backend/cmd/spx-backend/main.yap index 995ab3fef..0112722dd 100644 --- a/spx-backend/cmd/spx-backend/main.yap +++ b/spx-backend/cmd/spx-backend/main.yap @@ -17,6 +17,7 @@ import ( "github.com/goplus/builder/spx-backend/internal/config" "github.com/goplus/builder/spx-backend/internal/controller" "github.com/goplus/builder/spx-backend/internal/log" + "github.com/goplus/builder/spx-backend/internal/migration" "github.com/goplus/builder/spx-backend/internal/model" ) @@ -48,6 +49,14 @@ if err != nil { } defer sentry.Flush(10 * time.Second) +// Execute automatic migration for database if enabled. +if cfg.Database.AutoMigrate { + migrator := migration.New(cfg.Database.DSN, cfg.Database.GetMigrationTimeout()) + if err := migrator.Migrate(); err != nil { + logger.Fatalln("failed to migrate database:", err) + } +} + // Initialize database. db, err := model.OpenDB(context.Background(), cfg.Database.DSN, 0, 0) if err != nil { diff --git a/spx-backend/cmd/spx-backend/xgo_autogen.go b/spx-backend/cmd/spx-backend/xgo_autogen.go index 7e6f5843e..ec70c65cf 100644 --- a/spx-backend/cmd/spx-backend/xgo_autogen.go +++ b/spx-backend/cmd/spx-backend/xgo_autogen.go @@ -14,6 +14,7 @@ import ( "github.com/goplus/builder/spx-backend/internal/config" "github.com/goplus/builder/spx-backend/internal/controller" "github.com/goplus/builder/spx-backend/internal/log" + "github.com/goplus/builder/spx-backend/internal/migration" "github.com/goplus/builder/spx-backend/internal/model" "github.com/goplus/yap" "net/http" @@ -201,98 +202,110 @@ type put_user struct { yap.Handler *AppV2 } -//line cmd/spx-backend/main.yap:32 +//line cmd/spx-backend/main.yap:33 func (this *AppV2) MainEntry() { -//line cmd/spx-backend/main.yap:32:1 +//line cmd/spx-backend/main.yap:33:1 logger := log.GetLogger() -//line cmd/spx-backend/main.yap:35:1 - cfg, err := config.Load(logger) //line cmd/spx-backend/main.yap:36:1 - if err != nil { + cfg, err := config.Load(logger) //line cmd/spx-backend/main.yap:37:1 + if err != nil { +//line cmd/spx-backend/main.yap:38:1 logger.Fatalln("failed to load configuration:", err) } -//line cmd/spx-backend/main.yap:41:1 +//line cmd/spx-backend/main.yap:42:1 err = sentry.Init(sentry.ClientOptions{Dsn: cfg.Sentry.DSN, EnableTracing: true, TracesSampleRate: cfg.Sentry.SampleRate}) -//line cmd/spx-backend/main.yap:46:1 - if err != nil { //line cmd/spx-backend/main.yap:47:1 + if err != nil { +//line cmd/spx-backend/main.yap:48:1 logger.Fatalln("failed to initialize sentry:", err) } -//line cmd/spx-backend/main.yap:49:1 +//line cmd/spx-backend/main.yap:50:1 defer sentry.Flush(10 * time.Second) -//line cmd/spx-backend/main.yap:52:1 - db, err := model.OpenDB(context.Background(), cfg.Database.DSN, 0, 0) //line cmd/spx-backend/main.yap:53:1 - if err != nil { + if cfg.Database.AutoMigrate { //line cmd/spx-backend/main.yap:54:1 + migrator := migration.New(cfg.Database.DSN, cfg.Database.GetMigrationTimeout()) +//line cmd/spx-backend/main.yap:55:1 + if +//line cmd/spx-backend/main.yap:55:1 + err := migrator.Migrate(); err != nil { +//line cmd/spx-backend/main.yap:56:1 + logger.Fatalln("failed to migrate database:", err) + } + } +//line cmd/spx-backend/main.yap:61:1 + db, err := model.OpenDB(context.Background(), cfg.Database.DSN, 0, 0) +//line cmd/spx-backend/main.yap:62:1 + if err != nil { +//line cmd/spx-backend/main.yap:63:1 logger.Fatalln("failed to open database:", err) } -//line cmd/spx-backend/main.yap:59:1 +//line cmd/spx-backend/main.yap:68:1 authenticator := casdoor.New(db, cfg.Casdoor) -//line cmd/spx-backend/main.yap:61:1 +//line cmd/spx-backend/main.yap:70:1 // Initialize authorizer. var quotaTracker authz.QuotaTracker -//line cmd/spx-backend/main.yap:63:1 +//line cmd/spx-backend/main.yap:72:1 if cfg.Redis.Addr != "" { -//line cmd/spx-backend/main.yap:64:1 +//line cmd/spx-backend/main.yap:73:1 quotaTracker = quota.NewRedisQuotaTracker(cfg.Redis) -//line cmd/spx-backend/main.yap:65:1 +//line cmd/spx-backend/main.yap:74:1 logger.Printf("using redis quota tracker at %s", cfg.Redis.GetAddr()) } else { -//line cmd/spx-backend/main.yap:67:1 +//line cmd/spx-backend/main.yap:76:1 quotaTracker = quota.NewNopQuotaTracker() -//line cmd/spx-backend/main.yap:68:1 +//line cmd/spx-backend/main.yap:77:1 logger.Println("using no-op quota tracker") } -//line cmd/spx-backend/main.yap:70:1 +//line cmd/spx-backend/main.yap:79:1 pdp := embpdp.New(quotaTracker) -//line cmd/spx-backend/main.yap:71:1 +//line cmd/spx-backend/main.yap:80:1 authorizer := authz.New(db, pdp, quotaTracker) -//line cmd/spx-backend/main.yap:74:1 +//line cmd/spx-backend/main.yap:83:1 this.ctrl, err = controller.New(context.Background(), db, cfg) -//line cmd/spx-backend/main.yap:75:1 +//line cmd/spx-backend/main.yap:84:1 if err != nil { -//line cmd/spx-backend/main.yap:76:1 +//line cmd/spx-backend/main.yap:85:1 logger.Fatalln("failed to create a new controller:", err) } -//line cmd/spx-backend/main.yap:81:1 +//line cmd/spx-backend/main.yap:90:1 port := cfg.Server.GetPort() -//line cmd/spx-backend/main.yap:82:1 +//line cmd/spx-backend/main.yap:91:1 logger.Printf("listening to %s", port) -//line cmd/spx-backend/main.yap:84:1 +//line cmd/spx-backend/main.yap:93:1 h := this.Handler(authorizer.Middleware(), authn.Middleware(authenticator), NewCORSMiddleware(), NewReqIDMiddleware(), NewSentryMiddleware()) -//line cmd/spx-backend/main.yap:91:1 +//line cmd/spx-backend/main.yap:100:1 server := &http.Server{Addr: port, Handler: h} -//line cmd/spx-backend/main.yap:93:1 +//line cmd/spx-backend/main.yap:102:1 stopCtx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) -//line cmd/spx-backend/main.yap:94:1 +//line cmd/spx-backend/main.yap:103:1 defer stop() -//line cmd/spx-backend/main.yap:95:1 +//line cmd/spx-backend/main.yap:104:1 var serverErr error -//line cmd/spx-backend/main.yap:96:1 +//line cmd/spx-backend/main.yap:105:1 go func() { -//line cmd/spx-backend/main.yap:97:1 +//line cmd/spx-backend/main.yap:106:1 serverErr = server.ListenAndServe() -//line cmd/spx-backend/main.yap:98:1 +//line cmd/spx-backend/main.yap:107:1 stop() }() -//line cmd/spx-backend/main.yap:100:1 +//line cmd/spx-backend/main.yap:109:1 <-stopCtx.Done() -//line cmd/spx-backend/main.yap:101:1 +//line cmd/spx-backend/main.yap:110:1 if serverErr != nil && !errors.Is(serverErr, http.ErrServerClosed) { -//line cmd/spx-backend/main.yap:102:1 +//line cmd/spx-backend/main.yap:111:1 logger.Fatalln("server error:", serverErr) } -//line cmd/spx-backend/main.yap:105:1 +//line cmd/spx-backend/main.yap:114:1 shutdownCtx, cancel := context.WithTimeout(context.Background(), time.Minute) -//line cmd/spx-backend/main.yap:106:1 +//line cmd/spx-backend/main.yap:115:1 defer cancel() -//line cmd/spx-backend/main.yap:107:1 +//line cmd/spx-backend/main.yap:116:1 if -//line cmd/spx-backend/main.yap:107:1 +//line cmd/spx-backend/main.yap:116:1 err := server.Shutdown(shutdownCtx); err != nil { -//line cmd/spx-backend/main.yap:108:1 +//line cmd/spx-backend/main.yap:117:1 logger.Fatalln("failed to gracefully shut down:", err) } } diff --git a/spx-backend/go.mod b/spx-backend/go.mod index 327f1cda3..d0bad16db 100644 --- a/spx-backend/go.mod +++ b/spx-backend/go.mod @@ -1,6 +1,6 @@ module github.com/goplus/builder/spx-backend -go 1.22.0 +go 1.23.0 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 @@ -8,6 +8,7 @@ require ( github.com/getsentry/sentry-go v0.35.1 github.com/go-redis/redismock/v9 v9.2.0 github.com/go-sql-driver/mysql v1.9.3 + github.com/golang-migrate/migrate/v4 v4.18.3 github.com/goplus/yap v0.8.4 //xgo:class github.com/joho/godotenv v1.5.1 github.com/openai/openai-go v1.12.0 @@ -37,9 +38,11 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/gammazero/toposort v0.1.1 // indirect github.com/gofrs/flock v0.8.1 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/googleapis/gax-go/v2 v2.12.5 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -48,11 +51,12 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/net v0.34.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + golang.org/x/net v0.38.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/api v0.189.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade // indirect diff --git a/spx-backend/go.sum b/spx-backend/go.sum index 2df8d8d25..dc61525a9 100644 --- a/spx-backend/go.sum +++ b/spx-backend/go.sum @@ -8,21 +8,25 @@ cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= -cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= -cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= -cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= -cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= +cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4= github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI= -github.com/aws/aws-sdk-go v1.49.0 h1:g9BkW1fo9GqKfwg2+zCD+TW/D36Ux+vtfJ8guF4AYmY= -github.com/aws/aws-sdk-go v1.49.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.49.6 h1:yNldzF5kzLBRvKlKz1S0bkvc2+04R1kt13KfBWQBfFA= +github.com/aws/aws-sdk-go v1.49.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg= @@ -79,6 +83,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dhui/dktest v0.4.5 h1:uUfYBIVREmj/Rw6MvgmqNAYzTiKOHJak+enB5Di73MM= +github.com/dhui/dktest v0.4.5/go.mod h1:tmcyeHDKagvlDrz7gDKq4UAJOLIfVZYkfD5OnHDwcCo= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -111,8 +125,12 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs= +github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -154,6 +172,11 @@ github.com/goplus/yap v0.8.4 h1:nQpie5dZDH6p4T+vaHlfGmthrrzuFpDBWjPgjTatP8c= github.com/goplus/yap v0.8.4/go.mod h1:Br4hpgVn7KAln4GM/QWrgAHnn2U+TbQ7TegRYhKXgJw= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -175,6 +198,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= @@ -185,6 +216,10 @@ github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/openai/openai-go v1.12.0 h1:NBQCnXzqOTv5wsgNC36PrFEiskGfO5wccfCWDo9S1U0= github.com/openai/openai-go v1.12.0/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -204,11 +239,13 @@ github.com/qiniu/x v1.15.1/go.mod h1:AiovSOCaRijaf3fj+0CBOpR1457pn24b0Vdb1JpwhII github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg= github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -230,22 +267,24 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= gocloud.dev v0.36.0 h1:q5zoXux4xkOZP473e1EZbG8Gq9f0vlg1VNH5Du/ybus= gocloud.dev v0.36.0/go.mod h1:bLxah6JQVKBaIxzsr5BQLYB4IYdWHkMZdzCXlo6F0gg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -256,8 +295,8 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -265,21 +304,21 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -287,8 +326,8 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= diff --git a/spx-backend/internal/config/config.go b/spx-backend/internal/config/config.go index d982cbd9a..ef58fd629 100644 --- a/spx-backend/internal/config/config.go +++ b/spx-backend/internal/config/config.go @@ -3,6 +3,7 @@ package config import ( "slices" "strings" + "time" ) // Config holds all configuration for the application. @@ -38,7 +39,17 @@ type SentryConfig struct { // DatabaseConfig holds database configuration. type DatabaseConfig struct { - DSN string + DSN string + AutoMigrate bool + MigrationTimeout time.Duration +} + +// GetMigrationTimeout returns the migration timeout, defaulting to 5 minutes. +func (c *DatabaseConfig) GetMigrationTimeout() time.Duration { + if c.MigrationTimeout > 0 { + return c.MigrationTimeout + } + return 5 * time.Minute } // RedisConfig holds Redis configuration. diff --git a/spx-backend/internal/config/loader.go b/spx-backend/internal/config/loader.go index c9dee4646..e2e96f51f 100644 --- a/spx-backend/internal/config/loader.go +++ b/spx-backend/internal/config/loader.go @@ -5,6 +5,7 @@ import ( "io/fs" "os" "strconv" + "time" "github.com/joho/godotenv" "github.com/qiniu/x/log" @@ -23,7 +24,9 @@ func Load(logger *log.Logger) (*Config, error) { Port: os.Getenv("PORT"), }, Database: DatabaseConfig{ - DSN: mustGetEnv(logger, "GOP_SPX_DSN"), + DSN: mustGetEnv(logger, "GOP_SPX_DSN"), + AutoMigrate: getBoolEnv("GOP_SPX_AUTO_MIGRATE"), + MigrationTimeout: getDurationEnv("GOP_SPX_MIGRATION_TIMEOUT"), }, Sentry: SentryConfig{ DSN: os.Getenv("SENTRY_DSN"), @@ -95,3 +98,23 @@ func getFloatEnv(key string, defaultValue float64) float64 { } return floatValue } + +// getBoolEnv gets a boolean environment variable value or returns false if not set. +func getBoolEnv(key string) bool { + value := os.Getenv(key) + if value == "" { + return false + } + boolValue, _ := strconv.ParseBool(value) + return boolValue +} + +// getDurationEnv gets a duration environment variable value or returns 0 if not set. +func getDurationEnv(key string) time.Duration { + value := os.Getenv(key) + if value == "" { + return 0 + } + duration, _ := time.ParseDuration(value) + return duration +} diff --git a/spx-backend/internal/config/loader_test.go b/spx-backend/internal/config/loader_test.go index e4d6bfce0..00205b8c9 100644 --- a/spx-backend/internal/config/loader_test.go +++ b/spx-backend/internal/config/loader_test.go @@ -2,6 +2,7 @@ package config import ( "testing" + "time" "github.com/goplus/builder/spx-backend/internal/log" "github.com/stretchr/testify/assert" @@ -10,7 +11,9 @@ import ( func setTestEnv(t *testing.T) { // Database - t.Setenv("GOP_SPX_DSN", "root:root@tcp(mysql.example.com:3306)/builder?charset=utf8&parseTime=True") + t.Setenv("GOP_SPX_DSN", "root:root@tcp(mysql.example.com:3306)/builder?charset=utf8&parseTime=True&loc=UTC&multiStatements=true") + t.Setenv("GOP_SPX_AUTO_MIGRATE", "true") + t.Setenv("GOP_SPX_MIGRATION_TIMEOUT", "10m") // Redis t.Setenv("REDIS_ADDR", "redis.example.com:6379") @@ -56,7 +59,10 @@ func TestLoad(t *testing.T) { assert.Equal(t, ":8080", config.Server.GetPort()) // Database - assert.Equal(t, "root:root@tcp(mysql.example.com:3306)/builder?charset=utf8&parseTime=True", config.Database.DSN) + assert.Equal(t, "root:root@tcp(mysql.example.com:3306)/builder?charset=utf8&parseTime=True&loc=UTC&multiStatements=true", config.Database.DSN) + assert.True(t, config.Database.AutoMigrate) + assert.Equal(t, 10*time.Minute, config.Database.MigrationTimeout) + assert.Equal(t, config.Database.MigrationTimeout, config.Database.GetMigrationTimeout()) // Redis assert.Equal(t, "redis.example.com:6379", config.Redis.Addr) @@ -171,3 +177,61 @@ func TestGetIntEnv(t *testing.T) { assert.Equal(t, 0, getIntEnv("TEST_INT_FLOAT")) }) } + +func TestGetBoolEnv(t *testing.T) { + t.Run("TrueValues", func(t *testing.T) { + trueValues := []string{"true", "TRUE", "True", "1", "t", "T"} + for _, val := range trueValues { + t.Setenv("TEST_BOOL", val) + assert.True(t, getBoolEnv("TEST_BOOL"), "value: %s", val) + } + }) + + t.Run("FalseValues", func(t *testing.T) { + falseValues := []string{"false", "FALSE", "False", "0", "f", "F"} + for _, val := range falseValues { + t.Setenv("TEST_BOOL", val) + assert.False(t, getBoolEnv("TEST_BOOL"), "value: %s", val) + } + }) + + t.Run("EmptyValue", func(t *testing.T) { + t.Setenv("TEST_BOOL_EMPTY", "") + assert.False(t, getBoolEnv("TEST_BOOL_EMPTY")) + }) + + t.Run("NonExistentVariable", func(t *testing.T) { + assert.False(t, getBoolEnv("NON_EXISTENT_BOOL")) + }) +} + +func TestGetDurationEnv(t *testing.T) { + t.Run("ValidDuration", func(t *testing.T) { + t.Setenv("TEST_DURATION", "5m30s") + assert.Equal(t, 5*time.Minute+30*time.Second, getDurationEnv("TEST_DURATION")) + }) + + t.Run("Minutes", func(t *testing.T) { + t.Setenv("TEST_DURATION_MIN", "10m") + assert.Equal(t, 10*time.Minute, getDurationEnv("TEST_DURATION_MIN")) + }) + + t.Run("Seconds", func(t *testing.T) { + t.Setenv("TEST_DURATION_SEC", "45s") + assert.Equal(t, 45*time.Second, getDurationEnv("TEST_DURATION_SEC")) + }) + + t.Run("EmptyValue", func(t *testing.T) { + t.Setenv("TEST_DURATION_EMPTY", "") + assert.Equal(t, time.Duration(0), getDurationEnv("TEST_DURATION_EMPTY")) + }) + + t.Run("NonExistentVariable", func(t *testing.T) { + assert.Equal(t, time.Duration(0), getDurationEnv("NON_EXISTENT_DURATION")) + }) + + t.Run("InvalidDuration", func(t *testing.T) { + t.Setenv("TEST_DURATION_INVALID", "not-a-duration") + assert.Equal(t, time.Duration(0), getDurationEnv("TEST_DURATION_INVALID")) + }) +} diff --git a/spx-backend/internal/controller/controller_test.go b/spx-backend/internal/controller/controller_test.go index 69a012bfd..0483f384d 100644 --- a/spx-backend/internal/controller/controller_test.go +++ b/spx-backend/internal/controller/controller_test.go @@ -13,7 +13,7 @@ import ( ) func setTestEnv(t *testing.T) { - t.Setenv("GOP_SPX_DSN", "root:root@tcp(mysql.example.com:3306)/builder?charset=utf8&parseTime=True") + t.Setenv("GOP_SPX_DSN", "root:root@tcp(mysql.example.com:3306)/builder?charset=utf8&parseTime=True&loc=UTC&multiStatements=true") t.Setenv("KODO_AK", "test-kodo-ak") t.Setenv("KODO_SK", "test-kodo-sk") diff --git a/spx-backend/internal/migration/migration.go b/spx-backend/internal/migration/migration.go new file mode 100644 index 000000000..0198dea60 --- /dev/null +++ b/spx-backend/internal/migration/migration.go @@ -0,0 +1,109 @@ +package migration + +import ( + "database/sql" + "embed" + "errors" + "fmt" + "time" + + _ "github.com/go-sql-driver/mysql" + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/mysql" + "github.com/golang-migrate/migrate/v4/source/iofs" + + "github.com/goplus/builder/spx-backend/internal/log" +) + +// MigrationsTableName is the migration history table name. +// +// NOTE: The MigrationsTableName should never be changed once migrations have +// been run, as it would cause migrations to restart from the beginning. +const MigrationsTableName = "schema_migration" + +// migrationsFS contains all SQL migration files embedded in the binary. +// +//go:embed migrations/*.sql +var migrationsFS embed.FS + +// Migrator handles database schema migrations. +type Migrator struct { + dsn string + timeout time.Duration +} + +// New creates a new [Migrator]. +func New(dsn string, timeout time.Duration) *Migrator { + return &Migrator{ + dsn: dsn, + timeout: timeout, + } +} + +// Migrate executes pending database migrations. +func (m *Migrator) Migrate() error { + logger := log.GetLogger() + + // Open database connection. + db, err := sql.Open("mysql", m.dsn) + if err != nil { + return fmt.Errorf("failed to open database: %w", err) + } + defer db.Close() + + // Test connection. + if err := db.Ping(); err != nil { + return fmt.Errorf("failed to ping database: %w", err) + } + + // Create MySQL driver with custom migration table name and statement timeout. + driver, err := mysql.WithInstance(db, &mysql.Config{ + MigrationsTable: MigrationsTableName, + StatementTimeout: m.timeout, + }) + if err != nil { + return fmt.Errorf("failed to create mysql driver: %w", err) + } + + // Create source driver from embedded filesystem. + sourceDriver, err := iofs.New(migrationsFS, "migrations") + if err != nil { + return fmt.Errorf("failed to create source driver: %w", err) + } + + // Create migrator with custom configuration. + migrator, err := migrate.NewWithInstance("iofs", sourceDriver, "mysql", driver) + if err != nil { + return fmt.Errorf("failed to create migrator: %w", err) + } + defer migrator.Close() + + // Set lock timeout to use configured migration timeout. + migrator.LockTimeout = m.timeout + + // Get current migration status. + version, dirty, err := migrator.Version() + if err == nil { + logger.Printf("database current migration version: %d", version) + } else if errors.Is(err, migrate.ErrNilVersion) { + logger.Println("database has no migrations applied, will initialize schema") + } else { + return fmt.Errorf("failed to get migration version: %w", err) + } + + // Check if database is in dirty state. + if dirty { + return fmt.Errorf("database is dirty at version %d, manual intervention required", version) + } + + // Execute pending migrations. + if err := migrator.Up(); err != nil { + if errors.Is(err, migrate.ErrNoChange) { + logger.Println("database is already up to date, no migrations needed") + return nil + } + return fmt.Errorf("failed to run migrations: %w", err) + } + logger.Println("database migration completed successfully") + return nil +} diff --git a/spx-backend/internal/migration/migrations/001_initial_schema.down.sql b/spx-backend/internal/migration/migrations/001_initial_schema.down.sql new file mode 100644 index 000000000..c4b57f489 --- /dev/null +++ b/spx-backend/internal/migration/migrations/001_initial_schema.down.sql @@ -0,0 +1,46 @@ +-- Rollback initial database schema for spx-backend +-- This migration drops all tables in reverse dependency order + +DROP TABLE IF EXISTS `user_project_relationship`; +DROP TABLE IF EXISTS `user_relationship`; +DROP TABLE IF EXISTS `course_series`; +DROP TABLE IF EXISTS `course`; +DROP TABLE IF EXISTS `asset`; + +-- Check and drop fk_project_latest_release +SET @constraint_exists = ( + SELECT COUNT(*) + FROM information_schema.TABLE_CONSTRAINTS + WHERE CONSTRAINT_SCHEMA = DATABASE() + AND TABLE_NAME = 'project' + AND CONSTRAINT_NAME = 'fk_project_latest_release' + AND CONSTRAINT_TYPE = 'FOREIGN KEY' +); +SET @sql = IF(@constraint_exists > 0, + 'ALTER TABLE `project` DROP FOREIGN KEY `fk_project_latest_release`', + 'SELECT "Foreign key fk_project_latest_release does not exist, skipping" AS message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Check and drop fk_project_remixed_from_release +SET @constraint_exists = ( + SELECT COUNT(*) + FROM information_schema.TABLE_CONSTRAINTS + WHERE CONSTRAINT_SCHEMA = DATABASE() + AND TABLE_NAME = 'project' + AND CONSTRAINT_NAME = 'fk_project_remixed_from_release' + AND CONSTRAINT_TYPE = 'FOREIGN KEY' +); +SET @sql = IF(@constraint_exists > 0, + 'ALTER TABLE `project` DROP FOREIGN KEY `fk_project_remixed_from_release`', + 'SELECT "Foreign key fk_project_remixed_from_release does not exist, skipping" AS message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +DROP TABLE IF EXISTS `project_release`; +DROP TABLE IF EXISTS `project`; +DROP TABLE IF EXISTS `user`; diff --git a/spx-backend/internal/migration/migrations/001_initial_schema.up.sql b/spx-backend/internal/migration/migrations/001_initial_schema.up.sql new file mode 100644 index 000000000..85e386e62 --- /dev/null +++ b/spx-backend/internal/migration/migrations/001_initial_schema.up.sql @@ -0,0 +1,211 @@ +-- Initial database schema for spx-backend +-- This migration creates all the core tables required by the application + +CREATE TABLE IF NOT EXISTS `user` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `created_at` datetime(3) NOT NULL, + `updated_at` datetime(3) NOT NULL, + `deleted_at` datetime(3), + `username` varchar(191), + `display_name` longtext, + `avatar` longtext, + `description` longtext, + `roles` text, + `plan` tinyint unsigned, + `follower_count` bigint, + `following_count` bigint, + `project_count` bigint, + `public_project_count` bigint, + `liked_project_count` bigint, + `_deleted_at_is_null` bit(1) GENERATED ALWAYS AS (CASE WHEN deleted_at IS NULL THEN 1 ELSE NULL END) STORED, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_user_username` (`username`,`_deleted_at_is_null`), + KEY `idx_user_deleted_at` (`deleted_at`), + KEY `idx_user_plan` (`plan`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `project` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `created_at` datetime(3) NOT NULL, + `updated_at` datetime(3) NOT NULL, + `deleted_at` datetime(3), + `owner_id` bigint, + `remixed_from_release_id` bigint, + `latest_release_id` bigint, + `name` varchar(191), + `version` bigint, + `files` longblob, + `visibility` tinyint unsigned, + `description` longtext, + `instructions` longtext, + `thumbnail` longtext, + `view_count` bigint, + `like_count` bigint, + `release_count` bigint, + `remix_count` bigint, + `_deleted_at_is_null` bit(1) GENERATED ALWAYS AS (CASE WHEN deleted_at IS NULL THEN 1 ELSE NULL END) STORED, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_project_owner_id_name` (`owner_id`,`name`,`_deleted_at_is_null`), + KEY `idx_project_deleted_at` (`deleted_at`), + KEY `idx_project_remixed_from_release_id` (`remixed_from_release_id`), + KEY `idx_project_latest_release_id` (`latest_release_id`), + KEY `idx_project_visibility` (`visibility`), + KEY `idx_project_view_count` (`view_count`), + KEY `idx_project_like_count` (`like_count`), + KEY `idx_project_release_count` (`release_count`), + KEY `idx_project_remix_count` (`remix_count`), + KEY `idx_project_owner_id` (`owner_id`), + FULLTEXT KEY `idx_project_name` (`name`), + CONSTRAINT `fk_project_owner` FOREIGN KEY (`owner_id`) REFERENCES `user` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `project_release` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `created_at` datetime(3) NOT NULL, + `updated_at` datetime(3) NOT NULL, + `deleted_at` datetime(3), + `project_id` bigint, + `name` varchar(191), + `description` longtext, + `files` longblob, + `thumbnail` longtext, + `remix_count` bigint, + PRIMARY KEY (`id`), + UNIQUE KEY `idx_project_release_project_id_name` (`project_id`,`name`), + KEY `idx_project_release_deleted_at` (`deleted_at`), + KEY `idx_project_release_remix_count` (`remix_count`), + KEY `idx_project_release_project_id` (`project_id`), + FULLTEXT KEY `idx_project_release_name` (`name`), + CONSTRAINT `fk_project_release_project` FOREIGN KEY (`project_id`) REFERENCES `project` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Check and add fk_project_remixed_from_release +SET @constraint_exists = ( + SELECT COUNT(*) + FROM information_schema.TABLE_CONSTRAINTS + WHERE CONSTRAINT_SCHEMA = DATABASE() + AND TABLE_NAME = 'project' + AND CONSTRAINT_NAME = 'fk_project_remixed_from_release' + AND CONSTRAINT_TYPE = 'FOREIGN KEY' +); +SET @sql = IF(@constraint_exists = 0, + 'ALTER TABLE `project` ADD CONSTRAINT `fk_project_remixed_from_release` FOREIGN KEY (`remixed_from_release_id`) REFERENCES `project_release` (`id`)', + 'SELECT "Foreign key fk_project_remixed_from_release already exists, skipping" AS message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- Check and add fk_project_latest_release +SET @constraint_exists = ( + SELECT COUNT(*) + FROM information_schema.TABLE_CONSTRAINTS + WHERE CONSTRAINT_SCHEMA = DATABASE() + AND TABLE_NAME = 'project' + AND CONSTRAINT_NAME = 'fk_project_latest_release' + AND CONSTRAINT_TYPE = 'FOREIGN KEY' +); +SET @sql = IF(@constraint_exists = 0, + 'ALTER TABLE `project` ADD CONSTRAINT `fk_project_latest_release` FOREIGN KEY (`latest_release_id`) REFERENCES `project_release` (`id`)', + 'SELECT "Foreign key fk_project_latest_release already exists, skipping" AS message' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +CREATE TABLE IF NOT EXISTS `asset` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `created_at` datetime(3) NOT NULL, + `updated_at` datetime(3) NOT NULL, + `deleted_at` datetime(3), + `owner_id` bigint, + `display_name` varchar(191), + `type` tinyint unsigned, + `category` varchar(191), + `files` longblob, + `files_hash` longtext, + `visibility` tinyint unsigned, + PRIMARY KEY (`id`), + KEY `idx_asset_owner_id` (`owner_id`), + KEY `idx_asset_deleted_at` (`deleted_at`), + KEY `idx_asset_type` (`type`), + KEY `idx_asset_category` (`category`), + KEY `idx_asset_visibility` (`visibility`), + FULLTEXT KEY `idx_asset_display_name` (`display_name`), + CONSTRAINT `fk_asset_owner` FOREIGN KEY (`owner_id`) REFERENCES `user` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `course` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `created_at` datetime(3) NOT NULL, + `updated_at` datetime(3) NOT NULL, + `deleted_at` datetime(3), + `owner_id` bigint, + `title` varchar(191), + `thumbnail` longtext, + `entrypoint` longtext, + `references` longblob, + `prompt` longtext, + PRIMARY KEY (`id`), + KEY `idx_course_owner_id` (`owner_id`), + KEY `idx_course_deleted_at` (`deleted_at`), + FULLTEXT KEY `idx_course_title` (`title`), + CONSTRAINT `fk_course_owner` FOREIGN KEY (`owner_id`) REFERENCES `user` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `course_series` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `created_at` datetime(3) NOT NULL, + `updated_at` datetime(3) NOT NULL, + `deleted_at` datetime(3), + `owner_id` bigint, + `title` varchar(191), + `course_ids` longblob, + `order` bigint, + PRIMARY KEY (`id`), + KEY `idx_course_series_owner_id` (`owner_id`), + KEY `idx_course_series_deleted_at` (`deleted_at`), + KEY `idx_course_series_order` (`order`), + FULLTEXT KEY `idx_course_series_title` (`title`), + CONSTRAINT `fk_course_series_owner` FOREIGN KEY (`owner_id`) REFERENCES `user` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `user_relationship` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `created_at` datetime(3) NOT NULL, + `updated_at` datetime(3) NOT NULL, + `deleted_at` datetime(3), + `user_id` bigint, + `target_user_id` bigint, + `followed_at` datetime(3), + PRIMARY KEY (`id`), + UNIQUE KEY `idx_user_relationship_user_id_target_user_id` (`user_id`,`target_user_id`), + KEY `idx_user_relationship_deleted_at` (`deleted_at`), + KEY `idx_user_relationship_target_user_id` (`target_user_id`), + KEY `idx_user_relationship_followed_at` (`followed_at`), + KEY `idx_user_relationship_user_id` (`user_id`), + CONSTRAINT `fk_user_relationship_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`), + CONSTRAINT `fk_user_relationship_target_user` FOREIGN KEY (`target_user_id`) REFERENCES `user` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `user_project_relationship` ( + `id` bigint NOT NULL AUTO_INCREMENT, + `created_at` datetime(3) NOT NULL, + `updated_at` datetime(3) NOT NULL, + `deleted_at` datetime(3), + `user_id` bigint, + `project_id` bigint, + `view_count` bigint, + `last_viewed_at` datetime(3), + `liked_at` datetime(3), + PRIMARY KEY (`id`), + UNIQUE KEY `idx_user_project_relationship_user_id_project_id` (`user_id`,`project_id`), + KEY `idx_user_project_relationship_deleted_at` (`deleted_at`), + KEY `idx_user_project_relationship_project_id` (`project_id`), + KEY `idx_user_project_relationship_view_count` (`view_count`), + KEY `idx_user_project_relationship_last_viewed_at` (`last_viewed_at`), + KEY `idx_user_project_relationship_liked_at` (`liked_at`), + KEY `idx_user_project_relationship_user_id` (`user_id`), + CONSTRAINT `fk_user_project_relationship_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`), + CONSTRAINT `fk_user_project_relationship_project` FOREIGN KEY (`project_id`) REFERENCES `project` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/spx-backend/internal/model/model.go b/spx-backend/internal/model/model.go index 5e033af60..34bd27f30 100644 --- a/spx-backend/internal/model/model.go +++ b/spx-backend/internal/model/model.go @@ -33,8 +33,8 @@ func (_deleted_at_is_null) GormDataType() string { return "bit(1) GENERATED ALWAYS AS (CASE WHEN deleted_at IS NULL THEN 1 ELSE NULL END) STORED" } -// OpenDB opens the database with the given dsn and models to be migrated. -func OpenDB(ctx context.Context, dsn string, maxOpenConns, maxIdleConns int, models ...any) (*gorm.DB, error) { +// OpenDB opens the database with the given dsn. +func OpenDB(ctx context.Context, dsn string, maxOpenConns, maxIdleConns int) (*gorm.DB, error) { dialector := mysql.New(mysql.Config{DSN: dsn}) db, err := gorm.Open(dialector, &gorm.Config{ Logger: gormsentry.New(logger.Discard, nil, gormsentry.DefaultConfig()), @@ -49,11 +49,6 @@ func OpenDB(ctx context.Context, dsn string, maxOpenConns, maxIdleConns int, mod } sqlDB.SetMaxOpenConns(maxOpenConns) sqlDB.SetMaxIdleConns(maxIdleConns) - if len(models) > 0 { - if err := db.WithContext(ctx).AutoMigrate(models...); err != nil { - return nil, fmt.Errorf("failed to auto migrate models: %w", err) - } - } return db, nil } From 70f2efc73a72e378013a74445ed92ff472640b92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 10:01:41 +0800 Subject: [PATCH 3/9] chore(deps): bump github.com/casdoor/casdoor-go-sdk in /spx-backend (#2075) Bumps [github.com/casdoor/casdoor-go-sdk](https://github.com/casdoor/casdoor-go-sdk) from 1.16.0 to 1.17.0. - [Release notes](https://github.com/casdoor/casdoor-go-sdk/releases) - [Changelog](https://github.com/casdoor/casdoor-go-sdk/blob/master/.releaserc.json) - [Commits](https://github.com/casdoor/casdoor-go-sdk/compare/v1.16.0...v1.17.0) --- updated-dependencies: - dependency-name: github.com/casdoor/casdoor-go-sdk dependency-version: 1.17.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- spx-backend/go.mod | 2 +- spx-backend/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spx-backend/go.mod b/spx-backend/go.mod index d0bad16db..c9f2ae901 100644 --- a/spx-backend/go.mod +++ b/spx-backend/go.mod @@ -4,7 +4,7 @@ go 1.23.0 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 - github.com/casdoor/casdoor-go-sdk v1.16.0 + github.com/casdoor/casdoor-go-sdk v1.17.0 github.com/getsentry/sentry-go v0.35.1 github.com/go-redis/redismock/v9 v9.2.0 github.com/go-sql-driver/mysql v1.9.3 diff --git a/spx-backend/go.sum b/spx-backend/go.sum index dc61525a9..1c897e91c 100644 --- a/spx-backend/go.sum +++ b/spx-backend/go.sum @@ -69,8 +69,8 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/casdoor/casdoor-go-sdk v1.16.0 h1:H2GidU8Tw58lk2M6oJxkuQFP+uCSa7Qnc9w5CSgxoxk= -github.com/casdoor/casdoor-go-sdk v1.16.0/go.mod h1:cMnkCQJgMYpgAlgEx8reSt1AVaDIQLcJ1zk5pzBaz+4= +github.com/casdoor/casdoor-go-sdk v1.17.0 h1:ZWPXSILeJyjQpGV+seOOQBZbWFcZfaSfuUILgF8dXV8= +github.com/casdoor/casdoor-go-sdk v1.17.0/go.mod h1:cMnkCQJgMYpgAlgEx8reSt1AVaDIQLcJ1zk5pzBaz+4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= From f38b958621339acf2349b3475aa42cf347da1dc1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 10:02:48 +0800 Subject: [PATCH 4/9] chore(deps): bump github.com/golang-migrate/migrate/v4 in /spx-backend (#2076) Bumps [github.com/golang-migrate/migrate/v4](https://github.com/golang-migrate/migrate) from 4.18.3 to 4.19.0. - [Release notes](https://github.com/golang-migrate/migrate/releases) - [Changelog](https://github.com/golang-migrate/migrate/blob/master/.goreleaser.yml) - [Commits](https://github.com/golang-migrate/migrate/compare/v4.18.3...v4.19.0) --- updated-dependencies: - dependency-name: github.com/golang-migrate/migrate/v4 dependency-version: 4.19.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- spx-backend/go.mod | 9 ++++--- spx-backend/go.sum | 58 ++++++++++++++++++++++++---------------------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/spx-backend/go.mod b/spx-backend/go.mod index c9f2ae901..641e4ab6e 100644 --- a/spx-backend/go.mod +++ b/spx-backend/go.mod @@ -8,7 +8,7 @@ require ( github.com/getsentry/sentry-go v0.35.1 github.com/go-redis/redismock/v9 v9.2.0 github.com/go-sql-driver/mysql v1.9.3 - github.com/golang-migrate/migrate/v4 v4.18.3 + github.com/golang-migrate/migrate/v4 v4.19.0 github.com/goplus/yap v0.8.4 //xgo:class github.com/joho/godotenv v1.5.1 github.com/openai/openai-go v1.12.0 @@ -51,16 +51,15 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect go.opencensus.io v0.24.0 // indirect - go.uber.org/atomic v1.7.0 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/api v0.189.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade // indirect - google.golang.org/grpc v1.64.1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.67.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/fileutil v1.0.0 // indirect diff --git a/spx-backend/go.sum b/spx-backend/go.sum index 1c897e91c..41e99161c 100644 --- a/spx-backend/go.sum +++ b/spx-backend/go.sum @@ -5,7 +5,7 @@ cloud.google.com/go/auth v0.7.2 h1:uiha352VrCDMXg+yoBtaD0tUF4Kv9vrtrWPYXwutnDE= cloud.google.com/go/auth v0.7.2/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs= cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI= cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= -cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= +cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk= cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= @@ -76,6 +76,10 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -83,12 +87,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/dhui/dktest v0.4.5 h1:uUfYBIVREmj/Rw6MvgmqNAYzTiKOHJak+enB5Di73MM= -github.com/dhui/dktest v0.4.5/go.mod h1:tmcyeHDKagvlDrz7gDKq4UAJOLIfVZYkfD5OnHDwcCo= +github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4= +github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= -github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -107,8 +111,8 @@ github.com/getsentry/sentry-go v0.35.1 h1:iopow6UVLE2aXu46xKVIs8Z9D/YZkJrHkgozrx github.com/getsentry/sentry-go v0.35.1/go.mod h1:C55omcY9ChRQIUcVcGcs+Zdy4ZpQGvNJ7JYHIoSWOtE= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -129,8 +133,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs= -github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY= +github.com/golang-migrate/migrate/v4 v4.19.0 h1:RcjOnCGz3Or6HQYEJ/EEVLfWnmw9KnoigPSjzhCuaSE= +github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -239,13 +243,11 @@ github.com/qiniu/x v1.15.1/go.mod h1:AiovSOCaRijaf3fj+0CBOpR1457pn24b0Vdb1JpwhII github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg= github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -265,18 +267,18 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= gocloud.dev v0.36.0 h1:q5zoXux4xkOZP473e1EZbG8Gq9f0vlg1VNH5Du/ybus= @@ -298,8 +300,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -340,17 +342,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240722135656-d784300faade h1:lKFsS7wpngDgSCeFn7MoLy+wBDQZ1UQIJD4UNM1Qvkg= google.golang.org/genproto v0.0.0-20240722135656-d784300faade/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= -google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 h1:QW9+G6Fir4VcRXVH8x3LilNAb6cxBGLa6+GM4hRwexE= -google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3/go.mod h1:kdrSS/OiLkPrNUpzD4aHgCq2rVuC/YRxok32HXZ4vRE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade h1:oCRSWfwGXQsqlVdErcyTt4A93Y8fo0/9D4b1gnI++qo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From a02cbe383fe65ad5d53a26ea7df15c64740c0d70 Mon Sep 17 00:00:00 2001 From: Aofei Sheng Date: Wed, 3 Sep 2025 10:43:13 +0800 Subject: [PATCH 5/9] chore(deps): bump github.com/openai/openai-go from v1 to v2 in spx-backend (#2081) Signed-off-by: Aofei Sheng --- spx-backend/go.mod | 4 ++-- spx-backend/go.sum | 8 ++++---- spx-backend/internal/aidescription/aidescription.go | 2 +- spx-backend/internal/aidescription/aidescription_test.go | 4 ++-- spx-backend/internal/aiinteraction/aiinteraction.go | 2 +- spx-backend/internal/controller/controller.go | 4 ++-- spx-backend/internal/copilot/copilot.go | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/spx-backend/go.mod b/spx-backend/go.mod index 641e4ab6e..2c67f6cac 100644 --- a/spx-backend/go.mod +++ b/spx-backend/go.mod @@ -11,7 +11,7 @@ require ( github.com/golang-migrate/migrate/v4 v4.19.0 github.com/goplus/yap v0.8.4 //xgo:class github.com/joho/godotenv v1.5.1 - github.com/openai/openai-go v1.12.0 + github.com/openai/openai-go/v2 v2.2.2 github.com/qiniu/go-cdk-driver v0.1.1 github.com/qiniu/go-sdk/v7 v7.25.4 github.com/qiniu/x v1.15.1 @@ -46,7 +46,7 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect diff --git a/spx-backend/go.sum b/spx-backend/go.sum index 41e99161c..b95f4a30b 100644 --- a/spx-backend/go.sum +++ b/spx-backend/go.sum @@ -218,8 +218,8 @@ github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/openai/openai-go v1.12.0 h1:NBQCnXzqOTv5wsgNC36PrFEiskGfO5wccfCWDo9S1U0= -github.com/openai/openai-go v1.12.0/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y= +github.com/openai/openai-go/v2 v2.2.2 h1:nxOKipjlyrNaVY7nejORMQ8ttLbIEoyUgYII2tx5WjQ= +github.com/openai/openai-go/v2 v2.2.2/go.mod h1:sIUkR+Cu/PMUVkSKhkk742PRURkQOCFhiwJ7eRSBqmk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -256,8 +256,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= -github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= diff --git a/spx-backend/internal/aidescription/aidescription.go b/spx-backend/internal/aidescription/aidescription.go index 28cc4271d..d9636812e 100644 --- a/spx-backend/internal/aidescription/aidescription.go +++ b/spx-backend/internal/aidescription/aidescription.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/openai/openai-go" + "github.com/openai/openai-go/v2" ) const ( diff --git a/spx-backend/internal/aidescription/aidescription_test.go b/spx-backend/internal/aidescription/aidescription_test.go index 6e4960d74..b7055762e 100644 --- a/spx-backend/internal/aidescription/aidescription_test.go +++ b/spx-backend/internal/aidescription/aidescription_test.go @@ -3,8 +3,8 @@ package aidescription import ( "testing" - "github.com/openai/openai-go" - "github.com/openai/openai-go/option" + "github.com/openai/openai-go/v2" + "github.com/openai/openai-go/v2/option" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/spx-backend/internal/aiinteraction/aiinteraction.go b/spx-backend/internal/aiinteraction/aiinteraction.go index 9daf81291..a85e02c71 100644 --- a/spx-backend/internal/aiinteraction/aiinteraction.go +++ b/spx-backend/internal/aiinteraction/aiinteraction.go @@ -7,7 +7,7 @@ import ( "fmt" "strings" - "github.com/openai/openai-go" + "github.com/openai/openai-go/v2" ) const ( diff --git a/spx-backend/internal/controller/controller.go b/spx-backend/internal/controller/controller.go index 790182e3d..0c18c2e53 100644 --- a/spx-backend/internal/controller/controller.go +++ b/spx-backend/internal/controller/controller.go @@ -17,8 +17,8 @@ import ( "github.com/goplus/builder/spx-backend/internal/model" "github.com/goplus/builder/spx-backend/internal/tracer/httpclient" "github.com/goplus/builder/spx-backend/internal/workflow" - "github.com/openai/openai-go" - "github.com/openai/openai-go/option" + "github.com/openai/openai-go/v2" + "github.com/openai/openai-go/v2/option" _ "github.com/qiniu/go-cdk-driver/kodoblob" qiniuAuth "github.com/qiniu/go-sdk/v7/auth" "gorm.io/gorm" diff --git a/spx-backend/internal/copilot/copilot.go b/spx-backend/internal/copilot/copilot.go index 4386db096..95a454723 100644 --- a/spx-backend/internal/copilot/copilot.go +++ b/spx-backend/internal/copilot/copilot.go @@ -8,7 +8,7 @@ import ( "fmt" "io" - "github.com/openai/openai-go" + "github.com/openai/openai-go/v2" ) const ( From d98f2bc7b77927107b42823bf016e97e17a3d3d6 Mon Sep 17 00:00:00 2001 From: Aofei Sheng Date: Wed, 3 Sep 2025 10:43:54 +0800 Subject: [PATCH 6/9] feat(spx-backend): add lite model support for AI services (#2079) - Introduce lite model configuration and client allocation to optimize costs and improve performance for AI Description and AI Interaction features - Implement fallback logic: lite configs fall back to standard if unspecified Signed-off-by: Aofei Sheng --- spx-backend/.env.dev | 5 ++ spx-backend/internal/config/config.go | 28 ++++++++++ spx-backend/internal/config/config_test.go | 54 +++++++++++++++++++ spx-backend/internal/config/loader.go | 3 ++ spx-backend/internal/config/loader_test.go | 23 ++++++++ spx-backend/internal/controller/controller.go | 10 +++- 6 files changed, 121 insertions(+), 2 deletions(-) diff --git a/spx-backend/.env.dev b/spx-backend/.env.dev index a304966ee..2eee069d8 100644 --- a/spx-backend/.env.dev +++ b/spx-backend/.env.dev @@ -61,6 +61,11 @@ OPENAI_API_KEY= OPENAI_API_ENDPOINT= OPENAI_MODEL_ID= +# OpenAI (LITE) +OPENAI_LITE_API_KEY= +OPENAI_LITE_API_ENDPOINT= +OPENAI_LITE_MODEL_ID= + # OpenAI (PREMIUM) OPENAI_PREMIUM_API_KEY= OPENAI_PREMIUM_API_ENDPOINT= diff --git a/spx-backend/internal/config/config.go b/spx-backend/internal/config/config.go index ef58fd629..50ba97cef 100644 --- a/spx-backend/internal/config/config.go +++ b/spx-backend/internal/config/config.go @@ -115,11 +115,39 @@ type OpenAIConfig struct { APIEndpoint string ModelID string + LiteAPIKey string + LiteAPIEndpoint string + LiteModelID string + PremiumAPIKey string PremiumAPIEndpoint string PremiumModelID string } +// GetLiteAPIKey returns the lite API key, falling back to standard API key. +func (c *OpenAIConfig) GetLiteAPIKey() string { + if c.LiteAPIKey != "" { + return c.LiteAPIKey + } + return c.APIKey +} + +// GetLiteAPIEndpoint returns the lite API endpoint, falling back to standard endpoint. +func (c *OpenAIConfig) GetLiteAPIEndpoint() string { + if c.LiteAPIEndpoint != "" { + return c.LiteAPIEndpoint + } + return c.APIEndpoint +} + +// GetLiteModelID returns the lite model ID, falling back to standard model ID. +func (c *OpenAIConfig) GetLiteModelID() string { + if c.LiteModelID != "" { + return c.LiteModelID + } + return c.ModelID +} + // GetPremiumAPIKey returns the premium API key, falling back to standard API key. func (c *OpenAIConfig) GetPremiumAPIKey() string { if c.PremiumAPIKey != "" { diff --git a/spx-backend/internal/config/config_test.go b/spx-backend/internal/config/config_test.go index f6a91d4bf..dfa96a676 100644 --- a/spx-backend/internal/config/config_test.go +++ b/spx-backend/internal/config/config_test.go @@ -104,6 +104,60 @@ func TestRedisConfigGetPoolSize(t *testing.T) { }) } +func TestOpenAIConfigGetLiteAPIKey(t *testing.T) { + t.Run("WithLiteKey", func(t *testing.T) { + cfg := &OpenAIConfig{ + APIKey: "standard-key", + LiteAPIKey: "lite-key", + } + assert.Equal(t, "lite-key", cfg.GetLiteAPIKey()) + }) + + t.Run("WithoutLiteKey", func(t *testing.T) { + cfg := &OpenAIConfig{ + APIKey: "standard-key", + LiteAPIKey: "", + } + assert.Equal(t, "standard-key", cfg.GetLiteAPIKey()) + }) +} + +func TestOpenAIConfigGetLiteAPIEndpoint(t *testing.T) { + t.Run("WithLiteEndpoint", func(t *testing.T) { + cfg := &OpenAIConfig{ + APIEndpoint: "https://api.openai.com/v1", + LiteAPIEndpoint: "https://lite.openai.com/v1", + } + assert.Equal(t, "https://lite.openai.com/v1", cfg.GetLiteAPIEndpoint()) + }) + + t.Run("WithoutLiteEndpoint", func(t *testing.T) { + cfg := &OpenAIConfig{ + APIEndpoint: "https://api.openai.com/v1", + LiteAPIEndpoint: "", + } + assert.Equal(t, "https://api.openai.com/v1", cfg.GetLiteAPIEndpoint()) + }) +} + +func TestOpenAIConfigGetLiteModelID(t *testing.T) { + t.Run("WithLiteModel", func(t *testing.T) { + cfg := &OpenAIConfig{ + ModelID: "gpt-3.5-turbo", + LiteModelID: "gpt-4o-mini", + } + assert.Equal(t, "gpt-4o-mini", cfg.GetLiteModelID()) + }) + + t.Run("WithoutLiteModel", func(t *testing.T) { + cfg := &OpenAIConfig{ + ModelID: "gpt-3.5-turbo", + LiteModelID: "", + } + assert.Equal(t, "gpt-3.5-turbo", cfg.GetLiteModelID()) + }) +} + func TestOpenAIConfigGetPremiumAPIKey(t *testing.T) { t.Run("WithPremiumKey", func(t *testing.T) { cfg := &OpenAIConfig{ diff --git a/spx-backend/internal/config/loader.go b/spx-backend/internal/config/loader.go index e2e96f51f..03169d3f1 100644 --- a/spx-backend/internal/config/loader.go +++ b/spx-backend/internal/config/loader.go @@ -57,6 +57,9 @@ func Load(logger *log.Logger) (*Config, error) { APIKey: mustGetEnv(logger, "OPENAI_API_KEY"), APIEndpoint: mustGetEnv(logger, "OPENAI_API_ENDPOINT"), ModelID: mustGetEnv(logger, "OPENAI_MODEL_ID"), + LiteAPIKey: os.Getenv("OPENAI_LITE_API_KEY"), + LiteAPIEndpoint: os.Getenv("OPENAI_LITE_API_ENDPOINT"), + LiteModelID: os.Getenv("OPENAI_LITE_MODEL_ID"), PremiumAPIKey: os.Getenv("OPENAI_PREMIUM_API_KEY"), PremiumAPIEndpoint: os.Getenv("OPENAI_PREMIUM_API_ENDPOINT"), PremiumModelID: os.Getenv("OPENAI_PREMIUM_MODEL_ID"), diff --git a/spx-backend/internal/config/loader_test.go b/spx-backend/internal/config/loader_test.go index 00205b8c9..c59723528 100644 --- a/spx-backend/internal/config/loader_test.go +++ b/spx-backend/internal/config/loader_test.go @@ -92,6 +92,9 @@ func TestLoad(t *testing.T) { assert.Equal(t, "test-openai-key", config.OpenAI.APIKey) assert.Equal(t, "https://api.openai.com/v1", config.OpenAI.APIEndpoint) assert.Equal(t, "gpt-3.5-turbo", config.OpenAI.ModelID) + assert.Empty(t, config.OpenAI.LiteAPIKey) + assert.Empty(t, config.OpenAI.LiteAPIEndpoint) + assert.Empty(t, config.OpenAI.LiteModelID) assert.Empty(t, config.OpenAI.PremiumAPIKey) assert.Empty(t, config.OpenAI.PremiumAPIEndpoint) assert.Empty(t, config.OpenAI.PremiumModelID) @@ -116,6 +119,26 @@ func TestLoad(t *testing.T) { assert.Equal(t, expected, config.Redis.GetAddr()) }) + t.Run("LiteConfig", func(t *testing.T) { + setTestEnv(t) + t.Setenv("OPENAI_LITE_API_KEY", "lite-key") + t.Setenv("OPENAI_LITE_API_ENDPOINT", "https://lite.openai.com/v1") + t.Setenv("OPENAI_LITE_MODEL_ID", "gpt-4o-mini") + + logger := log.GetLogger() + config, err := Load(logger) + require.NoError(t, err) + require.NotNil(t, config) + + // OpenAI + assert.Equal(t, "lite-key", config.OpenAI.LiteAPIKey) + assert.Equal(t, config.OpenAI.LiteAPIKey, config.OpenAI.GetLiteAPIKey()) + assert.Equal(t, "https://lite.openai.com/v1", config.OpenAI.LiteAPIEndpoint) + assert.Equal(t, config.OpenAI.LiteAPIEndpoint, config.OpenAI.GetLiteAPIEndpoint()) + assert.Equal(t, "gpt-4o-mini", config.OpenAI.LiteModelID) + assert.Equal(t, config.OpenAI.LiteModelID, config.OpenAI.GetLiteModelID()) + }) + t.Run("PremiumConfig", func(t *testing.T) { setTestEnv(t) t.Setenv("PORT", ":9090") diff --git a/spx-backend/internal/controller/controller.go b/spx-backend/internal/controller/controller.go index 0c18c2e53..5c9d336db 100644 --- a/spx-backend/internal/controller/controller.go +++ b/spx-backend/internal/controller/controller.go @@ -54,6 +54,12 @@ func New(ctx context.Context, db *gorm.DB, cfg *config.Config) (*Controller, err option.WithHTTPClient(traceClient), ) + openaiLiteClient := openai.NewClient( + option.WithAPIKey(cfg.OpenAI.GetLiteAPIKey()), + option.WithBaseURL(cfg.OpenAI.GetLiteAPIEndpoint()), + option.WithHTTPClient(traceClient), + ) + openaiPremiumClient := openai.NewClient( option.WithAPIKey(cfg.OpenAI.GetPremiumAPIKey()), option.WithBaseURL(cfg.OpenAI.GetPremiumAPIEndpoint()), @@ -67,12 +73,12 @@ func New(ctx context.Context, db *gorm.DB, cfg *config.Config) (*Controller, err stdflow := NewWorkflow("stdflow", cpt, db) - aiDescription, err := aidescription.New(openaiClient, cfg.OpenAI.ModelID) + aiDescription, err := aidescription.New(openaiLiteClient, cfg.OpenAI.GetLiteModelID()) if err != nil { return nil, err } - aiInteraction, err := aiinteraction.New(openaiClient, cfg.OpenAI.ModelID) + aiInteraction, err := aiinteraction.New(openaiLiteClient, cfg.OpenAI.GetLiteModelID()) if err != nil { return nil, err } From 8f1436b8ce88878c3942e8f0e311b45dbc4d9282 Mon Sep 17 00:00:00 2001 From: Overu Date: Wed, 3 Sep 2025 00:29:21 -0700 Subject: [PATCH 7/9] feat(spx-gui): add ``spx-layer-action`` and ``spx-dir-action`` (#2074) --- spx-gui/.env | 2 +- spx-gui/install-spx.sh | 2 +- .../components/editor/code-editor/common.ts | 10 +++++ .../code-editor/document-base/spx/index.ts | 10 ++--- .../ui/input-helper/InputHelper.vue | 22 +++++++++++ .../ui/input-helper/SpxDirActionInput.vue | 37 +++++++++++++++++++ .../ui/input-helper/SpxLayerActionInput.vue | 37 +++++++++++++++++++ spx-gui/src/utils/spx/index.ts | 21 +++++++++++ tools/spxls/go.mod | 4 +- tools/spxls/go.sum | 8 ++-- 10 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 spx-gui/src/components/editor/code-editor/ui/input-helper/SpxDirActionInput.vue create mode 100644 spx-gui/src/components/editor/code-editor/ui/input-helper/SpxLayerActionInput.vue diff --git a/spx-gui/.env b/spx-gui/.env index 9b65d0d7c..824aa4c77 100644 --- a/spx-gui/.env +++ b/spx-gui/.env @@ -35,7 +35,7 @@ VITE_CASDOOR_ORGANIZATION_NAME="" VITE_DISABLE_AIGC="false" # Version of spx, keep in sync with the version in `install-spx.sh`. -VITE_SPX_VERSION=2.0.0-pre.9 +VITE_SPX_VERSION=2.0.0-pre.10 # Whether to show the license information (including copyright) in the footer. VITE_SHOW_LICENSE="false" diff --git a/spx-gui/install-spx.sh b/spx-gui/install-spx.sh index eea34742d..af633d2b0 100755 --- a/spx-gui/install-spx.sh +++ b/spx-gui/install-spx.sh @@ -2,7 +2,7 @@ set -e # Version of spx, keep in sync with the version in `.env`. -SPX_VERSION=2.0.0-pre.9 +SPX_VERSION=2.0.0-pre.10 SPX_NAME="spx_${SPX_VERSION}" SPX_FILE_NAME="${SPX_NAME}.zip" SPX_FILE_URL="https://github.com/goplus/spx/releases/download/v${SPX_VERSION}/spx_web.zip" diff --git a/spx-gui/src/components/editor/code-editor/common.ts b/spx-gui/src/components/editor/code-editor/common.ts index eae9b053b..5633fd6a2 100644 --- a/spx-gui/src/components/editor/code-editor/common.ts +++ b/spx-gui/src/components/editor/code-editor/common.ts @@ -667,6 +667,10 @@ export enum InputType { SpxResourceName = 'spx-resource-name', /** `Direction` in spx */ SpxDirection = 'spx-direction', + /** `layerAction` in spx */ + SpxLayerAction = 'spx-layer-action', + /** `dirAction` in spx */ + SpxDirAction = 'spx-dir-action', /** `Color` in spx */ SpxColor = 'spx-color', /** `EffectKind` in spx */ @@ -694,6 +698,8 @@ export type InputTypedValue = value: ResourceURI } | { type: InputType.SpxDirection; value: number } + | { type: InputType.SpxLayerAction; value: string } + | { type: InputType.SpxDirAction; value: string } | { type: InputType.SpxColor value: ColorValue @@ -745,6 +751,8 @@ export type InputSlotAccept = | InputType.String | InputType.Boolean | InputType.SpxDirection + | InputType.SpxLayerAction + | InputType.SpxDirAction | InputType.SpxColor | InputType.SpxEffectKind | InputType.SpxKey @@ -796,6 +804,8 @@ export function exprForInput(input: Input) { case InputType.SpxPlayAction: case InputType.SpxSpecialObj: case InputType.SpxRotationStyle: + case InputType.SpxLayerAction: + case InputType.SpxDirAction: return input.value default: return null diff --git a/spx-gui/src/components/editor/code-editor/document-base/spx/index.ts b/spx-gui/src/components/editor/code-editor/document-base/spx/index.ts index 3bc49693c..988ef731b 100644 --- a/spx-gui/src/components/editor/code-editor/document-base/spx/index.ts +++ b/spx-gui/src/components/editor/code-editor/document-base/spx/index.ts @@ -1,4 +1,4 @@ -import { packageSpx } from '@/utils/spx' +import { back, backward, forward, front, packageSpx } from '@/utils/spx' import { DefinitionKind, type DefinitionDocumentationItem, makeBasicMarkdownString, categories } from '../../common' import { defineConst } from './common' @@ -2930,10 +2930,10 @@ export const turningInfoDir: DefinitionDocumentationItem = { export const prev = defineConst('Prev', [], { en: 'Previous item', zh: '上一项' }) export const next = defineConst('Next', [], { en: 'Next item', zh: '下一项' }) -export const Front = defineConst('Front', [categories.look.visibility], { en: 'Front', zh: '最前' }) -export const Back = defineConst('Back', [categories.look.visibility], { en: 'Back', zh: '最后' }) -export const Forward = defineConst('Forward', [categories.look.visibility], { en: 'Forward', zh: '前移' }) -export const Backward = defineConst('Backward', [categories.look.visibility], { en: 'Backward', zh: '后移' }) +export const Front = defineConst(front.name, [categories.look.visibility], front.text) +export const Back = defineConst(back.name, [categories.look.visibility], back.text) +export const Forward = defineConst(forward.name, [categories.look.visibility], forward.text) +export const Backward = defineConst(backward.name, [categories.look.visibility], backward.text) export const up = defineConst('Up', [categories.motion.heading], { en: 'Up direction, i.e., 0 degree', diff --git a/spx-gui/src/components/editor/code-editor/ui/input-helper/InputHelper.vue b/spx-gui/src/components/editor/code-editor/ui/input-helper/InputHelper.vue index bb1dd3ccb..d6f2cdb4c 100644 --- a/spx-gui/src/components/editor/code-editor/ui/input-helper/InputHelper.vue +++ b/spx-gui/src/components/editor/code-editor/ui/input-helper/InputHelper.vue @@ -17,6 +17,8 @@ import DecimalInput, * as decimalInput from './DecimalInput.vue' import StringInput, * as stringInput from './StringInput.vue' import ResourceInput, * as resourceInput from './ResourceInput.vue' import SpxDirectionInput, * as spxDirectionInput from './SpxDirectionInput.vue' +import SpxLayerActionInput, * as spxLayerActionInput from './SpxLayerActionInput.vue' +import SpxDirActionInput, * as spxDirActionInput from './SpxDirActionInput.vue' import SpxColorInput, * as spxColorInput from './spx-color-input/SpxColorInput.vue' import SpxEffectKindInput, * as spxEffectKindInput from './spx-effect-input/SpxEffectKindInput.vue' import SpxKeyInput, * as spxKeyInput from './SpxKeyInput.vue' @@ -66,6 +68,10 @@ function getDefaultValue(): InputTypedValue['value'] | null { return resourceInput.getDefaultValue() case InputType.SpxDirection: return spxDirectionInput.getDefaultValue() + case InputType.SpxLayerAction: + return spxLayerActionInput.getDefaultValue() + case InputType.SpxDirAction: + return spxDirActionInput.getDefaultValue() case InputType.SpxColor: return spxColorInput.getDefaultValue() case InputType.SpxEffectKind: @@ -101,6 +107,10 @@ const inPlaceValueTitle = computed(() => { } case InputType.SpxDirection: return { en: 'Select a direction', zh: '选择方向' } + case InputType.SpxLayerAction: + return { en: 'Select a layer', zh: '选择向最前/后移' } + case InputType.SpxDirAction: + return { en: 'Select a direction', zh: '选择向前/向后' } case InputType.SpxColor: return { en: 'Select a color', zh: '选取颜色' } case InputType.SpxEffectKind: @@ -225,6 +235,18 @@ function handlePredefinedNameUpdate(name: string | null) { @update:value="handleInPlaceValueUpdate" @submit="emit('submit')" /> + + +export function getDefaultValue() { + return dirActions[0].name +} + + + + + diff --git a/spx-gui/src/components/editor/code-editor/ui/input-helper/SpxLayerActionInput.vue b/spx-gui/src/components/editor/code-editor/ui/input-helper/SpxLayerActionInput.vue new file mode 100644 index 000000000..3bdd05ff3 --- /dev/null +++ b/spx-gui/src/components/editor/code-editor/ui/input-helper/SpxLayerActionInput.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/spx-gui/src/utils/spx/index.ts b/spx-gui/src/utils/spx/index.ts index d72d6ec64..63f5a6978 100644 --- a/spx-gui/src/utils/spx/index.ts +++ b/spx-gui/src/utils/spx/index.ts @@ -206,6 +206,27 @@ export const effectKinds = [ { name: 'GhostEffect', text: { en: 'Ghost', zh: '幽灵' } } ] +export const front = { + name: 'Front', + text: { en: 'Front', zh: '最前' } +} +export const back = { + name: 'Back', + text: { en: 'Back', zh: '最后' } +} +export const forward = { + name: 'Forward', + text: { en: 'Forward', zh: '前移' } +} +export const backward = { + name: 'Backward', + text: { en: 'Backward', zh: '后移' } +} + +export const layerActions = [front, back] + +export const dirActions = [forward, backward] + export type KeyDefinition = { /** Name in spx */ name: string diff --git a/tools/spxls/go.mod b/tools/spxls/go.mod index 13d900373..419c495f8 100644 --- a/tools/spxls/go.mod +++ b/tools/spxls/go.mod @@ -6,14 +6,14 @@ tool github.com/goplus/xgolsw/cmd/pkgdatagen require ( github.com/goplus/builder/tools/ai v0.0.0 - github.com/goplus/xgolsw v0.11.0 + github.com/goplus/xgolsw v0.11.1-0.20250903031652-f9406a6e30d6 ) require ( github.com/goplus/gogen v0.0.0-20250825100505-c0f84643193c // indirect github.com/goplus/mod v0.17.1 // indirect github.com/goplus/spbase v0.1.0 // indirect - github.com/goplus/spx/v2 v2.0.0-pre.9 // indirect + github.com/goplus/spx/v2 v2.0.0-pre.10 // indirect github.com/goplus/xgo v0.0.0-20250826104338-05cc4b01eeed // indirect github.com/h2non/filetype v1.1.3 // indirect github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e // indirect diff --git a/tools/spxls/go.sum b/tools/spxls/go.sum index d57f86c6c..feea4f139 100644 --- a/tools/spxls/go.sum +++ b/tools/spxls/go.sum @@ -9,12 +9,12 @@ github.com/goplus/mod v0.17.1 h1:ITovxDcc5zbURV/Wrp3/SBsYLgC1KrxY6pq1zMM2V94= github.com/goplus/mod v0.17.1/go.mod h1:iXEszBKqi38BAyQApBPyQeurLHmQN34YMgC2ZNdap50= github.com/goplus/spbase v0.1.0 h1:JNZ0D/65DerYyv9/9IfrXHZZbd0WNK0jHiVvgCtZhwY= github.com/goplus/spbase v0.1.0/go.mod h1:brnD3OJnHtipqob2IsJ3/QzGBf+eOnqXNnHGKpv1irQ= -github.com/goplus/spx/v2 v2.0.0-pre.9 h1:7pWzr1Lg4eOxHD59I67ZJfPiY5gsnqEw1zItu2A6laA= -github.com/goplus/spx/v2 v2.0.0-pre.9/go.mod h1:nmCMvPSNMw1BuN7a1GRBHW+YPthstZpwSrn5UqQ4r1M= +github.com/goplus/spx/v2 v2.0.0-pre.10 h1:utkon95kKQtQ0nloQqC2OsTZ2HkagFlk/jyKGMGvxUE= +github.com/goplus/spx/v2 v2.0.0-pre.10/go.mod h1:nmCMvPSNMw1BuN7a1GRBHW+YPthstZpwSrn5UqQ4r1M= github.com/goplus/xgo v0.0.0-20250826104338-05cc4b01eeed h1:OenBul2zUEYn96RM5lphA50sO1Aqjhw4Y+ToBPsY2Io= github.com/goplus/xgo v0.0.0-20250826104338-05cc4b01eeed/go.mod h1:VW4MB5TdEcK7341XaQfokE7W+7BO2W1WV8lGYtaFwNE= -github.com/goplus/xgolsw v0.11.0 h1:CJbGevdZu2FWBSleJv+U98wEXV5TetY9JEpT9OcH6Uw= -github.com/goplus/xgolsw v0.11.0/go.mod h1:gFVTmRLYATDLrX8UFSUKM8xflFVojMQGIia1tUwHHPw= +github.com/goplus/xgolsw v0.11.1-0.20250903031652-f9406a6e30d6 h1:2ebxNW4WWJJE07eaXs2dGv5iZOjM4agZdJloIBuuhNs= +github.com/goplus/xgolsw v0.11.1-0.20250903031652-f9406a6e30d6/go.mod h1:xaJF0CGlU4LZi2zRyr5JL/yRQczKUT0hQ4i5jtEpVU8= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e h1:D0bJD+4O3G4izvrQUmzCL80zazlN7EwJ0PPDhpJWC/I= From 4c60f7502fcc00d93ff2dea3c5ea2a28fb792418 Mon Sep 17 00:00:00 2001 From: Aofei Sheng Date: Thu, 4 Sep 2025 10:48:02 +0800 Subject: [PATCH 8/9] refactor(tools/ai): optimize timeout and backoff parameters for improved reliability (#2090) Increase timeout values to provide sufficient time for complex AI operations: - transportTimeout: 15s -> 45s (for complex operations like history archiving) - backoffBase: 50ms -> 100ms (improved retry intervals) - backoffCap: 1s -> 2s (allow longer backoff periods for stability) Signed-off-by: Aofei Sheng --- tools/ai/ai.go | 10 +++++----- tools/ai/go.mod | 4 ++-- tools/ai/go.sum | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tools/ai/ai.go b/tools/ai/ai.go index ab66f8cab..9d0707cce 100644 --- a/tools/ai/ai.go +++ b/tools/ai/ai.go @@ -107,11 +107,11 @@ func (p *Player) Think__1(msg string) { } func (p *Player) think(owner any, msg string, context map[string]any) { const ( - transportTimeout = 15 * time.Second // Timeout for each transport call. - maxTransportRetries = 3 // Maximum number of retries for each AI transport call. - maxTurns = 20 // Maximum number of turns in a single call to prevent infinite loops. - backoffBase = 50 * time.Millisecond // Base time for exponential backoff calculation. - backoffCap = time.Second // Maximum backoff time cap. + transportTimeout = 45 * time.Second // Timeout for each transport call. + maxTransportRetries = 3 // Maximum number of retries for each AI transport call. + maxTurns = 20 // Maximum number of turns in a single call to prevent infinite loops. + backoffBase = 100 * time.Millisecond // Base time for exponential backoff calculation. + backoffCap = 2 * time.Second // Maximum backoff time cap. ) var ( diff --git a/tools/ai/go.mod b/tools/ai/go.mod index 8f99f8f71..ce2117ca5 100644 --- a/tools/ai/go.mod +++ b/tools/ai/go.mod @@ -2,11 +2,11 @@ module github.com/goplus/builder/tools/ai go 1.23.0 -require github.com/goplus/spx/v2 v2.0.0-pre.4 +require github.com/goplus/spx/v2 v2.0.0-pre.10 require ( + github.com/goplus/spbase v0.1.0 // indirect github.com/h2non/filetype v1.1.3 // indirect github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e // indirect - github.com/realdream-ai/mathf v0.0.0-20250513071532-e55e1277a8c5 // indirect golang.org/x/image v0.23.0 // indirect ) diff --git a/tools/ai/go.sum b/tools/ai/go.sum index 33f80fbbb..83c7480bf 100644 --- a/tools/ai/go.sum +++ b/tools/ai/go.sum @@ -1,10 +1,10 @@ -github.com/goplus/spx/v2 v2.0.0-pre.4 h1:zUW/4D/i7DsXHLYva/zkpjCp7Gy7ssSQJUJMmFzCMPY= -github.com/goplus/spx/v2 v2.0.0-pre.4/go.mod h1:pJw+stGi0Uah1czCfnviaXsRhjgPFo31XovTP/YB/Mo= +github.com/goplus/spbase v0.1.0 h1:JNZ0D/65DerYyv9/9IfrXHZZbd0WNK0jHiVvgCtZhwY= +github.com/goplus/spbase v0.1.0/go.mod h1:brnD3OJnHtipqob2IsJ3/QzGBf+eOnqXNnHGKpv1irQ= +github.com/goplus/spx/v2 v2.0.0-pre.10 h1:utkon95kKQtQ0nloQqC2OsTZ2HkagFlk/jyKGMGvxUE= +github.com/goplus/spx/v2 v2.0.0-pre.10/go.mod h1:nmCMvPSNMw1BuN7a1GRBHW+YPthstZpwSrn5UqQ4r1M= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e h1:D0bJD+4O3G4izvrQUmzCL80zazlN7EwJ0PPDhpJWC/I= github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/realdream-ai/mathf v0.0.0-20250513071532-e55e1277a8c5 h1:KuC3mEHh8NPOaOR0acOW+6ISKL4S9iY3n102KE/tst0= -github.com/realdream-ai/mathf v0.0.0-20250513071532-e55e1277a8c5/go.mod h1:zCbLmeT7a+pH1sknxgdFLiha6xA3gezTtwbTuuNRJWE= golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= From c855f93d0293ab6705736322029f8ebd8d86c632 Mon Sep 17 00:00:00 2001 From: Aofei Sheng Date: Thu, 4 Sep 2025 11:48:45 +0800 Subject: [PATCH 9/9] chore(deps): bump github.com/goplus/xgolsw in tools/spxls (#2092) Signed-off-by: Aofei Sheng --- tools/spxls/go.mod | 4 ++-- tools/spxls/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/spxls/go.mod b/tools/spxls/go.mod index 419c495f8..69556d1b0 100644 --- a/tools/spxls/go.mod +++ b/tools/spxls/go.mod @@ -6,11 +6,11 @@ tool github.com/goplus/xgolsw/cmd/pkgdatagen require ( github.com/goplus/builder/tools/ai v0.0.0 - github.com/goplus/xgolsw v0.11.1-0.20250903031652-f9406a6e30d6 + github.com/goplus/xgolsw v0.11.1 ) require ( - github.com/goplus/gogen v0.0.0-20250825100505-c0f84643193c // indirect + github.com/goplus/gogen v1.19.2 // indirect github.com/goplus/mod v0.17.1 // indirect github.com/goplus/spbase v0.1.0 // indirect github.com/goplus/spx/v2 v2.0.0-pre.10 // indirect diff --git a/tools/spxls/go.sum b/tools/spxls/go.sum index feea4f139..6f8e40055 100644 --- a/tools/spxls/go.sum +++ b/tools/spxls/go.sum @@ -3,8 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/goplus/gogen v0.0.0-20250825100505-c0f84643193c h1:eiXugPEcbNsJm5Kukc+0+To9s9Vfu5igdBnxjdD8CZs= -github.com/goplus/gogen v0.0.0-20250825100505-c0f84643193c/go.mod h1:owX2e1EyU5WD+Nm6oH2m/GXjLdlBYcwkLO4wN8HHXZI= +github.com/goplus/gogen v1.19.2 h1:0WCfbMy9V2gGIUG4gnlhbFkLLuEwVuxOgkzjJjeGsP0= +github.com/goplus/gogen v1.19.2/go.mod h1:owX2e1EyU5WD+Nm6oH2m/GXjLdlBYcwkLO4wN8HHXZI= github.com/goplus/mod v0.17.1 h1:ITovxDcc5zbURV/Wrp3/SBsYLgC1KrxY6pq1zMM2V94= github.com/goplus/mod v0.17.1/go.mod h1:iXEszBKqi38BAyQApBPyQeurLHmQN34YMgC2ZNdap50= github.com/goplus/spbase v0.1.0 h1:JNZ0D/65DerYyv9/9IfrXHZZbd0WNK0jHiVvgCtZhwY= @@ -13,8 +13,8 @@ github.com/goplus/spx/v2 v2.0.0-pre.10 h1:utkon95kKQtQ0nloQqC2OsTZ2HkagFlk/jyKGM github.com/goplus/spx/v2 v2.0.0-pre.10/go.mod h1:nmCMvPSNMw1BuN7a1GRBHW+YPthstZpwSrn5UqQ4r1M= github.com/goplus/xgo v0.0.0-20250826104338-05cc4b01eeed h1:OenBul2zUEYn96RM5lphA50sO1Aqjhw4Y+ToBPsY2Io= github.com/goplus/xgo v0.0.0-20250826104338-05cc4b01eeed/go.mod h1:VW4MB5TdEcK7341XaQfokE7W+7BO2W1WV8lGYtaFwNE= -github.com/goplus/xgolsw v0.11.1-0.20250903031652-f9406a6e30d6 h1:2ebxNW4WWJJE07eaXs2dGv5iZOjM4agZdJloIBuuhNs= -github.com/goplus/xgolsw v0.11.1-0.20250903031652-f9406a6e30d6/go.mod h1:xaJF0CGlU4LZi2zRyr5JL/yRQczKUT0hQ4i5jtEpVU8= +github.com/goplus/xgolsw v0.11.1 h1:a2bdnMUB/adPWoYMQZ9d0I2FPUsMZL3LM95okjm039I= +github.com/goplus/xgolsw v0.11.1/go.mod h1:T4ZVspjOqPYKy2Uxxd7P+Q0EheQf3PXUS+arA17rvkg= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e h1:D0bJD+4O3G4izvrQUmzCL80zazlN7EwJ0PPDhpJWC/I=