diff options
Diffstat (limited to 'src/web/views/api')
-rw-r--r-- | src/web/views/api/__init__.py | 0 | ||||
-rw-r--r-- | src/web/views/api/v2/__init__.py | 3 | ||||
-rw-r--r-- | src/web/views/api/v2/article.py | 53 | ||||
-rw-r--r-- | src/web/views/api/v2/category.py | 27 | ||||
-rw-r--r-- | src/web/views/api/v2/common.py | 222 | ||||
-rw-r--r-- | src/web/views/api/v2/feed.py | 47 | ||||
-rw-r--r-- | src/web/views/api/v3/__init__.py | 3 | ||||
-rw-r--r-- | src/web/views/api/v3/article.py | 84 | ||||
-rw-r--r-- | src/web/views/api/v3/common.py | 109 | ||||
-rw-r--r-- | src/web/views/api/v3/feed.py | 58 |
10 files changed, 0 insertions, 606 deletions
diff --git a/src/web/views/api/__init__.py b/src/web/views/api/__init__.py deleted file mode 100644 index e69de29b..00000000 --- a/src/web/views/api/__init__.py +++ /dev/null diff --git a/src/web/views/api/v2/__init__.py b/src/web/views/api/v2/__init__.py deleted file mode 100644 index 46760261..00000000 --- a/src/web/views/api/v2/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from web.views.api.v2 import article, feed, category - -__all__ = ['article', 'feed', 'category'] diff --git a/src/web/views/api/v2/article.py b/src/web/views/api/v2/article.py deleted file mode 100644 index 2be286c6..00000000 --- a/src/web/views/api/v2/article.py +++ /dev/null @@ -1,53 +0,0 @@ -from conf import API_ROOT -import dateutil.parser -from datetime import datetime -from flask import current_app -from flask_restful import Api - -from web.views.common import api_permission -from web.controllers import ArticleController -from web.views.api.v2.common import (PyAggAbstractResource, - PyAggResourceNew, PyAggResourceExisting, PyAggResourceMulti) - - -class ArticleNewAPI(PyAggResourceNew): - controller_cls = ArticleController - - -class ArticleAPI(PyAggResourceExisting): - controller_cls = ArticleController - - -class ArticlesAPI(PyAggResourceMulti): - controller_cls = ArticleController - - -class ArticlesChallenge(PyAggAbstractResource): - controller_cls = ArticleController - attrs = {'ids': {'type': list, 'default': []}} - - @api_permission.require(http_exception=403) - def get(self): - parsed_args = self.reqparse_args(right='read') - # collecting all attrs for casting purpose - attrs = self.controller_cls._get_attrs_desc('admin') - for id_dict in parsed_args['ids']: - keys_to_ignore = [] - for key in id_dict: - if key not in attrs: - keys_to_ignore.append(key) - if issubclass(attrs[key]['type'], datetime): - id_dict[key] = dateutil.parser.parse(id_dict[key]) - for key in keys_to_ignore: - del id_dict[key] - - result = list(self.controller.challenge(parsed_args['ids'])) - return result or None, 200 if result else 204 - -api = Api(current_app, prefix=API_ROOT) - -api.add_resource(ArticleNewAPI, '/article', endpoint='article_new.json') -api.add_resource(ArticleAPI, '/article/<int:obj_id>', endpoint='article.json') -api.add_resource(ArticlesAPI, '/articles', endpoint='articles.json') -api.add_resource(ArticlesChallenge, '/articles/challenge', - endpoint='articles_challenge.json') diff --git a/src/web/views/api/v2/category.py b/src/web/views/api/v2/category.py deleted file mode 100644 index 70fda1ea..00000000 --- a/src/web/views/api/v2/category.py +++ /dev/null @@ -1,27 +0,0 @@ -from conf import API_ROOT -from flask import current_app -from flask_restful import Api - -from web.controllers.category import CategoryController -from web.views.api.v2.common import (PyAggResourceNew, - PyAggResourceExisting, - PyAggResourceMulti) - - -class CategoryNewAPI(PyAggResourceNew): - controller_cls = CategoryController - - -class CategoryAPI(PyAggResourceExisting): - controller_cls = CategoryController - - -class CategoriesAPI(PyAggResourceMulti): - controller_cls = CategoryController - - -api = Api(current_app, prefix=API_ROOT) -api.add_resource(CategoryNewAPI, '/category', endpoint='category_new.json') -api.add_resource(CategoryAPI, '/category/<int:obj_id>', - endpoint='category.json') -api.add_resource(CategoriesAPI, '/categories', endpoint='categories.json') diff --git a/src/web/views/api/v2/common.py b/src/web/views/api/v2/common.py deleted file mode 100644 index 8a53d7e6..00000000 --- a/src/web/views/api/v2/common.py +++ /dev/null @@ -1,222 +0,0 @@ -"""For a given resources, classes in the module intend to create the following -routes : - GET resource/<id> - -> to retrieve one - POST resource - -> to create one - PUT resource/<id> - -> to update one - DELETE resource/<id> - -> to delete one - - GET resources - -> to retrieve several - POST resources - -> to create several - PUT resources - -> to update several - DELETE resources - -> to delete several -""" -import ast -import logging -from functools import wraps -from werkzeug.exceptions import Unauthorized, BadRequest, Forbidden, NotFound -from flask import request -from flask_restful import Resource, reqparse -from flask_login import current_user - -from web.views.common import admin_permission, api_permission, \ - login_user_bundle, jsonify -from web.controllers import UserController - -logger = logging.getLogger(__name__) - - -def authenticate(func): - @wraps(func) - def wrapper(*args, **kwargs): - if request.authorization: - ucontr = UserController() - try: - user = ucontr.get(nickname=request.authorization.username) - except NotFound: - raise Forbidden("Couldn't authenticate your user") - if not ucontr.check_password(user, request.authorization.password): - raise Forbidden("Couldn't authenticate your user") - if not user.is_active: - raise Forbidden("User is deactivated") - login_user_bundle(user) - if current_user.is_authenticated: - return func(*args, **kwargs) - raise Unauthorized() - return wrapper - - -class PyAggAbstractResource(Resource): - method_decorators = [authenticate, jsonify] - controller_cls = None - attrs = None - - @property - def controller(self): - if admin_permission.can(): - return self.controller_cls() - return self.controller_cls(current_user.id) - - def reqparse_args(self, right, req=None, strict=False, default=True, - allow_empty=False): - """ - strict: bool - if True will throw 400 error if args are defined and not in request - default: bool - if True, won't return defaults - args: dict - the args to parse, if None, self.attrs will be used - """ - try: - if req: - in_values = req.json - else: - in_values = request.args or request.json or {} - if not in_values and allow_empty: - return {} - except BadRequest: - if allow_empty: - return {} - raise - parser = reqparse.RequestParser() - if self.attrs is not None: - attrs = self.attrs - elif admin_permission.can(): - attrs = self.controller_cls._get_attrs_desc('admin') - elif api_permission.can(): - attrs = self.controller_cls._get_attrs_desc('api', right) - else: - attrs = self.controller_cls._get_attrs_desc('base', right) - assert attrs, "No defined attrs for %s" % self.__class__.__name__ - - for attr_name, attr in attrs.items(): - if not default and attr_name not in in_values: - continue - else: - parser.add_argument(attr_name, location='json', - default=in_values[attr_name]) - return parser.parse_args(req=request.args, strict=strict) - - -class PyAggResourceNew(PyAggAbstractResource): - - @api_permission.require(http_exception=403) - def post(self): - """Create a single new object""" - return self.controller.create(**self.reqparse_args(right='write')), 201 - - -class PyAggResourceExisting(PyAggAbstractResource): - - def get(self, obj_id=None): - """Retrieve a single object""" - return self.controller.get(id=obj_id) - - def put(self, obj_id=None): - """update an object, new attrs should be passed in the payload""" - args = self.reqparse_args(right='write', default=False) - if not args: - raise BadRequest() - return self.controller.update({'id': obj_id}, args), 200 - - def delete(self, obj_id=None): - """delete a object""" - self.controller.delete(obj_id) - return None, 204 - - -class PyAggResourceMulti(PyAggAbstractResource): - - def get(self): - """retrieve several objects. filters can be set in the payload on the - different fields of the object, and a limit can be set in there as well - """ - args = {} - try: - limit = request.json.pop('limit', 10) - order_by = request.json.pop('order_by', None) - except Exception: - args = self.reqparse_args(right='read', default=False) - limit = request.args.get('limit', 10) - order_by = request.args.get('order_by', None) - query = self.controller.read(**args) - if order_by: - query = query.order_by(order_by) - if limit: - query = query.limit(limit) - return [res for res in query] - - @api_permission.require(http_exception=403) - def post(self): - """creating several objects. payload should be: - >>> payload - [{attr1: val1, attr2: val2}, {attr1: val1, attr2: val2}] - """ - status, fail_count, results = 200, 0, [] - - class Proxy: - pass - for attrs in request.json: - try: - Proxy.json = attrs - args = self.reqparse_args('write', req=Proxy, default=False) - obj = self.controller.create(**args) - results.append(obj) - except Exception as error: - fail_count += 1 - results.append(str(error)) - if fail_count == len(results): # all failed => 500 - status = 500 - elif fail_count: # some failed => 206 - status = 206 - return results, status - - def put(self): - """updating several objects. payload should be: - >>> payload - [[obj_id1, {attr1: val1, attr2: val2}] - [obj_id2, {attr1: val1, attr2: val2}]] - """ - status, results = 200, [] - - class Proxy: - pass - for obj_id, attrs in request.json: - try: - Proxy.json = attrs - args = self.reqparse_args('write', req=Proxy, default=False) - result = self.controller.update({'id': obj_id}, args) - if result: - results.append('ok') - else: - results.append('nok') - except Exception as error: - results.append(str(error)) - if results.count('ok') == 0: # all failed => 500 - status = 500 - elif results.count('ok') != len(results): # some failed => 206 - status = 206 - return results, status - - def delete(self): - """will delete several objects, - a list of their ids should be in the payload""" - status, results = 204, [] - for obj_id in request.json: - try: - self.controller.delete(obj_id) - results.append('ok') - except Exception as error: - status = 206 - results.append(error) - # if no operation succeeded, it's not partial anymore, returning err 500 - if status == 206 and results.count('ok') == 0: - status = 500 - return results, status diff --git a/src/web/views/api/v2/feed.py b/src/web/views/api/v2/feed.py deleted file mode 100644 index a0691277..00000000 --- a/src/web/views/api/v2/feed.py +++ /dev/null @@ -1,47 +0,0 @@ -from conf import API_ROOT -from flask import current_app -from flask_restful import Api - -from web.views.common import api_permission -from web.controllers.feed import (FeedController, - DEFAULT_MAX_ERROR, - DEFAULT_LIMIT) - -from web.views.api.v2.common import PyAggAbstractResource, \ - PyAggResourceNew, \ - PyAggResourceExisting, \ - PyAggResourceMulti - - -class FeedNewAPI(PyAggResourceNew): - controller_cls = FeedController - - -class FeedAPI(PyAggResourceExisting): - controller_cls = FeedController - - -class FeedsAPI(PyAggResourceMulti): - controller_cls = FeedController - - -class FetchableFeedAPI(PyAggAbstractResource): - controller_cls = FeedController - attrs = {'max_error': {'type': int, 'default': DEFAULT_MAX_ERROR}, - 'limit': {'type': int, 'default': DEFAULT_LIMIT}} - - @api_permission.require(http_exception=403) - def get(self): - args = self.reqparse_args(right='read', allow_empty=True) - result = [feed for feed - in self.controller.list_fetchable(**args)] - return result or None, 200 if result else 204 - - -api = Api(current_app, prefix=API_ROOT) - -api.add_resource(FeedNewAPI, '/feed', endpoint='feed_new.json') -api.add_resource(FeedAPI, '/feed/<int:obj_id>', endpoint='feed.json') -api.add_resource(FeedsAPI, '/feeds', endpoint='feeds.json') -api.add_resource(FetchableFeedAPI, '/feeds/fetchable', - endpoint='fetchable_feed.json') diff --git a/src/web/views/api/v3/__init__.py b/src/web/views/api/v3/__init__.py deleted file mode 100644 index 76aa1f19..00000000 --- a/src/web/views/api/v3/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from web.views.api.v3 import article - -__all__ = ['article'] diff --git a/src/web/views/api/v3/article.py b/src/web/views/api/v3/article.py deleted file mode 100644 index 4cf35648..00000000 --- a/src/web/views/api/v3/article.py +++ /dev/null @@ -1,84 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- - -# Newspipe - A Web based news aggregator. -# Copyright (C) 2010-2018 Cédric Bonhomme - https://www.cedricbonhomme.org -# -# For more information : http://gitlab.com/newspipe/newspipe -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/> - -__author__ = "Cedric Bonhomme" -__version__ = "$Revision: 0.1 $" -__date__ = "$Date: 2016/04/29 $" -__revision__ = "$Date: 2016/04/29 $" -__copyright__ = "Copyright (c) Cedric Bonhomme" -__license__ = "GPLv3" - -from flask_login import current_user -from werkzeug.exceptions import NotFound -from flask_restless import ProcessingException -from web import models -from bootstrap import application, manager -from web.controllers import ArticleController, FeedController -from web.views.api.v3.common import AbstractProcessor -from web.views.api.v3.common import url_prefix, auth_func - -class ArticleProcessor(AbstractProcessor): - """Concrete processors for the Article Web service. - """ - - def get_single_preprocessor(self, instance_id=None, **kw): - try: - article = ArticleController(current_user.id).get(id=instance_id) - except NotFound: - raise ProcessingException(description='No such article.', code=404) - self.is_authorized(current_user, article) - - def post_preprocessor(self, data=None, **kw): - data["user_id"] = current_user.id - - try: - feed = FeedController(current_user.id).get(id=data["feed_id"]) - except NotFound: - raise ProcessingException(description='No such feed.', code=404) - self.is_authorized(current_user, feed) - - data["category_id"] = feed.category_id - - def delete_preprocessor(self, instance_id=None, **kw): - try: - article = ArticleController(current_user.id).get(id=instance_id) - except NotFound: - raise ProcessingException(description='No such article.', code=404) - self.is_authorized(current_user, article) - -article_processor = ArticleProcessor() - -blueprint_article = manager.create_api_blueprint(models.Article, - url_prefix=url_prefix, - methods=['GET', 'POST', 'PUT', 'DELETE'], - preprocessors=dict(GET_SINGLE=[auth_func, - article_processor.get_single_preprocessor], - GET_MANY=[auth_func, - article_processor.get_many_preprocessor], - POST=[auth_func, - article_processor.post_preprocessor], - PUT_SINGLE=[auth_func, - article_processor.put_single_preprocessor], - PUT_MANY=[auth_func, - article_processor.put_many_preprocessor], - DELETE=[auth_func, - article_processor.delete_preprocessor])) -application.register_blueprint(blueprint_article) diff --git a/src/web/views/api/v3/common.py b/src/web/views/api/v3/common.py deleted file mode 100644 index d5e94a3f..00000000 --- a/src/web/views/api/v3/common.py +++ /dev/null @@ -1,109 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- - -# Newspipe - A Web based news aggregator. -# Copyright (C) 2010-2018 Cédric Bonhomme - https://www.cedricbonhomme.org -# -# For more information : http://gitlab.com/newspipe/newspipe -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/> - -__author__ = "Cedric Bonhomme" -__version__ = "$Revision: 0.1 $" -__date__ = "$Date: 2016/04/29 $" -__revision__ = "$Date: 2016/04/29 $" -__copyright__ = "Copyright (c) Cedric Bonhomme" -__license__ = "GPLv3" - -from flask import request -from flask_login import current_user -from flask_restless import ProcessingException -from werkzeug.exceptions import NotFound -from web.controllers import ArticleController, UserController -from web.views.common import login_user_bundle - -url_prefix = '/api/v3' - -def auth_func(*args, **kw): - if request.authorization: - ucontr = UserController() - try: - user = ucontr.get(nickname=request.authorization.username) - except NotFound: - raise ProcessingException("Couldn't authenticate your user", - code=401) - if not ucontr.check_password(user, request.authorization.password): - raise ProcessingException("Couldn't authenticate your user", - code=401) - if not user.is_active: - raise ProcessingException("User is deactivated", code=401) - login_user_bundle(user) - if not current_user.is_authenticated: - raise ProcessingException(description='Not authenticated!', code=401) - -class AbstractProcessor(): - """Abstract processors for the Web services. - """ - - def is_authorized(self, user, obj): - if user.id != obj.user_id: - raise ProcessingException(description='Not Authorized', code=401) - - def get_single_preprocessor(self, instance_id=None, **kw): - # Check if the user is authorized to modify the specified - # instance of the model. - pass - - def get_many_preprocessor(self, search_params=None, **kw): - """Accepts a single argument, `search_params`, which is a dictionary - containing the search parameters for the request. - """ - filt = dict(name="user_id", - op="eq", - val=current_user.id) - - # Check if there are any filters there already. - if "filters" not in search_params: - search_params["filters"] = [] - - search_params["filters"].append(filt) - - def post_preprocessor(self, data=None, **kw): - pass - - def put_single_preprocessor(instance_id=None, data=None, **kw): - """Accepts two arguments, `instance_id`, the primary key of the - instance of the model to patch, and `data`, the dictionary of fields - to change on the instance. - """ - pass - - def put_many_preprocessor(search_params=None, data=None, **kw): - """Accepts two arguments: `search_params`, which is a dictionary - containing the search parameters for the request, and `data`, which - is a dictionary representing the fields to change on the matching - instances and the values to which they will be set. - """ - filt = dict(name="user_id", - op="eq", - val=current_user.id) - - # Check if there are any filters there already. - if "filters" not in search_params: - search_params["filters"] = [] - - search_params["filters"].append(filt) - - def delete_preprocessor(self, instance_id=None, **kw): - pass diff --git a/src/web/views/api/v3/feed.py b/src/web/views/api/v3/feed.py deleted file mode 100644 index 2cbbafd9..00000000 --- a/src/web/views/api/v3/feed.py +++ /dev/null @@ -1,58 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- - -# Newspipe - A Web based news aggregator. -# Copyright (C) 2010-2018 Cédric Bonhomme - https://www.cedricbonhomme.org -# -# For more information : http://gitlab.com/newspipe/newspipe -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/> - -__author__ = "Cedric Bonhomme" -__version__ = "$Revision: 0.1 $" -__date__ = "$Date: 2016/04/29 $" -__revision__ = "$Date: 2016/04/29 $" -__copyright__ = "Copyright (c) Cedric Bonhomme" -__license__ = "GPLv3" - -from flask_login import current_user -from web import models -from bootstrap import application, manager -from web.controllers import FeedController -from web.views.api.v3.common import AbstractProcessor -from web.views.api.v3.common import url_prefix, auth_func - -class FeedProcessor(AbstractProcessor): - """Concrete processors for the Feed Web service. - """ - - def get_single_preprocessor(self, instance_id=None, **kw): - # Check if the user is authorized to modify the specified - # instance of the model. - feed = FeedController(current_user.id).get(id=instance_id) - self.is_authorized(current_user, feed) - -feed_processor = FeedProcessor() - -blueprint_feed = manager.create_api_blueprint(models.Feed, - url_prefix=url_prefix, - methods=['GET', 'POST', 'PUT', 'DELETE'], - preprocessors=dict(GET_SINGLE=[auth_func, - feed_processor.get_single_preprocessor], - GET_MANY=[auth_func, - feed_processor.get_many_preprocessor], - PUT_SINGLE=[auth_func], - POST=[auth_func], - DELETE=[auth_func])) -application.register_blueprint(blueprint_feed) |