お知らせ管理

概要説明

お知らせ管理機能により、管理者はシステム全体のお知らせを作成、表示、更新、削除することができます。これらのお知らせは、管理者とシステムユーザー間のコミュニケーションチャネルとして機能し、重要な情報、更新、または通知を提供します。この機能はテキストや画像を含むリッチコンテンツをサポートし、クラウドストレージへの画像アップロードに専用の機能を提供しています。

アクティビティ図

---
config:
  theme: base
  layout: dagre
  flowchart:
    curve: linear
    htmlLabels: true
  themeVariables:
    edgeLabelBackground: "transparent"
---
flowchart TD
    %% Main components
    AdminUser[管理者ユーザー]
    AdminInterface[お知らせ管理インターフェース]
    ActionSelect{アクション選択}
    
    %% Action components
    ViewAnnouncements[お知らせ一覧]
    AnnouncementDetails[お知らせ詳細]
    CreateForm[お知らせ作成フォーム]
    EditForm[お知らせ編集フォーム]
    DeleteConfirm{削除確認}
    
    %% Processing components
    EnterContent[コンテンツ入力]
    EditContent[コンテンツ編集]
    ImageAddCheck{画像追加?}
    ImageChangeCheck{画像追加/変更?}
    UploadImages[画像アップロード]
    SaveAnnouncement[お知らせ保存]
    DeleteFromDB[データベースから削除]
    
    %% Flow connections with numbered steps
    AdminUser --- Step1[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>1</span>
            <p style='margin-top: 8px'>インターフェースにアクセス</p>
        </div>
    ]
    Step1 --> AdminInterface
    
    AdminInterface --- Step2[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>2</span>
            <p style='margin-top: 8px'>アクション選択</p>
        </div>
    ]
    Step2 --> ActionSelect
    
    ActionSelect --- Step3A[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #6699cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>3A</span>
            <p style='margin-top: 8px'>お知らせ表示</p>
        </div>
    ]
    Step3A --> ViewAnnouncements
    
    ActionSelect --- Step3B[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #99cc66 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>3B</span>
            <p style='margin-top: 8px'>詳細表示</p>
        </div>
    ]
    Step3B --> AnnouncementDetails
    
    ActionSelect --- Step3C[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc66cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>3C</span>
            <p style='margin-top: 8px'>お知らせ作成</p>
        </div>
    ]
    Step3C --> CreateForm
    
    ActionSelect --- Step3D[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>3D</span>
            <p style='margin-top: 8px'>お知らせ更新</p>
        </div>
    ]
    Step3D --> EditForm
    
    ActionSelect --- Step3E[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc6666 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>3E</span>
            <p style='margin-top: 8px'>お知らせ削除</p>
        </div>
    ]
    Step3E --> DeleteConfirm
    
    CreateForm --- Step4C[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc66cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>4C</span>
            <p style='margin-top: 8px'>コンテンツ入力</p>
        </div>
    ]
    Step4C --> EnterContent
    
    EditForm --- Step4D[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>4D</span>
            <p style='margin-top: 8px'>コンテンツ編集</p>
        </div>
    ]
    Step4D --> EditContent
    
    EnterContent --- Step5C[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc66cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>5C</span>
            <p style='margin-top: 8px'>画像確認</p>
        </div>
    ]
    Step5C --> ImageAddCheck
    
    EditContent --- Step5D[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>5D</span>
            <p style='margin-top: 8px'>画像確認</p>
        </div>
    ]
    Step5D --> ImageChangeCheck
    
    ImageAddCheck --- Step6C1[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc66cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>6C</span>
            <p style='margin-top: 8px'>画像アップロード</p>
        </div>
    ]
    Step6C1 -->|はい| UploadImages
    
    ImageChangeCheck --- Step6D1[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>6D</span>
            <p style='margin-top: 8px'>画像アップロード</p>
        </div>
    ]
    Step6D1 -->|はい| UploadImages
    
    ImageAddCheck --- Step6C2[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc66cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>6C</span>
            <p style='margin-top: 8px'>アップロードスキップ</p>
        </div>
    ]
    Step6C2 -->|いいえ| SaveAnnouncement
    
    ImageChangeCheck --- Step6D2[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc9966 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>6D</span>
            <p style='margin-top: 8px'>アップロードスキップ</p>
        </div>
    ]
    Step6D2 -->|いいえ| SaveAnnouncement
    
    UploadImages --- Step7CD[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #9966cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>7</span>
            <p style='margin-top: 8px'>保存</p>
        </div>
    ]
    Step7CD --> SaveAnnouncement
    
    SaveAnnouncement --- Step8CD[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #9966cc !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>8</span>
            <p style='margin-top: 8px'>インターフェースに戻る</p>
        </div>
    ]
    Step8CD --> AdminInterface
    
    DeleteConfirm --- Step4E[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc6666 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>4E</span>
            <p style='margin-top: 8px'>レコード削除</p>
        </div>
    ]
    Step4E -->|確認| DeleteFromDB
    
    DeleteFromDB --- Step5E[
        <div style='text-align: center'>
            <span style='display: inline-block; background-color: #cc6666 !important; color:white; width: 28px; height: 28px; line-height: 28px; border-radius: 50%; font-weight: bold'>5E</span>
            <p style='margin-top: 8px'>インターフェースに戻る</p>
        </div>
    ]
    Step5E --> AdminInterface
    
    DeleteConfirm -.->|キャンセル| AdminInterface
    
    %% Styling
    style AdminUser fill:#e6f3ff,stroke:#0066cc,stroke-width:2px
    style AdminInterface fill:#e6f3ff,stroke:#0066cc,stroke-width:2px
    style ActionSelect fill:#f5f0ff,stroke:#9966cc,stroke-width:2px
    style ViewAnnouncements fill:#f0f8e6,stroke:#339933,stroke-width:2px
    style AnnouncementDetails fill:#f0f8e6,stroke:#339933,stroke-width:2px
    style CreateForm fill:#f0f8e6,stroke:#339933,stroke-width:2px
    style EditForm fill:#f0f8e6,stroke:#339933,stroke-width:2px
    style EnterContent fill:#fff0f5,stroke:#cc6699,stroke-width:2px
    style EditContent fill:#fff0f5,stroke:#cc6699,stroke-width:2px
    style ImageAddCheck fill:#f5f0ff,stroke:#9966cc,stroke-width:2px
    style ImageChangeCheck fill:#f5f0ff,stroke:#9966cc,stroke-width:2px
    style UploadImages fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
    style SaveAnnouncement fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
    style DeleteConfirm fill:#f5f0ff,stroke:#9966cc,stroke-width:2px
    style DeleteFromDB fill:#ffe6cc,stroke:#ff9900,stroke-width:2px
    
    style Step1 fill:transparent,stroke:transparent,stroke-width:1px
    style Step2 fill:transparent,stroke:transparent,stroke-width:1px
    style Step3A fill:transparent,stroke:transparent,stroke-width:1px
    style Step3B fill:transparent,stroke:transparent,stroke-width:1px
    style Step3C fill:transparent,stroke:transparent,stroke-width:1px
    style Step3D fill:transparent,stroke:transparent,stroke-width:1px
    style Step3E fill:transparent,stroke:transparent,stroke-width:1px
    style Step4C fill:transparent,stroke:transparent,stroke-width:1px
    style Step4D fill:transparent,stroke:transparent,stroke-width:1px
    style Step4E fill:transparent,stroke:transparent,stroke-width:1px
    style Step5C fill:transparent,stroke:transparent,stroke-width:1px
    style Step5D fill:transparent,stroke:transparent,stroke-width:1px
    style Step5E fill:transparent,stroke:transparent,stroke-width:1px
    style Step6C1 fill:transparent,stroke:transparent,stroke-width:1px
    style Step6C2 fill:transparent,stroke:transparent,stroke-width:1px
    style Step6D1 fill:transparent,stroke:transparent,stroke-width:1px
    style Step6D2 fill:transparent,stroke:transparent,stroke-width:1px
    style Step7CD fill:transparent,stroke:transparent,stroke-width:1px
    style Step8CD fill:transparent,stroke:transparent,stroke-width:1px

API: お知らせ管理API

ケースドキュメント

ケース1: お知らせ一覧の取得

説明

管理者はシステム内のすべてのお知らせのページ分けされたリストを取得します。

シーケンス図

sequenceDiagram
    participant Admin as 管理者
    participant API as お知らせコントローラー
    participant Repository as お知らせリポジトリ
    participant DB as データベース

    Note over Admin,DB: ステップ1: お知らせリストのリクエスト
    Admin->>API: GET /api/admin/announcements (クエリパラメータ付き)
    
    Note over API,Repository: ステップ2: お知らせの取得
    API->>Repository: getPaginated(params)
    Repository->>DB: お知らせのクエリ
    DB-->>Repository: 該当お知らせを返す
    Repository-->>API: ページ分けされた結果を返す
    
    Note over API,Admin: ステップ3: お知らせリストの返却
    API-->>Admin: 200 OK (お知らせコレクション)

ステップ

ステップ1: お知らせリストのリクエスト

  • 説明: 管理者はオプションのページネーションを使用してお知らせのリストをリクエスト
  • リクエスト: GET /api/admin/announcements
  • クエリパラメータ:
    • per_page: ページあたりのアイテム数
    • page: ページ番号
    • その他の潜在的なフィルタ(実装によって異なる)

ステップ2: お知らせの取得

  • 説明: システムはデータベースからお知らせを取得
  • アクション:
    • ページネーションと任意のフィルタを適用
    • お知らせを順番に取得(おそらく最新のものが最初)

ステップ3: お知らせリストの返却

  • 説明: ページ分けされたお知らせリストを返す
  • レスポンス:
    • 成功: 200 OKとお知らせコレクションおよびページネーションメタデータ
    • エラー: 適切なエラーメッセージ

エラー処理

  • ログ
    • お知らせリスト取得失敗はアプリケーションログに記録
  • エラー詳細:
    ステータスコード エラーメッセージ 説明
    400 例外メッセージを含む一般的なエラー 予期しないエラーが発生した場合

ケース2: 特定のお知らせの表示

説明

管理者が特定のお知らせの詳細を表示します。

シーケンス図

sequenceDiagram
    participant Admin as 管理者
    participant API as お知らせコントローラー
    participant Model as お知らせモデル
    participant DB as データベース

    Note over Admin,DB: ステップ1: お知らせ詳細のリクエスト
    Admin->>API: GET /api/admin/announcements/{id}
    
    Note over API,Model: ステップ2: お知らせの取得
    API->>Model: IDでお知らせをロード
    Model->>DB: IDによるクエリ
    DB-->>Model: お知らせデータを返す
    
    Note over API,Admin: ステップ3: お知らせ詳細の返却
    API-->>Admin: 200 OK (お知らせリソース)

ステップ

ステップ1: お知らせ詳細のリクエスト

  • 説明: 管理者が特定のお知らせの詳細をリクエスト
  • リクエスト: GET /api/admin/announcements/{id}

ステップ2: お知らせの取得

  • 説明: システムはIDによってお知らせを取得
  • アクション: 完全なお知らせレコードを取得

ステップ3: お知らせ詳細の返却

  • 説明: お知らせの詳細を返す
  • レスポンス:
    • 成功: 200 OKとお知らせリソース
    • エラー: お知らせが見つからない場合は適切なエラーメッセージ

エラー処理

  • ログ
    • エラーが発生した場合はログに記録
  • エラー詳細:
    ステータスコード エラーメッセージ 説明
    404 "お知らせが見つかりません" 要求されたお知らせが存在しない場合
    400 例外メッセージを含む一般的なエラー 予期しないエラーが発生した場合

ケース3: 新しいお知らせの作成

説明

管理者がシステムに新しいお知らせを作成します。

シーケンス図

sequenceDiagram
    participant Admin as 管理者
    participant API as お知らせコントローラー
    participant Repository as お知らせリポジトリ
    participant Event as お知らせ作成イベント
    participant DB as データベース

    Note over Admin,DB: ステップ1: お知らせ作成の送信
    Admin->>API: POST /api/admin/announcements (announcement_data)
    
    Note over API,API: ステップ2: 入力検証
    API->>API: リクエストデータを検証
    
    Note over API,Repository: ステップ3: お知らせ作成
    API->>Repository: create(validated_data)
    Repository->>DB: 新しいお知らせを保存
    DB-->>Repository: 作成されたお知らせを返す
    
    Note over API,Event: ステップ4: イベント発火
    API->>Event: event(AnnouncementCreated)
    
    Note over API,Admin: ステップ5: レスポンス返却
    Repository-->>API: お知らせデータを返す
    API-->>Admin: 201 Created (お知らせリソース)

ステップ

ステップ1: お知らせ作成の送信

  • 説明: 管理者が新しいお知らせを作成するフォームを送信
  • リクエスト: POST /api/admin/announcements
  • 必須フィールド:
    • title: お知らせのタイトル
    • content: お知らせの内容(HTMLを含む場合あり)
    • 実装によって必要なその他のフィールド

ステップ2: 入力検証

  • 説明: システムがすべての入力データを検証
  • アクション: StoreAnnouncementRequestから検証ルールを実行
  • 発生しうるエラー: 検証失敗

ステップ3: お知らせ作成

  • 説明: システムが新しいお知らせを作成
  • アクション: データベースにお知らせレコードを保存

ステップ4: イベント発火

  • 説明: システムがお知らせ作成イベントを発火
  • アクション: 潜在的なリスナーのためにAnnouncementCreatedイベントを発火
  • 目的: システムの他の部分が新しいお知らせに反応できるようにする

ステップ5: レスポンス返却

  • 説明: 作成されたお知らせデータを返す
  • レスポンス:
    • 成功: 201 Createdとお知らせデータ
    • エラー: 適切なエラーメッセージ

エラー処理

  • ログ
    • お知らせ作成失敗はアプリケーションログに記録
  • エラー詳細:
    ステータスコード エラーメッセージ 説明
    400 "error.announcement.create" お知らせ作成が失敗した場合

ケース4: お知らせの更新

説明

管理者が既存のお知らせを更新します。

シーケンス図

sequenceDiagram
    participant Admin as 管理者
    participant API as お知らせコントローラー
    participant Repository as お知らせリポジトリ
    participant Event as お知らせ更新イベント
    participant DB as データベース

    Note over Admin,DB: ステップ1: お知らせ更新の送信
    Admin->>API: PUT /api/admin/announcements/{id} (updated_data)
    
    Note over API,API: ステップ2: 入力検証
    API->>API: リクエストデータを検証
    
    Note over API,Repository: ステップ3: お知らせ更新
    API->>Repository: update(id, validated_data)
    Repository->>DB: お知らせレコードを更新
    DB-->>Repository: 更新されたお知らせを返す
    
    Note over API,Admin: ステップ4: レスポンス返却
    Repository-->>API: お知らせデータを返す
    API-->>Admin: 200 OK (お知らせリソース)

ステップ

ステップ1: お知らせ更新の送信

  • 説明: 管理者がお知らせ情報を更新するフォームを送信
  • リクエスト: PUT /api/admin/announcements/{id}
  • 更新可能フィールド:
    • title: お知らせのタイトル
    • content: お知らせの内容
    • status: お知らせのステータス
    • 実装によって許可されるその他のフィールド

ステップ2: 入力検証

  • 説明: システムがすべての入力データを検証
  • アクション: UpdateAnnouncementRequestから検証ルールを実行
  • 発生しうるエラー: 検証失敗

ステップ3: お知らせ更新

  • 説明: システムがお知らせ情報を更新
  • アクション: 新しいデータでお知らせレコードを更新

ステップ4: レスポンス返却

  • 説明: 更新されたお知らせデータを返す
  • レスポンス:
    • 成功: 200 OKと更新されたお知らせデータ
    • エラー: 適切なエラーメッセージ

エラー処理

  • ログ
    • お知らせ更新失敗はアプリケーションログに記録
  • エラー詳細:
    ステータスコード エラーメッセージ 説明
    400 "error.announcement.update" お知らせ更新が失敗した場合

ケース5: お知らせの削除

説明

管理者がシステムからお知らせを削除します。

シーケンス図

sequenceDiagram
    participant Admin as 管理者
    participant API as お知らせコントローラー
    participant Model as お知らせモデル
    participant DB as データベース

    Note over Admin,DB: ステップ1: お知らせ削除リクエスト
    Admin->>API: DELETE /api/admin/announcements/{id}
    
    Note over API,Model: ステップ2: お知らせ削除
    API->>Model: delete()
    Model->>DB: お知らせレコードを削除
    DB-->>Model: 削除を確認
    
    Note over API,Admin: ステップ3: レスポンス返却
    API-->>Admin: 200 OK (成功メッセージ)

ステップ

ステップ1: お知らせ削除リクエスト

  • 説明: 管理者がお知らせを削除するリクエスト
  • リクエスト: DELETE /api/admin/announcements/{id}

ステップ2: お知らせ削除

  • 説明: システムがデータベースからお知らせを削除
  • アクション: お知らせレコードを削除

ステップ3: レスポンス返却

  • 説明: 削除成功を確認
  • レスポンス:
    • 成功: 200 OKと成功メッセージ
    • エラー: 削除が失敗した場合は適切なエラーメッセージ

エラー処理

  • ログ
    • お知らせ削除失敗はログに記録
  • エラー詳細:
    ステータスコード エラーメッセージ 説明
    400 "error.announcement.delete" 削除が失敗した場合

ケース6: お知らせ内容用の画像アップロード

説明

管理者がお知らせ内容で使用する画像をアップロードします。

シーケンス図

sequenceDiagram
    participant Admin as 管理者
    participant API as お知らせコントローラー
    participant Storage as クラウドストレージ
    participant DB as データベース

    Note over Admin,DB: ステップ1: 画像アップロードの送信
    Admin->>API: POST /api/admin/announcements/upload-image (画像ファイル)
    
    Note over API,API: ステップ2: 画像検証
    API->>API: 画像ファイルを検証
    
    Note over API,Storage: ステップ3: 画像処理と保存
    API->>API: ユニークなファイル名を生成
    API->>Storage: クラウドストレージにアップロード
    Storage-->>API: 画像URLを返す
    
    Note over API,Admin: ステップ4: レスポンス返却
    API-->>Admin: 200 OK (画像URL)

ステップ

ステップ1: 画像アップロードの送信

  • 説明: 管理者がお知らせ用の画像ファイルをアップロード
  • リクエスト: POST /api/admin/announcements/upload-image
  • 必須フィールド:
    • image: 画像ファイル(multipart/form-data)

ステップ2: 画像検証

  • 説明: システムがアップロードされた画像を検証
  • アクション: ファイルが存在し、要件を満たしているか確認
  • 発生しうるエラー: ファイルがない、無効なファイルタイプ、ファイルが大きすぎる

ステップ3: 画像処理と保存

  • 説明: システムが画像を処理して保存
  • アクション:
    • ユニークなファイル名を生成(UUIDを使用)
    • ストレージパスを決定
    • コンテンツタイプとキャッシングメタデータを設定
    • パブリック可視性でクラウドストレージにアップロード

ステップ4: レスポンス返却

  • 説明: 保存された画像へのURLを返す
  • レスポンス:
    • 成功: 200 OKと画像URLおよびファイル名
    • エラー: 適切なエラーメッセージ

エラー処理

  • ログ
    • 画像アップロード失敗はアプリケーションログに記録
  • エラー詳細:
    ステータスコード エラーメッセージ 説明
    400 "error.announcement.no_image_uploaded" 画像ファイルが提供されていない場合
    500 "error.announcement.upload_image_failed" クラウドストレージへのアップロードが失敗した場合
    400 例外メッセージを含む一般的なエラー アップロード中のその他のエラー

追加メモ

  • お知らせ管理機能は、お知らせのコンテンツフィールドにHTMLを使用したリッチコンテンツをサポートしています
  • 画像はGoogle Cloud Storageでホストされ、お知らせでの表示のためにパブリックアクセスが可能です
  • クラウドストレージでの衝突を防ぐため、UUIDを使用してユニークなファイル名が生成されます
  • 画像アセットの最適なパフォーマンスのために、適切なコンテンツタイプとキャッシュ制御ヘッダーが設定されています
  • お知らせはアプリケーションの他の部分で監視できるシステムイベントをトリガーする場合があります
  • エラー処理には、トラブルシューティング用のスタックトレースを含む詳細なログ記録が含まれています

関連データベーステーブルとフィールド

erDiagram
    announcements {
        bigint id PK "主キー"
        string title "お知らせタイトル"
        text content "お知らせ内容(HTML許可)"
        bigint created_by FK "users.idへの参照"
        string status "お知らせステータス(例:下書き、公開済み)"
        timestamp created_at "レコード作成タイムスタンプ"
        timestamp updated_at "レコード最終更新タイムスタンプ"
    }
    users {
        bigint id PK "主キー"
        string name "ユーザーのフルネーム"
        string email "ユーザーのメールアドレス(一意)"
        timestamp created_at "アカウント作成タイムスタンプ"
        timestamp updated_at "最終更新タイムスタンプ"
    }

    announcements }|--|| users : created_by