クラス図・ER図

データモデル(ORMエンティティ)の関連と、バックエンドの主要クラス構成を示します。

1. データモデル(ER図)

erDiagram
  USER ||--o{ SERVER : "owns(owner_id)"
  USER ||--o{ SERVER_MEMBER : "belongs"
  SERVER ||--o{ SERVER_MEMBER : "has"
  SERVER ||--o{ CHANNEL : "has"
  SERVER ||--o{ SERVER_INVITE : "issues"
  USER ||--o{ SERVER_INVITE : "creates"
  CHANNEL ||--o{ MESSAGE : "contains"
  DM_CHANNEL ||--o{ DM_MEMBER : "has"
  USER ||--o{ DM_MEMBER : "joins"
  DM_CHANNEL ||--o{ MESSAGE : "contains"
  USER ||--o{ MESSAGE : "authors"
  MESSAGE ||--o{ MESSAGE : "thread(parent_id)"
  MESSAGE ||--o{ REACTION : "has"
  USER ||--o{ REACTION : "reacts"
  MESSAGE ||--o{ ATTACHMENT : "has"
  USER ||--o{ ATTACHMENT : "uploads"
  USER ||--o{ READ_STATE : "tracks"

  USER {
    int id PK
    string username UK
    string display_name
    string password_hash
    string avatar_url
    string status_message
    bool is_active
    datetime created_at
  }
  SERVER {
    int id PK
    string name
    string icon_url
    int owner_id FK
    datetime created_at
  }
  SERVER_MEMBER {
    int id PK
    int server_id FK
    int user_id FK
    string role "admin/moderator/member"
    datetime joined_at
  }
  SERVER_INVITE {
    int id PK
    int server_id FK
    string code UK
    int created_by FK
    int max_uses
    int uses
    datetime expires_at
    bool is_revoked
  }
  CHANNEL {
    int id PK
    int server_id FK
    string name
    string topic
    string type "text"
    int position
  }
  DM_CHANNEL {
    int id PK
    bool is_group
    string name
    datetime created_at
  }
  DM_MEMBER {
    int id PK
    int dm_channel_id FK
    int user_id FK
  }
  MESSAGE {
    int id PK
    int channel_id FK
    int dm_channel_id FK
    int author_id FK
    string content
    int parent_id FK
    datetime edited_at
    bool is_deleted
    datetime created_at
  }
  REACTION {
    int id PK
    int message_id FK
    int user_id FK
    string emoji
  }
  ATTACHMENT {
    int id PK
    int message_id FK
    int uploader_id FK
    string filename
    string stored_name
    string content_type
    int size
  }
  READ_STATE {
    int id PK
    int user_id FK
    int channel_id FK
    int dm_channel_id FK
    int last_read_message_id
  }
        
erDiagram
  USER ||--o{ SERVER : "owns(owner_id)"
  USER ||--o{ SERVER_MEMBER : "belongs"
  SERVER ||--o{ SERVER_MEMBER : "has"
  SERVER ||--o{ CHANNEL : "has"
  SERVER ||--o{ SERVER_INVITE : "issues"
  USER ||--o{ SERVER_INVITE : "creates"
  CHANNEL ||--o{ MESSAGE : "contains"
  DM_CHANNEL ||--o{ DM_MEMBER : "has"
  USER ||--o{ DM_MEMBER : "joins"
  DM_CHANNEL ||--o{ MESSAGE : "contains"
  USER ||--o{ MESSAGE : "authors"
  MESSAGE ||--o{ MESSAGE : "thread(parent_id)"
  MESSAGE ||--o{ REACTION : "has"
  USER ||--o{ REACTION : "reacts"
  MESSAGE ||--o{ ATTACHMENT : "has"
  USER ||--o{ ATTACHMENT : "uploads"
  USER ||--o{ READ_STATE : "tracks"

  USER {
    int id PK
    string username UK
    string display_name
    string password_hash
    string avatar_url
    string status_message
    bool is_active
    datetime created_at
  }
  SERVER {
    int id PK
    string name
    string icon_url
    int owner_id FK
    datetime created_at
  }
  SERVER_MEMBER {
    int id PK
    int server_id FK
    int user_id FK
    string role "admin/moderator/member"
    datetime joined_at
  }
  SERVER_INVITE {
    int id PK
    int server_id FK
    string code UK
    int created_by FK
    int max_uses
    int uses
    datetime expires_at
    bool is_revoked
  }
  CHANNEL {
    int id PK
    int server_id FK
    string name
    string topic
    string type "text"
    int position
  }
  DM_CHANNEL {
    int id PK
    bool is_group
    string name
    datetime created_at
  }
  DM_MEMBER {
    int id PK
    int dm_channel_id FK
    int user_id FK
  }
  MESSAGE {
    int id PK
    int channel_id FK
    int dm_channel_id FK
    int author_id FK
    string content
    int parent_id FK
    datetime edited_at
    bool is_deleted
    datetime created_at
  }
  REACTION {
    int id PK
    int message_id FK
    int user_id FK
    string emoji
  }
  ATTACHMENT {
    int id PK
    int message_id FK
    int uploader_id FK
    string filename
    string stored_name
    string content_type
    int size
  }
  READ_STATE {
    int id PK
    int user_id FK
    int channel_id FK
    int dm_channel_id FK
    int last_read_message_id
  }
        
図5. ER図。MESSAGEchannel_id(サーバー内)または dm_channel_id(DM)のいずれかに属する。スレッドは parent_id による自己参照で表現する。
主な制約: SERVER_MEMBER(server_id, user_id)DM_MEMBER(dm_channel_id, user_id)REACTION(message_id, user_id, emoji)READ_STATE(user_id, channel_id, dm_channel_id) はそれぞれ一意制約を持つ。

2. ドメインクラス図(バックエンド主要クラス)

classDiagram
  class Settings {
    +str HOST
    +int PORT
    +str SECRET_KEY
    +str DATABASE_URL
    +str UPLOAD_DIR
    +bool RESTRICT_TO_PRIVATE
    +str PLANTUML_JAR
    +ensure_dirs()
  }

  class ConnectionManager {
    -dict connections
    -Lock lock
    +connect(user_id, ws)
    +disconnect(user_id, ws)
    +send_to_users(user_ids, message)
    +online_user_ids() list
  }

  class PrivateNetworkGuard {
    -list networks
    -bool trust_forwarded
    +call(scope, receive, send)
    -client_ip(scope) str
    -reject(scope, send)
  }

  class SecurityService {
    <<module>>
    +hash_password(pw) str
    +verify_password(pw, stored) bool
    +create_access_token(user_id) str
    +decode_access_token(token) int
  }

  class DomainService {
    <<module>>
    +get_membership(db, server_id, user_id)
    +require_membership(db, server_id, user_id)
    +require_role(db, server_id, user_id, roles)
    +channel_member_ids(db, channel) list
    +dm_member_ids(db, dm_channel_id) list
    +serialize_message(db, msg) MessagePublic
  }

  class ServerMember {
    +str role
  }

  ConnectionManager ..> SecurityService : トークン検証
  DomainService ..> ServerMember : 権限判定
  PrivateNetworkGuard ..> Settings : 許可CIDR参照
  SecurityService ..> Settings : SECRET_KEY参照
        
classDiagram
  class Settings {
    +str HOST
    +int PORT
    +str SECRET_KEY
    +str DATABASE_URL
    +str UPLOAD_DIR
    +bool RESTRICT_TO_PRIVATE
    +str PLANTUML_JAR
    +ensure_dirs()
  }

  class ConnectionManager {
    -dict connections
    -Lock lock
    +connect(user_id, ws)
    +disconnect(user_id, ws)
    +send_to_users(user_ids, message)
    +online_user_ids() list
  }

  class PrivateNetworkGuard {
    -list networks
    -bool trust_forwarded
    +call(scope, receive, send)
    -client_ip(scope) str
    -reject(scope, send)
  }

  class SecurityService {
    <<module>>
    +hash_password(pw) str
    +verify_password(pw, stored) bool
    +create_access_token(user_id) str
    +decode_access_token(token) int
  }

  class DomainService {
    <<module>>
    +get_membership(db, server_id, user_id)
    +require_membership(db, server_id, user_id)
    +require_role(db, server_id, user_id, roles)
    +channel_member_ids(db, channel) list
    +dm_member_ids(db, dm_channel_id) list
    +serialize_message(db, msg) MessagePublic
  }

  class ServerMember {
    +str role
  }

  ConnectionManager ..> SecurityService : verify token
  DomainService ..> ServerMember : check role
  PrivateNetworkGuard ..> Settings : allowed CIDRs
  SecurityService ..> Settings : SECRET_KEY
        
図6. バックエンドの主要クラス・モジュール。認証・権限・接続管理・アクセス制御の責務分割を示す。
(図中の call はASGIの __call__connections/lock 等は内部属性に対応)

3. ロール(権限)モデル

ロール 権限範囲
adminチャンネル作成・招待発行・ロール変更・メンバーのキック・メッセージ削除(全員分)
moderatorチャンネル作成・招待発行・メッセージ削除(全員分)
memberメッセージ送信・自分のメッセージの編集/削除・リアクション

サーバー作成者は自動的に admin となり、オーナー(owner_id)はキック・降格の対象外。