add Paddle webhook auto-setup, ngrok tunnel, and clean DB on each dev run

setup_paddle.py creates a notification destination in Paddle and writes
the webhook secret + setting ID to .env. dev_run.sh resets the DB, seeds
data, and starts an ngrok tunnel to update the webhook URL automatically
for end-to-end checkout testing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-18 13:52:23 +01:00
parent 77da44f3c8
commit 0b218f35ca
5 changed files with 184 additions and 13 deletions

View File

@@ -3,20 +3,67 @@
#
# Usage: ./scripts/dev_run.sh
#
# Starts: app (port 5000), background worker, CSS watcher.
# On each start: resets the DB, runs migrations, seeds data, builds CSS,
# optionally starts ngrok for Paddle webhook forwarding, then starts
# app (port 5000), background worker, and CSS watcher.
# Ctrl-C stops everything cleanly.
set -euo pipefail
cd "$(dirname "$0")/.."
# -- Colors for each process -------------------------------------------------
# -- Colors & helpers --------------------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'
COLOR_APP='\033[0;36m' # cyan
COLOR_WORKER='\033[0;33m' # yellow
COLOR_CSS='\033[0;35m' # magenta
NC='\033[0m'
BOLD='\033[1m'
COLOR_NGROK='\033[0;32m' # green
info() { echo -e "${BLUE}==>${NC} ${BOLD}$1${NC}"; }
ok() { echo -e "${GREEN}${NC} $1"; }
warn() { echo -e "${YELLOW} !${NC} $1"; }
fail() { echo -e "${RED}${NC} $1"; exit 1; }
# -- Preflight ---------------------------------------------------------------
if [ ! -f .env ]; then
fail ".env not found. Run ./scripts/dev_setup.sh first."
fi
# Load config from .env (|| true prevents set -e from aborting on empty values)
DATABASE_PATH=$(grep '^DATABASE_PATH=' .env 2>/dev/null | cut -d= -f2- || true)
DATABASE_PATH=${DATABASE_PATH:-data/app.db}
PADDLE_API_KEY=$(grep '^PADDLE_API_KEY=' .env 2>/dev/null | cut -d= -f2- || true)
PADDLE_NOTIFICATION_SETTING_ID=$(grep '^PADDLE_NOTIFICATION_SETTING_ID=' .env 2>/dev/null | cut -d= -f2- || true)
PADDLE_ENVIRONMENT=$(grep '^PADDLE_ENVIRONMENT=' .env 2>/dev/null | cut -d= -f2- || true)
PADDLE_ENVIRONMENT=${PADDLE_ENVIRONMENT:-sandbox}
# -- Preparation -------------------------------------------------------------
info "Resetting database"
rm -f "$DATABASE_PATH"
ok "Removed $DATABASE_PATH"
info "Running migrations"
uv run python -m padelnomics.migrations.migrate
ok "Migrations applied"
info "Seeding development data"
uv run python -m padelnomics.scripts.seed_dev_data
ok "Dev data seeded"
info "Building CSS"
make css-build
ok "CSS built"
# -- Process management ------------------------------------------------------
PIDS=()
@@ -34,7 +81,6 @@ cleanup() {
trap cleanup SIGINT SIGTERM
# Prefix each line of a command's output with a colored label.
# Usage: run_with_label COLOR LABEL COMMAND...
run_with_label() {
local color="$1" label="$2"
shift 2
@@ -44,13 +90,70 @@ run_with_label() {
PIDS+=($!)
}
# -- Ngrok tunnel (if Paddle is configured) ----------------------------------
TUNNEL_URL=""
if [ -n "$PADDLE_API_KEY" ] && [ -n "$PADDLE_NOTIFICATION_SETTING_ID" ]; then
if command -v ngrok >/dev/null 2>&1; then
info "Starting ngrok tunnel for Paddle webhooks"
ngrok http 5000 --log=stdout --log-level=warn > /tmp/padelnomics-ngrok.log 2>&1 &
NGROK_PID=$!
PIDS+=($NGROK_PID)
# Wait for ngrok to be ready (up to 10 seconds)
WAIT_SECONDS=10
for i in $(seq 1 $WAIT_SECONDS); do
TUNNEL_URL=$(curl -s http://localhost:4040/api/tunnels 2>/dev/null \
| python3 -c "import sys,json; print(json.load(sys.stdin)['tunnels'][0]['public_url'])" 2>/dev/null) \
&& break
sleep 1
done
if [ -n "$TUNNEL_URL" ]; then
ok "ngrok tunnel: $TUNNEL_URL"
# Update Paddle notification destination with tunnel URL
WEBHOOK_URL="${TUNNEL_URL}/billing/webhook/paddle"
info "Updating Paddle webhook destination → $WEBHOOK_URL"
uv run python -c "
import os
from dotenv import load_dotenv
load_dotenv()
from paddle_billing import Client, Environment, Options
from paddle_billing.Resources.NotificationSettings.Operations import UpdateNotificationSetting
env = Environment.SANDBOX if '${PADDLE_ENVIRONMENT}' == 'sandbox' else Environment.PRODUCTION
paddle = Client('${PADDLE_API_KEY}', options=Options(env))
paddle.notification_settings.update(
'${PADDLE_NOTIFICATION_SETTING_ID}',
UpdateNotificationSetting(destination='${WEBHOOK_URL}'),
)
print(' Updated.')
"
ok "Paddle webhooks → $WEBHOOK_URL"
else
warn "ngrok started but tunnel URL not available — webhooks won't reach localhost"
fi
else
warn "ngrok not installed — Paddle webhooks won't reach localhost"
warn "Install: https://ngrok.com/download (or brew install ngrok)"
fi
elif [ -n "$PADDLE_API_KEY" ]; then
warn "PADDLE_NOTIFICATION_SETTING_ID not set — run setup_paddle to create webhook destination"
fi
# -- Start processes ---------------------------------------------------------
echo ""
echo -e "${BOLD}Starting Padelnomics dev environment${NC}"
echo ""
echo " app: http://localhost:5000"
echo " admin: http://localhost:5000/admin"
echo " login: http://localhost:5000/auth/dev-login?email=dev@localhost"
if [ -n "$TUNNEL_URL" ]; then
echo " tunnel: $TUNNEL_URL"
fi
echo ""
echo "Press Ctrl-C to stop all processes."
echo ""