Adding explore endpoint and fixing bugs
This commit is contained in:
parent
d40a8b9b6b
commit
29cf1532f7
6 changed files with 73 additions and 33 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -1,5 +1,13 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.9-dev
|
||||||
|
|
||||||
|
* Added `create_account` endpoint to API. This endpoint does not require an access token.
|
||||||
|
* Added `has_more` field to feed endpoints (`feed`, `explore` and `profile_feed`).
|
||||||
|
* Added `/favicon.ico`.
|
||||||
|
* Added `explore` endpoint.
|
||||||
|
* Fixed some bugs when creating mentions and using offsets in feeds.
|
||||||
|
|
||||||
## 0.8.0
|
## 0.8.0
|
||||||
|
|
||||||
* Added the admin dashboard, accessible from `/admin/` via basic auth. Only users with admin right can access it. Added endpoints `admin.reports` and `admin.reports_detail`.
|
* Added the admin dashboard, accessible from `/admin/` via basic auth. Only users with admin right can access it. Added endpoints `admin.reports` and `admin.reports_detail`.
|
||||||
|
|
@ -12,7 +20,7 @@
|
||||||
* Added `relationships_follow`, `relationships_unfollow`, `username_availability`, `edit_profile`, `request_edit` and `confirm_edit` endpoints to API.
|
* Added `relationships_follow`, `relationships_unfollow`, `username_availability`, `edit_profile`, `request_edit` and `confirm_edit` endpoints to API.
|
||||||
* Added `url` utility to model `Upload`.
|
* Added `url` utility to model `Upload`.
|
||||||
* Changed default `robots.txt`, adding report and admin-related lines.
|
* Changed default `robots.txt`, adding report and admin-related lines.
|
||||||
* Released official [Android client](https://github.com/sakuragasaki46/coriplusapp/releases/tag/v0.8.0)
|
* Released official [Android client](https://github.com/sakuragasaki46/coriplusapp/releases/tag/v0.8.0).
|
||||||
|
|
||||||
## 0.7.1-dev
|
## 0.7.1-dev
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import datetime, time, re, os, sys, string, json, html
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from flask_login import LoginManager
|
from flask_login import LoginManager
|
||||||
|
|
||||||
__version__ = '0.8.0'
|
__version__ = '0.9-dev'
|
||||||
|
|
||||||
# we want to support Python 3 only.
|
# we want to support Python 3 only.
|
||||||
# Python 2 has too many caveats.
|
# Python 2 has too many caveats.
|
||||||
|
|
@ -69,6 +69,10 @@ def _inject_user(userid):
|
||||||
@app.errorhandler(404)
|
@app.errorhandler(404)
|
||||||
def error_404(body):
|
def error_404(body):
|
||||||
return render_template('404.html'), 404
|
return render_template('404.html'), 404
|
||||||
|
|
||||||
|
@app.route('/favicon.ico')
|
||||||
|
def favicon_ico():
|
||||||
|
return send_from_directory(os.getcwd(), 'favicon.ico')
|
||||||
|
|
||||||
@app.route('/robots.txt')
|
@app.route('/robots.txt')
|
||||||
def robots_txt():
|
def robots_txt():
|
||||||
|
|
|
||||||
71
app/api.py
71
app/api.py
|
|
@ -1,11 +1,11 @@
|
||||||
from flask import Blueprint, jsonify, request
|
from flask import Blueprint, jsonify, request
|
||||||
import sys, os, datetime, re
|
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, 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
|
create_mentions, is_username, generate_access_token, pwdhash
|
||||||
|
|
||||||
bp = Blueprint('api', __name__, url_prefix='/api/V1')
|
bp = Blueprint('api', __name__, url_prefix='/api/V1')
|
||||||
|
|
||||||
|
|
@ -64,17 +64,33 @@ def feed(self):
|
||||||
if date is None:
|
if date is None:
|
||||||
date = datetime.datetime.now()
|
date = datetime.datetime.now()
|
||||||
else:
|
else:
|
||||||
date = datetime.datetime.fromtimestamp(date)
|
date = datetime.datetime.fromtimestamp(float(date))
|
||||||
query = Visibility(Message
|
query = Visibility(Message
|
||||||
.select()
|
.select()
|
||||||
.where(((Message.user << self.following())
|
.where(((Message.user << self.following())
|
||||||
| (Message.user == self))
|
| (Message.user == self))
|
||||||
& (Message.pub_date < date))
|
& (Message.pub_date < date))
|
||||||
.order_by(Message.pub_date.desc())
|
.order_by(Message.pub_date.desc()))
|
||||||
.limit(20))
|
for message in query.paginate(1):
|
||||||
for message in query:
|
|
||||||
timeline_media.append(get_message_info(message))
|
timeline_media.append(get_message_info(message))
|
||||||
return {'timeline_media': timeline_media}
|
return {'timeline_media': timeline_media, 'has_more': query.count() > len(timeline_media)}
|
||||||
|
|
||||||
|
@bp.route('/explore')
|
||||||
|
@validate_access
|
||||||
|
def explore(self):
|
||||||
|
timeline_media = []
|
||||||
|
date = request.args.get('offset')
|
||||||
|
if date is None:
|
||||||
|
date = datetime.datetime.now()
|
||||||
|
else:
|
||||||
|
date = datetime.datetime.fromtimestamp(float(date))
|
||||||
|
query = Visibility(Message
|
||||||
|
.select()
|
||||||
|
.where(Message.pub_date < date)
|
||||||
|
.order_by(Message.pub_date.desc()), True)
|
||||||
|
for message in query.paginate(1):
|
||||||
|
timeline_media.append(get_message_info(message))
|
||||||
|
return {'timeline_media': timeline_media, 'has_more': query.count() > len(timeline_media)}
|
||||||
|
|
||||||
@bp.route('/create', methods=['POST'])
|
@bp.route('/create', methods=['POST'])
|
||||||
@validate_access
|
@validate_access
|
||||||
|
|
@ -88,7 +104,7 @@ def create(self):
|
||||||
pub_date=datetime.datetime.now(),
|
pub_date=datetime.datetime.now(),
|
||||||
privacy=privacy)
|
privacy=privacy)
|
||||||
# This API does not support files. Use create2 instead.
|
# This API does not support files. Use create2 instead.
|
||||||
create_mentions(self, text)
|
create_mentions(self, text, privacy)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@bp.route('/create2', methods=['POST'])
|
@bp.route('/create2', methods=['POST'])
|
||||||
|
|
@ -110,7 +126,7 @@ def create2(self):
|
||||||
message=message
|
message=message
|
||||||
)
|
)
|
||||||
file.save(os.path.join(UPLOAD_DIRECTORY, str(upload.id) + '.' + ext))
|
file.save(os.path.join(UPLOAD_DIRECTORY, str(upload.id) + '.' + ext))
|
||||||
create_mentions(self, text)
|
create_mentions(self, text, privacy)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_relationship_info(self, other):
|
def get_relationship_info(self, other):
|
||||||
|
|
@ -169,16 +185,15 @@ def profile_feed(self, userid):
|
||||||
if date is None:
|
if date is None:
|
||||||
date = datetime.datetime.now()
|
date = datetime.datetime.now()
|
||||||
else:
|
else:
|
||||||
date = datetime.datetime.fromtimestamp(date)
|
date = datetime.datetime.fromtimestamp(float(date))
|
||||||
query = Visibility(Message
|
query = Visibility(Message
|
||||||
.select()
|
.select()
|
||||||
.where((Message.user == user)
|
.where((Message.user == user)
|
||||||
& (Message.pub_date < date))
|
& (Message.pub_date < date))
|
||||||
.order_by(Message.pub_date.desc())
|
.order_by(Message.pub_date.desc()))
|
||||||
.limit(20))
|
for message in query.paginate(1):
|
||||||
for message in query:
|
|
||||||
timeline_media.append(get_message_info(message))
|
timeline_media.append(get_message_info(message))
|
||||||
return {'timeline_media': timeline_media}
|
return {'timeline_media': timeline_media, 'has_more': query.count() > len(timeline_media)}
|
||||||
|
|
||||||
@bp.route('/relationships/<int:userid>/follow', methods=['POST'])
|
@bp.route('/relationships/<int:userid>/follow', methods=['POST'])
|
||||||
@validate_access
|
@validate_access
|
||||||
|
|
@ -305,3 +320,31 @@ def save_edit(self, id):
|
||||||
data = request.get_json(True)
|
data = request.get_json(True)
|
||||||
Message.update(text=data['text'], privacy=data['privacy']).where(Message.id == id).execute()
|
Message.update(text=data['text'], privacy=data['privacy']).where(Message.id == id).execute()
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
# no validate access for this endpoint!
|
||||||
|
@bp.route('/create_account', methods=['POST'])
|
||||||
|
def create_account():
|
||||||
|
try:
|
||||||
|
data = request.get_json(True)
|
||||||
|
try:
|
||||||
|
birthday = datetime.datetime.fromisoformat(data['birthday'])
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError('invalid date format')
|
||||||
|
username = data['username'].lower()
|
||||||
|
if not is_username(username):
|
||||||
|
raise ValueError('invalid username')
|
||||||
|
with database.atomic():
|
||||||
|
user = User.create(
|
||||||
|
username=username,
|
||||||
|
full_name=data.get('full_name') or username,
|
||||||
|
password=pwdhash(data['password']),
|
||||||
|
email=data['email'],
|
||||||
|
birthday=birthday,
|
||||||
|
join_date=datetime.datetime.now())
|
||||||
|
UserProfile.create(
|
||||||
|
user=user
|
||||||
|
)
|
||||||
|
|
||||||
|
return jsonify({'access_token': generate_access_token(user), 'status': 'ok'})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'message': str(e), 'status': 'fail'})
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ def check_access_token(token):
|
||||||
if h.hexdigest()[:32] == hh:
|
if h.hexdigest()[:32] == hh:
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def create_mentions(cur_user, text):
|
def create_mentions(cur_user, text, privacy):
|
||||||
# create mentions
|
# create mentions
|
||||||
mention_usernames = set()
|
mention_usernames = set()
|
||||||
for mo in re.finditer(r'\+([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)', text):
|
for mo in re.finditer(r'\+([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)', text):
|
||||||
|
|
|
||||||
|
|
@ -188,22 +188,7 @@ def create():
|
||||||
message=message
|
message=message
|
||||||
)
|
)
|
||||||
file.save(UPLOAD_DIRECTORY + str(upload.id) + '.' + ext)
|
file.save(UPLOAD_DIRECTORY + str(upload.id) + '.' + ext)
|
||||||
# create mentions
|
create_mentions(user, text, privacy)
|
||||||
mention_usernames = set()
|
|
||||||
for mo in re.finditer(r'\+([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)', text):
|
|
||||||
mention_usernames.add(mo.group(1))
|
|
||||||
# to avoid self mention
|
|
||||||
mention_usernames.difference_update({user.username})
|
|
||||||
for u in mention_usernames:
|
|
||||||
try:
|
|
||||||
mention_user = User.get(User.username == u)
|
|
||||||
if privacy in (MSGPRV_PUBLIC, MSGPRV_UNLISTED) or \
|
|
||||||
(privacy == MSGPRV_FRIENDS and
|
|
||||||
mention_user.is_following(user) and
|
|
||||||
user.is_following(mention_user)):
|
|
||||||
push_notification('mention', mention_user, user=user.id)
|
|
||||||
except User.DoesNotExist:
|
|
||||||
pass
|
|
||||||
flash('Your message has been posted successfully')
|
flash('Your message has been posted successfully')
|
||||||
return redirect(url_for('website.user_detail', username=user.username))
|
return redirect(url_for('website.user_detail', username=user.username))
|
||||||
return render_template('create.html')
|
return render_template('create.html')
|
||||||
|
|
|
||||||
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Loading…
Add table
Add a link
Reference in a new issue