habit_tracker / src /database /models.py
GitHub Action Sync
Automated sync from GitHub
2ededba
Raw
History Blame Contribute Delete
6.11 kB
from datetime import datetime, timezone, time, date as dt_date
from sqlmodel import SQLModel, Field, Relationship
from sqlalchemy import Column, DateTime, text, Numeric, func
from enum import Enum
from sqlalchemy.dialects.postgresql import JSONB
from typing import Optional
from decimal import Decimal
class ActivityCategory(str, Enum):
prayer = "prayer"
meal = "meal"
habit = "habit"
exercise = "exercise"
productivity = "productivity"
class TransactionCategory(str, Enum):
INCOME = "income"
EXPENSE = "expense"
class User(SQLModel, table=True):
__tablename__ = "users"
id: int = Field(default=None, primary_key=True)
phone_number: str = Field(index=True, unique=True)
email: str = Field(index=True, unique=True)
hashed_password: str
name: Optional[str] = None
timezone: str = "Asia/Karachi"
created_at: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc),
sa_column=Column(DateTime(timezone=True), server_default=text("CURRENT_TIMESTAMP"), nullable=False)
)
updated_at: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc),
sa_column=Column(
DateTime(timezone=True),
server_default=func.now(),
onupdate=func.now(), # SQLAlchemy will now handle this dynamically
nullable=False
))
activity_entries: list["ActivityEntry"] = Relationship(back_populates="user")
goals: list["Goal"] = Relationship(back_populates="user")
day_logs: list["DayLog"] = Relationship(back_populates="user")
wallet: "Wallet" = Relationship(back_populates="user")
transactions: list["Transaction"] = Relationship(back_populates="user")
class Goal(SQLModel, table=True):
__tablename__ = "goals"
id: int = Field(default=None, primary_key=True)
user_id: int = Field(
foreign_key="users.id",
index=True
)
date: dt_date = Field(default_factory=dt_date.today, index=True)
title: str = Field(index=True)
description: Optional[str] = None
total_productivity_hours: Optional[float] = Field(default=0.0)
active_status: Optional[bool] = Field(default=None)
activity_entries: list["ActivityEntry"] = Relationship(back_populates="goal")
user: "User" = Relationship(back_populates="goals")
class DayLog(SQLModel, table=True):
__tablename__ = "day_logs"
id: int = Field(default=None, primary_key=True)
user_id: int = Field(
foreign_key="users.id",
index=True
)
date: dt_date = Field(default_factory=dt_date.today, index=True)
bed_time: Optional[time] = None
sleep_time: Optional[time] = None
wake_time: Optional[time] = None
sleep_duration_hours: Optional[float] = None
total_productivity_min: Optional[float] = Field(default=0.0)
total_calories: Optional[int] = None
notes: Optional[str] = None
user: "User" = Relationship(back_populates="day_logs")
activity_entries: list["ActivityEntry"] = Relationship(
back_populates="day_log"
)
transactions: list["Transaction"] = Relationship(back_populates="day_log")
class ActivityEntry(SQLModel, table=True):
__tablename__ = "activity_entries"
id: int = Field(default=None, primary_key=True)
user_id: int = Field(
foreign_key="users.id",
index=True
)
day_log_id: int = Field(
foreign_key="day_logs.id",
index=True
)
goal_id: Optional[int] = Field(
default=None,
foreign_key="goals.id",
index=True
)
goal_name: Optional[str] = None
date: dt_date = Field(default_factory=dt_date.today, index=True)
category: ActivityCategory = Field(index=True)
activity_type_name: str = Field(index=True)
note: Optional[str] = None
#for dynamic fields like location, quantity_text, what_i_did, result_text, jamaat,
#sets, reps, on_time, duration_min, occurred_at, active_status etc.
details: dict = Field(
default_factory=dict,
sa_column=Column(JSONB, server_default='{}')
)
user: "User" = Relationship(back_populates="activity_entries")
goal: Optional["Goal"] = Relationship(back_populates="activity_entries")
day_log: "DayLog" = Relationship(
back_populates="activity_entries"
)
class Wallet(SQLModel, table=True):
"""Stores the running total/balance for the user."""
__tablename__ = "wallets"
id: int = Field(default=None, primary_key=True)
user_id: int = Field(foreign_key="users.id", index=True, unique=True)
# Using Numeric/Decimal for precise money math
balance: Decimal = Field(default=Decimal("0.00"), sa_column=Column(Numeric(10, 2)))
updated_at: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc),
sa_column=Column(DateTime(timezone=True), server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"))
)
user: "User" = Relationship(back_populates="wallet")
transactions: list["Transaction"] = Relationship(back_populates="wallet")
class Transaction(SQLModel, table=True):
"""Records every addition (income) and subtraction (expense)."""
__tablename__ = "transactions"
id: int = Field(default=None, primary_key=True)
user_id: int = Field(foreign_key="users.id", index=True)
day_log_id: int = Field(foreign_key="day_logs.id", index=True)
wallet_id: int = Field(foreign_key="wallets.id", index=True)
date: dt_date = Field(default_factory=dt_date.today, index=True)
category: TransactionCategory = Field(index=True)
amount: Decimal = Field(sa_column=Column(Numeric(10, 2)))
note: Optional[str] = None
# JSONB for the dynamic fields (source, type, litre, dayMode, what_eat)
details: dict = Field(
default_factory=dict,
sa_column=Column(JSONB, server_default='{}')
)
# ADDED: Relationships to map back to your other tables
user: "User" = Relationship(back_populates="transactions")
day_log: "DayLog" = Relationship(back_populates="transactions")
wallet: "Wallet" = Relationship(back_populates="transactions")