コンテンツにスキップ

家族共有仕様 (FR-011)

項目内容
対象FRFR-011(家族共有)
優先度
ステータス詳細化済み
フェーズPhase 4

概要

ペットオーナーは家族メンバーを招待し、ペット単位でデータを共有できる。招待はメールアドレス指定または招待コード共有の2方式を提供する。共有メンバーにはリソース×CRUD単位の権限を付与でき、UX向上のためプリセット(閲覧のみ・編集可能・フルアクセス)を用意する。共有メンバーへの通知設定もメンバーごとにカスタマイズ可能。

前提条件(依存FR)

依存FR必要な機能用途
FR-001/002(認証)ユーザーアカウント招待先ユーザーの識別
FR-003(ペット管理)ペット登録共有対象のペット
FR-010(通知)通知設定共有メンバーへの通知配信

招待方式

メール招待

  1. オーナーが共有先のメールアドレスと権限プリセットを指定して招待を作成
  2. システムが招待メールを送信(招待リンク付き)
  3. 受信者がリンクをクリックして承認
  4. 未登録ユーザーの場合、アカウント登録後に自動で共有が有効化される

招待コード

  1. オーナーが権限プリセットを指定して招待コードを生成
  2. オーナーが任意の方法(LINE、口頭等)でコードを共有
  3. 受信者がアプリ内でコードを入力して承認

招待仕様

項目
コード形式英数字8文字(nanoid生成)
有効期限7日(メール・コード共通)
最大利用回数メール招待: 1回、コード招待: 1回
1ペットあたりの共有上限10人
1ペットあたりの有効な招待上限20件

権限モデル

リソース×CRUD

共有メンバーの権限はリソースごとにCRUD操作を制御する。

リソースキー対象データ
ペットプロフィールpet名前・品種・性別等の基本情報
体重記録weight体重記録
食事記録meal食事記録
通院履歴vetVisit通院記録
ワクチン記録vaccinationワクチン接種記録
体調ログhealthLog症状・体調記録
投薬管理medication投薬スケジュール・投薬ログ

操作: create / read / update / delete

  • ペットプロフィールの createdelete は常にオーナー専用(共有メンバーには付与不可)
  • 各リソースの操作を個別に ON/OFF できる

権限プリセット

プリセットキー説明
閲覧のみviewer全リソース read のみ
編集可能editor全リソース create/read/update。ペットは read/update
フルアクセスfull全リソース create/read/update/delete。ペットは read/update

プリセット適用後にリソース単位でカスタマイズも可能(プリセットはあくまで初期値)。

プリセット詳細

viewer:

{
"pet": ["read"],
"weight": ["read"],
"meal": ["read"],
"vetVisit": ["read"],
"vaccination": ["read"],
"healthLog": ["read"],
"medication": ["read"]
}

editor:

{
"pet": ["read", "update"],
"weight": ["create", "read", "update"],
"meal": ["create", "read", "update"],
"vetVisit": ["create", "read", "update"],
"vaccination": ["create", "read", "update"],
"healthLog": ["create", "read", "update"],
"medication": ["create", "read", "update"]
}

full:

{
"pet": ["read", "update"],
"weight": ["create", "read", "update", "delete"],
"meal": ["create", "read", "update", "delete"],
"vetVisit": ["create", "read", "update", "delete"],
"vaccination": ["create", "read", "update", "delete"],
"healthLog": ["create", "read", "update", "delete"],
"medication": ["create", "read", "update", "delete"]
}

通知設定(共有メンバー向け)

共有メンバーにもペット×通知タイプ単位で通知設定を付与する。既存の notification_settings テーブルを共用する(共有成立時にデフォルト設定を自動作成)。

通知プリセット

プリセットキー説明
すべて受信all全通知タイプ有効、プッシュ ON
重要のみimportant_onlyワクチンのみ有効、プッシュ ON
受信しないnone全通知タイプ無効

招待時にオーナーが通知プリセットを選択する。共有メンバーは承認後に自分の通知設定を自由に変更できる。

APIエンドポイント

全エンドポイントで認証必須(__Host-session Cookie)。

招待管理

メソッドパス概要
POST/api/pets/:petId/shares/invitations招待作成
GET/api/pets/:petId/shares/invitations招待一覧(オーナー用)
DELETE/api/pets/:petId/shares/invitations/:invitationId招待取消
POST/api/shares/accept招待承認(メールリンク経由)
POST/api/shares/accept-code招待承認(コード入力)

共有管理

メソッドパス概要
GET/api/pets/:petId/shares共有メンバー一覧
PATCH/api/pets/:petId/shares/:shareId権限更新
DELETE/api/pets/:petId/shares/:shareId共有解除(オーナーが解除)
DELETE/api/shares/:shareId共有離脱(メンバーが自主離脱)
GET/api/shared-pets自分に共有されているペット一覧

プリセット

メソッドパス概要
GET/api/share-presets/permissions権限プリセット一覧
GET/api/share-presets/notifications通知プリセット一覧

POST /api/pets/:petId/shares/invitations — 招待作成

// リクエスト(メール招待)
{
"type": "email",
"email": "family@example.com",
"permissionPreset": "editor",
"notificationPreset": "all"
}
// リクエスト(コード招待)
{
"type": "code",
"permissionPreset": "viewer",
"notificationPreset": "important_only"
}
フィールド必須バリデーション
typevarcharemail / code
emailvarchartype=email 時必須。有効なメールアドレス形式
permissionPresetvarcharviewer / editor / full
notificationPresetvarchar-all / important_only / none(default: all
  • 成功時: 201 Created
  • 自分自身のメールアドレス: 422 Unprocessable Entity
  • 既に共有済みのユーザー: 409 Conflict
  • 共有上限(10人)超過: 409 Conflict
  • 有効な招待上限(20件)超過: 409 Conflict
  • ペットが存在しない / 自分のペットでない: 404 Not Found
// 成功レスポンス(メール招待)
{
"data": {
"id": 1,
"type": "email",
"email": "family@example.com",
"permissionPreset": "editor",
"status": "pending",
"expiresAt": "2026-03-01T10:00:00Z",
"createdAt": "2026-02-22T10:00:00Z"
}
}
// 成功レスポンス(コード招待)
{
"data": {
"id": 2,
"type": "code",
"code": "Ab3xK9mQ",
"permissionPreset": "viewer",
"status": "pending",
"expiresAt": "2026-03-01T10:00:00Z",
"createdAt": "2026-02-22T10:00:00Z"
}
}

GET /api/pets/:petId/shares/invitations — 招待一覧

オーナーが自分のペットに対する招待一覧を取得する。ステータスでフィルタ可能。

パラメータ必須デフォルト説明
statusvarchar--pending / accepted / expired / revoked
  • 成功時: 200 OK

DELETE /api/pets/:petId/shares/invitations/:invitationId — 招待取消

  • 成功時: 204 No Content
  • pending 以外のステータス: 409 Conflict

POST /api/shares/accept — 招待承認(メールリンク経由)

{
"token": "invitation-token-from-email-link"
}
  • 成功時: 200 OK、共有情報を返す
  • トークン無効 / 期限切れ: 422 Unprocessable Entity
  • 未登録ユーザー: 401 Unauthorized(登録を促すメッセージを返す)

POST /api/shares/accept-code — 招待承認(コード入力)

{
"code": "Ab3xK9mQ"
}
  • 成功時: 200 OK、共有情報を返す
  • コード無効 / 期限切れ: 422 Unprocessable Entity
// 承認成功レスポンス
{
"data": {
"id": 1,
"petId": 1,
"petName": "そうにゃ",
"ownerName": "田中太郎",
"permissions": {
"pet": ["read", "update"],
"weight": ["create", "read", "update"],
"meal": ["create", "read", "update"],
"vetVisit": ["create", "read", "update"],
"vaccination": ["create", "read", "update"],
"healthLog": ["create", "read", "update"],
"medication": ["create", "read", "update"]
},
"createdAt": "2026-02-22T10:00:00Z"
}
}

GET /api/pets/:petId/shares — 共有メンバー一覧

{
"data": [
{
"id": 1,
"userId": 5,
"email": "family@example.com",
"permissions": {
"pet": ["read", "update"],
"weight": ["create", "read", "update"],
"meal": ["create", "read", "update"],
"vetVisit": ["create", "read", "update"],
"vaccination": ["create", "read", "update"],
"healthLog": ["create", "read", "update"],
"medication": ["create", "read", "update"]
},
"createdAt": "2026-02-22T10:00:00Z"
}
]
}

PATCH /api/pets/:petId/shares/:shareId — 権限更新

// リクエスト(プリセットで一括変更)
{
"permissionPreset": "full"
}
// リクエスト(リソース単位でカスタマイズ)
{
"permissions": {
"weight": ["create", "read", "update", "delete"],
"meal": ["read"]
}
}
  • permissionPresetpermissions は排他(両方指定時は 422
  • permissions は指定したリソースのみ上書き(未指定リソースは変更なし)
  • 成功時: 200 OK、更新後の共有情報を返す

DELETE /api/pets/:petId/shares/:shareId — 共有解除

オーナーが共有メンバーを解除する。

  • 成功時: 204 No Content
  • 該当共有の通知設定も削除される

DELETE /api/shares/:shareId — 共有離脱

共有メンバーが自分から共有を離脱する。

  • 成功時: 204 No Content

GET /api/shared-pets — 共有されているペット一覧

自分に共有されているペットの一覧を返す。

{
"data": [
{
"shareId": 1,
"pet": {
"id": 1,
"name": "そうにゃ",
"breedId": 3,
"sex": "female",
"photoUrl": null
},
"owner": {
"id": 2,
"email": "owner@example.com"
},
"permissions": { "..." : "..." },
"createdAt": "2026-02-22T10:00:00Z"
}
]
}

GET /api/share-presets/permissions — 権限プリセット一覧

{
"data": [
{
"key": "viewer",
"label": "閲覧のみ",
"description": "すべてのデータを閲覧できます",
"permissions": { "..." : "..." }
},
{
"key": "editor",
"label": "編集可能",
"description": "データの追加・編集ができます",
"permissions": { "..." : "..." }
},
{
"key": "full",
"label": "フルアクセス",
"description": "削除を含むすべての操作ができます",
"permissions": { "..." : "..." }
}
]
}

GET /api/share-presets/notifications — 通知プリセット一覧

{
"data": [
{
"key": "all",
"label": "すべて受信",
"description": "すべての通知をプッシュで受信します"
},
{
"key": "important_only",
"label": "重要のみ",
"description": "ワクチンのリマインドのみ受信します"
},
{
"key": "none",
"label": "受信しない",
"description": "通知を受信しません"
}
]
}

認可ルール

  • 全エンドポイントで認証必須
  • 招待の作成・一覧・取消: ペットオーナーのみ
  • 共有メンバー一覧: ペットオーナーのみ
  • 権限更新・共有解除: ペットオーナーのみ
  • 共有離脱: 共有メンバー自身のみ
  • 共有ペットのデータアクセス: pet_shares.permissions に基づいて制御
  • 権限がない操作を試みた場合: 403 Forbidden
  • 他ユーザーのペットID / 共有IDを指定した場合: 404 Not Found

既存エンドポイントへの影響

共有メンバーが既存のペット関連エンドポイント(体重記録、食事記録等)にアクセスする場合:

  1. pet_shares テーブルで該当ペットへの共有が存在するか確認
  2. 該当リソース×操作の権限があるか permissions JSONを照合
  3. 権限がある場合のみ処理を実行、ない場合は 403 Forbidden

DBテーブル

pet_shares

カラム備考
idserial主キー
pet_idintegerFK → pets.id
user_idintegerFK → users.id(共有先ユーザー)
permissionsjsonbリソース×CRUD権限
invited_byintegerFK → users.id(招待者)
created_attimestamptz作成日時
updated_attimestamptz更新日時
  • (pet_id, user_id) に UNIQUE 制約
  • ペットオーナー自身を共有先にすることはできない

share_invitations

カラム備考
idserial主キー
pet_idintegerFK → pets.id
inviter_idintegerFK → users.id
invite_typevarcharemail / code
emailvarchar招待先メールアドレス(nullable、email タイプ時のみ)
codevarchar招待コード(nullable、code タイプ時のみ、UNIQUE)
tokenvarcharメール招待リンク用トークン(nullable、email タイプ時のみ)
permissionsjsonb付与する権限(プリセット展開後の実体)
notification_presetvarchar通知プリセットキー
statusvarcharpending / accepted / expired / revoked
expires_attimestamptz有効期限
created_attimestamptz作成日時
  • 期限切れの pending 招待は1日1回のバッチで expired に更新

エッジケース

  • 未登録ユーザーへのメール招待 → 招待メールは送信する。未登録ユーザーが新規登録後に招待を承認すると共有が成立する
  • 自分自身への招待 → 422 Unprocessable Entity
  • 既に共有済みのユーザーへの再招待 → 409 Conflict
  • 招待コードを複数人に共有した場合 → 最初の1人のみ承認可能(1回限り)
  • オーナーがペットを論理削除した場合 → 既存の共有は保持するが、共有メンバーからのアクセスは 404 を返す
  • 共有メンバーのアカウントが停止された場合 → 共有は保持するが、該当ユーザーはログインできないためアクセス不可
  • 権限変更後のセッション → 即座に反映(リクエストごとに権限を確認)
  • 共有解除後 → 該当ペットの通知設定も削除される
  • オーナーがアカウント削除された場合 → ペットの共有も全て無効化される

拡張予定(現時点ではスコープ外)

  • オーナー権限の移譲(オーナー変更)
  • 共有メンバーの招待権限(メンバーが別のメンバーを招待)
  • 共有グループ(家族単位での一括管理)
  • 共有メンバーの活動ログ(誰がいつ何を変更したか)
  • 共有ペットへのコメント・メッセージ機能

検証方法

  • メール招待でペットが共有され、共有メンバーがデータを閲覧できること
  • コード招待でペットが共有されること
  • 権限プリセット(viewer/editor/full)が正しく適用されること
  • 権限のないリソース・操作に 403 が返ること
  • 権限のカスタマイズ(プリセット後の個別変更)が反映されること
  • 共有上限(10人)を超えた場合にエラーになること
  • 有効期限切れの招待が承認できないこと
  • オーナーが共有を解除でき、メンバーのアクセスが不可になること
  • メンバーが自主離脱でき、ペットが一覧から消えること
  • 未登録ユーザーへの招待が新規登録後に承認できること
  • 共有メンバーに通知設定が自動作成されること
  • 共有解除後に通知設定が削除されること
  • 自分自身への招待が拒否されること
  • 他ユーザーのペットへの招待操作が 404 を返すこと