#!/bin/sh # Calls one of the app's CRON_SECRET-gated endpoints (see # src/app/api/cron/process-notifications/route.ts and # src/app/api/cron/sync-google-reviews/route.ts) for every business # currently provisioned under STACKS_DIR — so a single crontab entry on # the host covers every business automatically, including ones added # later, without a manual crontab line per business. # # Without something calling process-notifications on a schedule, queued # notifications (staff invitation emails, appointment reminders, loyalty # reward emails, etc.) are created in the database as PENDING and never # actually sent — see that route's own header for why this is a required # piece of infrastructure, not optional. This script is the reference # implementation for wiring that up; see the root README's "Notification # delivery" section for the crontab lines that call it. # # Usage: # process-cron.sh notifications # recommended every 1-2 minutes # process-cron.sh reviews # recommended once every 24 hours # # STACKS_DIR defaults to /opt/nextchair/stacks — override via env var if # this repo is cloned somewhere else (must match every other STACKS_DIR # use across this deployment — see .env.infra.example's own note on why). set -eu if [ $# -lt 1 ]; then echo "Usage: $0 notifications|reviews" >&2 exit 1 fi ENDPOINT="$1" STACKS_DIR="${STACKS_DIR:-/opt/nextchair/stacks}" case "$ENDPOINT" in notifications) PATH_SUFFIX="process-notifications" ;; reviews) PATH_SUFFIX="sync-google-reviews" ;; *) echo "Unknown endpoint '$ENDPOINT' — expected 'notifications' or 'reviews'." >&2 exit 1 ;; esac if [ ! -d "$STACKS_DIR" ]; then echo "STACKS_DIR '$STACKS_DIR' does not exist." >&2 exit 1 fi for env_file in "$STACKS_DIR"/*/.env; do [ -f "$env_file" ] || continue domain=$(grep -E '^DOMAIN=' "$env_file" | head -n1 | cut -d= -f2- | tr -d '"') cron_secret=$(grep -E '^CRON_SECRET=' "$env_file" | head -n1 | cut -d= -f2- | tr -d '"') slug=$(basename "$(dirname "$env_file")") if [ -z "$domain" ] || [ -z "$cron_secret" ]; then echo "[$slug] skipped — no DOMAIN/CRON_SECRET in .env (not fully provisioned yet)." >&2 continue fi status=$(curl -sS -o /tmp/process-cron-response.$$ -w '%{http_code}' -X POST \ "https://${domain}/api/cron/${PATH_SUFFIX}" \ -H "Authorization: Bearer ${cron_secret}" || echo "curl_failed") if [ "$status" = "200" ]; then echo "[$slug] $PATH_SUFFIX ok" else echo "[$slug] $PATH_SUFFIX FAILED (status=$status): $(cat /tmp/process-cron-response.$$ 2>/dev/null)" >&2 fi rm -f /tmp/process-cron-response.$$ done