import json from datetime import datetime from sqlalchemy import String, Text, Integer, DateTime, Boolean, ForeignKey, func from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.base import Base class Class_(Base): __tablename__ = "classes" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(100), nullable=False) cohort_year: Mapped[int] = mapped_column(Integer, nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) invite_code: Mapped[str | None] = mapped_column(String(20), unique=True, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), onupdate=func.now() ) members: Mapped[list["User"]] = relationship("User", back_populates="class_") timelines: Mapped[list["Timeline"]] = relationship( "Timeline", back_populates="class_", cascade="all, delete-orphan" ) schedules: Mapped[list["Schedule"]] = relationship( "Schedule", back_populates="class_", cascade="all, delete-orphan" ) announcements: Mapped[list["Announcement"]] = relationship( "Announcement", back_populates="class_", cascade="all, delete-orphan" ) resources: Mapped[list["Resource"]] = relationship( "Resource", back_populates="class_", cascade="all, delete-orphan" ) roster: Mapped[list["StudentRoster"]] = relationship( "StudentRoster", back_populates="class_", cascade="all, delete-orphan" ) class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True) password_hash: Mapped[str] = mapped_column(Text, nullable=False) name: Mapped[str] = mapped_column(String(100), nullable=False) student_id: Mapped[str | None] = mapped_column(String(50), nullable=True, unique=True) # role: super_admin | class_admin | student role: Mapped[str] = mapped_column(String(20), default="student", nullable=False) # status: pending | approved | rejected | disabled status: Mapped[str] = mapped_column(String(20), default="pending", nullable=False) class_id: Mapped[int | None] = mapped_column( Integer, ForeignKey("classes.id"), nullable=True ) class_: Mapped["Class_ | None"] = relationship("Class_", back_populates="members") # Profile industry: Mapped[str | None] = mapped_column(String(100), nullable=True) company: Mapped[str | None] = mapped_column(String(100), nullable=True) position: Mapped[str | None] = mapped_column(String(100), nullable=True) skills_tags: Mapped[str | None] = mapped_column(Text, nullable=True) # JSON array wechat_id: Mapped[str | None] = mapped_column(String(100), nullable=True) phone: Mapped[str | None] = mapped_column(String(20), nullable=True) avatar_url: Mapped[str | None] = mapped_column(Text, nullable=True) bio: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), onupdate=func.now() ) timeline_posts: Mapped[list["Timeline"]] = relationship( "Timeline", back_populates="author" ) def get_skills_list(self) -> list[str]: if not self.skills_tags: return [] try: return json.loads(self.skills_tags) except (json.JSONDecodeError, TypeError): return [] def set_skills_list(self, tags: list[str]): self.skills_tags = json.dumps(tags, ensure_ascii=False) if tags else None class Timeline(Base): __tablename__ = "timelines" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) class_id: Mapped[int] = mapped_column( Integer, ForeignKey("classes.id"), nullable=False, index=True ) author_id: Mapped[int] = mapped_column( Integer, ForeignKey("users.id"), nullable=False ) title: Mapped[str] = mapped_column(String(200), nullable=False) content: Mapped[str | None] = mapped_column(Text, nullable=True) image_urls: Mapped[str | None] = mapped_column(Text, nullable=True) # JSON array created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), onupdate=func.now() ) class_: Mapped["Class_"] = relationship("Class_", back_populates="timelines") author: Mapped["User"] = relationship("User", back_populates="timeline_posts") def get_image_urls_list(self) -> list[str]: if not self.image_urls: return [] try: return json.loads(self.image_urls) except (json.JSONDecodeError, TypeError): return [] def set_image_urls_list(self, urls: list[str]): self.image_urls = json.dumps(urls) if urls else None class Schedule(Base): __tablename__ = "schedules" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) class_id: Mapped[int] = mapped_column( Integer, ForeignKey("classes.id"), nullable=False, index=True ) # type: course | deadline | activity type: Mapped[str] = mapped_column(String(20), nullable=False) title: Mapped[str] = mapped_column(String(200), nullable=False) start_time: Mapped[datetime] = mapped_column(DateTime, nullable=False) end_time: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) location: Mapped[str | None] = mapped_column(String(200), nullable=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), onupdate=func.now() ) class_: Mapped["Class_"] = relationship("Class_", back_populates="schedules") class Announcement(Base): __tablename__ = "announcements" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) class_id: Mapped[int] = mapped_column( Integer, ForeignKey("classes.id"), nullable=False, index=True ) author_id: Mapped[int] = mapped_column( Integer, ForeignKey("users.id"), nullable=False ) title: Mapped[str] = mapped_column(String(200), nullable=False) content: Mapped[str | None] = mapped_column(Text, nullable=True) is_pinned: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), onupdate=func.now() ) class_: Mapped["Class_"] = relationship("Class_", back_populates="announcements") author: Mapped["User"] = relationship("User") class Resource(Base): __tablename__ = "resources" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) class_id: Mapped[int] = mapped_column( Integer, ForeignKey("classes.id"), nullable=False, index=True ) uploader_id: Mapped[int] = mapped_column( Integer, ForeignKey("users.id"), nullable=False ) title: Mapped[str] = mapped_column(String(200), nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) file_url: Mapped[str] = mapped_column(Text, nullable=False) file_type: Mapped[str] = mapped_column(String(50), nullable=False) file_size: Mapped[int] = mapped_column(Integer, nullable=False) category: Mapped[str] = mapped_column(String(50), nullable=False) download_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) class_: Mapped["Class_"] = relationship("Class_", back_populates="resources") uploader: Mapped["User"] = relationship("User") class Notification(Base): __tablename__ = "notifications" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column( Integer, ForeignKey("users.id"), nullable=False, index=True ) type: Mapped[str] = mapped_column(String(50), nullable=False) title: Mapped[str] = mapped_column(String(200), nullable=False) content: Mapped[str | None] = mapped_column(Text, nullable=True) related_id: Mapped[int | None] = mapped_column(Integer, nullable=True) is_read: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) user: Mapped["User"] = relationship("User") class StudentRoster(Base): __tablename__ = "student_rosters" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) class_id: Mapped[int] = mapped_column( Integer, ForeignKey("classes.id"), nullable=False, index=True ) student_id: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(100), nullable=False) status: Mapped[str] = mapped_column( String(20), default="unregistered", nullable=False ) user_id: Mapped[int | None] = mapped_column( Integer, ForeignKey("users.id"), nullable=True ) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) class_: Mapped["Class_"] = relationship("Class_", back_populates="roster") user: Mapped["User | None"] = relationship("User")