CI / ci (push) Canceled after 0s
The session built: Appointments page: a single list, newest first. Block Time: edit and delete. Customer CSV export: with barber notes excluded. Guest-link regression test: a test that an appointment id alone is rejected, plus fixes to the stale docs. It built the scheduler, job runner with live progress, backup and restore, and the health alerts. Its own report says a real delete-and-restore on throwaway Docker passed, but I haven’t repeated that. It also says real SMTP and large dumps were not exercised. The manager image needs rebuilding for the backups to work, because it now installs 7zip.
77 lines
2.9 KiB
Bash
Executable File
77 lines
2.9 KiB
Bash
Executable File
#!/bin/sh
|
|
# FALLBACK ONLY. The Manager's built-in scheduler (manager/src/instrumentation.ts,
|
|
# see manager/README.md) now calls these endpoints itself — every 30 s by
|
|
# default, configurable — so with the Manager running you do NOT need a
|
|
# crontab entry for this script. Keep it for a deployment where the
|
|
# scheduler is disabled (MANAGER_SCHEDULER_DISABLED=1); never run both.
|
|
#
|
|
# 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
|