- 产品编辑入口统一走 ProductFormFull(卖点/风格/人群/品牌词全字段); 修复开任务页 <form> 套 <form> 致"编辑产品"报错、改不了、跳回首个产品 - dashboard 入口卡片对齐实际路由: 系统管理(/config) 与 工作配置(/settings) 分开; settings ?tab=products 直达改用挂载后读 URL, 消除 hydration mismatch - 新增用户管理(users API/admin service/改密页) + alembic 022/023/024 - 上线部署: Dockerfile / docker-compose.prod+https / nginx https / .env.example - A8 三套正交叙事(痛点/场景/成分背书) + beige 调色去AI化 + 飞轮 text_import 高权重信号 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
"""
|
||
app/models/user.py — users / login_records / user_preferences
|
||
Alembic 001:从 banana 搬,删除 credits 字段(架构方案规定)。
|
||
"""
|
||
|
||
from datetime import datetime
|
||
|
||
from sqlalchemy import (
|
||
BigInteger, Boolean, DateTime, ForeignKey,
|
||
Integer, String, Text, func,
|
||
)
|
||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||
|
||
from app.core.database import Base
|
||
|
||
|
||
class User(Base):
|
||
__tablename__ = "users"
|
||
|
||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||
username: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
|
||
email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
|
||
hashed_password: Mapped[str] = mapped_column(String(255), nullable=False)
|
||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
||
must_change_password: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime, server_default=func.now(), nullable=False
|
||
)
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
|
||
)
|
||
# 注:删除 banana 的 credits 字段(架构方案规定)
|
||
|
||
login_records: Mapped[list["LoginRecord"]] = relationship(
|
||
back_populates="user", lazy="noload"
|
||
)
|
||
|
||
|
||
class LoginRecord(Base):
|
||
__tablename__ = "login_records"
|
||
|
||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||
user_id: Mapped[int] = mapped_column(
|
||
BigInteger, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
|
||
)
|
||
ip_address: Mapped[str | None] = mapped_column(String(64))
|
||
user_agent: Mapped[str | None] = mapped_column(String(512))
|
||
created_at: Mapped[datetime] = mapped_column(
|
||
DateTime, server_default=func.now(), nullable=False
|
||
)
|
||
|
||
user: Mapped["User"] = relationship(back_populates="login_records")
|
||
|
||
|
||
class UserPreference(Base):
|
||
"""UI 设置偏好(主题/语言等),不含 API Key。"""
|
||
__tablename__ = "user_preferences"
|
||
|
||
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||
user_id: Mapped[int] = mapped_column(
|
||
BigInteger, ForeignKey("users.id", ondelete="CASCADE"),
|
||
unique=True, nullable=False,
|
||
)
|
||
preferences_json: Mapped[str | None] = mapped_column(Text) # JSON字符串
|
||
updated_at: Mapped[datetime] = mapped_column(
|
||
DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
|
||
)
|
||
|
||
user: Mapped["User"] = relationship()
|