diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a563e46..2740ea9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -63,3 +63,44 @@ jobs: username: ${{ secrets.VPS2_USER }} key: ${{ secrets.VPS2_SSH_KEY }} script: bash /opt/cleanup-miniapp.sh + + # Edge functions live in a Supabase volume shared with other projects, so they + # are written through the ownership gate on the host rather than copied in + # directly. The gate refuses any name this project does not own, which is what + # stops a repeat of the 2026-06-28 incident where another project's deploy + # silently replaced telegram-auth and broke sign-in for a month. + deploy-functions: + name: Deploy edge functions + needs: deploy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Package functions + run: tar czf functions.tgz -C supabase functions + + - name: Copy to staging on VPS + uses: appleboy/scp-action@v1.0.0 + with: + host: ${{ secrets.VPS2_HOST }} + username: ${{ secrets.VPS2_USER }} + key: ${{ secrets.VPS2_SSH_KEY }} + source: 'functions.tgz' + target: '/opt/miniapp-deploy-staging' + + - name: Deploy through ownership gate + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.VPS2_HOST }} + username: ${{ secrets.VPS2_USER }} + key: ${{ secrets.VPS2_SSH_KEY }} + script: | + set -e + BASE=/opt/miniapp-deploy-staging + trap 'rm -rf "$BASE"' EXIT + rm -rf "$BASE/functions" + tar xzf "$BASE/functions.tgz" -C "$BASE" + supabase-deploy-functions \ + --project pezkuwi-telegram-miniapp \ + --src "$BASE/functions" \ + --restart diff --git a/package.json b/package.json index 73458c5..6fd93af 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pezkuwi-telegram-miniapp", - "version": "1.0.242", + "version": "1.0.243", "type": "module", "description": "Pezkuwichain Telegram Mini App - Forum, Announcements, Rewards", "author": "Pezkuwichain Team", diff --git a/src/lib/supabase.ts b/src/lib/supabase.ts index ab4e436..72091c8 100644 --- a/src/lib/supabase.ts +++ b/src/lib/supabase.ts @@ -11,7 +11,10 @@ export async function signInWithTelegram(initData: string) { throw new Error('No Telegram initData provided'); } - const { data, error } = await supabase.functions.invoke('telegram-auth', { + // Namespaced: the shared Supabase instance also hosts pwap-web, whose own + // login-widget handler owns the bare 'telegram-auth' name. See + // /opt/supabase-self-hosted/functions-registry.json on the host. + const { data, error } = await supabase.functions.invoke('tgm-telegram-auth', { body: { initData }, }); diff --git a/src/version.json b/src/version.json index 89e672c..be8ebc1 100644 --- a/src/version.json +++ b/src/version.json @@ -1,5 +1,5 @@ { - "version": "1.0.242", + "version": "1.0.243", "buildTime": "2026-07-21T14:51:23.002Z", "buildNumber": 1784645483003 } diff --git a/supabase/functions/telegram-bot/index.ts b/supabase/functions/telegram-bot/index.ts index 1033a87..1586e33 100644 --- a/supabase/functions/telegram-bot/index.ts +++ b/supabase/functions/telegram-bot/index.ts @@ -563,6 +563,12 @@ async function sendDksWelcome(token: string, chatId: number) { web_app: { url: MINI_APP_URLS.dks }, }, ], + [ + { + text: '💱 Buy/Sell Crypto — PEX.network', + url: 'https://pex.network', + }, + ], [ { text: '📢 Join Channel / Kanalê Tev Bibin', diff --git a/supabase/functions/process-withdraw/index.ts b/supabase/functions/tgm-process-withdraw/index.ts similarity index 100% rename from supabase/functions/process-withdraw/index.ts rename to supabase/functions/tgm-process-withdraw/index.ts diff --git a/supabase/functions/telegram-auth/index.ts b/supabase/functions/tgm-telegram-auth/index.ts similarity index 100% rename from supabase/functions/telegram-auth/index.ts rename to supabase/functions/tgm-telegram-auth/index.ts diff --git a/supabase/migrations/20260730_ai_chat_log.sql b/supabase/migrations/20260730_ai_chat_log.sql new file mode 100644 index 0000000..025bb71 --- /dev/null +++ b/supabase/migrations/20260730_ai_chat_log.sql @@ -0,0 +1,28 @@ +-- Rate-limit ledger for the public `ask` edge function (news.pex.mom assistant). +-- +-- `ask` is unauthenticated and sends `Access-Control-Allow-Origin: *`, so this +-- table is its only throttle: 8 requests per IP per minute, 120 per day. The +-- limiter counts rows here and fails open when the count cannot be read, which +-- means shipping `ask` without this table would leave a public endpoint calling +-- paid model APIs with no limit at all. + +create table if not exists public.ai_chat_log ( + id bigserial primary key, + ip text not null, + created_at timestamptz not null default now() +); + +-- The limiter always filters on both columns together (ip = ? and created_at >= ?). +create index if not exists ai_chat_log_ip_created_at_idx + on public.ai_chat_log (ip, created_at desc); + +-- Only the service role touches this table; the function reaches it through +-- PostgREST with the service key, which bypasses RLS. Enabling RLS without any +-- policy therefore keeps the behaviour intact while denying anon and +-- authenticated clients, who have no reason to read a table of IP addresses. +alter table public.ai_chat_log enable row level security; + +revoke all on public.ai_chat_log from anon, authenticated; + +comment on table public.ai_chat_log is + 'Rate-limit ledger for the public ask endpoint. Holds IP addresses; prune rows older than the 24h window the limiter uses.';