Skip to content

feat(admin): 運営者向け管理 SPA — みんなの課題/クラス管理/バグ報告閲覧 (EPIC #1073)#1094

Merged
smalruby3-editor-bot[bot] merged 31 commits into
developfrom
feature/admin
Jul 19, 2026
Merged

feat(admin): 運営者向け管理 SPA — みんなの課題/クラス管理/バグ報告閲覧 (EPIC #1073)#1094
smalruby3-editor-bot[bot] merged 31 commits into
developfrom
feature/admin

Conversation

@smalruby3-editor-bot

@smalruby3-editor-bot smalruby3-editor-bot Bot commented Jul 18, 2026

Copy link
Copy Markdown

Summary

EPIC #1073: 運営者向け管理 SPA(Smalruby Admin) の最終 PR です。smalruby.app/admin/ で提供する独立 SPA + 専用バックエンドで、以下の 3 ドメインを 1 画面に集約します。

  1. みんなの課題(feat(admin): みんなの課題管理(通報キュー・unpublish/republish)(S3) #1083): 通報キュー / 全投稿一覧 / 詳細 / 非公開⇄再公開(2 段階確認・audit)
  2. クラス・課題(feat(admin): クラス・課題管理 + 期限切れ復元 UI (S4) #1084): 全クラス検索 / 詳細(参加・提出カウント)/ アーカイブ切替 / 期限切れクラスの復元(ddb-archive スナップショット検索 → dry-run → 実行。EPIC feat(classroom): 誤アーカイブ/期限切れ問い合わせ対応 — アーカイブ復元・保存期限対策の総合対応 (EPIC) #1049 の CLI の UI 後継)
  3. バグ報告(feat(admin): バグ報告の閲覧ビュー(既存 API・read のみ)(S5) #1085 + feat(admin): バグ報告の状態変更と進捗コメントを可能にする #1100): 一覧・詳細・添付 presigned DL に加え、状態変更と進捗コメント(既存 bug-report admin API の PATCH をそのまま使用・バックエンド不変。決定 D の拡張は EPIC コメント参照)

アーキテクチャ / セキュリティ(Decision Log A〜F 準拠)

  • A ハイブリッド API: classroom / shared は新 admin Lambda(名前規約 import・classroom スタック不変)、バグ報告は既存 API を SPA から直接(read のみ)
  • B 専用 Google Client ID: エディタと共用しない。bug-report Lambda は ADMIN_GOOGLE_CLIENT_ID を追加 audience として受理(F — email レジストリゲートは不変)
  • C email→sub 固定: deny-by-default 許可リスト SmalrubyAdmins(RETAIN)。登録は AWS コンソール手動のみ(F4・アプリ内に管理者管理 UI なし)。初回ログインで sub 固定、email 再利用は 403
  • トークンはモジュールメモリのみ(localStorage 不使用)。全変更操作に audit()(prod ログ保持 1 年)。認証 401 / 認可 403
  • CI: root build とは独立に packages/admin をビルドし、smalruby.app デプロイdestination_dir: admin + keep_files/admin/ へ配信

Sub-issues(トピックブランチへマージ済み)

PR 内容
#1087 S1 infra/smalruby-admin 認可基盤(allowlist / sub 固定 / audit / CORS)
#1088 S2 packages/admin スキャフォールド + Google Sign-In + CI 配信
#1089 S3 みんなの課題 moderation
#1091 S4 クラス・課題管理 + 期限切れ復元 UI
#1092 S5 バグ報告閲覧(read のみ)+ bug-report aud 最小変更
#1093 S6 docs / E2E / appContext 表示修正
#1095-#1097 dev server の webpack-cli 解決 / /admin フォールバック / 壊れ添付画像の非表示 + build/ 除外
#1098 みんなの課題詳細にスターター DL 表示
#1099 セッション切れの再読み込みプロンプト + セクションの URL ハッシュ保持
#1100 バグ報告の状態変更・進捗コメント(決定 D 拡張)

テスト / 検証

  • infra/smalruby-admin: jest 28(認可マトリクス・復元プラン純関数・全ハンドラ)+ tsc + synth
  • packages/admin: jest 22 + eslint(zero warnings)
  • bug-report: 53(audience 分岐 2 追加)
  • stg 実機 E2E(tools/playwright-verify/verify-admin.mjs)PASS: ログインゲート → devlogin → 3 セクション通し。実スナップショットで復元プラン → 復元実行(fresh 90 日 TTL 確認)→ アーカイブ戻し、バグ報告詳細が read-only であることも確認
  • stg デプロイ済み: AdminStack-stg(https://stg.admin.api.smalruby.app)+ bug-report stg(aud / CORS)

残タスク(prod 展開・人間のみ)

  • GCP で admin 専用 OAuth Client ID 作成(生成元: https://smalruby.app + http://localhost:8602)→ infra/smalruby-admin/.env.prodinfra/smalruby-bug-report/.env.prodADMIN_GOOGLE_CLIENT_ID
  • repo Variables に ADMIN_API_ENDPOINT=https://admin.api.smalruby.app / ADMIN_GOOGLE_CLIENT_ID を登録(CI ビルド埋め込み)
  • AdminStack(prod)+ bug-report(prod)の cdk deploy
  • prod SmalrubyAdmins に管理者 email を AWS コンソールで登録(+ バグ報告閲覧用に BugReportAdmins にも同 email)

Related Issues

Closes #1081, #1082, #1083, #1084, #1085, #1086 (EPIC #1073)

🤖 Generated with Claude Code

smalruby3-editor-bot Bot and others added 13 commits July 18, 2026 03:37
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)
@github-actions

Copy link
Copy Markdown

🚀 Preview deployed: https://smalruby.jp/smalruby3-editor/feature/admin/

smalruby3-editor-bot Bot and others added 15 commits July 19, 2026 01:33
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>
smalruby3-editor-bot Bot and others added 2 commits July 19, 2026 05:15
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 対応)
@smalruby3-editor-bot
smalruby3-editor-bot Bot merged commit 70f0808 into develop Jul 19, 2026
21 checks passed
@smalruby3-editor-bot
smalruby3-editor-bot Bot deleted the feature/admin branch July 19, 2026 06:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(admin-infra): infra/smalruby-admin — 認可基盤(Admins テーブル・監査ログ)(S1)

0 participants