Update spec and fix minor problems in implementation

This commit is contained in:
Tulir Asokan 2018-11-01 23:57:24 +02:00
parent 383c9ce5ec
commit 8b97134efd
4 changed files with 107 additions and 22 deletions

View File

@ -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}")

View File

@ -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}")

View File

@ -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

View File

@ -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: