From e3c6c9057bd62b2a3c4ef2fd894b9c68c8e06bba Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 22 Aug 2014 13:40:37 +0100 Subject: [PATCH 01/16] Added initial swagger REST API spec. --- docs/client-server/swagger_matrix/api-docs | 38 + docs/client-server/swagger_matrix/events | 299 +++++++ docs/client-server/swagger_matrix/login | 102 +++ docs/client-server/swagger_matrix/presence | 164 ++++ docs/client-server/swagger_matrix/profile | 122 +++ .../client-server/swagger_matrix/registration | 75 ++ docs/client-server/swagger_matrix/rooms | 807 ++++++++++++++++++ 7 files changed, 1607 insertions(+) create mode 100644 docs/client-server/swagger_matrix/api-docs create mode 100644 docs/client-server/swagger_matrix/events create mode 100644 docs/client-server/swagger_matrix/login create mode 100644 docs/client-server/swagger_matrix/presence create mode 100644 docs/client-server/swagger_matrix/profile create mode 100644 docs/client-server/swagger_matrix/registration create mode 100644 docs/client-server/swagger_matrix/rooms diff --git a/docs/client-server/swagger_matrix/api-docs b/docs/client-server/swagger_matrix/api-docs new file mode 100644 index 000000000..d974dbb37 --- /dev/null +++ b/docs/client-server/swagger_matrix/api-docs @@ -0,0 +1,38 @@ +{ + "apiVersion": "1.0.0", + "swaggerVersion": "1.2", + "apis": [ + { + "path": "/login", + "description": "Login operations" + }, + { + "path": "/registration", + "description": "Registration operations" + }, + { + "path": "/rooms", + "description": "Room operations" + }, + { + "path": "/profile", + "description": "Profile operations" + }, + { + "path": "/presence", + "description": "Presence operations" + } + ], + "authorizations": { + "token": { + "scopes": [] + } + }, + "info": { + "title": "Matrix Client-Server API Reference", + "description": "This contains the client-server API for the reference implementation of the home server", + "termsOfServiceUrl": "http://matrix.org", + "license": "Apache 2.0", + "licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.html" + } +} diff --git a/docs/client-server/swagger_matrix/events b/docs/client-server/swagger_matrix/events new file mode 100644 index 000000000..c9eb3f6ff --- /dev/null +++ b/docs/client-server/swagger_matrix/events @@ -0,0 +1,299 @@ +{ + "apiVersion": "1.0.0", + "swaggerVersion": "1.2", + "basePath": "http://petstore.swagger.wordnik.com/api", + "resourcePath": "/user", + "produces": [ + "application/json" + ], + "apis": [ + { + "path": "/user", + "operations": [ + { + "method": "POST", + "summary": "Create user", + "notes": "This can only be done by the logged in user.", + "type": "void", + "nickname": "createUser", + "authorizations": { + "oauth2": [ + { + "scope": "test:anything", + "description": "anything" + } + ] + }, + "parameters": [ + { + "name": "body", + "description": "Created user object", + "required": true, + "type": "User", + "paramType": "body" + } + ] + } + ] + }, + { + "path": "/user/logout", + "operations": [ + { + "method": "GET", + "summary": "Logs out current logged in user session", + "notes": "", + "type": "void", + "nickname": "logoutUser", + "authorizations": {}, + "parameters": [] + } + ] + }, + { + "path": "/user/createWithArray", + "operations": [ + { + "method": "POST", + "summary": "Creates list of users with given input array", + "notes": "", + "type": "void", + "nickname": "createUsersWithArrayInput", + "authorizations": { + "oauth2": [ + { + "scope": "test:anything", + "description": "anything" + } + ] + }, + "parameters": [ + { + "name": "body", + "description": "List of user object", + "required": true, + "type": "array", + "items": { + "$ref": "User" + }, + "paramType": "body" + } + ] + } + ] + }, + { + "path": "/user/createWithList", + "operations": [ + { + "method": "POST", + "summary": "Creates list of users with given list input", + "notes": "", + "type": "void", + "nickname": "createUsersWithListInput", + "authorizations": { + "oauth2": [ + { + "scope": "test:anything", + "description": "anything" + } + ] + }, + "parameters": [ + { + "name": "body", + "description": "List of user object", + "required": true, + "type": "array", + "items": { + "$ref": "User" + }, + "paramType": "body" + } + ] + } + ] + }, + { + "path": "/user/{username}", + "operations": [ + { + "method": "PUT", + "summary": "Updated user", + "notes": "This can only be done by the logged in user.", + "type": "void", + "nickname": "updateUser", + "authorizations": { + "oauth2": [ + { + "scope": "test:anything", + "description": "anything" + } + ] + }, + "parameters": [ + { + "name": "username", + "description": "name that need to be deleted", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "body", + "description": "Updated user object", + "required": true, + "type": "User", + "paramType": "body" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Invalid username supplied" + }, + { + "code": 404, + "message": "User not found" + } + ] + }, + { + "method": "DELETE", + "summary": "Delete user", + "notes": "This can only be done by the logged in user.", + "type": "void", + "nickname": "deleteUser", + "authorizations": { + "oauth2": [ + { + "scope": "test:anything", + "description": "anything" + } + ] + }, + "parameters": [ + { + "name": "username", + "description": "The name that needs to be deleted", + "required": true, + "type": "string", + "paramType": "path" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Invalid username supplied" + }, + { + "code": 404, + "message": "User not found" + } + ] + }, + { + "method": "GET", + "summary": "Get user by user name", + "notes": "", + "type": "User", + "nickname": "getUserByName", + "authorizations": {}, + "parameters": [ + { + "name": "username", + "description": "The name that needs to be fetched. Use user1 for testing.", + "required": true, + "type": "string", + "paramType": "path" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Invalid username supplied" + }, + { + "code": 404, + "message": "User not found" + } + ] + } + ] + }, + { + "path": "/user/login", + "operations": [ + { + "method": "GET", + "summary": "Logs user into the system", + "notes": "", + "type": "string", + "nickname": "loginUser", + "authorizations": {}, + "parameters": [ + { + "name": "username", + "description": "The user name for login", + "required": true, + "type": "string", + "paramType": "query" + }, + { + "name": "password", + "description": "The password for login in clear text", + "required": true, + "type": "string", + "paramType": "query" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Invalid username and password combination" + } + ] + } + ] + } + ], + "models": { + "User": { + "id": "User", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "firstName": { + "type": "string" + }, + "username": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "userStatus": { + "type": "integer", + "format": "int32", + "description": "User Status", + "enum": [ + "1-registered", + "2-active", + "3-closed" + ] + } + } + } + } +} \ No newline at end of file diff --git a/docs/client-server/swagger_matrix/login b/docs/client-server/swagger_matrix/login new file mode 100644 index 000000000..4410d3c88 --- /dev/null +++ b/docs/client-server/swagger_matrix/login @@ -0,0 +1,102 @@ +{ + "apiVersion": "1.0.0", + "apis": [ + { + "operations": [ + { + "method": "GET", + "nickname": "get_login_info", + "notes": "All login stages MUST be mentioned if there is >1 login type.", + "summary": "Get the login mechanism to use when logging in.", + "type": "LoginInfo" + }, + { + "method": "POST", + "nickname": "submit_login", + "notes": "If this is part of a multi-stage login, there MUST be a 'session' key.", + "parameters": [ + { + "description": "A login submission", + "name": "body", + "paramType": "body", + "required": true, + "type": "LoginSubmission" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Bad login type" + }, + { + "code": 400, + "message": "Missing JSON keys" + } + ], + "summary": "Submit a login action.", + "type": "LoginResult" + } + ], + "path": "/login" + } + ], + "basePath": "http://localhost:8080/matrix/client/api/v1", + "consumes": [ + "application/json" + ], + "models": { + "LoginInfo": { + "id": "LoginInfo", + "properties": { + "stages": { + "description": "Multi-stage login only: An array of all the login types required to login.", + "format": "string", + "type": "array" + }, + "type": { + "description": "The login type that must be used when logging in.", + "type": "string" + } + } + }, + "LoginResult": { + "id": "LoginResult", + "properties": { + "access_token": { + "description": "The access token for this user's login if this is the final stage of the login process.", + "type": "string" + }, + "next": { + "description": "Multi-stage login only: The next login type to submit.", + "type": "string" + }, + "session": { + "description": "Multi-stage login only: The session token to send when submitting the next login type.", + "type": "string" + } + } + }, + "LoginSubmission": { + "id": "LoginSubmission", + "properties": { + "type": { + "description": "The type of login being submitted.", + "type": "string" + }, + "session": { + "description": "Multi-stage login only: The session token from an earlier login stage.", + "type": "string" + }, + "_login_type_defined_keys_": { + "description": "Keys as defined by the specified login type, e.g. \"user\", \"password\"" + } + } + } + }, + "produces": [ + "application/json" + ], + "resourcePath": "/login", + "swaggerVersion": "1.2" +} + diff --git a/docs/client-server/swagger_matrix/presence b/docs/client-server/swagger_matrix/presence new file mode 100644 index 000000000..ee9deb12f --- /dev/null +++ b/docs/client-server/swagger_matrix/presence @@ -0,0 +1,164 @@ +{ + "apiVersion": "1.0.0", + "swaggerVersion": "1.2", + "basePath": "http://localhost:8080/matrix/client/api/v1", + "resourcePath": "/presence", + "produces": [ + "application/json" + ], + "consumes": [ + "application/json" + ], + "apis": [ + { + "path": "/presence/{userId}/status", + "operations": [ + { + "method": "PUT", + "summary": "Update this user's presence state.", + "notes": "This can only be done by the logged in user.", + "type": "void", + "nickname": "update_presence", + "parameters": [ + { + "name": "body", + "description": "The new presence state", + "required": true, + "type": "PresenceUpdate", + "paramType": "body" + }, + { + "name": "userId", + "description": "The user whose presence to set.", + "required": true, + "type": "string", + "paramType": "path" + } + ] + }, + { + "method": "GET", + "summary": "Get this user's presence state.", + "notes": "Get this user's presence state.", + "type": "PresenceUpdate", + "nickname": "get_presence", + "parameters": [ + { + "name": "userId", + "description": "The user whose presence to get.", + "required": true, + "type": "string", + "paramType": "path" + } + ] + } + ] + }, + { + "path": "/presence_list/{userId}", + "operations": [ + { + "method": "GET", + "summary": "Retrieve a list of presences for all of this user's friends.", + "notes": "", + "type": "array", + "items": { + "$ref": "Presence" + }, + "nickname": "get_presence_list", + "parameters": [ + { + "name": "userId", + "description": "The user whose presence list to get.", + "required": true, + "type": "string", + "paramType": "path" + } + ] + }, + { + "method": "POST", + "summary": "Add or remove users from this presence list.", + "notes": "Add or remove users from this presence list.", + "type": "void", + "nickname": "modify_presence_list", + "parameters": [ + { + "name": "userId", + "description": "The user whose presence list is being modified.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "body", + "description": "The modifications to make to this presence list.", + "required": true, + "type": "PresenceListModifications", + "paramType": "body" + } + ] + } + ] + } + ], + "models": { + "PresenceUpdate": { + "id": "PresenceUpdate", + "properties": { + "state": { + "type": "string", + "description": "Enum: The presence state.", + "enum": [ + "offline", + "unavailable", + "online", + "free_for_chat" + ] + }, + "status_msg": { + "type": "string", + "description": "The user-defined message associated with this presence state." + } + }, + "subTypes": [ + "Presence" + ] + }, + "Presence": { + "id": "Presence", + "properties": { + "mtime_age": { + "type": "integer", + "format": "int64", + "description": "The last time this user's presence state changed, in milliseconds." + }, + "user_id": { + "type": "string", + "description": "The fully qualified user ID" + } + } + }, + "PresenceListModifications": { + "id": "PresenceListModifications", + "properties": { + "invite": { + "type": "array", + "description": "A list of user IDs to add to the list.", + "items": { + "type": "string", + "description": "A fully qualified user ID." + } + }, + "drop": { + "type": "array", + "description": "A list of user IDs to remove from the list.", + "items": { + "type": "string", + "description": "A fully qualified user ID." + } + } + } + } + } +} diff --git a/docs/client-server/swagger_matrix/profile b/docs/client-server/swagger_matrix/profile new file mode 100644 index 000000000..1ebde62e2 --- /dev/null +++ b/docs/client-server/swagger_matrix/profile @@ -0,0 +1,122 @@ +{ + "apiVersion": "1.0.0", + "swaggerVersion": "1.2", + "basePath": "http://localhost:8080/matrix/client/api/v1", + "resourcePath": "/profile", + "produces": [ + "application/json" + ], + "consumes": [ + "application/json" + ], + "apis": [ + { + "path": "/profile/{userId}/displayname", + "operations": [ + { + "method": "PUT", + "summary": "Set a display name.", + "notes": "This can only be done by the logged in user.", + "type": "void", + "nickname": "set_display_name", + "parameters": [ + { + "name": "body", + "description": "The new display name for this user.", + "required": true, + "type": "DisplayName", + "paramType": "body" + }, + { + "name": "userId", + "description": "The user whose display name to set.", + "required": true, + "type": "string", + "paramType": "path" + } + ] + }, + { + "method": "GET", + "summary": "Get a display name.", + "notes": "This can be done by anyone.", + "type": "DisplayName", + "nickname": "get_display_name", + "parameters": [ + { + "name": "userId", + "description": "The user whose display name to get.", + "required": true, + "type": "string", + "paramType": "path" + } + ] + } + ] + }, + { + "path": "/profile/{userId}/avatar_url", + "operations": [ + { + "method": "PUT", + "summary": "Set an avatar URL.", + "notes": "This can only be done by the logged in user.", + "type": "void", + "nickname": "set_avatar_url", + "parameters": [ + { + "name": "body", + "description": "The new avatar url for this user.", + "required": true, + "type": "AvatarUrl", + "paramType": "body" + }, + { + "name": "userId", + "description": "The user whose avatar url to set.", + "required": true, + "type": "string", + "paramType": "path" + } + ] + }, + { + "method": "GET", + "summary": "Get an avatar url.", + "notes": "This can be done by anyone.", + "type": "AvatarUrl", + "nickname": "get_avatar_url", + "parameters": [ + { + "name": "userId", + "description": "The user whose avatar url to get.", + "required": true, + "type": "string", + "paramType": "path" + } + ] + } + ] + } + ], + "models": { + "DisplayName": { + "id": "DisplayName", + "properties": { + "displayname": { + "type": "string", + "description": "The textual display name" + } + } + }, + "AvatarUrl": { + "id": "AvatarUrl", + "properties": { + "avatar_url": { + "type": "string", + "description": "A url to an image representing an avatar." + } + } + } + } +} diff --git a/docs/client-server/swagger_matrix/registration b/docs/client-server/swagger_matrix/registration new file mode 100644 index 000000000..ccd542d11 --- /dev/null +++ b/docs/client-server/swagger_matrix/registration @@ -0,0 +1,75 @@ +{ + "apiVersion": "1.0.0", + "apis": [ + { + "operations": [ + { + "method": "POST", + "nickname": "register", + "notes": "Volatile: This API is likely to change.", + "parameters": [ + { + "description": "A registration request", + "name": "body", + "paramType": "body", + "required": true, + "type": "RegistrationRequest" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "No JSON object." + }, + { + "code": 400, + "message": "User ID must only contain characters which do not require url encoding." + }, + { + "code": 400, + "message": "User ID already taken." + } + ], + "summary": "Register with the home server.", + "type": "RegistrationResponse" + } + ], + "path": "/register" + } + ], + "basePath": "http://localhost:8080/matrix/client/api/v1", + "consumes": [ + "application/json" + ], + "models": { + "RegistrationResponse": { + "id": "RegistrationResponse", + "properties": { + "access_token": { + "description": "The access token for this user.", + "type": "string" + }, + "user_id": { + "description": "The fully-qualified user ID.", + "type": "string" + } + } + }, + "RegistrationRequest": { + "id": "RegistrationRequest", + "properties": { + "user_id": { + "description": "The desired user ID. If not specified, a random user ID will be allocated.", + "type": "string", + "required": false + } + } + } + }, + "produces": [ + "application/json" + ], + "resourcePath": "/register", + "swaggerVersion": "1.2" +} + diff --git a/docs/client-server/swagger_matrix/rooms b/docs/client-server/swagger_matrix/rooms new file mode 100644 index 000000000..47a888724 --- /dev/null +++ b/docs/client-server/swagger_matrix/rooms @@ -0,0 +1,807 @@ +{ + "apiVersion": "1.0.0", + "swaggerVersion": "1.2", + "basePath": "http://localhost:8080/matrix/client/api/v1", + "resourcePath": "/rooms", + "produces": [ + "application/json" + ], + "consumes": [ + "application/json" + ], + "authorizations": { + "token": [] + }, + "apis": [ + { + "path": "/rooms/{roomId}/messages/{userId}/{messageId}", + "operations": [ + { + "method": "PUT", + "summary": "Send a message in this room.", + "notes": "Send a message in this room.", + "type": "void", + "nickname": "send_message", + "consumes": [ + "application/json" + ], + "parameters": [ + { + "name": "body", + "description": "The message contents", + "required": true, + "type": "Message", + "paramType": "body" + }, + { + "name": "roomId", + "description": "The room to send the message in.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "userId", + "description": "The fully qualified message sender's user ID.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "messageId", + "description": "A message ID which is unique for each room and user.", + "required": true, + "type": "string", + "paramType": "path" + } + ], + "responseMessages": [ + { + "code": 403, + "message": "Must send messages as yourself." + } + ] + }, + { + "method": "GET", + "summary": "Get a message from this room.", + "notes": "Get a message from this room.", + "type": "Message", + "nickname": "get_message", + "parameters": [ + { + "name": "roomId", + "description": "The room to send the message in.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "userId", + "description": "The fully qualified message sender's user ID.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "messageId", + "description": "A message ID which is unique for each room and user.", + "required": true, + "type": "string", + "paramType": "path" + } + ], + "responseMessages": [ + { + "code": 404, + "message": "Message not found." + } + ] + } + ] + }, + { + "path": "/rooms/{roomId}/topic", + "operations": [ + { + "method": "PUT", + "summary": "Set the topic for this room.", + "notes": "Set the topic for this room.", + "type": "void", + "nickname": "set_topic", + "consumes": [ + "application/json" + ], + "parameters": [ + { + "name": "body", + "description": "The topic contents", + "required": true, + "type": "Topic", + "paramType": "body" + }, + { + "name": "roomId", + "description": "The room to set the topic in.", + "required": true, + "type": "string", + "paramType": "path" + } + ], + "responseMessages": [ + { + "code": 403, + "message": "Must send messages as yourself." + } + ] + }, + { + "method": "GET", + "summary": "Get the topic for this room.", + "notes": "Get the topic for this room.", + "type": "Topic", + "nickname": "get_topic", + "parameters": [ + { + "name": "roomId", + "description": "The room to get topic in.", + "required": true, + "type": "string", + "paramType": "path" + } + ], + "responseMessages": [ + { + "code": 404, + "message": "Topic not found." + } + ] + } + ] + }, + { + "path": "/rooms/{roomId}/messages/{msgSenderId}/{messageId}/feedback/{senderId}/{feedbackType}", + "operations": [ + { + "method": "PUT", + "summary": "Send feedback to a message.", + "notes": "Send feedback to a message.", + "type": "void", + "nickname": "send_feedback", + "consumes": [ + "application/json" + ], + "parameters": [ + { + "name": "body", + "description": "The feedback contents", + "required": true, + "type": "Feedback", + "paramType": "body" + }, + { + "name": "roomId", + "description": "The room to send the feedback in.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "msgSenderId", + "description": "The fully qualified message sender's user ID.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "messageId", + "description": "A message ID which is unique for each room and user.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "senderId", + "description": "The fully qualified feedback sender's user ID.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "feedbackType", + "description": "The type of feedback being sent.", + "required": true, + "type": "string", + "paramType": "path", + "enum": [ + "d", + "r" + ] + } + ], + "responseMessages": [ + { + "code": 403, + "message": "Must send feedback as yourself." + }, + { + "code": 400, + "message": "Bad feedback type." + } + ] + }, + { + "method": "GET", + "summary": "Get feedback for a message.", + "notes": "Get feedback for a message.", + "type": "Feedback", + "nickname": "get_feedback", + "parameters": [ + { + "name": "roomId", + "description": "The room to send the message in.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "msgSenderId", + "description": "The fully qualified message sender's user ID.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "messageId", + "description": "A message ID which is unique for each room and user.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "senderId", + "description": "The fully qualified feedback sender's user ID.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "feedbackType", + "description": "Enum: The type of feedback being sent.", + "required": true, + "type": "string", + "paramType": "path", + "enum": [ + "d", + "r" + ] + } + ], + "responseMessages": [ + { + "code": 404, + "message": "Feedback not found." + } + ] + } + ] + }, + { + "path": "/rooms/{roomId}/members/{userId}/state", + "operations": [ + { + "method": "PUT", + "summary": "Change the membership state for a user in a room.", + "notes": "Change the membership state for a user in a room.", + "type": "void", + "nickname": "set_membership", + "consumes": [ + "application/json" + ], + "parameters": [ + { + "name": "body", + "description": "The new membership state", + "required": true, + "type": "Member", + "paramType": "body" + }, + { + "name": "userId", + "description": "The user whose membership is being changed.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "roomId", + "description": "The room which has this user.", + "required": true, + "type": "string", + "paramType": "path" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "No membership key." + }, + { + "code": 400, + "message": "Bad membership value." + }, + { + "code": 403, + "message": "When inviting: You are not in the room." + }, + { + "code": 403, + "message": "When inviting: is already in the room." + }, + { + "code": 403, + "message": "When joining: Cannot force another user to join." + }, + { + "code": 403, + "message": "When joining: You are not invited to this room." + } + ] + }, + { + "method": "GET", + "summary": "Get the membership state of a user in a room.", + "notes": "Get the membership state of a user in a room.", + "type": "Member", + "nickname": "get_membership", + "parameters": [ + { + "name": "userId", + "description": "The user whose membership state you want to get.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "roomId", + "description": "The room which has this user.", + "required": true, + "type": "string", + "paramType": "path" + } + ], + "responseMessages": [ + { + "code": 404, + "message": "Member not found." + } + ] + }, + { + "method": "DELETE", + "summary": "Leave a room.", + "notes": "Leave a room.", + "type": "void", + "nickname": "remove_membership", + "parameters": [ + { + "name": "userId", + "description": "The user who is leaving.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "roomId", + "description": "The room which has this user.", + "required": true, + "type": "string", + "paramType": "path" + } + ], + "responseMessages": [ + { + "code": 403, + "message": "You are not in the room." + }, + { + "code": 403, + "message": "Cannot force another user to leave." + } + ] + } + ] + }, + { + "path": "/join/{roomAlias}", + "operations": [ + { + "method": "PUT", + "summary": "Join a room via a room alias.", + "notes": "Join a room via a room alias.", + "type": "RoomInfo", + "nickname": "join_room_via_alias", + "consumes": [ + "application/json" + ], + "parameters": [ + { + "name": "roomAlias", + "description": "The room alias to join.", + "required": true, + "type": "string", + "paramType": "path" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Bad room alias." + } + ] + } + ] + }, + { + "path": "/rooms", + "operations": [ + { + "method": "POST", + "summary": "Create a room.", + "notes": "Create a room.", + "type": "RoomInfo", + "nickname": "create_room", + "consumes": [ + "application/json" + ], + "parameters": [ + { + "name": "body", + "description": "The desired configuration for the room.", + "required": true, + "type": "RoomConfig", + "paramType": "body" + } + ], + "responseMessages": [ + { + "code": 400, + "message": "Body must be JSON." + }, + { + "code": 400, + "message": "Room alias already taken." + } + ] + } + ] + }, + { + "path": "/rooms/{roomId}/messages/list", + "operations": [ + { + "method": "GET", + "summary": "Get a list of messages for this room.", + "notes": "Get a list of messages for this room.", + "type": "MessagePaginationChunk", + "nickname": "get_messages", + "parameters": [ + { + "name": "roomId", + "description": "The room to get messages in.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "from", + "description": "The token to start getting results from.", + "required": false, + "type": "string", + "paramType": "query" + }, + { + "name": "to", + "description": "The token to stop getting results at.", + "required": false, + "type": "string", + "paramType": "query" + }, + { + "name": "limit", + "description": "The maximum number of messages to return.", + "required": false, + "type": "integer", + "paramType": "query" + } + ] + } + ] + }, + { + "path": "/rooms/{roomId}/members/list", + "operations": [ + { + "method": "GET", + "summary": "Get a list of members for this room.", + "notes": "Get a list of members for this room.", + "type": "MemberPaginationChunk", + "nickname": "get_members", + "parameters": [ + { + "name": "roomId", + "description": "The room to get a list of members from.", + "required": true, + "type": "string", + "paramType": "path" + }, + { + "name": "from", + "description": "The token to start getting results from.", + "required": false, + "type": "string", + "paramType": "query" + }, + { + "name": "to", + "description": "The token to stop getting results at.", + "required": false, + "type": "string", + "paramType": "query" + }, + { + "name": "limit", + "description": "The maximum number of members to return.", + "required": false, + "type": "integer", + "paramType": "query" + } + ] + } + ] + } + ], + "models": { + "Topic": { + "id": "Topic", + "properties": { + "topic": { + "type": "string", + "description": "The topic text" + } + } + }, + "Message": { + "id": "Message", + "properties": { + "msgtype": { + "type": "string", + "description": "The type of message being sent, e.g. \"m.text\"", + "required": true + }, + "_msgtype_defined_keys_": { + "description": "Additional keys as defined by the msgtype, e.g. \"body\"" + } + } + }, + "Feedback": { + "id": "Feedback", + "properties": { + } + }, + "Member": { + "id": "Member", + "properties": { + "membership": { + "type": "string", + "description": "Enum: The membership state of this member.", + "enum": [ + "invite", + "join", + "leave", + "knock" + ] + } + } + }, + "RoomInfo": { + "id": "RoomInfo", + "properties": { + "room_id": { + "type": "string", + "description": "The allocated room ID.", + "required": true + }, + "room_alias": { + "type": "string", + "description": "The alias for the room.", + "required": false + } + } + }, + "RoomConfig": { + "id": "RoomConfig", + "properties": { + "visibility": { + "type": "string", + "description": "Enum: The room visibility.", + "required": false, + "enum": [ + "public", + "private" + ] + }, + "room_alias_name": { + "type": "string", + "description": "The alias to give the new room.", + "required": false + } + } + }, + "PaginationRequest": { + "id": "PaginationRequest", + "properties": { + "from": { + "type": "string", + "description": "The token to start getting results from." + }, + "to": { + "type": "string", + "description": "The token to stop getting results at." + }, + "limit": { + "type": "integer", + "description": "The maximum number of entries to return." + } + } + }, + "PaginationChunk": { + "id": "PaginationChunk", + "properties": { + "start": { + "type": "string", + "description": "A token which correlates to the first value in \"chunk\" for paginating.", + "required": true + }, + "end": { + "type": "string", + "description": "A token which correlates to the last value in \"chunk\" for paginating.", + "required": true + } + }, + "subTypes": [ + "MessagePaginationChunk" + ] + }, + "MessagePaginationChunk": { + "id": "MessagePaginationChunk", + "properties": { + "chunk": { + "type": "array", + "description": "A list of message events.", + "items": { + "$ref": "MessageEvent" + }, + "required": true + } + } + }, + "MemberPaginationChunk": { + "id": "MemberPaginationChunk", + "properties": { + "chunk": { + "type": "array", + "description": "A list of member events.", + "items": { + "$ref": "MemberEvent" + }, + "required": true + } + } + }, + "Event": { + "id": "Event", + "properties": { + "event_id": { + "type": "string", + "description": "An ID which uniquely identifies this event.", + "required": true + }, + "room_id": { + "type": "string", + "description": "The room in which this event occurred.", + "required": true + } + }, + "subTypes": [ + "MessageEvent" + ] + }, + "MessageEvent": { + "id": "MessageEvent", + "properties": { + "content": { + "type": "Message" + } + } + }, + "MemberEvent": { + "id": "MemberEvent", + "properties": { + "content": { + "type": "Member" + } + } + }, + "Tag": { + "id": "Tag", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + } + }, + "Pet": { + "id": "Pet", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64", + "description": "unique identifier for the pet", + "minimum": "0.0", + "maximum": "100.0" + }, + "category": { + "$ref": "Category" + }, + "name": { + "type": "string" + }, + "photoUrls": { + "type": "array", + "items": { + "type": "string" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + } + }, + "Category": { + "id": "Category", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "pet": { + "$ref": "Pet" + } + } + } + } +} From a96076f335679fc67790bf9bf0f37577d7aa1abc Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Fri, 22 Aug 2014 16:13:09 +0100 Subject: [PATCH 02/16] add 0.0.0 into the changelog, and add dates --- CHANGES.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 055f8bc01..86e78c288 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,6 @@ -Changes in synapse 0.0.1 -======================= +Changes in synapse 0.0.1 (2014-08-22) +===================================== + Homeserver: * Completely change the database schema to support generic event types. * Improve presence reliability. @@ -18,3 +19,8 @@ Webclient: * Use the new initial sync API to reduce number of round trips to the homeserver. * Change url scheme to use room aliases instead of room ids where known. * Increase longpoll timeout. + +Changes in synapse 0.0.0 (2014-08-13) +===================================== + + * Initial alpha release From f40844def237f514e2a7398e8c7ed65b45277b0d Mon Sep 17 00:00:00 2001 From: root Date: Fri, 22 Aug 2014 16:20:53 +0100 Subject: [PATCH 03/16] avatar url --- webclient/rooms/rooms.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webclient/rooms/rooms.html b/webclient/rooms/rooms.html index ba3b7d8ba..5e422bf83 100644 --- a/webclient/rooms/rooms.html +++ b/webclient/rooms/rooms.html @@ -19,7 +19,7 @@ -->
- +
From 9f514915afe813cfb531748e252479b6332e5d76 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Fri, 22 Aug 2014 18:02:45 +0100 Subject: [PATCH 04/16] Add indices to schema --- synapse/handlers/presence.py | 4 ++++ synapse/storage/schema/im.sql | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 540e114b8..4ce77cbff 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -187,6 +187,10 @@ class PresenceHandler(BaseHandler): @defer.inlineCallbacks def set_state(self, target_user, auth_user, state): + return + # TODO (erikj): Turn this back on. Why did we end up sending EDUs + # everywhere? + if not target_user.is_mine: raise SynapseError(400, "User is not hosted on this Home Server") diff --git a/synapse/storage/schema/im.sql b/synapse/storage/schema/im.sql index 39a1ed703..7f4e75889 100644 --- a/synapse/storage/schema/im.sql +++ b/synapse/storage/schema/im.sql @@ -26,6 +26,11 @@ CREATE TABLE IF NOT EXISTS events( CONSTRAINT ev_uniq UNIQUE (event_id) ); +CREATE INDEX IF NOT EXISTS events_event_id ON events (event_id); +CREATE INDEX IF NOT EXISTS events_stream_ordering ON events (stream_ordering); +CREATE INDEX IF NOT EXISTS events_topological_ordering ON events (topological_ordering); +CREATE INDEX IF NOT EXISTS events_room_id ON events (room_id); + CREATE TABLE IF NOT EXISTS state_events( event_id TEXT NOT NULL, room_id TEXT NOT NULL, @@ -34,6 +39,12 @@ CREATE TABLE IF NOT EXISTS state_events( prev_state TEXT ); +CREATE UNIQUE INDEX IF NOT EXISTS state_events_event_id ON state_events (event_id); +CREATE INDEX IF NOT EXISTS state_events_room_id ON state_events (room_id); +CREATE INDEX IF NOT EXISTS state_events_type ON state_events (type); +CREATE INDEX IF NOT EXISTS state_events_state_key ON state_events (state_key); + + CREATE TABLE IF NOT EXISTS current_state_events( event_id TEXT NOT NULL, room_id TEXT NOT NULL, @@ -42,6 +53,11 @@ CREATE TABLE IF NOT EXISTS current_state_events( CONSTRAINT curr_uniq UNIQUE (room_id, type, state_key) ON CONFLICT REPLACE ); +CREATE INDEX IF NOT EXISTS curr_events_event_id ON current_state_events (event_id); +CREATE INDEX IF NOT EXISTS current_state_events_room_id ON current_state_events (room_id); +CREATE INDEX IF NOT EXISTS current_state_events_type ON current_state_events (type); +CREATE INDEX IF NOT EXISTS current_state_events_state_key ON current_state_events (state_key); + CREATE TABLE IF NOT EXISTS room_memberships( event_id TEXT NOT NULL, user_id TEXT NOT NULL, @@ -50,6 +66,10 @@ CREATE TABLE IF NOT EXISTS room_memberships( membership TEXT NOT NULL ); +CREATE INDEX IF NOT EXISTS room_memberships_event_id ON room_memberships (event_id); +CREATE INDEX IF NOT EXISTS room_memberships_room_id ON room_memberships (room_id); +CREATE INDEX IF NOT EXISTS room_memberships_user_id ON room_memberships (user_id); + CREATE TABLE IF NOT EXISTS feedback( event_id TEXT NOT NULL, feedback_type TEXT, From 537ecd4e9929cc66eec3747aa37ce8c19ad6aa0a Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Fri, 22 Aug 2014 18:12:38 +0100 Subject: [PATCH 05/16] Turn off spammy logging --- synapse/storage/roommember.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/storage/roommember.py b/synapse/storage/roommember.py index 89c87290c..aca5cff73 100644 --- a/synapse/storage/roommember.py +++ b/synapse/storage/roommember.py @@ -145,7 +145,7 @@ class RoomMemberStore(SQLBaseStore): rows = yield self._execute_and_decode(sql, *where_values) - logger.debug("_get_members_query Got rows %s", rows) + # logger.debug("_get_members_query Got rows %s", rows) results = [self._parse_event_from_row(r) for r in rows] defer.returnValue(results) From b18db63c066682677f82bdba43db3754eb9bbb2e Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Fri, 22 Aug 2014 18:13:50 +0100 Subject: [PATCH 06/16] Turn off more spammy logging. --- synapse/handlers/room.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 899b653fb..8ab0b8033 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -307,7 +307,7 @@ class MessageHandler(BaseHandler): ret = {"rooms": rooms_ret, "presence": presence[0], "end": now_token} - logger.debug("snapshot_all_rooms returning: %s", ret) + # logger.debug("snapshot_all_rooms returning: %s", ret) defer.returnValue(ret) From 5b058a79cbe4ba0e8d35a52aa79693fe5a074537 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Fri, 22 Aug 2014 18:21:21 +0100 Subject: [PATCH 07/16] Make is_presence_visible always return true as it was thrashing the database. --- synapse/handlers/presence.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index 4ce77cbff..bccb24a10 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -142,6 +142,9 @@ class PresenceHandler(BaseHandler): @defer.inlineCallbacks def is_presence_visible(self, observer_user, observed_user): + return defer.succeed(True) + # FIXME (erikj): This code path absolutely kills the database. + assert(observed_user.is_mine) if observer_user == observed_user: From cda4ff85198fd68ba10905e90f3e7172fe24559b Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Fri, 22 Aug 2014 18:23:02 +0100 Subject: [PATCH 08/16] Oops, we need to use defer.returnValue. --- synapse/handlers/presence.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py index bccb24a10..c88cc1878 100644 --- a/synapse/handlers/presence.py +++ b/synapse/handlers/presence.py @@ -142,7 +142,8 @@ class PresenceHandler(BaseHandler): @defer.inlineCallbacks def is_presence_visible(self, observer_user, observed_user): - return defer.succeed(True) + defer.returnValue(True) + return # FIXME (erikj): This code path absolutely kills the database. assert(observed_user.is_mine) From 104808107ac6857e3d7bafefaa21f9ea90d9bad3 Mon Sep 17 00:00:00 2001 From: Mark Haines Date: Fri, 22 Aug 2014 18:40:31 +0100 Subject: [PATCH 09/16] skip presence tests which broke when presence polling was disabled --- tests/handlers/test_presence.py | 7 +++++++ tests/handlers/test_presencelike.py | 4 ++++ tests/rest/test_presence.py | 2 ++ 3 files changed, 13 insertions(+) diff --git a/tests/handlers/test_presence.py b/tests/handlers/test_presence.py index 8b88c49a0..6d3cd76db 100644 --- a/tests/handlers/test_presence.py +++ b/tests/handlers/test_presence.py @@ -190,6 +190,7 @@ class PresenceStateTestCase(unittest.TestCase): ), SynapseError ) + test_get_disallowed_state.skip = "Presence polling is disabled" @defer.inlineCallbacks def test_set_my_state(self): @@ -214,6 +215,7 @@ class PresenceStateTestCase(unittest.TestCase): state={"state": OFFLINE}) self.mock_stop.assert_called_with(self.u_apple) + test_set_my_state.skip = "Presence polling is disabled" class PresenceInvitesTestCase(unittest.TestCase): @@ -653,6 +655,7 @@ class PresencePushTestCase(unittest.TestCase): observed_user=self.u_banana, statuscache=ANY), # self-reflection ]) # and no others... + test_push_local.skip = "Presence polling is disabled" @defer.inlineCallbacks def test_push_remote(self): @@ -704,6 +707,7 @@ class PresencePushTestCase(unittest.TestCase): ) yield put_json.await_calls() + test_push_remote.skip = "Presence polling is disabled" @defer.inlineCallbacks def test_recv_remote(self): @@ -996,6 +1000,8 @@ class PresencePollingTestCase(unittest.TestCase): self.assertFalse("banana" in self.handler._local_pushmap) self.assertFalse("clementine" in self.handler._local_pushmap) + test_push_local.skip = "Presence polling is disabled" + @defer.inlineCallbacks def test_remote_poll_send(self): @@ -1044,6 +1050,7 @@ class PresencePollingTestCase(unittest.TestCase): put_json.await_calls() self.assertFalse(self.u_potato in self.handler._remote_recvmap) + test_remote_poll_send.skip = "Presence polling is disabled" @defer.inlineCallbacks def test_remote_poll_receive(self): diff --git a/tests/handlers/test_presencelike.py b/tests/handlers/test_presencelike.py index bba5dd4e5..c25c6889b 100644 --- a/tests/handlers/test_presencelike.py +++ b/tests/handlers/test_presencelike.py @@ -135,6 +135,7 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): mocked_set.assert_called_with("apple", {"state": UNAVAILABLE, "status_msg": "Away"}) + test_set_my_state.skip = "Presence polling is disabled" @defer.inlineCallbacks def test_push_local(self): @@ -209,6 +210,8 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): "displayname": "I am an Apple", "avatar_url": "http://foo", }, statuscache.state) + test_push_local.skip = "Presence polling is disabled" + @defer.inlineCallbacks def test_push_remote(self): @@ -239,6 +242,7 @@ class PresenceProfilelikeDataTestCase(unittest.TestCase): ], }, ) + test_push_remote.skip = "Presence polling is disabled" @defer.inlineCallbacks def test_recv_remote(self): diff --git a/tests/rest/test_presence.py b/tests/rest/test_presence.py index 8ac246b4d..970405d27 100644 --- a/tests/rest/test_presence.py +++ b/tests/rest/test_presence.py @@ -114,6 +114,7 @@ class PresenceStateTestCase(unittest.TestCase): self.assertEquals(200, code) mocked_set.assert_called_with("apple", {"state": UNAVAILABLE, "status_msg": "Away"}) + test_set_my_status.skip = "Presence polling is disabled" class PresenceListTestCase(unittest.TestCase): @@ -309,3 +310,4 @@ class PresenceEventStreamTestCase(unittest.TestCase): "mtime_age": 0, }}, ]}, response) + test_shortpoll.skip = "Presence polling is disabled" From 68f4d737175c44f05b750f4bd1f0a3d9943c4b7f Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Fri, 22 Aug 2014 19:03:28 +0100 Subject: [PATCH 10/16] Mention in changelog that we disabled presence. --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 86e78c288..fc6385cb1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,7 @@ Changes in synapse 0.0.1 (2014-08-22) ===================================== +Presence has been disabled in this release due to a bug that caused the +homeserver to spam other remote homeservers. Homeserver: * Completely change the database schema to support generic event types. From 45e70a6b70745b9e5e1d2e37900a1dc43737a47b Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 23 Aug 2014 00:50:49 +0100 Subject: [PATCH 11/16] point out the non-quick-start guide --- README.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 069f37ec0..cfdc2a1c7 100644 --- a/README.rst +++ b/README.rst @@ -34,6 +34,10 @@ To get up and running: machine.my.domain.name``. Then come join ``#matrix:matrix.org`` and say hi! :) +For more detailed setup instructions, please see further down this document. + +[1] VoIP currently in development + About Matrix ============ @@ -85,8 +89,6 @@ https://github.com/matrix-org/synapse/issues or at matrix@matrix.org. Thanks for trying Matrix! -[1] VoIP currently in development - [2] Cryptographic signing of messages isn't turned on yet [3] End-to-end encryption is currently in development From d2bb28d2df6ffe58db1a56e7aec85c34d9d1425f Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Sat, 23 Aug 2014 20:45:00 +0100 Subject: [PATCH 12/16] very quick and dirty responsive design for iPhones --- webclient/app.css | 98 +++++++++++++++++++++++++++++++++----- webclient/index.html | 6 +-- webclient/login/login.html | 4 +- webclient/room/room.html | 30 ++++++------ webclient/rooms/rooms.html | 1 + webclient/user/user.html | 1 + 6 files changed, 110 insertions(+), 30 deletions(-) diff --git a/webclient/app.css b/webclient/app.css index 207f35f5f..6f320414b 100644 --- a/webclient/app.css +++ b/webclient/app.css @@ -1,3 +1,71 @@ +/*** Mobile voodoo ***/ +@media all and (max-device-width: 640px) { + + #messageTableWrapper { + margin-right: 0px ! important; + } + + .leftBlock { + width: 8em ! important; + } + + #header, + #messageTable, + #wrapper, + #roomName, + #controls { + max-width: 640px ! important; + } + + #userIdCell, + #usersTableWrapper, + #extraControls { + display: none; + } + + #buttonsCell { + width: 60px ! important; + padding-left: 20px ! important; + } + + #roomLogo { + display: none; + } + + #roomName { + text-align: left ! important; + top: -35px ! important; + } + + .bubble { + font-size: 12px ! important; + height: 20px ! important; + } + + #page { + top: 35px ! important; + bottom: 70px ! important; + } + + #header, + #page { + margin: 5px ! important; + } + + #header { + padding: 5px ! important; + } + + /* stop zoom on select */ + select:focus, + textarea, + input + { + font-size: 16px ! important; + } + +} + body { font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif; font-size: 12pt; @@ -17,7 +85,6 @@ h1 { left: 0px; right: 0px; margin: 20px; - margin: 20px; } #wrapper { @@ -32,8 +99,7 @@ h1 { text-align: right; top: -40px; position: absolute; - font-size: 16pt; - margin-bottom: 10px; + font-size: 16px; } #controlPanel { @@ -50,6 +116,10 @@ h1 { margin: auto; } +#buttonsCell { + width: 150px; +} + #inputBarTable { width: 100%; } @@ -111,13 +181,13 @@ h1 { color: #fff; margin: 2px; bottom: 0px; - font-size: 8pt; + font-size: 12px; word-break: break-all; } .userPresence { text-align: center; - font-size: 8pt; + font-size: 12px; color: #fff; background-color: #aaa; border-bottom: 1px #ddd solid; @@ -159,7 +229,7 @@ h1 { background-color: #fff; color: #888; font-weight: medium; - font-size: 8pt; + font-size: 12px; text-align: right; border-top: 1px #ddd solid; } @@ -277,7 +347,7 @@ h1 { .profile-avatar { width: 160px; height: 160px; - display:table-cell; + display: table-cell; vertical-align: middle; text-align: center; } @@ -293,13 +363,19 @@ h1 { } #user-displayname { - font-size: 16pt; + font-size: 24px; } /******************************/ -#header { - padding-left: 20px; - padding-right: 20px; +#header +{ + padding: 20px; + max-width: 1280px; + margin: auto; +} + +#logo, +#roomLogo { max-width: 1280px; margin: auto; } diff --git a/webclient/index.html b/webclient/index.html index 27d920819..5b8e27fa6 100644 --- a/webclient/index.html +++ b/webclient/index.html @@ -2,10 +2,12 @@ [matrix] - + + + @@ -36,8 +38,6 @@ - -

[matrix]

diff --git a/webclient/login/login.html b/webclient/login/login.html index b1488b37f..4b2ea6092 100644 --- a/webclient/login/login.html +++ b/webclient/login/login.html @@ -1,4 +1,6 @@ -