- Habits and the account now accumulate XP and levels from check streaks, with streak/level-up celebration banners and push notifications. - The "yesterday missed" banner on /today now lets a habit be explicitly marked failed (not just checked done), backed by a new HabitLog.status column so completion-rate/streak calculations never count a failed day as done. - The habit management tabs scroll horizontally on narrow screens instead of wrapping to two lines, with a pure-CSS edge shadow indicating more content.
32 lines
1.2 KiB
Python
32 lines
1.2 KiB
Python
import enum
|
|
from datetime import date, datetime
|
|
|
|
from sqlalchemy import Date, Enum, ForeignKey, Integer, UniqueConstraint
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
from sqlalchemy.sql import func
|
|
|
|
from app.database import Base
|
|
from app.models.habit import _by_value
|
|
|
|
|
|
class HabitLogStatus(str, enum.Enum):
|
|
DONE = "done"
|
|
FAILED = "failed" # /today의 "어제 놓친 습관" 배너에서 사용자가 명시적으로 실패를 확정한 경우
|
|
|
|
|
|
class HabitLog(Base):
|
|
__tablename__ = "habit_log"
|
|
__table_args__ = (UniqueConstraint("habit_id", "log_date", name="uq_habit_log_habit_date"),)
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
habit_id: Mapped[int] = mapped_column(ForeignKey("habit.id", ondelete="CASCADE"), nullable=False)
|
|
log_date: Mapped[date] = mapped_column(Date, nullable=False)
|
|
status: Mapped[HabitLogStatus] = mapped_column(
|
|
Enum(HabitLogStatus, native_enum=False, length=20, values_callable=_by_value),
|
|
nullable=False,
|
|
default=HabitLogStatus.DONE,
|
|
)
|
|
checked_at: Mapped[datetime] = mapped_column(server_default=func.now(), nullable=False)
|
|
|
|
habit: Mapped["Habit"] = relationship(back_populates="logs")
|