diff options
Diffstat (limited to 'pysite')
| -rw-r--r-- | pysite/base_route.py | 3 | ||||
| -rw-r--r-- | pysite/database.py | 30 | ||||
| -rw-r--r-- | pysite/decorators.py | 1 | ||||
| -rw-r--r-- | pysite/route_manager.py | 2 | ||||
| -rw-r--r-- | pysite/views/api/bot/tag.py | 57 | ||||
| -rw-r--r-- | pysite/views/api/bot/tags.py | 116 | ||||
| -rw-r--r-- | pysite/views/api/error_view.py | 40 | 
7 files changed, 189 insertions, 60 deletions
| diff --git a/pysite/base_route.py b/pysite/base_route.py index f389b56e..4e1a63a7 100644 --- a/pysite/base_route.py +++ b/pysite/base_route.py @@ -86,11 +86,12 @@ class APIView(RouteView):      ...         return self.error(ErrorCodes.unknown_route)      """ -    def error(self, error_code: ErrorCodes) -> Response: +    def error(self, error_code: ErrorCodes, error_info: str = "") -> Response:          """          Generate a JSON response for you to return from your handler, for a specific type of API error          :param error_code: The type of error to generate a response for - see `constants.ErrorCodes` for more +        :param error_info: An optional message with more information about the error.          :return: A Flask Response object that you can return from your handler          """ diff --git a/pysite/database.py b/pysite/database.py index 78c4368a..add76923 100644 --- a/pysite/database.py +++ b/pysite/database.py @@ -103,6 +103,34 @@ class RethinkDB:              self.log.debug(f"Table created: '{table_name}'")              return True +    def delete(self, table_name: str, primary_key: Optional[str] = None, +               durability: str = "hard", return_changes: Union[bool, str] = False +               ) -> Union[Dict[str, Any], None]: +        """ +        Delete one or all documents from a table. This can only delete +        either the contents of an entire table, or a single document. +        For more complex delete operations, please use self.query. + +        :param table_name: The name of the table to delete from. This must be provided. +        :param primary_key: The primary_key to delete from that table. This is optional. +        :param durability: "hard" (the default) to write the change immediately, "soft" otherwise +        :param return_changes: Whether to return a list of changed values or not - defaults to False +        :return: if return_changes is True, returns a dict containing all changes. Else, returns None. +        """ + +        if primary_key: +            query = self.query(table_name).get(primary_key).delete( +                durability=durability, return_changes=return_changes +            ) +        else: +            query = self.query(table_name).delete( +                durability=durability, return_changes=return_changes +            ) + +        if return_changes: +            return self.run(query, coerce=dict) +        self.run(query) +      def drop_table(self, table_name: str):          """          Attempt to drop a table from the database, along with its data @@ -168,7 +196,7 @@ class RethinkDB:          :param connect_database: If creating a new connection, whether to connect to the database immediately          :param coerce: Optionally, an object type to attempt to coerce the result to -        :return: THe result of the operation +        :return: The result of the operation          """          if not new_connection: diff --git a/pysite/decorators.py b/pysite/decorators.py index 952c2349..d8bf1381 100644 --- a/pysite/decorators.py +++ b/pysite/decorators.py @@ -48,6 +48,7 @@ def api_params(schema: Schema, validation_type: ValidationTypes = ValidationType                      if not isinstance(data, list):                          data = [data] +                  except JSONDecodeError:                      return self.error(ErrorCodes.bad_data_format)  # pragma: no cover diff --git a/pysite/route_manager.py b/pysite/route_manager.py index 53b24def..72517a3c 100644 --- a/pysite/route_manager.py +++ b/pysite/route_manager.py @@ -27,7 +27,7 @@ class RouteManager:          self.db = RethinkDB()          self.log = logging.getLogger()          self.app.secret_key = os.environ.get("WEBPAGE_SECRET_KEY", "super_secret") -        self.app.config["SERVER_NAME"] = os.environ.get("SERVER_NAME", "pythondiscord.com:8080") +        self.app.config["SERVER_NAME"] = os.environ.get("SERVER_NAME", "pythondiscord.local:8080")          self.app.before_request(self.db.before_request)          self.app.teardown_request(self.db.teardown_request) diff --git a/pysite/views/api/bot/tag.py b/pysite/views/api/bot/tag.py deleted file mode 100644 index 8818074e..00000000 --- a/pysite/views/api/bot/tag.py +++ /dev/null @@ -1,57 +0,0 @@ -# coding=utf-8 - -from flask import jsonify, request - -from pysite.base_route import APIView -from pysite.constants import ErrorCodes -from pysite.decorators import api_key -from pysite.mixins import DBMixin - - -class TagView(APIView, DBMixin): -    path = "/tag" -    name = "tag" -    table_name = "tag" -    table_primary_key = "tag_name" - -    @api_key -    def get(self): -        """ -        Data must be provided as params, -        API key must be provided as header -        """ - -        tag_name = request.args.get("tag_name") - -        if tag_name: -            data = self.db.get(self.table_name, tag_name) or {}  # pragma: no cover -        else: -            data = self.db.pluck(self.table_name, "tag_name") or [] - -        return jsonify(data)  # pragma: no cover - -    @api_key -    def post(self): -        """ -        Data must be provided as JSON. -        """ - -        data = request.get_json() - -        tag_name = data.get("tag_name") -        tag_content = data.get("tag_content") -        tag_category = data.get("tag_category") - -        if tag_name and tag_content: -            self.db.insert( -                self.table_name, -                { -                    "tag_name": tag_name, -                    "tag_content": tag_content, -                    "tag_category": tag_category -                } -            ) -        else: -            return self.error(ErrorCodes.incorrect_parameters) - -        return jsonify({"success": True})  # pragma: no cover diff --git a/pysite/views/api/bot/tags.py b/pysite/views/api/bot/tags.py new file mode 100644 index 00000000..9db926f4 --- /dev/null +++ b/pysite/views/api/bot/tags.py @@ -0,0 +1,116 @@ +# coding=utf-8 + +from flask import jsonify +from schema import Schema, Optional + +from pysite.base_route import APIView +from pysite.constants import ValidationTypes +from pysite.decorators import api_key, api_params +from pysite.mixins import DBMixin + +GET_SCHEMA = Schema([ +    { +        Optional("tag_name"): str +    } +]) + +POST_SCHEMA = Schema([ +    { +        "tag_name": str, +        "tag_content": str +    } +]) + +DELETE_SCHEMA = Schema([ +    { +        "tag_name": str +    } +]) + + +class TagsView(APIView, DBMixin): +    path = "/tags" +    name = "tags" +    table_name = "tags" +    table_primary_key = "tag_name" + +    @api_key +    @api_params(schema=GET_SCHEMA, validation_type=ValidationTypes.params) +    def get(self, params=None): +        """ +        Fetches tags from the database. + +        - If tag_name is provided, it fetches +        that specific tag. + +        - If tag_category is provided, it fetches +        all tags in that category. + +        - If nothing is provided, it will +        fetch a list of all tag_names. + +        Data must be provided as params. +        API key must be provided as header. +        """ + +        tag_name = None + +        if params: +            tag_name = params[0].get("tag_name") + +        if tag_name: +            data = self.db.get(self.table_name, tag_name) or {} +        else: +            data = self.db.pluck(self.table_name, "tag_name") or [] + +        return jsonify(data) + +    @api_key +    @api_params(schema=POST_SCHEMA, validation_type=ValidationTypes.json) +    def post(self, json_data): +        """ +        If the tag_name doesn't exist, this +        saves a new tag in the database. + +        If the tag_name already exists, +        this will edit the existing tag. + +        Data must be provided as JSON. +        API key must be provided as header. +        """ + +        json_data = json_data[0] + +        tag_name = json_data.get("tag_name") +        tag_content = json_data.get("tag_content") + +        self.db.insert( +            self.table_name, +            { +                "tag_name": tag_name, +                "tag_content": tag_content +            }, +            conflict="update"  # If it exists, update it. +        ) + +        return jsonify({"success": True}) + +    @api_key +    @api_params(schema=DELETE_SCHEMA, validation_type=ValidationTypes.json) +    def delete(self, data): +        """ +        Deletes a tag from the database. + +        Data must be provided as JSON. +        API key must be provided as header. +        """ + +        json = data[0] +        tag_name = json.get("tag_name") + +        self.db.delete( +            self.table_name, +            tag_name +        ) + +        return jsonify({"success": True}) diff --git a/pysite/views/api/error_view.py b/pysite/views/api/error_view.py new file mode 100644 index 00000000..e5301336 --- /dev/null +++ b/pysite/views/api/error_view.py @@ -0,0 +1,40 @@ +# coding=utf-8 +from flask import jsonify +from werkzeug.exceptions import HTTPException + +from pysite.base_route import ErrorView + + +class APIErrorView(ErrorView): +    name = "api_error_all" +    error_code = range(400, 600) + +    def __init__(self): + +        # Direct errors for all methods at self.return_error +        methods = [ +            'get', 'post', 'put', +            'delete', 'patch', 'connect', +            'options', 'trace' +        ] + +        for method in methods: +            setattr(self, method, self.return_error) + +    def return_error(self, error: HTTPException): +        """ +        Return a basic JSON object representing the HTTP error, +        as well as propagating its status code +        """ + +        message = str(error) +        code = 500 + +        if isinstance(error, HTTPException): +            message = error.description +            code = error.code + +        return jsonify({ +            "error_code": -1, +            "error_message": message +        }), code | 
