イベント管理機能の大幅改善 - 仮想日時とリアルタイム目標値反映

概要

本日は、イベント管理機能に大きな改善を加えました。主な実装内容は以下の通りです:

仮想日時機能の実装

背景

イベント完了時のUI表示を確認したいが、実際にイベント終了日まで待つのは非効率でした。また、本番データベースを使っているため、システム日時を変更することもできません。

解決策

データベースにvirtual_datetimeカラムを追加し、設定されている場合はその日時を現在時刻として使用する仕組みを実装しました。

// src/lib/aggregator.ts
const getEffectiveJSTDate = async (db: any): Promise<Date> => {
  const setting = await db.prepare(
    "SELECT value FROM settings WHERE key = 'virtual_datetime'"
  ).first();
  
  if (setting && setting.value) {
    return new Date(setting.value.replace(/\//g, '-').replace(' ', 'T'));
  }
  
  return getJSTDate(); // 通常のJST現在時刻
};

UI側の実装

策戦の間(管理画面)で仮想日時を設定・解除できるようにし、タイムラインページには黄色いバナーを表示して仮想モードであることを明示しました。

{virtualDatetime && (
  <div class="virtual-datetime-banner">
    <strong>🕐 仮想日時モード:</strong> このタイムラインは現在、仮想日時 
    <span class="datetime-value">{virtualDatetime}</span> で動作しています
  </div>
)}

学び


目標値変更の即座反映

問題点

以前の実装では、目標値を変更しても翌日まで軍師AIの計算に反映されませんでした。これは以下の理由によるものでした:

しかし実際の運用では、「目標値を変更したのに反映されない」「変更したこと自体を忘れる」という問題が発生しました。

解決策

1. 管理画面側の変更

目標値を更新した際に、以下の処理を追加しました:

// admin-dashboard.astro
const today = now.substring(0, 10); // YYYY-MM-DD
const todayKey = `daily_target_${today}`;

// 本日の日次目標をリセット
await db.prepare("DELETE FROM settings WHERE key = ?")
  .bind(todayKey).run();

// 変更時刻を記録
await db.prepare(
  "INSERT OR REPLACE INTO settings (key, value) VALUES ('last_goal_change', ?)"
).bind(now).run();

2. イベントWorker側の変更

固定ロジックを削除し、常に最新状況で再計算するように変更:

// event-worker/handler.js
// 以前:if (dailyTargetPoints === 0) { 計算 }
// 現在:常に計算

let dailyTargetPoints = 0;
if (activeEvent) {
  const daysLeft = Math.max(1, Math.ceil(diff / (1000 * 60 * 60 * 24)));
  const remainingFromBase = goal - basePoints;
  dailyTargetPoints = Math.ceil(basePoints + (remainingFromBase / daysLeft));
  
  // 計算結果を保存
  await env.DB.prepare("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?)")
    .bind(todayKey, dailyTargetPoints.toString()).run();
}

さらに、目標値変更を検知してSlackで報告する機能も追加しました。

学び


VSCode Git拡張のトラブルシューティング

問題発生

空のコミットメッセージでコミットボタンを押したところ、VSCodeのソース管理パネルが動作不能になりました。リポジトリ名の横にローディングインジケータが止まらなくなりました。

原因

  1. Gitはコミットメッセージが空の場合、コミット処理を中断
  2. しかしVSCodeが作成した.git/COMMIT_EDITMSGファイルは残る
  3. このファイルが残っていると、Gitが「コミット処理中」と誤認識

解決方法

ステップ1: 中途半端なファイルを削除

Remove-Item -Path .git\COMMIT_EDITMSG -Force

ステップ2: 適切なメッセージでコミット

git commit -m "適切なコミットメッセージ"

ステップ3: VSCodeのビュー更新

ファイルを削除しても、VSCodeの内部キャッシュが更新されない場合があります。以下のいずれかの方法で解決:

方法1: 表示フィルタを切り替える(最も簡単)

  1. ソース管理パネルを開く
  2. 右上の「︙」(3点メニュー)をクリック
  3. 「変更」のチェックを外す→再度つける

方法2: ウィンドウの再読み込み

予防策

学び


カレンダーピッカーの全面導入

仮想日時設定、イベント登録・編集フォームのすべての日時入力を、HTML5のdatetime-localdate型に変更しました。

メリット

<!-- 日時の入力 -->
<input type="datetime-local" name="start_at" />

<!-- 日付のみの入力 -->
<input type="date" name="goal_date" />

過去イベント履歴の表示

策戦の間の最下部に、過去に実施されたイベント(is_active = 0)を一覧表示する機能を実装しました。

SELECT * FROM events 
WHERE is_active = 0 
ORDER BY end_at DESC 
LIMIT 20

これにより、過去の作戦の達成状況を振り返ることができるようになりました。


まとめ

本日の実装により、以下のメリットが得られました:

  1. テストの効率化 - 仮想日時により時間経過をシミュレート可能
  2. 即座の反映 - 目標値変更が翌日を待たずに適用される
  3. 使いやすさの向上 - カレンダーピッカーによる直感的な操作
  4. 履歴の可視化 - 過去のイベントを振り返り可能

また、VSCodeのトラブルシューティングを通じて、エディタの内部動作への理解も深まりました。


外部リンクのセキュリティ対策

なぜrel="noopener noreferrer"が必要なのか

ブログ記事に外部サイトへのリンクを貼る際、何も対策をしないとセキュリティリスクが発生する可能性があります。

問題1: Tabnabbing攻撃

target="_blank"で新しいタブを開くリンクは、デフォルトで元のページを操作できるという脆弱性を持っています。

攻撃シナリオ:

  1. あなたがブログの外部リンクをクリックして新しいタブを開く
  2. リンク先のサイトが悪意のあるスクリプトを実行
  3. 元のタブ(このブログ)がフィッシング詐欺サイトにすり替わる
  4. あなたがタブに戻ったとき、偽サイトでログイン情報を入力してしまう

これをTabnabbing攻撃と呼びます。

問題2: プライバシーの侵害

リンクをクリックすると、「どのページから来たか」という情報(リファラー)が送信されます。これにより:

問題3: パフォーマンスの低下

新しいタブが元のページと同じプロセスで実行されるため、リンク先のサイトが重い処理をすると、元のページも遅くなる可能性があります。

対策: rel="noopener noreferrer"の効果

noopenerの効果:

noreferrerの効果:

このブログでの実装

記事を読み込む際、JavaScriptが自動的に外部リンクを検出し、以下の処理を行います:

  1. target="_blank"を追加(新しいタブで開く)
  2. rel="noopener noreferrer"を追加(セキュリティ対策)
  3. 🔗アイコンを追加(外部リンクであることを視覚的に表示)

これにより、記事を書く際に毎回手動で設定する必要がなく、自動的に安全なリンクになります。

参考: モダンなブラウザ(Chrome 88+など)ではtarget="_blank"に自動的にnoopenerが適用されますが、すべての環境で安全性を保証するため、明示的に指定しています。


ブログ機能の拡張

記事が増えてきた際の利便性向上のため、カテゴリ機能月別アーカイブ機能を実装しました。

カテゴリ機能

各記事に付けられたカテゴリタグがクリック可能になり、同じカテゴリの記事だけを表示できるようになりました。

実装内容:

月別アーカイブ機能

特定の月に投稿された記事をまとめて表示できるようになりました。

実装内容:

サイドバーの追加

ブログ一覧ページに右サイドバーを追加し、以下の情報を表示:

レスポンシブ対応により、モバイル画面ではサイドバーがメインコンテンツの下に移動します。


技術スタック


関連リンク