File size: 4,818 Bytes
6e876c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
837a707
 
6e876c1
 
 
 
 
 
 
 
 
 
 
 
 
 
384e6b6
 
4bc82b0
8193c6f
4bc82b0
 
 
8193c6f
 
 
23938a8
 
 
 
6e876c1
 
 
 
 
 
 
 
 
 
 
 
837a707
 
6e876c1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""SQLAlchemy ORM 模型:User / Match / Stat。

数据关系(核心数据结构):
- User   一名队员/管理员(管理员同时也可是球员)
- Match  一场比赛(比分由管理员录入)
- Stat   某队员在某场的进球/助攻/出勤(队员自己录入)
         唯一约束 (user_id, match_id):一人一场至多一条记录。
         "无记录" 在聚合层视为「默认出勤、0 球 0 助攻」,从而消除特殊分支。
"""
from __future__ import annotations

from datetime import date, datetime

from sqlalchemy import (
    Boolean,
    Date,
    DateTime,
    ForeignKey,
    Integer,
    String,
    UniqueConstraint,
)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    username: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
    display_name: Mapped[str] = mapped_column(String(64), nullable=False)
    password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
    jersey_number: Mapped[int | None] = mapped_column(Integer, nullable=True)
    is_admin: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
    # is_player=True 才计入球员榜单;领队/教练等可设为 False
    is_player: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
    # is_goalkeeper=True 的球员才显示/统计扑救数据,并出现在扑救榜
    is_goalkeeper: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
    active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)


class Match(Base):
    __tablename__ = "matches"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    match_date: Mapped[date] = mapped_column(Date, nullable=False)
    opponent: Mapped[str] = mapped_column(String(128), nullable=False)
    home_away: Mapped[str] = mapped_column(String(8), default="home", nullable=False)
    our_score: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
    opp_score: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
    note: Mapped[str | None] = mapped_column(String(255), nullable=True)
    # mvp_locked=True:该场 MVP 投票已由管理员结算锁定,不再接受投/改票
    mvp_locked: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
    # 本场 MVP(由管理员结算时指定;None=尚未结算)。榜单统计获得次数。
    # 支持双 MVP:mvp2_user_id 为第二人(可空;单 MVP 时留空)。
    mvp_user_id: Mapped[int | None] = mapped_column(
        ForeignKey("users.id"), nullable=True
    )
    mvp2_user_id: Mapped[int | None] = mapped_column(
        ForeignKey("users.id"), nullable=True
    )
    # 铁血防守 MVP(同样由管理员结算指定;独立于最佳球员 MVP,可同场同人)
    dmvp_user_id: Mapped[int | None] = mapped_column(
        ForeignKey("users.id"), nullable=True
    )
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)


class Stat(Base):
    __tablename__ = "stats"
    __table_args__ = (UniqueConstraint("user_id", "match_id", name="uq_user_match"),)

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
    match_id: Mapped[int] = mapped_column(ForeignKey("matches.id"), nullable=False)
    goals: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
    assists: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
    # saves 扑救数:仅门将有意义,但所有行统一存储(默认 0),避免为门将单开一张表
    saves: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
    attended: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
    updated_at: Mapped[datetime] = mapped_column(
        DateTime, default=datetime.now, onupdate=datetime.now
    )


class Vote(Base):
    """MVP 投票:每人每场至多投一票(投给某位队员)。

    唯一约束 (voter_id, match_id):一人一场一票,可改可撤。
    """

    __tablename__ = "votes"
    __table_args__ = (UniqueConstraint("voter_id", "match_id", name="uq_voter_match"),)

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    voter_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
    match_id: Mapped[int] = mapped_column(ForeignKey("matches.id"), nullable=False)
    voted_for_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False)
    created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.now)