From baed59ea39b354b479dd45470e91099fa7b6b9ac Mon Sep 17 00:00:00 2001 From: Mattia Succurro Date: Mon, 25 Nov 2019 09:39:33 +0100 Subject: [PATCH] Adding notifications and +1's to messages --- CHANGELOG.md | 5 +- app/ajax.py | 30 +++++++++- app/api.py | 87 ++++++++++++++++++++++++++++- app/models.py | 26 ++++++++- app/static/lib.js | 18 ++++++ app/templates/includes/message.html | 10 +++- 6 files changed, 168 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e8a425..4e027d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,13 @@ ## 0.9-dev +* Added positive feedback mechanism: now you can +1 a message. So, `score_message_add` and `score_message_remove` API endpoints were added, and `MessageUpvote` table was created. +* Added notifications support for API. * Added `create_account` endpoint to API. This endpoint does not require an access token. +* Added `explore`, `notifications_count`, `notifications` and `notifications_seen` endpoints. * Added `has_more` field to feed endpoints (`feed`, `explore` and `profile_feed`). +* Added `join_date` field into `user` object of `profile_info` endpoint, for more profile transparency. * Added `/favicon.ico`. -* Added `explore` endpoint. * Fixed some bugs when creating mentions and using offsets in feeds. ## 0.8.0 diff --git a/app/ajax.py b/app/ajax.py index 63dc532..d2c0be3 100644 --- a/app/ajax.py +++ b/app/ajax.py @@ -5,8 +5,9 @@ Warning: this is not the public API. ''' from flask import Blueprint, jsonify -from .models import User +from .models import User, Message, MessageUpvote from .utils import locations, get_current_user, is_username +import datetime bp = Blueprint('ajax', __name__, url_prefix='/ajax') @@ -35,3 +36,30 @@ def location_search(name): if value.lower().startswith(name.lower()): results.append({'value': key, 'display': value}) return jsonify({'results': results}) + +@bp.route('/score//toggle', methods=['POST']) +def score_toggle(id): + user = get_current_user() + message = Message[id] + upvoted_by_self = (MessageUpvote + .select() + .where((MessageUpvote.message == message) & (MessageUpvote.user == user)) + .exists()) + if upvoted_by_self: + (MessageUpvote + .delete() + .where( + (MessageUpvote.message == message) & + (MessageUpvote.user == user)) + .execute() + ) + else: + MessageUpvote.create( + message=message, + user=user, + created_date=datetime.datetime.now() + ) + return jsonify({ + "score": message.score, + "status": "ok" + }) diff --git a/app/api.py b/app/api.py index c74efa3..6ee0fd3 100644 --- a/app/api.py +++ b/app/api.py @@ -2,7 +2,8 @@ from flask import Blueprint, jsonify, request import sys, os, datetime, re, uuid from functools import wraps from peewee import IntegrityError -from .models import User, UserProfile, Message, Upload, Relationship, database, \ +from .models import User, UserProfile, Message, Upload, Relationship, Notification, \ + MessageUpvote, database, \ MSGPRV_PUBLIC, MSGPRV_UNLISTED, MSGPRV_FRIENDS, MSGPRV_ONLYME, UPLOAD_DIRECTORY from .utils import check_access_token, Visibility, push_notification, unpush_notification, \ create_mentions, is_username, generate_access_token, pwdhash @@ -25,7 +26,9 @@ def get_message_info(message): 'text': message.text, 'privacy': message.privacy, 'pub_date': message.pub_date.timestamp(), - 'media': media + 'media': media, + 'score': len(message.upvotes), + 'upvoted_by_self': message.upvoted_by_self(), } def validate_access(func): @@ -162,6 +165,7 @@ def profile_info(self, userid): "generation": profile.year, "instagram": profile.instagram, "facebook": profile.facebook, + "join_date": user.join_date.timestamp(), "relationships": get_relationship_info(self, user), "messages_count": len(user.messages), "followers_count": len(user.followers()), @@ -348,3 +352,82 @@ def create_account(): return jsonify({'access_token': generate_access_token(user), 'status': 'ok'}) except Exception as e: return jsonify({'message': str(e), 'status': 'fail'}) + +def get_notification_info(notification): + obj = { + "id": notification.id, + "type": notification.type, + "timestamp": notification.pub_date.timestamp(), + "seen": notification.seen + } + obj.update(json.loads(notification.detail)) + return obj + +@bp.route('/notifications/count') +@validate_access +def notifications_count(self): + count = len(Notification + .select() + .where((Notification.target == self) & (Notification.seen == 0))) + return { + 'count': count + } + +@bp.route('/notifications') +@validate_access +def notifications(self): + items = [] + query = (Notification + .select() + .where(Notification.target == self) + .order_by(Notification.pub_date.desc()) + .limit(100)) + unseen_count = len(Notification + .select() + .where((Notification.target == self) & (Notification.seen == 0))) + for notification in query: + items.append(get_notification_info(query)) + return { + "notifications": { + "items": items, + "unseen_count": unseen_count + } + } + +@bp.route('/notifications/seen', methods=['POST']) +@validate_access +def notifications_seen(self): + data = request.get_json(True) + (Notification + .update(seen=1) + .where((Notification.target == self) & (Notification.pub_date < data['offset'])) + .execute()) + return {} + +@bp.route('/score/message//add', methods=['POST']) +@validate_access +def score_message_add(self, id): + message = Message[id] + MessageUpvote.create( + message=message, + user=self, + created_date=datetime.datetime.now() + ) + return { + 'score': len(message.upvotes) + } + +@bp.route('/score/message//remove', methods=['POST']) +@validate_access +def score_message_remove(self, id): + message = Message[id] + (MessageUpvote + .delete() + .where( + (MessageUpvote.message == message) & + (MessageUpvote.user == self)) + .execute() + ) + return { + 'score': len(message.upvotes) + } diff --git a/app/models.py b/app/models.py index ec37798..4d2cc8c 100644 --- a/app/models.py +++ b/app/models.py @@ -162,6 +162,17 @@ class Message(BaseModel): return user.is_following(cur_user) and cur_user.is_following(user) else: return False + @property + def score(self): + return self.upvotes.count() + def upvoted_by_self(self): + from .utils import get_current_user + user = get_current_user() + return (MessageUpvote + .select() + .where((MessageUpvote.message == self) & (MessageUpvote.user == user)) + .exists() + ) # this model contains two foreign keys to user -- it essentially allows us to # model a "many-to-many" relationship between users. by querying and joining @@ -226,6 +237,8 @@ report_reasons = [ (REPORT_REASON_FIREARMS, "Sale or promotion of firearms"), (REPORT_REASON_DRUGS, "Sale or promotion of drugs"), (REPORT_REASON_UNDERAGE, "This user is less than 13 years old"), + (REPORT_REASON_LEAK, "Leak of sensitive information"), + (REPORT_REASON_DMCA, "Copyright violation") ] REPORT_STATUS_DELIVERED = 0 @@ -251,10 +264,21 @@ class Report(BaseModel): except DoesNotExist: return +# New in 0.9. +class MessageUpvote(BaseModel): + message = ForeignKeyField(Message, backref='upvotes') + user = ForeignKeyField(User) + created_date = DateTimeField() + + class Meta: + indexes = ( + (('message', 'user'), True), + ) + def create_tables(): with database: database.create_tables([ User, UserAdminship, UserProfile, Message, Relationship, - Upload, Notification, Report]) + Upload, Notification, Report, MessageUpvote]) if not os.path.isdir(UPLOAD_DIRECTORY): os.makedirs(UPLOAD_DIRECTORY) diff --git a/app/static/lib.js b/app/static/lib.js index 538218a..cc78bea 100644 --- a/app/static/lib.js +++ b/app/static/lib.js @@ -98,3 +98,21 @@ function showHideMessageOptions(id){ options.style.display = 'block'; } } + +function toggleUpvote(id){ + var msgElem = document.getElementById(id); + var upvoteLink = msgElem.getElementsByClassName('message-upvote')[0]; + var scoreCounter = msgElem.getElementsByClassName('message-score')[0]; + var xhr = new XMLHttpRequest(); + xhr.open("POST", "/ajax/score/" + id + "/toggle", true); + xhr.onreadystatechange = function(){ + if(xhr.readyState == XMLHttpRequest.DONE){ + if(xhr.status == 200){ + console.log('liked #' + id); + var data = JSON.parse(xhr.responseText); + scoreCounter.innerHTML = data.score; + } + } + }; + xhr.send(); +} diff --git a/app/templates/includes/message.html b/app/templates/includes/message.html index 8095ab2..aa10503 100644 --- a/app/templates/includes/message.html +++ b/app/templates/includes/message.html @@ -5,12 +5,16 @@ {% endif %}