Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def initialize_firebase():
raise ValueError("GOOGLE_SERVICE_ACCOUNT_PATH environment variable not set.")
else:
firebase_app = firebase_admin.get_app()
logging.info("Firebase app created...")
logging.info("Firebase app created")
return firebase_app


Expand Down Expand Up @@ -198,7 +198,7 @@ def cleanup_expired_tokens():

# Update hourly average capacity every hour
@scheduler.task("cron", id="update_capacity", hour="*")
def scheduled_job():
def update_hourly_avg_capacity():
current_time = datetime.now()
current_day = current_time.strftime("%A").upper()
current_hour = current_time.hour
Expand Down
77 changes: 77 additions & 0 deletions migrations/versions/723cd68cf306_add_workout_reminders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""add workout reminders

Revision ID: 723cd68cf306
Revises: 7a3c14648e56
Create Date: 2025-04-01 01:06:38.476352

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = '723cd68cf306'
down_revision = '7a3c14648e56'
branch_labels = None
depends_on = None

def upgrade():

op.execute("""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.tables WHERE table_name = 'workout_reminder'
) THEN
CREATE TABLE workout_reminder (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
days_of_week dayofweekenum[] NOT NULL,
reminder_time TIME NOT NULL,
is_active BOOLEAN DEFAULT TRUE
);
END IF;
END
$$;
""")

op.execute("""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'users' AND column_name = 'fcm_token'
) THEN
ALTER TABLE users ADD COLUMN fcm_token VARCHAR NOT NULL DEFAULT 'unset';
ALTER TABLE users ALTER COLUMN fcm_token DROP DEFAULT;
END IF;
END
$$;
""")


def downgrade():
op.execute("""
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.tables WHERE table_name = 'workout_reminder'
) THEN
DROP TABLE workout_reminder;
END IF;
END
$$;
""")

op.execute("""
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'users' AND column_name = 'fcm_token'
) THEN
ALTER TABLE users DROP COLUMN fcm_token;
END IF;
END
$$;
""")
21 changes: 19 additions & 2 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,8 @@ enum MuscleGroup {

type Mutation {
createGiveaway(name: String!): Giveaway
createUser(email: String!, encodedImage: String, name: String!, netId: String!): User
editUser(email: String, encodedImage: String, name: String, netId: String!): User
createUser(email: String!, encodedImage: String, fcmToken: String!, name: String!, netId: String!): User
editUser(email: String, encodedImage: String, fcmToken: String, name: String, netId: String!): User
enterGiveaway(giveawayId: Int!, userNetId: String!): GiveawayInstance
setWorkoutGoals(userId: Int!, workoutGoal: [String]!): User
logWorkout(facilityId: Int!, userId: Int!, workoutTime: DateTime!): Workout
Expand All @@ -229,6 +229,10 @@ type Mutation {
createCapacityReminder(capacityPercent: Int!, daysOfWeek: [String]!, fcmToken: String!, gyms: [String]!): CapacityReminder
editCapacityReminder(capacityPercent: Int!, daysOfWeek: [String]!, gyms: [String]!, reminderId: Int!): CapacityReminder
deleteCapacityReminder(reminderId: Int!): CapacityReminder
createWorkoutReminder(daysOfWeek: [String]!, reminderTime: Time!, userId: Int!): WorkoutReminder
toggleWorkoutReminder(reminderId: Int!): WorkoutReminder
editWorkoutReminder(daysOfWeek: [String], reminderId: Int!, reminderTime: Time, userId: Int): WorkoutReminder
deleteWorkoutReminder(reminderId: Int!): WorkoutReminder
addFriend(friendId: Int!, userId: Int!): Friendship
acceptFriendRequest(friendshipId: Int!): Friendship
removeFriend(friendId: Int!, userId: Int!): RemoveFriend
Expand Down Expand Up @@ -273,6 +277,7 @@ type Query {
getWorkoutGoals(id: Int!): [String]
getUserStreak(id: Int!): JSONString
getHourlyAverageCapacitiesByFacilityId(facilityId: Int): [HourlyAverageCapacity]
getWorkoutRemindersByUserId(userId: Int): [WorkoutReminder]
getUserFriends(userId: Int!): [User]
}

Expand Down Expand Up @@ -301,6 +306,8 @@ enum ReportType {
OTHER
}

scalar Time

type User {
id: ID!
email: String
Expand All @@ -310,7 +317,9 @@ type User {
maxStreak: Int
workoutGoal: [DayOfWeekGraphQLEnum]
encodedImage: String
fcmToken: String!
giveaways: [Giveaway]
workoutReminders: [WorkoutReminder]
friendRequestsSent: [Friendship]
friendRequestsReceived: [Friendship]
friendships: [Friendship]
Expand All @@ -323,3 +332,11 @@ type Workout {
userId: Int!
facilityId: Int!
}

type WorkoutReminder {
id: ID!
userId: Int!
daysOfWeek: [DayOfWeekGraphQLEnum]
reminderTime: String!
isActive: Boolean
}
2 changes: 2 additions & 0 deletions src/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class User(Base):
max_streak = Column(Integer, nullable=True)
workout_goal = Column(ARRAY(Enum(DayOfWeekEnum)), nullable=True)
encoded_image = Column(String, nullable=True)
fcm_token = Column(String, nullable=False)
workout_reminders = relationship("WorkoutReminder")

friend_requests_sent = relationship("Friendship",
foreign_keys="Friendship.user_id",
Expand Down
24 changes: 24 additions & 0 deletions src/models/workout_reminder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from sqlalchemy import Column, Integer, ForeignKey, TIME, Boolean
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy import Enum as SQLAEnum
from src.models.enums import DayOfWeekEnum
from src.database import Base

class WorkoutReminder(Base):
"""
A workout reminder for an Uplift user.
Attributes:
- `id` The ID of the workout reminder.
- `user_id` The ID of the user who owns this reminder.
- `days_of_week` The days of the week when the reminder is active.
- `reminder_time` The time of day the reminder is scheduled for.
- `is_active` Whether the reminder is currently active (default is True).
"""

__tablename__ = "workout_reminder"

id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
days_of_week = Column(ARRAY(SQLAEnum(DayOfWeekEnum)), nullable=False)
reminder_time = Column(TIME, nullable=False)
is_active = Column(Boolean, default=True)
128 changes: 125 additions & 3 deletions src/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from src.models.giveaway import Giveaway as GiveawayModel
from src.models.giveaway import GiveawayInstance as GiveawayInstanceModel
from src.models.workout import Workout as WorkoutModel
from src.models.workout_reminder import WorkoutReminder as WorkoutReminderModel
from src.models.report import Report as ReportModel
from src.models.hourly_average_capacity import HourlyAverageCapacity as HourlyAverageCapacityModel
from src.database import db_session
Expand Down Expand Up @@ -295,6 +296,16 @@ class Meta:
exclude_fields = ("fcm_token",)


# MARK: - Workout Reminder


class WorkoutReminder(SQLAlchemyObjectType):
class Meta:
model = WorkoutReminderModel

days_of_week = graphene.List(DayOfWeekGraphQLEnum)


# MARK: - Query


Expand All @@ -320,6 +331,7 @@ class Query(graphene.ObjectType):
get_hourly_average_capacities_by_facility_id = graphene.List(
HourlyAverageCapacity, facility_id=graphene.Int(), description="Get all facility hourly average capacities."
)
get_workout_reminders_by_user_id = graphene.List(WorkoutReminder, user_id=graphene.Int(), description="Get the workout reminders of a user by user ID.")
get_user_friends = graphene.List(
User,
user_id=graphene.Int(required=True),
Expand Down Expand Up @@ -450,6 +462,10 @@ def resolve_get_hourly_average_capacities_by_facility_id(self, info, facility_id
raise GraphQLError("Invalid facility ID.")
query = HourlyAverageCapacity.get_query(info).filter(HourlyAverageCapacityModel.facility_id == facility_id)
return query.all()

def resolve_get_workout_reminders_by_user_id(self, info, user_id):
query = WorkoutReminder.get_query(info).filter(WorkoutReminderModel.user_id == user_id)
return query.all()

@jwt_required()
def resolve_get_user_friends(self, info, user_id):
Expand Down Expand Up @@ -551,11 +567,12 @@ class Arguments:
name = graphene.String(required=True)
net_id = graphene.String(required=True)
email = graphene.String(required=True)
fcm_token = graphene.String(required=True)
encoded_image = graphene.String(required=False)

Output = User

def mutate(self, info, name, net_id, email, encoded_image=None):
def mutate(self, info, name, net_id, email, fcm_token, encoded_image=None):
# Check if a user with the given NetID already exists
existing_user = db_session.query(UserModel).filter(UserModel.net_id == net_id).first()
final_photo_url = None
Expand All @@ -577,7 +594,7 @@ def mutate(self, info, name, net_id, email, encoded_image=None):
print(f"Request failed: {e}")
raise GraphQLError("Failed to upload photo.")

new_user = UserModel(name=name, net_id=net_id, email=email, encoded_image=final_photo_url)
new_user = UserModel(name=name, net_id=net_id, email=email, encoded_image=final_photo_url, fcm_token=fcm_token)
db_session.add(new_user)
db_session.commit()

Expand All @@ -589,11 +606,12 @@ class Arguments:
name = graphene.String(required=False)
net_id = graphene.String(required=True)
email = graphene.String(required=False)
fcm_token = graphene.String(required=False)
encoded_image = graphene.String(required=False)

Output = User

def mutate(self, info, net_id, name=None, email=None, encoded_image=None):
def mutate(self, info, net_id, name=None, email=None, fcm_token=None, encoded_image=None):
existing_user = db_session.query(UserModel).filter(UserModel.net_id == net_id).first()
if not existing_user:
raise GraphQLError("User with given net id does not exist.")
Expand All @@ -602,6 +620,8 @@ def mutate(self, info, net_id, name=None, email=None, encoded_image=None):
existing_user.name = name
if email is not None:
existing_user.email = email
if fcm_token is not None:
existing_user.fcm_token = fcm_token
if encoded_image is not None:
upload_url = os.getenv("DIGITAL_OCEAN_URL") # Base URL for upload endpoint
if not upload_url:
Expand Down Expand Up @@ -1067,6 +1087,104 @@ def mutate(self, info, user_id):
return GetPendingFriendRequests(pending_requests=pending)


class CreateWorkoutReminder(graphene.Mutation):
class Arguments:
user_id = graphene.Int(required=True)
reminder_time = graphene.Time(required=True)
days_of_week = graphene.List(graphene.String, required=True)

Output = WorkoutReminder

def mutate(self, info, user_id, reminder_time, days_of_week):
# Validate user existence
user = db_session.query(UserModel).filter_by(id=user_id).first()
if not user:
raise GraphQLError("User not found.")

# Validate days of the week
validated_workout_days = []
for day in days_of_week:
try:
validated_workout_days.append(DayOfWeekGraphQLEnum[day.upper()].value)
except KeyError:
raise GraphQLError(f"Invalid day of the week: {day}")

reminder = WorkoutReminderModel(
user_id=user_id, reminder_time=reminder_time, days_of_week=validated_workout_days
)

db_session.add(reminder)
db_session.commit()

return reminder


class ToggleWorkoutReminder(graphene.Mutation):
class Arguments:
reminder_id = graphene.Int(required=True)

Output = WorkoutReminder

def mutate(self, info, reminder_id):
reminder = db_session.query(WorkoutReminderModel).filter_by(id=reminder_id).first()
if not reminder:
raise GraphQLError("Workout reminder not found.")

reminder.is_active = not reminder.is_active
db_session.commit()

return reminder


class EditWorkoutReminder(graphene.Mutation):
class Arguments:
reminder_id = graphene.Int(required=True)
user_id = graphene.Int(required=False)
reminder_time = graphene.Time(required=False)
days_of_week = graphene.List(graphene.String, required=False)

Output = WorkoutReminder

def mutate(self, info, reminder_id, user_id=None, reminder_time=None, days_of_week=None):
reminder = db_session.query(WorkoutReminderModel).filter_by(id=reminder_id).first()
if not reminder:
raise GraphQLError("Workout reminder not found.")

if user_id is not None:
reminder.user_id = user_id
if reminder_time is not None:
reminder.reminder_time = reminder_time
if days_of_week is not None:
validated_days = []
for day in days_of_week:
try:
validated_days.append(DayOfWeekGraphQLEnum[day.upper()].value)
except KeyError:
raise GraphQLError(f"Invalid day of the week: {day}")
reminder.days_of_week = validated_days

db_session.commit()

return reminder


class DeleteWorkoutReminder(graphene.Mutation):
class Arguments:
reminder_id = graphene.Int(required=True)

Output = WorkoutReminder

def mutate(self, info, reminder_id):
reminder = db_session.query(WorkoutReminderModel).filter_by(id=reminder_id).first()
if not reminder:
raise GraphQLError("Workout reminder not found.")

db_session.delete(reminder)
db_session.commit()

return reminder


class Mutation(graphene.ObjectType):
create_giveaway = CreateGiveaway.Field(description="Creates a new giveaway.")
create_user = CreateUser.Field(description="Creates a new user.")
Expand All @@ -1084,6 +1202,10 @@ class Mutation(graphene.ObjectType):
create_capacity_reminder = CreateCapacityReminder.Field(description="Create a new capacity reminder.")
edit_capacity_reminder = EditCapacityReminder.Field(description="Edit capacity reminder.")
delete_capacity_reminder = DeleteCapacityReminder.Field(description="Delete a capacity reminder")
create_workout_reminder = CreateWorkoutReminder.Field(description="Create a new workout reminder.")
toggle_workout_reminder = ToggleWorkoutReminder.Field(description="Toggle a workout reminder on or off.")
edit_workout_reminder = EditWorkoutReminder.Field(description="Edit a workout reminder.")
delete_workout_reminder = DeleteWorkoutReminder.Field(description="Delete a workout reminder.")
add_friend = AddFriend.Field(description="Send a friend request to another user.")
accept_friend_request = AcceptFriendRequest.Field(description="Accept a friend request.")
remove_friend = RemoveFriend.Field(description="Remove a friendship.")
Expand Down
Loading