Adding feed to public API
This commit is contained in:
parent
5536e764e7
commit
dc33b5567a
5 changed files with 113 additions and 8 deletions
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
67
app/api.py
Normal 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}
|
||||||
11
app/utils.py
11
app/utils.py
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue