Adding feed to public API

This commit is contained in:
Yusur 2019-10-27 11:30:14 +01:00
parent 5536e764e7
commit dc33b5567a
5 changed files with 113 additions and 8 deletions

View file

@ -2,7 +2,7 @@
## 0.7-dev ## 0.7-dev
* Biggest change: unpacking modules. The single `app.py` file has become an `app` package, with submodules `models.py`, `utils.py`, `filters.py`, `website.py` and `ajax.py`. * Biggest change: unpacking modules. The single `app.py` file has become an `app` package, with submodules `models.py`, `utils.py`, `filters.py`, `website.py` and `ajax.py`. There is also a new module `api.py`.
* Now `/about/` shows Python and Flask versions. * Now `/about/` shows Python and Flask versions.
* Now the error 404 handler returns HTTP 404. * Now the error 404 handler returns HTTP 404.
* Added user followers and following lists, accessible via `/+<username>/followers` and `/+<username>/following` and from the profile info box, linked to the followers/following number. * Added user followers and following lists, accessible via `/+<username>/followers` and `/+<username>/following` and from the profile info box, linked to the followers/following number.
@ -11,6 +11,7 @@
* Added the capability to change password. * Added the capability to change password.
* Corrected a bug into `pwdhash`: it accepted an argument, but pulled data from the form instead of processing it. Now it uses the argument. * Corrected a bug into `pwdhash`: it accepted an argument, but pulled data from the form instead of processing it. Now it uses the argument.
* Schema changes: added column `telegram` to `UserProfile` table. To update schema, execute the script `migrate_0_6_to_0_7.py` * Schema changes: added column `telegram` to `UserProfile` table. To update schema, execute the script `migrate_0_6_to_0_7.py`
* Adding public API. Each of the API endpoints take a mandatory query string argument: the access token, generated by a separate endpoint at `/get_access_token` and stored into the client. All API routes start with `/api/V1`. Added endpoints `feed`.
## 0.6.0 ## 0.6.0

View file

@ -2,7 +2,7 @@
A simple social network, inspired by the now dead Google-Plus. A simple social network, inspired by the now dead Google-Plus.
To run the app, run the file "run_example.py" To run the app, do "flask run" in the package's parent directory.
Based on Tweepee example of [peewee](https://github.com/coleifer/peewee/). Based on Tweepee example of [peewee](https://github.com/coleifer/peewee/).
@ -13,6 +13,7 @@ Based on Tweepee example of [peewee](https://github.com/coleifer/peewee/).
* Timeline feed * Timeline feed
* Add info to your profile * Add info to your profile
* In-site notifications * In-site notifications
* Public API
* SQLite-based app * SQLite-based app
## Requirements ## Requirements

View file

@ -16,7 +16,6 @@ from flask import (
Flask, abort, flash, g, jsonify, redirect, render_template, request, Flask, abort, flash, g, jsonify, redirect, render_template, request,
send_from_directory, session, url_for, __version__ as flask_version) send_from_directory, session, url_for, __version__ as flask_version)
import hashlib import hashlib
from peewee import *
import datetime, time, re, os, sys, string, json, html 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
@ -76,12 +75,46 @@ def robots_txt():
def uploads(id, type='jpg'): def uploads(id, type='jpg'):
return send_from_directory(UPLOAD_DIRECTORY, id + '.' + type) return send_from_directory(UPLOAD_DIRECTORY, id + '.' + type)
@app.route('/get_access_token', methods=['POST'])
def send_access_token():
try:
try:
user = User.get(
(User.username == request.form['username']) &
(User.password == pwdhash(request.form['password'])))
except User.DoesNotExist:
return jsonify({
'message': 'Invalid username or password',
'login_correct': False,
'status': 'ok'
})
if user.is_disabled == 1:
user.is_disabled = 0
elif user.is_disabled == 2:
return jsonify({
'message': 'Your account has been disabled by violating our Terms.',
'login_correct': False,
'status': 'ok'
})
return jsonify({
'token': generate_access_token(user),
'login_correct': True,
'status': 'ok'
})
except Exception:
sys.excepthook(*sys.exc_info())
return jsonify({
'message': 'An unknown error has occurred.',
'status': 'fail'
})
from .website import bp from .website import bp
app.register_blueprint(bp) app.register_blueprint(bp)
from .ajax import bp from .ajax import bp
app.register_blueprint(bp) app.register_blueprint(bp)
from .api import bp
app.register_blueprint(bp)

67
app/api.py Normal file
View file

@ -0,0 +1,67 @@
from flask import Blueprint, jsonify, request
import sys, datetime
from functools import wraps
from .models import User, Message
from .utils import check_access_token, Visibility
bp = Blueprint('api', __name__, url_prefix='/api/V1')
def get_message_info(message):
return {
'id': message.id,
'user': {
'id': message.user.id,
'username': message.user.username,
},
'text': message.text,
'privacy': message.privacy,
'pub_date': message.pub_date.timestamp()
}
def validate_access(func):
@wraps(func)
def wrapper(*args, **kwargs):
access_token = request.args.get('access_token')
if access_token is None:
return jsonify({
'message': 'missing access_token',
'status': 'fail'
})
user = check_access_token(access_token)
if user is None:
return jsonify({
'message': 'invalid access_token',
'status': 'fail'
})
try:
result = func(user, *args, **kwargs)
assert isinstance(result, dict)
except Exception:
sys.excepthook(*sys.exc_info())
return jsonify({
'message': str(sys.exc_info()[1]),
'status': 'fail'
})
result['status'] = 'ok'
return jsonify(result)
return wrapper
@bp.route('/feed')
@validate_access
def feed(self):
timeline_media = []
date = request.args.get('offset')
if date is None:
date = datetime.datetime.now()
else:
date = datetime.datetime.fromtimestamp(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:
timeline_media.append(get_message_info(message))
return {'timeline_media': timeline_media}

View file

@ -178,13 +178,16 @@ def generate_access_token(user):
h.update(str(user.password).encode('utf-8')) h.update(str(user.password).encode('utf-8'))
return str(user.id) + ':' + h.hexdigest()[:32] return str(user.id) + ':' + h.hexdigest()[:32]
def check_access_token(user, token): def check_access_token(token):
uid, hh = token.split(':') uid, hh = token.split(':')
if uid != user.get_id(): try:
return False user = User[uid]
except User.DoesNotExist:
return
h = hashlib.sha256(get_secret_key()) h = hashlib.sha256(get_secret_key())
h.update(b':') h.update(b':')
h.update(str(user.id).encode('utf-8')) h.update(str(user.id).encode('utf-8'))
h.update(b':') h.update(b':')
h.update(str(user.password).encode('utf-8')) h.update(str(user.password).encode('utf-8'))
return h.hexdigest()[:32] == hh if h.hexdigest()[:32] == hh:
return user