Skip to content

Commit

Permalink
Merge pull request #311 from tfnribeiro/add-notification-endpoint
Browse files Browse the repository at this point in the history
Add notification endpoint
  • Loading branch information
mircealungu authored Dec 5, 2024
2 parents 2d50d2f + 3249d7d commit a9c5c50
Show file tree
Hide file tree
Showing 10 changed files with 250 additions and 6 deletions.
1 change: 0 additions & 1 deletion tools/crawl_summary/crawl_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import inspect
import json
import pathlib
from zeeguu.core.util.time import get_server_time_utc

STR_DATETIME_FORMAT = "%d_%m_%y_%H_%M_%S"
CRAWL_REPORT_DATA = os.environ.get(
Expand Down
12 changes: 12 additions & 0 deletions tools/migrations/24-12-02--add_notification_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE TABLE `zeeguu_test`.`notification` (
`id` INT NOT NULL AUTO_INCREMENT,
`type` VARCHAR(45) NULL,
PRIMARY KEY (`id`)
);

INSERT INTO
`notification`
VALUES
(1, 'EXERCISE_AVAILABLE'),
(2, 'NEW_ARTICLE_AVAILABLE'),
(3, 'DAILY_LOGIN');
12 changes: 12 additions & 0 deletions tools/migrations/24-12-02--add_user_notification_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CREATE TABLE `zeeguu_test`.`user_notification` (
`id` INT NOT NULL AUTO_INCREMENT,
`user_id` INT NULL,
`notification_id` INT NULL,
`notification_date` DATETIME NULL,
`notification_click_time` DATETIME NULL,
PRIMARY KEY (`id`),
INDEX `user_notification_ibfk_1_idx` (`user_id` ASC),
INDEX `user_notification_ibfk_2_idx` (`notification_id` ASC),
CONSTRAINT `user_notification_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `zeeguu_test`.`user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `user_notification_ibfk_2` FOREIGN KEY (`notification_id`) REFERENCES `zeeguu_test`.`notification` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
);
1 change: 1 addition & 0 deletions zeeguu/api/endpoints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from . import accounts
from . import speech
from . import own_texts
from . import user_notifications
from .student import *
from .nlp import *
from .reading_sessions import *
94 changes: 94 additions & 0 deletions zeeguu/api/endpoints/user_notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import flask


from zeeguu.api.utils.json_result import json_result
from zeeguu.api.utils.route_wrappers import cross_domain, requires_session
from . import api, db_session
from .exercises import scheduled_bookmarks_to_study
from zeeguu.core.model.user_notification import UserNotification
from zeeguu.core.model.notification import Notification
from zeeguu.core.model.user import User
from zeeguu.core.content_recommender.elastic_recommender import (
article_recommendations_for_user,
)
from zeeguu.core.model.session import Session
from datetime import datetime, timedelta

NOTIFICATION_TYPES = {
"EXERCISES": {
"message": "You have exercises scheduled for today!",
"url": "/exercises",
},
"ARTICLES": {
"message": "You have new articles for your topics!",
"url": "/articles",
},
"DAILY_LOGIN": {
"message": "Try reading an article with Zeeguu!",
"url": "/articles",
},
}


@api.route("/get_notification_for_user", methods=["GET"])
@cross_domain
@requires_session
def get_notification_for_user():
"""
For now, we try to check if the user has any scheduled bookmarks
to practice. Otherwise, we will invite the user to check articles.
"""
notification_data = {"notification_available": True}
user = User.find_by_id(flask.g.user_id)

# Is there at least one exercise for the user?
if scheduled_bookmarks_to_study(1):
user_notification = UserNotification.create_user_notification(
user.id, Notification.EXERCISE_AVAILABLE, db_session
)
db_session.commit()
notification_data.update(NOTIFICATION_TYPES["EXERCISES"])
notification_data["user_notification_id"] = user_notification.id
return json_result(notification_data)

# Is there new articles for the user?
articles = article_recommendations_for_user(user, 3)
last_activity_date_for_user = Session.get_last_use_for_user(user.id)
if last_activity_date_for_user is None:
# If the user has no sessions, assume they have not logged in.
last_activity_date_for_user = datetime.min

if any([a.published_time > last_activity_date_for_user for a in articles]):
user_notification = UserNotification.create_user_notification(
user.id, Notification.NEW_ARTICLE_AVAILABLE, db_session
)
db_session.commit()
notification_data.update(NOTIFICATION_TYPES["ARTICLES"])
notification_data["user_notification_id"] = user_notification.id
return json_result(notification_data)

# Daily login
if (datetime.now() - last_activity_date_for_user).days >= 1:
user_notification = UserNotification.create_user_notification(
user.id, Notification.DAILY_LOGIN, db_session
)
db_session.commit()
notification_data.update(NOTIFICATION_TYPES["DAILY_LOGIN"])
notification_data["user_notification_id"] = user_notification.id
return json_result(notification_data)

notification_data["notification_available"] = False
return json_result(notification_data)


@api.route("/set_notification_click_date", methods=["POST"])
@cross_domain
@requires_session
def set_notification_click_date():
data = flask.request.form
# user = User.find_by_id(flask.g.user_id)
user_notification_id = data.get("user_notification_id", None)
UserNotification.update_user_notification_time(user_notification_id, db_session)
db_session.commit()

return "OK"
50 changes: 50 additions & 0 deletions zeeguu/core/model/notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import sqlalchemy
from sqlalchemy import Column, Integer

from zeeguu.core.model import db


class Notification(db.Model):
"""
A Notification reflects the different types of notifications
that the system is able to output.
"""

__table_args__ = {"mysql_collate": "utf8_bin"}

id = Column(Integer, primary_key=True)
type = db.Column(db.String)

# Should match the ids in the Database
# this class is defined to make it easier to
# quickly access the code.

EXERCISE_AVAILABLE = 1
NEW_ARTICLE_AVAILABLE = 2
DAILY_LOGIN = 3

def __init__(self):
self.type = type

def __repr__(self):
return f"<Notification: {self.id} Type: {self.type}>"

@classmethod
def find(cls, type):
try:
notification = cls.query.filter(cls.type == type).one()
return notification
except sqlalchemy.orm.exc.NoResultFound:
return None

@classmethod
def find_by_id(cls, i):
try:
notification = cls.query.filter(cls.id == i).one()
return notification
except Exception as e:
from sentry_sdk import capture_exception

capture_exception(e)
return None
12 changes: 12 additions & 0 deletions zeeguu/core/model/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ def __init__(self, user, _uuid: str):
def update_use_date(self):
self.last_use = datetime.datetime.now()

@classmethod
def get_last_use_for_user(cls, user_id):
try:
return (
cls.query.filter(cls.user_id == user_id)
.order_by(cls.last_use.desc())
.limit(1)
.one()
).last_use
except NoResultFound:
return None

@classmethod
def create_for_user(cls, user):

Expand Down
6 changes: 3 additions & 3 deletions zeeguu/core/model/topic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from zeeguu.core.model import db
from zeeguu.core.model.language import Language
from zeeguu.core.model.article_topic_map import ArticleTopicMap
from zeeguu.core.util.time import get_server_time_utc
from datetime import datetime


class Topic(db.Model):
Expand Down Expand Up @@ -103,7 +103,7 @@ def update_available_topic_cache():
)
cls.language_topic_available_cache[language.id] = (
topics_for_language,
get_server_time_utc(),
datetime.now(),
)

if language is None:
Expand All @@ -115,7 +115,7 @@ def update_available_topic_cache():
if last_check is None:
update_available_topic_cache()
else:
time_since_last_check = get_server_time_utc() - last_check
time_since_last_check = datetime.now() - last_check
if time_since_last_check.days > 7:
update_available_topic_cache()

Expand Down
4 changes: 2 additions & 2 deletions zeeguu/core/model/user_feedback.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from zeeguu.core.model.user import User
from zeeguu.core.model.url import Url
from zeeguu.core.model.feedback_component import FeedbackComponent
from zeeguu.core.util.time import get_server_time_utc
from datetime import datetime

import sqlalchemy

Expand Down Expand Up @@ -42,7 +42,7 @@ def __init__(
self.user = user
self.feedback_component = feedback_component
self.message = message
self.report_time = get_server_time_utc()
self.report_time = datetime.now()
self.url = url

def __str__(self):
Expand Down
64 changes: 64 additions & 0 deletions zeeguu/core/model/user_notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import sqlalchemy

from zeeguu.core.model import db
from zeeguu.core.model.user import User
from zeeguu.core.model.notification import Notification
from datetime import datetime


class UserNotification(db.Model):
"""
A Notification that was sent to the user.
If the user clicks it, the notification_click_time will have the datetime
when that click was performed. IF not, this field will be null
"""

__table_args__ = {"mysql_collate": "utf8_bin"}

id = db.Column(db.Integer, primary_key=True)

user_id = db.Column(db.Integer, db.ForeignKey(User.id))
notification_id = db.Column(db.Integer, db.ForeignKey(Notification.id))
notification_date = db.Column(db.DateTime)
notification_click_time = db.Column(db.DateTime, nullable=True)

def __init__(self, user_id, notification_id):
self.user_id = user_id
self.notification_id = notification_id
self.notification_date = datetime.now()

def __repr__(self):
return f"<UserNotification({self.id}): User: {self.user_id}, Notification: {self.notification_id}>"

@classmethod
def find_by_id(cls, i):
try:
notification = cls.query.filter(cls.id == i).one()
return notification
except Exception as e:
from sentry_sdk import capture_exception

capture_exception(e)
return None

@classmethod
def get_all_notifications_for_user(cls, user_id):
try:
user_notification = cls.query.filter(cls.user_id == user_id).all()
return user_notification
except sqlalchemy.orm.exc.NoResultFound:
return None

@classmethod
def create_user_notification(cls, user_id, notification_id, db_session):
user_notification = UserNotification(user_id, notification_id)
db_session.add(user_notification)
return user_notification

@classmethod
def update_user_notification_time(cls, user_notification_id, db_session):
user_notification = cls.find_by_id(user_notification_id)
user_notification.notification_click_time = datetime.now()
db_session.add(user_notification)
return user_notification

0 comments on commit a9c5c50

Please sign in to comment.