rdev/cookbooks/trees/slackpath-1-authenticated-service.yaml
jordan 592b2d5ec0 fix: clarify database types across docs and fix video storage persistence
Two distinct fixes:

1. Database terminology: Make it crystal clear that generated projects use
   CockroachDB in production and PostgreSQL for local dev, while the rdev
   platform itself uses PostgreSQL. Updated 15 files across skeleton agents,
   component templates, cookbook trees, and platform docs.

2. Video storage: VideoHandler was ignoring vid.Data bytes (already downloaded
   by the Gemini adapter with auth) and re-downloading from the provider URL
   with a plain GET — which fails because Gemini URLs require API key auth.
   Now uses vid.Data first, falls back to downloadURL only for public URLs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 23:13:21 -07:00

149 lines
4.6 KiB
YAML

name: authenticated-service
description: "Slack Path 1: Identity Layer. Implements User Auth, JWT generation, and Protected Middleware."
version: 1
vars:
project_name: ""
service_name: "auth-api"
feature_slug: "auth-system"
steps:
# --- Infrastructure ---
create-project:
action: api
method: POST
endpoint: /project
body:
name: "{{ .vars.project_name }}"
description: "Slack Path 1: Authentication"
outputs:
- project_id: .data.name
- domain: .data.domain
add-db:
description: Add CockroachDB for user storage
depends_on: [create-project]
action: api
method: POST
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
body:
type: postgres
name: "main-db"
outputs:
- db_url: .data.connection_string
add-service:
description: Add API service
depends_on: [add-db]
action: api
method: POST
endpoint: "/projects/{{ .outputs.create-project.project_id }}/components"
body:
type: service
name: "{{ .vars.service_name }}"
template: service
wait-init:
depends_on: [add-service]
action: wait_pipeline
project_id: "{{ .outputs.create-project.project_id }}"
# --- SDLC: Build Auth ---
create-feature:
depends_on: [wait-init]
action: api
method: POST
endpoint: "/projects/{{ .outputs.create-project.project_id }}/sdlc/features"
body:
slug: "{{ .vars.feature_slug }}"
title: "Authentication System"
implement-auth:
description: "Agent implements Login, Register, and JWT Middleware"
depends_on: [create-feature]
action: api
method: POST
endpoint: "/projects/{{ .outputs.create-project.project_id }}/builds"
body:
prompt: "/implement-feature {{ .vars.feature_slug }} --requirements 'User model with email/password. POST /register, POST /login (returns JWT). Middleware that checks Authorization header. GET /me returns user profile.'"
auto_commit: true
auto_push: true
git_clone_url: "https://git.threesix.ai/jordan/{{ .outputs.create-project.project_id }}.git"
outputs:
- build_id: .data.task_id
wait-build:
description: Wait for agent code generation
depends_on: [implement-auth]
action: wait_build
build_id: "{{ .outputs.implement-auth.build_id }}"
max_attempts: 120
poll_interval: 5
wait-deploy:
depends_on: [wait-build]
action: wait_pipeline
project_id: "{{ .outputs.create-project.project_id }}"
# --- Verification ---
verify-service-running:
description: "Verify the auth service is running and reachable"
depends_on: [wait-deploy]
action: shell
command: |
DOMAIN="{{ .outputs.create-project.domain }}"
SERVICE_NAME="{{ .vars.service_name }}"
# Check health endpoint
HEALTH=$(curl -s "https://$DOMAIN/api/$SERVICE_NAME/health" | jq -r '.data.status // empty')
if [ "$HEALTH" == "healthy" ]; then
echo "Service healthy: /api/$SERVICE_NAME/health returned healthy"
exit 0
else
echo "Fail: service not healthy"
exit 1
fi
verify-login-flow:
description: "Register -> Login -> Access Protected Route (optional - depends on agent implementation)"
depends_on: [verify-service-running]
on_error: continue
action: shell
command: |
DOMAIN="{{ .outputs.create-project.domain }}"
SERVICE_NAME="{{ .vars.service_name }}"
PROJECT_ID="{{ .outputs.create-project.project_id }}"
EMAIL="test-${PROJECT_ID}@example.com"
BASE_URL="https://$DOMAIN/api/$SERVICE_NAME"
# 1. Register
echo "Registering $EMAIL..."
REGISTER_RESP=$(curl -s -X POST "$BASE_URL/register" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$EMAIL\",\"password\":\"hunter2\"}")
echo "Register response: $REGISTER_RESP"
# 2. Login
echo "Logging in..."
LOGIN_RESP=$(curl -s -X POST "$BASE_URL/login" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$EMAIL\",\"password\":\"hunter2\"}")
echo "Login response: $LOGIN_RESP"
TOKEN=$(echo "$LOGIN_RESP" | jq -r .token)
if [ -z "$TOKEN" ] || [ "$TOKEN" == "null" ]; then
echo "Failed: Could not get token from login response"
exit 1
fi
# 3. Access Protected
echo "Accessing protected route..."
RESP=$(curl -s -H "Authorization: Bearer $TOKEN" "$BASE_URL/me")
echo "Protected response: $RESP"
if echo "$RESP" | grep -q "$EMAIL"; then echo "Login flow OK"; exit 0; else echo "Failed: Email not found in response"; exit 1; fi
teardown:
- action: api
method: DELETE
endpoint: "/project/{{ .outputs.create-project.project_id }}"