✨ feat: WP-11 社区——发布、互动、关注与治理
- 迁移 12:community_content(帖子/角色 Release、publication 六态、可见性)、comment(父级树)、reaction((content,actor,kind) 唯一防重复)、follow(双 FK 显式命名修复 convention 冲突)、report + moderation_case(举报即建司议案) - 发布:帖子直发;角色 Release 由服务端构造公开快照(name/道体/版本数)——私密记忆/关系/对话结构性缺席,测试以注入私密记忆后发布验证零泄露 - 互动:评论(父级校验同内容可见)、点赞/收藏(重复 409、取消后计数正确) - 关注/取关与计数;举报自动生成司议案(治理队列入口,status=pending) - 下架传播:removed 后列表/详情/评论统一 404 或过滤(require_content 单点);非作者下架 404 fail-closed - 测试基建修复:login 返回自包含 headers(显式 Cookie 优先),多用户交替不再串会话 - 测试 79 项全绿(+7)
This commit is contained in:
1 parent
34c481ab9e
commit
99985f5855
7 files changed
+823
-2
No files matched your search
@@ -796,3 +796,172 @@ class WebhookEvent(Base):
|
||||
__table_args__ = (
|
||||
CheckConstraint("status IN ('processed', 'ignored')", name="status_values"),
|
||||
)
|
||||
|
||||
|
||||
class CommunityContent(Base):
|
||||
"""社区内容:帖子与角色 Release(WP-11)。
|
||||
|
||||
角色 Release 由服务端构造公开快照;私密记忆/关系/对话在结构上不进入发布物。
|
||||
"""
|
||||
|
||||
__tablename__ = "community_content"
|
||||
|
||||
id: Mapped[str] = mapped_column(
|
||||
String, primary_key=True, default=lambda: new_id("content_")
|
||||
)
|
||||
author_domain_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("personal_domain.id", ondelete="cascade"), nullable=False, index=True
|
||||
)
|
||||
kind: Mapped[str] = mapped_column(String, nullable=False, default="post")
|
||||
title: Mapped[str] = mapped_column(String, nullable=False)
|
||||
body: Mapped[dict[str, Any]] = mapped_column(JSONB, nullable=False, default=dict)
|
||||
character_id: Mapped[str | None] = mapped_column(
|
||||
ForeignKey("character.id", ondelete="set null"), nullable=True
|
||||
)
|
||||
visibility: Mapped[str] = mapped_column(String, nullable=False, default="public")
|
||||
status: Mapped[str] = mapped_column(String, nullable=False, default="published")
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("kind IN ('post', 'character_release')", name="kind_values"),
|
||||
CheckConstraint("visibility IN ('public', 'followers')", name="visibility_values"),
|
||||
CheckConstraint(
|
||||
"status IN ('draft', 'submitted', 'published', 'limited', 'suspended', 'removed')",
|
||||
name="status_values",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class Comment(Base):
|
||||
"""社区评论:平铺 + 可选父级;下架内容下的评论不可见。"""
|
||||
|
||||
__tablename__ = "comment"
|
||||
|
||||
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: new_id("comment_"))
|
||||
content_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("community_content.id", ondelete="cascade"), nullable=False, index=True
|
||||
)
|
||||
author_domain_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("personal_domain.id", ondelete="cascade"), nullable=False
|
||||
)
|
||||
parent_comment_id: Mapped[str | None] = mapped_column(
|
||||
ForeignKey("comment.id", ondelete="cascade"), nullable=True
|
||||
)
|
||||
body: Mapped[str] = mapped_column(String, nullable=False)
|
||||
status: Mapped[str] = mapped_column(String, nullable=False, default="visible")
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("status IN ('visible', 'removed')", name="status_values"),
|
||||
)
|
||||
|
||||
|
||||
class Reaction(Base):
|
||||
"""互动:点赞/收藏;(content, actor, kind) 唯一防重复。"""
|
||||
|
||||
__tablename__ = "reaction"
|
||||
|
||||
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: new_id("reaction_"))
|
||||
content_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("community_content.id", ondelete="cascade"), nullable=False, index=True
|
||||
)
|
||||
actor_domain_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("personal_domain.id", ondelete="cascade"), nullable=False
|
||||
)
|
||||
kind: Mapped[str] = mapped_column(String, nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("kind IN ('like', 'save')", name="kind_values"),
|
||||
Index(
|
||||
"uq_reaction_content_actor_kind",
|
||||
"content_id", "actor_domain_id", "kind",
|
||||
unique=True,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class Follow(Base):
|
||||
"""关注关系(域→域)。"""
|
||||
|
||||
__tablename__ = "follow"
|
||||
|
||||
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: new_id("follow_"))
|
||||
# 双 FK 同指 personal_domain:显式命名避免 convention 同名冲突
|
||||
follower_domain_id: Mapped[str] = mapped_column(
|
||||
ForeignKey(
|
||||
"personal_domain.id", ondelete="cascade", name="fk_follow_follower_domain"
|
||||
),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
followee_domain_id: Mapped[str] = mapped_column(
|
||||
ForeignKey(
|
||||
"personal_domain.id", ondelete="cascade", name="fk_follow_followee_domain"
|
||||
),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index(
|
||||
"uq_follow_pair", "follower_domain_id", "followee_domain_id", unique=True
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class Report(Base):
|
||||
"""举报:创建即生成司议案(治理队列入口)。"""
|
||||
|
||||
__tablename__ = "report"
|
||||
|
||||
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: new_id("report_"))
|
||||
reporter_domain_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("personal_domain.id", ondelete="cascade"), nullable=False, index=True
|
||||
)
|
||||
target_type: Mapped[str] = mapped_column(String, nullable=False)
|
||||
target_id: Mapped[str] = mapped_column(String, nullable=False)
|
||||
reason: Mapped[str] = mapped_column(String, nullable=False)
|
||||
status: Mapped[str] = mapped_column(String, nullable=False, default="pending")
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("target_type IN ('content', 'comment')", name="target_type_values"),
|
||||
CheckConstraint("status IN ('pending', 'reviewed', 'actioned')", name="status_values"),
|
||||
)
|
||||
|
||||
|
||||
class ModerationCase(Base):
|
||||
"""司议案:由举报生成,处置动作与结果留痕。"""
|
||||
|
||||
__tablename__ = "moderation_case"
|
||||
|
||||
id: Mapped[str] = mapped_column(
|
||||
String, primary_key=True, default=lambda: new_id("moderation_")
|
||||
)
|
||||
report_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("report.id", ondelete="cascade"), nullable=False, index=True
|
||||
)
|
||||
status: Mapped[str] = mapped_column(String, nullable=False, default="pending")
|
||||
action_taken: Mapped[str | None] = mapped_column(String, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
CheckConstraint("status IN ('pending', 'resolved')", name="status_values"),
|
||||
)
|
||||
Reference in new issue
Block a user