diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4e28dc7 --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# Brave Search API Key +# Get from https://brave.com/search/api/ +BRAVE_API_KEY=your_brave_api_key_here + +# OpenAI API Key +# Get from https://platform.openai.com/api-keys +OPENAI_API_KEY=sk-your_openai_api_key_here + +# Optional: Cache TTL in seconds (default: 300) +CACHE_TTL_SECONDS=300 + +# Optional: Port (default: 3000) +PORT=3000 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4e24b85 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Run tests + run: bun test + + - name: Type check + run: bun run tsc --noEmit diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..45e716c --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,93 @@ +# QueryX Deployment Guide + +## Prerequisites + +- [Railway](https://railway.app) account +- [Railway CLI](https://docs.railway.app/guides/cli) installed +- Brave Search API key +- OpenAI API key + +## Step 1: Install Railway CLI + +```bash +# Install via npm +npm install -g @railway/cli + +# Or via brew +brew install railway +``` + +## Step 2: Login to Railway + +```bash +railway login +``` + +## Step 3: Initialize Project + +```bash +# Navigate to your project directory +cd queryx + +# Initialize Railway project +railway init + +# Follow the prompts to create a new project +``` + +## Step 4: Configure Environment Variables + +Set the required environment variables via the Railway dashboard: + +1. Go to your project on [Railway Dashboard](https://railway.dashboard) +2. Click on the environment (e.g., "production") +3. Click "Variables" tab +4. Add the following variables: + +| Variable | Description | Required | +|----------|-------------|----------| +| `BRAVE_API_KEY` | Brave Search API key | Yes | +| `OPENAI_API_KEY` | OpenAI API key for synthesis | Yes | +| `CACHE_TTL_SECONDS` | Cache TTL in seconds (default: 300) | No | +| `PORT` | Server port (default: 3000) | No | + +## Step 5: Deploy + +```bash +railway up +``` + +The deployment will: +1. Build the Docker container +2. Start the server on port 3000 +3. Run health checks on `/health` + +## Step 6: Verify Deployment + +```bash +# Check health endpoint +curl https://your-project-name.up.railway.app/health + +# Should return: { "status": "ok" } +``` + +## Step 7: Custom Domain Setup (Optional) + +1. Go to your project settings on Railway +2. Click "Domains" +3. Click "Generate Domain" for Railway-provided domain +4. Or add custom domain (e.g., `queryx.run`) + +## Troubleshooting + +### Health check fails +- Ensure `PORT` is set to `3000` +- Check logs: `railway logs` + +### API errors +- Verify `BRAVE_API_KEY` is valid +- Verify `OPENAI_API_KEY` has credits + +### Rate limiting +- Reduce `CACHE_TTL_SECONDS` for shorter cache +- Check Brave API rate limits diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..39c4036 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM oven/bun:1 AS base +WORKDIR /app +COPY package.json bun.lockb ./ +RUN bun install --frozen-lockfile +COPY . . +EXPOSE 3000 +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \ + CMD curl -f http://localhost:3000/health || exit 1 +CMD ["bun", "run", "src/index.ts"] diff --git a/railway.json b/railway.json new file mode 100644 index 0000000..635788a --- /dev/null +++ b/railway.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://railway.app/railway.schema.json", + "build": { + "builder": "DOCKERFILE" + }, + "deploy": { + "healthcheckPath": "/health", + "healthcheckTimeout": 10, + "restartPolicyType": "ON_FAILURE", + "restartPolicyMaxRetries": 3, + "numReplicas": 1 + } +} diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh new file mode 100755 index 0000000..ea0cf21 --- /dev/null +++ b/scripts/smoke-test.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Smoke test script for QueryX deployment + +set -e + +# Get base URL from first argument or use default +BASE_URL="${1:-http://localhost:3000}" + +echo "Running smoke tests against $BASE_URL" +echo "======================================" + +# Test 1: Health check +echo "Test 1: Health check..." +HEALTH_RESPONSE=$(curl -s -w "\n%{http_code}" "$BASE_URL/health") +HTTP_CODE=$(echo "$HEALTH_RESPONSE" | tail -n1) +BODY=$(echo "$HEALTH_RESPONSE" | head -n-1) + +if [ "$HTTP_CODE" != "200" ]; then + echo "FAIL: Health check returned HTTP $HTTP_CODE" + echo "Response: $BODY" + exit 1 +fi + +if ! echo "$BODY" | grep -q "ok"; then + echo "FAIL: Health response does not contain 'ok'" + echo "Response: $BODY" + exit 1 +fi + +echo "PASS: Health check returned 200 with { status: 'ok' }" + +# Test 2: /v1/search returns 402 without payment +echo "" +echo "Test 2: /v1/search without payment..." +SEARCH_RESPONSE=$(curl -s -w "\n%{http_code}" "$BASE_URL/v1/search?q=test") +HTTP_CODE=$(echo "$SEARCH_RESPONSE" | tail -n1) + +if [ "$HTTP_CODE" != "402" ]; then + echo "FAIL: /v1/search should return 402 without payment, got $HTTP_CODE" + exit 1 +fi + +echo "PASS: /v1/search returns 402 Payment Required without payment" + +# Test 3: Verify x402 headers in 402 response +echo "" +echo "Test 3: Check x402 headers in 402 response..." +HEADERS=$(curl -s -D - "$BASE_URL/v1/search?q=test" -o /dev/null) + +if ! echo "$HEADERS" | grep -qi "x402"; then + echo "FAIL: 402 response missing x402 headers" + echo "Headers: $HEADERS" + exit 1 +fi + +echo "PASS: 402 response includes x402 headers" + +echo "" +echo "======================================" +echo "All smoke tests passed!" +exit 0