Adding notifications and +1's to messages
This commit is contained in:
parent
29cf1532f7
commit
baed59ea39
6 changed files with 168 additions and 8 deletions
|
|
@ -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
|
||||
|
|
|
|||
30
app/ajax.py
30
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/<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"
|
||||
})
|
||||
|
|
|
|||
87
app/api.py
87
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/<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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,16 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
<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>
|
||||
-
|
||||
{% set message_privacy = message.privacy %}
|
||||
{% if message.privacy in (0, 1) %} Public
|
||||
{% elif message.privacy == 2 %} Friends
|
||||
{% elif message.privacy == 3 %} Only me
|
||||
{% if message_privacy == 0 %} Public
|
||||
{% elif message_privacy == 1 %} Unlisted
|
||||
{% elif message_privacy == 2 %} Friends
|
||||
{% elif message_privacy == 3 %} Only me
|
||||
{% endif %}
|
||||
-
|
||||
<time datetime="{{ message.pub_date.isoformat() }}" title="{{ message.pub_date.ctime() }}">{{ message.pub_date | human_date }}</time>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue