aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pysite/base_route.py21
-rw-r--r--pysite/constants.py3
-rw-r--r--pysite/database.py43
-rw-r--r--pysite/route_manager.py76
-rw-r--r--pysite/views/__init__.py0
-rw-r--r--pysite/views/api/bot/__init__.py0
-rw-r--r--pysite/views/api/bot/tag.py66
-rw-r--r--pysite/views/api/healthcheck.py4
-rw-r--r--pysite/views/staff/__init__.py0
-rw-r--r--pysite/views/staff/index.py10
-rw-r--r--templates/staff.html9
11 files changed, 176 insertions, 56 deletions
diff --git a/pysite/base_route.py b/pysite/base_route.py
index a3e8615b..c705a350 100644
--- a/pysite/base_route.py
+++ b/pysite/base_route.py
@@ -1,4 +1,8 @@
# coding=utf-8
+import os
+import random
+import string
+
from flask import Blueprint, jsonify, render_template
from flask.views import MethodView
@@ -27,7 +31,17 @@ class RouteView(BaseView):
class APIView(RouteView):
+ def validate_key(self, api_key: str):
+ """ Placeholder! """
+ return api_key == os.environ("API_KEY")
+
+ def generate_api_key(self):
+ """ Generate a random string of n characters. """
+ pool = random.choices(string.ascii_letters + string.digits, k=32)
+ return "".join(pool)
+
def error(self, error_code: ErrorCodes):
+
data = {
"error_code": error_code.value,
"error_message": "Unknown error"
@@ -40,7 +54,12 @@ class APIView(RouteView):
http_code = 404
elif error_code is ErrorCodes.unauthorized:
data["error_message"] = "Unauthorized"
- http_code = 403
+ http_code = 401
+ elif error_code is ErrorCodes.invalid_api_key:
+ data["error_message"] = "Invalid API-key"
+ http_code = 401
+ elif error_code is ErrorCodes.missing_parameters:
+ data["error_message"] = "Not all required parameters were provided"
response = jsonify(data)
response.status_code = http_code
diff --git a/pysite/constants.py b/pysite/constants.py
index e5a283a7..32d7fd14 100644
--- a/pysite/constants.py
+++ b/pysite/constants.py
@@ -1,5 +1,4 @@
# coding=utf-8
-__author__ = "Gareth Coles"
from enum import IntEnum
@@ -7,3 +6,5 @@ from enum import IntEnum
class ErrorCodes(IntEnum):
unknown_route = 0
unauthorized = 1
+ invalid_api_key = 2
+ missing_parameters = 3
diff --git a/pysite/database.py b/pysite/database.py
new file mode 100644
index 00000000..75f01378
--- /dev/null
+++ b/pysite/database.py
@@ -0,0 +1,43 @@
+# coding=utf-8
+
+import os
+
+from flask import abort
+
+import rethinkdb
+
+
+class RethinkDB:
+
+ def __init__(self, loop_type: str = "gevent"):
+ self.host = os.environ.get("RETHINKDB_HOST", "127.0.0.1")
+ self.port = os.environ.get("RETHINKDB_PORT", "28016")
+ self.database = os.environ.get("RETHINKDB_DATABASE", "pythondiscord")
+ self.conn = None
+
+ rethinkdb.set_loop_type(loop_type)
+
+ with self.get_connection(connect_database=False) as conn:
+ try:
+ rethinkdb.db_create(self.database).run(conn)
+ print(f"Database created: {self.database}")
+ except rethinkdb.RqlRuntimeError:
+ print(f"Database found: {self.database}")
+
+ def get_connection(self, connect_database: bool = True):
+ if connect_database:
+ return rethinkdb.connect(host=self.host, port=self.port, db=self.database)
+ else:
+ return rethinkdb.connect(host=self.host, port=self.port)
+
+ def before_request(self):
+ try:
+ self.conn = self.get_connection()
+ except rethinkdb.RqlDriverError:
+ abort(503, "Database connection could not be established.")
+
+ def teardown_request(self, _):
+ try:
+ self.conn.close()
+ except AttributeError:
+ pass
diff --git a/pysite/route_manager.py b/pysite/route_manager.py
index d7cf0fa1..6f973767 100644
--- a/pysite/route_manager.py
+++ b/pysite/route_manager.py
@@ -3,16 +3,10 @@ import importlib
import inspect
import os
-from flask import Blueprint, Flask, abort, g
-
-import rethinkdb
+from flask import Blueprint, Flask, g
from pysite.base_route import APIView, BaseView, ErrorView, RouteView
-
-DB_HOST = os.environ.get("RETHINKDB_HOST")
-DB_PORT = os.environ.get("RETHINKDB_PORT")
-DB_DATABASE = os.environ.get("RETHINKDB_DATABASE")
-DB_TABLE = os.environ.get("RETHINKDB_TABLE")
+from pysite.database import RethinkDB
TEMPLATES_PATH = "../templates"
STATIC_PATH = "../static"
@@ -20,29 +14,42 @@ STATIC_PATH = "../static"
class RouteManager:
def __init__(self):
+
+ # Set up the app and the database
self.app = Flask(
__name__, template_folder=TEMPLATES_PATH, static_folder=STATIC_PATH, static_url_path="/static",
)
- self.app.secret_key = os.environ.get("WEBPAGE_SECRET_KEY")
- self.app.config["SERVER_NAME"] = os.environ.get("SERVER_NAME", "localhost")
+ self.db = RethinkDB()
+ 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.before_request(self.db.before_request)
+ self.app.teardown_request(self.db.teardown_request)
- self.main_blueprint = Blueprint("main", __name__)
+ # Store the database in the Flask global context
+ with self.app.app_context():
+ g.db = self.db # type: RethinkDB
+ # Load the main blueprint
+ self.main_blueprint = Blueprint("main", __name__)
print(f"Loading Blueprint: {self.main_blueprint.name}")
self.load_views(self.main_blueprint, "pysite/views/main")
self.app.register_blueprint(self.main_blueprint)
print("")
- self.api_blueprint = Blueprint("api", __name__, subdomain="api")
+ # Load the subdomains
+ self.subdomains = ['api', 'staff']
- print(f"Loading Blueprint: {self.api_blueprint.name}")
- self.load_views(self.api_blueprint, "pysite/views/api")
- self.app.register_blueprint(self.api_blueprint)
- print("")
+ for sub in self.subdomains:
+ self.sub_blueprint = Blueprint(sub, __name__, subdomain=sub)
+
+ print(f"Loading Blueprint: {self.sub_blueprint.name}")
+ self.load_views(self.sub_blueprint, f"pysite/views/{sub}")
+ self.app.register_blueprint(self.sub_blueprint)
+ print("")
def run(self):
self.app.run(
- port=int(os.environ.get("WEBPAGE_PORT")), debug="FLASK_DEBUG" in os.environ
+ port=int(os.environ.get("WEBPAGE_PORT", 8080)), debug="FLASK_DEBUG" in os.environ
)
def load_views(self, blueprint, location="pysite/views"):
@@ -66,38 +73,3 @@ class RouteManager:
):
cls.setup(blueprint)
print(f">> View loaded: {cls.name: <15} ({module.__name__}.{cls_name})")
-
- def setup_db(self):
- connection = self.get_db_connection(connect_database=False)
-
- try:
- rethinkdb.db_create(DB_DATABASE).run(connection)
- rethinkdb.db(DB_DATABASE).table_create(DB_TABLE).run(connection)
- print("Database created")
- except rethinkdb.RqlRuntimeError:
- print("Database found")
- finally:
- connection.close()
-
- self.app.before_request(self.db_before_request)
- self.app.teardown_request(self.db_teardown_request)
-
- def get_db_connection(self, connect_database=True):
- if connect_database:
- return rethinkdb.connect(host=DB_HOST, port=DB_PORT, db=DB_DATABASE)
- else:
- return rethinkdb.connect(host=DB_HOST, port=DB_PORT)
-
- def db_before_request(self):
- try:
- # g is the Flask global context object
- g.rdb_conn = self.get_db_connection()
- except rethinkdb.RqlDriverError:
- abort(503, "Database connection could be established.")
-
- def db_teardown_request(self, _):
- try:
- # g is the Flask global context object
- g.rdb_conn.close()
- except AttributeError:
- pass
diff --git a/pysite/views/__init__.py b/pysite/views/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/pysite/views/__init__.py
diff --git a/pysite/views/api/bot/__init__.py b/pysite/views/api/bot/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/pysite/views/api/bot/__init__.py
diff --git a/pysite/views/api/bot/tag.py b/pysite/views/api/bot/tag.py
new file mode 100644
index 00000000..acbdbc0c
--- /dev/null
+++ b/pysite/views/api/bot/tag.py
@@ -0,0 +1,66 @@
+# coding=utf-8
+
+from flask import g, jsonify, request
+
+import rethinkdb
+
+from pysite.base_route import APIView
+from pysite.constants import ErrorCodes
+
+
+class TagView(APIView):
+ path = '/tag'
+ name = 'tag'
+ table = 'tag'
+
+ def __init__(self):
+ # make sure the table exists
+ with g.db.get_connection() as conn:
+ try:
+ rethinkdb.db(g.db.database).table_create(self.table, {'primary_key': 'tag_name'}).run(conn)
+ except rethinkdb.RqlRuntimeError:
+ print(f'Table {self.table} exists')
+
+ def get(self):
+ """
+ Indata must be provided as params,
+ API key must be provided as header
+ """
+ rdb = rethinkdb.table(self.table)
+ api_key = request.headers.get('X-API-Key')
+ tag_name = request.args.get('tag_name')
+
+ if self.validate_key(api_key):
+ if tag_name:
+ data = rdb.get(tag_name).run(g.db.conn)
+ data = dict(data) if data else {}
+ else:
+ data = rdb.pluck('tag_name').run(g.db.conn)
+ data = list(data) if data else []
+ else:
+ self.error(ErrorCodes.invalid_api_key)
+
+ return jsonify(data)
+
+ def post(self):
+ """ Indata must be provided as JSON. """
+ rdb = rethinkdb.table(self.table)
+ indata = request.get_json()
+ tag_name = indata.get('tag_name')
+ tag_content = indata.get('tag_content')
+ tag_category = indata.get('tag_category')
+ api_key = indata.get('api_key')
+
+ if self.validate_key(api_key):
+ if tag_name and tag_content:
+ rdb.insert({
+ 'tag_name': tag_name,
+ 'tag_content': tag_content,
+ 'tag_category': tag_category
+ }).run(g.db.conn)
+ else:
+ self.error(ErrorCodes.missing_parameters)
+ else:
+ self.error(ErrorCodes.invalid_api_key)
+
+ return jsonify({'success': True})
diff --git a/pysite/views/api/healthcheck.py b/pysite/views/api/healthcheck.py
index 9d1f681a..2ff5dfb0 100644
--- a/pysite/views/api/healthcheck.py
+++ b/pysite/views/api/healthcheck.py
@@ -1,10 +1,10 @@
# coding=utf-8
from flask import jsonify
-from pysite.base_route import RouteView
+from pysite.base_route import APIView
-class IndexView(RouteView):
+class HealthCheckView(APIView):
path = "/healthcheck"
name = "healthcheck"
diff --git a/pysite/views/staff/__init__.py b/pysite/views/staff/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/pysite/views/staff/__init__.py
diff --git a/pysite/views/staff/index.py b/pysite/views/staff/index.py
new file mode 100644
index 00000000..c23af392
--- /dev/null
+++ b/pysite/views/staff/index.py
@@ -0,0 +1,10 @@
+# coding=utf-8
+from pysite.base_route import RouteView
+
+
+class StaffView(RouteView):
+ path = "/"
+ name = "staff"
+
+ def get(self):
+ return self.render("staff.html")
diff --git a/templates/staff.html b/templates/staff.html
new file mode 100644
index 00000000..3017f65c
--- /dev/null
+++ b/templates/staff.html
@@ -0,0 +1,9 @@
+{% extends "base.html" %}
+{% block title %}Home{% endblock %}
+{% block content %}
+ <div class="uk-container uk-section">
+ <h1 class="uk-title uk-text-center">
+ This will be for staff only. Login required.
+ </h1>
+ </div>
+{% endblock %} \ No newline at end of file