mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-31 23:47:51 +00:00
Closes the two gaps found smoke-testing the local stack: an empty gallery (zero media_files rows, media URLs hardcoded to the remote r2.dev bucket) and generation flows that could not complete (omni_gen calls providers synchronously in the handler, so dummy keys meant a 500 after billing; the webapp blocked submits earlier anyway because signup grants no wallet). Backend (all development-only): - CDN_BASE_URL env override in cdn_link.rs (host-only, validated loudly; precedence over the compiled-in hosts for every domain/env) and a dev-gated actix-files mount serving LOCAL_MEDIA_ROOT/media at /media, mirroring the public bucket layout - media URLs in API responses resolve against the backend itself - DEV_FAKE_GENERATION flag on ServerState (easyenv bool, forced off outside Development): the omni_gen image/video pipelines skip the external provider call and return a synthetic Fal enqueue (external id fake_<uuid>), so billing, prompt insert, and the pending generic_inference_jobs row all stay real - dev_fake_generation_resolver_thread: polls pending fake_ jobs (new runtime-query list_pending_fake_generation_jobs in mysql_queries) and after DEV_FAKE_GENERATION_RESOLVE_SECS (default 6s) marks them complete_success with a media_files row backed by a test_data asset copied under LOCAL_MEDIA_ROOT (mp4 results get -thumb.jpg/-thumb.gif siblings since the thumbnail job never runs locally), or complete_failure via the existing simulate_artcraft_failure prompt triggers (category word supported, e.g. "simulate_artcraft_failure face_not_detected") Bootstrap/seeds (PS + bash kept in lockstep): - seed_demo_user seeds an artcraft wallet (DEMO_CREDITS, default 100k banked + ledger rows; idempotent top-up) - signup creates no wallet and the webapp gates generate on balance, which is why submits went nowhere - seed_demo_user also seeds 6 gallery images (test_data jpgs) with objects under .devstack/media in the exact bucket fan-out layout - fixed the already-exists detection in seed_demo_user: create_account returns error_type, not error_code, so re-runs died on UsernameTaken - generated secrets env gains DEV_FAKE_GENERATION/CDN_BASE_URL/ LOCAL_MEDIA_ROOT (appended to existing files on bootstrap re-runs); stale "providers are only contacted by workers" comments corrected - doctor checks wallet balance, media rows, and the new secrets keys Docs: dev_setup_local_stack.md documents the new flags, corrects the sync-provider-call and upload-500 claims, updates the seams table and verification checklist; storyteller_web CLAUDE.md video model examples fixed (seedance_2p0_fast_g -> seedance_2p0_fast). Verified E2E on this machine: gallery lists 8 items on localhost URLs; image and video submits go pending ~6s then complete with locally-served results (mp4 + gif preview 200); failure trigger yields complete_failure with category face_not_detected; credits genuinely deducted (100000 -> 99932); doctor 0 failures / 0 warnings.
172 lines
8.4 KiB
Bash
Executable File
172 lines
8.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Create (or verify) the local demo user via the running backend, then verify
|
|
# that login works. Idempotent: an already-existing user is a success.
|
|
#
|
|
# Requires the backend to be up (./script/bootstrap/run_backend_dev.sh) — this
|
|
# script waits up to --wait-seconds (default 60) for it.
|
|
#
|
|
# The account is created through POST /v1/create_account rather than raw SQL so
|
|
# it exercises the real signup path (bcrypt hash, session, firehose row) and
|
|
# stays correct if the users schema evolves.
|
|
#
|
|
# Override the credentials via DEMO_USERNAME / DEMO_PASSWORD / DEMO_EMAIL.
|
|
# Username rules: 3-16 chars of [A-Za-z0-9_-], not on the reserved list
|
|
# (includes/binary_includes/usernames/reserved_usernames.txt); password >= 6 chars.
|
|
set -euo pipefail
|
|
|
|
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
source "${root_dir}/script/bootstrap/common.sh"
|
|
|
|
WAIT_SECONDS=60
|
|
for arg in "$@"; do
|
|
case "${arg}" in
|
|
--wait-seconds=*) WAIT_SECONDS="${arg#*=}" ;;
|
|
--help|-h) sed -n '2,15p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
*) die "Unknown option: ${arg} (try --help)" ;;
|
|
esac
|
|
done
|
|
|
|
command -v jq >/dev/null 2>&1 || die "jq is required (apt-get install jq)."
|
|
|
|
log "Waiting for backend at ${DEV_BACKEND_URL} (up to ${WAIT_SECONDS}s)..."
|
|
deadline=$((SECONDS + WAIT_SECONDS))
|
|
until backend_reachable; do
|
|
if [ "${SECONDS}" -ge "${deadline}" ]; then
|
|
die "Backend not reachable at ${DEV_BACKEND_URL}/_status. Start it with ./script/bootstrap/run_backend_dev.sh"
|
|
fi
|
|
sleep 2
|
|
done
|
|
log "Backend is up."
|
|
|
|
log "Creating account '${DEMO_USERNAME}'..."
|
|
create_response="$(curl -sS -X POST "${DEV_BACKEND_URL}/v1/create_account" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$(jq -n \
|
|
--arg u "${DEMO_USERNAME}" \
|
|
--arg p "${DEMO_PASSWORD}" \
|
|
--arg e "${DEMO_EMAIL}" \
|
|
'{username: $u, password: $p, password_confirmation: $p, email_address: $e}')")"
|
|
|
|
if [ "$(echo "${create_response}" | jq -r '.success // false')" = "true" ]; then
|
|
log "Account created."
|
|
else
|
|
error_code="$(echo "${create_response}" | jq -r '.error_type // .error_code // .error_code_str // empty')"
|
|
case "${error_code}" in
|
|
*[Uu]sername[Tt]aken*|*[Ee]mail[Tt]aken*)
|
|
log "Account already exists (${error_code}) — continuing."
|
|
;;
|
|
*)
|
|
die "Account creation failed: ${create_response}"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
log "Verifying login..."
|
|
login_response="$(curl -sS -X POST "${DEV_BACKEND_URL}/v1/login" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$(jq -n \
|
|
--arg u "${DEMO_USERNAME}" \
|
|
--arg p "${DEMO_PASSWORD}" \
|
|
'{username_or_email: $u, password: $p}')")"
|
|
|
|
if [ "$(echo "${login_response}" | jq -r '.success // false')" != "true" ]; then
|
|
die "Login verification failed: ${login_response}"
|
|
fi
|
|
if [ -z "$(echo "${login_response}" | jq -r '.signed_session // empty')" ]; then
|
|
die "Login succeeded but returned no signed_session: ${login_response}"
|
|
fi
|
|
|
|
# --- Credits wallet ---------------------------------------------------------
|
|
# Generation submits deduct from the user's artcraft wallet and the webapp
|
|
# gates the generate button on the balance, so a user without credits cannot
|
|
# exercise any generation flow. Signup does not create a wallet; seed one.
|
|
# Raw SQL (not an endpoint): the only credit-granting paths are Stripe
|
|
# webhooks and moderator tooling, neither of which works locally.
|
|
log "Seeding credits wallet (${DEMO_CREDITS} banked credits)..."
|
|
|
|
user_token="$(mysql_app -N -e "SELECT token FROM users WHERE username = '${DEMO_USERNAME}' LIMIT 1")"
|
|
[ -n "${user_token}" ] || die "Could not find users row for '${DEMO_USERNAME}'."
|
|
|
|
wallet_row="$(mysql_app -N -e "SELECT token, banked_credits FROM wallets WHERE wallet_namespace = 'artcraft' AND owner_user_token = '${user_token}' LIMIT 1")"
|
|
|
|
if [ -z "${wallet_row}" ]; then
|
|
wallet_token="$(new_dev_token "wallet_" 32)"
|
|
create_ledger_token="$(new_dev_token "wle_" 32)"
|
|
credit_ledger_token="$(new_dev_token "wle_" 32)"
|
|
mysql_app -e "
|
|
INSERT INTO wallets SET token='${wallet_token}', wallet_namespace='artcraft', owner_user_token='${user_token}', banked_credits=${DEMO_CREDITS}, monthly_credits=0;
|
|
INSERT INTO wallet_ledger_entries SET token='${create_ledger_token}', wallet_token='${wallet_token}', entry_type='create', credits_delta=0, banked_credits_before=0, banked_credits_after=0, monthly_credits_before=0, monthly_credits_after=0;
|
|
INSERT INTO wallet_ledger_entries SET token='${credit_ledger_token}', wallet_token='${wallet_token}', entry_type='credit_banked', maybe_entity_ref='dev_bootstrap_seed', credits_delta=${DEMO_CREDITS}, banked_credits_before=0, banked_credits_after=${DEMO_CREDITS}, monthly_credits_before=0, monthly_credits_after=0;
|
|
" || die "Failed to insert the demo wallet."
|
|
log "Wallet created with ${DEMO_CREDITS} banked credits."
|
|
else
|
|
wallet_token="$(echo "${wallet_row}" | cut -f1)"
|
|
balance="$(echo "${wallet_row}" | cut -f2)"
|
|
if [ "${balance}" -lt "${DEMO_CREDITS}" ]; then
|
|
credit_ledger_token="$(new_dev_token "wle_" 32)"
|
|
delta=$((DEMO_CREDITS - balance))
|
|
mysql_app -e "
|
|
UPDATE wallets SET banked_credits=${DEMO_CREDITS}, version=version+1 WHERE token='${wallet_token}' LIMIT 1;
|
|
INSERT INTO wallet_ledger_entries SET token='${credit_ledger_token}', wallet_token='${wallet_token}', entry_type='credit_banked', maybe_entity_ref='dev_bootstrap_topup', credits_delta=${delta}, banked_credits_before=${balance}, banked_credits_after=${DEMO_CREDITS}, monthly_credits_before=0, monthly_credits_after=0;
|
|
" || die "Failed to top up the demo wallet."
|
|
log "Wallet topped up: ${balance} -> ${DEMO_CREDITS} banked credits."
|
|
else
|
|
log "Wallet already has ${balance} banked credits — leaving as-is."
|
|
fi
|
|
fi
|
|
|
|
# --- Gallery media ----------------------------------------------------------
|
|
# Seed a few images so the library/gallery isn't empty on a fresh stack. The
|
|
# files live under .devstack/media (served by the backend at /media when
|
|
# LOCAL_MEDIA_ROOT + CDN_BASE_URL are set — bootstrap writes both into the
|
|
# generated secrets env). Rows mirror what a real image upload would write.
|
|
log "Seeding demo gallery media..."
|
|
|
|
seed_sources=(
|
|
"test_data/image/juno.jpg|Dev seed - Juno 1"
|
|
"test_data/image/mochi.jpg|Dev seed - Mochi 1"
|
|
"test_data/image/juno.jpg|Dev seed - Juno 2"
|
|
"test_data/image/mochi.jpg|Dev seed - Mochi 2"
|
|
"test_data/image/juno.jpg|Dev seed - Juno 3"
|
|
"test_data/image/mochi.jpg|Dev seed - Mochi 3"
|
|
)
|
|
|
|
existing_media="$(mysql_app -N -e "SELECT COUNT(*) FROM media_files WHERE maybe_creator_user_token='${user_token}' AND maybe_title LIKE 'Dev seed %'" 2>/dev/null || echo 0)"
|
|
if [ "${existing_media}" -ge "${#seed_sources[@]}" ]; then
|
|
log "Gallery already has ${existing_media} seeded media rows — leaving as-is."
|
|
else
|
|
for entry in "${seed_sources[@]}"; do
|
|
source_rel="${entry%%|*}"
|
|
title="${entry#*|}"
|
|
source_path="${root_dir}/${source_rel}"
|
|
[ -f "${source_path}" ] || die "Seed image missing: ${source_path}"
|
|
|
|
# Mirror MediaFileBucketPath: /media/{h0..h4}/{hash}/image_{hash}.jpg
|
|
hash="$(new_dev_token "" 32)"
|
|
fanout="${hash:0:1}/${hash:1:1}/${hash:2:1}/${hash:3:1}/${hash:4:1}"
|
|
object_dir="${DEV_MEDIA_ROOT}/media/${fanout}/${hash}"
|
|
mkdir -p "${object_dir}"
|
|
cp "${source_path}" "${object_dir}/image_${hash}.jpg"
|
|
|
|
sha="$(sha256sum "${source_path}" | cut -d' ' -f1)"
|
|
size="$(stat -c%s "${source_path}" 2>/dev/null || stat -f%z "${source_path}")"
|
|
media_token="$(new_dev_token "m_" 32)"
|
|
|
|
mysql_app -e "
|
|
INSERT INTO media_files SET token='${media_token}', maybe_creator_user_token='${user_token}',
|
|
origin_category='upload', origin_product_category='unknown', media_class='image', media_type='jpg',
|
|
maybe_mime_type='image/jpeg', file_size_bytes=${size}, checksum_sha2='${sha}',
|
|
public_bucket_directory_hash='${hash}', maybe_public_bucket_prefix='image_', maybe_public_bucket_extension='.jpg',
|
|
creator_ip_address='127.0.0.1', creator_set_visibility='public', is_user_upload=1, is_intermediate_system_file=0,
|
|
maybe_title='${title}';
|
|
" || die "Failed to insert seeded media row for ${title}."
|
|
done
|
|
log "Seeded ${#seed_sources[@]} gallery images under ${DEV_MEDIA_ROOT}."
|
|
fi
|
|
|
|
log "Demo user is ready."
|
|
log " Username: ${DEMO_USERNAME}"
|
|
log " Password: ${DEMO_PASSWORD}"
|
|
log "Log in at the webapp /login page, or via curl:"
|
|
log " curl -c /tmp/cookies.txt -X POST ${DEV_BACKEND_URL}/v1/login -H 'Content-Type: application/json' -d '{\"username_or_email\":\"${DEMO_USERNAME}\",\"password\":\"${DEMO_PASSWORD}\"}'"
|