Update spec and fix minor problems in implementation
This commit is contained in:
parent
383c9ce5ec
commit
8b97134efd
4 changed files with 107 additions and 22 deletions
|
@ -13,7 +13,9 @@
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
from typing import Optional
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
|
@ -43,7 +45,7 @@ async def get_client(request: web.Request) -> web.Response:
|
||||||
return web.json_response(client.to_dict())
|
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)
|
homeserver = data.get("homeserver", None)
|
||||||
access_token = data.get("access_token", None)
|
access_token = data.get("access_token", None)
|
||||||
new_client = MatrixClient(base_url=homeserver, token=access_token, loop=Client.loop,
|
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
|
return ErrBadClientAccessToken
|
||||||
except MatrixRequestError:
|
except MatrixRequestError:
|
||||||
return ErrBadClientAccessDetails
|
return ErrBadClientAccessDetails
|
||||||
if user_id == "new":
|
if user_id is None:
|
||||||
existing_client = Client.get(mxid, None)
|
existing_client = Client.get(mxid, None)
|
||||||
if existing_client is not None:
|
if existing_client is not None:
|
||||||
return ErrUserExists
|
return ErrUserExists
|
||||||
|
@ -73,7 +75,7 @@ async def create_client(user_id: UserID, data: dict) -> web.Response:
|
||||||
return web.json_response(client.to_dict())
|
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:
|
try:
|
||||||
await client.update_access_details(data.get("access_token", None),
|
await client.update_access_details(data.get("access_token", None),
|
||||||
data.get("homeserver", 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.enabled = data.get("enabled", client.enabled)
|
||||||
client.autojoin = data.get("autojoin", client.autojoin)
|
client.autojoin = data.get("autojoin", client.autojoin)
|
||||||
client.sync = data.get("sync", client.sync)
|
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}")
|
@routes.put("/client/{id}")
|
||||||
async def update_client(request: web.Request) -> web.Response:
|
async def update_client(request: web.Request) -> web.Response:
|
||||||
user_id = request.match_info.get("id", None)
|
user_id = request.match_info.get("id", None)
|
||||||
# /client/new always creates a new client
|
client = Client.get(user_id, None)
|
||||||
client = Client.get(user_id, None) if user_id != "new" else None
|
|
||||||
try:
|
try:
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
except JSONDecodeError:
|
except JSONDecodeError:
|
||||||
return ErrBodyNotJSON
|
return ErrBodyNotJSON
|
||||||
if not client:
|
if not client:
|
||||||
return await create_client(user_id, data)
|
return await _create_client(user_id, data)
|
||||||
else:
|
else:
|
||||||
return await update_client(client, data)
|
return await _update_client(client, data)
|
||||||
|
|
||||||
|
|
||||||
@routes.delete("/client/{id}")
|
@routes.delete("/client/{id}")
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ async def get_instance(request: web.Request) -> web.Response:
|
||||||
return web.json_response(instance.to_dict())
|
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)
|
plugin_type = data.get("type", None)
|
||||||
primary_user = data.get("primary_user", None)
|
primary_user = data.get("primary_user", None)
|
||||||
if not plugin_type:
|
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.add(db_instance)
|
||||||
PluginInstance.db.commit()
|
PluginInstance.db.commit()
|
||||||
await instance.start()
|
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")):
|
if not await instance.update_primary_user(data.get("primary_user")):
|
||||||
return ErrPrimaryUserNotFound
|
return ErrPrimaryUserNotFound
|
||||||
instance.update_id(data.get("id", None))
|
instance.update_id(data.get("id", None))
|
||||||
|
@ -83,9 +84,9 @@ async def update_instance(request: web.Request) -> web.Response:
|
||||||
except JSONDecodeError:
|
except JSONDecodeError:
|
||||||
return ErrBodyNotJSON
|
return ErrBodyNotJSON
|
||||||
if not instance:
|
if not instance:
|
||||||
return await create_instance(instance_id, data)
|
return await _create_instance(instance_id, data)
|
||||||
else:
|
else:
|
||||||
return await update_instance(instance, data)
|
return await _update_instance(instance, data)
|
||||||
|
|
||||||
|
|
||||||
@routes.delete("/instance/{id}")
|
@routes.delete("/instance/{id}")
|
||||||
|
|
|
@ -13,13 +13,15 @@
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from aiohttp import web
|
from http import HTTPStatus
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from time import time
|
from time import time
|
||||||
import traceback
|
import traceback
|
||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
from ...loader import PluginLoader, ZippedPluginLoader, MaubotZipImportError
|
from ...loader import PluginLoader, ZippedPluginLoader, MaubotZipImportError
|
||||||
from .responses import (ErrPluginNotFound, ErrPluginInUse, plugin_import_error,
|
from .responses import (ErrPluginNotFound, ErrPluginInUse, plugin_import_error,
|
||||||
plugin_reload_error, RespDeleted, RespOK, ErrUnsupportedPluginLoader)
|
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:
|
except MaubotZipImportError as e:
|
||||||
ZippedPluginLoader.trash(path)
|
ZippedPluginLoader.trash(path)
|
||||||
return plugin_import_error(str(e), traceback.format_exc())
|
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
|
async def upload_replacement_plugin(plugin: ZippedPluginLoader, content: bytes, new_version: str
|
||||||
|
|
|
@ -81,12 +81,10 @@ paths:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Plugin'
|
$ref: '#/components/schemas/Plugin'
|
||||||
|
400:
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
401:
|
401:
|
||||||
$ref: '#/components/responses/Unauthorized'
|
$ref: '#/components/responses/Unauthorized'
|
||||||
412:
|
|
||||||
description: >-
|
|
||||||
Instances of the uploaded plugin type are currently active,
|
|
||||||
therefore the plugin can't be updated
|
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
application/zip:
|
application/zip:
|
||||||
|
@ -131,6 +129,10 @@ paths:
|
||||||
$ref: '#/components/responses/PluginNotFound'
|
$ref: '#/components/responses/PluginNotFound'
|
||||||
412:
|
412:
|
||||||
description: One or more plugin instances of this type exist
|
description: One or more plugin instances of this type exist
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
/plugin/{id}/reload:
|
/plugin/{id}/reload:
|
||||||
parameters:
|
parameters:
|
||||||
- name: id
|
- name: id
|
||||||
|
@ -215,10 +217,16 @@ paths:
|
||||||
description: Plugin instance edited
|
description: Plugin instance edited
|
||||||
201:
|
201:
|
||||||
description: Plugin instance created
|
description: Plugin instance created
|
||||||
|
400:
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
401:
|
401:
|
||||||
$ref: '#/components/responses/Unauthorized'
|
$ref: '#/components/responses/Unauthorized'
|
||||||
404:
|
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':
|
'/clients':
|
||||||
get:
|
get:
|
||||||
|
@ -236,6 +244,35 @@ paths:
|
||||||
$ref: '#/components/schemas/MatrixClient'
|
$ref: '#/components/schemas/MatrixClient'
|
||||||
401:
|
401:
|
||||||
$ref: '#/components/responses/Unauthorized'
|
$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}':
|
'/client/{id}':
|
||||||
parameters:
|
parameters:
|
||||||
- name: id
|
- name: id
|
||||||
|
@ -281,10 +318,10 @@ paths:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/MatrixClient'
|
$ref: '#/components/schemas/MatrixClient'
|
||||||
|
400:
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
401:
|
401:
|
||||||
$ref: '#/components/responses/Unauthorized'
|
$ref: '#/components/responses/Unauthorized'
|
||||||
404:
|
|
||||||
$ref: '#/components/responses/ClientNotFound'
|
|
||||||
delete:
|
delete:
|
||||||
operationId: delete_client
|
operationId: delete_client
|
||||||
summary: Delete a Matrix client
|
summary: Delete a Matrix client
|
||||||
|
@ -298,23 +335,58 @@ paths:
|
||||||
$ref: '#/components/responses/ClientNotFound'
|
$ref: '#/components/responses/ClientNotFound'
|
||||||
412:
|
412:
|
||||||
description: One or more plugin instances with this as their primary client exist
|
description: One or more plugin instances with this as their primary client exist
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
|
||||||
components:
|
components:
|
||||||
responses:
|
responses:
|
||||||
Unauthorized:
|
Unauthorized:
|
||||||
description: Invalid or missing access token
|
description: Invalid or missing access token
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
PluginNotFound:
|
PluginNotFound:
|
||||||
description: Plugin not found
|
description: Plugin not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
ClientNotFound:
|
ClientNotFound:
|
||||||
description: Client not found
|
description: Client not found
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
InstanceNotFound:
|
InstanceNotFound:
|
||||||
description: Plugin instance not found
|
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:
|
securitySchemes:
|
||||||
bearer:
|
bearer:
|
||||||
type: http
|
type: http
|
||||||
scheme: bearer
|
scheme: bearer
|
||||||
description: Required authentication for all endpoints
|
description: Required authentication for all endpoints
|
||||||
schemas:
|
schemas:
|
||||||
|
Error:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
description: A human-readable error message
|
||||||
|
errcode:
|
||||||
|
type: string
|
||||||
|
description: A simple error code
|
||||||
Plugin:
|
Plugin:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
Loading…
Reference in a new issue