-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #311 from tfnribeiro/add-notification-endpoint
Add notification endpoint
- Loading branch information
Showing
10 changed files
with
250 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
12
tools/migrations/24-12-02--add_user_notification_table.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |