Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .claude/rules/infra/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ AWS CDK infrastructure projects live in `infra/`. Each project is independent fr
| smalruby-mesh-v2 | `infra/smalruby-mesh-v2/` | Mesh v2 networking service (AppSync + DynamoDB) |
| smalruby-rubytee-relay | `infra/smalruby-rubytee-relay/` | Rubytee AI relay (API Gateway + Lambda + DynamoDB) |
| smalruby-classroom | `infra/smalruby-classroom/` | Classroom service (API Gateway + Lambda + DynamoDB + S3) |
| smalruby-api | `infra/smalruby-api/` | Smalruby API endpoints (HTTP API v2 + Lambda): cors-proxy, mesh-domain, scratch-api-proxy/* |

See project-specific rules for details:
- `.claude/rules/infra/smalruby-mesh-v2.md`
- `.claude/rules/infra/smalruby-classroom.md`
- `.claude/rules/infra/smalruby-rubytee-relay.md`
- `.claude/rules/infra/smalruby-api.md`

## Docker Service

Expand Down
98 changes: 98 additions & 0 deletions .claude/rules/infra/smalruby-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# smalruby-api

CDK project for Smalruby's API Gateway endpoints (HTTP API v2 + Lambda).

旧 SAM 実装 (`smalruby/smalruby-infra` リポジトリ) の置き換え。
4 エンドポイントを TypeScript Lambda + HTTP API v2 (built-in CORS) に移行する。

## Endpoints

| Path | Method | Lambda 関数名 (prod) | 説明 |
|------|--------|----------------------|------|
| `/cors-proxy` | GET | `smalruby-cors-proxy` | 任意 URL のフェッチ + Google Drive URL 変換 + バイナリ Base64 化 |
| `/mesh-domain` | GET | `smalruby-mesh-zone-get` | source IP から Mesh ドメイン (CRC32) を生成 |
| `/scratch-api-proxy/projects/{projectId}` | GET | `smalruby-scratch-api-projects` | Scratch API のプロジェクト情報取得プロキシ (status pass-through) |
| `/scratch-api-proxy/translate` | GET | `smalruby-scratch-api-translate` | Scratch translate サービスプロキシ |

stg では Lambda 関数名に `-stg` サフィックスが付く。
OPTIONS (preflight) は HTTP API v2 の built-in CORS で自動処理 — 旧 `cors-for-smalruby` Lambda は不要。

## Custom Domains

| Stage | Domain |
|-------|--------|
| stg | `stg.api.smalruby.app` |
| prod | `api.smalruby.app` |

prod ドメインは旧 SAM スタック (`smalruby-infra-prod`) が現在保持しているため、
prod カットオーバーは旧スタックのドメイン解放と協調が必要 (後続作業)。

## Commands

```bash
# Install
docker compose run --rm -w /app/infra/smalruby-api infra npm install

# Synth / diff / deploy
docker compose run --rm -w /app/infra/smalruby-api infra npx cdk synth
docker compose run --rm -w /app/infra/smalruby-api infra npx cdk diff
docker compose run --rm -w /app/infra/smalruby-api infra npx cdk deploy

# Unit tests (mocked fetch, fast)
docker compose run --rm -w /app/infra/smalruby-api infra npm test

# Integration tests (実際の stg エンドポイントへ HTTP 送信)
docker compose run --rm -w /app/infra/smalruby-api infra npm run test:integration
```

## Integration Tests

`lambda/tests/*.integration.test.ts` は **デプロイ済み stg エンドポイント** に対して
実際の HTTP リクエストを送信して動作を検証する。コーナーケース確認とデグレ防止が目的。

- Issue #573 の **404 透過バグ再発防止** が最重要のテストケース
- 必要な環境変数: `SMALRUBY_API_ENDPOINT` (`.env.stg` で設定済み、デフォルト `https://stg.api.smalruby.app`)
- デプロイ後は必ず `npm run test:integration` を実行して 18 テストすべて緑であることを確認する
- CI では実行しない (npm test のユニットテストとは独立。ローカル/手動運用)

## Stage Switching

```bash
cd infra/smalruby-api
rm .env && ln -s .env.stg .env # → stg
rm .env && ln -s .env.prod .env # → prod
```

## Environment Variables

| Variable | Description |
|----------|-------------|
| `STAGE` | Deployment stage (`stg`, `stg2`, `prod`) |
| `CORS_ALLOWED_ORIGINS` | Comma-separated allowed origins |
| `ROUTE53_PARENT_ZONE_NAME` | Parent zone for custom domain (default: `api.smalruby.app`) |
| `SMALRUBY_API_CUSTOM_DOMAIN` | Override custom domain. Set `false` to disable |
| `MESH_ZONE_SECRET_KEY` | **Secret** — used to derive Mesh group identity from source IP |

## Migration Notes

旧 SAM スタックとの主な差分:

1. **REST API v1 → HTTP API v2** へ変更。built-in CORS で OPTIONS Lambda 不要に
2. **Ruby 3.3 → Node.js 20.x (TypeScript)** で他 infra プロジェクトと言語を統一
3. **`scratch-api-proxy/projects/{projectId}` のステータスコード透過** — 旧実装は `Net::HTTP.get` でボディだけ取得 → 常に 200 を返していた (関連 Issue #573)
4. **`mesh-zone-get` の secret key を環境変数化** — 旧実装はハードコード
5. **stg 環境を新設** — 旧実装は prod のみ

## Cutover (prod) 手順 — 後続作業

1. stg で動作確認 + frontend を `stg.api.smalruby.app` で結合テスト
2. `MESH_ZONE_SECRET_KEY` を旧実装と同値で `.env.prod` に設定 (mesh ドメインが既存ユーザーで変わらないようにする)
3. SAM スタック (`smalruby-infra-prod`) のドメインマッピング解除 (`api.smalruby.app`)
4. `cdk deploy --context stage=prod` で新スタックに `api.smalruby.app` を紐付け
5. 動作確認後、SAM スタック (`smalruby-infra-prod`) を CloudFormation から削除
6. smalruby/smalruby-infra リポジトリの該当ファイルを deprecate

## Source

実装場所: `lambda/*.ts`, `lib/smalruby-api-stack.ts`, `bin/smalruby-api.ts`
ユニットテスト: `lambda/tests/*.test.ts`
1 change: 1 addition & 0 deletions .claude/rules/scratch-gui/smalruby-markers.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ upstream ファイルに追加した Smalruby 固有コードのマーカー一
| `src/components/menu-bar/menu-bar.jsx` | classroom button | クラスルームボタンの import、レンダリング、Redux 接続 |
| `src/components/menu-bar/settings-menu.jsx` | classroom management menu | クラス管理メニューアイテムの import、レンダリング、Redux 接続 |
| `webpack.config.js` | classroom API | CLASSROOM_API_ENDPOINT 環境変数注入 |
| `webpack.config.js` | scratch api proxy endpoint | SCRATCH_API_PROXY_ENDPOINT 環境変数注入 |
| `src/lib/blocks.js` | gesture recovery import | ジェスチャー復旧モジュールの import |
| `src/lib/blocks.js` | gesture recovery | ジェスチャー復旧ハンドラーのインストール |
| `src/playground/render-gui.jsx` | URL params for Playwright | URL パラメーター import |
Expand Down
2 changes: 2 additions & 0 deletions .claude/rules/scratch-gui/smalruby-prettier-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ upstream (Scratch) ファイルは対象外。
- `src/lib/rubytee-api.js`
- `src/lib/rubytee-context.js`
- `src/lib/url-loader-hoc.jsx`
- `src/lib/url-loader.js`
- `src/lib/url-params.js`
- `src/lib/url-parser.js`
- `src/lib/version-checker.js`
Expand Down Expand Up @@ -203,6 +204,7 @@ upstream (Scratch) ファイルは対象外。
- `test/unit/lib/furigana-annotator-perf.test.js`
- `test/unit/lib/furigana-annotator.test.js`
- `test/unit/lib/google-drive-api.test.js`
- `test/unit/lib/url-loader.test.js`
- `test/unit/lib/insert-class.test.js`
- `test/unit/lib/join-code-history.test.js`
- `test/unit/lib/layout-constants.test.js`
Expand Down
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ MESH_PERIODIC_DATA_SYNC_INTERVAL_MS=15000
# Example: https://xxxxx.execute-api.ap-northeast-1.amazonaws.com
CLASSROOM_API_ENDPOINT=

# Scratch API Proxy Configuration (cors-proxy / mesh-domain / scratch-api-proxy/*)
# See infra/smalruby-api/ for deployment details
# Default: https://api.smalruby.app (prod). For local dev override with stg:
# SCRATCH_API_PROXY_ENDPOINT=https://stg.api.smalruby.app
SCRATCH_API_PROXY_ENDPOINT=

# Dev bypass token for automated testing (stg/local only)
# Allows skipping Google auth with ?devlogin=1 URL parameter
# Must match DEV_BYPASS_TOKEN in infra/smalruby-classroom/.env.stg
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ jobs:
MESH_PERIODIC_DATA_SYNC_INTERVAL_MS: ${{ vars.MESH_PERIODIC_DATA_SYNC_INTERVAL_MS }}
RUBYTEE_RELAY_ENDPOINT: ${{ secrets.RUBYTEE_RELAY_ENDPOINT }}
CLASSROOM_API_ENDPOINT: ${{ secrets.CLASSROOM_API_ENDPOINT }}
SCRATCH_API_PROXY_ENDPOINT: ${{ vars.SCRATCH_API_PROXY_ENDPOINT }}
MICROSOFT_CLIENT_ID: ${{ secrets.MICROSOFT_CLIENT_ID }}
MAX_USER_MESSAGE_LENGTH: ${{ vars.MAX_USER_MESSAGE_LENGTH }}
MIN_USER_MESSAGE_LENGTH: ${{ vars.MIN_USER_MESSAGE_LENGTH }}
Expand Down Expand Up @@ -310,6 +311,7 @@ jobs:
MESH_PERIODIC_DATA_SYNC_INTERVAL_MS: ${{ vars.MESH_PERIODIC_DATA_SYNC_INTERVAL_MS }}
RUBYTEE_RELAY_ENDPOINT: ${{ secrets.RUBYTEE_RELAY_ENDPOINT }}
CLASSROOM_API_ENDPOINT: ${{ secrets.CLASSROOM_API_ENDPOINT }}
SCRATCH_API_PROXY_ENDPOINT: ${{ vars.SCRATCH_API_PROXY_ENDPOINT }}
MICROSOFT_CLIENT_ID: ${{ secrets.MICROSOFT_CLIENT_ID }}
MAX_USER_MESSAGE_LENGTH: ${{ vars.MAX_USER_MESSAGE_LENGTH }}
MIN_USER_MESSAGE_LENGTH: ${{ vars.MIN_USER_MESSAGE_LENGTH }}
Expand Down Expand Up @@ -351,6 +353,7 @@ jobs:
MESH_PERIODIC_DATA_SYNC_INTERVAL_MS: ${{ vars.MESH_PERIODIC_DATA_SYNC_INTERVAL_MS }}
RUBYTEE_RELAY_ENDPOINT: ${{ secrets.RUBYTEE_RELAY_ENDPOINT }}
CLASSROOM_API_ENDPOINT: ${{ secrets.CLASSROOM_API_ENDPOINT }}
SCRATCH_API_PROXY_ENDPOINT: ${{ vars.SCRATCH_API_PROXY_ENDPOINT }}
MICROSOFT_CLIENT_ID: ${{ secrets.MICROSOFT_CLIENT_ID }}
MAX_USER_MESSAGE_LENGTH: ${{ vars.MAX_USER_MESSAGE_LENGTH }}
MIN_USER_MESSAGE_LENGTH: ${{ vars.MIN_USER_MESSAGE_LENGTH }}
Expand Down
63 changes: 63 additions & 0 deletions .github/workflows/ci-infra.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,66 @@ jobs:
- name: Run npm audit
run: npm audit --audit-level=moderate
continue-on-error: true

# ---------------------------------------------------------------------------
# smalruby-api
# ---------------------------------------------------------------------------
smalruby-api-unit-tests:
name: "[smalruby-api] Unit Tests"
runs-on: ubuntu-latest
defaults:
run:
working-directory: infra/smalruby-api
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
with:
node-version-file: infra/smalruby-api/.node-version
cache: npm
cache-dependency-path: infra/smalruby-api/package-lock.json
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm test

smalruby-api-cdk-build:
name: "[smalruby-api] CDK Build & Synth"
runs-on: ubuntu-latest
defaults:
run:
working-directory: infra/smalruby-api
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
with:
node-version-file: infra/smalruby-api/.node-version
cache: npm
cache-dependency-path: infra/smalruby-api/package-lock.json
- name: Install dependencies
run: npm ci
- name: Build TypeScript
run: npm run build
- name: Synthesize CDK stack
env:
MESH_ZONE_SECRET_KEY: 'dummy-key-for-synth'
SMALRUBY_API_CUSTOM_DOMAIN: 'false'
run: npx cdk synth --context stage=stg

smalruby-api-security:
name: "[smalruby-api] Security Audit"
runs-on: ubuntu-latest
defaults:
run:
working-directory: infra/smalruby-api
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
with:
node-version-file: infra/smalruby-api/.node-version
cache: npm
cache-dependency-path: infra/smalruby-api/package-lock.json
- name: Install dependencies
run: npm ci
- name: Run npm audit
run: npm audit --audit-level=moderate
continue-on-error: true
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The `infra/` directory contains AWS CDK infrastructure projects (independent fro
- **`infra/smalruby-mesh-v2`**: AWS CDK project for the Mesh v2 networking service (AppSync + DynamoDB)
- **`infra/smalruby-rubytee-relay`**: AWS CDK project for the Rubytee AI relay service (Anthropic Claude API + DynamoDB)
- **`infra/smalruby-classroom`**: AWS CDK project for the Classroom service (API Gateway + Lambda + DynamoDB)
- **`infra/smalruby-api`**: AWS CDK project for general API endpoints (HTTP API v2 + Lambda): cors-proxy, mesh-domain, scratch-api-proxy

The `ruby/` directory contains the smalruby3 Ruby gem and its native dependencies (git submodules):

Expand Down
3 changes: 3 additions & 0 deletions bin/sync-worktree-env
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ declare -a FILES=(
"infra/smalruby-classroom/.env.stg"
"infra/smalruby-classroom/.env.stg2"
"infra/smalruby-classroom/.env.production"
"infra/smalruby-api/.env.stg"
"infra/smalruby-api/.env.stg2"
"infra/smalruby-api/.env.prod"
)

copied=0
Expand Down
32 changes: 32 additions & 0 deletions infra/smalruby-api/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Smalruby API Environment Variables
#
# Environment files are managed per-stage and gitignored.
# Copy this template per stage and create a symlink:
#
# cp .env.example .env.stg # then fill in real values
# cp .env.example .env.prod # then fill in real values
# ln -s .env.stg .env # switch active environment

# AWS CDK Stage (stg / stg2 / prod)
STAGE=stg

# AWS Configuration (optional: CDK_DEFAULT_ACCOUNT/CDK_DEFAULT_REGION from AWS CLI take priority)
# AWS_ACCOUNT_ID=your-account-id
# AWS_REGION=ap-northeast-1

# CORS (comma-separated origins)
CORS_ALLOWED_ORIGINS=https://smalruby.app,https://smalruby.jp,http://localhost:8601

# Custom domain configuration
# - Default: stg => stg.api.smalruby.app, prod => api.smalruby.app
# - To disable custom domain (use the *.execute-api endpoint), set SMALRUBY_API_CUSTOM_DOMAIN=false
# - To override, set explicit hostname (e.g. SMALRUBY_API_CUSTOM_DOMAIN=stg2.api.smalruby.app)
ROUTE53_PARENT_ZONE_NAME=api.smalruby.app
# SMALRUBY_API_CUSTOM_DOMAIN=

# Endpoint used by integration tests (npm run test:integration)
# SMALRUBY_API_ENDPOINT=https://stg.api.smalruby.app

# Mesh-zone-get secret key (used to derive Mesh group identity from source IP)
# IMPORTANT: keep this stable across stages once set; changing rotates all derived domains
MESH_ZONE_SECRET_KEY=replace-me-with-a-long-random-string
14 changes: 14 additions & 0 deletions infra/smalruby-api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
node_modules/
dist/
cdk.out/
cdk.out.*

# Environment variables (all are gitignored; use symlink: ln -s .env.stg .env)
.env
.env.prod
.env.stg
.env.stg2

*.js.map
*.d.ts
!jest.config.js
1 change: 1 addition & 0 deletions infra/smalruby-api/.node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24.8.0
79 changes: 79 additions & 0 deletions infra/smalruby-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# smalruby-api

CDK project for Smalruby's API Gateway endpoints (HTTP API v2 + Lambda).

旧 SAM 実装 (`smalruby/smalruby-infra`) の置き換え。4 つのエンドポイントを TypeScript Lambda + HTTP API v2 (built-in CORS) に移行する。

## Endpoints

| Path | Method | Lambda | 説明 |
|------|--------|--------|------|
| `/cors-proxy` | GET | `smalruby-cors-proxy` | 任意 URL のフェッチ + Google Drive URL 変換 + バイナリ Base64 化 |
| `/mesh-domain` | GET | `smalruby-mesh-zone-get` | source IP から Mesh ドメイン (CRC32) を生成 |
| `/scratch-api-proxy/projects/{projectId}` | GET | `smalruby-scratch-api-projects` | Scratch API のプロジェクト情報取得プロキシ (status pass-through) |
| `/scratch-api-proxy/translate` | GET | `smalruby-scratch-api-translate` | Scratch translate サービスプロキシ |

OPTIONS は API Gateway HTTP API v2 の built-in CORS で処理 (旧 `cors-for-smalruby` Lambda は不要)。

## Custom Domain

| Stage | Domain |
|-------|--------|
| stg | `stg.api.smalruby.app` |
| prod | `api.smalruby.app` |

prod ドメインへのカットオーバーは旧 SAM スタック (`smalruby-infra-prod`) のドメイン解放後に実施する。

## Commands

```bash
# Install dependencies
docker compose run --rm -w /app/infra/smalruby-api infra npm install

# Synthesize CloudFormation template
docker compose run --rm -w /app/infra/smalruby-api infra npx cdk synth

# Show diff against deployed stack
docker compose run --rm -w /app/infra/smalruby-api infra npx cdk diff

# Deploy (uses STAGE from .env symlink)
docker compose run --rm -w /app/infra/smalruby-api infra npx cdk deploy

# Run unit tests
docker compose run --rm -w /app/infra/smalruby-api infra npm test
```

## Stage Switching

```bash
cd infra/smalruby-api
rm .env && ln -s .env.stg .env # → stg
rm .env && ln -s .env.prod .env # → prod
```

## Environment Variables

| Variable | Description |
|----------|-------------|
| `STAGE` | Deployment stage (`stg`, `stg2`, `prod`) |
| `CORS_ALLOWED_ORIGINS` | Comma-separated allowed origins |
| `ROUTE53_PARENT_ZONE_NAME` | Parent zone for custom domain (default: `api.smalruby.app`) |
| `SMALRUBY_API_CUSTOM_DOMAIN` | Override custom domain. Set `false` to disable |
| `MESH_ZONE_SECRET_KEY` | Secret key used to derive Mesh group identity from source IP |

## Migration Notes

旧 SAM スタックとの主な差分:

1. **REST API v1 → HTTP API v2** へ変更。built-in CORS で `cors-for-smalruby` Lambda 不要に
2. **Ruby 3.3 → Node.js 20.x (TypeScript)** で他 infra プロジェクトと言語を統一
3. **`scratch-api-proxy/projects/{projectId}` のステータスコード透過** バグ修正 — 旧実装は `Net::HTTP.get` でボディだけ取得 → 常に 200 を返していた
4. **`mesh-zone-get` の secret key を環境変数化** — 旧実装はハードコード
5. **stg 環境を新設** — 旧実装は prod のみ

## Cutover (prod) — 後続作業

1. stg で動作確認 + frontend を `stg.api.smalruby.app` で結合テスト
2. SAM スタック (`smalruby-infra-prod`) のドメイン マッピング解除
3. `.env.prod` を作成 → `cdk deploy` で `api.smalruby.app` を新スタックに紐付け
4. SAM スタック (`smalruby-infra-prod`) を削除
Loading
Loading