Landing page cookbook implementation (Weeks 1-4): Domain Infrastructure: - Add project_domains table with migration (013_project_domains.sql) - Add ProjectDomain model with domain types (primary_auto, primary_custom, alias) - Add SlugGenerator and ProjectDomainRepository interfaces - Implement postgres adapters for domain and slug management Service Layer: - Add domain CRUD methods to ProjectInfraService - Generate 8-char random slugs for auto-domains - Support custom subdomains during project creation - Add site_live health check to project status - Trigger CI build after template seeding Handler Updates: - Add DomainService interface and adapter pattern - Rewrite domain handlers to use database-backed service - Add proper error handling for duplicate/missing domains CI Integration: - Add TriggerBuild to CIProvider interface - Implement TriggerBuild in Woodpecker adapter - Manually trigger initial build after template seed Cookbook & Scripts: - Add landing-test.sh script for E2E testing - Add release.sh for version releases - Add logs.sh for quick log access Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
334 lines
10 KiB
Bash
Executable File
334 lines
10 KiB
Bash
Executable File
#!/bin/bash
|
|
# Landing Page Cookbook Test Script
|
|
# Tests the full landing page flow from cookbooks/landing-page.md
|
|
#
|
|
# Usage:
|
|
# ./cookbooks/scripts/landing-test.sh run # Run the full flow
|
|
# ./cookbooks/scripts/landing-test.sh teardown # Clean up test resources
|
|
# ./cookbooks/scripts/landing-test.sh status # Check current status
|
|
|
|
set -euo pipefail
|
|
|
|
# Configuration
|
|
API_URL="${RDEV_API_URL:-https://rdev.masq-ops.orchard9.ai}"
|
|
API_KEY="${RDEV_API_KEY:?RDEV_API_KEY environment variable required}"
|
|
PROJECT_NAME="${1:-landing-test}"
|
|
TEMPLATE="astro-landing"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
|
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
|
|
api_call() {
|
|
local method="$1"
|
|
local endpoint="$2"
|
|
local data="${3:-}"
|
|
|
|
if [[ -n "$data" ]]; then
|
|
curl -s -X "$method" "${API_URL}${endpoint}" \
|
|
-H "X-API-Key: ${API_KEY}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$data"
|
|
else
|
|
curl -s -X "$method" "${API_URL}${endpoint}" \
|
|
-H "X-API-Key: ${API_KEY}"
|
|
fi
|
|
}
|
|
|
|
check_health() {
|
|
log_info "Checking API health..."
|
|
local response
|
|
response=$(curl -s "${API_URL}/health")
|
|
if echo "$response" | jq -e '.data.status == "ok"' > /dev/null 2>&1; then
|
|
log_success "API is healthy"
|
|
return 0
|
|
else
|
|
log_error "API health check failed"
|
|
echo "$response" | jq .
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
run_flow() {
|
|
local project_name="${1:-landing-test}"
|
|
local custom_subdomain="${2:-}"
|
|
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " Landing Page Cookbook Test"
|
|
echo " Project: $project_name"
|
|
if [[ -n "$custom_subdomain" ]]; then
|
|
echo " Custom subdomain: $custom_subdomain"
|
|
fi
|
|
echo "=========================================="
|
|
echo ""
|
|
|
|
# Step 0: Health check
|
|
check_health || exit 1
|
|
echo ""
|
|
|
|
# Step 1: Create project
|
|
log_info "Step 1: Creating project with $TEMPLATE template..."
|
|
local create_payload="{
|
|
\"name\": \"$project_name\",
|
|
\"description\": \"Cookbook test: landing page flow\",
|
|
\"template\": \"$TEMPLATE\""
|
|
if [[ -n "$custom_subdomain" ]]; then
|
|
create_payload="$create_payload,
|
|
\"custom_subdomain\": \"$custom_subdomain\""
|
|
fi
|
|
create_payload="$create_payload
|
|
}"
|
|
|
|
local create_response
|
|
create_response=$(api_call POST "/project" "$create_payload")
|
|
|
|
if echo "$create_response" | jq -e '.error' > /dev/null 2>&1; then
|
|
log_error "Failed to create project"
|
|
echo "$create_response" | jq .
|
|
exit 1
|
|
fi
|
|
|
|
log_success "Project created"
|
|
echo "$create_response" | jq '{
|
|
project_id: .data.project_id,
|
|
slug: .data.slug,
|
|
git_url: .data.git.html_url,
|
|
domain: .data.domain,
|
|
url: .data.url,
|
|
domains: .data.domains,
|
|
next_steps: .data.next_steps
|
|
}'
|
|
|
|
# Extract domain info
|
|
local primary_domain
|
|
local slug
|
|
primary_domain=$(echo "$create_response" | jq -r '.data.domain')
|
|
slug=$(echo "$create_response" | jq -r '.data.slug // empty')
|
|
|
|
if [[ -n "$slug" ]]; then
|
|
log_success "Auto-generated slug: $slug"
|
|
fi
|
|
|
|
# Check for manual steps
|
|
local next_steps
|
|
next_steps=$(echo "$create_response" | jq -r '.data.next_steps[]?' 2>/dev/null || echo "")
|
|
if [[ -n "$next_steps" ]]; then
|
|
echo ""
|
|
log_warn "Some steps require manual intervention:"
|
|
echo "$create_response" | jq -r '.data.next_steps[]'
|
|
echo ""
|
|
log_info "Check API logs for details: ./scripts/logs.sh -e"
|
|
fi
|
|
echo ""
|
|
|
|
# Step 2: List all domains
|
|
log_info "Step 2: Listing all project domains..."
|
|
local domains_response
|
|
domains_response=$(api_call GET "/projects/$project_name/domains")
|
|
|
|
if echo "$domains_response" | jq -e '.error' > /dev/null 2>&1; then
|
|
log_warn "Could not list domains"
|
|
echo "$domains_response" | jq .
|
|
else
|
|
local domain_count
|
|
domain_count=$(echo "$domains_response" | jq -r '.data.total')
|
|
log_success "Found $domain_count domain(s)"
|
|
echo "$domains_response" | jq '.data.domains[] | {domain, type, verified}'
|
|
fi
|
|
echo ""
|
|
|
|
# Step 3: Verify project status
|
|
log_info "Step 3: Verifying project status..."
|
|
sleep 2 # Give DNS/Gitea a moment
|
|
|
|
local status_response
|
|
status_response=$(api_call GET "/project/$project_name")
|
|
|
|
if echo "$status_response" | jq -e '.error' > /dev/null 2>&1; then
|
|
log_warn "Could not fetch project status"
|
|
echo "$status_response" | jq .
|
|
else
|
|
log_success "Project status retrieved"
|
|
echo "$status_response" | jq '{
|
|
name: .data.name,
|
|
slug: .data.slug,
|
|
domain: .data.domain,
|
|
url: .data.url,
|
|
domains: .data.domains,
|
|
git: .data.git.html_url,
|
|
deployment: .data.deployment
|
|
}'
|
|
fi
|
|
echo ""
|
|
|
|
# Step 4: Check DNS
|
|
log_info "Step 4: Checking DNS resolution..."
|
|
if host "$primary_domain" > /dev/null 2>&1; then
|
|
log_success "DNS resolves: $primary_domain"
|
|
host "$primary_domain" | head -1
|
|
else
|
|
log_warn "DNS not yet resolving: $primary_domain (may take a few minutes)"
|
|
fi
|
|
echo ""
|
|
|
|
# Step 5: Summary
|
|
echo "=========================================="
|
|
echo " Summary"
|
|
echo "=========================================="
|
|
echo ""
|
|
echo " Git repo: $(echo "$create_response" | jq -r '.data.git.html_url')"
|
|
if [[ -n "$slug" ]]; then
|
|
echo " Slug: $slug"
|
|
fi
|
|
echo " Primary: https://$primary_domain"
|
|
echo ""
|
|
echo " All domains:"
|
|
echo "$domains_response" | jq -r '.data.domains[]? | " - \(.domain) (\(.type))"' 2>/dev/null || echo " (none listed)"
|
|
echo ""
|
|
echo " Next steps:"
|
|
echo " 1. If CI/template failed, check logs: ./scripts/logs.sh -e"
|
|
echo " 2. Push code to trigger CI build"
|
|
echo " 3. Monitor pipeline: curl -s -H 'X-API-Key: \$RDEV_API_KEY' $API_URL/projects/$project_name/pipelines | jq"
|
|
echo " 4. Verify site: curl -I https://$primary_domain"
|
|
echo ""
|
|
echo " Add custom domain:"
|
|
echo " curl -X POST -H 'X-API-Key: \$RDEV_API_KEY' -H 'Content-Type: application/json' \\"
|
|
echo " -d '{\"domain\":\"my-site.threesix.ai\"}' \\"
|
|
echo " $API_URL/projects/$project_name/domains"
|
|
echo ""
|
|
echo " Teardown:"
|
|
echo " ./cookbooks/scripts/landing-test.sh teardown $project_name"
|
|
echo ""
|
|
}
|
|
|
|
teardown() {
|
|
local project_name="${1:-landing-test}"
|
|
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " Teardown: $project_name"
|
|
echo "=========================================="
|
|
echo ""
|
|
|
|
# First, list domains that will be deleted
|
|
log_info "Checking domains to be deleted..."
|
|
local domains_response
|
|
domains_response=$(api_call GET "/projects/$project_name/domains")
|
|
|
|
if echo "$domains_response" | jq -e '.data.total' > /dev/null 2>&1; then
|
|
local domain_count
|
|
domain_count=$(echo "$domains_response" | jq -r '.data.total')
|
|
log_info "Will delete $domain_count domain(s):"
|
|
echo "$domains_response" | jq -r '.data.domains[]? | " - \(.domain)"'
|
|
echo ""
|
|
fi
|
|
|
|
log_info "Deleting project $project_name..."
|
|
local response
|
|
response=$(api_call DELETE "/project/$project_name")
|
|
|
|
if echo "$response" | jq -e '.data.status == "deleted"' > /dev/null 2>&1; then
|
|
log_success "Project deleted"
|
|
echo "$response" | jq .
|
|
elif echo "$response" | jq -e '.error.code == "NOT_FOUND"' > /dev/null 2>&1; then
|
|
log_warn "Project not found (already deleted?)"
|
|
else
|
|
log_error "Failed to delete project"
|
|
echo "$response" | jq .
|
|
exit 1
|
|
fi
|
|
echo ""
|
|
|
|
log_info "Note: Gitea repo is preserved for safety. Delete manually if needed:"
|
|
echo " https://git.threesix.ai/jordan/$project_name/settings"
|
|
echo ""
|
|
}
|
|
|
|
status() {
|
|
local project_name="${1:-landing-test}"
|
|
|
|
echo ""
|
|
log_info "Fetching status for: $project_name"
|
|
echo ""
|
|
|
|
local response
|
|
response=$(api_call GET "/project/$project_name")
|
|
|
|
if echo "$response" | jq -e '.error' > /dev/null 2>&1; then
|
|
log_error "Project not found or error"
|
|
echo "$response" | jq .
|
|
exit 1
|
|
fi
|
|
|
|
echo "$response" | jq '{
|
|
name: .data.name,
|
|
description: .data.description,
|
|
slug: .data.slug,
|
|
domain: .data.domain,
|
|
url: .data.url,
|
|
domains: .data.domains,
|
|
git: .data.git,
|
|
deployment: .data.deployment
|
|
}'
|
|
|
|
echo ""
|
|
log_info "Listing all domains..."
|
|
local domains_response
|
|
domains_response=$(api_call GET "/projects/$project_name/domains")
|
|
|
|
if echo "$domains_response" | jq -e '.data.domains' > /dev/null 2>&1; then
|
|
echo "$domains_response" | jq '.data.domains'
|
|
fi
|
|
}
|
|
|
|
# Main
|
|
case "${1:-}" in
|
|
run)
|
|
shift
|
|
run_flow "${1:-landing-test}" "${2:-}"
|
|
;;
|
|
teardown)
|
|
shift
|
|
teardown "${1:-landing-test}"
|
|
;;
|
|
status)
|
|
shift
|
|
status "${1:-landing-test}"
|
|
;;
|
|
*)
|
|
echo "Usage: $0 {run|teardown|status} [project-name] [custom-subdomain]"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " run [name] [subdomain] Create project and run full flow"
|
|
echo " teardown [name] Delete project and clean up"
|
|
echo " status [name] Check current project status"
|
|
echo ""
|
|
echo "Features tested:"
|
|
echo " - Auto-generated random slug (8-char) for primary domain"
|
|
echo " - Optional custom subdomain (e.g., 'my-app' -> my-app.threesix.ai)"
|
|
echo " - Multi-domain listing via /projects/{id}/domains"
|
|
echo " - DNS record creation and cleanup"
|
|
echo ""
|
|
echo "Environment:"
|
|
echo " RDEV_API_URL API endpoint (default: https://rdev.masq-ops.orchard9.ai)"
|
|
echo " RDEV_API_KEY API key (required)"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 run # Test with auto-slug only"
|
|
echo " $0 run my-landing # Test with custom project name"
|
|
echo " $0 run my-landing my-site # Also create my-site.threesix.ai"
|
|
echo " $0 status my-landing # Check status and domains"
|
|
echo " $0 teardown my-landing # Clean up (deletes all domains)"
|
|
exit 1
|
|
;;
|
|
esac
|