プロフィール

arismmn timeline blog

← ブログ一覧に戻る

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

AstroCloudflareデバッグ開発環境機能実装

概要

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

  • 🕐 仮想日時機能 - 本番環境でイベント完了時の表示をテストできる
  • 🔄 目標値変更の即座反映 - 翌日まで待たずに軍師AIが再計算
  • 📝 過去イベント履歴 - 過去に実施したイベントを一覧表示
  • ✏️ 進行中イベント編集機能 - カレンダーピッカーで簡単に設定変更

仮想日時機能の実装

背景

イベント完了時の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>
)}

学び

  • データベースに柔軟な設定値を持たせることで、システム全体の振る舞いを動的に変更できる
  • テスト用の機能でも、視覚的なフィードバック(バナー表示)は重要
  • datetime-local型のinput要素により、カレンダーピッカーで直感的に日時を選択可能

目標値変更の即座反映

問題点

以前の実装では、目標値を変更しても翌日まで軍師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: ウィンドウの再読み込み

  • コマンドパレット(Ctrl + Shift + P
  • 開発者: ウィンドウの再読み込みを実行

予防策

  • 必ずコミットメッセージを入力してからコミットボタンを押す
  • メッセージ欄が空欄の場合は、コミットを実行しない習慣をつける

学び

  • VSCodeのGit拡張は内部キャッシュを持っており、ファイルシステムの変更だけでは状態が更新されない場合がある
  • UIの再描画をトリガーすることで、キャッシュをクリアできる
  • エディタの「表示フィルタ切り替え」という意外な方法が有効だった

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

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

メリット

  • ユーザーがカレンダーUIから直感的に日付を選択可能
  • 入力形式のバリデーションが自動で行われる
  • モバイルでも最適化されたピッカーが表示される
<!-- 日時の入力 -->
<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: プライバシーの侵害

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

  • あなたの閲覧履歴が外部サイトに把握される
  • URLにセッション情報が含まれている場合、それが漏洩する

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

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

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

noopenerの効果:

  • 新しいタブから元のページを操作できなくなる
  • 別プロセスで実行されるため、パフォーマンスが向上

noreferrerの効果:

  • リファラー情報が送信されなくなり、プライバシーが保護される
  • noopenerの効果も含まれる(古いブラウザ対応)

このブログでの実装

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

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

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

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


ブログ機能の拡張

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

カテゴリ機能

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

実装内容:

  • カテゴリ別記事一覧ページ(/blog/category/{カテゴリ名}
  • サイドバーに全カテゴリを記事数付きで表示
  • カテゴリタグをリンク化(ホバー時のアニメーション付き)

月別アーカイブ機能

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

実装内容:

  • 月別アーカイブページ(/blog/archive/YYYY-MM
  • サイドバーに月別リンクを新しい順に表示
  • 各月の記事数を集計して表示

サイドバーの追加

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

  • 🏷️ カテゴリ一覧(記事数が多い順)
  • 📅 月別アーカイブ(新しい月が上)

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


技術スタック

  • Framework: Astro.js (SSR mode)
  • Hosting: Cloudflare Pages
  • Database: Cloudflare D1 (SQLite)
  • AI: Cloudflare Workers AI
  • Editor: Visual Studio Code

関連リンク

Gemini
Powered by
Gemini
(使用モデル Gemini3)
← ブログ一覧に戻る