From ab04cd70c5f0d7e3907ad0d95bd0886c2a6ecb68 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Sun, 19 Jul 2026 10:01:34 +0800 Subject: [PATCH] chore: add manual supabase profile migration workflow --- .../apply-supabase-profile-migrations.yml | 79 +++++++++++++++++++ tests/test_agent_chat_contract.py | 2 +- ...est_supabase_profile_migration_workflow.py | 27 +++++++ tests/test_supabase_user_data_contract.py | 3 +- 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/apply-supabase-profile-migrations.yml create mode 100644 tests/test_supabase_profile_migration_workflow.py diff --git a/.github/workflows/apply-supabase-profile-migrations.yml b/.github/workflows/apply-supabase-profile-migrations.yml new file mode 100644 index 00000000..a63a15a9 --- /dev/null +++ b/.github/workflows/apply-supabase-profile-migrations.yml @@ -0,0 +1,79 @@ +name: Apply Supabase profile migrations + +on: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: supabase-profile-migrations + cancel-in-progress: false + +env: + DEPLOY_HOST: 103.117.123.53 + DEPLOY_PORT: "22000" + DEPLOY_USER: root + DEPLOY_PATH: /opt/jyotisha-app + +jobs: + apply: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout migration files + uses: actions/checkout@v4 + + - name: Configure SSH + env: + SSH_PRIVATE_KEY: ${{ secrets.PRODUCTION_SSH_PRIVATE_KEY }} + run: | + install -m 700 -d ~/.ssh + printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/jyotisha-production + chmod 600 ~/.ssh/jyotisha-production + printf '%s\n' '[103.117.123.53]:22000 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHQJvN2Mo3Yq8e6ZIK4P2blJ5Vjj0HbknEuk7TyjhMbO' > ~/.ssh/known_hosts + + - name: Copy profile migrations to VPS + run: | + SSH_OPTIONS="-i $HOME/.ssh/jyotisha-production -p $DEPLOY_PORT -o BatchMode=yes -o IdentitiesOnly=yes" + RSYNC_SSH="ssh $SSH_OPTIONS" + ssh $SSH_OPTIONS "$DEPLOY_USER@$DEPLOY_HOST" "install -m 700 -d '$DEPLOY_PATH/tmp/profile-migrations'" + rsync -az -e "$RSYNC_SSH" \ + frontend/supabase/migrations/20260718010000_recover_missing_profile_rows.sql \ + frontend/supabase/migrations/20260718020000_profiles_service_role_upsert_grants.sql \ + frontend/supabase/migrations/20260718050000_profiles_service_role_upsert_grants.sql \ + frontend/supabase/migrations/20260718070000_profiles_service_role_upsert_id.sql \ + frontend/supabase/migrations/20260718080000_profiles_service_role_account_upsert_selects.sql \ + "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/tmp/profile-migrations/" + + - name: Apply profile migrations using VPS database URL + run: | + SSH_OPTIONS="-i $HOME/.ssh/jyotisha-production -p $DEPLOY_PORT -o BatchMode=yes -o IdentitiesOnly=yes" + ssh $SSH_OPTIONS "$DEPLOY_USER@$DEPLOY_HOST" "cd '$DEPLOY_PATH' && bash -s" <<'REMOTE' + set -euo pipefail + set +x + ENV_FILE="$PWD/.env.production" + if [ ! -f "$ENV_FILE" ]; then + echo ".env.production missing" >&2 + exit 1 + fi + set -a + . "$ENV_FILE" + set +a + DB_URL="${SUPABASE_DB_URL:-${DATABASE_URL:-}}" + if [ -z "$DB_URL" ]; then + echo "SUPABASE_DB_URL or DATABASE_URL is required in .env.production" >&2 + exit 1 + fi + for SQL_FILE in \ + tmp/profile-migrations/20260718010000_recover_missing_profile_rows.sql \ + tmp/profile-migrations/20260718020000_profiles_service_role_upsert_grants.sql \ + tmp/profile-migrations/20260718050000_profiles_service_role_upsert_grants.sql \ + tmp/profile-migrations/20260718070000_profiles_service_role_upsert_id.sql \ + tmp/profile-migrations/20260718080000_profiles_service_role_account_upsert_selects.sql + do + echo "applying $(basename "$SQL_FILE")" + cat "$SQL_FILE" | docker run --rm -i postgres:16-alpine \ + psql "$DB_URL" --set ON_ERROR_STOP=1 --quiet + done + REMOTE diff --git a/tests/test_agent_chat_contract.py b/tests/test_agent_chat_contract.py index aac3c277..0a15ee59 100644 --- a/tests/test_agent_chat_contract.py +++ b/tests/test_agent_chat_contract.py @@ -36,7 +36,7 @@ def test_onboarding_and_agent_suggestion_contract() -> None: assert "saveOnboardingName" in page assert "saveOnboardingBirth" in page assert "saveOnboardingPlace" in page - assert " None: + text = WORKFLOW.read_text(encoding="utf-8") + + assert "workflow_dispatch:" in text + assert "PRODUCTION_SSH_PRIVATE_KEY" in text + assert "SUPABASE_DB_URL" in text + assert "DATABASE_URL" in text + assert "docker run --rm -i postgres:16-alpine" in text + assert "set +x" in text + assert "cat \"$SQL_FILE\" |" in text + + +def test_profile_migration_workflow_targets_only_account_profile_migrations() -> None: + text = WORKFLOW.read_text(encoding="utf-8") + + assert "20260718010000_recover_missing_profile_rows.sql" in text + assert "20260718020000_profiles_service_role_upsert_grants.sql" in text + assert "20260718050000_profiles_service_role_upsert_grants.sql" in text + assert "20260718070000_profiles_service_role_upsert_id.sql" in text + assert "20260718080000_profiles_service_role_account_upsert_selects.sql" in text diff --git a/tests/test_supabase_user_data_contract.py b/tests/test_supabase_user_data_contract.py index 27d96533..5cf7966f 100644 --- a/tests/test_supabase_user_data_contract.py +++ b/tests/test_supabase_user_data_contract.py @@ -109,7 +109,8 @@ def test_chat_page_uses_authenticated_cloud_persistence() -> None: assert 'if (ownsInterface && !partialReply)' in source assert 'await persistSession(interruptedSession)' in source assert 'await persistence' in source - assert 'disabled={Boolean(pendingSessionId) || cancellationPending}' in source + assert "pendingSessionId || cancellationInFlight.current || pendingConsultation.current" in source + assert "setCancellationPending(true)" in source assert 'localStorage.setItem(chartLibraryStorageKey(accountId)' in source assert 'localStorage.setItem("chat_sessions"' not in source