-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4229217
commit f5cecbd
Showing
14 changed files
with
374 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from flask import Blueprint | ||
|
||
api = Blueprint('api', __name__) | ||
|
||
from . import authentication, posts, users, comments, errors |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from flask import g, jsonify | ||
from flask_httpauth import HTTPBasicAuth | ||
from ..models import User | ||
from . import api | ||
from .errors import unauthorized, forbidden | ||
|
||
auth = HTTPBasicAuth() | ||
|
||
|
||
@auth.verify_password | ||
def verify_password(email_or_token, password): | ||
if email_or_token == '': | ||
return False | ||
if password == '': | ||
g.current_user = User.verify_auth_token(email_or_token) | ||
g.token_used = True | ||
return g.current_user is not None | ||
user = User.query.filter_by(email=email_or_token.lower()).first() | ||
if not user: | ||
return False | ||
g.current_user = user | ||
g.token_used = False | ||
return user.verify_password(password) | ||
|
||
|
||
@auth.error_handler | ||
def auth_error(): | ||
return unauthorized('Invalid credentials') | ||
|
||
|
||
@api.before_request | ||
@auth.login_required | ||
def before_request(): | ||
if not g.current_user.is_anonymous and \ | ||
not g.current_user.confirmed: | ||
return forbidden('Unconfirmed account') | ||
|
||
|
||
@api.route('/tokens/', methods=['POST']) | ||
def get_token(): | ||
if g.current_user.is_anonymous or g.token_used: | ||
return unauthorized('Invalid credentials') | ||
return jsonify({'token': g.current_user.generate_auth_token( | ||
expiration=3600), 'expiration': 3600}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
from flask import jsonify, request, g, url_for, current_app | ||
from .. import db | ||
from ..models import Post, Permission, Comment | ||
from . import api | ||
from .decorators import permission_required | ||
|
||
|
||
@api.route('/comments/') | ||
def get_comments(): | ||
page = request.args.get('page', 1, type=int) | ||
pagination = Comment.query.order_by(Comment.timestamp.desc()).paginate( | ||
page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'], | ||
error_out=False) | ||
comments = pagination.items | ||
prev = None | ||
if pagination.has_prev: | ||
prev = url_for('api.get_comments', page=page-1) | ||
next = None | ||
if pagination.has_next: | ||
next = url_for('api.get_comments', page=page+1) | ||
return jsonify({ | ||
'comments': [comment.to_json() for comment in comments], | ||
'prev': prev, | ||
'next': next, | ||
'count': pagination.total | ||
}) | ||
|
||
|
||
@api.route('/comments/<int:id>') | ||
def get_comment(id): | ||
comment = Comment.query.get_or_404(id) | ||
return jsonify(comment.to_json()) | ||
|
||
|
||
@api.route('/posts/<int:id>/comments/') | ||
def get_post_comments(id): | ||
post = Post.query.get_or_404(id) | ||
page = request.args.get('page', 1, type=int) | ||
pagination = post.comments.order_by(Comment.timestamp.asc()).paginate( | ||
page, per_page=current_app.config['FLASKY_COMMENTS_PER_PAGE'], | ||
error_out=False) | ||
comments = pagination.items | ||
prev = None | ||
if pagination.has_prev: | ||
prev = url_for('api.get_post_comments', id=id, page=page-1) | ||
next = None | ||
if pagination.has_next: | ||
next = url_for('api.get_post_comments', id=id, page=page+1) | ||
return jsonify({ | ||
'comments': [comment.to_json() for comment in comments], | ||
'prev': prev, | ||
'next': next, | ||
'count': pagination.total | ||
}) | ||
|
||
|
||
@api.route('/posts/<int:id>/comments/', methods=['POST']) | ||
@permission_required(Permission.COMMENT) | ||
def new_post_comment(id): | ||
post = Post.query.get_or_404(id) | ||
comment = Comment.from_json(request.json) | ||
comment.author = g.current_user | ||
comment.post = post | ||
db.session.add(comment) | ||
db.session.commit() | ||
return jsonify(comment.to_json()), 201, \ | ||
{'Location': url_for('api.get_comment', id=comment.id)} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from functools import wraps | ||
from flask import g | ||
from .errors import forbidden | ||
|
||
|
||
def permission_required(permission): | ||
def decorator(f): | ||
@wraps(f) | ||
def decorated_function(*args, **kwargs): | ||
if not g.current_user.can(permission): | ||
return forbidden('Insufficient permissions') | ||
return f(*args, **kwargs) | ||
return decorated_function | ||
return decorator |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from flask import jsonify | ||
from app.exceptions import ValidationError | ||
from . import api | ||
|
||
|
||
def bad_request(message): | ||
response = jsonify({'error': 'bad request', 'message': message}) | ||
response.status_code = 400 | ||
return response | ||
|
||
|
||
def unauthorized(message): | ||
response = jsonify({'error': 'unauthorized', 'message': message}) | ||
response.status_code = 401 | ||
return response | ||
|
||
|
||
def forbidden(message): | ||
response = jsonify({'error': 'forbidden', 'message': message}) | ||
response.status_code = 403 | ||
return response | ||
|
||
|
||
@api.errorhandler(ValidationError) | ||
def validation_error(e): | ||
return bad_request(e.args[0]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
from flask import jsonify, request, g, url_for, current_app | ||
from .. import db | ||
from ..models import Post, Permission | ||
from . import api | ||
from .decorators import permission_required | ||
from .errors import forbidden | ||
|
||
|
||
@api.route('/posts/') | ||
def get_posts(): | ||
page = request.args.get('page', 1, type=int) | ||
pagination = Post.query.paginate( | ||
page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'], | ||
error_out=False) | ||
posts = pagination.items | ||
prev = None | ||
if pagination.has_prev: | ||
prev = url_for('api.get_posts', page=page-1) | ||
next = None | ||
if pagination.has_next: | ||
next = url_for('api.get_posts', page=page+1) | ||
return jsonify({ | ||
'posts': [post.to_json() for post in posts], | ||
'prev': prev, | ||
'next': next, | ||
'count': pagination.total | ||
}) | ||
|
||
|
||
@api.route('/posts/<int:id>') | ||
def get_post(id): | ||
post = Post.query.get_or_404(id) | ||
return jsonify(post.to_json()) | ||
|
||
|
||
@api.route('/posts/', methods=['POST']) | ||
@permission_required(Permission.WRITE) | ||
def new_post(): | ||
post = Post.from_json(request.json) | ||
post.author = g.current_user | ||
db.session.add(post) | ||
db.session.commit() | ||
return jsonify(post.to_json()), 201, \ | ||
{'Location': url_for('api.get_post', id=post.id)} | ||
|
||
|
||
@api.route('/posts/<int:id>', methods=['PUT']) | ||
@permission_required(Permission.WRITE) | ||
def edit_post(id): | ||
post = Post.query.get_or_404(id) | ||
if g.current_user != post.author and \ | ||
not g.current_user.can(Permission.ADMIN): | ||
return forbidden('Insufficient permissions') | ||
post.body = request.json.get('body', post.body) | ||
db.session.add(post) | ||
db.session.commit() | ||
return jsonify(post.to_json()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from flask import jsonify, request, current_app, url_for | ||
from . import api | ||
from ..models import User, Post | ||
|
||
|
||
@api.route('/users/<int:id>') | ||
def get_user(id): | ||
user = User.query.get_or_404(id) | ||
return jsonify(user.to_json()) | ||
|
||
|
||
@api.route('/users/<int:id>/posts/') | ||
def get_user_posts(id): | ||
user = User.query.get_or_404(id) | ||
page = request.args.get('page', 1, type=int) | ||
pagination = user.posts.order_by(Post.timestamp.desc()).paginate( | ||
page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'], | ||
error_out=False) | ||
posts = pagination.items | ||
prev = None | ||
if pagination.has_prev: | ||
prev = url_for('api.get_user_posts', id=id, page=page-1) | ||
next = None | ||
if pagination.has_next: | ||
next = url_for('api.get_user_posts', id=id, page=page+1) | ||
return jsonify({ | ||
'posts': [post.to_json() for post in posts], | ||
'prev': prev, | ||
'next': next, | ||
'count': pagination.total | ||
}) | ||
|
||
|
||
@api.route('/users/<int:id>/timeline/') | ||
def get_user_followed_posts(id): | ||
user = User.query.get_or_404(id) | ||
page = request.args.get('page', 1, type=int) | ||
pagination = user.followed_posts.order_by(Post.timestamp.desc()).paginate( | ||
page, per_page=current_app.config['FLASKY_POSTS_PER_PAGE'], | ||
error_out=False) | ||
posts = pagination.items | ||
prev = None | ||
if pagination.has_prev: | ||
prev = url_for('api.get_user_followed_posts', id=id, page=page-1) | ||
next = None | ||
if pagination.has_next: | ||
next = url_for('api.get_user_followed_posts', id=id, page=page+1) | ||
return jsonify({ | ||
'posts': [post.to_json() for post in posts], | ||
'prev': prev, | ||
'next': next, | ||
'count': pagination.total | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
class ValidationError(ValueError): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,32 @@ | ||
from flask import render_template | ||
from flask import render_template, request, jsonify | ||
from . import main | ||
|
||
|
||
@main.app_errorhandler(403) | ||
def forbidden(e): | ||
if request.accept_mimetypes.accept_json and \ | ||
not request.accept_mimetypes.accept_html: | ||
response = jsonify({'error': 'forbidden'}) | ||
response.status_code = 403 | ||
return response | ||
return render_template('403.html'), 403 | ||
|
||
|
||
@main.app_errorhandler(404) | ||
def page_not_found(e): | ||
if request.accept_mimetypes.accept_json and \ | ||
not request.accept_mimetypes.accept_html: | ||
response = jsonify({'error': 'not found'}) | ||
response.status_code = 404 | ||
return response | ||
return render_template('404.html'), 404 | ||
|
||
|
||
@main.app_errorhandler(500) | ||
def internal_server_error(e): | ||
if request.accept_mimetypes.accept_json and \ | ||
not request.accept_mimetypes.accept_html: | ||
response = jsonify({'error': 'internal server error'}) | ||
response.status_code = 500 | ||
return response | ||
return render_template('500.html'), 500 |
Oops, something went wrong.
f5cecbd
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hakanohi Yes, I think you should be able to remove the implementation in api/errors.py and use only the one in main/errors.py. The one in the api blueprint is specifically coded to respond with a JSON payload, but the generic one in the main blueprint can do that too, so there is a bit of an overlap there.