Skip to content

Commit dfdc6c1

Browse files
committed
Scheduled job to notify of upcoming favourites
1 parent d62e4a3 commit dfdc6c1

File tree

5 files changed

+110
-15
lines changed

5 files changed

+110
-15
lines changed

apps/notifications/jobs.py

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
from sqlalchemy import and_
12
from main import db
2-
from datetime import datetime
3+
from datetime import datetime, timedelta
34
from flask import current_app as app
45

56
from models import scheduled_task
7+
from models.cfp import FavouriteProposal, Proposal
8+
from models.user import User
69
from models.web_push import PushNotificationJob
10+
from models.notifications import UserNotificationPreference
711
from pywebpush import webpush, WebPushException
812

913

@@ -36,14 +40,49 @@ def deliver_notification(job: PushNotificationJob):
3640
def send_queued_notifications():
3741
jobs = PushNotificationJob.query.where(
3842
PushNotificationJob.state == "queued"
39-
and (
40-
PushNotificationJob.not_before is None
41-
or PushNotificationJob.not_before <= datetime.now()
42-
)
43+
and (PushNotificationJob.not_before is None or PushNotificationJob.not_before <= datetime.now())
4344
).all()
4445

4546
for job in jobs:
4647
deliver_notification(job)
4748
db.session.add(job)
4849

4950
db.session.commit()
51+
52+
53+
@scheduled_task(minutes=15)
54+
def queue_content_notifications(time=None) -> None:
55+
if time is None:
56+
time = datetime.now()
57+
58+
users = User.query.join(
59+
UserNotificationPreference,
60+
User.notification_preferences.and_(UserNotificationPreference.favourited_content),
61+
)
62+
63+
upcoming_content = Proposal.query.filter(
64+
and_(Proposal.scheduled_time >= time, Proposal.scheduled_time <= time + timedelta(minutes=16))
65+
).all()
66+
67+
for user in users:
68+
user_favourites = [f.id for f in user.favourites]
69+
favourites = [p for p in upcoming_content if p.id in user_favourites]
70+
for proposal in favourites:
71+
for target in user.web_push_targets:
72+
related_to = f"favourite,user:{user.id},proposal:{proposal.id},target:{target.id}"
73+
if (
74+
PushNotificationJob.query.where(
75+
PushNotificationJob.related_to == related_to
76+
).one_or_none()
77+
is None
78+
):
79+
job = PushNotificationJob(
80+
target=target,
81+
title=f"{proposal.title} is happening soon at {proposal.scheduled_venue.name}",
82+
related_to=related_to,
83+
not_before=proposal.scheduled_time - timedelta(minutes=15),
84+
)
85+
print(f"Queued notification for {job.related_to}")
86+
db.session.add(job)
87+
88+
db.session.commit()
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""add_unique_index_to_related_to
2+
3+
Revision ID: 91cd34a04a10
4+
Revises: be0934972583
5+
Create Date: 2024-04-21 20:46:28.014878
6+
7+
"""
8+
9+
# revision identifiers, used by Alembic.
10+
revision = '91cd34a04a10'
11+
down_revision = 'be0934972583'
12+
13+
from alembic import op
14+
import sqlalchemy as sa
15+
16+
17+
def upgrade():
18+
# ### commands auto generated by Alembic - please adjust! ###
19+
op.create_unique_constraint(op.f('uq_push_notification_job_related_to'), 'push_notification_job', ['related_to'])
20+
# ### end Alembic commands ###
21+
22+
23+
def downgrade():
24+
# ### commands auto generated by Alembic - please adjust! ###
25+
op.drop_constraint(op.f('uq_push_notification_job_related_to'), 'push_notification_job', type_='unique')
26+
# ### end Alembic commands ###
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""add_indexes_to_notification_preferences
2+
3+
Revision ID: be0934972583
4+
Revises: cd825d16072a
5+
Create Date: 2024-04-21 09:53:36.317560
6+
7+
"""
8+
9+
# revision identifiers, used by Alembic.
10+
revision = 'be0934972583'
11+
down_revision = 'cd825d16072a'
12+
13+
from alembic import op
14+
import sqlalchemy as sa
15+
16+
17+
def upgrade():
18+
# ### commands auto generated by Alembic - please adjust! ###
19+
op.create_index(op.f('ix_user_notification_preference_announcements'), 'user_notification_preference', ['announcements'], unique=False)
20+
op.create_index(op.f('ix_user_notification_preference_favourited_content'), 'user_notification_preference', ['favourited_content'], unique=False)
21+
op.create_index(op.f('ix_user_notification_preference_volunteer_shifts'), 'user_notification_preference', ['volunteer_shifts'], unique=False)
22+
op.drop_index('ix_web_push_target_user_id', table_name='web_push_target')
23+
op.drop_index('ix_web_push_target_user_id_endpoint', table_name='web_push_target')
24+
# ### end Alembic commands ###
25+
26+
27+
def downgrade():
28+
# ### commands auto generated by Alembic - please adjust! ###
29+
op.create_index('ix_web_push_target_user_id_endpoint', 'web_push_target', ['user_id', 'endpoint'], unique=False)
30+
op.create_index('ix_web_push_target_user_id', 'web_push_target', ['user_id'], unique=False)
31+
op.drop_index(op.f('ix_user_notification_preference_volunteer_shifts'), table_name='user_notification_preference')
32+
op.drop_index(op.f('ix_user_notification_preference_favourited_content'), table_name='user_notification_preference')
33+
op.drop_index(op.f('ix_user_notification_preference_announcements'), table_name='user_notification_preference')
34+
# ### end Alembic commands ###

models/notifications.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ class UserNotificationPreference(BaseModel):
88
id = db.Column(db.Integer, primary_key=True)
99
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
1010
created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
11-
volunteer_shifts = db.Column(db.Boolean, default=False, nullable=False)
12-
favourited_content = db.Column(db.Boolean, default=False, nullable=False)
13-
announcements = db.Column(db.Boolean, default=False, nullable=False)
11+
volunteer_shifts = db.Column(db.Boolean, default=False, nullable=False, index=True)
12+
favourited_content = db.Column(db.Boolean, default=False, nullable=False, index=True)
13+
announcements = db.Column(db.Boolean, default=False, nullable=False, index=True)
1414

1515
user = db.relationship("User")
1616

models/web_push.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,11 @@ def __init__(self, user, endpoint, subscription_info, expires=None):
4848
class PushNotificationJob(BaseModel):
4949
__table_name__ = "web_push_notification_job"
5050
id: int = db.Column(db.Integer, primary_key=True)
51-
target_id: int = db.Column(
52-
db.Integer, db.ForeignKey("web_push_target.id"), nullable=False
53-
)
51+
target_id: int = db.Column(db.Integer, db.ForeignKey("web_push_target.id"), nullable=False)
5452
created: datetime = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
55-
state: Literal["queued", "delivered", "failed"] = db.Column(
56-
db.String, default="queued", nullable=False
57-
)
53+
state: Literal["queued", "delivered", "failed"] = db.Column(db.String, default="queued", nullable=False)
5854
not_before: datetime | None = db.Column(db.DateTime, nullable=True)
59-
related_to: str | None = db.Column(db.String, nullable=True)
55+
related_to: str | None = db.Column(db.String, nullable=True, unique=True)
6056
title: str = db.Column(db.String, nullable=False)
6157
body: str | None = db.Column(db.String, nullable=True)
6258
error: str | None = db.Column(db.String, nullable=True)

0 commit comments

Comments
 (0)