---
title: "events テーブル定義書"
---

最終更新: 2026-08-03
関連 Issue: #926（eventsテーブル実装）【v5.0.0 タイムライン機能】 / #1108（V2設計書整備）, #1109（festival統合）, #1110（タグシステム）【v7.0.0 タイムライン機能拡張(V2)】 / #1381（イベントテーブル拡張・time カラム追加） / #1472（タイムライン改善: 同日順序・プレースホルダー全文表示・ツアー時刻二重表示解消）

---

## 1. 目的

既存データ（Releases / Members / Lives / Radio / Manual Events）を時系列で横断的に扱うための統合イベントレイヤーを構築する。

- 既存データは変更しない
- events は「派生データ」として生成する
- タイムライン UI は events のみを参照する

---

## 2. テーブル定義

### テーブル名

`events`

### スキーマ

| カラム | 型 | NOT NULL | デフォルト | 説明 |
|---|---|---|---|---|
| `id` | UUID | ✅ | —（Prisma がアプリ層で UUID 生成） | PK |
| `date` | DATE | ✅ | — | イベント発生日 |
| `time` | TIME(0) | — | NULL | イベント開始時刻。`lives.time` と同じ表現方式（秒精度なし）。時刻不明・未登録時は NULL |
| `type` | TEXT | ✅ | — | イベント種別（後述） |
| `title` | TEXT | ✅ | — | イベントタイトル |
| `description` | TEXT | — | NULL | 補足説明 |
| `members` | TEXT[] | — | `'{}'` | 関連メンバー ID 配列（members.json の id と対応） |
| `tags` | TEXT[] | — | `'{}'` | タグ配列（後述） |
| `imageUrl` | TEXT | — | NULL | 表示画像の URL（Vercel Blob URL 等） |
| `link` | TEXT | — | NULL | 関連リンク（MV / ニュース / radiko 等） |
| `source` | TEXT | ✅ | — | 元データ種別（後述） |
| `sourceId` | TEXT | ✅ | — | 元データの ID |
| `createdAt` | TIMESTAMPTZ | ✅ | `CURRENT_TIMESTAMP` | 作成日時 |
| `updatedAt` | TIMESTAMPTZ | ✅ | —（Prisma の `@updatedAt` で管理） | 更新日時（更新時に自動更新） |

---

## 3. type（イベント種別）

| 値 | 説明 | 元データ | V1/V2 |
|---|---|---|---|
| `release` | シングル・アルバム発売 | releases | V1 |
| `live` | ライブ・ツアー | lives | V1 |
| `join` | メンバー加入 | members.json | V1 |
| `graduate` | メンバー卒業 | members.json | V1 |
| `media` | ラジオ・TV 出演 | radio_episodes | V1 |
| `topic` | その他の話題・発表 | manual | V1 |
| `festival` | 外部フェス出演 | festivals | V2追加予定 |

### festival type の仕様（V2）

- `festivals` テーブルからの派生データ
- `source = 'festival'`、`sourceId = festival.festivalId`
- `members` フィールドは空配列（メンバー×フェス紐づけテーブルが存在しないため）
- フェスが複数日にまたがる場合も `dateEnd` は持たない（セクション 3b 参照）。V2 時点では開始日のみ表示とし、期間表示が必要になった際に改めて検討する

---

## 3a. tags（タグ）仕様

`tags` フィールドは `type` とは別軸のイベント性質を表すラベル配列。フィルタリングやUIでの表示に使用する。

| タグ値 | 説明 | 設定対象 type の例 | 付与方法 |
|---|---|---|---|
| `OG` | OG メンバーが関与するイベント | 全 type | 自動（`assignTags`） |
| `Birthday` | 誕生日関連イベント | 全 type | 自動（`assignTags`） |
| `Solo` | メンバーのソロ活動 | `topic`, `live`, `media` | 手動（manual-events.json） |
| `Unit` | ユニット活動 | `release`, `live`, `topic` | 手動（manual-events.json） |
| `Special` | 記念・特別イベント | `live`, `topic` | 手動（manual-events.json） |
| `Collaboration` | 他アーティストとのコラボ | `release`, `topic` | 手動（manual-events.json） |

- タグは任意付与。付与なし（空配列）も有効
- 1イベントに複数タグを付与可能
- 自動付与タグ（`OG`・`Birthday`）は `sync-events.ts` の `assignTags()` 関数で付与される
  - `OG`: イベントに関与するメンバーのうち1名以上が `status='OG'`
  - `Birthday`: イベント日の UTC 月/日がいずれかの関与メンバーの `profile.dob` と一致
  - 全データソース（Releases / Members / Lives / Radio / Festivals / **Manual**）に適用される
- `data/inputs/manual-events.json` の `tags` フィールドで任意タグを明示指定できる
  - `tags` を**省略**した場合 → `assignTags()` が OG・Birthday を自動付与
  - `tags` を**明示指定**した場合（`[]` を含む）→ 明示値を優先し、`assignTags()` は適用されない

---

## 3b. dateEnd フィールドの要否

`dateEnd`（終了日）フィールドは当初 V2 設計として検討されたが、**追加しない**と判断した。

理由：
- `live` 型はツアーの各公演を1レコードずつ持つ設計のため、期間を持つ概念がない
- `festival` 型の元データ（`festivals` テーブル）は単日記録であり、終了日のデータソースがない
- タイムライン UI での期間表示が必要になった時点で、データ供給方法と合わせて改めて検討する

---

## 4. source / source_id の意味

events は既存データの派生であるため、どのデータから生成されたかを保持する。

| source 値 | sourceId の意味 | 例 |
|---|---|---|
| `release` | Release.releaseId | `"release_001"` |
| `member` | Member.memberId（members.json の id） | `"nakazawa-yuko"` |
| `live` | Live.liveId | `"2026B001"` |
| `radio` | RadioEpisode.episodeId | `"morning-musume-radio-20240517"` |
| `manual` | 任意（手動入力時に付与） | `"topic-2024-01"` |
| `festival` | Festival.festivalId | `"summer-sonic-2025"` |

---

## 5. 変換ロジック仕様

変換レイヤーの実装は #927 で行う。本書には入力元フィールドのマッピング仕様のみを記載する。

### 5-1. Releases → events(release)

| events カラム | 元フィールド |
|---|---|
| `date` | `release.releaseDate` |
| `type` | `"release"` |
| `title` | `release.title` |
| `members` | releases.json のメンバー紐づけ |
| `imageUrl` | Cover Art Archive から取得したジャケット画像 URL（後述）|
| `link` | releases.json の MV URL |
| `source` | `"release"` |
| `sourceId` | `release.releaseId`（= MusicBrainz リリースグループ MBID） |

### 5-2. Members → events(join / graduate)

#### 加入（join）

| events カラム | 元フィールド |
|---|---|
| `date` | `member.joined`（members.json） |
| `type` | `"join"` |
| `title` | `"{期}期加入"` |
| `members` | `[member.id]` |
| `imageUrl` | `member.image`（members.json） |
| `source` | `"member"` |
| `sourceId` | `member.id` |

#### 卒業（graduate）

| events カラム | 元フィールド |
|---|---|
| `date` | `member.graduated`（members.json） |
| `type` | `"graduate"` |
| `title` | `"{name} 卒業"` |
| `members` | `[member.id]` |
| `imageUrl` | `member.image`（members.json） |
| `source` | `"member"` |
| `sourceId` | `member.id` |

### 5-3. Lives → events(live)

| events カラム | 元フィールド |
|---|---|
| `date` | `live.date` |
| `time` | `live.time`（そのままコピー） |
| `type` | `"live"` |
| `title` | `live.title`（または `tour.title`） |
| `members` | `MemberLive` 経由の `memberId` 配列 |
| `source` | `"live"` |
| `sourceId` | `live.liveId` |

### 5-4. Radio → events(media)

| events カラム | 元フィールド |
|---|---|
| `date` | `radioEpisode.date` |
| `type` | `"media"` |
| `title` | `radioShow.name` |
| `members` | `RadioMember` 経由の `memberId` 配列 |
| `source` | `"radio"` |
| `sourceId` | `radioEpisode.episodeId` |

### 5-5. Manual → events(topic)

全フィールド手動入力。`source = "manual"`、`source_id` は任意の識別子を付与する。

`data/inputs/manual-events.json` の `time` フィールド（`"HH:MM"` 形式の文字列、任意）を指定すると、`time` カラムに変換される。省略時は既存値を保持する（`imageUrl` と同じ undefined/null の使い分け。#1381）。

### 5-6. Festivals → events(festival)（V2追加予定）

| events カラム | 元フィールド |
|---|---|
| `date` | `festival.date` |
| `type` | `"festival"` |
| `title` | `festival.name` |
| `members` | `[]`（メンバー×フェス紐づけなし） |
| `source` | `"festival"` |
| `sourceId` | `festival.festivalId` |

---

## 6. imageUrl 管理方針

### 6-1. release イベントのジャケット画像（#1070）

`source = 'release'` のイベントは **Cover Art Archive（CAA）** からジャケット画像を取得して `imageUrl` に保存する。

#### データフロー

```
releases.releaseId
  = MusicBrainz リリースグループ MBID
    └─ https://coverartarchive.org/release-group/{MBID}/front
         → 307 リダイレクト → archive.org 上の実画像 URL
              → events.imageUrl に保存
```

#### 取得タイミング

`imageUrl` の自動設定は行わず、以下のタイミングで手動実行する。

| タイミング | 手順 |
|---|---|
| 初回一括バックフィル | `bun run scripts/patch/fetch-release-images.ts` を実行 |
| 新シングル発売後 | MusicBrainz 登録・sync-discography・sync-events 完了後に同スクリプトを実行 |

> 詳細な手順は `docs/operations/discography-operations.md` の「新シングル発売時のカバーアート取得手順」を参照。

#### 取得対象外

- `source = 'manual'`（`manual-` プレフィックス ID）の手動登録リリース→ MusicBrainz MBID を持たないため CAA 取得不可

### 6-2. release 以外のイベントの画像表示（#1070、プレースホルダー方式見直し #1472）

`imageUrl = NULL` かつ `type ≠ release` のイベントは、EventCard コンポーネントが種別に応じたプレースホルダーを動的生成する。DB への保存は不要。プレースホルダーは表示内容によって2方式に分かれる。

**方式A: SVG テキストアート**（`live`・`join`・`graduate`・`leader`）

タイトル文字列そのものではなく、種別ごとに合成した短い定型テキスト（開催年・`JOIN`/`GRAD` ラベル・メンバー名等）を固定サイズの `viewBox` に描画する。表示内容が短く定型のため、折り返しや省略は発生しない。

| type | SVG に表示する内容 |
|---|---|
| `live` | 開催年 + 季節（UTC 月から算出: 3〜5月→春、6〜8月→夏、9〜11月→秋、12〜2月→冬） |
| `join` | 1行目: `JOIN` / 2行目: メンバー名（1行に収まる範囲、超過は省略） |
| `graduate` | 1行目: `GRAD` / 2行目: メンバー名（1行に収まる範囲、超過は省略） |
| `leader` | 1行目: `第X代` / 2行目: `リーダー就任` |

**方式B: HTML テキストブロック**（`media`・`topic`・`festival`、#1472）

`events.title` をそのまま全文表示する種別。SVG の `<text>` は CSS の折り返し（`white-space`/`overflow-wrap`）に対応しておらず、行数を固定して溢れた分を `…` で省略する方式だと、タイトル長が可変な限り必ず一部のケースで全文を表示できなくなる問題があった（#1472で発覚）。そのため、これらの type は `<div>` 等の HTML ブロック（`overflow-wrap: break-word`）で描画し、コンテナは固定の縦横比をやめて `min-height` のみを指定し、コンテンツ量に応じて高さが伸びる可変レイアウトにする。行数の上限は設けず、`events.title` を省略なく全文表示する。

| type | HTML ブロックに表示する内容 |
|---|---|
| `media` | 番組名（`events.title`、全文） |
| `topic` | タイトル（`events.title`、全文） |
| `festival` | フェス名（`events.title`、全文） |

### 6-3. タイトル表示の重複回避（#1472）

EventCard は画像・プレースホルダー領域の下に `<h3>` でタイトルを別途表示するが、6-2 の方式Bはプレースホルダー自体に `events.title` の全文を表示するため、`imageUrl` 未設定時は `<h3>` と内容が完全に重複する。

- `imageUrl = NULL` かつ 6-2 方式B対象の type（`media`・`topic`・`festival`）の場合のみ、`<h3>` タイトルを非表示にする
- 上記以外（`imageUrl` が設定されている場合、または方式A対象の type）は、プレースホルダー側がタイトル文字列自体を表示しないため、従来通り `<h3>` を表示する

---

## 7. インデックス

| インデックス対象 | インデックス種別 | 理由 |
|---|---|---|
| `date` | B-tree | タイムライン表示（`ORDER BY date`）の主要クエリキー |
| `type` | B-tree | イベント種別フィルタリング |
| `source, sourceId` | B-tree | 元データからのルックアップ・重複排除 |
| `tags` | GIN | タグ配列フィルタリング（`@> ARRAY[...]`）（フィルタ機能実装時に追加予定） |

---

## 8. タイムライン UI 表示仕様

### 8-1. 年別表示（#998）

- タイムラインページは **1年分のイベントのみ** を表示する
- URL クエリパラメータ `year`（例: `/timeline?year=2024`）で表示年を指定する
- 未指定・無効値の場合は最新年を表示する
- ページ上部にすべての年のタブリンクを表示し、選択中の年をハイライトする

### 8-2. ライブイベントのグループ表示（#997）

同一ツアー（`title` が同じ `type=live` イベント群）は1グループとして折りたたんで表示する。

| グループの公演数 | 表示方法 |
|---|---|
| 1件 | 通常のフルサイズカード（会場名を日付の後に表示） |
| 2件 | 2枚のフルサイズカードを並列表示（会場名あり） |
| 3件以上 | 初日・千秋楽をフルサイズカード、中間日をコンパクトリスト（日付 / 会場 / 開演時刻）で表示 |

- 会場名・開演時刻は `lives` テーブルを `sourceId`（liveId）経由でジョインして取得する
- 同日に複数公演がある場合は1行にまとめ、時刻を `"14:00 / 18:00"` 形式で列挙する
- 開演時刻が `00:00`（未設定）の場合は時刻を表示しない

### 8-3. EventCard の時刻表示（#1381）

ツアーグループ化されない単発イベント（`EventCard` で直接描画される全 type）は、`events.time` を日付の直後に表示する。

- `time` が NULL または `00:00` の場合は表示しない（8-2 の Live 表示ルールと同じ判定関数 `shouldShowTime`/`formatLiveTime` を共通利用）
- `type=live` のツアーグループ表示（8-2）は `lives` テーブルの時刻を別途取得して表示するため、`events.time` の表示とは独立している
- 8-2 のツアーグループで会場欄に複数公演の時刻一覧（`"14:00 / 18:00"`）が含まれる代表カード（初日・千秋楽、または同日2件時の1枚カード）は、時刻が会場欄と二重表示にならないよう `events.time` 自体の表示を抑制する（#1472）

### 8-4. イベントの並び順（#1472）

タイムライン表示（年・週・日・年表の各ビュー）で `events` を取得するクエリは、`date` 昇順に加えて **`time` を副次ソートキー** として指定する（`orderBy: [{ date: "asc" }, { time: "asc" }]`）。

- `date` のみで `ORDER BY` すると、同日内のタイ（同値）の並び順は Postgres が保証しないため、物理的な行順（挿入順等）が表示順になり不安定になる（#1472で発覚）
- `time` が NULL のイベントは Postgres の `ASC` デフォルト挙動（`NULLS LAST`）により末尾に並ぶ

---

## 改訂履歴

| 版 | 更新日 | 変更内容 |
| --- | --- | --- |
| 2.3 | 2026-08-03 | タイムライン改善（#1472）: 同日イベントの並び順仕様（8-4）を追加、ツアーグループ代表カードの時刻二重表示回避仕様（8-3）を追加、`media`/`topic`/`festival` のプレースホルダーをSVG2行折り返しからHTML全文表示（可変高さ）に変更し6-2を更新、タイトル重複回避仕様（6-3）を追加 |
| 2.2 | 2026-07-20 | `time` カラムを追加。Lives → events(live) の変換仕様・Manual の time 入力仕様・EventCard の時刻表示仕様（8-3）を追記（#1381） |
| 2.1 | 2026-06-14 | tags フィールドを実装済みに更新。OG・Birthday 自動タグ仕様を追記。手動タグ付与フローを追記（#1110） |
| 2.0 | 2026-06-13 | V2 拡張設計を追記（festival type・tags フィールド・dateEnd フィールド・GIN インデックス）。セクション番号重複を修正（#1108） |
| 1.3 | 2026-06-05 | imageUrl 管理方針を追加（CAA によるジャケット画像取得・SVG テキストアート）（#1070） |
| 1.2 | 2026-05-29 | タイムライン UI 表示仕様を追加（年別表示・ライブグループ表示）（#997, #998） |
| 1.1 | 2026-05-24 | カラム名を実際の DB カラム名（camelCase）に修正。id・updatedAt のデフォルト値記述を修正（#989） |
| 1.0 | 2026-05-24 | 初版作成（#926） |
