#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" resolve_repo_root() { local candidate for candidate in "${SCRIPT_DIR}/.." "${SCRIPT_DIR}/../.." "${PWD}"; do if [[ -f "${candidate}/credentials/.env" ]]; then (cd "${candidate}" && pwd) return 0 fi done return 1 } if ! REPO_ROOT="$(resolve_repo_root)"; then echo "Error: could not locate repository root containing credentials/.env" >&2 exit 1 fi ENV_FILE="${REPO_ROOT}/credentials/.env" if [[ ! -f "${ENV_FILE}" ]]; then echo "Error: credentials file not found: ${ENV_FILE}" >&2 exit 1 fi load_env_value() { local key="$1" local line line="$(grep -E "^${key}=" "${ENV_FILE}" | tail -n 1 || true)" line="${line#*=}" line="${line%\"}" line="${line#\"}" line="${line%\'}" line="${line#\'}" printf '%s' "${line}" } DB_NAME="$(load_env_value MAILSHOTS_REMOTE_MYSQL_DB)" MYSQL_USER="$(load_env_value REMOTE_MYSQL_USER)" MYSQL_PASSWORD="$(load_env_value REMOTE_MYSQL_PASSWORD)" REMOTE_MYSQL_HOST="$(load_env_value REMOTE_MYSQL_HOST)" REMOTE_MYSQL_PORT="$(load_env_value REMOTE_MYSQL_PORT)" LOCAL_TUNNEL_PORT="${LOCAL_PORT:-13306}" # Match scripts/mysql_tunnel.sh defaults unless explicitly overridden. MYSQL_HOST="${MYSQL_HOST:-127.0.0.1}" MYSQL_PORT="${MYSQL_PORT:-${LOCAL_TUNNEL_PORT}}" if [[ -z "${DB_NAME}" ]]; then echo "Error: MAILSHOTS_REMOTE_MYSQL_DB is missing in ${ENV_FILE}" >&2 exit 1 fi if [[ -z "${MYSQL_USER}" || -z "${MYSQL_PASSWORD}" ]]; then echo "Error: REMOTE_MYSQL_USER/REMOTE_MYSQL_PASSWORD missing in ${ENV_FILE}" >&2 exit 1 fi if [[ -z "${MYSQL_HOST}" || -z "${MYSQL_PORT}" ]]; then echo "Error: MYSQL host/port is empty. Set REMOTE_MYSQL_HOST/REMOTE_MYSQL_PORT or MYSQL_HOST/MYSQL_PORT." >&2 exit 1 fi DRY_RUN=0 AUTO_YES=0 INCLUDE_CREDENTIALS=0 USE_REMOTE=0 INCLUDE_MAILSHOT_TEST=0 # Strict, test-suite-specific markers only. ARTEFACT_REGEX='^(e2e_|phase2_db_access_|phase3_|phase4_)' MAILSHOT_PURPOSE_REGEX='^(e2e_|Phase3 Mailshot |Phase4 )' usage() { cat <<'USAGE' Usage: cleanup_testing_artefacts.sh [options] Options: --dry-run Show row counts only; make no changes. --yes Skip confirmation prompt. --include-credentials Also delete test-marked rows in mailshot_credentials. --include-mailshot-test Also delete test-marked rows in mailshot_test. --remote Connect directly to REMOTE_MYSQL_HOST:REMOTE_MYSQL_PORT. -h, --help Show this help text. Environment overrides: LOCAL_PORT, MYSQL_HOST, MYSQL_PORT USAGE } while [[ $# -gt 0 ]]; do case "$1" in --dry-run) DRY_RUN=1 ;; --yes) AUTO_YES=1 ;; --include-credentials) INCLUDE_CREDENTIALS=1 ;; --include-mailshot-test) INCLUDE_MAILSHOT_TEST=1 ;; --remote) USE_REMOTE=1 ;; -h|--help) usage exit 0 ;; *) echo "Error: unknown option: $1" >&2 usage exit 1 ;; esac shift done if [[ "${USE_REMOTE}" -eq 1 ]]; then MYSQL_HOST="${REMOTE_MYSQL_HOST}" MYSQL_PORT="${REMOTE_MYSQL_PORT}" fi MYSQL_CMD=(mysql --protocol=TCP "-h${MYSQL_HOST}" "-P${MYSQL_PORT}" "-u${MYSQL_USER}" "-p${MYSQL_PASSWORD}" -N -B) echo "Target database: ${DB_NAME}" echo "MySQL endpoint: ${MYSQL_HOST}:${MYSQL_PORT}" echo "Cleanup mode: selective delete only (no TRUNCATE)." echo "Artefact regex: ${ARTEFACT_REGEX}" echo "Mailshot purpose regex: ${MAILSHOT_PURPOSE_REGEX}" for t in mailshot_last_run mailshot_queries mailshot_attachments mailshot_pdf_assets mailshots mailshot_credentials mailshot_test; do exists="$("${MYSQL_CMD[@]}" -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${DB_NAME}' AND table_name='${t}';")" if [[ "${exists}" != "1" ]]; then echo "Error: required table ${DB_NAME}.${t} does not exist." >&2 exit 1 fi done candidate_counts_sql=" SELECT 'mailshot_queries' AS t, COUNT(*) AS c FROM \`${DB_NAME}\`.\`mailshot_queries\` WHERE \`name\` REGEXP '${ARTEFACT_REGEX}' UNION ALL SELECT 'mailshot_attachments' AS t, COUNT(*) AS c FROM \`${DB_NAME}\`.\`mailshot_attachments\` WHERE \`name\` REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`file_name\`, '') REGEXP '${ARTEFACT_REGEX}' UNION ALL SELECT 'mailshot_pdf_assets' AS t, COUNT(*) AS c FROM \`${DB_NAME}\`.\`mailshot_pdf_assets\` WHERE \`name\` REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`file_name\`, '') REGEXP '${ARTEFACT_REGEX}' UNION ALL SELECT 'mailshots' AS t, COUNT(*) AS c FROM \`${DB_NAME}\`.\`mailshots\` WHERE COALESCE(\`Purpose\`, '') REGEXP '${MAILSHOT_PURPOSE_REGEX}' OR COALESCE(\`DataSource\`, '') REGEXP '${ARTEFACT_REGEX}' UNION ALL SELECT 'mailshot_last_run' AS t, COUNT(*) AS c FROM \`${DB_NAME}\`.\`mailshot_last_run\` lr WHERE COALESCE(lr.\`data_source\`, '') REGEXP '${ARTEFACT_REGEX}' OR COALESCE(lr.\`recipient_key\`, '') REGEXP '^phase[0-9]' OR lr.\`mailshot_id\` IN ( SELECT m.\`id\` FROM \`${DB_NAME}\`.\`mailshots\` m WHERE COALESCE(m.\`Purpose\`, '') REGEXP '${MAILSHOT_PURPOSE_REGEX}' OR COALESCE(m.\`DataSource\`, '') REGEXP '${ARTEFACT_REGEX}' );" echo echo "Candidate rows to delete:" "${MYSQL_CMD[@]}" -e "${candidate_counts_sql}" | while IFS=$'\t' read -r t c; do printf ' %-26s %s\n' "${t}" "${c}" done if [[ "${INCLUDE_MAILSHOT_TEST}" -eq 1 ]]; then c="$("${MYSQL_CMD[@]}" -e "SELECT COUNT(*) FROM \`${DB_NAME}\`.\`mailshot_test\` WHERE COALESCE(\`Name\`, '') REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`Email\`, '') REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`Email\`, '') LIKE '%@example.test';")" printf ' %-26s %s\n' "mailshot_test (optional)" "${c}" else echo " mailshot_test skipped (use --include-mailshot-test)" fi if [[ "${INCLUDE_CREDENTIALS}" -eq 1 ]]; then c="$("${MYSQL_CMD[@]}" -e "SELECT COUNT(*) FROM \`${DB_NAME}\`.\`mailshot_credentials\` WHERE COALESCE(\`smtp_host\`, '') LIKE 'smtp.test.%' OR COALESCE(\`imap_host\`, '') LIKE 'imap.test.%' OR COALESCE(\`smtp_user\`, '') REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`imap_user\`, '') REGEXP '${ARTEFACT_REGEX}';")" printf ' %-26s %s\n' "mailshot_credentials (optional)" "${c}" else echo " mailshot_credentials skipped (use --include-credentials)" fi if [[ "${DRY_RUN}" -eq 1 ]]; then echo echo "Dry run complete. No changes made." exit 0 fi if [[ "${AUTO_YES}" -ne 1 ]]; then echo read -r -p "Proceed to delete only the candidate test artefact rows above? [y/N] " reply if [[ ! "${reply}" =~ ^[Yy]$ ]]; then echo "Aborted. No changes made." exit 0 fi fi delete_sql=" DELETE FROM \`${DB_NAME}\`.\`mailshot_last_run\` WHERE COALESCE(\`data_source\`, '') REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`recipient_key\`, '') REGEXP '^phase[0-9]' OR \`mailshot_id\` IN ( SELECT \`id\` FROM ( SELECT m.\`id\` FROM \`${DB_NAME}\`.\`mailshots\` m WHERE COALESCE(m.\`Purpose\`, '') REGEXP '${MAILSHOT_PURPOSE_REGEX}' OR COALESCE(m.\`DataSource\`, '') REGEXP '${ARTEFACT_REGEX}' ) x ); DELETE FROM \`${DB_NAME}\`.\`mailshots\` WHERE COALESCE(\`Purpose\`, '') REGEXP '${MAILSHOT_PURPOSE_REGEX}' OR COALESCE(\`DataSource\`, '') REGEXP '${ARTEFACT_REGEX}'; DELETE FROM \`${DB_NAME}\`.\`mailshot_queries\` WHERE \`name\` REGEXP '${ARTEFACT_REGEX}'; DELETE FROM \`${DB_NAME}\`.\`mailshot_attachments\` WHERE \`name\` REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`file_name\`, '') REGEXP '${ARTEFACT_REGEX}'; DELETE FROM \`${DB_NAME}\`.\`mailshot_pdf_assets\` WHERE \`name\` REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`file_name\`, '') REGEXP '${ARTEFACT_REGEX}'; " "${MYSQL_CMD[@]}" -e "${delete_sql}" if [[ "${INCLUDE_MAILSHOT_TEST}" -eq 1 ]]; then "${MYSQL_CMD[@]}" -e "DELETE FROM \`${DB_NAME}\`.\`mailshot_test\` WHERE COALESCE(\`Name\`, '') REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`Email\`, '') REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`Email\`, '') LIKE '%@example.test';" fi if [[ "${INCLUDE_CREDENTIALS}" -eq 1 ]]; then "${MYSQL_CMD[@]}" -e "DELETE FROM \`${DB_NAME}\`.\`mailshot_credentials\` WHERE COALESCE(\`smtp_host\`, '') LIKE 'smtp.test.%' OR COALESCE(\`imap_host\`, '') LIKE 'imap.test.%' OR COALESCE(\`smtp_user\`, '') REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`imap_user\`, '') REGEXP '${ARTEFACT_REGEX}';" fi echo echo "Cleanup complete. Remaining candidate row counts:" "${MYSQL_CMD[@]}" -e "${candidate_counts_sql}" | while IFS=$'\t' read -r t c; do printf ' %-26s %s\n' "${t}" "${c}" done if [[ "${INCLUDE_MAILSHOT_TEST}" -eq 1 ]]; then c="$("${MYSQL_CMD[@]}" -e "SELECT COUNT(*) FROM \`${DB_NAME}\`.\`mailshot_test\` WHERE COALESCE(\`Name\`, '') REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`Email\`, '') REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`Email\`, '') LIKE '%@example.test';")" printf ' %-26s %s\n' "mailshot_test (optional)" "${c}" fi if [[ "${INCLUDE_CREDENTIALS}" -eq 1 ]]; then c="$("${MYSQL_CMD[@]}" -e "SELECT COUNT(*) FROM \`${DB_NAME}\`.\`mailshot_credentials\` WHERE COALESCE(\`smtp_host\`, '') LIKE 'smtp.test.%' OR COALESCE(\`imap_host\`, '') LIKE 'imap.test.%' OR COALESCE(\`smtp_user\`, '') REGEXP '${ARTEFACT_REGEX}' OR COALESCE(\`imap_user\`, '') REGEXP '${ARTEFACT_REGEX}';")" printf ' %-26s %s\n' "mailshot_credentials (optional)" "${c}" fi