Skip to content

Commit 6ac544b

Browse files
authored
Merge pull request #211 from cuappdev/master
Dev to Prod Schema Migrations
2 parents a92b822 + 65d1af8 commit 6ac544b

15 files changed

Lines changed: 858 additions & 31 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ build/
1616
Archive
1717
scripts
1818
*.sqlite3
19-
service-account-key.json
19+
service-account-key.json
20+
docker-compose.yml

app_factory.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import logging
22
from datetime import timedelta, timezone
33
from flask_jwt_extended import JWTManager
4+
from src.utils.constants import SERVICE_ACCOUNT_PATH, JWT_SECRET_KEY
45
from datetime import datetime
56
from flask import Flask, render_template
67
from graphene import Schema
78
from graphql.utils import schema_printer
8-
from src.utils.constants import JWT_SECRET_KEY
99
from src.database import db_session, init_db
1010
from src.database import Base as db
1111
from src.database import db_url, db_user, db_password, db_name, db_host, db_port
@@ -14,6 +14,20 @@
1414
from flasgger import Swagger
1515
from flask_graphql import GraphQLView
1616
from src.models.token_blacklist import TokenBlocklist
17+
import firebase_admin
18+
from firebase_admin import credentials
19+
20+
def initialize_firebase():
21+
if not firebase_admin._apps:
22+
if SERVICE_ACCOUNT_PATH:
23+
cred = credentials.Certificate(SERVICE_ACCOUNT_PATH)
24+
firebase_app = firebase_admin.initialize_app(cred)
25+
else:
26+
raise ValueError("GOOGLE_SERVICE_ACCOUNT_PATH environment variable not set.")
27+
else:
28+
firebase_app = firebase_admin.get_app()
29+
logging.info("Firebase app created...")
30+
return firebase_app
1731

1832

1933
# Set up logging at module level
@@ -32,6 +46,7 @@ def create_app(run_migrations=False):
3246
Configured Flask application
3347
"""
3448
logger.info("Initializing application")
49+
initialize_firebase()
3550

3651
# Create and configure Flask app
3752
app = Flask(__name__)
@@ -56,7 +71,7 @@ def create_app(run_migrations=False):
5671
schema = Schema(query=Query, mutation=Mutation)
5772
swagger = Swagger(app)
5873

59-
app.config["JWT_SECRET_KEY"] = JWT_SECRET_KEY
74+
app.config['JWT_SECRET_KEY'] = JWT_SECRET_KEY
6075
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=1)
6176
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=30)
6277

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""merge branches
2+
3+
Revision ID: 3c406131c004
4+
Revises: 7a3c14648e56
5+
Create Date: 2025-03-29 00:36:22.980924
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '3c406131c004'
14+
down_revision = '7a3c14648e56'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
pass
21+
22+
23+
def downgrade():
24+
pass
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""add capacity reminder model
2+
3+
Revision ID: 7a3c14648e56
4+
Revises: add99ce06ff5
5+
Create Date: 2025-03-19 17:32:02.592027
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
from sqlalchemy.dialects import postgresql
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = '7a3c14648e56'
15+
down_revision = 'add99ce06ff5'
16+
branch_labels = None
17+
depends_on = None
18+
19+
capacity_reminder_gym_enum = postgresql.ENUM(
20+
'TEAGLEUP', 'TEAGLEDOWN', 'HELENNEWMAN', 'TONIMORRISON', 'NOYES',
21+
name='capacityremindergym', create_type=False
22+
)
23+
24+
day_of_week_enum = postgresql.ENUM(
25+
'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday',
26+
name='dayofweekenum', create_type=False
27+
)
28+
29+
def upgrade():
30+
op.execute("""
31+
DO $$
32+
BEGIN
33+
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'capacity_reminder') THEN
34+
CREATE TABLE capacity_reminder (
35+
id SERIAL PRIMARY KEY,
36+
fcm_token VARCHAR NOT NULL,
37+
gyms capacityremindergym[] NOT NULL,
38+
capacity_threshold INTEGER NOT NULL,
39+
days_of_week dayofweekenum[] NOT NULL,
40+
is_active BOOLEAN DEFAULT TRUE NOT NULL
41+
);
42+
END IF;
43+
END $$;
44+
""")
45+
46+
47+
def downgrade():
48+
op.execute("""
49+
DO $$
50+
BEGIN
51+
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'capacity_reminder') THEN
52+
DROP TABLE capacity_reminder;
53+
END IF;
54+
END $$;
55+
""")
56+
57+
op.execute("""
58+
DO $$
59+
BEGIN
60+
IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'capacityremindergym')
61+
AND NOT EXISTS (
62+
SELECT 1 FROM pg_attribute
63+
WHERE atttypid = (SELECT oid FROM pg_type WHERE typname = 'capacityremindergym')
64+
) THEN
65+
DROP TYPE capacityremindergym CASCADE;
66+
END IF;
67+
END $$ LANGUAGE plpgsql;
68+
""")
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Add friends table
2+
3+
Revision ID: add_friends_table
4+
Revises: 3c406131c004
5+
Create Date: 2025-03-29 00:55:00.000000
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
from datetime import datetime
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'add_friends_table'
15+
down_revision = '3c406131c004'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# Create friends table
22+
op.create_table('friends',
23+
sa.Column('user_id', sa.Integer(), nullable=False),
24+
sa.Column('friend_id', sa.Integer(), nullable=False),
25+
sa.Column('created_at', sa.DateTime(), nullable=True, server_default=sa.text('CURRENT_TIMESTAMP')),
26+
sa.Column('is_accepted', sa.Boolean(), nullable=True, server_default=sa.text('false')),
27+
sa.Column('accepted_at', sa.DateTime(), nullable=True),
28+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
29+
sa.ForeignKeyConstraint(['friend_id'], ['users.id'], ondelete='CASCADE'),
30+
sa.PrimaryKeyConstraint('user_id', 'friend_id')
31+
)
32+
33+
34+
def downgrade():
35+
# Drop friends table
36+
op.drop_table('friends')

requirements.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Flask-Script==2.0.5
2424
Flask-SQLAlchemy==2.3.1
2525
Flask-RESTful==0.3.10
2626
flasgger==0.9.7.1
27-
google-auth==1.12.0
27+
google-auth==2.14.1
2828
graphene==2.1.3
2929
graphene-sqlalchemy==2.3.0
3030
graphql-core==2.1
@@ -79,4 +79,5 @@ wcwidth==0.2.6
7979
Werkzeug==2.2.2
8080
zipp==3.15.0
8181
sentry-sdk==2.13.0
82-
flask_jwt_extended==4.7.1
82+
flask_jwt_extended==4.7.1
83+
firebase-admin==6.4.0

schema.graphql

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,23 @@ type Capacity {
3838
updated: Int!
3939
}
4040

41+
type CapacityReminder {
42+
id: ID!
43+
fcmToken: String!
44+
gyms: [CapacityReminderGym]!
45+
capacityThreshold: Int!
46+
daysOfWeek: [DayOfWeekEnum]!
47+
isActive: Boolean
48+
}
49+
50+
enum CapacityReminderGym {
51+
TEAGLEUP
52+
TEAGLEDOWN
53+
HELENNEWMAN
54+
TONIMORRISON
55+
NOYES
56+
}
57+
4158
type Class {
4259
id: ID!
4360
name: String!
@@ -71,6 +88,16 @@ type CreateReport {
7188

7289
scalar DateTime
7390

91+
enum DayOfWeekEnum {
92+
MONDAY
93+
TUESDAY
94+
WEDNESDAY
95+
THURSDAY
96+
FRIDAY
97+
SATURDAY
98+
SUNDAY
99+
}
100+
74101
enum DayOfWeekGraphQLEnum {
75102
MONDAY
76103
TUESDAY
@@ -109,6 +136,21 @@ enum FacilityType {
109136
COURT
110137
}
111138

139+
type Friendship {
140+
id: ID!
141+
userId: Int!
142+
friendId: Int!
143+
createdAt: DateTime
144+
isAccepted: Boolean
145+
acceptedAt: DateTime
146+
user: User
147+
friend: User
148+
}
149+
150+
type GetPendingFriendRequests {
151+
pendingRequests: [Friendship]
152+
}
153+
112154
type Giveaway {
113155
id: ID!
114156
name: String!
@@ -184,6 +226,13 @@ type Mutation {
184226
refreshAccessToken: RefreshAccessToken
185227
createReport(createdAt: DateTime!, description: String!, gymId: Int!, issue: String!): CreateReport
186228
deleteUser(userId: Int!): User
229+
createCapacityReminder(capacityPercent: Int!, daysOfWeek: [String]!, fcmToken: String!, gyms: [String]!): CapacityReminder
230+
editCapacityReminder(capacityPercent: Int!, daysOfWeek: [String]!, gyms: [String]!, reminderId: Int!): CapacityReminder
231+
deleteCapacityReminder(reminderId: Int!): CapacityReminder
232+
addFriend(friendId: Int!, userId: Int!): Friendship
233+
acceptFriendRequest(friendshipId: Int!): Friendship
234+
removeFriend(friendId: Int!, userId: Int!): RemoveFriend
235+
getPendingFriendRequests(userId: Int!): GetPendingFriendRequests
187236
}
188237

189238
type OpenHours {
@@ -215,6 +264,7 @@ enum PriceType {
215264
type Query {
216265
getAllGyms: [Gym]
217266
getUserByNetId(netId: String): [User]
267+
getUsersFriends(id: Int): [User]
218268
getUsersByGiveawayId(id: Int): [User]
219269
getWeeklyWorkoutDays(id: Int): [String]
220270
getWorkoutsById(id: Int): [Workout]
@@ -223,12 +273,17 @@ type Query {
223273
getWorkoutGoals(id: Int!): [String]
224274
getUserStreak(id: Int!): JSONString
225275
getHourlyAverageCapacitiesByFacilityId(facilityId: Int): [HourlyAverageCapacity]
276+
getUserFriends(userId: Int!): [User]
226277
}
227278

228279
type RefreshAccessToken {
229280
newAccessToken: String
230281
}
231282

283+
type RemoveFriend {
284+
success: Boolean
285+
}
286+
232287
type Report {
233288
id: ID!
234289
createdAt: DateTime!
@@ -256,6 +311,10 @@ type User {
256311
workoutGoal: [DayOfWeekGraphQLEnum]
257312
encodedImage: String
258313
giveaways: [Giveaway]
314+
friendRequestsSent: [Friendship]
315+
friendRequestsReceived: [Friendship]
316+
friendships: [Friendship]
317+
friends: [User]
259318
}
260319

261320
type Workout {

src/models/capacity_reminder.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from sqlalchemy import Column, Integer, ForeignKey, ARRAY, Boolean, Table, String
2+
from sqlalchemy.orm import relationship
3+
from sqlalchemy import Enum as SQLAEnum
4+
from src.models.enums import DayOfWeekEnum, CapacityReminderGym
5+
from src.database import Base
6+
7+
class CapacityReminder(Base):
8+
"""
9+
A capacity reminder for an Uplift user.
10+
Attributes:
11+
- `id` The ID of the capacity reminder.
12+
- `fcm_token` FCM token used to send notifications to the user's device.
13+
- `user_id` The ID of the user who owns this reminder.
14+
- `gyms` The list of gyms the user wants to monitor for capacity.
15+
- `capacity_threshold` Notify user when gym capacity dips below this percentage.
16+
- `days_of_week` The days of the week when the reminder is active.
17+
- `is_active` Whether the reminder is currently active (default is True).
18+
"""
19+
20+
__tablename__ = "capacity_reminder"
21+
22+
id = Column(Integer, primary_key=True)
23+
fcm_token = Column(String, nullable=False)
24+
gyms = Column(ARRAY(SQLAEnum(CapacityReminderGym)), nullable=False)
25+
capacity_threshold = Column(Integer, nullable=False)
26+
days_of_week = Column(ARRAY(SQLAEnum(DayOfWeekEnum)), nullable=False)
27+
is_active = Column(Boolean, default=True)

src/models/enums.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,18 @@ class DayOfWeekGraphQLEnum(GrapheneEnum):
1919
THURSDAY = "THURSDAY"
2020
FRIDAY = "FRIDAY"
2121
SATURDAY = "SATURDAY"
22-
SUNDAY = "SUNDAY"
22+
SUNDAY = "SUNDAY"
23+
24+
class CapacityReminderGym(enum.Enum):
25+
TEAGLEUP = "TEAGLEUP"
26+
TEAGLEDOWN = "TEAGLEDOWN"
27+
HELENNEWMAN = "HELENNEWMAN"
28+
TONIMORRISON = "TONIMORRISON"
29+
NOYES = "NOYES"
30+
31+
class CapacityReminderGymGraphQLEnum(GrapheneEnum):
32+
TEAGLEUP = "TEAGLEUP"
33+
TEAGLEDOWN = "TEAGLEDOWN"
34+
HELENNEWMAN = "HELENNEWMAN"
35+
TONIMORRISON = "TONIMORRISON"
36+
NOYES = "NOYES"

0 commit comments

Comments
 (0)