Initial commit: habit tracker PWA with Google OAuth, push notifications
FastAPI + SQLAlchemy/Alembic + MariaDB backend with Jinja2/htmx/Alpine server-rendered frontend. Multi-user via Google OAuth, daily habit tracking, monthly/weekly history views, Web Push reminders via APScheduler, and PWA support (manifest, service worker, offline caching). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
from datetime import datetime, time
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, field_validator
|
||||
|
||||
from app.models.habit import ALL_WEEKDAYS_MASK, HabitStatus, HabitType
|
||||
|
||||
|
||||
class HabitBase(BaseModel):
|
||||
name: str
|
||||
habit_type: HabitType
|
||||
weekdays_mask: int = ALL_WEEKDAYS_MASK
|
||||
condition_text: str | None = None
|
||||
reminder_time: time | None = None
|
||||
|
||||
@field_validator("name")
|
||||
@classmethod
|
||||
def name_not_blank(cls, v: str) -> str:
|
||||
v = v.strip()
|
||||
if not v:
|
||||
raise ValueError("습관 이름을 입력해주세요")
|
||||
return v
|
||||
|
||||
@field_validator("condition_text")
|
||||
@classmethod
|
||||
def blank_condition_to_none(cls, v: str | None) -> str | None:
|
||||
if v is None:
|
||||
return None
|
||||
v = v.strip()
|
||||
return v or None
|
||||
|
||||
@field_validator("weekdays_mask")
|
||||
@classmethod
|
||||
def mask_in_range(cls, v: int) -> int:
|
||||
if not (1 <= v <= ALL_WEEKDAYS_MASK):
|
||||
raise ValueError("요일을 최소 하루 이상 선택해주세요")
|
||||
return v
|
||||
|
||||
|
||||
class HabitCreate(HabitBase):
|
||||
pass
|
||||
|
||||
|
||||
class HabitUpdate(HabitBase):
|
||||
pass
|
||||
|
||||
|
||||
class HabitOut(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
name: str
|
||||
habit_type: HabitType
|
||||
status: HabitStatus
|
||||
weekdays_mask: int
|
||||
condition_text: str | None
|
||||
reminder_time: time | None
|
||||
created_at: datetime
|
||||
completed_at: datetime | None
|
||||
|
||||
|
||||
class HabitReorderRequest(BaseModel):
|
||||
habit_ids: list[int]
|
||||
@@ -0,0 +1,45 @@
|
||||
from datetime import date, datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class HabitLogOut(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
habit_id: int
|
||||
log_date: date
|
||||
checked_at: datetime
|
||||
|
||||
|
||||
class TodayItem(BaseModel):
|
||||
habit_id: int
|
||||
name: str
|
||||
habit_type: str
|
||||
condition_text: str | None
|
||||
reminder_time: str | None
|
||||
checked: bool
|
||||
completion_rate: float
|
||||
current_streak: int
|
||||
scheduled_days: int
|
||||
|
||||
|
||||
class MonthlySummaryDay(BaseModel):
|
||||
log_date: date
|
||||
scheduled_count: int
|
||||
checked_count: int
|
||||
|
||||
|
||||
class WeeklyMatrixRow(BaseModel):
|
||||
habit_id: int
|
||||
name: str
|
||||
habit_type: str
|
||||
checks: dict[str, bool | None] # ISO 날짜 문자열 -> 체크 여부 (None이면 그 요일에 예정되지 않음)
|
||||
completion_rate: float # 이번 주, 오늘까지 지난 예정일 중 체크한 비율 (%)
|
||||
|
||||
|
||||
class HabitStats(BaseModel):
|
||||
completion_rate: float # 습관 생성일부터 오늘까지, 예정된 날 중 체크한 비율 (%)
|
||||
current_streak: int # 오늘(또는 어제)부터 거슬러 올라가며 끊기지 않고 체크한 예정일 수
|
||||
scheduled_days: int
|
||||
checked_days: int
|
||||
@@ -0,0 +1,11 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class PushKeys(BaseModel):
|
||||
p256dh: str
|
||||
auth: str
|
||||
|
||||
|
||||
class PushSubscribeRequest(BaseModel):
|
||||
endpoint: str
|
||||
keys: PushKeys
|
||||
Reference in New Issue
Block a user