This commit is contained in:
2026-06-14 11:16:42 +08:00
parent ca39190ad7
commit c9fc87cd89
35 changed files with 1480 additions and 18 deletions
+11
View File
@@ -16,6 +16,11 @@ from app.models.sync_seed import SyncQuestion, SyncSeed
from app.models.chat_climate import ChatClimate
from app.models.flash_event import FlashEvent, FlashParticipation
from app.models.user_streak import UserStreak
from app.models.group_announcement import GroupAnnouncement
from app.models.message_reaction import MessageReaction
from app.models.user_block import UserBlock
from app.models.password_reset import PasswordResetToken
from app.models.friend_tag import FriendTag, FriendTagAssignment
__all__ = [
"User",
@@ -38,4 +43,10 @@ __all__ = [
"FlashEvent",
"FlashParticipation",
"UserStreak",
"GroupAnnouncement",
"MessageReaction",
"UserBlock",
"PasswordResetToken",
"FriendTag",
"FriendTagAssignment",
]
+2 -1
View File
@@ -2,7 +2,7 @@
from datetime import datetime, timezone
from sqlalchemy import String, DateTime, ForeignKey, Text
from sqlalchemy import String, DateTime, ForeignKey, Text, Boolean
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
@@ -17,6 +17,7 @@ class Conversation(Base):
avatar_url: Mapped[str | None] = mapped_column(String(500), nullable=True) # 群头像
description: Mapped[str | None] = mapped_column(String(500), nullable=True) # 群描述
creator_id: Mapped[str | None] = mapped_column(String(36), ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
mute_all: Mapped[bool] = mapped_column(Boolean, default=False) # 全员禁言(仅成员不能发,管理员可以)
last_message_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
last_message_preview: Mapped[str | None] = mapped_column(String(200), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.utcnow())
+4 -1
View File
@@ -2,7 +2,7 @@
from datetime import datetime, timezone
from sqlalchemy import String, DateTime, ForeignKey, UniqueConstraint
from sqlalchemy import String, DateTime, Boolean, ForeignKey, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
@@ -24,6 +24,9 @@ class ConversationMember(Base):
role: Mapped[str] = mapped_column(String(20), default="member") # owner / admin / member
nickname: Mapped[str | None] = mapped_column(String(50), nullable=True) # 群内昵称
last_read_message_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
is_pinned: Mapped[bool] = mapped_column(Boolean, default=False) # 置顶
pinned_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
is_muted: Mapped[bool] = mapped_column(Boolean, default=False) # 免打扰
joined_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.utcnow())
left_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
+30
View File
@@ -0,0 +1,30 @@
"""好友分组/标签模型"""
from datetime import datetime
from sqlalchemy import String, DateTime, ForeignKey, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class FriendTag(Base):
"""好友标签(一个好友可有多个标签)"""
__tablename__ = "friend_tags"
id: Mapped[str] = mapped_column(String(36), primary_key=True)
user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="CASCADE")) # 标签所有者
name: Mapped[str] = mapped_column(String(30), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.utcnow())
class FriendTagAssignment(Base):
"""好友-标签 关联"""
__tablename__ = "friend_tag_assignments"
__table_args__ = (
UniqueConstraint("friend_id", "tag_id", name="uq_friend_tag"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True)
friend_id: Mapped[str] = mapped_column(String(36), ForeignKey("friends.id", ondelete="CASCADE"))
tag_id: Mapped[str] = mapped_column(String(36), ForeignKey("friend_tags.id", ondelete="CASCADE"))
+25
View File
@@ -0,0 +1,25 @@
"""群公告模型"""
from datetime import datetime
from sqlalchemy import String, DateTime, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class GroupAnnouncement(Base):
__tablename__ = "group_announcements"
id: Mapped[str] = mapped_column(String(36), primary_key=True)
conversation_id: Mapped[str] = mapped_column(
String(36), ForeignKey("conversations.id", ondelete="CASCADE"), nullable=False
)
author_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
content: Mapped[str] = mapped_column(String(1000), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.utcnow())
updated_at: Mapped[datetime] = mapped_column(
DateTime, default=lambda: datetime.utcnow(), onupdate=lambda: datetime.utcnow()
)
author = relationship("User", foreign_keys=[author_id])
+4 -1
View File
@@ -21,12 +21,15 @@ class Message(Base):
sender_id: Mapped[str] = mapped_column(
String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False
)
type: Mapped[str] = mapped_column(String(20), default="text") # text / image / file / system
type: Mapped[str] = mapped_column(String(20), default="text") # text / image / file / voice / system
content: Mapped[str] = mapped_column(Text, nullable=False)
reply_to_id: Mapped[str | None] = mapped_column(
String(36), ForeignKey("messages.id", ondelete="SET NULL"), nullable=True
)
mentions: Mapped[str | None] = mapped_column(Text, nullable=True) # JSON: 被@的用户ID列表
is_deleted: Mapped[bool] = mapped_column(Boolean, default=False)
is_recalled: Mapped[bool] = mapped_column(Boolean, default=False) # 撤回
recalled_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime, default=lambda: datetime.utcnow(), index=True
)
+24
View File
@@ -0,0 +1,24 @@
"""消息表情回应模型"""
from datetime import datetime
from sqlalchemy import String, DateTime, ForeignKey, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class MessageReaction(Base):
__tablename__ = "message_reactions"
__table_args__ = (
UniqueConstraint("message_id", "user_id", "emoji", name="uq_msg_reaction"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True)
message_id: Mapped[str] = mapped_column(String(36), ForeignKey("messages.id", ondelete="CASCADE"))
user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="CASCADE"))
emoji: Mapped[str] = mapped_column(String(10), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.utcnow())
message = relationship("Message")
user = relationship("User", foreign_keys=[user_id])
+21
View File
@@ -0,0 +1,21 @@
"""密码重置令牌模型"""
from datetime import datetime
from sqlalchemy import String, DateTime, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class PasswordResetToken(Base):
__tablename__ = "password_reset_tokens"
id: Mapped[str] = mapped_column(String(36), primary_key=True)
user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="CASCADE"))
token_hash: Mapped[str] = mapped_column(String(64), nullable=False) # 验证码的 hash
expires_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
used: Mapped[bool] = mapped_column(default=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.utcnow())
user = relationship("User", foreign_keys=[user_id])
+5 -1
View File
@@ -18,7 +18,11 @@ class User(Base):
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
avatar_url: Mapped[str | None] = mapped_column(String(500), nullable=True)
bio: Mapped[str | None] = mapped_column(String(200), nullable=True)
status: Mapped[str] = mapped_column(String(20), default="offline") # online/offline/away
status: Mapped[str] = mapped_column(String(20), default="offline") # online/offline/away (presence)
custom_status: Mapped[str | None] = mapped_column(String(50), nullable=True) # 个人心情状态文字
status_emoji: Mapped[str | None] = mapped_column(String(10), nullable=True) # 心情 emoji
status_expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) # 状态到期自动清空
email_verified: Mapped[bool] = mapped_column(Boolean, default=False) # 邮箱是否已验证
is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
is_banned: Mapped[bool] = mapped_column(Boolean, default=False)
banned_reason: Mapped[str | None] = mapped_column(String(500), nullable=True)
+23
View File
@@ -0,0 +1,23 @@
"""用户拉黑模型"""
from datetime import datetime
from sqlalchemy import String, DateTime, ForeignKey, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class UserBlock(Base):
__tablename__ = "user_blocks"
__table_args__ = (
UniqueConstraint("blocker_id", "blocked_id", name="uq_user_block"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True)
blocker_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="CASCADE"))
blocked_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="CASCADE"))
created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.utcnow())
blocker = relationship("User", foreign_keys=[blocker_id])
blocked = relationship("User", foreign_keys=[blocked_id])