File size: 8,884 Bytes
594ed40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
from sqlalchemy import Column, String, DateTime, JSON, Boolean, ForeignKey, Text, Integer, Float
from sqlalchemy.orm import declarative_base, relationship
from datetime import datetime
import uuid

Base = declarative_base()


class User(Base):
    """ユーザーモデル - 複数の認証方法をサポート"""
    __tablename__ = "users"

    id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
    email = Column(String, unique=True, nullable=True, index=True)  # OAuth時はnullable
    username = Column(String, unique=True, nullable=True, index=True)
    display_name = Column(String, nullable=True)
    hashed_password = Column(String, nullable=True)  # OAuth時はnullable

    # 認証プロバイダー情報
    auth_provider = Column(String, default="local")  # local, google, github, orcid
    google_id = Column(String, unique=True, nullable=True, index=True)
    github_id = Column(String, unique=True, nullable=True, index=True)
    orcid_id = Column(String, unique=True, nullable=True, index=True)

    # 権限とステータス
    role = Column(String, default="viewer", nullable=False)  # viewer, expert, reviewer, curator, admin
    is_expert = Column(Boolean, default=False)
    is_guest = Column(Boolean, default=False)
    expert_verification_status = Column(String, default="none")  # none, pending, approved, rejected
    expert_credentials = Column(JSON)

    # プロフィール情報
    avatar_url = Column(String, nullable=True)
    bio = Column(Text, nullable=True)
    affiliation = Column(String, nullable=True)

    # OAuth tokens (暗号化して保存すべき)
    google_access_token = Column(String, nullable=True)
    google_refresh_token = Column(String, nullable=True)
    github_access_token = Column(String, nullable=True)
    orcid_access_token = Column(String, nullable=True)
    orcid_refresh_token = Column(String, nullable=True)

    # タイムスタンプ
    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    last_login_at = Column(DateTime, nullable=True)

    # リレーションシップ
    workspaces = relationship("Workspace", back_populates="owner", cascade="all, delete-orphan")
    workspace_members = relationship("WorkspaceMember", back_populates="user", cascade="all, delete-orphan")
    proposals = relationship("Proposal", back_populates="proposer", foreign_keys="[Proposal.proposer_id]")
    reviews = relationship("Proposal", back_populates="reviewer", foreign_keys="[Proposal.reviewer_id]")
    contributions = relationship("KnowledgeTile", back_populates="contributor", foreign_keys="[KnowledgeTile.contributor_id]")


class KnowledgeTile(Base):
    """知識タイルモデル"""
    __tablename__ = "knowledge_tiles"

    id = Column(String, primary_key=True, default=lambda: f"ktile_{uuid.uuid4().hex}")
    workspace_id = Column(String, ForeignKey("workspaces.id"), nullable=False, index=True)
    domain_id = Column(String, index=True)
    topic = Column(String, nullable=False)
    content = Column(Text, nullable=False)
    tags = Column(JSON, nullable=True)

    # 6次元座標 [x, y, z, c, g, v]
    # [x, y, z]: ドメイン固有の3次元空間
    # [c, g, v]: メタ空間 (Certainty, Granularity, Verification)
    coordinates = Column(JSON, nullable=True)  # Example: [0.5, 0.3, 0.8, 0.9, 0.7, 0.85]

    # バージョン管理
    version = Column(Integer, default=1)
    is_latest_version = Column(Boolean, default=True)
    based_on_version = Column(Integer, nullable=True)
    
    # 貢献者と検証
    contributor_id = Column(String, ForeignKey("users.id"), nullable=True)
    confidence_score = Column(Float, default=0.0)
    verification_type = Column(String, default="none", index=True)  # none, community, expert, multi_expert
    verification_count = Column(Integer, default=0)
    last_verified_by_id = Column(String, ForeignKey("users.id"), nullable=True)
    last_verified_at = Column(DateTime)
    
    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    workspace = relationship("Workspace")
    contributor = relationship("User", back_populates="contributions", foreign_keys=[contributor_id])
    last_verified_by = relationship("User", foreign_keys=[last_verified_by_id])
    proposals = relationship("Proposal", back_populates="tile")


class Proposal(Base):
    """編集提案モデル"""
    __tablename__ = "proposals"

    id = Column(String, primary_key=True, default=lambda: f"prop_{uuid.uuid4().hex}")
    workspace_id = Column(String, ForeignKey("workspaces.id"), nullable=False, index=True)
    tile_id = Column(String, ForeignKey("knowledge_tiles.id"), nullable=True) # 新規作成時はnull
    proposer_id = Column(String, ForeignKey("users.id"), nullable=False)
    
    status = Column(String, default="pending", index=True) # pending, approved, rejected
    proposal_type = Column(String, nullable=False) # create, update, delete
    
    justification = Column(Text)
    proposed_content = Column(JSON)
    
    reviewer_id = Column(String, ForeignKey("users.id"), nullable=True)
    reviewer_comment = Column(Text)
    reviewed_at = Column(DateTime)

    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    workspace = relationship("Workspace")
    tile = relationship("KnowledgeTile", back_populates="proposals")
    proposer = relationship("User", back_populates="proposals", foreign_keys=[proposer_id])
    reviewer = relationship("User", back_populates="reviews", foreign_keys=[reviewer_id])




class Workspace(Base):
    """ワークスペース - ユーザーごとの独立したDB環境"""
    __tablename__ = "workspaces"

    id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
    name = Column(String, nullable=False)
    slug = Column(String, unique=True, nullable=False, index=True)  # URLフレンドリーな識別子
    description = Column(Text, nullable=True)

    # オーナー情報
    owner_id = Column(String, ForeignKey("users.id"), nullable=False)

    # 設定
    is_public = Column(Boolean, default=False)  # 公開ワークスペース
    allow_guest_edit = Column(Boolean, default=True)  # ゲストによる編集を許可
    allow_guest_view = Column(Boolean, default=True)  # ゲストによる閲覧を許可

    # データベース設定
    db_type = Column(String, default="sqlite")  # sqlite, postgresql
    db_path = Column(String, nullable=True)  # SQLiteの場合のファイルパス
    db_connection_string = Column(String, nullable=True)  # PostgreSQLの場合

    # 統計情報
    tile_count = Column(Integer, default=0)
    domain_count = Column(Integer, default=0)
    member_count = Column(Integer, default=1)

    # タイムスタンプ
    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    # リレーションシップ
    owner = relationship("User", back_populates="workspaces")
    members = relationship("WorkspaceMember", back_populates="workspace", cascade="all, delete-orphan")


class WorkspaceMember(Base):
    """ワークスペースメンバー - 共同作業者の管理"""
    __tablename__ = "workspace_members"

    id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
    workspace_id = Column(String, ForeignKey("workspaces.id"), nullable=False)
    user_id = Column(String, ForeignKey("users.id"), nullable=False)

    # 権限
    role = Column(String, default="viewer")  # viewer, editor, admin
    can_read = Column(Boolean, default=True)
    can_write = Column(Boolean, default=False)
    can_delete = Column(Boolean, default=False)
    can_invite = Column(Boolean, default=False)

    # タイムスタンプ
    joined_at = Column(DateTime, default=datetime.utcnow)

    # リレーションシップ
    workspace = relationship("Workspace", back_populates="members")
    user = relationship("User", back_populates="workspace_members")


class OAuthState(Base):
    """OAuth認証の一時的なstate管理"""
    __tablename__ = "oauth_states"

    id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
    state = Column(String, unique=True, nullable=False, index=True)
    provider = Column(String, nullable=False)  # google, orcid
    redirect_url = Column(String, nullable=True)
    user_id = Column(String, ForeignKey("users.id"), nullable=True)  # 既存ユーザーとの連携時

    created_at = Column(DateTime, default=datetime.utcnow)
    expires_at = Column(DateTime, nullable=False)  # 10分で期限切れ

    def is_expired(self):
        return datetime.utcnow() > self.expires_at