feat(admin): 運営者向け管理 SPA — みんなの課題/クラス管理/バグ報告閲覧 (EPIC #1073)#1094
Merged
Conversation
Admin SPA backend (EPIC #1073, decisions A-F in spike #1074): - new CDK project infra/smalruby-admin: HTTP API v2 + Lambda (Node 22) + SmalrubyAdmins allowlist table (PK email, RETAIN) + custom domain admin.api.smalruby.app / stg.admin.api.smalruby.app - deny-by-default authorization: the allowlist is populated only by a human in the AWS console (F4); the first successful login pins the Google sub onto the row (conditional write) and a later token with the same email but a different sub is rejected (email-reuse defense) - admin-dedicated Google client ID (decision B) with an aud check; prod refuses to deploy without it. DEV_BYPASS_TOKEN is stg-only (prod guard), and the bypass identity still needs allowlisting - audit() structured logs on every sensitive action; prod log retention is ONE_YEAR (deliberate deviation from the fleet's ONE_MONTH — these are audit records) - error conventions follow bug-report (authn 401 / authz 403) - S1 surface: GET /admin/me (authorization probe for the SPA) - docs/admin/operations.md (admin registration runbook) and .claude/rules/infra/smalruby-admin.md (invariants) EPIC #1073 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(admin-infra): infra/smalruby-admin — 認可基盤 (S1 #1081)
Operator admin SPA (EPIC #1073 S2): - new npm workspace packages/admin: minimal React SPA on the same toolchain (webpack 5 / babel / eslint-config-scratch / jest) but hand-rolled config — no scratch-vm, no PWA, hash-friendly static bundle with publicPath /admin/, dev server on 8602 - auth flow: Google Identity Services button (admin-dedicated client ID, decision B) or ?devlogin bypass (stg) -> /admin/me authorization probe -> dashboard skeleton; 403 renders an explicit "no access" screen and drops the token - the id_token lives in module memory only (never localStorage, N1) - workspace registered in the root package.json explicit list; deps pinned to the versions scratch-gui already carries - plain Japanese strings (operator-only tool — react-intl deliberately not used, unlike the editor) EPIC #1073 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- the root build already fans out to packages/admin via workspaces; inject ADMIN_API_ENDPOINT / ADMIN_GOOGLE_CLIENT_ID (repo Variables) - publish packages/admin/build to the smalruby/smalruby.app Pages repo under destination_dir admin with keep_files, ordered AFTER the main deploy (which replaces the branch content on every release) EPIC #1073 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(admin): packages/admin スキャフォールド + Google ログイン + /admin/ 配信 (S2 #1082)
Admin SPA + API, first management domain (EPIC #1073 decision E): - admin Lambda: report queue (grouped, most-reported first, joined with the reported items; reporterSub never exposed), fleet-wide listing with status/q filters, detail with presigned page images, and the audited unpublish/republish status flip - cross-service access imports the classroom stack's shared tables / bucket by the stage naming convention (grantIndexPermissions; the classroom stack itself is untouched — N2) - SPA: 通報キュー / すべての投稿 tabs, detail preview with the CC BY credit, and a two-step confirm for the status flip - app dashboard now hosts the view (S4/S5 domains slot in next) EPIC #1073 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(admin): みんなの課題管理(通報キュー・unpublish/republish)(S3 #1083)
admin Lambda に classroom ドメインを追加:
- GET /admin/classrooms (q= 参加コード完全一致 / クラス名・課題名部分一致、
quota 行は除外)
- GET /admin/classrooms/{id} (参加・提出カウント付き詳細)
- PATCH /admin/classrooms/{id} (archive/active の切替、audit 付き)
- GET /admin/classrooms/restore-candidates?q= (ddb-archive スナップショット検索)
- GET /admin/classrooms/{id}/restore-plan (dry-run: 件数・組復元・欠損ファイル)
- POST /admin/classrooms/{id}/restore (組→クラス→メンバー→提出の順で
fresh TTL 再水和、classroom put は attribute_not_exists 条件、audit 付き)
restore-plan.ts は classroom の restore-lib.ts から意図的に複製した純関数
(独立 infra プロジェクトのため。スナップショット形式変更時は両方を同期)。
スタックは classroom テーブル 4 つ + 提出バケットを名前規約で import して
grant (classroom スタックには変更なし・N2)。
SPA は ClassroomsView (クラス検索 / 期限切れ復元の 2 タブ、
2 段階確認つきアーカイブ切替・復元実行) とセクションナビを追加。
テスト: restore-plan 純関数 5、handler 7 (認可済み前提の一覧/詳細/状態/
候補検索/plan/実行/失敗系)、SPA 5 + nav 1。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(admin): クラス・課題管理 + 期限切れクラスの復元 UI (#1084)
既存のバグ報告機能は不変。Admin に追加するのは閲覧の UI/UX のみで、 状態変更・返信は従来の運用手段を継続する (EPIC #1073 決定 D)。 - packages/admin: BugReportsView (一覧 + 状態フィルタ → 詳細 + presigned 添付 DL・スクショ表示)。書き込み系 API は一切呼ばない。bug-report-api.js は GET 専用クライアント (admin-api のトークンをモジュールメモリで共有) - bug-report Lambda: 決定 F — verifyGoogleIdToken の audience に ADMIN_GOOGLE_CLIENT_ID を追加許可 (未設定なら従来どおり単一 audience)。 BugReportAdmins の email レジストリゲートは不変 - stack: ADMIN_GOOGLE_CLIENT_ID env の受け渡しのみ (デフォルト空) テスト: audience 分岐 2 (bug-report) + SPA 4 (一覧/フィルタ/read-only 詳細/エラー)。stg .env は CORS +localhost:8602 と ADMIN_GOOGLE_CLIENT_ID 行を追加済み (gitignored、デプロイ時反映)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(admin): バグ報告の閲覧ビュー — 既存 API・read のみ (#1085)
- docs/admin/README.md: 🆕 バッジ・全体像 (3 ドメイン)・認可モデル要点・
主要ファイル・設定・ローカル開発/E2E 手順・スクリーンショット一覧
(運用 runbook は既存 operations.md)
- tools/playwright-verify/verify-admin.mjs: stg E2E — /admin/me プリフライト
(403 = allowlist 未登録を明示) → SPA dev server 自前起動 → ログインゲート →
devlogin → 3 セクション通し (バグ報告詳細に書き込み UI が無いことを含む)。
README 一覧にも追記
- fix: バグ報告詳細の appContext は実データではオブジェクト
({rubyVersion, url}) で React child にできず詳細画面がクラッシュしていた
(E2E で発見)。JSON テキスト化して pre 表示、テストもオブジェクトでピン留め
stg 実機検証済み: E2E PASS + 実スナップショットで復元プラン→復元実行
(fresh 90 日 TTL 確認)→アーカイブ戻しまで通し。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
docs(admin): ドキュメント・E2E スクリプト・appContext 表示修正 (#1086)
1 task
|
🚀 Preview deployed: https://smalruby.jp/smalruby3-editor/feature/admin/ |
root には別ワークスペース由来の webpack-cli@4 が hoist されており、 `webpack serve` が root の webpack bin 経由で v4 CLI に解決されると webpack-dev-server v5 と互換がなく起動に失敗する (options unknown property _assetEmittingPreviousFiles)。 webpack-cli の bin はバージョン差により必ず packages/admin ローカルに 存在するため、start/build を webpack-cli 直接呼び出しに変更して hoisting の揺れに依存しないようにする。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(admin): webpack-cli をローカル bin で決定的に解決
本番の GitHub Pages はディレクトリへの末尾スラッシュリダイレクトを行うが、 webpack-dev-server は行わないため /admin が Cannot GET になっていた。 historyApiFallback の index を publicPath 配下 (/admin/) に向ける。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(admin): dev server で /admin もSPAにフォールバック
- stg で確認された「画像リンク切れ」は、テスト作成の報告がメタデータ (screenshotCount 等) だけ登録して実ファイルを S3 にアップロードして いないことが原因 (presigned URL は有効だがオブジェクトが 404)。 本番でもアップロード途中失敗で起こり得るため、サムネイル・ スクリーンショットの img に onError で非表示にする防御を追加 - packages/admin/build/ が S2/S3 のコミットに誤って混入していたため git 管理から外し、packages/admin/.gitignore を追加 (build 成果物は コミットしない規約) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ages fix(admin): アップロード未完了の添付画像を非表示 + build/ を除外
スターターはモデレーション対象のコンテンツなのに、admin の詳細は hasStarter フラグだけで実体を確認できなかった。 - admin Lambda: GET 詳細で content.starterKey を presign して starterUrl を返す (無ければ null) - SPA: 詳細に「スタータープロジェクト (.sb3) をダウンロード」リンク、 無い場合は「スタータープロジェクトなし」を明示 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(admin): みんなの課題詳細にスタータープロジェクトの DL を表示
Google ID トークンは約 1 時間で失効し、これまでは各ビューに 「Invalid ID token」が生のまま出るだけで不親切だった。 - API クライアント (admin / bug-report) は 401 で smalruby-admin:unauthorized をブロードキャスト - App はログイン済みのときだけ購読し、全画面の「セッションの有効期限が 切れました」オーバーレイ + 再読み込みボタンを表示 (ログイン時の 401 は従来どおり通常エラー) - 表示中セクションは URL ハッシュ (#/classrooms 等) に保持。 再読み込み → 再ログイン後に元のセクションへ復帰する (localStorage は使わない方針) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(admin): セッション切れの再読み込みプロンプト + セクションのURLハッシュ保持
仕様追加 (決定 D の拡張・2026-07-19): 閲覧だけでなく、この画面から
状態変更と進捗コメント (developerReply) を行えるようにする。
- バックエンド変更なし: 既存 bug-report admin API の
PATCH /admin/bug-reports/{id} をそのまま使う (status /
developerReply。報告者側の再表示・終端ステータスの自動削除 TTL は
サーバー側の既存挙動)
- 詳細に「対応」セクション: 状態 select + コメント textarea (2000 字)。
保存は変更があるときだけ有効で、2 段階確認 (終端ステータス選択時は
自動削除の警告文言)。変更フィールドのみ送信 (空更新は API が拒否)
- 保存後は一覧を再取得。ビュー冒頭の「閲覧専用」注記を差し替え
- E2E は対応セクションの存在と save の disabled 初期状態を確認
(stg の報告を汚さないため保存はしない)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(admin): バグ報告の状態変更と進捗コメントを可能にする
バグ報告の受付・改修・返信は /bug-report スキルで進める運用のため、 管理画面から Claude へスムーズに引き継げるようにする。 - bug-report-prompts.js: スキル仕様 (show/issue/reply <reportId> [stg|prod]、stg のときだけステージ引数、やさしい返信、メール非掲載) に沿って状態別のプロンプト案を生成する純関数 - open: 受付→再現確認→Issue 化→in_progress+受付返信 / 内容確認のみ - in_progress: 改修続行 / 修正済み→resolved+解決返信 - resolved・wont_fix: 再開 (open に戻すと TTL 解除の注意つき) - 詳細に「Claude に依頼」セクション: プロンプトのプレビュー + コピーボタン (navigator.clipboard、押すと「コピーしました ✓」) - 取り違え防止に reportId フルとプロジェクト名/日付/説明冒頭の コンテキスト行をプロンプトへ埋め込む Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(admin): バグ報告詳細に状態別のClaude連携プロンプト(ワンクリックコピー)
セキュリティレビュー (ソース公開前提の脅威モデル) の対応。 - prod: API Gateway に Google JWT authorizer (issuer https://accounts.google.com / audience = ADMIN_GOOGLE_CLIENT_ID) を全ルートに付与。正当な署名トークンでない要求を Lambda 到達前に 401 で 弾き、認証なしの DoS フラッドが Lambda 起動・Lambda ログ ingestion 課金を 発生させないようにする。Lambda 側の fail-closed 認可 (許可リスト + sub 固定) は多層防御として維持 - stg は dev bypass (非 JWT) を使う E2E のため authorizer なし (Lambda の 同じ fail-closed 認可のみ) - スロットルを単一運用者向けに rate 5 / burst 10 へ強化 (攻撃者が積める API Gateway リクエスト課金の上限を圧縮) - docs/rules に脅威モデルとコスト方針を明文化 (X-Ray 不使用・アクセスlog 無効・dev-admin@example.com を prod に登録しない 等) X-Ray は全 Lambda で PassThrough (未使用) を確認済み。prod テンプレートで authorizer が全 11 ルートに付与され throttle=5 になることを synth で確認。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(admin): 公開ソース前提の DoS・コスト防御を強化 (prod JWT authorizer + throttle)
CI の integration-test-{vm,gui} は root で素の npm run build:dev を実行し、
これは全ワークスペースへ fan-out する。packages/admin に build:dev が
無かったため Missing script で 4 ジョブが失敗していた。
webpack の development モードビルドとして追加 (build と同一 config、
devtool が source map 付きになるだけ)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(admin): build:dev スクリプトを追加 (CI の全ワークスペース fan-out 対応)
sugiymki
pushed a commit
to gfd-dennou-club/smt-editor
that referenced
this pull request
Jul 21, 2026
The admin SPA deploy to smalruby.app/admin/ (EPIC smalruby#1073) runs a second peaceiris/actions-gh-pages in the same job. That action binds a fixed SSH_AUTH_SOCK (/tmp/ssh-auth.sock) when using deploy_key, and the main smalruby.app deploy above leaves its ssh-agent running, so the admin deploy failed with 'unix_listener: cannot bind to path /tmp/ssh-auth.sock: Address already in use'. Free the socket between the two deploys. Surfaced on the first develop run that included the admin deploy step (PR smalruby#1094 merge).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
EPIC #1073: 運営者向け管理 SPA(Smalruby Admin) の最終 PR です。
smalruby.app/admin/で提供する独立 SPA + 専用バックエンドで、以下の 3 ドメインを 1 画面に集約します。アーキテクチャ / セキュリティ(Decision Log A〜F 準拠)
ADMIN_GOOGLE_CLIENT_IDを追加 audience として受理(F — email レジストリゲートは不変)SmalrubyAdmins(RETAIN)。登録は AWS コンソール手動のみ(F4・アプリ内に管理者管理 UI なし)。初回ログインで sub 固定、email 再利用は 403audit()(prod ログ保持 1 年)。認証 401 / 認可 403packages/adminをビルドし、smalruby.app デプロイ後にdestination_dir: admin+keep_filesで/admin/へ配信Sub-issues(トピックブランチへマージ済み)
テスト / 検証
tools/playwright-verify/verify-admin.mjs)PASS: ログインゲート → devlogin → 3 セクション通し。実スナップショットで復元プラン → 復元実行(fresh 90 日 TTL 確認)→ アーカイブ戻し、バグ報告詳細が read-only であることも確認AdminStack-stg(https://stg.admin.api.smalruby.app)+ bug-report stg(aud / CORS)残タスク(prod 展開・人間のみ)
https://smalruby.app+http://localhost:8602)→infra/smalruby-admin/.env.prod・infra/smalruby-bug-report/.env.prodのADMIN_GOOGLE_CLIENT_IDADMIN_API_ENDPOINT=https://admin.api.smalruby.app/ADMIN_GOOGLE_CLIENT_IDを登録(CI ビルド埋め込み)AdminStack(prod)+ bug-report(prod)の cdk deploySmalrubyAdminsに管理者 email を AWS コンソールで登録(+ バグ報告閲覧用にBugReportAdminsにも同 email)Related Issues
Closes #1081, #1082, #1083, #1084, #1085, #1086 (EPIC #1073)
🤖 Generated with Claude Code