#!/usr/bin/env bash set -euo pipefail # Seed one deterministic acceptance-ready job into the live API. # # This script requires AUTH_TOKEN because local dev auth is enabled in this milestone. # Retrieve a bearer token manually before running, for example by logging into the local # API via POST /api/auth/login with the active local account for this environment, then: # export AUTH_TOKEN="" # bash scripts/s06-acceptance-data.sh # # If the placeholder dev credentials in appsettings.Development.json do not match the # current database, use the real seeded account for this environment or a bearer token # captured from an already-authenticated local browser session. The script never prints # token values. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" API_BASE="${API_BASE:-http://localhost:5202/api}" AUTH_TOKEN="${AUTH_TOKEN:-}" CURL_TIMEOUT="${CURL_TIMEOUT:-20}" CONNECT_TIMEOUT="${CONNECT_TIMEOUT:-5}" COMPANY_NAME="S06 Acceptance Labs" JOB_TITLE="S06 Acceptance Backend Engineer" JOB_URL="https://example.invalid/jobs/s06-acceptance-backend-engineer" THREAD_ID="s06-acceptance-thread" MESSAGE_ID="s06-acceptance-message-1" FOLLOW_UP_AT="2026-03-10T09:00:00Z" DATE_APPLIED="2026-02-28T10:00:00Z" CORRESPONDENCE_AT="2026-03-09T11:30:00Z" TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT note() { printf '%s\n' "$*" } fail() { printf 'ERROR: %s\n' "$*" >&2 exit 1 } if [[ -z "$AUTH_TOKEN" ]]; then fail "AUTH_TOKEN is required. Export a bearer token first, then rerun." fi validate_json() { local file="$1" if ! python3 - "$file" <<'PY' import json import sys with open(sys.argv[1], 'r', encoding='utf-8') as fh: json.load(fh) PY then printf 'Raw response body:\n' >&2 cat "$file" >&2 printf '\n' >&2 fail "Malformed JSON returned by API." fi } request() { local method="$1" local name="$2" local url="$3" local body_file="$4" local expect_json="$5" local data_file="${6:-}" local err_file="$TMP_DIR/${name// /-}.curl.err" local status_file="$TMP_DIR/${name// /-}.status" : >"$err_file" local -a curl_args=( -sS --connect-timeout "$CONNECT_TIMEOUT" --max-time "$CURL_TIMEOUT" -X "$method" -H 'Accept: application/json' -H "Authorization: Bearer ${AUTH_TOKEN}" -o "$body_file" -w '%{http_code}' ) if [[ -n "$data_file" ]]; then curl_args+=(-H 'Content-Type: application/json' --data-binary "@$data_file") fi set +e curl "${curl_args[@]}" "$url" >"$status_file" 2>"$err_file" local curl_exit=$? set -e local status='000' if [[ -s "$status_file" ]]; then status="$(tr -d '\r\n' <"$status_file")" fi if [[ $curl_exit -ne 0 ]]; then case "$curl_exit:$status" in 7:*|28:*|52:*|56:*|6:*) printf 'curl: %s\n' "$(tr -d '\r' <"$err_file")" >&2 fail "Cannot reach ${url}. Start the API and rerun." ;; 22:401|22:403) printf 'Response body:\n' >&2 cat "$body_file" >&2 || true printf '\n' >&2 fail "AUTH_TOKEN appears invalid or lacks access for ${name}." ;; 22:5*) printf 'Response body:\n' >&2 cat "$body_file" >&2 || true printf '\n' >&2 fail "${name} failed with server error HTTP ${status}." ;; 22:*) printf 'Response body:\n' >&2 cat "$body_file" >&2 || true printf '\n' >&2 fail "${name} failed with HTTP ${status}." ;; *) printf 'curl: %s\n' "$(tr -d '\r' <"$err_file")" >&2 fail "${name} failed with curl exit ${curl_exit}." ;; esac fi if [[ "$status" == 401 || "$status" == 403 ]]; then printf 'Response body:\n' >&2 cat "$body_file" >&2 || true printf '\n' >&2 fail "AUTH_TOKEN appears invalid or lacks access for ${name}." fi if [[ "$status" =~ ^5 ]]; then printf 'Response body:\n' >&2 cat "$body_file" >&2 || true printf '\n' >&2 fail "${name} failed with server error HTTP ${status}." fi if [[ "$expect_json" == "json" ]]; then validate_json "$body_file" fi } json_extract() { local file="$1" local expression="$2" python3 - "$file" "$expression" <<'PY' import json import sys file_path, expression = sys.argv[1], sys.argv[2] with open(file_path, 'r', encoding='utf-8') as fh: data = json.load(fh) value = eval(expression, {'__builtins__': {'next': next, 'len': len}}, {'data': data}) if value is None: print('') elif isinstance(value, bool): print('true' if value else 'false') elif isinstance(value, (dict, list)): print(json.dumps(value, separators=(',', ':'))) else: print(value) PY } json_string() { python3 - "$1" <<'PY' import json import sys print(json.dumps(sys.argv[1])) PY } write_json() { local path="$1" shift printf '%s\n' "$*" >"$path" } urlencode() { python3 - "$1" <<'PY' import sys import urllib.parse print(urllib.parse.quote(sys.argv[1])) PY } note "Running preflight before seeding acceptance data." bash "$SCRIPT_DIR/s06-preflight.sh" >/dev/null company_body="$TMP_DIR/company.json" write_json "$company_body" "{\"name\":$(json_string "$COMPANY_NAME"),\"location\":$(json_string "Oslo, Norway"),\"source\":$(json_string "LinkedIn")}" company_response="$TMP_DIR/company-response.json" request POST "company create" "$API_BASE/companies" "$company_response" json "$company_body" company_id="$(json_extract "$company_response" 'data["id"]')" [[ -n "$company_id" ]] || fail "Company create response did not include an id." company_update_body="$TMP_DIR/company-update.json" write_json "$company_update_body" "{\"name\":$(json_string "$COMPANY_NAME"),\"location\":$(json_string "Oslo, Norway"),\"source\":$(json_string "LinkedIn"),\"recruiterName\":$(json_string "Maria Recruiter"),\"recruiterEmail\":$(json_string "maria.recruiter@example.invalid"),\"recruiterLinkedIn\":$(json_string "https://www.linkedin.com/in/maria-recruiter"),\"lastContactedAt\":$(json_string "$CORRESPONDENCE_AT"),\"nextContactAt\":$(json_string "$FOLLOW_UP_AT"),\"pipelineStage\":$(json_string "Follow-up ready")}" company_update_response="$TMP_DIR/company-update-response.json" request PUT "company update" "$API_BASE/companies/$company_id" "$company_update_response" json "$company_update_body" jobs_lookup="$TMP_DIR/jobs-lookup.json" encoded_title="$(urlencode "$JOB_TITLE")" request GET "job lookup" "$API_BASE/jobapplications?page=1&pageSize=15&q=$encoded_title" "$jobs_lookup" json job_id="$(json_extract "$jobs_lookup" 'next((item["id"] for item in data.get("items", []) if item.get("jobTitle") == "S06 Acceptance Backend Engineer" and ((item.get("company") or {}).get("name") == "S06 Acceptance Labs")), "")')" job_action="updated" create_job_body="$TMP_DIR/create-job.json" write_json "$create_job_body" "{\"jobTitle\":$(json_string "$JOB_TITLE"),\"companyId\":$company_id,\"status\":$(json_string "Waiting"),\"location\":$(json_string "Oslo / Hybrid"),\"salary\":$(json_string "NOK 900000"),\"nextAction\":$(json_string "Review saved package and send a manual recruiter follow-up from the linked thread."),\"followUpAt\":$(json_string "$FOLLOW_UP_AT"),\"notes\":$(json_string $'Acceptance seed notes for S06.\n\n<<>>\nSaved acceptance application answer with concrete API and workflow evidence.\n<<>>'),\"description\":$(json_string "Lead the backend of an individual-first job tracking platform, maintain recruiter-thread continuity, and keep daily dashboard reminders trustworthy."),\"translatedDescription\":$(json_string "Backend role focused on workflow trust signals, saved application package reuse, and manual-send follow-up discipline."),\"descriptionLanguage\":$(json_string "en"),\"tags\":$(json_string "ASP.NET Core, React, SQLite, Workflow, Follow-up"),\"deadline\":$(json_string "2026-04-10T12:00:00Z"),\"coverLetterText\":$(json_string "Saved acceptance cover letter that references workflow trust signals, recruiter continuity, and measured backend delivery."),\"jobUrl\":$(json_string "$JOB_URL"),\"dateApplied\":$(json_string "$DATE_APPLIED"),\"feedbackRequestedAt\":null,\"hasResume\":true,\"hasCoverLetter\":true,\"hasPortfolio\":true,\"hasOtherAttachment\":false}" if [[ -z "$job_id" ]]; then create_job_response="$TMP_DIR/create-job-response.json" request POST "job create" "$API_BASE/jobapplications" "$create_job_response" json "$create_job_body" job_id="$(json_extract "$create_job_response" 'data["id"]')" job_action="created" fi [[ -n "$job_id" ]] || fail "Job create/update flow did not yield a job id." update_job_body="$TMP_DIR/update-job.json" write_json "$update_job_body" "{\"jobTitle\":$(json_string "$JOB_TITLE"),\"companyId\":$company_id,\"status\":$(json_string "Waiting"),\"responseReceived\":false,\"responseDate\":null,\"location\":$(json_string "Oslo / Hybrid"),\"salary\":$(json_string "NOK 900000"),\"nextAction\":$(json_string "Review saved package and send a manual recruiter follow-up from the linked thread."),\"followUpAt\":$(json_string "$FOLLOW_UP_AT"),\"hasResume\":true,\"hasCoverLetter\":true,\"hasPortfolio\":true,\"hasOtherAttachment\":false,\"notes\":$(json_string $'Acceptance seed notes for S06.\n\n<<>>\nSaved acceptance application answer with concrete API and workflow evidence.\n<<>>'),\"description\":$(json_string "Lead the backend of an individual-first job tracking platform, maintain recruiter-thread continuity, and keep daily dashboard reminders trustworthy."),\"translatedDescription\":$(json_string "Backend role focused on workflow trust signals, saved application package reuse, and manual-send follow-up discipline."),\"descriptionLanguage\":$(json_string "en"),\"tags\":$(json_string "ASP.NET Core, React, SQLite, Workflow, Follow-up"),\"deadline\":$(json_string "2026-04-10T12:00:00Z"),\"coverLetterText\":$(json_string "Saved acceptance cover letter that references workflow trust signals, recruiter continuity, and measured backend delivery."),\"jobUrl\":$(json_string "$JOB_URL"),\"dateApplied\":$(json_string "$DATE_APPLIED"),\"feedbackRequestedAt\":null,\"statusChangedAt\":null}" update_job_response="$TMP_DIR/update-job-response.json" request PUT "job update" "$API_BASE/jobapplications/$job_id" "$update_job_response" empty "$update_job_body" save_cv_body="$TMP_DIR/save-cv.json" write_json "$save_cv_body" "{\"tailoredCvText\":$(json_string "Saved acceptance tailored CV highlighting ASP.NET Core delivery, workflow trust signals, recruiter-thread continuity, and dashboard reminder ownership.")}" save_cv_response="$TMP_DIR/save-cv-response.json" request PUT "tailored CV save" "$API_BASE/jobapplications/$job_id/tailored-cv" "$save_cv_response" empty "$save_cv_body" save_drafts_body="$TMP_DIR/save-drafts.json" write_json "$save_drafts_body" "{\"coverLetterText\":$(json_string "Saved acceptance cover letter that references workflow trust signals, recruiter continuity, and measured backend delivery."),\"notes\":$(json_string $'Acceptance seed notes for S06.\n\n<<>>\nSaved acceptance application answer with concrete API and workflow evidence.\n<<>>'),\"recruiterMessageDraft\":$(json_string "Saved acceptance recruiter message draft that acknowledges the prior thread and keeps the human send step explicit.")}" save_drafts_response="$TMP_DIR/save-drafts-response.json" request PUT "application drafts save" "$API_BASE/jobapplications/$job_id/application-drafts" "$save_drafts_response" empty "$save_drafts_body" followup_body="$TMP_DIR/followup.json" write_json "$followup_body" "{\"followUpAt\":$(json_string "$FOLLOW_UP_AT")}" followup_response="$TMP_DIR/followup-response.json" request PATCH "follow-up schedule" "$API_BASE/jobapplications/$job_id/followup" "$followup_response" empty "$followup_body" correspondence_list="$TMP_DIR/correspondence-list.json" request GET "correspondence list" "$API_BASE/correspondence/$job_id" "$correspondence_list" json existing_correspondence_id="$(json_extract "$correspondence_list" 'next((item["id"] for item in data if item.get("externalMessageId") == "s06-acceptance-message-1"), "")')" correspondence_action="existing" if [[ -n "$existing_correspondence_id" ]]; then existing_subject="$(json_extract "$correspondence_list" 'next((item.get("subject") or "" for item in data if item.get("externalMessageId") == "s06-acceptance-message-1"), "")')" existing_content="$(json_extract "$correspondence_list" 'next((item.get("content") or "" for item in data if item.get("externalMessageId") == "s06-acceptance-message-1"), "")')" if [[ "$existing_subject" != "Backend Engineer follow-up" || "$existing_content" != "Hi Casey,\n\nThanks again for applying. We reviewed your saved package and would welcome a short manual follow-up next week so we can confirm timeline and next steps.\n\nBest,\nMaria" ]]; then delete_response="$TMP_DIR/delete-correspondence-response.json" request DELETE "correspondence delete" "$API_BASE/correspondence/$existing_correspondence_id" "$delete_response" empty existing_correspondence_id="" correspondence_action="replaced" fi fi if [[ -z "$existing_correspondence_id" ]]; then create_correspondence_body="$TMP_DIR/create-correspondence.json" write_json "$create_correspondence_body" "{\"jobApplicationId\":$job_id,\"from\":$(json_string "Maria Recruiter"),\"content\":$(json_string $'Hi Casey,\n\nThanks again for applying. We reviewed your saved package and would welcome a short manual follow-up next week so we can confirm timeline and next steps.\n\nBest,\nMaria'),\"subject\":$(json_string "Backend Engineer follow-up"),\"channel\":$(json_string "email"),\"date\":$(json_string "$CORRESPONDENCE_AT"),\"externalMessageId\":$(json_string "$MESSAGE_ID"),\"externalThreadId\":$(json_string "$THREAD_ID"),\"externalFrom\":$(json_string "maria.recruiter@example.invalid"),\"externalTo\":$(json_string "casey@example.invalid")}" create_correspondence_response="$TMP_DIR/create-correspondence-response.json" request POST "correspondence create" "$API_BASE/correspondence" "$create_correspondence_response" json "$create_correspondence_body" existing_correspondence_id="$(json_extract "$create_correspondence_response" 'data["id"]')" if [[ "$correspondence_action" != "replaced" ]]; then correspondence_action="created" fi fi request GET "job details" "$API_BASE/jobapplications/$job_id" "$TMP_DIR/job-details.json" json request GET "readiness" "$API_BASE/jobapplications/$job_id/readiness" "$TMP_DIR/readiness.json" json request GET "correspondence list final" "$API_BASE/correspondence/$job_id" "$TMP_DIR/correspondence-final.json" json workflow_action="$(json_extract "$TMP_DIR/readiness.json" 'data["workflowSignal"]["actionKey"]')" readiness_score="$(json_extract "$TMP_DIR/readiness.json" 'data["score"]')" readiness_level="$(json_extract "$TMP_DIR/readiness.json" 'data["level"]')" correspondence_count="$(json_extract "$TMP_DIR/correspondence-final.json" 'len(data)')" completed_items="$(json_extract "$TMP_DIR/readiness.json" '" | ".join(data.get("completed", []))')" reminder_items="$(json_extract "$TMP_DIR/readiness.json" '" | ".join(data.get("reminders", []))')" note "seed.result=success" note "seed.company.id=$company_id" note "seed.job.id=$job_id" note "seed.job.action=$job_action" note "seed.correspondence.id=$existing_correspondence_id" note "seed.correspondence.action=$correspondence_action" note "seed.correspondence.count=$correspondence_count" note "seed.readiness.score=$readiness_score" note "seed.readiness.level=$readiness_level" note "seed.workflow.action=$workflow_action" note "seed.followUp.at=$FOLLOW_UP_AT" note "seed.completed=$completed_items" note "seed.reminders=$reminder_items"