diff --git a/pyproject.toml b/pyproject.toml index 33b91041..c81856fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "royalnet" - version = "5.5" + version = "5.6" description = "A multipurpose bot and web framework" authors = ["Stefano Pigozzi "] license = "AGPL-3.0+" diff --git a/royalnet/backpack/stars/__init__.py b/royalnet/backpack/stars/__init__.py index 3c869821..81d5a6c6 100644 --- a/royalnet/backpack/stars/__init__.py +++ b/royalnet/backpack/stars/__init__.py @@ -4,6 +4,7 @@ from .api_login_royalnet import ApiLoginRoyalnetStar from .api_token_info import ApiTokenInfoStar from .api_token_passwd import ApiTokenPasswdStar from .api_token_create import ApiTokenCreateStar +from .docs import DocsStar # Enter the PageStars of your Pack here! available_page_stars = [ @@ -12,6 +13,7 @@ available_page_stars = [ ApiTokenInfoStar, ApiTokenPasswdStar, ApiTokenCreateStar, + DocsStar, ] # Don't change this, it should automatically generate __all__ diff --git a/royalnet/backpack/stars/api_login_royalnet.py b/royalnet/backpack/stars/api_login_royalnet.py index 32c04d28..23744346 100644 --- a/royalnet/backpack/stars/api_login_royalnet.py +++ b/royalnet/backpack/stars/api_login_royalnet.py @@ -11,6 +11,15 @@ class ApiLoginRoyalnetStar(ApiStar): methods = ["POST"] + summary = "Login as a Royalnet user, creating a 7-day login token." + + parameters = { + "username": "The name of the user you are logging in as.", + "password": "The password of the user you are logging in as." + } + + tags = ["royalnet"] + async def api(self, data: ApiData) -> ru.JSON: TokenT = self.alchemy.get(Token) UserT = self.alchemy.get(User) diff --git a/royalnet/backpack/stars/api_royalnet_version.py b/royalnet/backpack/stars/api_royalnet_version.py index 74092984..c746cffe 100644 --- a/royalnet/backpack/stars/api_royalnet_version.py +++ b/royalnet/backpack/stars/api_royalnet_version.py @@ -6,6 +6,10 @@ import royalnet.utils as ru class ApiRoyalnetVersionStar(ApiStar): path = "/api/royalnet/version/v1" + summary = "Get the current Royalnet version." + + tags = ["royalnet"] + async def api(self, data: ApiData) -> ru.JSON: return { "semantic": rv.semantic diff --git a/royalnet/backpack/stars/api_token_create.py b/royalnet/backpack/stars/api_token_create.py index 4c1f6211..cfae4133 100644 --- a/royalnet/backpack/stars/api_token_create.py +++ b/royalnet/backpack/stars/api_token_create.py @@ -10,6 +10,14 @@ class ApiTokenCreateStar(ApiStar): methods = ["POST"] + summary = "Create a new login token of any duration." + + parameters = { + "duration": "The duration in seconds of the new token." + } + + tags = ["royalnet"] + async def api(self, data: ApiData) -> ru.JSON: user = await data.user() try: diff --git a/royalnet/backpack/stars/api_token_info.py b/royalnet/backpack/stars/api_token_info.py index 7174df36..606c6858 100644 --- a/royalnet/backpack/stars/api_token_info.py +++ b/royalnet/backpack/stars/api_token_info.py @@ -5,6 +5,10 @@ from royalnet.constellation.api import * class ApiTokenInfoStar(ApiStar): path = "/api/token/info/v1" + summary = "Get info the current login token." + + tags = ["royalnet"] + async def api(self, data: ApiData) -> ru.JSON: token = await data.token() return token.json() diff --git a/royalnet/backpack/stars/api_token_passwd.py b/royalnet/backpack/stars/api_token_passwd.py index 323dd930..d26ffe8f 100644 --- a/royalnet/backpack/stars/api_token_passwd.py +++ b/royalnet/backpack/stars/api_token_passwd.py @@ -11,6 +11,14 @@ class ApiTokenPasswdStar(ApiStar): methods = ["POST"] + summary = "Change Royalnet password for an user." + + parameters = { + "new_password": "The password you want to set." + } + + tags = ["royalnet"] + async def api(self, data: ApiData) -> ru.JSON: TokenT = self.alchemy.get(Token) token = await data.token() diff --git a/royalnet/backpack/stars/docs.py b/royalnet/backpack/stars/docs.py new file mode 100644 index 00000000..4fb23645 --- /dev/null +++ b/royalnet/backpack/stars/docs.py @@ -0,0 +1,68 @@ +import json +from typing import * +from royalnet.constellation import PageStar +from royalnet.constellation.api import ApiStar +from starlette.requests import Request +from starlette.responses import Response, HTMLResponse +from royalnet.version import semantic + + +class DocsStar(PageStar): + path = "/docs" + + async def page(self, request: Request) -> Response: + paths = {} + + for star in self.constellation.stars: + if not isinstance(star, ApiStar): + continue + paths[star.path] = star.swagger() + + spec = json.dumps({ + "openapi": "3.0.0", + "info": { + "description": "Autogenerated Royalnet API documentation", + "title": "Royalnet", + "version": f"{semantic}", + }, + "paths": paths, + "components": { + "securitySchemes": { + "LoginTokenAuth": { + "type": "apiKey", + "in": "query", + "name": "token", + } + } + }, + "security": [ + {"LoginTokenAuth": []} + ] + }) + + return HTMLResponse( + f""" + + + Royalnet Docs + + + + + +
+ + + + """ + ) diff --git a/royalnet/constellation/api/apistar.py b/royalnet/constellation/api/apistar.py index afa58fc2..62ccdd53 100644 --- a/royalnet/constellation/api/apistar.py +++ b/royalnet/constellation/api/apistar.py @@ -11,6 +11,14 @@ import royalnet.utils as ru class ApiStar(PageStar, ABC): + summary: str = "" + + description: str = "" + + parameters: Dict[str, str] = {} + + tags: List[str] = [] + async def page(self, request: Request) -> JSONResponse: if request.query_params: data = request.query_params @@ -40,3 +48,30 @@ class ApiStar(PageStar, ABC): async def api(self, data: ApiData) -> ru.JSON: raise NotImplementedError() + + @classmethod + def swagger(cls) -> ru.JSON: + """Generate one or more swagger paths for this ApiStar.""" + result = {} + for method in cls.methods: + result[method.lower()] = { + "operationId": cls.__name__, + "summary": cls.summary, + "description": cls.description, + "responses": { + "200": {"description": "Success"}, + "400": {"description": "Bad request"}, + "403": {"description": "Forbidden"}, + "404": {"description": "Not found"}, + "500": {"description": "Serverside unhandled exception"}, + "501": {"description": "Not yet implemented"} + }, + "tags": cls.tags, + "parameters": [{ + "name": parameter, + "in": "query", + "description": cls.parameters[parameter], + "type": "string" + } for parameter in cls.parameters] + } + return result diff --git a/royalnet/constellation/constellation.py b/royalnet/constellation/constellation.py index 2cdc94e9..1065c6df 100644 --- a/royalnet/constellation/constellation.py +++ b/royalnet/constellation/constellation.py @@ -99,6 +99,9 @@ class Constellation: self.starlette = starlette.applications.Starlette(debug=__debug__) """The :class:`~starlette.Starlette` app.""" + self.stars: List[PageStar] = [] + """A list of all the :class:`PageStar` registered to this :class:`Constellation`.""" + # Register Events for pack_name in packs: pack = packs[pack_name] @@ -268,6 +271,7 @@ class Constellation: f"{SelectedPageStar.__qualname__} - {e.__class__.__qualname__} in the initialization.") ru.sentry_exc(e) continue + self.stars.append(page_star_instance) self.starlette.add_route(*self._page_star_wrapper(page_star_instance)) def run_blocking(self): diff --git a/royalnet/generate.py b/royalnet/generate.py index 80e18944..c0366bf8 100644 --- a/royalnet/generate.py +++ b/royalnet/generate.py @@ -1,6 +1,7 @@ import toml import importlib import click +from .version import semantic p = click.echo diff --git a/royalnet/version.py b/royalnet/version.py index 63bf6999..4705c69e 100644 --- a/royalnet/version.py +++ b/royalnet/version.py @@ -1 +1 @@ -semantic = "5.5" +semantic = "5.6"