diff --git a/maubot/management/api/client.py b/maubot/management/api/client.py
index 84d5b37..9e6cac7 100644
--- a/maubot/management/api/client.py
+++ b/maubot/management/api/client.py
@@ -13,7 +13,9 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+from typing import Optional
from json import JSONDecodeError
+from http import HTTPStatus
from aiohttp import web
@@ -43,7 +45,7 @@ async def get_client(request: web.Request) -> web.Response:
return web.json_response(client.to_dict())
-async def create_client(user_id: UserID, data: dict) -> web.Response:
+async def _create_client(user_id: Optional[UserID], data: dict) -> web.Response:
homeserver = data.get("homeserver", None)
access_token = data.get("access_token", None)
new_client = MatrixClient(base_url=homeserver, token=access_token, loop=Client.loop,
@@ -54,7 +56,7 @@ async def create_client(user_id: UserID, data: dict) -> web.Response:
return ErrBadClientAccessToken
except MatrixRequestError:
return ErrBadClientAccessDetails
- if user_id == "new":
+ if user_id is None:
existing_client = Client.get(mxid, None)
if existing_client is not None:
return ErrUserExists
@@ -73,7 +75,7 @@ async def create_client(user_id: UserID, data: dict) -> web.Response:
return web.json_response(client.to_dict())
-async def update_client(client: Client, data: dict) -> web.Response:
+async def _update_client(client: Client, data: dict) -> web.Response:
try:
await client.update_access_details(data.get("access_token", None),
data.get("homeserver", None))
@@ -89,22 +91,30 @@ async def update_client(client: Client, data: dict) -> web.Response:
client.enabled = data.get("enabled", client.enabled)
client.autojoin = data.get("autojoin", client.autojoin)
client.sync = data.get("sync", client.sync)
- return web.json_response(client.to_dict())
+ return web.json_response(client.to_dict(), status=HTTPStatus.CREATED)
+
+
+@routes.post("/client/new")
+async def create_client(request: web.Request) -> web.Response:
+ try:
+ data = await request.json()
+ except JSONDecodeError:
+ return ErrBodyNotJSON
+ return await _create_client(None, data)
@routes.put("/client/{id}")
async def update_client(request: web.Request) -> web.Response:
user_id = request.match_info.get("id", None)
- # /client/new always creates a new client
- client = Client.get(user_id, None) if user_id != "new" else None
+ client = Client.get(user_id, None)
try:
data = await request.json()
except JSONDecodeError:
return ErrBodyNotJSON
if not client:
- return await create_client(user_id, data)
+ return await _create_client(user_id, data)
else:
- return await update_client(client, data)
+ return await _update_client(client, data)
@routes.delete("/client/{id}")
diff --git a/maubot/management/api/instance.py b/maubot/management/api/instance.py
index 166108a..2947247 100644
--- a/maubot/management/api/instance.py
+++ b/maubot/management/api/instance.py
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
from json import JSONDecodeError
+from http import HTTPStatus
from aiohttp import web
@@ -40,7 +41,7 @@ async def get_instance(request: web.Request) -> web.Response:
return web.json_response(instance.to_dict())
-async def create_instance(instance_id: str, data: dict) -> web.Response:
+async def _create_instance(instance_id: str, data: dict) -> web.Response:
plugin_type = data.get("type", None)
primary_user = data.get("primary_user", None)
if not plugin_type:
@@ -60,10 +61,10 @@ async def create_instance(instance_id: str, data: dict) -> web.Response:
PluginInstance.db.add(db_instance)
PluginInstance.db.commit()
await instance.start()
- return web.json_response(instance.to_dict())
+ return web.json_response(instance.to_dict(), status=HTTPStatus.CREATED)
-async def update_instance(instance: PluginInstance, data: dict) -> web.Response:
+async def _update_instance(instance: PluginInstance, data: dict) -> web.Response:
if not await instance.update_primary_user(data.get("primary_user")):
return ErrPrimaryUserNotFound
instance.update_id(data.get("id", None))
@@ -83,9 +84,9 @@ async def update_instance(request: web.Request) -> web.Response:
except JSONDecodeError:
return ErrBodyNotJSON
if not instance:
- return await create_instance(instance_id, data)
+ return await _create_instance(instance_id, data)
else:
- return await update_instance(instance, data)
+ return await _update_instance(instance, data)
@routes.delete("/instance/{id}")
diff --git a/maubot/management/api/plugin.py b/maubot/management/api/plugin.py
index 03fb609..87174ab 100644
--- a/maubot/management/api/plugin.py
+++ b/maubot/management/api/plugin.py
@@ -13,13 +13,15 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from aiohttp import web
+from http import HTTPStatus
from io import BytesIO
from time import time
import traceback
import os.path
import re
+from aiohttp import web
+
from ...loader import PluginLoader, ZippedPluginLoader, MaubotZipImportError
from .responses import (ErrPluginNotFound, ErrPluginInUse, plugin_import_error,
plugin_reload_error, RespDeleted, RespOK, ErrUnsupportedPluginLoader)
@@ -77,7 +79,7 @@ async def upload_new_plugin(content: bytes, pid: str, version: str) -> web.Respo
except MaubotZipImportError as e:
ZippedPluginLoader.trash(path)
return plugin_import_error(str(e), traceback.format_exc())
- return web.json_response(plugin.to_dict())
+ return web.json_response(plugin.to_dict(), status=HTTPStatus.CREATED)
async def upload_replacement_plugin(plugin: ZippedPluginLoader, content: bytes, new_version: str
diff --git a/maubot/management/api/spec.yaml b/maubot/management/api/spec.yaml
index 7b7ebd9..e89f18b 100644
--- a/maubot/management/api/spec.yaml
+++ b/maubot/management/api/spec.yaml
@@ -81,12 +81,10 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Plugin'
+ 400:
+ $ref: '#/components/responses/BadRequest'
401:
$ref: '#/components/responses/Unauthorized'
- 412:
- description: >-
- Instances of the uploaded plugin type are currently active,
- therefore the plugin can't be updated
requestBody:
content:
application/zip:
@@ -131,6 +129,10 @@ paths:
$ref: '#/components/responses/PluginNotFound'
412:
description: One or more plugin instances of this type exist
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
/plugin/{id}/reload:
parameters:
- name: id
@@ -215,10 +217,16 @@ paths:
description: Plugin instance edited
201:
description: Plugin instance created
+ 400:
+ $ref: '#/components/responses/BadRequest'
401:
$ref: '#/components/responses/Unauthorized'
404:
- $ref: '#/components/responses/InstanceNotFound'
+ description: The referenced client or plugin type could not be found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'/clients':
get:
@@ -236,6 +244,35 @@ paths:
$ref: '#/components/schemas/MatrixClient'
401:
$ref: '#/components/responses/Unauthorized'
+ /client/new:
+ post:
+ operationId: create_client
+ summary: Create a Matrix client
+ tags: [Clients]
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/MatrixClient'
+ responses:
+ 201:
+ description: Client created
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/MatrixClient'
+ 400:
+ $ref: '#/components/responses/BadRequest'
+ 401:
+ $ref: '#/components/responses/Unauthorized'
+ 404:
+ $ref: '#/components/responses/ClientNotFound'
+ 409:
+ description: There is already a client with the user ID of that token.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
'/client/{id}':
parameters:
- name: id
@@ -281,10 +318,10 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/MatrixClient'
+ 400:
+ $ref: '#/components/responses/BadRequest'
401:
$ref: '#/components/responses/Unauthorized'
- 404:
- $ref: '#/components/responses/ClientNotFound'
delete:
operationId: delete_client
summary: Delete a Matrix client
@@ -298,23 +335,58 @@ paths:
$ref: '#/components/responses/ClientNotFound'
412:
description: One or more plugin instances with this as their primary client exist
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
components:
responses:
Unauthorized:
description: Invalid or missing access token
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
PluginNotFound:
description: Plugin not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
ClientNotFound:
description: Client not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
InstanceNotFound:
description: Plugin instance not found
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ BadRequest:
+ description: Bad request (e.g. bad request body)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
securitySchemes:
bearer:
type: http
scheme: bearer
description: Required authentication for all endpoints
schemas:
+ Error:
+ type: object
+ properties:
+ error:
+ type: string
+ description: A human-readable error message
+ errcode:
+ type: string
+ description: A simple error code
Plugin:
type: object
properties: