Skip to content

Commit

Permalink
Attempting full migration off of lambda endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
voynow committed Nov 16, 2024
1 parent 3f54c98 commit bb188ef
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 453 deletions.
6 changes: 3 additions & 3 deletions api/src/apn.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import logging
import os
import time
import traceback

import httpx
import jwt
from dotenv import load_dotenv

from src.supabase_client import get_user_auth
from src.types.user import UserRow

Expand Down Expand Up @@ -103,7 +103,7 @@ def send_push_notif_wrapper(user: UserRow):
title="TrackFlow 🏃‍♂️🎯",
body="Your training week has been updated!",
)
except ValueError as e:
logger.error(f"Invalid device token for user {user.athlete_id}: {e}")
except Exception as e:
logger.error(f"Failed to send push notification to user {user.athlete_id}: {e}")
logger.error(traceback.format_exc())
raise
49 changes: 46 additions & 3 deletions api/src/main.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import logging
from typing import Optional

from fastapi import BackgroundTasks, Body, Depends, FastAPI, HTTPException, Request, Form
import os

from fastapi import (
BackgroundTasks,
Body,
Depends,
FastAPI,
Form,
HTTPException,
Request,
)
from src import activities, auth_manager, supabase_client, webhook
from src.types.training_week import TrainingWeek
from src.types.update_pipeline import ExeType
from src.types.user import UserRow
from src.types.webhook import StravaEvent
from src.update_pipeline import update_all_users, update_training_week

app = FastAPI()

Expand Down Expand Up @@ -154,3 +164,36 @@ async def strava_webhook(request: Request, background_tasks: BackgroundTasks) ->
strava_event = StravaEvent(**event)
background_tasks.add_task(webhook.maybe_process_strava_event, strava_event)
return {"success": True}


@app.post("/onboarding/")
async def trigger_new_user_onboarding(
user: UserRow = Depends(auth_manager.validate_user),
) -> dict:
"""
Initialize training weeks for new user onboarding
:param user: The authenticated user
:return: Success status
"""
try:
update_training_week(user, ExeType.NEW_WEEK)
update_training_week(user, ExeType.MID_WEEK)
return {"success": True}
except Exception as e:
logger.error(f"Failed to start onboarding: {e}", exc_info=True)
raise HTTPException(status_code=400, detail=str(e))


@app.post("/update-all-users/")
async def update_all_users_trigger(request: Request) -> dict:
"""
Trigger nightly updates for all users
Protected by API key authentication
"""
api_key = request.headers.get("x-api-key")
if api_key != os.environ["API_KEY"]:
raise HTTPException(status_code=403, detail="Invalid API key")

update_all_users()
return {"success": True}
28 changes: 28 additions & 0 deletions api/src/supabase_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,31 @@ def upsert_training_week(
}
table = client.table("training_week")
table.upsert(row_data).execute()


def has_user_updated_today(athlete_id: int) -> bool:
"""
Check if the user has received an update today. Where "today" is defined as
within the past 23 hours and 30 minutes (to account for any delays in
yesterday's evening update).
:param athlete_id: The ID of the athlete
:return: True if the user has received an update today, False otherwise
"""
table = client.table("training_week")
response = (
table.select("*")
.eq("athlete_id", athlete_id)
.order("created_at", desc=True)
.limit(1)
.execute()
)

if not response.data:
return False

# "Has this user posted an activity in the last 23 hours and 30 minutes?"
time_diff = datetime.datetime.now(
datetime.timezone.utc
) - datetime.datetime.fromisoformat(response.data[0]["created_at"])
return time_diff < datetime.timedelta(hours=23, minutes=30)
67 changes: 37 additions & 30 deletions api/src/update_pipeline.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import logging
import traceback

from src.utils import datetime_now_est
from src.supabase_client import has_user_updated_today
from src.activities import (
get_activity_summaries,
get_weekly_summaries,
)
from src.apn import send_push_notif_wrapper
from src.auth_manager import get_strava_client
from src.constants import COACH_ROLE
from src.email_manager import send_alert_email
from src.mid_week_update import generate_mid_week_update
from src.new_training_week import generate_new_training_week
from src.supabase_client import (
get_training_week,
list_users,
upsert_training_week,
)
from src.types.update_pipeline import ExeType
Expand All @@ -24,32 +25,38 @@

def update_training_week(user: UserRow, exe_type: ExeType) -> dict:
"""Single function to handle all training week updates"""
try:
strava_client = get_strava_client(user.athlete_id)

if exe_type == ExeType.NEW_WEEK:
weekly_summaries = get_weekly_summaries(strava_client)
training_week = generate_new_training_week(
sysmsg_base=f"{COACH_ROLE}\nClient Preferences: {user.preferences}",
weekly_summaries=weekly_summaries,
)
else: # ExeType.MID_WEEK
current_week = get_training_week(user.athlete_id)
activity_summaries = get_activity_summaries(strava_client, num_weeks=1)
training_week = generate_mid_week_update(
sysmsg_base=f"{COACH_ROLE}\nClient Preferences: {user.preferences}",
training_week=current_week,
completed_activities=activity_summaries,
)

upsert_training_week(user.athlete_id, training_week)
send_push_notif_wrapper(user)
return {"success": True}

except Exception as e:
logger.error(f"Error processing user {user.athlete_id}: {str(e)}")
send_alert_email(
subject="TrackFlow Alert: Update Error",
text_content=f"Error processing user {user.athlete_id}: {str(e)}\n{traceback.format_exc()}",
strava_client = get_strava_client(user.athlete_id)

if exe_type == ExeType.NEW_WEEK:
weekly_summaries = get_weekly_summaries(strava_client)
training_week = generate_new_training_week(
sysmsg_base=f"{COACH_ROLE}\nClient Preferences: {user.preferences}",
weekly_summaries=weekly_summaries,
)
else: # ExeType.MID_WEEK
current_week = get_training_week(user.athlete_id)
activity_summaries = get_activity_summaries(strava_client, num_weeks=1)
training_week = generate_mid_week_update(
sysmsg_base=f"{COACH_ROLE}\nClient Preferences: {user.preferences}",
training_week=current_week,
completed_activities=activity_summaries,
)
return {"success": False, "error": str(e)}

upsert_training_week(user.athlete_id, training_week)
send_push_notif_wrapper(user)
return {"success": True}


def update_all_users() -> dict:
"""
Evenings excluding Sunday: Send update to users who have not yet triggered an update today
Sunday evening: Send new training week to all active users
"""
if datetime_now_est().weekday() != 6:
for user in list_users():
if not has_user_updated_today(user.athlete_id):
update_training_week(user, ExeType.MID_WEEK)
else:
for user in list_users():
update_training_week(user, ExeType.NEW_WEEK)
return {"success": True}
Loading

0 comments on commit bb188ef

Please sign in to comment.