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
|
## 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
|
||||||
|
|
|
||||||
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 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"
|
||||||
|
})
|
||||||
|
|
|
||||||
87
app/api.py
87
app/api.py
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue