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:
2026-07-16 18:06:17 +09:00
co-authored by Claude Sonnet 5
commit cee589bb3e
80 changed files with 4695 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
from datetime import date
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.database import get_db
from app.models.user import User
from app.schemas.habit_log import HabitLogOut, MonthlySummaryDay, TodayItem, WeeklyMatrixRow
from app.security import require_login
from app.services import habit_service, log_service
router = APIRouter(prefix="/api", tags=["logs"], dependencies=[Depends(require_login)])
@router.get("/today", response_model=list[TodayItem])
def get_today(db: Session = Depends(get_db), current_user: User = Depends(require_login)):
build_items, quit_items = log_service.get_today_items(db, current_user.id, date.today())
return build_items + quit_items
@router.post("/logs/{habit_id}/toggle")
def toggle_log(habit_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_login)):
habit = habit_service.get_habit(db, habit_id, current_user.id)
if habit is None:
raise HTTPException(status_code=404, detail="습관을 찾을 수 없습니다")
checked, milestone_streak = log_service.toggle_check_and_celebrate(db, habit, date.today())
return {"checked": checked, "milestone_streak": milestone_streak}
@router.get("/logs", response_model=list[HabitLogOut])
def list_logs(
habit_id: int | None = None,
start: date | None = None,
end: date | None = None,
db: Session = Depends(get_db),
current_user: User = Depends(require_login),
):
return log_service.list_logs(db, current_user.id, habit_id=habit_id, start=start, end=end)
@router.get("/logs/summary/monthly", response_model=list[MonthlySummaryDay])
def monthly_summary(
year: int, month: int, db: Session = Depends(get_db), current_user: User = Depends(require_login)
):
return log_service.get_monthly_summary(db, current_user.id, year, month)
@router.get("/logs/summary/weekly", response_model=list[WeeklyMatrixRow])
def weekly_summary(
start_date: date, db: Session = Depends(get_db), current_user: User = Depends(require_login)
):
return log_service.get_weekly_matrix(db, current_user.id, start_date)