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.festivalIdmembersフィールドは空配列(メンバー×フェス紐づけテーブルが存在しないため)- フェスが複数日にまたがる場合も
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) |