Files
sproutly/backend/models.py
derekc 4db9988406 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>
2026-03-09 00:08:28 -07:00

126 lines
4.4 KiB
Python

import enum
from sqlalchemy import Column, Integer, String, Date, Boolean, Text, DateTime, Enum, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
class Category(str, enum.Enum):
vegetable = "vegetable"
herb = "herb"
flower = "flower"
fruit = "fruit"
class SunRequirement(str, enum.Enum):
full_sun = "full_sun"
part_shade = "part_shade"
full_shade = "full_shade"
class WaterNeeds(str, enum.Enum):
low = "low"
medium = "medium"
high = "high"
class BatchStatus(str, enum.Enum):
planned = "planned"
germinating = "germinating"
seedling = "seedling"
potted_up = "potted_up"
hardening = "hardening"
garden = "garden"
harvested = "harvested"
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)
weeks_to_start = Column(Integer)
weeks_to_greenhouse = Column(Integer)
weeks_to_garden = Column(Integer)
days_to_germinate = Column(Integer, default=7)
direct_sow_ok = Column(Boolean, default=False)
frost_tolerant = Column(Boolean, default=False)
sun_requirement = Column(Enum(SunRequirement), default=SunRequirement.full_sun)
water_needs = Column(Enum(WaterNeeds), default=WaterNeeds.medium)
color = Column(String(7), default="#52b788")
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")
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)
sow_date = Column(Date)
germination_date = Column(Date)
greenhouse_date = Column(Date)
garden_date = Column(Date)
status = Column(Enum(BatchStatus), default=BatchStatus.planned)
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, 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))
ntfy_server = Column(String(200), default="https://ntfy.sh")
notification_time = Column(String(5), default="07:00")
timezone = Column(String(50), default="UTC")
location_name = Column(String(100))
ntfy_username = Column(String(200))
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")