mirror of
https://github.com/dancojocaru2000/foxbank.git
synced 2025-02-22 23:39:36 +02:00
Implemented notifications support
This commit is contained in:
parent
4ecb5b426b
commit
f1cbb3a125
5 changed files with 167 additions and 3 deletions
|
@ -4,6 +4,7 @@ from flask_smorest import Api
|
||||||
from .accounts import bp as acc_bp
|
from .accounts import bp as acc_bp
|
||||||
from .login import bp as login_bp
|
from .login import bp as login_bp
|
||||||
from .transactions import bp as transactions_bp
|
from .transactions import bp as transactions_bp
|
||||||
|
from .notifications import bp as notifications_bp
|
||||||
|
|
||||||
class ApiWithErr(Api):
|
class ApiWithErr(Api):
|
||||||
def handle_http_exception(self, error):
|
def handle_http_exception(self, error):
|
||||||
|
@ -29,3 +30,4 @@ def init_apis(app: Flask):
|
||||||
api.register_blueprint(login_bp, url_prefix='/login')
|
api.register_blueprint(login_bp, url_prefix='/login')
|
||||||
api.register_blueprint(acc_bp, url_prefix='/accounts')
|
api.register_blueprint(acc_bp, url_prefix='/accounts')
|
||||||
api.register_blueprint(transactions_bp, url_prefix='/transactions')
|
api.register_blueprint(transactions_bp, url_prefix='/transactions')
|
||||||
|
api.register_blueprint(notifications_bp, url_prefix='/notifications')
|
||||||
|
|
|
@ -70,7 +70,7 @@ class AccountResponseSchema(returns.SuccessSchema):
|
||||||
|
|
||||||
@bp.get('/<int:account_id>')
|
@bp.get('/<int:account_id>')
|
||||||
@ensure_logged_in
|
@ensure_logged_in
|
||||||
@bp.response(401, returns.ErrorSchema, description='Login failure')
|
@bp.response(401, returns.ErrorSchema, description='Login failure or not allowed')
|
||||||
@bp.doc(security=[{'Token': []}])
|
@bp.doc(security=[{'Token': []}])
|
||||||
@bp.response(200, AccountResponseSchema)
|
@bp.response(200, AccountResponseSchema)
|
||||||
def get_account_id(account_id: int):
|
def get_account_id(account_id: int):
|
||||||
|
|
60
server/foxbank_server/apis/notifications.py
Normal file
60
server/foxbank_server/apis/notifications.py
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from flask.views import MethodView
|
||||||
|
from flask_smorest import Blueprint
|
||||||
|
from marshmallow import Schema, fields
|
||||||
|
|
||||||
|
from ..db_utils import get_notifications, insert_notification, mark_notification_as_read, whose_notification
|
||||||
|
from ..decorators import ensure_logged_in
|
||||||
|
from ..models import Notification
|
||||||
|
from .. import decorators, returns
|
||||||
|
|
||||||
|
bp = Blueprint('notifications', __name__, description='Notifications operations')
|
||||||
|
|
||||||
|
@bp.post('/<int:notification_id>/mark_read')
|
||||||
|
@ensure_logged_in
|
||||||
|
@bp.response(401, returns.ErrorSchema, description='Login failure or not allowed')
|
||||||
|
@bp.doc(security=[{'Token': []}])
|
||||||
|
@bp.response(201, description='Successfully marked as read')
|
||||||
|
def mark_as_read(notification_id: int):
|
||||||
|
"""Mark notification as read"""
|
||||||
|
if decorators.user_id != whose_notification(notification_id):
|
||||||
|
return returns.abort(returns.UNAUTHORIZED)
|
||||||
|
mark_notification_as_read(notification_id)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/')
|
||||||
|
class NotificationsList(MethodView):
|
||||||
|
class NotificationsListPostParams(Schema):
|
||||||
|
body = fields.Str(description='Text of the notification')
|
||||||
|
read = fields.Bool(default=False, description='Whether the notification was read or not')
|
||||||
|
|
||||||
|
class NotificationsListPostSchema(returns.SuccessSchema):
|
||||||
|
notification = fields.Nested(Notification.NotificationSchema)
|
||||||
|
|
||||||
|
@ensure_logged_in
|
||||||
|
@bp.response(401, returns.ErrorSchema, description='Login failure')
|
||||||
|
@bp.doc(security=[{'Token': []}])
|
||||||
|
@bp.arguments(NotificationsListPostParams, as_kwargs=True)
|
||||||
|
@bp.response(200, NotificationsListPostSchema)
|
||||||
|
def post(self, body: str, read: bool = False):
|
||||||
|
"""Post a notification to the currently logged in user
|
||||||
|
|
||||||
|
The usefulness of this endpoint is questionable besides debugging since it's a notification to self
|
||||||
|
"""
|
||||||
|
now = datetime.now()
|
||||||
|
notification = Notification.new_notification(body, now, read)
|
||||||
|
insert_notification(decorators.user_id, notification)
|
||||||
|
return returns.success(notification=notification)
|
||||||
|
|
||||||
|
class NotificationsListGetSchema(returns.SuccessSchema):
|
||||||
|
notifications = fields.List(fields.Nested(Notification.NotificationSchema))
|
||||||
|
|
||||||
|
@ensure_logged_in
|
||||||
|
@bp.response(401, returns.ErrorSchema, description='Login failure')
|
||||||
|
@bp.doc(security=[{'Token': []}])
|
||||||
|
@bp.response(200, NotificationsListGetSchema)
|
||||||
|
def get(self):
|
||||||
|
"""Get all notifications for current user"""
|
||||||
|
notifications = get_notifications(decorators.user_id)
|
||||||
|
|
||||||
|
return returns.success(notifications=notifications)
|
|
@ -231,10 +231,80 @@ class Module(ModuleType):
|
||||||
transaction.id = cur.fetchone()['id']
|
transaction.id = cur.fetchone()['id']
|
||||||
|
|
||||||
cur.execute(
|
cur.execute(
|
||||||
'insert into accounts_transactions(account_id, transaction_id) VALUES (?, ?)',
|
'insert into accounts_transactions(account_id, transaction_id) values (?, ?)',
|
||||||
(account_id, transaction.id),
|
(account_id, transaction.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
|
@get_db
|
||||||
|
def get_notifications(self, user_id: int) -> list[models.Notification]:
|
||||||
|
cur = self.db.cursor()
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
'''
|
||||||
|
select n.id, n.body, n.datetime, n.read
|
||||||
|
from notifications as n
|
||||||
|
inner join users_notifications on n.id = users_notifications.notification_id
|
||||||
|
where users_notifications.user_id = ?
|
||||||
|
''',
|
||||||
|
(user_id,),
|
||||||
|
)
|
||||||
|
|
||||||
|
return [models.Notification.from_query(q) for q in cur.fetchall()]
|
||||||
|
|
||||||
|
@get_db
|
||||||
|
def insert_notification(self, user_id: int, notification: models.Notification):
|
||||||
|
cur = self.db.cursor()
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
'insert into notifications(body, datetime, read) values (?, ?, ?)',
|
||||||
|
(
|
||||||
|
notification.body,
|
||||||
|
notification.date_time.isoformat(),
|
||||||
|
1 if notification.read else 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
'select id from notifications where body = ? and datetime = ? and read = ?',
|
||||||
|
(
|
||||||
|
notification.body,
|
||||||
|
notification.date_time.isoformat(),
|
||||||
|
1 if notification.read else 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
notification.id = cur.fetchone()['id']
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
'insert into users_notifications values (?, ?)',
|
||||||
|
(user_id, notification.id,),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.db.commit()
|
||||||
|
|
||||||
|
@get_db
|
||||||
|
def whose_notification(self, notification: int | models.Notification) -> int | None:
|
||||||
|
try:
|
||||||
|
notification_id = notification.id
|
||||||
|
except AttributeError:
|
||||||
|
notification_id = notification
|
||||||
|
|
||||||
|
cur = self.db.cursor()
|
||||||
|
cur.execute('select user_id from users_notifications where notification_id = ?', (notification_id,))
|
||||||
|
result = cur.fetchone()
|
||||||
|
if not result:
|
||||||
|
return None
|
||||||
|
return result[0]
|
||||||
|
|
||||||
|
@get_db
|
||||||
|
def mark_notification_as_read(self, notification_id: int):
|
||||||
|
cur = self.db.cursor()
|
||||||
|
cur.execute(
|
||||||
|
'update notifications set read = 1 where id = ?',
|
||||||
|
(notification_id,),
|
||||||
|
)
|
||||||
|
self.db.commit()
|
||||||
|
|
||||||
|
|
||||||
sys.modules[__name__] = Module(__name__)
|
sys.modules[__name__] = Module(__name__)
|
||||||
|
|
|
@ -106,7 +106,7 @@ class Transaction:
|
||||||
extra = fields.Dict(keys=fields.Str(), values=fields.Raw())
|
extra = fields.Dict(keys=fields.Str(), values=fields.Raw())
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def new_transaction(date_time: datetime, other_party: str, status: str, transaction_type: str, extra: str = '') -> 'Account':
|
def new_transaction(date_time: datetime, other_party: str, status: str, transaction_type: str, extra: str = '') -> 'Transaction':
|
||||||
return Transaction(
|
return Transaction(
|
||||||
id=-1,
|
id=-1,
|
||||||
date_time=date_time,
|
date_time=date_time,
|
||||||
|
@ -141,3 +141,35 @@ class Transaction:
|
||||||
query_result[5] = json.loads(query_result[5])
|
query_result[5] = json.loads(query_result[5])
|
||||||
|
|
||||||
return cls(*query_result)
|
return cls(*query_result)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Notification:
|
||||||
|
id: int
|
||||||
|
body: str
|
||||||
|
date_time: datetime
|
||||||
|
read: bool
|
||||||
|
|
||||||
|
class NotificationSchema(Schema):
|
||||||
|
id = fields.Int(required=False)
|
||||||
|
body = fields.Str()
|
||||||
|
date_time = fields.DateTime(data_key='datetime')
|
||||||
|
read = fields.Bool()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def new_notification(body: str, date_time: datetime, read: bool = False) -> 'Notification':
|
||||||
|
return Notification(
|
||||||
|
id=-1,
|
||||||
|
body=body,
|
||||||
|
date_time=date_time,
|
||||||
|
read=read,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_query(cls, query_result):
|
||||||
|
query_result = list(query_result)
|
||||||
|
if type(query_result[2]) is str:
|
||||||
|
query_result[2] = datetime.fromisoformat(query_result[2])
|
||||||
|
if type(query_result[3]) is not bool:
|
||||||
|
query_result[3] = bool(query_result[3])
|
||||||
|
|
||||||
|
return cls(*query_result)
|
||||||
|
|
Loading…
Add table
Reference in a new issue