added not_producer, view for mark_as_read

This commit is contained in:
Kostiantyn Stoliarskyi 2023-06-12 12:46:43 +03:00
parent 0d64b9f2e9
commit 0052e20859
15 changed files with 467 additions and 188 deletions

View File

@ -0,0 +1,216 @@
from flask import url_for
from flask_login import current_user
from app import models as m, db
from app.logger import log
def create_notification(
entity: m.Notification.Entities,
action: m.Notification.Actions,
entity_id: int,
user_id: int,
text: str,
link: str,
):
m.Notification(
link=link,
text=text,
user_id=user_id,
action=action,
entity=entity,
entity_id=entity_id,
).save()
log(
log.INFO,
"Create notification for user with id [%s]",
user_id,
)
def section_notification(action: m.Notification.Actions, entity_id: int, user_id: int):
text = None
link = None
section: m.Section = db.session.get(m.Section, entity_id)
book: m.Book = db.session.get(m.Book, section.book_id)
match action:
case m.Notification.Actions.CREATE:
text = f"{current_user.username} create a new section on {book.label}"
link = (
url_for("book.collection_view", book_id=book.id)
+ f"#section-{section.label}"
)
case m.Notification.Actions.EDIT:
text = f"{current_user.username} rename a section on {book.label}"
link = (
url_for("book.collection_view", book_id=book.id)
+ f"#section-{section.label}"
)
case m.Notification.Actions.DELETE:
text = f"{current_user.username} delete a section on {book.label}"
link = url_for("book.collection_view", book_id=book.id)
create_notification(
m.Notification.Entities.SECTION, action, entity_id, user_id, text, link
)
def collection_notification(
action: m.Notification.Actions, entity_id: int, user_id: int
):
text = None
link = None
collection: m.Collection = db.session.get(m.Collection, entity_id)
book: m.Book = db.session.get(m.Book, collection.book_id)
match action:
case m.Notification.Actions.CREATE:
text = f"{current_user.username} create a new collection on {book.label}"
link = (
url_for("book.collection_view", book_id=book.id)
+ f"#collection-{collection.label}"
)
case m.Notification.Actions.EDIT:
text = f"{current_user.username} renamed a collection on {book.label}"
link = (
url_for("book.collection_view", book_id=book.id)
+ f"#collection-{collection.label}"
)
case m.Notification.Actions.DELETE:
text = f"{current_user.username} delete a collection on {book.label}"
link = url_for("book.collection_view", book_id=book.id)
create_notification(
m.Notification.Entities.COLLECTION, action, entity_id, user_id, text, link
)
def interpretation_notification(
action: m.Notification.Actions, entity_id: int, user_id: int
):
text = None
link = None
interpretation: m.Interpretation = db.session.get(m.Interpretation, entity_id)
section: m.Section = db.session.get(m.Section, interpretation.section_id)
book: m.Book = db.session.get(m.Book, interpretation.book.id)
match action:
case m.Notification.Actions.CREATE:
text = f"New interpretation to {section.label} on {book.label}"
link = url_for(
"book.interpretation_view",
book_id=book.id,
section_id=section.id,
)
case m.Notification.Actions.DELETE:
text = "A moderator has removed your interpretation"
link = url_for(
"book.interpretation_view",
book_id=book.id,
section_id=section.id,
)
case m.Notification.Actions.APPROVE:
if user_id == book.owner.id:
# This for the book owner
text = f"{current_user.username} approved an interpretation for {section.label} on {book.label}"
link = url_for(
"book.interpretation_view",
book_id=book.id,
section_id=section.id,
)
elif user_id == interpretation.user_id and user_id != book.owner.id:
# This for the interpretation owner
text = f"Your interpretation has been approved for {section.label} on {book.label}"
else:
return
create_notification(
m.Notification.Entities.INTERPRETATION, action, entity_id, user_id, text, link
)
def comment_notification(action: m.Notification.Actions, entity_id: int, user_id: int):
text = None
link = None
comment: m.Comment = db.session.get(m.Comment, entity_id)
interpretation: m.Interpretation = db.session.get(
m.Interpretation, comment.interpretation_id
)
section: m.Section = db.session.get(m.Section, interpretation.section_id)
book: m.Book = db.session.get(m.Book, comment.book.id)
match action:
case m.Notification.Actions.CREATE:
text = "New comment to your interpretation"
link = url_for(
"book.qa_view",
book_id=book.id,
interpretation_id=comment.interpretation_id,
)
case m.Notification.Actions.DELETE:
text = "A moderator has removed your comment"
link = url_for(
"book.qa_view",
book_id=book.id,
interpretation_id=comment.interpretation_id,
)
case m.Notification.Actions.APPROVE:
if user_id == comment.user_id and user_id != book.owner.id:
text = f"Your comment has been approved for {section.label} on {book.label}"
link = url_for(
"book.qa_view",
book_id=comment.book.id,
interpretation_id=comment.interpretation_id,
)
elif user_id == book.owner.id:
text = f"{current_user.username} approved an comment for {section.label} on {book.label}"
link = url_for(
"book.qa_view",
book_id=comment.book.id,
interpretation_id=comment.interpretation_id,
)
else:
return
case m.Notification.Actions.MENTION:
text = "You been mention in comment"
link = url_for(
"book.qa_view",
book_id=book.id,
interpretation_id=comment.interpretation_id,
)
create_notification(
m.Notification.Entities.COMMENT, action, entity_id, user_id, text, link
)
def contributor_notification(
action: m.Notification.Actions, entity_id: int, user_id: int
):
text = None
link = None
book: m.Book = db.session.get(m.Book, entity_id)
match action:
case m.Notification.Actions.CONTRIBUTING:
text = f"You've been added to {book.label} as an Editor/Moderator"
link = url_for(
"book.collection_view",
book_id=book.id,
)
case m.Notification.Actions.DELETE:
text = f"You've been removed from {book.label} as an Editor/Moderator"
link = url_for(
"book.collection_view",
book_id=book.id,
)
create_notification(
m.Notification.Entities.BOOK, action, entity_id, user_id, text, link
)

View File

@ -1,9 +1,32 @@
from enum import IntEnum
from app.models.utils import BaseModel
from app import db
class Notification(BaseModel):
__tablename__ = "notifications"
class Actions(IntEnum):
CREATE = 1
EDIT = 2
DELETE = 3
VOTE = 4
APPROVE = 5
CONTRIBUTING = 6
MENTION = 7
class Entities(IntEnum):
SECTION = 1
COLLECTION = 2
INTERPRETATION = 3
COMMENT = 4
BOOK = 5
action = db.Column(db.Enum(Actions))
entity = db.Column(db.Enum(Entities))
entity_id = db.Column(db.Integer, nullable=False)
link = db.Column(db.String(256), unique=False, nullable=False)
text = db.Column(db.String(256), unique=False, nullable=False)
is_read = db.Column(db.Boolean, default=False)

File diff suppressed because one or more lines are too long

View File

@ -12,10 +12,11 @@
<!-- prettier-ignore -->
{% for notification in current_user.active_notifications[:5]%}
<a
href="{{notification.link}}"
href="{{url_for('notifications.mark_as_read',notification_id=notification.id)}}"
class="flex px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-700">
<div class="w-full pl-3">
<div class="text-gray-500 text-sm mb-1.5 dark:text-gray-400">
<div
class="text-gray-500 text-sm mb-1.5 dark:text-gray-400 {% if not notification.is_read %} font-bold{% endif %}">
{{notification.text}}
</div>
</div>

View File

@ -19,7 +19,7 @@
{% endif %} {% for notification in current_user.notifications %}
<!-- prettier-ignore -->
<dl class="bg-white dark:bg-gray-900 max-w-full p-5 text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700 m-3 border-2 border-gray-200 border-solid rounded-lg dark:border-gray-700">
<dt class="mb-2"> <a class="flex flex-col pb-4" href="{{notification.link}}">{{notification.text}}</a> </dt>
<dt class="mb-2"> <a class="flex flex-col pb-4 {% if not notification.is_read %} font-bold {% endif %}" href="{{url_for('notifications.mark_as_read',notification_id=notification.id)}}">{{notification.text}}</a> </dt>
<dd class="flex flex-col md:flex-row text-lg font-semibold text-gray-500 md:text-lg dark:text-gray-400">
<p> Created at {{notification.created_at.strftime('%B %d, %Y')}}</p>
</dd>

View File

@ -1,11 +1,14 @@
from flask import (
Blueprint,
jsonify,
url_for,
)
from flask_login import login_required, current_user
from app import models as m, db
from app.controllers.notification_producer import (
interpretation_notification,
comment_notification,
)
from app.controllers.require_permission import require_permission
from app.logger import log
@ -27,7 +30,6 @@ def approve_interpretation(interpretation_id: int):
m.Interpretation, interpretation_id
)
book: m.Book = db.session.get(m.Book, interpretation.book.id)
section: m.Section = db.session.get(m.Section, interpretation.section_id)
if not interpretation:
log(log.WARNING, "Interpretation with id [%s] not found", interpretation_id)
return jsonify({"message": "Interpretation not found"}), 404
@ -64,33 +66,12 @@ def approve_interpretation(interpretation_id: int):
and current_user.id != interpretation.user_id
):
# notifications
redirect_url = url_for(
"book.interpretation_view", book_id=book.id, section_id=section.id
interpretation_notification(
m.Notification.Actions.APPROVE, interpretation.id, book.owner.id
)
notification_text = f"{current_user.username} approved an interpretation for {section.label} on {book.label}"
m.Notification(
link=redirect_url, text=notification_text, user_id=book.owner.id
).save()
log(
log.INFO,
"Create notification for user with id [%s]",
book.owner.id,
interpretation_notification(
m.Notification.Actions.APPROVE, interpretation.id, interpretation.user_id
)
elif interpretation.approved and current_user.id != interpretation.user_id:
# Your interpretation has been approved for SectionLabel on BookLabel
notification_text = (
f"Your interpretation has been approved for {section.label} on {book.label}"
)
m.Notification(
link=redirect_url, text=notification_text, user_id=interpretation.user_id
).save()
log(
log.INFO,
"Create notification for user with id [%s]",
book.owner.id,
)
# -------------
interpretation.save()
@ -110,7 +91,6 @@ def approve_interpretation(interpretation_id: int):
def approve_comment(comment_id: int):
comment: m.Comment = db.session.get(m.Comment, comment_id)
book: m.Book = db.session.get(m.Book, comment.book.id)
section: m.Section = db.session.get(m.Section, comment.interpretation.section_id)
if not comment:
log(log.WARNING, "Comment with id [%s] not found", comment_id)
return jsonify({"message": "Comment not found"}), 404
@ -123,36 +103,19 @@ def approve_comment(comment_id: int):
"Approve" if comment.approved else "Cancel approve",
comment,
)
# TODO:refactor if
if (
comment.approved
and current_user.id != comment.book.owner.id
and current_user.id != comment.user_id
):
# notifications
redirect_url = url_for(
"book.qa_view",
book_id=comment.book.id,
interpretation_id=comment.interpretation_id,
)
notification_text = f"{current_user.username} approved an comment for {section.label} on {book.label}"
m.Notification(
link=redirect_url, text=notification_text, user_id=book.owner.id
).save()
log(
log.INFO,
"Create notification for user with id [%s]",
book.owner.id,
)
comment_notification(m.Notification.Actions.APPROVE, comment.id, book.owner.id)
elif comment.approved and current_user.id != comment.user_id:
# Your interpretation has been approved for SectionLabel on BookLabel
notification_text = (
f"Your interpretation has been approved for {section.label} on {book.label}"
comment_notification(
m.Notification.Actions.APPROVE, comment.id, comment.user_id
)
m.Notification(
link=redirect_url, text=notification_text, user_id=comment.user_id
).save()
log(log.INFO, "Create notification for user with id [%s]", comment.user_id)
# -------------
comment.save()

View File

@ -5,6 +5,7 @@ from app.controllers import (
create_breadcrumbs,
register_book_verify_route,
)
from app.controllers.notification_producer import collection_notification
from app.controllers.delete_nested_book_entities import (
delete_nested_collection_entities,
)
@ -110,16 +111,8 @@ def collection_create(book_id: int, collection_id: int | None = None):
# notifications
if current_user.id != book.owner.id:
notification_text = (
f"{current_user.username} added a collection on {book.label}"
)
m.Notification(
link=redirect_url, text=notification_text, user_id=book.owner.id
).save()
log(
log.INFO,
"Create notification for user with id [%s]",
book.owner.id,
collection_notification(
m.Notification.Actions.CREATE, collection.id, book.owner.id
)
# -------------
@ -194,16 +187,8 @@ def collection_edit(book_id: int, collection_id: int):
# notifications
if current_user.id != book.owner.id:
notification_text = (
f"{current_user.username} renamed a collection on {book.label}"
)
m.Notification(
link=redirect_url, text=notification_text, user_id=book.owner.id
).save()
log(
log.INFO,
"Create notification for user with id [%s]",
book.owner.id,
collection_notification(
m.Notification.Actions.EDIT, collection.id, book.owner.id
)
# -------------
@ -246,16 +231,8 @@ def collection_delete(book_id: int, collection_id: int):
# notifications
if current_user.id != book.owner.id:
notification_text = (
f"{current_user.username} deleted a collection on {book.label}"
)
m.Notification(
link=redirect_url, text=notification_text, user_id=book.owner.id
).save()
log(
log.INFO,
"Create notification for user with id [%s]",
book.owner.id,
collection_notification(
m.Notification.Actions.DELETE, collection.id, book.owner.id
)
# -------------

View File

@ -1,9 +1,8 @@
from flask import flash, redirect, url_for, current_app
from flask_login import login_required, current_user
from app.controllers import (
register_book_verify_route,
)
from app.controllers import register_book_verify_route
from app.controllers.notification_producer import comment_notification
from app.controllers.delete_nested_book_entities import (
delete_nested_comment_entities,
)
@ -60,22 +59,26 @@ def create_comment(
comment.save()
# notifications
if current_user.id != book.owner.id:
notification_text = "New comment to your interpretation"
m.Notification(
link=redirect_url,
text=notification_text,
user_id=interpretation.user_id,
).save()
log(
log.INFO,
"Create notification for user with id [%s]",
interpretation.user_id,
if current_user.id != interpretation.user_id:
comment_notification(
m.Notification.Actions.CREATE, comment.id, interpretation.user_id
)
# -------------
tags = current_app.config["TAG_REGEX"].findall(text)
set_comment_tags(comment, tags)
users_mentions = current_app.config["USER_MENTION_REGEX"].findall(text)
for mention in users_mentions:
mention = mention.replace("@", "")
user = m.User.query.filter(m.User.username.ilike(mention.lower())).first()
if user:
# notifications
comment_notification(
m.Notification.Actions.MENTION, comment.id, user.id
)
# -------------
# TODO Send notifications
# users_mentions = current_app.config["USER_MENTION_REGEX"].findall(text)
@ -113,14 +116,8 @@ def comment_delete(book_id: int, interpretation_id: int):
# notifications
if current_user.id != interpretation.user_id:
notification_text = "A moderator has removed your comment"
m.Notification(
link=redirect_url, text=notification_text, user_id=comment.user_id
).save()
log(
log.INFO,
"Create notification for user with id [%s]",
comment.user_id,
comment_notification(
m.Notification.Actions.DELETE, comment.id, comment.user_id
)
# -------------

View File

@ -7,7 +7,12 @@ from flask import (
)
from flask_login import login_required, current_user
from app.controllers import register_book_verify_route, create_breadcrumbs, clean_html
from app.controllers import (
register_book_verify_route,
create_breadcrumbs,
clean_html,
)
from app.controllers.notification_producer import interpretation_notification
from app.controllers.delete_nested_book_entities import (
delete_nested_interpretation_entities,
)
@ -105,14 +110,8 @@ def interpretation_create(
# notifications
if current_user.id != book.owner.id:
notification_text = f"New interpretation to {section.label} on {book.label}"
m.Notification(
link=redirect_url, text=notification_text, user_id=book.owner.id
).save()
log(
log.INFO,
"Create notification for user with id [%s]",
book.owner.id,
interpretation_notification(
m.Notification.Actions.CREATE, interpretation.id, book.owner.id
)
# -------------
@ -225,16 +224,8 @@ def interpretation_delete(
)
# notifications
if current_user.id != interpretation.user_id:
notification_text = "A moderator has removed your interpretation"
m.Notification(
link=redirect_url,
text=notification_text,
user_id=interpretation.user_id,
).save()
log(
log.INFO,
"Create notification for user with id [%s]",
interpretation.user_id,
interpretation_notification(
m.Notification.Actions.DELETE, interpretation.id, interpretation.user_id
)
# -------------

View File

@ -2,6 +2,7 @@ from flask import flash, redirect, url_for, request
from flask_login import login_required, current_user
from app.controllers import register_book_verify_route
from app.controllers.notification_producer import section_notification
from app.controllers.delete_nested_book_entities import delete_nested_section_entities
from app import models as m, db, forms as f
from app.controllers.require_permission import require_permission
@ -49,16 +50,8 @@ def section_create(book_id: int, collection_id: int):
if current_user.id != book.owner.id:
# notifications
notification_text = (
f"{current_user.username} create a section on {book.label}"
)
m.Notification(
link=redirect_url, text=notification_text, user_id=book.owner.id
).save()
log(
log.INFO,
"Create notification for user with id[%s]",
book.owner.id,
section_notification(
m.Notification.Actions.CREATE, section.id, book.owner.id
)
# -------------
flash("Success!", "success")
@ -97,17 +90,7 @@ def section_edit(book_id: int, section_id: int):
if current_user.id != book.owner.id:
# notifications
notification_text = (
f"{current_user.username} renamed a section on {book.label}"
)
m.Notification(
link=redirect_url, text=notification_text, user_id=book.owner.id
).save()
log(
log.INFO,
"Create notification for user with id[%s]",
book.owner.id,
)
section_notification(m.Notification.Actions.EDIT, section.id, book.owner.id)
# -------------
flash("Success!", "success")
@ -152,15 +135,7 @@ def section_delete(
if current_user.id != book.owner.id:
# notifications
notification_text = f"{current_user.username} deleted a section on {book.label}"
m.Notification(
link=redirect_url, text=notification_text, user_id=book.owner.id
).save()
log(
log.INFO,
"Create notification for user with id[%s]",
book.owner.id,
)
section_notification(m.Notification.Actions.DELETE, section.id, book.owner.id)
# -------------
flash("Success!", "success")

View File

@ -7,9 +7,8 @@ from flask import (
)
from flask_login import login_required
from app.controllers import (
register_book_verify_route,
)
from app.controllers import register_book_verify_route
from app.controllers.notification_producer import contributor_notification
from app import models as m, db, forms as f
from app.controllers.require_permission import require_permission
from app.controllers.contributor import (

View File

@ -1,4 +1,4 @@
from flask import Blueprint, render_template
from flask import Blueprint, render_template, redirect
from flask_login import login_required, current_user
@ -7,7 +7,7 @@ from app.controllers import (
)
from app import models as m
from app import models as m, db
from app.logger import log
bp = Blueprint("notifications", __name__, url_prefix="/notifications")
@ -33,7 +33,11 @@ def get_all():
page=pagination,
)
@bp.route('/mark_as_read',methods=["GET"])
@bp.route("/<int:notification_id>/mark_as_read", methods=["GET"])
@login_required
def mark_as_read():
def mark_as_read(notification_id: int):
notification: m.Notification = db.session.get(m.Notification, notification_id)
notification.is_read = True
notification.save()
return redirect(notification.link)

View File

@ -1,4 +1,4 @@
from flask import Blueprint, jsonify, request, url_for
from flask import Blueprint, jsonify, request
from flask_login import login_required, current_user
from app import models as m, db
@ -19,7 +19,6 @@ def vote_interpretation(interpretation_id: int):
if not interpretation:
log(log.WARNING, "Interpretation with id [%s] not found", interpretation_id)
return jsonify({"message": "Interpretation not found"}), 404
book: m.Book = db.session.get(m.Book, interpretation.book.id)
vote: m.InterpretationVote = m.InterpretationVote.query.filter_by(
user_id=current_user.id, interpretation_id=interpretation_id
@ -52,23 +51,23 @@ def vote_interpretation(interpretation_id: int):
interpretation,
)
db.session.commit()
# TODO:Add notification if we deal with batching tem to "12 users voted your..."
# notifications
if current_user.id != book.owner.id:
redirect_url = url_for(
"book.interpretation_view",
book_id=book.id,
section_id=interpretation.section_id,
)
notification_text = f"{current_user.username} voted your interpretation"
m.Notification(
link=redirect_url, text=notification_text, user_id=book.owner.id
).save()
log(
log.INFO,
"Create notification for user with id [%s]",
book.owner.id,
)
# if current_user.id != book.owner.id:
# redirect_url = url_for(
# "book.interpretation_view",
# book_id=book.id,
# section_id=interpretation.section_id,
# )
# notification_text = f"{current_user.username} voted your interpretation"
# m.Notification(
# link=redirect_url, text=notification_text, user_id=book.owner.id
# ).save()
# log(
# log.INFO,
# "Create notification for user with id [%s]",
# book.owner.id,
# )
# -------------
return jsonify(

View File

@ -0,0 +1,104 @@
"""add_fields_to_notification
Revision ID: 3d45df38ffa9
Revises: 8f9233babba4
Create Date: 2023-06-09 17:23:32.809580
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = "3d45df38ffa9"
down_revision = "8f9233babba4"
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
action = postgresql.ENUM(
"CREATE",
"EDIT",
"DELETE",
"VOTE",
"APPROVE",
"CONTRIBUTING",
"MENTION",
name="actions",
)
action.create(op.get_bind())
entity = postgresql.ENUM(
"SECTION",
"COLLECTION",
"INTERPRETATION",
"COMMENT",
"BOOK",
name="entities",
)
entity.create(op.get_bind())
with op.batch_alter_table("notifications", schema=None) as batch_op:
batch_op.add_column(
sa.Column(
"action",
sa.Enum(
"CREATE",
"EDIT",
"DELETE",
"VOTE",
"APPROVE",
"CONTRIBUTING",
"MENTION",
name="actions",
),
nullable=True,
)
)
batch_op.add_column(
sa.Column(
"entity",
sa.Enum(
"SECTION",
"COLLECTION",
"INTERPRETATION",
"COMMENT",
"BOOK",
name="entities",
),
nullable=True,
)
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
action = postgresql.ENUM(
"CREATE",
"EDIT",
"DELETE",
"VOTE",
"APPROVE",
"CONTRIBUTING",
"MENTION",
name="actions",
)
action.drop(op.get_bind())
entity = postgresql.ENUM(
"SECTION",
"COLLECTION",
"INTERPRETATION",
"COMMENT",
"BOOK",
name="entities",
)
entity.drop(op.get_bind())
with op.batch_alter_table("notifications", schema=None) as batch_op:
batch_op.drop_column("entity")
batch_op.drop_column("action")
# ### end Alembic commands ###

View File

@ -0,0 +1,32 @@
"""entity_id
Revision ID: ad0ed27f417f
Revises: 3d45df38ffa9
Create Date: 2023-06-12 12:03:44.954134
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'ad0ed27f417f'
down_revision = '3d45df38ffa9'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('notifications', schema=None) as batch_op:
batch_op.add_column(sa.Column('entity_id', sa.Integer(), nullable=False))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('notifications', schema=None) as batch_op:
batch_op.drop_column('entity_id')
# ### end Alembic commands ###