メンバー データ設計書
最終更新: 2026-07-21
1. 概要
members.json に格納される Member(メンバー)エンティティのデータ設計。
Member は他の全機能(リリース紐付け・プレイリスト・ユニット・ライブ・関係性マップ・タイムライン等)から参照される最も基盤的なエンティティだが、専用のデータ設計書がこれまで存在しなかった(#1379)。
2. 背景・目的
| 目的 | 詳細 |
|---|---|
Member 本体のフィールド定義の一元化 |
types/member.ts の MemberSchema に散在するフィールドの意味・業務ルールをまとめる |
| 機能固有ドキュメントとの役割分担 | フィールドごとの詳細な算出ロジック・UI仕様は既存の機能別設計書に記載済みのため、本書では概要とリンクに留め重複を避ける |
3. 型定義
Member インターフェース(types/member.ts)
export const MemberSchema = z.object({
id: z.string().min(1),
name: z.string().min(1),
ruby: z.string().min(1),
status: z.string().min(1),
generation: z.number(),
color: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
colorName: z.string().optional(),
leaderGenerationNumber: z.number().int().positive().nullable().optional(),
nickname: z.array(z.string()).optional(),
joiningRoute: z.string().optional(),
latestStatus: StatusEntrySchema,
officialSns: z.array(SnsEntrySchema).optional(),
statusHistory: z.array(StatusEntrySchema).optional(),
snsCheck: SnsCheckSchema.optional(),
youtubeChannel: YoutubeChannelSchema.optional(),
tiktokChannel: TikTokChannelSchema.optional(),
profile: z.object({
dob: z.string(),
joinDate: z.string(),
gradDate: z.string().nullable(),
family: z.string(),
birthplace: z.string().optional(),
instructor: z.array(z.string()).optional(),
}),
});
export type Member = z.infer<typeof MemberSchema>;
フィールド詳細
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
id |
string |
✅ | メンバーの識別子(kebab-case。例: iida-kaori)。他エンティティから memberId として参照される主キー |
name |
string |
✅ | 氏名 |
ruby |
string |
✅ | ふりがな(五十音ソート等に使用) |
status |
string |
✅ | 在籍状況を表す短い文字列(例: 現役メンバーの所属ユニット名、卒業メンバーの 卒業) |
generation |
number |
✅ | 期数 |
color |
string(#RRGGBB) |
— | メンバーカラー(HEX)。バッジ表示のスタイル決定に使用。詳細 → メンバーカラーバッジ設計書 |
colorName |
string |
— | メンバーカラーの表示名(例: ライトピンク)。未設定時は color の HEX 値をそのまま表示 |
leaderGenerationNumber |
number(正整数) |
— | リーダー在任時の「代」番号。関係性マップのリーダー継承エッジ算出に使用。詳細 → メンバー関係性マップ データ設計書 |
nickname |
string[] |
— | 愛称(複数バリエーションを持つメンバーがいるため配列)。詳細 → メンバー関係性マップ データ設計書 §9 |
joiningRoute |
string |
— | 加入ルート(オーディション名等)。詳細 → メンバー関係性マップ データ設計書 §10 |
latestStatus |
StatusEntry |
✅ | 最新の近況エントリ。詳細 → Amebaブログ機能 データ設計書 |
officialSns |
SnsEntry[] |
— | 公式 SNS リンク一覧 |
statusHistory |
StatusEntry[] |
— | 近況の変更履歴(新しい順)。保持ルールは §5 を参照 |
snsCheck |
SnsCheck |
— | 近況自動検索(sync-status)の直近チェック結果 |
youtubeChannel |
YoutubeChannel |
— | 個人 YouTube チャンネル情報。解決ロジックは §5 を参照 |
tiktokChannel |
TikTokChannel |
— | 個人 TikTok アカウント情報。解決ロジックは §5 を参照(#1478) |
profile.dob |
string |
✅ | 生年月日 |
profile.joinDate |
string(YYYY-MM-DD) |
✅ | 加入日。MemberReleaseLink の実質基準日算出に使用。詳細 → リリース データ設計書 |
profile.gradDate |
string | null |
✅ | 卒業日。現役メンバーは null |
profile.family |
string |
✅ | 出身グループ・ユニット等(自由記述) |
profile.birthplace |
string |
— | 出身地。ライブ機能の「ご当地ライブ」判定に使用(§6の Neon 側 Member と対)。詳細 → ライブ・フェス機能 テーブル設計書 |
profile.instructor |
string[] |
— | 師匠にあたるメンバーの id 一覧。詳細 → メンバー関係性マップ データ設計書 §「師弟関係」 |
StatusEntry インターフェース(Member.latestStatus / Member.statusHistory の要素)
export const StatusEntrySchema = z.object({
content: z.string(),
lastUpdated: z.string(),
sources: z.array(z.string()),
});
| フィールド | 型 | 説明 |
|---|---|---|
content |
string |
近況本文 |
lastUpdated |
string(YYYY-MM-DD) |
最終更新日(JST) |
sources |
string[] |
情報源 URL 一覧 |
SnsEntry インターフェース(Member.officialSns の要素)
export const SnsEntrySchema = z.object({
url: z.string(),
active: z.boolean(),
});
SnsCheck インターフェース(Member.snsCheck)
export const SnsCheckSchema = z.object({
status: z.enum(['ok', 'error', 'skipped', 'unknown']),
hasRecentUpdate: z.boolean(),
lastCheckedAt: z.string(),
reason: z.string().optional(),
lastPostAt: z.string().optional(),
});
| フィールド | 型 | 説明 |
|---|---|---|
status |
'ok' | 'error' | 'skipped' | 'unknown' |
直近の自動検索(Exa Search API)の実行結果 |
hasRecentUpdate |
boolean |
直近の検索で近況の更新が見つかったか |
lastCheckedAt |
string |
最終チェック日時 |
reason |
string |
status が error/skipped の場合の理由(任意) |
lastPostAt |
string |
検出した最新投稿の日時(任意) |
YoutubeChannel インターフェース(Member.youtubeChannel、types/youtube.ts)
export const YoutubeChannelSchema = z.object({
handle: z.string().min(1),
playlistId: z.string().min(1),
});
| フィールド | 型 | 説明 |
|---|---|---|
handle |
string |
YouTube チャンネルハンドル(@ 始まり) |
playlistId |
string |
ハンドルから解決されたアップロード動画プレイリスト ID(サーバー側で自動解決。§5参照) |
TikTokChannel インターフェース(Member.tiktokChannel、types/tiktok.ts)
export const TikTokChannelSchema = z.object({
username: z.string().min(1),
});
| フィールド | 型 | 説明 |
|---|---|---|
username |
string |
TikTok のユーザー名(@ を含まない。RapidAPI の unique_id パラメータにそのまま渡せる値) |
TikTok は RapidAPI(tiktok-scraper7.p.rapidapi.com)が unique_id(username)を直接指定して投稿取得できるため、YouTube の playlistId のようなサーバー側 API 解決は不要(§5参照)。詳細 → TikTok投稿機能 データ設計書
4. データファイル
members.json(Vercel Blob)
- Vercel Blob(
MEMBERS_BLOB_URL)で管理。ローカルにフリーズドコピーは持たない(読み取りは Blob URL へのfetch) - 更新経路:
PUT /api/members(メンバー詳細画面の編集フォーム)、sync-membersワークフロー(近況自動検索・Ameba同期)、各種scripts/patch/patch-*.ts(愛称・加入ルート・メンバーカラー・リーダー代・師匠情報等の一括投入)
5. PUT /api/members の業務ルール
app/api/members/route.ts が Blob への書き込みを一元的に担う。クライアントから送信されたペイロードの一部フィールドはサーバー側で上書き・算出され、クライアント値に依存しない。
5-1. statusHistory の保持ルール
latestStatus.contentが既存値から変化した場合のみ、新しいStatusEntryをstatusHistoryの先頭に追加する- 変化の有無に関わらず、保存前に以下の2条件でフィルタする(早い方を適用):
- 30日以内(
lastUpdatedが現在日から30日以内) - 100件以内(
slice(0, 100))
- 30日以内(
latestStatus.lastUpdatedは JST(Asia/Tokyo)でサーバー側が生成する。クライアントが送った値は無視される(#141)
5-2. youtubeChannel の解決ロジック
| 条件 | 動作 |
|---|---|
handle が空 |
youtubeChannel フィールドごと削除する |
handle が既存値と同じ |
既存の playlistId を引き継ぐ(YouTube API クォータ節約のため再解決しない) |
handle が新規・変更あり |
fetchPlaylistId(handle) で YouTube API から playlistId を解決する。解決失敗時は 422 エラーを返し保存しない |
5-3. tiktokChannel の解決ロジック(#1478)
| 条件 | 動作 |
|---|---|
username が空 |
tiktokChannel フィールドごと削除する |
username が指定あり |
トリムしてそのまま保存する(API 解決不要。§3参照) |
6. Neon 側「出身地照合用メンバー」(Member)
ライブ機能の「ご当地ライブ」判定でのみ使用される、Blob Member とは別の Prisma モデル。
model Member {
memberId String @id @db.VarChar(50)
birthplace String? @db.VarChar(255)
@@map("members")
}
memberIdは BlobMember.idと同値を Neon 側に複製したもの(DB レベルの FK 制約はない)birthplaceのみを保持する部分的なミラーであり、BlobMemberの完全な複製ではない- 詳細 → ライブ・フェス機能 テーブル設計書
7. ER 図
→ ER 図 を参照すること。Member はコア音楽データ・ライブ/フェス・メンバー関係性マップの各セクションに登場する。
8. 関連設計書
| 設計書 | 本書との関係 |
|---|---|
| Amebaブログ機能 データ設計書 | latestStatus/statusHistory の RSS 取得・Topページ新着判定の詳細 |
| メンバーカラーバッジ設計書 | color/colorName を用いたバッジスタイル算出ロジック |
| メンバー関係性マップ データ設計書 | nickname・joiningRoute・leaderGenerationNumber・profile.instructor の算出・表示ロジック |
| リリース データ設計書 | profile.joinDate/profile.gradDate を用いた MemberReleaseLink 紐付けロジック |
| ライブ・フェス機能 テーブル設計書 | Neon 側「出身地照合用メンバー」(Member)・ご当地ライブ判定 |
| TikTok投稿機能 データ設計書 | tiktokChannel を用いたOGメンバー個人アカウント収集ロジック |
改訂履歴
| 版 | 更新日 | 変更内容 |
|---|---|---|
| 1.1 | 2026-08-09 | OGメンバーのTikTok個人アカウント収集に伴い tiktokChannel フィールドを追加(§3・§5-3・§8)。YouTubeの youtubeChannel と異なりAPI側のID解決が不要なため username のみを保持する(#1478) |
| 1.0 | 2026-07-21 | 初版作成(#1379) |