kowai-operation
WIP(現在進行中)
Work In Progress
このプロジェクトで現在進行中の作業と、過去のスナップショットを記録する。
現在の状況
Android クラッシュ修正完了(2026-06-09)
5/24 の Lightsail 移管以降に発生していた Android クラッシュ(Enter ボタン押下・詳細ページ遷移)を緊急修正。エミュレーターでの検証で OK 確認。
- 原因 1: nginx の HTTPS 強制が、旧アプリの
http://kh.thomsonsapp.comハードコードと衝突 →/khApi/*のみ HTTP パススルーに戻して救済 - 原因 2:
is_html/is_infoを str に変えていたのが Android Realm モデルBoolean定義と不整合 → bool に戻し、S3 上の既存 4189 件も bool 版で全件再 put
いま見守り中:
- Play Console / レビュー欄でクラッシュ報告が収まるか(5/29 / 5/31 のレビュー以降)
- OneSignal 予約配信が予定時刻に届くか
- cron / S3 同期が継続するか
次にやること(任意・優先度低め):
- Route53 の子 hosted zone
kh.thomsonsapp.comの削除(親に一本化済み・月 $0.50 節約) - 旧 Cake (fn-* EC2) の KhApi コントローラ・関連コード・vhost のクリーンアップ
- 次の iOS / Android リリースで baseURL を
https://に統一 → 旧 80 番/khApi/パススルーを撤去 - 次の Android リリースで
MainActivity.kt:232のgetBoolean(2)をgetInt(2) != 0に変更(サーバー側型変更への耐性向上)
ペンディング(中期):
sendPushNotification配信実績(errored 件数)の検知・アラート- OneSignal SDK の更新検討
- kowai-android: Realm 10.19.0 からの脱却(MongoDB が EOL 化)
過去のWIPアーカイブ
(新しい「現在の状況」を書く前に、古いものをここに追記でアーカイブする。新しいものが上)
2026-06-09 18:00 時点のスナップショット
kowai サーバー機能の独立 Lightsail 移管が完了(2026-05-24)
旧 Cake (fn-* EC2、footballnext と同居) で動いていた /khApi/* と /fearstoryPortal/* 一式を、新 Lightsail インスタンス kowai-server に独立移管した。Codex レビュー(2 ラウンド)反映、DNS 切替、HTTPS 化まですべて完了済み。
- 本番URL: https://kh.thomsonsapp.com
- Lightsail: 54.249.174.108 / Ubuntu 24.04 / Python 3.12 / 1GB
- DB は既存 RDS (
kowaischema) を共用、スキーマ無変更 - 旧 fn-* EC2 への kowai 関連トラフィックは無し(footballnext は継続使用)
- 詳細:
.devnotes/lightsail-migration-report.mdと.gods-talk/lightsail-migration-review-{001,002}-{issue,feedback}.md
次にやること(任意・優先度低め):
- Route53 の子 hosted zone
kh.thomsonsapp.comの削除(親に一本化済み・月 $0.50 節約) - 旧 Cake (fn-* EC2) の KhApi コントローラ・関連コード・
kh.thomsonsapp.comvhost のクリーンアップ - 数日〜1週間の稼働観察(OneSignal 予約配信が予定時刻に届くか、S3 同期が継続するか)
ペンディング(中期):
sendPushNotification配信実績(errored 件数)の検知・アラート- OneSignal SDK の更新検討
- kowai-android: Realm 10.19.0 からの脱却(MongoDB が EOL 化)
- kowai-android: 古い androidx 群(appcompat 1.0.2 等)の近代化
2026-05-23 19:00 時点のスナップショット
kowai-android の Google Play 16KBページサイズ対応(対応期限 2026-05-31)
技術作業はすべて完了。署名済みリリースAAB(versionCode 10 / versionName 1.0.2)をビルド済み。
- 成果物:
kowai-android/app/build/outputs/bundle/release/app-release.aab - 内容: 16KB対応 / targetSdk 35 / nend SDK削除 / AdMob 25.2.0・ADG 2.36.0・Realm 10.19.0 / 初回タップ無反応バグ修正
- 16KBアラインメント・タップ修正とも検証済み(実機 YAL-L21)
次にやること:
- vc10 のAABを Play Console にアップロードして公開(先にアップした vc9 は破棄)
- アップロード時の3警告(minSdk低下 / 難読化解除ファイルなし / ネイティブシンボルなし)はすべて無視可
ビルド環境メモ:
JAVA_HOMEは Android Studio 同梱 JBR(/Applications/Android Studio.app/Contents/jbr/Contents/Home)- 署名は
kowai-android/keystore.properties(gitignore対象・要パスワード記入)から読む - リリースAAB生成:
cd kowai-android && ./gradlew :app:bundleRelease
未決・改善余地:
- Realm 10.19.0 は deprecated(MongoDB が Realm を EOL化)。中期的に脱却検討
- 古い androidx 群(appcompat 1.0.2 等・2018年版)は今回未更新。Android 15 実機での表示崩れがあれば個別対応
loadPieceItemのonFailureが握り潰し・タップ時フィードバックなし(今回のタップ修正とは別の改善余地)
2026-05-21 18:27 時点のスナップショット
iOS プッシュ通知の不達は復旧済み(2026-05-21)。
- 原因: OneSignal に登録された APNs 証明書の Bundle ID が
jp.thomsons.ThomsonK002(誤)で、実アプリjp.thomsons.Thomson002と不一致 → APNs が全 push を拒否(errored)。4月の証明書更新時の打ち間違いが原因 - 対処: 正しい Bundle ID の証明書に更新済み。自分の端末へのテスト送信で
successful:1・実機着信を確認 - リポジトリ構成: kowai-operation 配下の kowai-ios / kowai-android / footballnext-server は
.gitignoreで分離(各リポは独立して扱う)
次にやること:
- 次の怖い話追加時に管理画面から本番送信し、約1万台への配信を実地確認
- Apple Developer の誤 App ID
jp.thomsons.ThomsonK002を削除
未決・改善余地:
sendPushNotification()が errored を検知できない(送信後に配信実績を見る監視の追加余地)- OneSignal のタグ上限(プラン上限2 / アプリは3タグ送信)の見直し
ROADMAP(計画)
ロードマップ
今週
- kowai-android: vc10 のリリースAABを Play Console にアップロード・公開(16KB対応期限 2026-05-31)
- kowai サーバー機能を独立 Lightsail (FastAPI) に移管・DNS 切替・HTTPS 化
- Android クラッシュ(Enter / 詳細ページ)緊急修正 + S3 全件 bool 化
- レビュー欄でクラッシュ報告が収まるか見守り
- kowai-server の数日稼働観察(OneSignal 予約配信が予定時刻に届くか、S3 同期が継続するか)
- Apple Developer の誤 App ID
jp.thomsons.ThomsonK002を削除
今月
- Route53 の子 hosted zone
kh.thomsonsapp.comを削除(親に一本化済み・月 $0.50 節約) - 旧 Cake (fn-* EC2) の KhApi コントローラ・関連コード・
kh.thomsonsapp.comvhost をクリーンアップ(fn-* EC2 自体は footballnext で継続) -
sendPushNotification()に配信実績(errored 件数)の検知・アラートを追加 - OneSignal のタグ上限(プラン上限2 / アプリは3タグ送信)の見直し — movie 通知のターゲティング影響を確認
- API レスポンス型変更時のクロスチェック手順をチェックリスト化(クライアント側 Realm / Swift モデル定義の grep を必須に)
今四半期
- 次の iOS リリース:
kowai-ios/Kowai/Const.swift:14のbaseUrlStringをhttps://に変更 - 次の Android リリース:
HomeArticleViewModel.kt/HomeMovieViewModel.kt/StoryListViewModel.kt:33のhttp://をhttps://に、MainActivity.kt:232のgetBoolean(2)をgetInt(2) != 0に変更(サーバー側型変更への耐性向上) - 全クライアントが HTTPS 化したら nginx の 80 番
/khApi/パススルーを撤去して HTTPS only に戻す - kowai-server の S3 出力を ACL=public-read → bucket policy ベース / CloudFront 経由に移行(Block Public Access 適合化)
- kowai-server の OneSignal 失敗時 candidate 戻し or 手動再試行 UI(現状はログ強化のみ)
いつか
- OneSignal SDK の更新検討(iOS は現状 2.12.6 / 最新は 5.x 系)
- kowai-android: Realm からの脱却検討(10.19.0 は deprecated・MongoDB が EOL化)
- kowai-android: 古い androidx 群(appcompat 1.0.2 等・2018年版)の近代化
- kowai-android:
loadPieceItemのエラーハンドリング改善(onFailureの握り潰し・タップ時フィードバックなし) - kowai-server の RSS クローラ強化(旧 RssAnalytics 互換: data-lazy-src / 相対 URL 絶対化 / URL リダイレクト解決)
- kowai-server の Cake 互換性 golden test(API / S3 JSON の代表 fixture を Cake 出力と diff)
DECISIONS(意思決定)
意思決定記録
このプロジェクトで下した重要な意思決定を記録する。 最新が上に来る。
2026-06-09: 旧アプリ救済のため /khApi/* だけは HTTP 受付を残す
背景: リリース済み iOS / Android アプリが http://kh.thomsonsapp.com/khApi/... をハードコードしており、5/24 の 80→301→HTTPS 化で旧クライアントが詰まる事態に。レビュー欄に「読めなくなった」報告が発生した。
決定: nginx の 80 番 server で /khApi/* だけは HTTP のままパススルーする。/login・/fearstoryPortal・/internal 等の認証 / 副作用系は HTTPS 強制を維持。
理由: /khApi/* は読み出し専用・認証情報なしなので HTTP 公開しても攻撃面はほぼ変化なし。代替案として「iOS / Android アプリの baseURL を https に変えて再リリース」も検討したが、審査含めて即時救済にならない。次のリリース時に全クライアントが https に揃ったらこのパススルーは撤去する短期措置として位置づけ。
2026-06-09: is_html / is_info はクライアント側 Realm モデルに合わせて bool で返す
背景: Codex round1 medium-7 で「API と S3 で型を str "0"/"1" に揃える」と判断したが、Android Realm モデル StoryContent.kt:34-35 が is_html: Boolean / is_info: Boolean で受けており、realm.createOrUpdateObjectFromJson が "0" を Boolean 変換できず JSONException → クラッシュしていた。
決定: schemas.py:StoryItem / stories.py:_to_item / cron.py:_story_struct の is_html / is_info を bool に戻す。S3 上の既存 4189 件も resync_all_stories.py で bool 版に一括上書き。
理由: ① クライアントが Boolean を要求しているのが実装で確認できた事実 ② iOS の SwiftyJSON は .boolValue で int / bool / "true"/"false" 文字列いずれも柔軟に扱うため bool に統一しても影響なし ③ 今後 API レスポンス型を変更するときは、必ずクライアントの Realm / Swift モデル定義を grep してクロスチェックする運用に切り替える。
2026-05-24: kowai サーバーは独立 Lightsail (FastAPI) に移管、Cake は使わない
背景: kowai 関連の /khApi/* と /fearstoryPortal/* が footballnext-server (CakePHP, EC2) に間借りしていた。クロール / S3 同期 / 予約 push まで巻き込んで動いていて、footballnext 側の障害・改修の影響を直接受ける構造だった。
決定: 新 Lightsail インスタンス kowai-server を独立で立て、FastAPI + Jinja2 + SQLAlchemy 2.0 で全面移植する。DB は既存 RDS (kowai schema) を共用してスキーマは触らない。kh.thomsonsapp.com の DNS を新サーバーに切り替えて完全分離する。
理由: ① 言語スタックを Python に統一して保守性を上げる ② footballnext と物理的に分けて互いの障害を遮断 ③ RDS は同じスキーマで動作実績がある ④ 旧 fn-* EC2 自体は footballnext で継続使用するので即廃止コストは無し。代替案として「Cake 側に閉じこめたまま改修」も検討したが、PHP 5 系コードベースを今後も保守する負債が大きすぎるため不採用。
2026-05-24: 副作用エンドポイントは /internal/*(127.0.0.1 限定)と /fearstoryPortal/api/*(ログイン必須)に分離、/khApi/* は読み出し API のみ
背景: 移植初期は Cake 互換で /khApi/* 配下に sendPushNotification や cron 系 13 本まで全て無認証で並べていた。Codex レビュー round1 で「外部 IP から push 全配信や DB 書き換えが叩ける」と high 指摘。
決定: ルーティングを 3 系統に再構成する。
/khApi/*: モバイル公開読み出し API(stories / movies / articles / spots)のみ/internal/*: cron 用副作用エンドポイント(13 本)+/internal/dbhealth。nginx でallow 127.0.0.1; allow ::1; deny all;/fearstoryPortal/api/*: 管理画面 JS から叩く副作用 API。require_login+ CSRF dependency
理由: モバイル公開と内部処理が同じ prefix に並んでいると認証境界が曖昧になる。prefix で物理的に分ければ nginx の location ベースで防御でき、設計の意図がコードからも読める。crontab は 127.0.0.1:8000/internal/* を叩く形に変更。
2026-05-24: 時刻基準は Python 側 JST に統一、func.now() は使わない
背景: RDS の @@global.time_zone が UTC のまま動いていて、stories.py の published_date < func.now() で公開判定が 9 時間ズレていた(旧 Cake は datetime() で JST 文字列を渡しており、DB の文字列比較で完結していたため問題が表面化しなかった)。
決定: すべての時刻判定を Python ホスト時刻(Asia/Tokyo の naive datetime)に統一。MySQL の NOW() / func.now() は使わない。RDS の time_zone は触らない。
理由: ① RDS の time_zone を変えると他システムへの影響が読めない ② アプリ側で datetime.now() に揃える方が変更箇所が少ない(2 ヶ所だけ) ③ YouTube publishedAt (UTC) も datetime.fromisoformat(...).replace(tzinfo=utc).astimezone() で JST naive に変換して保存することで、DB 側は一貫して JST 文字列のままに保てる。
2026-05-24: 予約 push は atomic claim(UPDATE WHERE candidate=:val)で二重送信を防ぐ
背景: sendReservedNotification が「全 type を SELECT → 条件一致なら candidate=0 を commit → OneSignal 送信」の順だった。--workers 2 で同じ分に走ると、両 worker が同じ candidate を読み終えてから両方 0 にして両方送る race が成立する。
決定: UPDATE kh_notification_types SET candidate=0 WHERE id=:id AND candidate=:val の atomic claim にする。rowcount==1 の呼び出しだけが OneSignal 送信に進む。
理由: 重複送信は本番ユーザーに直接迷惑がかかる。advisory lock や SELECT FOR UPDATE も検討したが、MySQL の UPDATE 自体が行ロックを取るため最小実装で十分。flock を crontab 側にも入れて多段でガード。
2026-05-24: S3 ACL=public-read を維持(短期)、Block Public Access への移行は将来
背景: Cake 由来の s3:PutObject + ACL=public-read を kowai-server でも踏襲。Codex から「将来の Block Public Access / Object Ownership Bucket owner enforced と衝突する」と low 指摘。
決定: 短期は ACL=public-read を維持。将来 bucket を作り直す / Object Ownership を切り替えるタイミングで、bucket policy ベース or CloudFront 経由に移行する。
理由: モバイルアプリ側の取得 URL を変えると再リリースが必要になる。S3 → CloudFront 化はキャッシュ戦略まで含めた別案件として切り出した方が安全。
2026-05-21: kowai-android の nend SDK を完全削除する
背景: Google Play の16KBページサイズ要件対応で、ネイティブ依存SDKを16KB対応版へ更新する必要があった。nend 広告SDKは更新版が必要だが、nend は2024/3にサービス終了しており16KB対応版が存在しない。
決定: nend SDK をアプリから完全に削除する(依存・専用ファイル・レイアウト・広告ローテーション分岐すべて)。空いた広告枠は AdMob / ADG にフォールバックさせる。
理由: nend は16KB対応版が出ない上、そもそも2024/3以降は広告が配信されていない(空表示)。残す意味がなく、削除が16KB対応の前提でもある。広告比率データ(Realm)は変更せず、enum変換で UNKNOWN→既存の else 分岐に落ちる形にして影響を最小化。
2026-05-21: kowai-android の minSdk を 21→24 に引き上げる
背景: 16KB対応の ADG SDK 2.36.0 が minSdk 23 以上を要求。AdMob 等の新版も古いAndroidを切り捨てる方向。
決定: minSdk を 21→24(Android 7.0)に引き上げる。
理由: API 21〜23(Android 5.0〜6.0)は2026年時点で利用シェアがほぼゼロ。前回更新が2023年で長期未更新だったこともあり実害は小さい。tools:overrideLibrary での強制回避は実行時クラッシュのリスクがあり不採用。24 にしておけば新しい広告SDK群の要求も満たせる。
2026-05-21: Play への提出形式を APK→AAB に移行する
背景: 前回リリース(2023)は APK 形式。16KB対応リリースにあたり提出形式を確認した。
決定: AAB 形式で提出する。
理由: 調査の結果、このアプリは既に Play App Signing 登録済み(配信版が Google署名鍵で署名されている)と判明。kowai 鍵はアップロード鍵として機能し、AAB 提出に追加手続きは不要だった。AAB は今後の標準形式。
2026-05-21: 多重タップガードの窓を 3.5秒→0.6秒に短縮する
背景: 一覧で項目をタップしても初回が無反応で再タップが必要、という既存バグを logcat で調査。原因は BasicUtil.isDisableClickEvent() の3.5秒グローバル多重タップガードが、通常のテンポの操作まで全部ブロックしていたこと(10タップ中2遷移を確認)。
決定: ガードの窓を 3500ms→600ms に短縮(定数 CLICK_GUARD_MS として明示)。
理由: ガード自体(誤連打で詳細画面が二重に開くのを防ぐ)は残す価値がある。誤連打は200ms程度で発生するため600msで十分防げ、かつ通常の閲覧操作はブロックしない。ガード撤廃(0秒)は二重遷移リスクが残るため不採用。
2026-05-21: 古い androidx 群は今回まとめて更新しない
背景: appcompat 1.0.2 など2018年版の androidx 依存が多数。compileSdk 35 化にあたり一括近代化も検討した。
決定: 16KB対応に必要な変更のみに留め、古い androidx 群の一括アップグレードは今回行わない。
理由: compileSdk/targetSdk 35 でビルドは通っており、大量アップグレードはテーマ・リソース属性・Navigation API 等の広範な破壊を招くリスクが高い。5/31期限を優先し変更範囲を限定。Android 15 実機での表示崩れがあれば個別対応する。
2026-05-21: ネストした独立リポは gitignore で分離する
背景: kowai-operation(運用リポ)配下に、独立した git リポジトリである kowai-ios / kowai-android / footballnext-server をクローンして配置した。このままだと親リポが各ディレクトリの中身を gitlink として中途半端に記録してしまう。
決定: 3リポを kowai-operation の .gitignore に追加して追跡対象から外す。git submodule 化はしない。
理由: 各プラットフォームのリポを完全に独立して扱える。submodule の運用(--recurse-submodules、コミットポインタ管理)の煩雑さを避けられる。kowai-operation 自体は運用ドキュメント・スクリプトのみを管理する方針とした。
DEVLOG(作業ログ)
開発日誌
このプロジェクトでの作業を時系列で記録する。 最新のエントリが上に来る。
2026-06-09
18:00 - Android クラッシュ修正(HTTPS 301・is_html/is_info 型不一致 / S3 全件再 sync)
やったこと:
- ユーザーレビュー報告で「Android で読めなくなった」「Enter ボタン押下でクラッシュ」「詳細ページに行くときクラッシュ」を受けて緊急修正
- 原因 1(HTTPS 強制で旧クライアント詰まり)対応:
- リリース済み iOS / Android アプリが
http://kh.thomsonsapp.com/khApi/...を URL ハードコードしていたため、5/24 の80 → 301 → HTTPS化で詰まっていた - nginx の 80 番 server で
/khApi/だけ HTTP のままパススルー、認証系・管理画面は HTTPS 強制を維持(commit0214fe2)
- リリース済み iOS / Android アプリが
- 原因 2(
is_html/is_info型不一致)対応:- Codex round1 medium-7 で「型統一」と判断して bool → str に変えた箇所が、Android Realm モデル
StoryContent.ktのis_html: Boolean/is_info: Booleanと不整合 realm.createOrUpdateObjectFromJsonが"0"を Boolean 変換できず JSONException → Realm transaction 内 uncaught → クラッシュschemas.py:StoryItem/stories.py:_to_item/cron.py:_story_structの3箇所で str → bool に戻す(commit0dcb65d)all_story_contents_ids[i][2]も同様にbool(is_info)で出すよう修正(commit2c345c9)
- Codex round1 medium-7 で「型統一」と判断して bool → str に変えた箇所が、Android Realm モデル
- S3 既存ファイルの一括上書き:
cronStorySyncToS3はis_s3_synced=0のものだけ再 put する仕様のため、修正前に作られた 4000+ 個別 stories/{id:05d}.json が str のまま残っていた- 一回限りのスクリプト
kowai-server/resync_all_stories.pyを作って全 4189 件を bool 版で再 put(162 秒、失敗 0)
- iOS への影響なし(SwiftyJSON の
.boolValueは int / bool / "true"/"false" 文字列いずれも柔軟に解釈) - AVD
kowai16kでエミュレーター起動、ユーザー側で実機 / エミュレーター検証 → OK 報告
詰まったこと / 気づき:
- Codex round1 の「API と S3 で型を揃える」指摘を反映するときに、実際にこの JSON をパースしている Android Realm モデル(Boolean フィールド)の存在を確認しなかったのが致命的だった
- 「Cake が string で返してた」は推測で、現役クライアントが何を要求するかは別問題
cronStorySyncToS3のis_s3_synced=1を信頼するクエリ設計は、JSON 仕様変更時に「全件再生成」できる経路を持たない弱点- 旧 80 番 HTTP は将来も残さざるを得ない(リリース済みアプリの baseUrl ハードコードを変えるには再リリースが必要)
次回やること:
- 数日見守り(クラッシュレビューが収まるか)
- 次の iOS リリース:
kowai-ios/Kowai/Const.swift:14のhttp://kh.thomsonsapp.comをhttps://に - 次の Android リリース:
HomeArticleViewModel.kt/HomeMovieViewModel.kt/StoryListViewModel.kt:33のhttp://をhttps://に、MainActivity.kt:232のgetBoolean(2)をgetInt(2) != 0に - 全クライアントが HTTPS に揃ったら nginx の 80 番
/khApi/パススルーを撤去して HTTPS only に戻す
2026-05-24
17:00 - Codex レビュー反映 → DNS 切替 → HTTPS 化(kowai-server 仕上げ)
やったこと:
Codex レビュー round1 反映(high 3 / medium 7 / low 数件):
- high-1: 副作用エンドポイントを
/khApi/*から分離。cron を/internal/*(nginx で 127.0.0.1 / ::1 のみ許可)、sendPushNotification / updateNotification* を/fearstoryPortal/api/*(require_login)。/khApi/*は読み出し API 4 本のみに整理 - high-2:
func.now()をdatetime.now()に置換。RDS が UTC・アプリが JST 想定だったため、stories.pyの公開判定で 9 時間ズレていた問題を解消 - high-3:
sendReservedNotificationをUPDATE ... WHERE candidate=:valの atomic claim に変更。rowcount==1の呼び出しだけが OneSignal 送信に進む - medium-1: YouTube
publishedAtを aware UTC として epoch 取得、JST naive に変換してupload_dateに保存 - medium-2: RSS クローラの 1 サイト取り込みを 2 件に制限、PR/AD タイトル除外
- medium-3: OneSignal 送信失敗を握って
{"errors":[...], "status_code":N}の JSON で返す + 予約 push のログ message にOK/FAILステータスを埋める - medium-4: CSRF token 機構を追加(
app/csrf.pyのensure_csrf_token/verify_csrfdependency、_base.htmlに<meta name="csrf-token">と{% macro csrf() %}を定義し全 POST form に展開) - medium-5:
/login?next=の open redirect を修正(/fearstoryPortal配下の相対パスのみ許可) - medium-7:
is_html/is_infoの API vs S3 JSON 型不一致を str "0"/"1" に統一 - low: cron に
flock -nとcurl --max-timeを付与、DB URL をURL.create()でエスケープ
- high-1: 副作用エンドポイントを
Codex round2 反映:
- message 生成失敗時に
[reserved/{tag}/{candidate}/FAIL:empty-message]をログ追加 /dbhealthを/internal/dbhealthに移して loopback 限定に- 本番では FastAPI の
/docs/redoc/openapi.jsonを非公開化(KOWAI_ENABLE_DOCS=1で開発時のみ有効) deploy/README.mdをフル更新(swap・cron 適用手順・ルーティング設計・安全運用メモを追加).env.exampleを全変数(DB / SESSION / ONESIGNAL / YOUTUBE / AWS)網羅- 旧
cron.txtに「廃止 / 参考用」のヘッダコメントを追加し誤適用を防ぐ
- message 生成失敗時に
DNS 切替(kh.thomsonsapp.com → 54.249.174.108):
- Route53 の親
thomsonsapp.comにkh A 54.249.174.108を直接追加、親側のkh NS委任レコードを削除(子 hosted zone への委任を停止) - dig で複数 resolver から
54.249.174.108が返ることを確認
- Route53 の親
HTTPS 化:
- Lightsail に snap で certbot をインストール、
certbot certonly --webrootで Let's Encrypt 証明書取得(有効期限 2026-08-22) - Lightsail のファイアウォールで 443/TCP を Any IPv4 で許可
- nginx を HTTPS server block に再構成: HSTS(max-age=1y; includeSubDomains)、TLSv1.2/1.3、HTTP→HTTPS 301 redirect、
limit_req_zoneで/loginに rate limit(10req/min/IP) SessionMiddlewareにhttps_only=Trueを組み込み(KOWAI_INSECURE_COOKIES=1で開発時無効化可)
- Lightsail に snap で certbot をインストール、
動作確認:
https://kh.thomsonsapp.com/healthz200 / HTTP→HTTPS 301 / HSTS 有効 //khApi/stories200 //internal/*外部から 403 //fearstoryPortal未認証 303→/login
詰まったこと / 気づき:
- nginx 1.24 では
http2 on;ディレクティブ未対応 →listen 443 ssl http2;の旧形式に直して解決 - Lightsail はデフォルトで 443 が閉じている。インスタンスのファイアウォールで明示的に Any IPv4 で開けるまで HTTPS が通らない
- RDS の time_zone が UTC のまま(Cake は
datetime()を文字列で渡していたので JST 完結だったが、FastAPI でfunc.now()を使うと比較が UTC ベースになって 9 時間ズレる)。MySQL 側の@@time_zoneを変えずに Python 側でdatetime.now()に揃える方が変更量が小さい - CSRF dependency の
await request.form()先読みは、Starlette が form を cache するため後続の FastAPIForm(...)と協調する。ライブラリ実装に依存して動く前提のメモ - Lightsail に IAM ロールが直接アタッチできないため、S3 用に専用 IAM ユーザー
kowai-serverを新規発行(fearstory bucket に PutObject / GetObject / PutObjectAcl のみ)
次回やること:
- 任意: 子 hosted zone
kh.thomsonsapp.comを Route53 から削除(月 $0.50 節約) - 任意: 旧 Cake (fn-* EC2) の KhApi コントローラ・関連コード・
kh.thomsonsapp.comvhost のクリーンアップ(fn-* EC2 自体は footballnext で継続使用) - 数日〜1週間の稼働観察(OneSignal 予約配信が予定時刻に届くか、S3 同期が継続するか)
2026-05-23
19:00〜26:00 - kowai-server (FastAPI) への移管・管理画面・cron 13本
やったこと:
- 旧 Cake (footballnext-server) に間借りしていた
/khApi/*と/fearstoryPortal/*を新 Lightsail インスタンスkowai-serverに独立移管(FastAPI + Jinja2 + SQLAlchemy 2.0) - Lightsail Tokyo 1GB / 2 vCPU / 40GB を新規作成、VPC peering で既存 RDS (
kowaischema) を共用、Static IP54.249.174.108 - 読み出し API 4 本(stories / movies / articles / spots)を Cake と完全一致になるよう移植(ID 系は str、bool 系は str "0"/"1"、digest_body は body[:70] → 改行除去 → HTML タグ除去)
- fearstoryPortal 管理画面を Jinja2 SSR で全面移植: 広告比率(iOS/Android 各 5 テーブル一括)/ 作者・タグ(CRUD + ページング)/ 怖い話・心霊スポット・記事・RSS(一覧・編集・必要に応じて追加)/ 予約配信 send_time 編集・候補変更(SSR テーブル・現候補ハイライト・候補クリア)/ プッシュ通知(全配信・テスト送信)
- 管理画面デザインを共通 CSS + Jinja2 base テンプレで刷新(モダンライト + 赤アクセント、カードレイアウト、テーブル hover、削除済み行ハイライト、sticky 保存バー、breadcrumb)
- プッシュ通知: OneSignal credentials を旧 Cake
KhOneSignal.phpから移管、/khApi/sendPushNotification(全配信)と/khApi/sendPushNotificationTest(Subscription ID 指定)を実装、特定端末への送信を実機検証で OK - cron 13 本を FastAPI に移植: A 群 4 本(DB のみ)→ D 群 3 本(RSS / YouTube)→ B+C 群 6 本(S3 同期 / 集計データ)。S3 用に専用 IAM ユーザー(fearstory bucket への PutObject / GetObject / PutObjectAcl)を発行
- crontab を新サーバーへ完全切り替え(Cake 側 cron は OFF、fn-* EC2 自体は footballnext で継続)
- Lightsail 1GB プランで安定運用するためのチューニング: swap 1GB(fstab 永続化)、uvicorn
--workers 2 --log-level warning、systemdLimitNOFILE=65536/OOMScoreAdjust=-100、nginx gzip + keep-alive +/staticcache
詰まったこと / 気づき:
- Lightsail は EC2 IAM ロールが直接付けられないため、S3 認証用に専用 IAM ユーザーを発行する形になった
- RDS の time_zone は UTC のまま(後の round1 review で公開判定 9 時間ズレに気付く)
- 一部 RSS が
Server disconnected without sending a responseを返す。並行稼働中は Cake が拾っていたが、新サーバー完全切り替え後は user-agent 等の調整余地 - 既存
KhOnesignalLogsテーブルにupdatedカラムが無い(createdのみ)。models.py 生成時に気付かず修正 - ssh 経由 curl で日本語を送ると URL encode の関係で文字化けする。ブラウザ経由なら問題なし
- index.html のテーブルヘッダが sticky + table-wrap の overflow との組み合わせで崩れる → sticky を撤去
- ボタンの色が出なかった:
input[type=button].btn(specificity 21) が.btn-primary(specificity 10) より勝って background を白で上書きしていた CSS バグを修正
次回やること:
- Codex に移管内容をレビュー依頼(.gods-talk)
18:34 - kowai-android 16KBページサイズ対応・新規リリース整備
やったこと:
- 開発環境を新PCで再構築: Android Studio 2025.3(JBR 21内蔵)/ Android SDK(API 35・build-tools 35・platform-tools)/ コマンドラインツール
- ビルドツールチェーン更新: Gradle 8.0→8.9 / AGP 8.1.0→8.6.1 / Kotlin 1.8.0→2.1.0、廃止された
jcenter()を除去 - jcenter廃止の巻き添えを解消: flexbox を新座標
com.google.android.flexboxへ、未使用の youtube-player-view(実体消失・コード未使用)を削除 - compileSdk / targetSdk を 33→35(Play更新要件)、minSdk 21→24 に変更
- nend SDK を完全削除(2024/3サービス終了・16KB対応版なし): 専用Kotlin5本+レイアウト5本を削除、広告ローテーション処理13ファイルから nend 分岐を除去
- 広告SDKを16KB対応版へ: AdMob 22.3.0→25.2.0、ADG を
+→2.36.0 にピン留め、Realm 10.15.1→10.19.0 - ADG 2.36.0 のAPI変更(リスナー引数@NonNull化・getter群@Nullable化)に追従
- リリース署名を keystore.properties 方式(gitignore対象)で build.gradle に組み込み
- 署名済みリリースAABをビルドし16KBアラインメントを検証(ELF
2**14+zip整列、bundletool生成APKでも合格) - 既存バグ「リスト初回タップが無反応」を logcat で原因特定・修正、versionCode 10 を再ビルドし実機検証
詰まったこと / 気づき:
- ネイティブ
.soは Realm のlibrealm-jni.soのみ(AdMob・ADGは.so非同梱)。Realm 10.19.0 が16KB対応版で要件クリア - youtube-player-view 1.4.0 は jcenter専用で .aar がどこにも残っておらず、かつコード上完全に未使用 → 削除で解決
- タップ無反応の原因は通信失敗ではなく
BasicUtil.isDisableClickEvent()の3.5秒グローバル多重タップガード。logで10タップ中2遷移を確認し確定 - このアプリは既に Play App Signing 登録済み(配信版が Google署名鍵)。
kowai鍵はアップロード鍵で、vc9アップロードが署名エラーなく通った事実が裏付け
次回やること:
- vc10 のリリースAAB(
kowai-android/app/build/outputs/bundle/release/app-release.aab)を Play Console にアップロードして公開(対応期限 2026-05-31)
18:27 - リポジトリ整備 & iOS通知失敗の原因調査・復旧
やったこと:
- kowai-operation 配下にネストした独立リポ3つ(kowai-ios / kowai-android / footballnext-server)を
.gitignoreで分離 - iOS の OneSignal プッシュ通知が4月以降届かない問題を調査
- 送信経路を特定: footballnext-server の
KhApiController.php::sendPushNotification()→KhOneSignal::send()→ OneSignal REST API(included_segmentsでstory/old_appに送信) kh_onesignal_logsテーブルと OneSignal API(GET /notifications/{id})で配信実績を確認 → iOS 約1万台が全件errored(APNs に拒否されている状態)- 原因を特定: OneSignal に登録された APNs 証明書の Bundle ID が
jp.thomsons.ThomsonK002(誤・余計な K)。実アプリ(App Store / Xcode 両方で確認)はjp.thomsons.Thomson002。不一致のため APNs が全 push を拒否
- 送信経路を特定: footballnext-server の
- 復旧テスト: 自分の端末1台に直接テスト送信 → ①更新前
errored:1→ 証明書更新 → ②更新後successful:1(実機着信も確認)
詰まったこと / 気づき:
- OneSignal 管理画面では証明書が「Active」「期限内(2026-12-28)」と表示され一見問題なく見えた。見るべきは expiry ではなく Bundle ID 行だった
sendPushNotification()は OneSignal が受理さえすれば「成功」扱いで、全件 errored でも検知できない。失敗発覚が遅れた一因- 当初「証明書の期限切れ」と推測したが誤り。実際は4月の証明書更新時に担当者が Bundle ID を打ち間違えたのが原因
次回やること:
- 次の怖い話追加時に管理画面から本番送信し、約1万台への配信を確認
- Apple Developer の誤 App ID
jp.thomsons.ThomsonK002を削除
最近のコミット
- d05f658 .devnotes を更新(Android クラッシュ修正セッション) 2026/6/9
- 0dcb65d [Android クラッシュ修正・本命] is_html / is_info を bool に戻す 2026/6/8
- 2c345c9 [Android クラッシュ修正] all_story_contents_ids の is_info を bool で出す 2026/6/8
- 0214fe2 旧モバイルアプリ救済: /khApi/* は HTTP でも受けるよう nginx を緩和 2026/6/3
- e4d64d8 .devnotes を更新(kowai-server Lightsail 移管完了・Codex レビュー反映・DNS+HTTPS 化) 2026/5/24
- 0cf7732 nginx 1.24 互換: http2 を listen ディレクティブにインライン指定 2026/5/24
- 782a160 [medium-6 / round2 high-1] HTTPS 化を完了させる 2026/5/24
- fe72c0f [round2] Codex 再レビュー指摘の軽め項目を反映(DNS 切替前のクリーンアップ) 2026/5/24
- 32da722 [low] cron に flock+--max-time、DB URL を URL.create() でエスケープ 2026/5/24
- 4addda6 [medium-4] 管理画面 POST に CSRF トークン 2026/5/24
README
kowai-operation
概要
(記入予定)
セットアップ
(記入予定)
使い方
(記入予定)