diff --git a/README.md b/README.md
index a988ebbf..78242acd 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,28 @@
-# `examplepack`
+# `wikipack`
-This is an example Pack for [Royalnet](https://github.com/Steffo99/royalnet)!
+This pack adds a small Wiki to Royalnet, allowing communities to create their own small Wikis.
-> To be updated with more info on how to create your own pack.
\ No newline at end of file
+## Configuration options
+
+```toml
+[Packs."wikipack"]
+
+# The roles that are authorized by default to complete certain actions.
+# Setting them to an empty string disables the authentication requirement, allowing unauthenticated users that privilege
+[Packs."wikipack".roles]
+
+# Users with this role will be able to view wiki pages that do not have a different role set.
+view = ""
+
+# Users with this role will be able to create new wiki pages.
+create = "wiki_create"
+
+# Users with this role will be able to edit wiki pages that do not have a different role set.
+edit = "wiki_edit"
+
+# Users with this role will be able to delete wiki pages.
+delete = "wiki_delete"
+
+# Users with this role will override all other privileges.
+admin = "wiki_admin"
+```
diff --git a/examplepack/commands/example.py b/examplepack/commands/example.py
deleted file mode 100644
index f98652ba..00000000
--- a/examplepack/commands/example.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from typing import *
-from royalnet.commands import *
-from royalnet.utils import *
-
-
-class ExampleCommand(Command):
- name: str = "example"
-
- description: str = "Say Hello to the world!"
-
- async def run(self, args: CommandArgs, data: CommandData) -> None:
- await data.reply("Hello world!")
diff --git a/examplepack/events/example.py b/examplepack/events/example.py
deleted file mode 100644
index 866abc7a..00000000
--- a/examplepack/events/example.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from typing import *
-from royalnet.commands import *
-from royalnet.utils import *
-
-
-class ExampleEvent(Event):
- name = "example"
-
- async def run(self, **kwargs) -> dict:
- return {"hello": "world"}
diff --git a/examplepack/stars/__init__.py b/examplepack/stars/__init__.py
deleted file mode 100644
index 8193b6d8..00000000
--- a/examplepack/stars/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Imports go here!
-# TODO: If you create a new star, remember to import it here...
-# from .example import ExampleStar
-# from .api_example import ApiExampleStar
-
-# Enter the PageStars of your Pack here!
-# TODO: and to add it either to the list here if it is a PageStar...
-available_page_stars = [
- # ExampleStar,
- # ApiExampleStar,
-]
-
-# Don't change this, it should automatically generate __all__
-__all__ = [command.__name__ for command in available_page_stars]
diff --git a/examplepack/stars/api_example.py b/examplepack/stars/api_example.py
deleted file mode 100644
index 80b03165..00000000
--- a/examplepack/stars/api_example.py
+++ /dev/null
@@ -1,70 +0,0 @@
-import royalnet.utils as ru
-import royalnet.constellation.api as rca
-import royalnet.constellation.api.apierrors as rcae
-
-
-# View autogenerated docs for all ApiStars at the HTTP path `/docs`
-
-
-class EchoStar(rca.ApiStar):
- # What does this ApiStar do?
- summary = "Returns the same string entered as input."
-
- # Optional: A longer description of what the ApiStar should do.
- description = """
- NOTE: This method can only be used by Royalnet Admins.
- """
-
- # The HTTP methods that can be used with this ApiStar.
- methods = ["GET", "POST"]
- # You can disambiguate between methods using the `data.method` variable.
-
- # The HTTP path this ApiStar should bind to.
- path = "/api/example/echo/v1"
-
- # Does this method require any auth?
- # Only for documentation purposes, it doesn't do any check on it's own.
- requires_auth = True
- # To authenticate an user through their token, use the `await data.user()` method.
- # If the user isn't logged in, the method authomatically returns 403 Forbidden, unless `rcae.ForbiddenError`
- # is caught.
-
- # A dict of paramenters accepted by this method, with a description of their purpose.
- parameters = {
- "echo": "What should the method return? "
- "(Optional: if nothing is passed, the ApiStar will return the username of the caller.)",
- "error": "Should the method return a sample error?"
- }
- # You can access parameters by using `data` as a dict with the parameter name as key.
- # If a missing parameter is accessed, a `rcae.MissingParameterError` will be raised, which will lead to a
- # 400 Bad Request error if not caught.
-
- # The autodoc categories this ApiStar should fall in.
- tags = ["example"]
-
- # The actual method called when the ApiStar received a HTTP request.
- # It must return a JSON-compatible object, such as a str, a int, a float, a list, a dict or None.
- async def api(self, data: rca.ApiData) -> ru.JSON:
-
- # If "true" is passed as the "error" parameter in the query string...
- if data["error"] == "true":
- # ...return an example error
- raise Exception("Example error! Everything works as intended.")
- # If the "error" parameter is missing, the ApiStar will respond with 400 Bad Request
-
- # Ensure the user is logged in
- user = await data.user()
- # Check if the user has the role "admin"
- if "admin" not in user.roles:
- raise Exception("Only admins can call this method!")
-
- # Get the value of the "echo" parameter, without raising an exception if it doesn't exist
- echo = data.get("echo")
-
- if echo is None:
- # Find the username of the logged in user
- # user is a SQLAlchemy ORM object generated from the Users table defined in `royalnet.backpack.tables.users`
- echo = user.username
-
- # Return a 200 OK successful response containing the value of the echo variable and the HTTP method used
- return {"echo": echo, "method_used": data.method}
diff --git a/examplepack/stars/example.py b/examplepack/stars/example.py
deleted file mode 100644
index 6b044cea..00000000
--- a/examplepack/stars/example.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from starlette.requests import Request
-from starlette.responses import *
-from royalnet.constellation import *
-from royalnet.utils import *
-
-
-class ExampleStar(PageStar):
- path = "/example"
-
- async def page(self, request: Request) -> JSONResponse:
- return HTMLResponse("""
henlo!
""")
diff --git a/examplepack/tables/example.py b/examplepack/tables/example.py
deleted file mode 100644
index 83078e7e..00000000
--- a/examplepack/tables/example.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from sqlalchemy import *
-from sqlalchemy.orm import *
-from sqlalchemy.ext.declarative import declared_attr
-
-
-class Example:
- __tablename__ = "examples"
-
- @declared_attr
- def creator_id(self):
- return Column(Integer, ForeignKey("users.uid"), primary_key=True)
-
- @declared_attr
- def creator(self):
- return relationship("User", backref=backref("examples_createdx"))
-
- @declared_attr
- def example(self):
- return Column(String, nullable=False, default="Hello world!")
diff --git a/examplepack/version.py b/examplepack/version.py
deleted file mode 100644
index 422feb95..00000000
--- a/examplepack/version.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# TODO: Increment this every new version of your pack!
-semantic = "0.1.0"
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 00000000..4d966fc4
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,485 @@
+[[package]]
+category = "main"
+description = "Modern password hashing for your software and your servers"
+name = "bcrypt"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "3.1.7"
+
+[package.dependencies]
+cffi = ">=1.1"
+six = ">=1.4.1"
+
+[package.extras]
+tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)"]
+
+[[package]]
+category = "main"
+description = "Foreign Function Interface for Python calling C code."
+name = "cffi"
+optional = false
+python-versions = "*"
+version = "1.14.0"
+
+[package.dependencies]
+pycparser = "*"
+
+[[package]]
+category = "main"
+description = "Composable command line interface toolkit"
+name = "click"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "7.1.2"
+
+[[package]]
+category = "main"
+description = "Date parsing library designed to parse dates from HTML pages"
+name = "dateparser"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "0.7.6"
+
+[package.dependencies]
+python-dateutil = "*"
+pytz = "*"
+regex = "!=2019.02.19"
+tzlocal = "*"
+
+[[package]]
+category = "main"
+description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+name = "h11"
+optional = false
+python-versions = "*"
+version = "0.9.0"
+
+[[package]]
+category = "main"
+description = "A collection of framework independent HTTP protocol utils."
+marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"pypy\""
+name = "httptools"
+optional = false
+python-versions = "*"
+version = "0.0.13"
+
+[[package]]
+category = "main"
+description = "psycopg2 - Python-PostgreSQL Database Adapter"
+name = "psycopg2-binary"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
+version = "2.8.5"
+
+[[package]]
+category = "main"
+description = "C parser in Python"
+name = "pycparser"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "2.20"
+
+[[package]]
+category = "main"
+description = "Extensions to the standard Python datetime module"
+name = "python-dateutil"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+version = "2.8.1"
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+category = "main"
+description = "A streaming multipart parser for Python"
+name = "python-multipart"
+optional = false
+python-versions = "*"
+version = "0.0.5"
+
+[package.dependencies]
+six = ">=1.4.0"
+
+[[package]]
+category = "main"
+description = "World timezone definitions, modern and historical"
+name = "pytz"
+optional = false
+python-versions = "*"
+version = "2020.1"
+
+[[package]]
+category = "main"
+description = "Alternative regular expression module, to replace re."
+name = "regex"
+optional = false
+python-versions = "*"
+version = "2020.6.8"
+
+[[package]]
+category = "main"
+description = "A multipurpose bot and web framework"
+name = "royalnet"
+optional = false
+python-versions = ">=3.8,<4.0"
+version = "5.8.14"
+
+[package.dependencies]
+dateparser = ">=0.7.2,<0.8.0"
+toml = ">=0.10.0,<0.11.0"
+
+[package.dependencies.bcrypt]
+optional = true
+version = ">=3.1.7,<4.0.0"
+
+[package.dependencies.psycopg2_binary]
+optional = true
+version = ">=2.8.4,<3.0.0"
+
+[package.dependencies.python-multipart]
+optional = true
+version = ">=0.0.5,<0.0.6"
+
+[package.dependencies.sqlalchemy]
+optional = true
+version = ">=1.3.10,<2.0.0"
+
+[package.dependencies.starlette]
+optional = true
+version = ">=0.12.13,<0.13.0"
+
+[package.dependencies.uvicorn]
+optional = true
+version = ">=0.10.7,<0.11.0"
+
+[package.extras]
+alchemy_easy = ["sqlalchemy (>=1.3.10,<2.0.0)", "psycopg2_binary (>=2.8.4,<3.0.0)", "bcrypt (>=3.1.7,<4.0.0)"]
+alchemy_hard = ["sqlalchemy (>=1.3.10,<2.0.0)", "psycopg2 (>=2.8.4,<3.0.0)", "bcrypt (>=3.1.7,<4.0.0)"]
+bard = ["ffmpeg_python (>=0.2.0,<0.3.0)", "youtube-dl", "eyed3 (>=0.9,<0.10)"]
+coloredlogs = ["coloredlogs (>=10.0,<11.0)"]
+constellation = ["starlette (>=0.12.13,<0.13.0)", "uvicorn (>=0.10.7,<0.11.0)", "python-multipart (>=0.0.5,<0.0.6)"]
+discord = ["discord.py (>=1.3.1,<2.0.0)", "pynacl (>=1.3.0,<2.0.0)"]
+herald = ["websockets (>=8.1,<9.0)"]
+matrix = ["matrix-nio (>=0.6,<0.7)"]
+sentry = ["sentry_sdk (>=0.13.2,<0.14.0)"]
+telegram = ["python_telegram_bot (>=12.2.0,<13.0.0)"]
+
+[[package]]
+category = "main"
+description = "Python 2 and 3 compatibility utilities"
+name = "six"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+version = "1.15.0"
+
+[[package]]
+category = "main"
+description = "Database Abstraction Library"
+name = "sqlalchemy"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.3.17"
+
+[package.extras]
+mssql = ["pyodbc"]
+mssql_pymssql = ["pymssql"]
+mssql_pyodbc = ["pyodbc"]
+mysql = ["mysqlclient"]
+oracle = ["cx-oracle"]
+postgresql = ["psycopg2"]
+postgresql_pg8000 = ["pg8000"]
+postgresql_psycopg2binary = ["psycopg2-binary"]
+postgresql_psycopg2cffi = ["psycopg2cffi"]
+pymysql = ["pymysql"]
+
+[[package]]
+category = "main"
+description = "The little ASGI library that shines."
+name = "starlette"
+optional = false
+python-versions = ">=3.6"
+version = "0.12.13"
+
+[package.extras]
+full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"]
+
+[[package]]
+category = "main"
+description = "Python Library for Tom's Obvious, Minimal Language"
+name = "toml"
+optional = false
+python-versions = "*"
+version = "0.10.1"
+
+[[package]]
+category = "main"
+description = "tzinfo object for the local timezone"
+name = "tzlocal"
+optional = false
+python-versions = "*"
+version = "2.1"
+
+[package.dependencies]
+pytz = "*"
+
+[[package]]
+category = "main"
+description = "The lightning-fast ASGI server."
+name = "uvicorn"
+optional = false
+python-versions = "*"
+version = "0.10.9"
+
+[package.dependencies]
+click = ">=7.0.0,<8.0.0"
+h11 = ">=0.9.0,<0.10.0"
+httptools = "0.0.13"
+uvloop = ">=0.14.0"
+websockets = ">=8.0.0,<9.0.0"
+
+[[package]]
+category = "main"
+description = "Fast implementation of asyncio event loop on top of libuv"
+marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"pypy\""
+name = "uvloop"
+optional = false
+python-versions = "*"
+version = "0.14.0"
+
+[[package]]
+category = "main"
+description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+name = "websockets"
+optional = false
+python-versions = ">=3.6.1"
+version = "8.1"
+
+[metadata]
+content-hash = "94e12371cf2623dbf0fbd854c1667940694b6a5607d81a3bb9e588b30c8a5702"
+ python-versions = "^3.8"
+
+[metadata.files]
+bcrypt = [
+ {file = "bcrypt-3.1.7-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7"},
+ {file = "bcrypt-3.1.7-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:69361315039878c0680be456640f8705d76cb4a3a3fe1e057e0f261b74be4b31"},
+ {file = "bcrypt-3.1.7-cp27-cp27m-win32.whl", hash = "sha256:5432dd7b34107ae8ed6c10a71b4397f1c853bd39a4d6ffa7e35f40584cffd161"},
+ {file = "bcrypt-3.1.7-cp27-cp27m-win_amd64.whl", hash = "sha256:9fe92406c857409b70a38729dbdf6578caf9228de0aef5bc44f859ffe971a39e"},
+ {file = "bcrypt-3.1.7-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:763669a367869786bb4c8fcf731f4175775a5b43f070f50f46f0b59da45375d0"},
+ {file = "bcrypt-3.1.7-cp34-abi3-macosx_10_6_intel.whl", hash = "sha256:a190f2a5dbbdbff4b74e3103cef44344bc30e61255beb27310e2aec407766052"},
+ {file = "bcrypt-3.1.7-cp34-abi3-manylinux1_x86_64.whl", hash = "sha256:c9457fa5c121e94a58d6505cadca8bed1c64444b83b3204928a866ca2e599105"},
+ {file = "bcrypt-3.1.7-cp34-cp34m-win32.whl", hash = "sha256:8b10acde4e1919d6015e1df86d4c217d3b5b01bb7744c36113ea43d529e1c3de"},
+ {file = "bcrypt-3.1.7-cp34-cp34m-win_amd64.whl", hash = "sha256:cb93f6b2ab0f6853550b74e051d297c27a638719753eb9ff66d1e4072be67133"},
+ {file = "bcrypt-3.1.7-cp35-cp35m-win32.whl", hash = "sha256:6fe49a60b25b584e2f4ef175b29d3a83ba63b3a4df1b4c0605b826668d1b6be5"},
+ {file = "bcrypt-3.1.7-cp35-cp35m-win_amd64.whl", hash = "sha256:a595c12c618119255c90deb4b046e1ca3bcfad64667c43d1166f2b04bc72db09"},
+ {file = "bcrypt-3.1.7-cp36-cp36m-win32.whl", hash = "sha256:74a015102e877d0ccd02cdeaa18b32aa7273746914a6c5d0456dd442cb65b99c"},
+ {file = "bcrypt-3.1.7-cp36-cp36m-win_amd64.whl", hash = "sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89"},
+ {file = "bcrypt-3.1.7-cp37-cp37m-win32.whl", hash = "sha256:19a4b72a6ae5bb467fea018b825f0a7d917789bcfe893e53f15c92805d187294"},
+ {file = "bcrypt-3.1.7-cp37-cp37m-win_amd64.whl", hash = "sha256:ff032765bb8716d9387fd5376d987a937254b0619eff0972779515b5c98820bc"},
+ {file = "bcrypt-3.1.7-cp38-cp38-win32.whl", hash = "sha256:ce4e4f0deb51d38b1611a27f330426154f2980e66582dc5f438aad38b5f24fc1"},
+ {file = "bcrypt-3.1.7-cp38-cp38-win_amd64.whl", hash = "sha256:6305557019906466fc42dbc53b46da004e72fd7a551c044a827e572c82191752"},
+ {file = "bcrypt-3.1.7.tar.gz", hash = "sha256:0b0069c752ec14172c5f78208f1863d7ad6755a6fae6fe76ec2c80d13be41e42"},
+]
+cffi = [
+ {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"},
+ {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"},
+ {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"},
+ {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"},
+ {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"},
+ {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"},
+ {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"},
+ {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"},
+ {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"},
+ {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"},
+ {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"},
+ {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"},
+ {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"},
+ {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"},
+ {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"},
+ {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"},
+ {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"},
+ {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"},
+ {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"},
+ {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"},
+ {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"},
+ {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"},
+ {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"},
+ {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"},
+ {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"},
+ {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"},
+ {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"},
+ {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"},
+]
+click = [
+ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
+ {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
+]
+dateparser = [
+ {file = "dateparser-0.7.6-py2.py3-none-any.whl", hash = "sha256:7552c994f893b5cb8fcf103b4cd2ff7f57aab9bfd2619fdf0cf571c0740fd90b"},
+ {file = "dateparser-0.7.6.tar.gz", hash = "sha256:e875efd8c57c85c2d02b238239878db59ff1971f5a823457fcc69e493bf6ebfa"},
+]
+h11 = [
+ {file = "h11-0.9.0-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"},
+ {file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"},
+]
+httptools = [
+ {file = "httptools-0.0.13.tar.gz", hash = "sha256:e00cbd7ba01ff748e494248183abc6e153f49181169d8a3d41bb49132ca01dfc"},
+]
+psycopg2-binary = [
+ {file = "psycopg2-binary-2.8.5.tar.gz", hash = "sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6"},
+ {file = "psycopg2_binary-2.8.5-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:96d3038f5bd061401996614f65d27a4ecb62d843eb4f48e212e6d129171a721f"},
+ {file = "psycopg2_binary-2.8.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:08507efbe532029adee21b8d4c999170a83760d38249936038bd0602327029b5"},
+ {file = "psycopg2_binary-2.8.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:b9a8b391c2b0321e0cd7ec6b4cfcc3dd6349347bd1207d48bcb752aa6c553a66"},
+ {file = "psycopg2_binary-2.8.5-cp27-cp27m-win32.whl", hash = "sha256:3286541b9d85a340ee4ed42732d15fc1bb441dc500c97243a768154ab8505bb5"},
+ {file = "psycopg2_binary-2.8.5-cp27-cp27m-win_amd64.whl", hash = "sha256:008da3ab51adc70a5f1cfbbe5db3a22607ab030eb44bcecf517ad11a0c2b3cac"},
+ {file = "psycopg2_binary-2.8.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ba13346ff6d3eb2dca0b6fa0d8a9d999eff3dcd9b55f3a890f12b0b6362b2b38"},
+ {file = "psycopg2_binary-2.8.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:c8830b7d5f16fd79d39b21e3d94f247219036b29b30c8270314c46bf8b732389"},
+ {file = "psycopg2_binary-2.8.5-cp34-cp34m-win32.whl", hash = "sha256:51f7823f1b087d2020d8e8c9e6687473d3d239ba9afc162d9b2ab6e80b53f9f9"},
+ {file = "psycopg2_binary-2.8.5-cp34-cp34m-win_amd64.whl", hash = "sha256:107d9be3b614e52a192719c6bf32e8813030020ea1d1215daa86ded9a24d8b04"},
+ {file = "psycopg2_binary-2.8.5-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:930315ac53dc65cbf52ab6b6d27422611f5fb461d763c531db229c7e1af6c0b3"},
+ {file = "psycopg2_binary-2.8.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6bb2dd006a46a4a4ce95201f836194eb6a1e863f69ee5bab506673e0ca767057"},
+ {file = "psycopg2_binary-2.8.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:3939cf75fc89c5e9ed836e228c4a63604dff95ad19aed2bbf71d5d04c15ed5ce"},
+ {file = "psycopg2_binary-2.8.5-cp35-cp35m-win32.whl", hash = "sha256:a20299ee0ea2f9cca494396ac472d6e636745652a64a418b39522c120fd0a0a4"},
+ {file = "psycopg2_binary-2.8.5-cp35-cp35m-win_amd64.whl", hash = "sha256:cc30cb900f42c8a246e2cb76539d9726f407330bc244ca7729c41a44e8d807fb"},
+ {file = "psycopg2_binary-2.8.5-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:40abc319f7f26c042a11658bf3dd3b0b3bceccf883ec1c565d5c909a90204434"},
+ {file = "psycopg2_binary-2.8.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:702f09d8f77dc4794651f650828791af82f7c2efd8c91ae79e3d9fe4bb7d4c98"},
+ {file = "psycopg2_binary-2.8.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d1a8b01f6a964fec702d6b6dac1f91f2b9f9fe41b310cbb16c7ef1fac82df06d"},
+ {file = "psycopg2_binary-2.8.5-cp36-cp36m-win32.whl", hash = "sha256:17a0ea0b0eabf07035e5e0d520dabc7950aeb15a17c6d36128ba99b2721b25b1"},
+ {file = "psycopg2_binary-2.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:e004db88e5a75e5fdab1620fb9f90c9598c2a195a594225ac4ed2a6f1c23e162"},
+ {file = "psycopg2_binary-2.8.5-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:a34826d6465c2e2bbe9d0605f944f19d2480589f89863ed5f091943be27c9de4"},
+ {file = "psycopg2_binary-2.8.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cac918cd7c4c498a60f5d2a61d4f0a6091c2c9490d81bc805c963444032d0dab"},
+ {file = "psycopg2_binary-2.8.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:7b832d76cc65c092abd9505cc670c4e3421fd136fb6ea5b94efbe4c146572505"},
+ {file = "psycopg2_binary-2.8.5-cp37-cp37m-win32.whl", hash = "sha256:bb0608694a91db1e230b4a314e8ed00ad07ed0c518f9a69b83af2717e31291a3"},
+ {file = "psycopg2_binary-2.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:eb2f43ae3037f1ef5e19339c41cf56947021ac892f668765cd65f8ab9814192e"},
+ {file = "psycopg2_binary-2.8.5-cp38-cp38-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:07cf82c870ec2d2ce94d18e70c13323c89f2f2a2628cbf1feee700630be2519a"},
+ {file = "psycopg2_binary-2.8.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a69970ee896e21db4c57e398646af9edc71c003bc52a3cc77fb150240fefd266"},
+ {file = "psycopg2_binary-2.8.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7036ccf715925251fac969f4da9ad37e4b7e211b1e920860148a10c0de963522"},
+ {file = "psycopg2_binary-2.8.5-cp38-cp38-win32.whl", hash = "sha256:8f74e631b67482d504d7e9cf364071fc5d54c28e79a093ff402d5f8f81e23bfa"},
+ {file = "psycopg2_binary-2.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:fa466306fcf6b39b8a61d003123d442b23707d635a5cb05ac4e1b62cc79105cd"},
+]
+pycparser = [
+ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
+ {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
+]
+python-dateutil = [
+ {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"},
+ {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"},
+]
+python-multipart = [
+ {file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"},
+]
+pytz = [
+ {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"},
+ {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"},
+]
+regex = [
+ {file = "regex-2020.6.8-cp27-cp27m-win32.whl", hash = "sha256:fbff901c54c22425a5b809b914a3bfaf4b9570eee0e5ce8186ac71eb2025191c"},
+ {file = "regex-2020.6.8-cp27-cp27m-win_amd64.whl", hash = "sha256:112e34adf95e45158c597feea65d06a8124898bdeac975c9087fe71b572bd938"},
+ {file = "regex-2020.6.8-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:92d8a043a4241a710c1cf7593f5577fbb832cf6c3a00ff3fc1ff2052aff5dd89"},
+ {file = "regex-2020.6.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bae83f2a56ab30d5353b47f9b2a33e4aac4de9401fb582b55c42b132a8ac3868"},
+ {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:b2ba0f78b3ef375114856cbdaa30559914d081c416b431f2437f83ce4f8b7f2f"},
+ {file = "regex-2020.6.8-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:95fa7726d073c87141f7bbfb04c284901f8328e2d430eeb71b8ffdd5742a5ded"},
+ {file = "regex-2020.6.8-cp36-cp36m-win32.whl", hash = "sha256:e3cdc9423808f7e1bb9c2e0bdb1c9dc37b0607b30d646ff6faf0d4e41ee8fee3"},
+ {file = "regex-2020.6.8-cp36-cp36m-win_amd64.whl", hash = "sha256:c78e66a922de1c95a208e4ec02e2e5cf0bb83a36ceececc10a72841e53fbf2bd"},
+ {file = "regex-2020.6.8-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:08997a37b221a3e27d68ffb601e45abfb0093d39ee770e4257bd2f5115e8cb0a"},
+ {file = "regex-2020.6.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2f6f211633ee8d3f7706953e9d3edc7ce63a1d6aad0be5dcee1ece127eea13ae"},
+ {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:55b4c25cbb3b29f8d5e63aeed27b49fa0f8476b0d4e1b3171d85db891938cc3a"},
+ {file = "regex-2020.6.8-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:89cda1a5d3e33ec9e231ece7307afc101b5217523d55ef4dc7fb2abd6de71ba3"},
+ {file = "regex-2020.6.8-cp37-cp37m-win32.whl", hash = "sha256:690f858d9a94d903cf5cada62ce069b5d93b313d7d05456dbcd99420856562d9"},
+ {file = "regex-2020.6.8-cp37-cp37m-win_amd64.whl", hash = "sha256:1700419d8a18c26ff396b3b06ace315b5f2a6e780dad387e4c48717a12a22c29"},
+ {file = "regex-2020.6.8-cp38-cp38-manylinux1_i686.whl", hash = "sha256:654cb773b2792e50151f0e22be0f2b6e1c3a04c5328ff1d9d59c0398d37ef610"},
+ {file = "regex-2020.6.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:52e1b4bef02f4040b2fd547357a170fc1146e60ab310cdbdd098db86e929b387"},
+ {file = "regex-2020.6.8-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:cf59bbf282b627130f5ba68b7fa3abdb96372b24b66bdf72a4920e8153fc7910"},
+ {file = "regex-2020.6.8-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5aaa5928b039ae440d775acea11d01e42ff26e1561c0ffcd3d805750973c6baf"},
+ {file = "regex-2020.6.8-cp38-cp38-win32.whl", hash = "sha256:97712e0d0af05febd8ab63d2ef0ab2d0cd9deddf4476f7aa153f76feef4b2754"},
+ {file = "regex-2020.6.8-cp38-cp38-win_amd64.whl", hash = "sha256:6ad8663c17db4c5ef438141f99e291c4d4edfeaacc0ce28b5bba2b0bf273d9b5"},
+ {file = "regex-2020.6.8.tar.gz", hash = "sha256:e9b64e609d37438f7d6e68c2546d2cb8062f3adb27e6336bc129b51be20773ac"},
+]
+royalnet = [
+ {file = "royalnet-5.8.14-py3-none-any.whl", hash = "sha256:56cfe4966b636c5fe4deb763899c8a8b32c0827880dbe0774e181aefdcb7a640"},
+ {file = "royalnet-5.8.14.tar.gz", hash = "sha256:caec812f7b1216bea94c5790607fc0ecf9bf75e5b6c751e68136ab2ad4397486"},
+]
+six = [
+ {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
+ {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
+]
+sqlalchemy = [
+ {file = "SQLAlchemy-1.3.17-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:fe01bac7226499aedf472c62fa3b85b2c619365f3f14dd222ffe4f3aa91e5f98"},
+ {file = "SQLAlchemy-1.3.17-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:b50f45d0e82b4562f59f0e0ca511f65e412f2a97d790eea5f60e34e5f1aabc9a"},
+ {file = "SQLAlchemy-1.3.17-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:ce2646e4c0807f3461be0653502bb48c6e91a5171d6e450367082c79e12868bf"},
+ {file = "SQLAlchemy-1.3.17-cp27-cp27m-win32.whl", hash = "sha256:e4e2664232005bd306f878b0f167a31f944a07c4de0152c444f8c61bbe3cfb38"},
+ {file = "SQLAlchemy-1.3.17-cp27-cp27m-win_amd64.whl", hash = "sha256:925b4fe5e7c03ed76912b75a9a41dfd682d59c0be43bce88d3b27f7f5ba028fb"},
+ {file = "SQLAlchemy-1.3.17-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:869bbb637de58ab0a912b7f20e9192132f9fbc47fc6b5111cd1e0f6cdf5cf9b0"},
+ {file = "SQLAlchemy-1.3.17-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:703c002277f0fbc3c04d0ae4989a174753a7554b2963c584ce2ec0cddcf2bc53"},
+ {file = "SQLAlchemy-1.3.17-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:eb4fcf7105bf071c71068c6eee47499ab8d4b8f5a11fc35147c934f0faa60f23"},
+ {file = "SQLAlchemy-1.3.17-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8d01e949a5d22e5c4800d59b50617c56125fc187fbeb8fa423e99858546de616"},
+ {file = "SQLAlchemy-1.3.17-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a9e75e49a0f1583eee0ce93270232b8e7bb4b1edc89cc70b07600d525aef4f43"},
+ {file = "SQLAlchemy-1.3.17-cp35-cp35m-win32.whl", hash = "sha256:a87d496884f40c94c85a647c385f4fd5887941d2609f71043e2b73f2436d9c65"},
+ {file = "SQLAlchemy-1.3.17-cp35-cp35m-win_amd64.whl", hash = "sha256:6cd157ce74a911325e164441ff2d9b4e244659a25b3146310518d83202f15f7a"},
+ {file = "SQLAlchemy-1.3.17-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:27e2efc8f77661c9af2681755974205e7462f1ae126f498f4fe12a8b24761d15"},
+ {file = "SQLAlchemy-1.3.17-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:31c043d5211aa0e0773821fcc318eb5cbe2ec916dfbc4c6eea0c5188971988eb"},
+ {file = "SQLAlchemy-1.3.17-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:a9030cd30caf848a13a192c5e45367e3c6f363726569a56e75dc1151ee26d859"},
+ {file = "SQLAlchemy-1.3.17-cp36-cp36m-win32.whl", hash = "sha256:f502ef245c492b391e0e23e94cba030ab91722dcc56963c85bfd7f3441ea2bbe"},
+ {file = "SQLAlchemy-1.3.17-cp36-cp36m-win_amd64.whl", hash = "sha256:128bc917ed20d78143a45024455ff0aed7d3b96772eba13d5dbaf9cc57e5c41b"},
+ {file = "SQLAlchemy-1.3.17-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:2a12f8be25b9ea3d1d5b165202181f2b7da4b3395289000284e5bb86154ce87c"},
+ {file = "SQLAlchemy-1.3.17-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e8aa395482728de8bdcca9cc0faf3765ab483e81e01923aaa736b42f0294f570"},
+ {file = "SQLAlchemy-1.3.17-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f35248f7e0d63b234a109dd72fbfb4b5cb6cb6840b221d0df0ecbf54ab087654"},
+ {file = "SQLAlchemy-1.3.17-cp37-cp37m-win32.whl", hash = "sha256:ce1ddaadee913543ff0154021d31b134551f63428065168e756d90bdc4c686f5"},
+ {file = "SQLAlchemy-1.3.17-cp37-cp37m-win_amd64.whl", hash = "sha256:9cb1819008f0225a7c066cac8bb0cf90847b2c4a6eb9ebb7431dbd00c56c06c5"},
+ {file = "SQLAlchemy-1.3.17-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:65eb3b03229f684af0cf0ad3bcc771970c1260a82a791a8d07bffb63d8c95bcc"},
+ {file = "SQLAlchemy-1.3.17-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8a0e0cd21da047ea10267c37caf12add400a92f0620c8bc09e4a6531a765d6d7"},
+ {file = "SQLAlchemy-1.3.17-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b7878e59ec31f12d54b3797689402ee3b5cfcb5598f2ebf26491732758751908"},
+ {file = "SQLAlchemy-1.3.17-cp38-cp38-win32.whl", hash = "sha256:ce6c3d18b2a8ce364013d47b9cad71db815df31d55918403f8db7d890c9d07ae"},
+ {file = "SQLAlchemy-1.3.17-cp38-cp38-win_amd64.whl", hash = "sha256:ed375a79f06cad285166e5be74745df1ed6845c5624aafadec4b7a29c25866ef"},
+ {file = "SQLAlchemy-1.3.17.tar.gz", hash = "sha256:156a27548ba4e1fed944ff9fcdc150633e61d350d673ae7baaf6c25c04ac1f71"},
+]
+starlette = [
+ {file = "starlette-0.12.13.tar.gz", hash = "sha256:9597bc28e3c4659107c1c4a45ec32dc45e947d78fe56230222be673b2c36454a"},
+]
+toml = [
+ {file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
+ {file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
+]
+tzlocal = [
+ {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"},
+ {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"},
+]
+uvicorn = [
+ {file = "uvicorn-0.10.9-py3-none-any.whl", hash = "sha256:dc7119b28e15c4c737315c5a570081b0a5a7d8d5c1e8a70a7be70043d88b23a7"},
+ {file = "uvicorn-0.10.9.tar.gz", hash = "sha256:c010df69d16e27f1a18481316325b4fd23f562c1fac050915fc03a397d0f6b64"},
+]
+uvloop = [
+ {file = "uvloop-0.14.0-cp35-cp35m-macosx_10_11_x86_64.whl", hash = "sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd"},
+ {file = "uvloop-0.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726"},
+ {file = "uvloop-0.14.0-cp36-cp36m-macosx_10_11_x86_64.whl", hash = "sha256:b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7"},
+ {file = "uvloop-0.14.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362"},
+ {file = "uvloop-0.14.0-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891"},
+ {file = "uvloop-0.14.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95"},
+ {file = "uvloop-0.14.0-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5"},
+ {file = "uvloop-0.14.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09"},
+ {file = "uvloop-0.14.0.tar.gz", hash = "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e"},
+]
+websockets = [
+ {file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"},
+ {file = "websockets-8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170"},
+ {file = "websockets-8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8"},
+ {file = "websockets-8.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb"},
+ {file = "websockets-8.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5"},
+ {file = "websockets-8.1-cp36-cp36m-win32.whl", hash = "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a"},
+ {file = "websockets-8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5"},
+ {file = "websockets-8.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989"},
+ {file = "websockets-8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d"},
+ {file = "websockets-8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779"},
+ {file = "websockets-8.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8"},
+ {file = "websockets-8.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422"},
+ {file = "websockets-8.1-cp37-cp37m-win32.whl", hash = "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc"},
+ {file = "websockets-8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308"},
+ {file = "websockets-8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092"},
+ {file = "websockets-8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485"},
+ {file = "websockets-8.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1"},
+ {file = "websockets-8.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55"},
+ {file = "websockets-8.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824"},
+ {file = "websockets-8.1-cp38-cp38-win32.whl", hash = "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36"},
+ {file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"},
+ {file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"},
+]
diff --git a/pyproject.toml b/pyproject.toml
index cc9bf31d..6da163be 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,21 +1,12 @@
# Remember to run `poetry update` after you edit this file!
[tool.poetry]
- # TODO: Insert here your Pack name!
- name = "examplepack"
- # TODO: Insert here your Pack description!
- description = "An example pack for Royalnet."
- # TODO: Increment this every new version of your pack!
+ name = "wikipack"
+ description = "A Wiki for Royalnet"
version = "0.1.0"
- # TODO: Set this to your full name and email!
- authors = ["Name Surname "]
- # TODO: Set this to the license you want to use for your Pack!
- license = ""
- # TODO: Set this to the GitHub page of your pack (or another website)
- homepage = "https://github.com/your-username/"
- # TODO: Set this to a link to the Pack documentation (if there's any)
- documentation = "https://gh.steffo.eu/royalpack/"
- # TODO: Pick some classifiers to include your Pack in: https://pypi.org/classifiers/
+ authors = ["Stefano Pigozzi "]
+ license = "AGPL-3.0+"
+ homepage = "https://github.com/Steffo99/wikipack/"
classifiers = [
"Development Status :: 3 - Alpha",
"Operating System :: OS Independent",
@@ -29,17 +20,10 @@
python = "^3.8"
[tool.poetry.dependencies.royalnet]
- version = "^5.1.6"
- # TODO: select the Royalnet modules required by your pack
+ version = "~5.8.16"
extras = [
- "telegram",
- "discord",
"alchemy_easy",
- "bard",
"constellation",
- "sentry",
- "herald",
- "coloredlogs"
]
# Development dependencies
diff --git a/examplepack/commands/__init__.py b/wikipack/commands/__init__.py
similarity index 72%
rename from examplepack/commands/__init__.py
rename to wikipack/commands/__init__.py
index c9ffb60c..59260c80 100644
--- a/examplepack/commands/__init__.py
+++ b/wikipack/commands/__init__.py
@@ -1,9 +1,7 @@
# Imports go here!
-# TODO: If you create a new command, remember to import it here...
# from .example import ExampleCommand
# Enter the commands of your Pack here!
-# TODO: and add it to the list here!
available_commands = [
# ExampleCommand,
]
diff --git a/examplepack/events/__init__.py b/wikipack/events/__init__.py
similarity index 72%
rename from examplepack/events/__init__.py
rename to wikipack/events/__init__.py
index 8bafb696..8e909cef 100644
--- a/examplepack/events/__init__.py
+++ b/wikipack/events/__init__.py
@@ -1,9 +1,7 @@
# Imports go here!
-# TODO: If you create a new event, remember to import it here...
# from .example import ExampleEvent
# Enter the commands of your Pack here!
-# TODO: and add it to the list here!
available_events = [
# ExampleEvent,
]
diff --git a/wikipack/stars/__init__.py b/wikipack/stars/__init__.py
new file mode 100644
index 00000000..4ed81310
--- /dev/null
+++ b/wikipack/stars/__init__.py
@@ -0,0 +1,10 @@
+# Imports go here!
+from .api_wiki import ApiWikiStar
+
+# Enter the PageStars of your Pack here!
+available_page_stars = [
+ ApiWikiStar
+]
+
+# Don't change this, it should automatically generate __all__
+__all__ = [command.__name__ for command in available_page_stars]
diff --git a/wikipack/stars/api_wiki.py b/wikipack/stars/api_wiki.py
new file mode 100644
index 00000000..ceb5f744
--- /dev/null
+++ b/wikipack/stars/api_wiki.py
@@ -0,0 +1,215 @@
+from typing import *
+import royalnet.constellation.api as rca
+import royalnet.utils as ru
+from royalnet.backpack.tables import *
+from ..tables import *
+import datetime
+
+
+class ApiWikiStar(rca.ApiStar):
+ path = "/api/wiki/v2"
+
+ tags = ["wiki"]
+
+ methods = ["GET", "POST", "PUT", "DELETE"]
+
+ parameters = {
+ "get": {
+ "page_id": "The id of the wiki page to get the details of."
+ },
+ "post": {
+ "category": "The category of the page.",
+ "title": "The title of the page.",
+ "contents": "The contents of the page.",
+ "format": "(Optional) The format of the page. Default is 'gfm' for GitHub Flavored Markdown.",
+ "role_to_view": "(Optional) The role required to view this page. Be careful to not lock yourself out!",
+ "role_to_edit": "(Optional) The role required to edit this page. Be careful to not lock yourself out!",
+ },
+ "put": {
+ "page_id": "The id of the wiki page to create a new revision of.",
+ "category": "The category of the page.",
+ "title": "The title of the page.",
+ "contents": "The contents of the page.",
+ "format": "The format of the page. Default is 'gfm' for GitHub Flavored Markdown.",
+ "role_to_view": "The role required to view this page. Be careful to not lock yourself out!",
+ "role_to_edit": "The role required to edit this page. Be careful to not lock yourself out!",
+ },
+ "delete": {
+ "page_id": "The id of the wiki page to delete.",
+ },
+ }
+
+ @property
+ def default_view_role(self) -> str:
+ return self.config["wikipack"]["roles"]["view"]
+
+ async def can_view(self, user: User, page: WikiPage) -> bool:
+ lr = page.latest_revision
+
+ if lr.role_to_view == "":
+ return True
+
+ if lr.role_to_view:
+ if lr.role_to_view in user.roles or self.admin_role in user.roles:
+ return True
+ return False
+ return True
+
+ @property
+ def default_edit_role(self) -> str:
+ return self.config["wikipack"]["roles"]["edit"]
+
+ async def can_edit(self, user: User, page: WikiPage) -> bool:
+ lr = page.latest_revision
+
+ if lr.role_to_edit == "":
+ return True
+
+ if lr.role_to_edit:
+ if lr.role_to_edit in user.roles or self.admin_role in user.roles:
+ return True
+ return False
+ return True
+
+ @property
+ def create_role(self) -> str:
+ return self.config["wikipack"]["roles"]["create"]
+
+ async def can_create(self, user: User) -> bool:
+ if self.create_role in user.roles or self.admin_role in user.roles:
+ return True
+ return False
+
+ @property
+ def delete_role(self) -> str:
+ return self.config["wikipack"]["roles"]["delete"]
+
+ async def can_delete(self, user: User) -> bool:
+ if self.delete_role == "":
+ return True
+
+ if self.delete_role in user.roles or self.admin_role in user.roles:
+ return True
+ return False
+
+ @property
+ def admin_role(self) -> str:
+ return self.config["wikipack"]["roles"]["admin"]
+
+ async def find_page(self, data: rca.ApiData) -> Tuple[WikiPage, WikiRevision]:
+ WikiPageT = self.alchemy.get(WikiPage)
+
+ page_id = data.int("page_id")
+
+ page: WikiPage = await ru.asyncify(
+ data.session.query(WikiPageT).filter_by(page_id=page_id).one_or_none
+ )
+
+ if page is None:
+ raise rca.NotFoundError(f"No page found with the id `{page_id}`.")
+
+ return page, page.latest_revision
+
+ async def get(self, data: rca.ApiData) -> ru.JSON:
+ """Get the details of a specific Wiki page."""
+ page, lr = await self.find_page(data)
+ user = await data.user()
+
+ if not self.can_view(user, page):
+ raise rca.ForbiddenError(f"Viewing this page requires the `{lr.role_to_view}` role.")
+
+ return lr.json()
+
+ async def post(self, data: rca.ApiData) -> ru.JSON:
+ """Create a new Wiki page."""
+ WikiRevisionT = self.alchemy.get(WikiRevision)
+
+ user = await data.user()
+
+ if not self.can_create(user):
+ raise rca.ForbiddenError(f"Creating a new page requires the `{self.create_role}` role.")
+
+ category = data.str("category", optional=False)
+ title = data.str("title", optional=False)
+ contents = data.str("contents", optional=False)
+ format_ = data.str("format", optional=True)
+ role_to_view = data.str("role_to_view", optional=True)
+ role_to_edit = data.str("role_to_edit", optional=True)
+
+ page = WikiPage()
+ data.session.add(page)
+ data.session.flush()
+
+ nr: WikiRevision = WikiRevisionT(
+ page_id=page.page_id,
+ category=category,
+ title=title,
+ contents=contents,
+ format=format_ or "gfm",
+ author=user,
+ timestamp=datetime.datetime.now(),
+ role_to_view=role_to_view or self.default_view_role,
+ role_to_edit=role_to_edit or self.default_edit_role,
+ )
+
+ data.session.add(nr)
+ await data.session.commit()
+
+ return nr.json()
+
+ async def put(self, data: rca.ApiData) -> ru.JSON:
+ """Edit a specific Wiki page, creating a new revision."""
+ WikiRevisionT = self.alchemy.get(WikiRevision)
+
+ page, lr = await self.find_page(data)
+ user = await data.user()
+
+ if not self.can_edit(user, page):
+ raise rca.ForbiddenError(f"Editing this page requires the `{lr.role_to_edit}` role.")
+
+ category = data.str("category", optional=True)
+ title = data.str("title", optional=True)
+ contents = data.str("contents", optional=True)
+ format_ = data.str("format", optional=True)
+ role_to_view = data.str("role_to_view", optional=True)
+ role_to_edit = data.str("role_to_edit", optional=True)
+
+ nr: WikiRevision = WikiRevisionT(
+ page_id=page.page_id,
+ category=category or lr.category,
+ title=title or lr.title,
+ contents=contents or lr.contents,
+ format=format_ or lr.format,
+ author=user,
+ timestamp=datetime.datetime.now(),
+ role_to_view=role_to_view or lr.role_to_view,
+ role_to_edit=role_to_edit or lr.role_to_edit,
+ )
+
+ data.session.add(nr)
+ await data.session.commit()
+
+ return nr.json()
+
+ async def delete(self, data: rca.ApiData) -> ru.JSON:
+ """Delete a specific Wiki page and all its revisions."""
+ WikiDeletionT = self.alchemy.get(WikiDeletion)
+
+ page, lr = await self.find_page(data)
+ user = await data.user()
+
+ if not self.can_delete(user):
+ raise rca.ForbiddenError(f"Deleting pages requires the `{self.delete_role}` role.")
+
+ deletion = WikiDeletionT(
+ page_id=page.page_id,
+ deleted_by=user,
+ timestamp=datetime.datetime.now(),
+ )
+
+ data.session.delete(page)
+ data.session.add(deletion)
+
+ await data.session.commit()
+
+ return deletion.json()
\ No newline at end of file
diff --git a/examplepack/tables/__init__.py b/wikipack/tables/__init__.py
similarity index 55%
rename from examplepack/tables/__init__.py
rename to wikipack/tables/__init__.py
index e417ff16..c1a82dd0 100644
--- a/examplepack/tables/__init__.py
+++ b/wikipack/tables/__init__.py
@@ -1,9 +1,13 @@
# Imports go here!
-# from .example import Example
+from .wikipage import WikiPage
+from .wikirevision import WikiRevision
+from .wikideletion import WikiDeletion
# Enter the tables of your Pack here!
available_tables = [
- # Example
+ WikiPage,
+ WikiRevision,
+ WikiDeletion,
]
# Don't change this, it should automatically generate __all__
diff --git a/wikipack/tables/wikideletion.py b/wikipack/tables/wikideletion.py
new file mode 100644
index 00000000..241e5568
--- /dev/null
+++ b/wikipack/tables/wikideletion.py
@@ -0,0 +1,32 @@
+from sqlalchemy import *
+from sqlalchemy.orm import *
+from sqlalchemy.ext.declarative import declared_attr
+import datetime
+import royalnet.utils as ru
+
+
+class WikiDeletion:
+ __tablename__ = "wikideletions"
+
+ @declared_attr
+ def page_id(self):
+ return Column(Integer, primary_key=True)
+
+ @declared_attr
+ def deleted_by_id(self) -> int:
+ return Column(Integer, ForeignKey("users.uid"), nullable=False)
+
+ @declared_attr
+ def deleted_by(self):
+ return relationship("User", foreign_keys=self.deleted_by_id, backref="wiki_deletions")
+
+ @declared_attr
+ def timestamp(self) -> datetime.datetime:
+ return Column(DateTime, nullable=False)
+
+ def json(self) -> ru.JSON:
+ return {
+ "page_id": self.page_id,
+ "deleted_by": self.deleted_by.json(),
+ "timestamp": self.timestamp.isoformat(),
+ }
diff --git a/wikipack/tables/wikipage.py b/wikipack/tables/wikipage.py
new file mode 100644
index 00000000..02469796
--- /dev/null
+++ b/wikipack/tables/wikipage.py
@@ -0,0 +1,23 @@
+from typing import *
+from sqlalchemy import *
+from sqlalchemy.orm import *
+from sqlalchemy.ext.declarative import declared_attr
+
+if TYPE_CHECKING:
+ from .wikirevision import WikiRevision
+
+
+class WikiPage:
+ __tablename__ = "wikipages"
+
+ @declared_attr
+ def page_id(self) -> int:
+ return Column(Integer, primary_key=True)
+
+ @declared_attr
+ def latest_revision_id(self) -> int:
+ return Column(Integer, ForeignKey("wikirevisions.revision_id"), nullable=False)
+
+ @declared_attr
+ def latest_revision(self) -> "WikiRevision":
+ return relationship("WikiRevision", foreign_keys=self.latest_revision_id, uselist=False)
diff --git a/wikipack/tables/wikirevision.py b/wikipack/tables/wikirevision.py
new file mode 100644
index 00000000..34322e2b
--- /dev/null
+++ b/wikipack/tables/wikirevision.py
@@ -0,0 +1,92 @@
+from typing import *
+from sqlalchemy import *
+from sqlalchemy.orm import *
+from sqlalchemy.ext.declarative import *
+import royalnet.utils as ru
+import datetime
+
+
+if TYPE_CHECKING:
+ from royalnet.backpack.tables import User
+ from .wikipage import WikiPage
+
+
+class WikiRevision:
+ __tablename__ = "wikirevisions"
+
+ @declared_attr
+ def page_id(self) -> int:
+ return Column(Integer, ForeignKey("wikipages.page_id"), nullable=False)
+
+ @declared_attr
+ def page(self) -> "WikiPage":
+ return relationship("WikiPage",
+ foreign_keys=self.page_id,
+ backref=backref("revisions", cascade="save-update, merge, delete"))
+
+ @declared_attr
+ def revision_id(self) -> int:
+ return Column(Integer, primary_key=True)
+
+ @declared_attr
+ def category(self) -> str:
+ return Column(String, nullable=False)
+
+ @declared_attr
+ def title(self) -> str:
+ return Column(String, nullable=False)
+
+ @declared_attr
+ def contents(self) -> str:
+ return Column(Text, nullable=False)
+
+ @declared_attr
+ def format(self) -> str:
+ # GitHub Flavored Markdown
+ # https://github.github.com/gfm/
+ return Column(String, nullable=False, default="gfm")
+
+ @declared_attr
+ def author_id(self) -> int:
+ return Column(Integer, ForeignKey("users.uid"), nullable=False)
+
+ @declared_attr
+ def author(self) -> "User":
+ return relationship("User", foreign_keys=self.author_id, backref="wiki_revisions")
+
+ @declared_attr
+ def timestamp(self) -> datetime.datetime:
+ return Column(DateTime, nullable=False)
+
+ @declared_attr
+ def role_to_view(self) -> str:
+ return Column(String)
+
+ @declared_attr
+ def role_to_edit(self) -> str:
+ return Column(String)
+
+ def set_as_latest(self) -> None:
+ self.page.latest_revision = self
+
+ def json_list(self):
+ return {
+ "page_id": self.page_id,
+ "category": self.category,
+ "title": self.title,
+ "role_to_view": self.role_to_view,
+ }
+
+ def json(self) -> ru.JSON:
+ return {
+ "page_id": self.page_id,
+ "revision_id": self.revision_id,
+ "category": self.category,
+ "title": self.title,
+ "contents": self.contents,
+ "format": self.format,
+ "author": self.author.json(),
+ "timestamp": self.timestamp.isoformat(),
+ "role_to_view": self.role_to_view,
+ "role_to_edit": self.role_to_edit,
+ }
diff --git a/wikipack/version.py b/wikipack/version.py
new file mode 100644
index 00000000..9ee900d3
--- /dev/null
+++ b/wikipack/version.py
@@ -0,0 +1 @@
+semantic = "0.1.0"