Add multi-user authentication with JWT
- Users table with email/bcrypt-hashed password; register and login via /auth/ endpoints - JWT tokens (30-day expiry) stored in localStorage; all API routes require Bearer auth - All data (varieties, batches, settings, notification logs) scoped to the authenticated user - Login/register screen overlays the app; sidebar shows user email and logout button - Scheduler sends daily ntfy summaries for every configured user - DB schema rewritten for multi-user; SECRET_KEY added to env Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,10 +35,25 @@ class BatchStatus(str, enum.Enum):
|
||||
failed = "failed"
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
email = Column(String(255), unique=True, nullable=False)
|
||||
hashed_password = Column(String(255), nullable=False)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
|
||||
varieties = relationship("Variety", back_populates="user", cascade="all, delete-orphan")
|
||||
batches = relationship("Batch", back_populates="user", cascade="all, delete-orphan")
|
||||
settings = relationship("Settings", back_populates="user", uselist=False, cascade="all, delete-orphan")
|
||||
notification_logs = relationship("NotificationLog", back_populates="user", cascade="all, delete-orphan")
|
||||
|
||||
|
||||
class Variety(Base):
|
||||
__tablename__ = "varieties"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||
name = Column(String(100), nullable=False)
|
||||
variety_name = Column(String(100))
|
||||
category = Column(Enum(Category), default=Category.vegetable)
|
||||
@@ -54,6 +69,7 @@ class Variety(Base):
|
||||
notes = Column(Text)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
|
||||
user = relationship("User", back_populates="varieties")
|
||||
batches = relationship("Batch", back_populates="variety", cascade="all, delete-orphan")
|
||||
|
||||
|
||||
@@ -61,6 +77,7 @@ class Batch(Base):
|
||||
__tablename__ = "batches"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
|
||||
variety_id = Column(Integer, ForeignKey("varieties.id"), nullable=False)
|
||||
label = Column(String(100))
|
||||
quantity = Column(Integer, default=1)
|
||||
@@ -72,13 +89,15 @@ class Batch(Base):
|
||||
notes = Column(Text)
|
||||
created_at = Column(DateTime, server_default=func.now())
|
||||
|
||||
user = relationship("User", back_populates="batches")
|
||||
variety = relationship("Variety", back_populates="batches")
|
||||
|
||||
|
||||
class Settings(Base):
|
||||
__tablename__ = "settings"
|
||||
|
||||
id = Column(Integer, primary_key=True, default=1)
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), unique=True, nullable=False)
|
||||
last_frost_date = Column(Date)
|
||||
first_frost_fall_date = Column(Date)
|
||||
ntfy_topic = Column(String(200))
|
||||
@@ -90,12 +109,17 @@ class Settings(Base):
|
||||
ntfy_password = Column(String(200))
|
||||
ntfy_api_key = Column(String(200))
|
||||
|
||||
user = relationship("User", back_populates="settings")
|
||||
|
||||
|
||||
class NotificationLog(Base):
|
||||
__tablename__ = "notification_log"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=True)
|
||||
sent_at = Column(DateTime, server_default=func.now())
|
||||
message = Column(Text)
|
||||
status = Column(String(20))
|
||||
error = Column(Text)
|
||||
|
||||
user = relationship("User", back_populates="notification_logs")
|
||||
|
||||
Reference in New Issue
Block a user