Adding notifications and +1's to messages

This commit is contained in:
Yusur 2019-11-25 09:39:33 +01:00
parent 29cf1532f7
commit baed59ea39
6 changed files with 168 additions and 8 deletions

View file

@ -2,10 +2,13 @@
## 0.9-dev ## 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 `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 `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 `/favicon.ico`.
* Added `explore` endpoint.
* Fixed some bugs when creating mentions and using offsets in feeds. * Fixed some bugs when creating mentions and using offsets in feeds.
## 0.8.0 ## 0.8.0

View file

@ -5,8 +5,9 @@ Warning: this is not the public API.
''' '''
from flask import Blueprint, jsonify from flask import Blueprint, jsonify
from .models import User from .models import User, Message, MessageUpvote
from .utils import locations, get_current_user, is_username from .utils import locations, get_current_user, is_username
import datetime
bp = Blueprint('ajax', __name__, url_prefix='/ajax') bp = Blueprint('ajax', __name__, url_prefix='/ajax')
@ -35,3 +36,30 @@ def location_search(name):
if value.lower().startswith(name.lower()): if value.lower().startswith(name.lower()):
results.append({'value': key, 'display': value}) results.append({'value': key, 'display': value})
return jsonify({'results': results}) return jsonify({'results': results})
@bp.route('/score/<int:id>/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"
})

View file

@ -2,7 +2,8 @@ from flask import Blueprint, jsonify, request
import sys, os, datetime, re, uuid import sys, os, datetime, re, uuid
from functools import wraps from functools import wraps
from peewee import IntegrityError 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 MSGPRV_PUBLIC, MSGPRV_UNLISTED, MSGPRV_FRIENDS, MSGPRV_ONLYME, UPLOAD_DIRECTORY
from .utils import check_access_token, Visibility, push_notification, unpush_notification, \ from .utils import check_access_token, Visibility, push_notification, unpush_notification, \
create_mentions, is_username, generate_access_token, pwdhash create_mentions, is_username, generate_access_token, pwdhash
@ -25,7 +26,9 @@ def get_message_info(message):
'text': message.text, 'text': message.text,
'privacy': message.privacy, 'privacy': message.privacy,
'pub_date': message.pub_date.timestamp(), '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): def validate_access(func):
@ -162,6 +165,7 @@ def profile_info(self, userid):
"generation": profile.year, "generation": profile.year,
"instagram": profile.instagram, "instagram": profile.instagram,
"facebook": profile.facebook, "facebook": profile.facebook,
"join_date": user.join_date.timestamp(),
"relationships": get_relationship_info(self, user), "relationships": get_relationship_info(self, user),
"messages_count": len(user.messages), "messages_count": len(user.messages),
"followers_count": len(user.followers()), "followers_count": len(user.followers()),
@ -348,3 +352,82 @@ def create_account():
return jsonify({'access_token': generate_access_token(user), 'status': 'ok'}) return jsonify({'access_token': generate_access_token(user), 'status': 'ok'})
except Exception as e: except Exception as e:
return jsonify({'message': str(e), 'status': 'fail'}) 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/<int:id>/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/<int:id>/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)
}

View file

@ -162,6 +162,17 @@ class Message(BaseModel):
return user.is_following(cur_user) and cur_user.is_following(user) return user.is_following(cur_user) and cur_user.is_following(user)
else: else:
return False 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 # 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 # 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_FIREARMS, "Sale or promotion of firearms"),
(REPORT_REASON_DRUGS, "Sale or promotion of drugs"), (REPORT_REASON_DRUGS, "Sale or promotion of drugs"),
(REPORT_REASON_UNDERAGE, "This user is less than 13 years old"), (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 REPORT_STATUS_DELIVERED = 0
@ -251,10 +264,21 @@ class Report(BaseModel):
except DoesNotExist: except DoesNotExist:
return 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(): def create_tables():
with database: with database:
database.create_tables([ database.create_tables([
User, UserAdminship, UserProfile, Message, Relationship, User, UserAdminship, UserProfile, Message, Relationship,
Upload, Notification, Report]) Upload, Notification, Report, MessageUpvote])
if not os.path.isdir(UPLOAD_DIRECTORY): if not os.path.isdir(UPLOAD_DIRECTORY):
os.makedirs(UPLOAD_DIRECTORY) os.makedirs(UPLOAD_DIRECTORY)

View file

@ -98,3 +98,21 @@ function showHideMessageOptions(id){
options.style.display = 'block'; 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();
}

View file

@ -5,12 +5,16 @@
</div> </div>
{% endif %} {% endif %}
<p class="message-footer"> <p class="message-footer">
<a href="javascript:void(0);" class="message-upvote" onclick="toggleUpvote({{ message.id }});">+</a>
<span class="message-score">{{ message.score }}</span>
-
<a href="{{ url_for('website.user_detail', username=message.user.username) }}">{{ message.user.username }}</a> <a href="{{ url_for('website.user_detail', username=message.user.username) }}">{{ message.user.username }}</a>
- -
{% set message_privacy = message.privacy %} {% set message_privacy = message.privacy %}
{% if message.privacy in (0, 1) %} Public {% if message_privacy == 0 %} Public
{% elif message.privacy == 2 %} Friends {% elif message_privacy == 1 %} Unlisted
{% elif message.privacy == 3 %} Only me {% elif message_privacy == 2 %} Friends
{% elif message_privacy == 3 %} Only me
{% endif %} {% endif %}
- -
<time datetime="{{ message.pub_date.isoformat() }}" title="{{ message.pub_date.ctime() }}">{{ message.pub_date | human_date }}</time> <time datetime="{{ message.pub_date.isoformat() }}" title="{{ message.pub_date.ctime() }}">{{ message.pub_date | human_date }}</time>