diff --git a/CHANGELOG.md b/CHANGELOG.md index cae4de4..2e8a425 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # 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 * 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 `url` utility to model `Upload`. * 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 diff --git a/app/__init__.py b/app/__init__.py index 835fcbc..b8b5c06 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -23,7 +23,7 @@ import datetime, time, re, os, sys, string, json, html from functools import wraps from flask_login import LoginManager -__version__ = '0.8.0' +__version__ = '0.9-dev' # we want to support Python 3 only. # Python 2 has too many caveats. @@ -69,6 +69,10 @@ def _inject_user(userid): @app.errorhandler(404) def error_404(body): 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') def robots_txt(): diff --git a/app/api.py b/app/api.py index 0cb8719..c74efa3 100644 --- a/app/api.py +++ b/app/api.py @@ -1,11 +1,11 @@ from flask import Blueprint, jsonify, request -import sys, os, datetime, re +import sys, os, datetime, re, uuid from functools import wraps from peewee import IntegrityError from .models import User, UserProfile, Message, Upload, Relationship, 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 + create_mentions, is_username, generate_access_token, pwdhash bp = Blueprint('api', __name__, url_prefix='/api/V1') @@ -64,17 +64,33 @@ def feed(self): if date is None: date = datetime.datetime.now() else: - date = datetime.datetime.fromtimestamp(date) + date = datetime.datetime.fromtimestamp(float(date)) query = Visibility(Message .select() .where(((Message.user << self.following()) | (Message.user == self)) & (Message.pub_date < date)) - .order_by(Message.pub_date.desc()) - .limit(20)) - for message in query: + .order_by(Message.pub_date.desc())) + for message in query.paginate(1): 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']) @validate_access @@ -88,7 +104,7 @@ def create(self): pub_date=datetime.datetime.now(), privacy=privacy) # This API does not support files. Use create2 instead. - create_mentions(self, text) + create_mentions(self, text, privacy) return {} @bp.route('/create2', methods=['POST']) @@ -110,7 +126,7 @@ def create2(self): message=message ) file.save(os.path.join(UPLOAD_DIRECTORY, str(upload.id) + '.' + ext)) - create_mentions(self, text) + create_mentions(self, text, privacy) return {} def get_relationship_info(self, other): @@ -169,16 +185,15 @@ def profile_feed(self, userid): if date is None: date = datetime.datetime.now() else: - date = datetime.datetime.fromtimestamp(date) + date = datetime.datetime.fromtimestamp(float(date)) query = Visibility(Message .select() .where((Message.user == user) & (Message.pub_date < date)) - .order_by(Message.pub_date.desc()) - .limit(20)) - for message in query: + .order_by(Message.pub_date.desc())) + for message in query.paginate(1): 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//follow', methods=['POST']) @validate_access @@ -305,3 +320,31 @@ def save_edit(self, id): data = request.get_json(True) Message.update(text=data['text'], privacy=data['privacy']).where(Message.id == id).execute() 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'}) diff --git a/app/utils.py b/app/utils.py index f459bf1..e39e476 100644 --- a/app/utils.py +++ b/app/utils.py @@ -198,7 +198,7 @@ def check_access_token(token): if h.hexdigest()[:32] == hh: return user -def create_mentions(cur_user, text): +def create_mentions(cur_user, text, privacy): # create mentions mention_usernames = set() for mo in re.finditer(r'\+([A-Za-z0-9_]+(?:\.[A-Za-z0-9_]+)*)', text): diff --git a/app/website.py b/app/website.py index 9d7d13d..f5867e3 100644 --- a/app/website.py +++ b/app/website.py @@ -188,22 +188,7 @@ def create(): message=message ) file.save(UPLOAD_DIRECTORY + str(upload.id) + '.' + ext) - # create mentions - 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 + create_mentions(user, text, privacy) flash('Your message has been posted successfully') return redirect(url_for('website.user_detail', username=user.username)) return render_template('create.html') diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..89a3407 Binary files /dev/null and b/favicon.ico differ