From 72653c0d3ac5ea5265479e7e53ff39cfe62a00ce Mon Sep 17 00:00:00 2001 From: DavidRThrashJr <60800614+DavidRThrashJr@users.noreply.github.com> Date: Thu, 20 Feb 2020 03:35:03 -0500 Subject: Added support for multiple API versions (#533) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added support for multiple API versions Note that no changes were made to the api, the code was refactored to allow for new versions of the api to be created down the road. Here's what this would look like: +-- api/ +-- v1/ +-- __init__.py +-- resources.py +-- v1_1/ +-- __init__.py +-- resources.py +-- v2/ +-- __init__.py +-- resources.py +-- __init__.py +-- common.py * reformatted using black /Users/drthrash/PycharmProjects/ihatemoney/ihatemoney/api/v1/resources.py reformatted /Users/drthrash/PycharmProjects/ihatemoney/ihatemoney/api/common.py All done! ✨ 🍰 ✨ * Applying fix for unused import in init.py https://stackoverflow.com/questions/31079047/python-pep8-class-in-init-imported-but-not-used * Formatting changes recommended by black All done! ✨ 🍰 ✨ 1 file reformatted, 22 files left unchanged. --- ihatemoney/api.py | 213 ------------------------------------------------------ 1 file changed, 213 deletions(-) delete mode 100644 ihatemoney/api.py (limited to 'ihatemoney/api.py') diff --git a/ihatemoney/api.py b/ihatemoney/api.py deleted file mode 100644 index 67c6cc1..0000000 --- a/ihatemoney/api.py +++ /dev/null @@ -1,213 +0,0 @@ -# coding: utf8 -from flask import Blueprint, request, current_app -from flask_restful import Resource, Api, abort -from flask_cors import CORS -from wtforms.fields.core import BooleanField - -from ihatemoney.models import db, Project, Person, Bill -from ihatemoney.forms import ProjectForm, EditProjectForm, MemberForm, get_billform_for -from werkzeug.security import check_password_hash -from functools import wraps - - -api = Blueprint("api", __name__, url_prefix="/api") -CORS(api) -restful_api = Api(api) - - -def need_auth(f): - """Check the request for basic authentication for a given project. - - Return the project if the authorization is good, abort the request with a 401 otherwise - """ - - @wraps(f) - def wrapper(*args, **kwargs): - auth = request.authorization - project_id = kwargs.get("project_id") - - # Use Basic Auth - if auth and project_id and auth.username == project_id: - project = Project.query.get(auth.username) - if project and check_password_hash(project.password, auth.password): - # The whole project object will be passed instead of project_id - kwargs.pop("project_id") - return f(*args, project=project, **kwargs) - else: - # Use Bearer token Auth - auth_header = request.headers.get("Authorization", "") - auth_token = "" - try: - auth_token = auth_header.split(" ")[1] - except IndexError: - abort(401) - project_id = Project.verify_token(auth_token, token_type="non_timed_token") - if auth_token and project_id: - project = Project.query.get(project_id) - if project: - kwargs.pop("project_id") - return f(*args, project=project, **kwargs) - abort(401) - - return wrapper - - -class ProjectsHandler(Resource): - def post(self): - form = ProjectForm(meta={"csrf": False}) - if form.validate() and current_app.config.get("ALLOW_PUBLIC_PROJECT_CREATION"): - project = form.save() - db.session.add(project) - db.session.commit() - return project.id, 201 - return form.errors, 400 - - -class ProjectHandler(Resource): - method_decorators = [need_auth] - - def get(self, project): - return project - - def delete(self, project): - db.session.delete(project) - db.session.commit() - return "DELETED" - - def put(self, project): - form = EditProjectForm(meta={"csrf": False}) - if form.validate() and current_app.config.get("ALLOW_PUBLIC_PROJECT_CREATION"): - form.update(project) - db.session.commit() - return "UPDATED" - return form.errors, 400 - - -class ProjectStatsHandler(Resource): - method_decorators = [need_auth] - - def get(self, project): - return project.members_stats - - -class APIMemberForm(MemberForm): - """ Member is not disablable via a Form. - - But we want Member.enabled to be togglable via the API. - """ - - activated = BooleanField(false_values=("false", "", "False")) - - def save(self, project, person): - person.activated = self.activated.data - return super(APIMemberForm, self).save(project, person) - - -class MembersHandler(Resource): - method_decorators = [need_auth] - - def get(self, project): - return project.members - - def post(self, project): - form = MemberForm(project, meta={"csrf": False}) - if form.validate(): - member = Person() - form.save(project, member) - db.session.commit() - return member.id, 201 - return form.errors, 400 - - -class MemberHandler(Resource): - method_decorators = [need_auth] - - def get(self, project, member_id): - member = Person.query.get(member_id, project) - if not member or member.project != project: - return "Not Found", 404 - return member - - def put(self, project, member_id): - form = APIMemberForm(project, meta={"csrf": False}, edit=True) - if form.validate(): - member = Person.query.get(member_id, project) - form.save(project, member) - db.session.commit() - return member - return form.errors, 400 - - def delete(self, project, member_id): - if project.remove_member(member_id): - return "OK" - return "Not Found", 404 - - -class BillsHandler(Resource): - method_decorators = [need_auth] - - def get(self, project): - return project.get_bills().all() - - def post(self, project): - form = get_billform_for(project, True, meta={"csrf": False}) - if form.validate(): - bill = Bill() - form.save(bill, project) - db.session.add(bill) - db.session.commit() - return bill.id, 201 - return form.errors, 400 - - -class BillHandler(Resource): - method_decorators = [need_auth] - - def get(self, project, bill_id): - bill = Bill.query.get(project, bill_id) - if not bill: - return "Not Found", 404 - return bill, 200 - - def put(self, project, bill_id): - form = get_billform_for(project, True, meta={"csrf": False}) - if form.validate(): - bill = Bill.query.get(project, bill_id) - form.save(bill, project) - db.session.commit() - return bill.id, 200 - return form.errors, 400 - - def delete(self, project, bill_id): - bill = Bill.query.delete(project, bill_id) - db.session.commit() - if not bill: - return "Not Found", 404 - return "OK", 200 - - -class TokenHandler(Resource): - method_decorators = [need_auth] - - def get(self, project): - if not project: - return "Not Found", 404 - - token = project.generate_token() - return {"token": token}, 200 - - -restful_api.add_resource(ProjectsHandler, "/projects") -restful_api.add_resource(ProjectHandler, "/projects/") -restful_api.add_resource(TokenHandler, "/projects//token") -restful_api.add_resource(MembersHandler, "/projects//members") -restful_api.add_resource( - ProjectStatsHandler, "/projects//statistics" -) -restful_api.add_resource( - MemberHandler, "/projects//members/" -) -restful_api.add_resource(BillsHandler, "/projects//bills") -restful_api.add_resource( - BillHandler, "/projects//bills/" -) -- cgit v1.1