diff --git a/.run/Run.run.xml b/.run/Run.run.xml index 47b924fe55..78502fd86d 100644 --- a/.run/Run.run.xml +++ b/.run/Run.run.xml @@ -9,6 +9,7 @@ + diff --git a/core/api/docs/docs.go b/core/api/docs/docs.go index 838673118d..79214ab946 100644 --- a/core/api/docs/docs.go +++ b/core/api/docs/docs.go @@ -253,7 +253,7 @@ const docTemplate = `{ "200": { "description": "Space created successfully", "schema": { - "$ref": "#/definitions/space.CreateSpaceResponse" + "$ref": "#/definitions/space.SpaceResponse" } }, "400": { @@ -268,6 +268,61 @@ const docTemplate = `{ "$ref": "#/definitions/util.UnauthorizedError" } }, + "423": { + "description": "Rate limit exceeded", + "schema": { + "$ref": "#/definitions/util.RateLimitError" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/util.ServerError" + } + } + } + } + }, + "/spaces/{space_id}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "spaces" + ], + "summary": "Get space", + "parameters": [ + { + "type": "string", + "description": "Space ID", + "name": "space_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Space", + "schema": { + "$ref": "#/definitions/space.SpaceResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.UnauthorizedError" + } + }, + "404": { + "description": "Space not found", + "schema": { + "$ref": "#/definitions/util.NotFoundError" + } + }, "500": { "description": "Internal server error", "schema": { @@ -335,6 +390,62 @@ const docTemplate = `{ } } }, + "/spaces/{space_id}/members/{member_id}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "spaces" + ], + "summary": "Get member", + "parameters": [ + { + "type": "string", + "description": "Space ID", + "name": "space_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Member ID", + "name": "member_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Member", + "schema": { + "$ref": "#/definitions/space.MemberResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.UnauthorizedError" + } + }, + "404": { + "description": "Member not found", + "schema": { + "$ref": "#/definitions/util.NotFoundError" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/util.ServerError" + } + } + } + } + }, "/spaces/{space_id}/objects": { "get": { "consumes": [ @@ -440,6 +551,12 @@ const docTemplate = `{ "$ref": "#/definitions/util.UnauthorizedError" } }, + "423": { + "description": "Rate limit exceeded", + "schema": { + "$ref": "#/definitions/util.RateLimitError" + } + }, "500": { "description": "Internal server error", "schema": { @@ -556,6 +673,12 @@ const docTemplate = `{ "$ref": "#/definitions/util.NotFoundError" } }, + "423": { + "description": "Rate limit exceeded", + "schema": { + "$ref": "#/definitions/util.RateLimitError" + } + }, "500": { "description": "Internal server error", "schema": { @@ -947,6 +1070,7 @@ const docTemplate = `{ "type": "object", "properties": { "challenge_id": { + "description": "The challenge id associated with the displayed code and needed to solve the challenge for token", "type": "string", "example": "67647f5ecda913e9a2e11b26" } @@ -956,10 +1080,12 @@ const docTemplate = `{ "type": "object", "properties": { "app_key": { + "description": "The permanent app key", "type": "string", "example": "zhSG/zQRmgADyilWPtgdnfo1qD60oK02/SVgi1GaFt6=" }, "session_token": { + "description": "The ephemeral session token", "type": "string", "example": "eyJhbGciOeJIRzI1NiIsInR5cCI6IkpXVCJ1.eyJzZWVkIjaiY0dmVndlUnAifQ.Y1EZecYnwmvMkrXKOa2XJnAbaRt34urBabe06tmDQII" } @@ -969,6 +1095,7 @@ const docTemplate = `{ "type": "object", "properties": { "path": { + "description": "The path the object was exported to", "type": "string", "example": "/path/to/export" } @@ -978,6 +1105,7 @@ const docTemplate = `{ "type": "object", "properties": { "align": { + "description": "The alignment of the block", "type": "string", "enum": [ "AlignLeft", @@ -988,10 +1116,12 @@ const docTemplate = `{ "example": "AlignLeft" }, "background_color": { + "description": "The background color of the block", "type": "string", "example": "red" }, "children_ids": { + "description": "The ids of the block's children", "type": "array", "items": { "type": "string" @@ -1001,16 +1131,28 @@ const docTemplate = `{ ] }, "file": { - "$ref": "#/definitions/object.File" + "description": "The file of the block, if applicable", + "allOf": [ + { + "$ref": "#/definitions/object.File" + } + ] }, "id": { + "description": "The id of the block", "type": "string", "example": "64394517de52ad5acb89c66c" }, "text": { - "$ref": "#/definitions/object.Text" + "description": "The text of the block, if applicable", + "allOf": [ + { + "$ref": "#/definitions/object.Text" + } + ] }, "vertical_align": { + "description": "The vertical alignment of the block", "type": "string", "enum": [ "VerticalAlignTop", @@ -1025,30 +1167,37 @@ const docTemplate = `{ "type": "object", "properties": { "body": { + "description": "The body of the object", "type": "string", - "example": "Object Body" + "example": "This is the body of the object. Markdown syntax is supported here." }, "description": { + "description": "The description of the object", "type": "string", - "example": "Object Description" + "example": "This is a description of the object." }, "icon": { + "description": "The icon of the object", "type": "string", "example": "📄" }, "name": { + "description": "The name of the object", "type": "string", - "example": "Object Name" + "example": "My object" }, "object_type_unique_key": { + "description": "The unique key of the object type", "type": "string", "example": "ot-page" }, "source": { + "description": "The source url, only applicable for bookmarks", "type": "string", - "example": "https://source.com" + "example": "https://bookmark-source.com" }, "template_id": { + "description": "The id of the template to use", "type": "string", "example": "bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge" } @@ -1058,10 +1207,12 @@ const docTemplate = `{ "type": "object", "properties": { "details": { + "description": "The details", "type": "object", "additionalProperties": true }, "id": { + "description": "The id of the detail", "type": "string", "enum": [ "last_modified_date", @@ -1079,30 +1230,39 @@ const docTemplate = `{ "type": "object", "properties": { "added_at": { + "description": "The added at of the file", "type": "integer" }, "hash": { + "description": "The hash of the file", "type": "string" }, "mime": { + "description": "The mime of the file", "type": "string" }, "name": { + "description": "The name of the file", "type": "string" }, "size": { + "description": "The size of the file", "type": "integer" }, "state": { + "description": "The state of the file", "type": "string" }, "style": { + "description": "The style of the file", "type": "string" }, "target_object_id": { + "description": "The target object id of the file", "type": "string" }, "type": { + "description": "The type of the file", "type": "string" } } @@ -1111,46 +1271,56 @@ const docTemplate = `{ "type": "object", "properties": { "blocks": { + "description": "The blocks of the object", "type": "array", "items": { "$ref": "#/definitions/object.Block" } }, "details": { + "description": "The details of the object", "type": "array", "items": { "$ref": "#/definitions/object.Detail" } }, "icon": { + "description": "The icon of the object", "type": "string", "example": "📄" }, "id": { + "description": "The id of the object", "type": "string", "example": "bafyreie6n5l5nkbjal37su54cha4coy7qzuhrnajluzv5qd5jvtsrxkequ" }, "layout": { + "description": "The layout of the object", "type": "string", "example": "basic" }, "name": { + "description": "The name of the object", "type": "string", - "example": "Object Name" + "example": "My object" }, "root_id": { + "description": "The id of the object's root", "type": "string", "example": "bafyreicypzj6uvu54664ucv3hmbsd5cmdy2dv4fwua26sciq74khzpyn4u" }, "snippet": { + "description": "The snippet of the object, especially important for notes as they don't have a name", "type": "string", "example": "The beginning of the object body..." }, "space_id": { + "description": "The id of the space the object is in", "type": "string", "example": "bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1" }, "type": { + "description": "The type of the object", "type": "string", "example": "Page" } @@ -1160,7 +1330,12 @@ const docTemplate = `{ "type": "object", "properties": { "object": { - "$ref": "#/definitions/object.Object" + "description": "The object", + "allOf": [ + { + "$ref": "#/definitions/object.Object" + } + ] } } }, @@ -1168,18 +1343,22 @@ const docTemplate = `{ "type": "object", "properties": { "icon": { + "description": "The icon of the template", "type": "string", "example": "📄" }, "id": { + "description": "The id of the template", "type": "string", "example": "bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge" }, "name": { + "description": "The name of the template", "type": "string", - "example": "Template Name" + "example": "My template" }, "type": { + "description": "The type of the object", "type": "string", "example": "template" } @@ -1189,7 +1368,12 @@ const docTemplate = `{ "type": "object", "properties": { "template": { - "$ref": "#/definitions/object.Template" + "description": "The template", + "allOf": [ + { + "$ref": "#/definitions/object.Template" + } + ] } } }, @@ -1197,18 +1381,22 @@ const docTemplate = `{ "type": "object", "properties": { "checked": { + "description": "Whether the text is checked", "type": "boolean", "example": true }, "color": { + "description": "The color of the text", "type": "string", "example": "red" }, "icon": { + "description": "The icon of the text", "type": "string", "example": "📄" }, "style": { + "description": "The style of the text", "type": "string", "enum": [ "Paragraph", @@ -1229,8 +1417,9 @@ const docTemplate = `{ "example": "Paragraph" }, "text": { + "description": "The text", "type": "string", - "example": "Some text" + "example": "Some text..." } } }, @@ -1238,26 +1427,32 @@ const docTemplate = `{ "type": "object", "properties": { "icon": { + "description": "The icon of the type", "type": "string", "example": "📄" }, "id": { + "description": "The id of the type", "type": "string", "example": "bafyreigyb6l5szohs32ts26ku2j42yd65e6hqy2u3gtzgdwqv6hzftsetu" }, "name": { + "description": "The name of the type", "type": "string", "example": "Page" }, "recommended_layout": { + "description": "The recommended layout of the type", "type": "string", "example": "todo" }, "type": { + "description": "The type of the object", "type": "string", "example": "type" }, "unique_key": { + "description": "The unique key of the type", "type": "string", "example": "ot-page" } @@ -1267,7 +1462,12 @@ const docTemplate = `{ "type": "object", "properties": { "type": { - "$ref": "#/definitions/object.Type" + "description": "The type", + "allOf": [ + { + "$ref": "#/definitions/object.Type" + } + ] } } }, @@ -1275,13 +1475,19 @@ const docTemplate = `{ "type": "object", "properties": { "data": { + "description": "The list of items in the current result set", "type": "array", "items": { "$ref": "#/definitions/object.Object" } }, "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" + "description": "The pagination metadata for the response", + "allOf": [ + { + "$ref": "#/definitions/pagination.PaginationMeta" + } + ] } } }, @@ -1289,13 +1495,19 @@ const docTemplate = `{ "type": "object", "properties": { "data": { + "description": "The list of items in the current result set", "type": "array", "items": { "$ref": "#/definitions/object.Template" } }, "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" + "description": "The pagination metadata for the response", + "allOf": [ + { + "$ref": "#/definitions/pagination.PaginationMeta" + } + ] } } }, @@ -1303,13 +1515,19 @@ const docTemplate = `{ "type": "object", "properties": { "data": { + "description": "The list of items in the current result set", "type": "array", "items": { "$ref": "#/definitions/object.Type" } }, "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" + "description": "The pagination metadata for the response", + "allOf": [ + { + "$ref": "#/definitions/pagination.PaginationMeta" + } + ] } } }, @@ -1317,13 +1535,19 @@ const docTemplate = `{ "type": "object", "properties": { "data": { + "description": "The list of items in the current result set", "type": "array", "items": { "$ref": "#/definitions/space.Member" } }, "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" + "description": "The pagination metadata for the response", + "allOf": [ + { + "$ref": "#/definitions/pagination.PaginationMeta" + } + ] } } }, @@ -1331,13 +1555,19 @@ const docTemplate = `{ "type": "object", "properties": { "data": { + "description": "The list of items in the current result set", "type": "array", "items": { "$ref": "#/definitions/space.Space" } }, "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" + "description": "The pagination metadata for the response", + "allOf": [ + { + "$ref": "#/definitions/pagination.PaginationMeta" + } + ] } } }, @@ -1345,22 +1575,22 @@ const docTemplate = `{ "type": "object", "properties": { "has_more": { - "description": "whether there are more items available", + "description": "Indicates if there are more items available beyond the current result set", "type": "boolean", "example": true }, "limit": { - "description": "the current limit", + "description": "The maximum number of items returned in the result set", "type": "integer", "example": 100 }, "offset": { - "description": "the current offset", + "description": "The number of items skipped before starting to collect the result set", "type": "integer", "example": 0 }, "total": { - "description": "the total number of items available on that endpoint", + "description": "The total number of items available for the endpoint", "type": "integer", "example": 1024 } @@ -1370,16 +1600,30 @@ const docTemplate = `{ "type": "object", "properties": { "query": { - "type": "string" + "description": "The search term to look for in object names and snippets", + "type": "string", + "example": "test" }, "sort": { - "$ref": "#/definitions/search.SortOptions" + "description": "The sorting criteria and direction for the search results", + "allOf": [ + { + "$ref": "#/definitions/search.SortOptions" + } + ] }, "types": { + "description": "The types of objects to search for, specified by unique key or ID", "type": "array", "items": { "type": "string" - } + }, + "example": [ + "ot-note", + "ot-page", + "ot-678043f0cda9133be777049f", + "bafyreightzrdts2ymxyaeyzspwdfo2juspyam76ewq6qq7ixnw3523gs7q" + ] } } }, @@ -1387,6 +1631,7 @@ const docTemplate = `{ "type": "object", "properties": { "direction": { + "description": "The direction to sort the search results", "type": "string", "default": "desc", "enum": [ @@ -1395,6 +1640,7 @@ const docTemplate = `{ ] }, "timestamp": { + "description": "The timestamp to sort the search results by", "type": "string", "default": "last_modified_date", "enum": [ @@ -1409,43 +1655,42 @@ const docTemplate = `{ "type": "object", "properties": { "name": { + "description": "The name of the space", "type": "string", "example": "New Space" } } }, - "space.CreateSpaceResponse": { - "type": "object", - "properties": { - "space": { - "$ref": "#/definitions/space.Space" - } - } - }, "space.Member": { "type": "object", "properties": { "global_name": { + "description": "The global name of the member in the network", "type": "string", "example": "john.any" }, "icon": { + "description": "The icon of the member", "type": "string", "example": "http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay?width=100" }, "id": { + "description": "The profile object id of the member", "type": "string", "example": "_participant_bafyreigyfkt6rbv24sbv5aq2hko1bhmv5xxlf22b4bypdu6j7hnphm3psq_23me69r569oi1_AAjEaEwPF4nkEh9AWkqEnzcQ8HziBB4ETjiTpvRCQvWnSMDZ" }, "identity": { + "description": "The identity of the member in the network", "type": "string", "example": "AAjEaEwPF4nkEh7AWkqEnzcQ8HziGB4ETjiTpvRCQvWnSMDZ" }, "name": { + "description": "The name of the member", "type": "string", "example": "John Doe" }, "role": { + "description": "The role of the member", "type": "string", "enum": [ "Reader", @@ -1456,92 +1701,138 @@ const docTemplate = `{ "example": "Owner" }, "type": { + "description": "The type of the object", "type": "string", "example": "member" } } }, + "space.MemberResponse": { + "type": "object", + "properties": { + "member": { + "description": "The member", + "allOf": [ + { + "$ref": "#/definitions/space.Member" + } + ] + } + } + }, "space.Space": { "type": "object", "properties": { "account_space_id": { + "description": "The id of the account space", "type": "string", "example": "bafyreihpd2knon5wbljhtfeg3fcqtg3i2pomhhnigui6lrjmzcjzep7gcy.23me69r569oi1" }, "analytics_id": { + "description": "The analytics id of the account", "type": "string", "example": "624aecdd-4797-4611-9d61-a2ae5f53cf1c" }, "archive_object_id": { + "description": "The id of the archive object", "type": "string", "example": "bafyreialsgoyflf3etjm3parzurivyaukzivwortf32b4twnlwpwocsrri" }, "device_id": { + "description": "The id of the device", "type": "string", "example": "12D3KooWGZMJ4kQVyQVXaj7gJPZr3RZ2nvd9M2Eq2pprEoPih9WF" }, "gateway_url": { + "description": "The gateway url to serve files and media", "type": "string", "example": "http://127.0.0.1:31006" }, "home_object_id": { + "description": "The id of the home object", "type": "string", "example": "bafyreie4qcl3wczb4cw5hrfyycikhjyh6oljdis3ewqrk5boaav3sbwqya" }, "icon": { + "description": "The icon of the space", "type": "string", "example": "http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay" }, "id": { + "description": "The id of the space", "type": "string", "example": "bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1" }, "local_storage_path": { + "description": "The local storage path of the account", "type": "string", "example": "/Users/johndoe/Library/Application Support/Anytype/data/AAHTtt1wuQEnaYBNZ2Cyfcvs6DqPqxgn8VXDVk4avsUkMuha" }, "marketplace_workspace_id": { + "description": "The id of the marketplace workspace", "type": "string", "example": "_anytype_marketplace" }, "name": { + "description": "The name of the space", "type": "string", - "example": "Space Name" + "example": "My Space" }, "network_id": { + "description": "The network id of the space", "type": "string", "example": "N83gJpVd9MuNRZAuJLZ7LiMntTThhPc6DtzWWVjb1M3PouVU" }, "profile_object_id": { + "description": "The id of the profile object", "type": "string", "example": "bafyreiaxhwreshjqwndpwtdsu4mtihaqhhmlygqnyqpfyfwlqfq3rm3gw4" }, "space_view_id": { + "description": "The id of the space view", "type": "string", "example": "bafyreigzv3vq7qwlrsin6njoduq727ssnhwd6bgyfj6nm4hv3pxoc2rxhy" }, "tech_space_id": { + "description": "The id of tech space, where objects outside of user's actual spaces are stored, e.g. spaces itself", "type": "string", "example": "bafyreif4xuwncrjl6jajt4zrrfnylpki476nv2w64yf42ovt7gia7oypii.23me69r569oi1" }, "timezone": { + "description": "The timezone of the account", "type": "string", "example": "" }, "type": { + "description": "The type of the object", "type": "string", "example": "space" }, "widgets_id": { + "description": "The id of the widgets", "type": "string", "example": "bafyreialj7pceh53mifm5dixlho47ke4qjmsn2uh4wsjf7xq2pnlo5xfva" }, "workspace_object_id": { + "description": "The id of the workspace object", "type": "string", "example": "bafyreiapey2g6e6za4zfxvlgwdy4hbbfu676gmwrhnqvjbxvrchr7elr3y" } } }, + "space.SpaceResponse": { + "type": "object", + "properties": { + "space": { + "description": "The space", + "allOf": [ + { + "$ref": "#/definitions/space.Space" + } + ] + } + } + }, "util.ForbiddenError": { "type": "object", "properties": { @@ -1549,7 +1840,8 @@ const docTemplate = `{ "type": "object", "properties": { "message": { - "type": "string" + "type": "string", + "example": "Forbidden" } } } @@ -1562,7 +1854,22 @@ const docTemplate = `{ "type": "object", "properties": { "message": { - "type": "string" + "type": "string", + "example": "Resource not found" + } + } + } + } + }, + "util.RateLimitError": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Rate limit exceeded" } } } @@ -1575,7 +1882,8 @@ const docTemplate = `{ "type": "object", "properties": { "message": { - "type": "string" + "type": "string", + "example": "Internal server error" } } } @@ -1588,7 +1896,8 @@ const docTemplate = `{ "type": "object", "properties": { "message": { - "type": "string" + "type": "string", + "example": "Unauthorized" } } } @@ -1601,7 +1910,8 @@ const docTemplate = `{ "type": "object", "properties": { "message": { - "type": "string" + "type": "string", + "example": "Bad request" } } } diff --git a/core/api/docs/swagger.json b/core/api/docs/swagger.json index cf8fe16d36..7978d53c05 100644 --- a/core/api/docs/swagger.json +++ b/core/api/docs/swagger.json @@ -247,7 +247,7 @@ "200": { "description": "Space created successfully", "schema": { - "$ref": "#/definitions/space.CreateSpaceResponse" + "$ref": "#/definitions/space.SpaceResponse" } }, "400": { @@ -262,6 +262,61 @@ "$ref": "#/definitions/util.UnauthorizedError" } }, + "423": { + "description": "Rate limit exceeded", + "schema": { + "$ref": "#/definitions/util.RateLimitError" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/util.ServerError" + } + } + } + } + }, + "/spaces/{space_id}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "spaces" + ], + "summary": "Get space", + "parameters": [ + { + "type": "string", + "description": "Space ID", + "name": "space_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Space", + "schema": { + "$ref": "#/definitions/space.SpaceResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.UnauthorizedError" + } + }, + "404": { + "description": "Space not found", + "schema": { + "$ref": "#/definitions/util.NotFoundError" + } + }, "500": { "description": "Internal server error", "schema": { @@ -329,6 +384,62 @@ } } }, + "/spaces/{space_id}/members/{member_id}": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "spaces" + ], + "summary": "Get member", + "parameters": [ + { + "type": "string", + "description": "Space ID", + "name": "space_id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Member ID", + "name": "member_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Member", + "schema": { + "$ref": "#/definitions/space.MemberResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/util.UnauthorizedError" + } + }, + "404": { + "description": "Member not found", + "schema": { + "$ref": "#/definitions/util.NotFoundError" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/util.ServerError" + } + } + } + } + }, "/spaces/{space_id}/objects": { "get": { "consumes": [ @@ -434,6 +545,12 @@ "$ref": "#/definitions/util.UnauthorizedError" } }, + "423": { + "description": "Rate limit exceeded", + "schema": { + "$ref": "#/definitions/util.RateLimitError" + } + }, "500": { "description": "Internal server error", "schema": { @@ -550,6 +667,12 @@ "$ref": "#/definitions/util.NotFoundError" } }, + "423": { + "description": "Rate limit exceeded", + "schema": { + "$ref": "#/definitions/util.RateLimitError" + } + }, "500": { "description": "Internal server error", "schema": { @@ -941,6 +1064,7 @@ "type": "object", "properties": { "challenge_id": { + "description": "The challenge id associated with the displayed code and needed to solve the challenge for token", "type": "string", "example": "67647f5ecda913e9a2e11b26" } @@ -950,10 +1074,12 @@ "type": "object", "properties": { "app_key": { + "description": "The permanent app key", "type": "string", "example": "zhSG/zQRmgADyilWPtgdnfo1qD60oK02/SVgi1GaFt6=" }, "session_token": { + "description": "The ephemeral session token", "type": "string", "example": "eyJhbGciOeJIRzI1NiIsInR5cCI6IkpXVCJ1.eyJzZWVkIjaiY0dmVndlUnAifQ.Y1EZecYnwmvMkrXKOa2XJnAbaRt34urBabe06tmDQII" } @@ -963,6 +1089,7 @@ "type": "object", "properties": { "path": { + "description": "The path the object was exported to", "type": "string", "example": "/path/to/export" } @@ -972,6 +1099,7 @@ "type": "object", "properties": { "align": { + "description": "The alignment of the block", "type": "string", "enum": [ "AlignLeft", @@ -982,10 +1110,12 @@ "example": "AlignLeft" }, "background_color": { + "description": "The background color of the block", "type": "string", "example": "red" }, "children_ids": { + "description": "The ids of the block's children", "type": "array", "items": { "type": "string" @@ -995,16 +1125,28 @@ ] }, "file": { - "$ref": "#/definitions/object.File" + "description": "The file of the block, if applicable", + "allOf": [ + { + "$ref": "#/definitions/object.File" + } + ] }, "id": { + "description": "The id of the block", "type": "string", "example": "64394517de52ad5acb89c66c" }, "text": { - "$ref": "#/definitions/object.Text" + "description": "The text of the block, if applicable", + "allOf": [ + { + "$ref": "#/definitions/object.Text" + } + ] }, "vertical_align": { + "description": "The vertical alignment of the block", "type": "string", "enum": [ "VerticalAlignTop", @@ -1019,30 +1161,37 @@ "type": "object", "properties": { "body": { + "description": "The body of the object", "type": "string", - "example": "Object Body" + "example": "This is the body of the object. Markdown syntax is supported here." }, "description": { + "description": "The description of the object", "type": "string", - "example": "Object Description" + "example": "This is a description of the object." }, "icon": { + "description": "The icon of the object", "type": "string", "example": "📄" }, "name": { + "description": "The name of the object", "type": "string", - "example": "Object Name" + "example": "My object" }, "object_type_unique_key": { + "description": "The unique key of the object type", "type": "string", "example": "ot-page" }, "source": { + "description": "The source url, only applicable for bookmarks", "type": "string", - "example": "https://source.com" + "example": "https://bookmark-source.com" }, "template_id": { + "description": "The id of the template to use", "type": "string", "example": "bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge" } @@ -1052,10 +1201,12 @@ "type": "object", "properties": { "details": { + "description": "The details", "type": "object", "additionalProperties": true }, "id": { + "description": "The id of the detail", "type": "string", "enum": [ "last_modified_date", @@ -1073,30 +1224,39 @@ "type": "object", "properties": { "added_at": { + "description": "The added at of the file", "type": "integer" }, "hash": { + "description": "The hash of the file", "type": "string" }, "mime": { + "description": "The mime of the file", "type": "string" }, "name": { + "description": "The name of the file", "type": "string" }, "size": { + "description": "The size of the file", "type": "integer" }, "state": { + "description": "The state of the file", "type": "string" }, "style": { + "description": "The style of the file", "type": "string" }, "target_object_id": { + "description": "The target object id of the file", "type": "string" }, "type": { + "description": "The type of the file", "type": "string" } } @@ -1105,46 +1265,56 @@ "type": "object", "properties": { "blocks": { + "description": "The blocks of the object", "type": "array", "items": { "$ref": "#/definitions/object.Block" } }, "details": { + "description": "The details of the object", "type": "array", "items": { "$ref": "#/definitions/object.Detail" } }, "icon": { + "description": "The icon of the object", "type": "string", "example": "📄" }, "id": { + "description": "The id of the object", "type": "string", "example": "bafyreie6n5l5nkbjal37su54cha4coy7qzuhrnajluzv5qd5jvtsrxkequ" }, "layout": { + "description": "The layout of the object", "type": "string", "example": "basic" }, "name": { + "description": "The name of the object", "type": "string", - "example": "Object Name" + "example": "My object" }, "root_id": { + "description": "The id of the object's root", "type": "string", "example": "bafyreicypzj6uvu54664ucv3hmbsd5cmdy2dv4fwua26sciq74khzpyn4u" }, "snippet": { + "description": "The snippet of the object, especially important for notes as they don't have a name", "type": "string", "example": "The beginning of the object body..." }, "space_id": { + "description": "The id of the space the object is in", "type": "string", "example": "bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1" }, "type": { + "description": "The type of the object", "type": "string", "example": "Page" } @@ -1154,7 +1324,12 @@ "type": "object", "properties": { "object": { - "$ref": "#/definitions/object.Object" + "description": "The object", + "allOf": [ + { + "$ref": "#/definitions/object.Object" + } + ] } } }, @@ -1162,18 +1337,22 @@ "type": "object", "properties": { "icon": { + "description": "The icon of the template", "type": "string", "example": "📄" }, "id": { + "description": "The id of the template", "type": "string", "example": "bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge" }, "name": { + "description": "The name of the template", "type": "string", - "example": "Template Name" + "example": "My template" }, "type": { + "description": "The type of the object", "type": "string", "example": "template" } @@ -1183,7 +1362,12 @@ "type": "object", "properties": { "template": { - "$ref": "#/definitions/object.Template" + "description": "The template", + "allOf": [ + { + "$ref": "#/definitions/object.Template" + } + ] } } }, @@ -1191,18 +1375,22 @@ "type": "object", "properties": { "checked": { + "description": "Whether the text is checked", "type": "boolean", "example": true }, "color": { + "description": "The color of the text", "type": "string", "example": "red" }, "icon": { + "description": "The icon of the text", "type": "string", "example": "📄" }, "style": { + "description": "The style of the text", "type": "string", "enum": [ "Paragraph", @@ -1223,8 +1411,9 @@ "example": "Paragraph" }, "text": { + "description": "The text", "type": "string", - "example": "Some text" + "example": "Some text..." } } }, @@ -1232,26 +1421,32 @@ "type": "object", "properties": { "icon": { + "description": "The icon of the type", "type": "string", "example": "📄" }, "id": { + "description": "The id of the type", "type": "string", "example": "bafyreigyb6l5szohs32ts26ku2j42yd65e6hqy2u3gtzgdwqv6hzftsetu" }, "name": { + "description": "The name of the type", "type": "string", "example": "Page" }, "recommended_layout": { + "description": "The recommended layout of the type", "type": "string", "example": "todo" }, "type": { + "description": "The type of the object", "type": "string", "example": "type" }, "unique_key": { + "description": "The unique key of the type", "type": "string", "example": "ot-page" } @@ -1261,7 +1456,12 @@ "type": "object", "properties": { "type": { - "$ref": "#/definitions/object.Type" + "description": "The type", + "allOf": [ + { + "$ref": "#/definitions/object.Type" + } + ] } } }, @@ -1269,13 +1469,19 @@ "type": "object", "properties": { "data": { + "description": "The list of items in the current result set", "type": "array", "items": { "$ref": "#/definitions/object.Object" } }, "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" + "description": "The pagination metadata for the response", + "allOf": [ + { + "$ref": "#/definitions/pagination.PaginationMeta" + } + ] } } }, @@ -1283,13 +1489,19 @@ "type": "object", "properties": { "data": { + "description": "The list of items in the current result set", "type": "array", "items": { "$ref": "#/definitions/object.Template" } }, "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" + "description": "The pagination metadata for the response", + "allOf": [ + { + "$ref": "#/definitions/pagination.PaginationMeta" + } + ] } } }, @@ -1297,13 +1509,19 @@ "type": "object", "properties": { "data": { + "description": "The list of items in the current result set", "type": "array", "items": { "$ref": "#/definitions/object.Type" } }, "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" + "description": "The pagination metadata for the response", + "allOf": [ + { + "$ref": "#/definitions/pagination.PaginationMeta" + } + ] } } }, @@ -1311,13 +1529,19 @@ "type": "object", "properties": { "data": { + "description": "The list of items in the current result set", "type": "array", "items": { "$ref": "#/definitions/space.Member" } }, "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" + "description": "The pagination metadata for the response", + "allOf": [ + { + "$ref": "#/definitions/pagination.PaginationMeta" + } + ] } } }, @@ -1325,13 +1549,19 @@ "type": "object", "properties": { "data": { + "description": "The list of items in the current result set", "type": "array", "items": { "$ref": "#/definitions/space.Space" } }, "pagination": { - "$ref": "#/definitions/pagination.PaginationMeta" + "description": "The pagination metadata for the response", + "allOf": [ + { + "$ref": "#/definitions/pagination.PaginationMeta" + } + ] } } }, @@ -1339,22 +1569,22 @@ "type": "object", "properties": { "has_more": { - "description": "whether there are more items available", + "description": "Indicates if there are more items available beyond the current result set", "type": "boolean", "example": true }, "limit": { - "description": "the current limit", + "description": "The maximum number of items returned in the result set", "type": "integer", "example": 100 }, "offset": { - "description": "the current offset", + "description": "The number of items skipped before starting to collect the result set", "type": "integer", "example": 0 }, "total": { - "description": "the total number of items available on that endpoint", + "description": "The total number of items available for the endpoint", "type": "integer", "example": 1024 } @@ -1364,16 +1594,30 @@ "type": "object", "properties": { "query": { - "type": "string" + "description": "The search term to look for in object names and snippets", + "type": "string", + "example": "test" }, "sort": { - "$ref": "#/definitions/search.SortOptions" + "description": "The sorting criteria and direction for the search results", + "allOf": [ + { + "$ref": "#/definitions/search.SortOptions" + } + ] }, "types": { + "description": "The types of objects to search for, specified by unique key or ID", "type": "array", "items": { "type": "string" - } + }, + "example": [ + "ot-note", + "ot-page", + "ot-678043f0cda9133be777049f", + "bafyreightzrdts2ymxyaeyzspwdfo2juspyam76ewq6qq7ixnw3523gs7q" + ] } } }, @@ -1381,6 +1625,7 @@ "type": "object", "properties": { "direction": { + "description": "The direction to sort the search results", "type": "string", "default": "desc", "enum": [ @@ -1389,6 +1634,7 @@ ] }, "timestamp": { + "description": "The timestamp to sort the search results by", "type": "string", "default": "last_modified_date", "enum": [ @@ -1403,43 +1649,42 @@ "type": "object", "properties": { "name": { + "description": "The name of the space", "type": "string", "example": "New Space" } } }, - "space.CreateSpaceResponse": { - "type": "object", - "properties": { - "space": { - "$ref": "#/definitions/space.Space" - } - } - }, "space.Member": { "type": "object", "properties": { "global_name": { + "description": "The global name of the member in the network", "type": "string", "example": "john.any" }, "icon": { + "description": "The icon of the member", "type": "string", "example": "http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay?width=100" }, "id": { + "description": "The profile object id of the member", "type": "string", "example": "_participant_bafyreigyfkt6rbv24sbv5aq2hko1bhmv5xxlf22b4bypdu6j7hnphm3psq_23me69r569oi1_AAjEaEwPF4nkEh9AWkqEnzcQ8HziBB4ETjiTpvRCQvWnSMDZ" }, "identity": { + "description": "The identity of the member in the network", "type": "string", "example": "AAjEaEwPF4nkEh7AWkqEnzcQ8HziGB4ETjiTpvRCQvWnSMDZ" }, "name": { + "description": "The name of the member", "type": "string", "example": "John Doe" }, "role": { + "description": "The role of the member", "type": "string", "enum": [ "Reader", @@ -1450,92 +1695,138 @@ "example": "Owner" }, "type": { + "description": "The type of the object", "type": "string", "example": "member" } } }, + "space.MemberResponse": { + "type": "object", + "properties": { + "member": { + "description": "The member", + "allOf": [ + { + "$ref": "#/definitions/space.Member" + } + ] + } + } + }, "space.Space": { "type": "object", "properties": { "account_space_id": { + "description": "The id of the account space", "type": "string", "example": "bafyreihpd2knon5wbljhtfeg3fcqtg3i2pomhhnigui6lrjmzcjzep7gcy.23me69r569oi1" }, "analytics_id": { + "description": "The analytics id of the account", "type": "string", "example": "624aecdd-4797-4611-9d61-a2ae5f53cf1c" }, "archive_object_id": { + "description": "The id of the archive object", "type": "string", "example": "bafyreialsgoyflf3etjm3parzurivyaukzivwortf32b4twnlwpwocsrri" }, "device_id": { + "description": "The id of the device", "type": "string", "example": "12D3KooWGZMJ4kQVyQVXaj7gJPZr3RZ2nvd9M2Eq2pprEoPih9WF" }, "gateway_url": { + "description": "The gateway url to serve files and media", "type": "string", "example": "http://127.0.0.1:31006" }, "home_object_id": { + "description": "The id of the home object", "type": "string", "example": "bafyreie4qcl3wczb4cw5hrfyycikhjyh6oljdis3ewqrk5boaav3sbwqya" }, "icon": { + "description": "The icon of the space", "type": "string", "example": "http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay" }, "id": { + "description": "The id of the space", "type": "string", "example": "bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1" }, "local_storage_path": { + "description": "The local storage path of the account", "type": "string", "example": "/Users/johndoe/Library/Application Support/Anytype/data/AAHTtt1wuQEnaYBNZ2Cyfcvs6DqPqxgn8VXDVk4avsUkMuha" }, "marketplace_workspace_id": { + "description": "The id of the marketplace workspace", "type": "string", "example": "_anytype_marketplace" }, "name": { + "description": "The name of the space", "type": "string", - "example": "Space Name" + "example": "My Space" }, "network_id": { + "description": "The network id of the space", "type": "string", "example": "N83gJpVd9MuNRZAuJLZ7LiMntTThhPc6DtzWWVjb1M3PouVU" }, "profile_object_id": { + "description": "The id of the profile object", "type": "string", "example": "bafyreiaxhwreshjqwndpwtdsu4mtihaqhhmlygqnyqpfyfwlqfq3rm3gw4" }, "space_view_id": { + "description": "The id of the space view", "type": "string", "example": "bafyreigzv3vq7qwlrsin6njoduq727ssnhwd6bgyfj6nm4hv3pxoc2rxhy" }, "tech_space_id": { + "description": "The id of tech space, where objects outside of user's actual spaces are stored, e.g. spaces itself", "type": "string", "example": "bafyreif4xuwncrjl6jajt4zrrfnylpki476nv2w64yf42ovt7gia7oypii.23me69r569oi1" }, "timezone": { + "description": "The timezone of the account", "type": "string", "example": "" }, "type": { + "description": "The type of the object", "type": "string", "example": "space" }, "widgets_id": { + "description": "The id of the widgets", "type": "string", "example": "bafyreialj7pceh53mifm5dixlho47ke4qjmsn2uh4wsjf7xq2pnlo5xfva" }, "workspace_object_id": { + "description": "The id of the workspace object", "type": "string", "example": "bafyreiapey2g6e6za4zfxvlgwdy4hbbfu676gmwrhnqvjbxvrchr7elr3y" } } }, + "space.SpaceResponse": { + "type": "object", + "properties": { + "space": { + "description": "The space", + "allOf": [ + { + "$ref": "#/definitions/space.Space" + } + ] + } + } + }, "util.ForbiddenError": { "type": "object", "properties": { @@ -1543,7 +1834,8 @@ "type": "object", "properties": { "message": { - "type": "string" + "type": "string", + "example": "Forbidden" } } } @@ -1556,7 +1848,22 @@ "type": "object", "properties": { "message": { - "type": "string" + "type": "string", + "example": "Resource not found" + } + } + } + } + }, + "util.RateLimitError": { + "type": "object", + "properties": { + "error": { + "type": "object", + "properties": { + "message": { + "type": "string", + "example": "Rate limit exceeded" } } } @@ -1569,7 +1876,8 @@ "type": "object", "properties": { "message": { - "type": "string" + "type": "string", + "example": "Internal server error" } } } @@ -1582,7 +1890,8 @@ "type": "object", "properties": { "message": { - "type": "string" + "type": "string", + "example": "Unauthorized" } } } @@ -1595,7 +1904,8 @@ "type": "object", "properties": { "message": { - "type": "string" + "type": "string", + "example": "Bad request" } } } diff --git a/core/api/docs/swagger.yaml b/core/api/docs/swagger.yaml index 550a59d1c6..1f1b9a7690 100644 --- a/core/api/docs/swagger.yaml +++ b/core/api/docs/swagger.yaml @@ -3,27 +3,33 @@ definitions: auth.DisplayCodeResponse: properties: challenge_id: + description: The challenge id associated with the displayed code and needed + to solve the challenge for token example: 67647f5ecda913e9a2e11b26 type: string type: object auth.TokenResponse: properties: app_key: + description: The permanent app key example: zhSG/zQRmgADyilWPtgdnfo1qD60oK02/SVgi1GaFt6= type: string session_token: + description: The ephemeral session token example: eyJhbGciOeJIRzI1NiIsInR5cCI6IkpXVCJ1.eyJzZWVkIjaiY0dmVndlUnAifQ.Y1EZecYnwmvMkrXKOa2XJnAbaRt34urBabe06tmDQII type: string type: object export.ObjectExportResponse: properties: path: + description: The path the object was exported to example: /path/to/export type: string type: object object.Block: properties: align: + description: The alignment of the block enum: - AlignLeft - AlignCenter @@ -32,22 +38,30 @@ definitions: example: AlignLeft type: string background_color: + description: The background color of the block example: red type: string children_ids: + description: The ids of the block's children example: - '[''6797ce8ecda913cde14b02dc'']' items: type: string type: array file: - $ref: '#/definitions/object.File' + allOf: + - $ref: '#/definitions/object.File' + description: The file of the block, if applicable id: + description: The id of the block example: 64394517de52ad5acb89c66c type: string text: - $ref: '#/definitions/object.Text' + allOf: + - $ref: '#/definitions/object.Text' + description: The text of the block, if applicable vertical_align: + description: The vertical alignment of the block enum: - VerticalAlignTop - VerticalAlignMiddle @@ -58,24 +72,31 @@ definitions: object.CreateObjectRequest: properties: body: - example: Object Body + description: The body of the object + example: This is the body of the object. Markdown syntax is supported here. type: string description: - example: Object Description + description: The description of the object + example: This is a description of the object. type: string icon: + description: The icon of the object example: "\U0001F4C4" type: string name: - example: Object Name + description: The name of the object + example: My object type: string object_type_unique_key: + description: The unique key of the object type example: ot-page type: string source: - example: https://source.com + description: The source url, only applicable for bookmarks + example: https://bookmark-source.com type: string template_id: + description: The id of the template to use example: bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge type: string type: object @@ -83,8 +104,10 @@ definitions: properties: details: additionalProperties: true + description: The details type: object id: + description: The id of the detail enum: - last_modified_date - last_modified_by @@ -98,96 +121,128 @@ definitions: object.File: properties: added_at: + description: The added at of the file type: integer hash: + description: The hash of the file type: string mime: + description: The mime of the file type: string name: + description: The name of the file type: string size: + description: The size of the file type: integer state: + description: The state of the file type: string style: + description: The style of the file type: string target_object_id: + description: The target object id of the file type: string type: + description: The type of the file type: string type: object object.Object: properties: blocks: + description: The blocks of the object items: $ref: '#/definitions/object.Block' type: array details: + description: The details of the object items: $ref: '#/definitions/object.Detail' type: array icon: + description: The icon of the object example: "\U0001F4C4" type: string id: + description: The id of the object example: bafyreie6n5l5nkbjal37su54cha4coy7qzuhrnajluzv5qd5jvtsrxkequ type: string layout: + description: The layout of the object example: basic type: string name: - example: Object Name + description: The name of the object + example: My object type: string root_id: + description: The id of the object's root example: bafyreicypzj6uvu54664ucv3hmbsd5cmdy2dv4fwua26sciq74khzpyn4u type: string snippet: + description: The snippet of the object, especially important for notes as + they don't have a name example: The beginning of the object body... type: string space_id: + description: The id of the space the object is in example: bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1 type: string type: + description: The type of the object example: Page type: string type: object object.ObjectResponse: properties: object: - $ref: '#/definitions/object.Object' + allOf: + - $ref: '#/definitions/object.Object' + description: The object type: object object.Template: properties: icon: + description: The icon of the template example: "\U0001F4C4" type: string id: + description: The id of the template example: bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge type: string name: - example: Template Name + description: The name of the template + example: My template type: string type: + description: The type of the object example: template type: string type: object object.TemplateResponse: properties: template: - $ref: '#/definitions/object.Template' + allOf: + - $ref: '#/definitions/object.Template' + description: The template type: object object.Text: properties: checked: + description: Whether the text is checked example: true type: boolean color: + description: The color of the text example: red type: string icon: + description: The icon of the text example: "\U0001F4C4" type: string style: + description: The style of the text enum: - Paragraph - Header1 @@ -206,106 +261,143 @@ definitions: example: Paragraph type: string text: - example: Some text + description: The text + example: Some text... type: string type: object object.Type: properties: icon: + description: The icon of the type example: "\U0001F4C4" type: string id: + description: The id of the type example: bafyreigyb6l5szohs32ts26ku2j42yd65e6hqy2u3gtzgdwqv6hzftsetu type: string name: + description: The name of the type example: Page type: string recommended_layout: + description: The recommended layout of the type example: todo type: string type: + description: The type of the object example: type type: string unique_key: + description: The unique key of the type example: ot-page type: string type: object object.TypeResponse: properties: type: - $ref: '#/definitions/object.Type' + allOf: + - $ref: '#/definitions/object.Type' + description: The type type: object pagination.PaginatedResponse-object_Object: properties: data: + description: The list of items in the current result set items: $ref: '#/definitions/object.Object' type: array pagination: - $ref: '#/definitions/pagination.PaginationMeta' + allOf: + - $ref: '#/definitions/pagination.PaginationMeta' + description: The pagination metadata for the response type: object pagination.PaginatedResponse-object_Template: properties: data: + description: The list of items in the current result set items: $ref: '#/definitions/object.Template' type: array pagination: - $ref: '#/definitions/pagination.PaginationMeta' + allOf: + - $ref: '#/definitions/pagination.PaginationMeta' + description: The pagination metadata for the response type: object pagination.PaginatedResponse-object_Type: properties: data: + description: The list of items in the current result set items: $ref: '#/definitions/object.Type' type: array pagination: - $ref: '#/definitions/pagination.PaginationMeta' + allOf: + - $ref: '#/definitions/pagination.PaginationMeta' + description: The pagination metadata for the response type: object pagination.PaginatedResponse-space_Member: properties: data: + description: The list of items in the current result set items: $ref: '#/definitions/space.Member' type: array pagination: - $ref: '#/definitions/pagination.PaginationMeta' + allOf: + - $ref: '#/definitions/pagination.PaginationMeta' + description: The pagination metadata for the response type: object pagination.PaginatedResponse-space_Space: properties: data: + description: The list of items in the current result set items: $ref: '#/definitions/space.Space' type: array pagination: - $ref: '#/definitions/pagination.PaginationMeta' + allOf: + - $ref: '#/definitions/pagination.PaginationMeta' + description: The pagination metadata for the response type: object pagination.PaginationMeta: properties: has_more: - description: whether there are more items available + description: Indicates if there are more items available beyond the current + result set example: true type: boolean limit: - description: the current limit + description: The maximum number of items returned in the result set example: 100 type: integer offset: - description: the current offset + description: The number of items skipped before starting to collect the result + set example: 0 type: integer total: - description: the total number of items available on that endpoint + description: The total number of items available for the endpoint example: 1024 type: integer type: object search.SearchRequest: properties: query: + description: The search term to look for in object names and snippets + example: test type: string sort: - $ref: '#/definitions/search.SortOptions' + allOf: + - $ref: '#/definitions/search.SortOptions' + description: The sorting criteria and direction for the search results types: + description: The types of objects to search for, specified by unique key or + ID + example: + - ot-note + - ot-page + - ot-678043f0cda9133be777049f + - bafyreightzrdts2ymxyaeyzspwdfo2juspyam76ewq6qq7ixnw3523gs7q items: type: string type: array @@ -314,12 +406,14 @@ definitions: properties: direction: default: desc + description: The direction to sort the search results enum: - asc - desc type: string timestamp: default: last_modified_date + description: The timestamp to sort the search results by enum: - created_date - last_modified_date @@ -329,32 +423,34 @@ definitions: space.CreateSpaceRequest: properties: name: + description: The name of the space example: New Space type: string type: object - space.CreateSpaceResponse: - properties: - space: - $ref: '#/definitions/space.Space' - type: object space.Member: properties: global_name: + description: The global name of the member in the network example: john.any type: string icon: + description: The icon of the member example: http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay?width=100 type: string id: + description: The profile object id of the member example: _participant_bafyreigyfkt6rbv24sbv5aq2hko1bhmv5xxlf22b4bypdu6j7hnphm3psq_23me69r569oi1_AAjEaEwPF4nkEh9AWkqEnzcQ8HziBB4ETjiTpvRCQvWnSMDZ type: string identity: + description: The identity of the member in the network example: AAjEaEwPF4nkEh7AWkqEnzcQ8HziGB4ETjiTpvRCQvWnSMDZ type: string name: + description: The name of the member example: John Doe type: string role: + description: The role of the member enum: - Reader - Writer @@ -363,74 +459,110 @@ definitions: example: Owner type: string type: + description: The type of the object example: member type: string type: object + space.MemberResponse: + properties: + member: + allOf: + - $ref: '#/definitions/space.Member' + description: The member + type: object space.Space: properties: account_space_id: + description: The id of the account space example: bafyreihpd2knon5wbljhtfeg3fcqtg3i2pomhhnigui6lrjmzcjzep7gcy.23me69r569oi1 type: string analytics_id: + description: The analytics id of the account example: 624aecdd-4797-4611-9d61-a2ae5f53cf1c type: string archive_object_id: + description: The id of the archive object example: bafyreialsgoyflf3etjm3parzurivyaukzivwortf32b4twnlwpwocsrri type: string device_id: + description: The id of the device example: 12D3KooWGZMJ4kQVyQVXaj7gJPZr3RZ2nvd9M2Eq2pprEoPih9WF type: string gateway_url: + description: The gateway url to serve files and media example: http://127.0.0.1:31006 type: string home_object_id: + description: The id of the home object example: bafyreie4qcl3wczb4cw5hrfyycikhjyh6oljdis3ewqrk5boaav3sbwqya type: string icon: + description: The icon of the space example: http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay type: string id: + description: The id of the space example: bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1 type: string local_storage_path: + description: The local storage path of the account example: /Users/johndoe/Library/Application Support/Anytype/data/AAHTtt1wuQEnaYBNZ2Cyfcvs6DqPqxgn8VXDVk4avsUkMuha type: string marketplace_workspace_id: + description: The id of the marketplace workspace example: _anytype_marketplace type: string name: - example: Space Name + description: The name of the space + example: My Space type: string network_id: + description: The network id of the space example: N83gJpVd9MuNRZAuJLZ7LiMntTThhPc6DtzWWVjb1M3PouVU type: string profile_object_id: + description: The id of the profile object example: bafyreiaxhwreshjqwndpwtdsu4mtihaqhhmlygqnyqpfyfwlqfq3rm3gw4 type: string space_view_id: + description: The id of the space view example: bafyreigzv3vq7qwlrsin6njoduq727ssnhwd6bgyfj6nm4hv3pxoc2rxhy type: string tech_space_id: + description: The id of tech space, where objects outside of user's actual + spaces are stored, e.g. spaces itself example: bafyreif4xuwncrjl6jajt4zrrfnylpki476nv2w64yf42ovt7gia7oypii.23me69r569oi1 type: string timezone: + description: The timezone of the account example: "" type: string type: + description: The type of the object example: space type: string widgets_id: + description: The id of the widgets example: bafyreialj7pceh53mifm5dixlho47ke4qjmsn2uh4wsjf7xq2pnlo5xfva type: string workspace_object_id: + description: The id of the workspace object example: bafyreiapey2g6e6za4zfxvlgwdy4hbbfu676gmwrhnqvjbxvrchr7elr3y type: string type: object + space.SpaceResponse: + properties: + space: + allOf: + - $ref: '#/definitions/space.Space' + description: The space + type: object util.ForbiddenError: properties: error: properties: message: + example: Forbidden type: string type: object type: object @@ -439,6 +571,16 @@ definitions: error: properties: message: + example: Resource not found + type: string + type: object + type: object + util.RateLimitError: + properties: + error: + properties: + message: + example: Rate limit exceeded type: string type: object type: object @@ -447,6 +589,7 @@ definitions: error: properties: message: + example: Internal server error type: string type: object type: object @@ -455,6 +598,7 @@ definitions: error: properties: message: + example: Unauthorized type: string type: object type: object @@ -463,6 +607,7 @@ definitions: error: properties: message: + example: Bad request type: string type: object type: object @@ -637,7 +782,7 @@ paths: "200": description: Space created successfully schema: - $ref: '#/definitions/space.CreateSpaceResponse' + $ref: '#/definitions/space.SpaceResponse' "400": description: Bad request schema: @@ -646,6 +791,10 @@ paths: description: Unauthorized schema: $ref: '#/definitions/util.UnauthorizedError' + "423": + description: Rate limit exceeded + schema: + $ref: '#/definitions/util.RateLimitError' "500": description: Internal server error schema: @@ -653,6 +802,38 @@ paths: summary: Create space tags: - spaces + /spaces/{space_id}: + get: + consumes: + - application/json + parameters: + - description: Space ID + in: path + name: space_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: Space + schema: + $ref: '#/definitions/space.SpaceResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.UnauthorizedError' + "404": + description: Space not found + schema: + $ref: '#/definitions/util.NotFoundError' + "500": + description: Internal server error + schema: + $ref: '#/definitions/util.ServerError' + summary: Get space + tags: + - spaces /spaces/{space_id}/members: get: consumes: @@ -693,6 +874,43 @@ paths: summary: List members tags: - spaces + /spaces/{space_id}/members/{member_id}: + get: + consumes: + - application/json + parameters: + - description: Space ID + in: path + name: space_id + required: true + type: string + - description: Member ID + in: path + name: member_id + required: true + type: string + produces: + - application/json + responses: + "200": + description: Member + schema: + $ref: '#/definitions/space.MemberResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/util.UnauthorizedError' + "404": + description: Member not found + schema: + $ref: '#/definitions/util.NotFoundError' + "500": + description: Internal server error + schema: + $ref: '#/definitions/util.ServerError' + summary: Get member + tags: + - spaces /spaces/{space_id}/objects: get: consumes: @@ -763,6 +981,10 @@ paths: description: Unauthorized schema: $ref: '#/definitions/util.UnauthorizedError' + "423": + description: Rate limit exceeded + schema: + $ref: '#/definitions/util.RateLimitError' "500": description: Internal server error schema: @@ -804,6 +1026,10 @@ paths: description: Resource not found schema: $ref: '#/definitions/util.NotFoundError' + "423": + description: Rate limit exceeded + schema: + $ref: '#/definitions/util.RateLimitError' "500": description: Internal server error schema: diff --git a/core/api/internal/auth/model.go b/core/api/internal/auth/model.go index 2d34926f56..221797cd36 100644 --- a/core/api/internal/auth/model.go +++ b/core/api/internal/auth/model.go @@ -1,10 +1,10 @@ package auth type DisplayCodeResponse struct { - ChallengeId string `json:"challenge_id" example:"67647f5ecda913e9a2e11b26"` + ChallengeId string `json:"challenge_id" example:"67647f5ecda913e9a2e11b26"` // The challenge id associated with the displayed code and needed to solve the challenge for token } type TokenResponse struct { - SessionToken string `json:"session_token" example:"eyJhbGciOeJIRzI1NiIsInR5cCI6IkpXVCJ1.eyJzZWVkIjaiY0dmVndlUnAifQ.Y1EZecYnwmvMkrXKOa2XJnAbaRt34urBabe06tmDQII"` - AppKey string `json:"app_key" example:"zhSG/zQRmgADyilWPtgdnfo1qD60oK02/SVgi1GaFt6="` + SessionToken string `json:"session_token" example:"eyJhbGciOeJIRzI1NiIsInR5cCI6IkpXVCJ1.eyJzZWVkIjaiY0dmVndlUnAifQ.Y1EZecYnwmvMkrXKOa2XJnAbaRt34urBabe06tmDQII"` // The ephemeral session token + AppKey string `json:"app_key" example:"zhSG/zQRmgADyilWPtgdnfo1qD60oK02/SVgi1GaFt6="` // The permanent app key } diff --git a/core/api/internal/export/model.go b/core/api/internal/export/model.go index dde2422462..e6541b62bc 100644 --- a/core/api/internal/export/model.go +++ b/core/api/internal/export/model.go @@ -1,9 +1,9 @@ package export type ObjectExportRequest struct { - Path string `json:"path" example:"/path/to/export"` + Path string `json:"path" example:"/path/to/export"` // The path to export the object to } type ObjectExportResponse struct { - Path string `json:"path" example:"/path/to/export"` + Path string `json:"path" example:"/path/to/export"` // The path the object was exported to } diff --git a/core/api/internal/object/handler.go b/core/api/internal/object/handler.go index 671f255839..57c1e2e877 100644 --- a/core/api/internal/object/handler.go +++ b/core/api/internal/object/handler.go @@ -91,6 +91,7 @@ func GetObjectHandler(s *ObjectService) gin.HandlerFunc { // @Failure 401 {object} util.UnauthorizedError "Unauthorized" // @Failure 403 {object} util.ForbiddenError "Forbidden" // @Failure 404 {object} util.NotFoundError "Resource not found" +// @Failure 423 {object} util.RateLimitError "Rate limit exceeded" // @Failure 500 {object} util.ServerError "Internal server error" // @Router /spaces/{space_id}/objects/{object_id} [delete] func DeleteObjectHandler(s *ObjectService) gin.HandlerFunc { @@ -126,6 +127,7 @@ func DeleteObjectHandler(s *ObjectService) gin.HandlerFunc { // @Success 200 {object} ObjectResponse "The created object" // @Failure 400 {object} util.ValidationError "Bad request" // @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 423 {object} util.RateLimitError "Rate limit exceeded" // @Failure 500 {object} util.ServerError "Internal server error" // @Router /spaces/{space_id}/objects [post] func CreateObjectHandler(s *ObjectService) gin.HandlerFunc { diff --git a/core/api/internal/object/model.go b/core/api/internal/object/model.go index 70ba449aa0..d591214913 100644 --- a/core/api/internal/object/model.go +++ b/core/api/internal/object/model.go @@ -1,93 +1,93 @@ package object type CreateObjectRequest struct { - Name string `json:"name" example:"Object Name"` - Icon string `json:"icon" example:"📄"` - Description string `json:"description" example:"Object Description"` - Body string `json:"body" example:"Object Body"` - Source string `json:"source" example:"https://source.com"` - TemplateId string `json:"template_id" example:"bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge"` - ObjectTypeUniqueKey string `json:"object_type_unique_key" example:"ot-page"` + Name string `json:"name" example:"My object"` // The name of the object + Icon string `json:"icon" example:"📄"` // The icon of the object + Description string `json:"description" example:"This is a description of the object."` // The description of the object + Body string `json:"body" example:"This is the body of the object. Markdown syntax is supported here."` // The body of the object + Source string `json:"source" example:"https://bookmark-source.com"` // The source url, only applicable for bookmarks + TemplateId string `json:"template_id" example:"bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge"` // The id of the template to use + ObjectTypeUniqueKey string `json:"object_type_unique_key" example:"ot-page"` // The unique key of the object type } type ObjectResponse struct { - Object Object `json:"object"` + Object Object `json:"object"` // The object } type Object struct { - Type string `json:"type" example:"Page"` - Id string `json:"id" example:"bafyreie6n5l5nkbjal37su54cha4coy7qzuhrnajluzv5qd5jvtsrxkequ"` - Name string `json:"name" example:"Object Name"` - Icon string `json:"icon" example:"📄"` - Snippet string `json:"snippet" example:"The beginning of the object body..."` - Layout string `json:"layout" example:"basic"` - SpaceId string `json:"space_id" example:"bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1"` - RootId string `json:"root_id" example:"bafyreicypzj6uvu54664ucv3hmbsd5cmdy2dv4fwua26sciq74khzpyn4u"` - Blocks []Block `json:"blocks"` - Details []Detail `json:"details"` + Type string `json:"type" example:"Page"` // The type of the object + Id string `json:"id" example:"bafyreie6n5l5nkbjal37su54cha4coy7qzuhrnajluzv5qd5jvtsrxkequ"` // The id of the object + Name string `json:"name" example:"My object"` // The name of the object + Icon string `json:"icon" example:"📄"` // The icon of the object + Snippet string `json:"snippet" example:"The beginning of the object body..."` // The snippet of the object, especially important for notes as they don't have a name + Layout string `json:"layout" example:"basic"` // The layout of the object + SpaceId string `json:"space_id" example:"bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1"` // The id of the space the object is in + RootId string `json:"root_id" example:"bafyreicypzj6uvu54664ucv3hmbsd5cmdy2dv4fwua26sciq74khzpyn4u"` // The id of the object's root + Blocks []Block `json:"blocks"` // The blocks of the object + Details []Detail `json:"details"` // The details of the object } type Block struct { - Id string `json:"id" example:"64394517de52ad5acb89c66c"` - ChildrenIds []string `json:"children_ids" example:"['6797ce8ecda913cde14b02dc']"` - BackgroundColor string `json:"background_color" example:"red"` - Align string `json:"align" enums:"AlignLeft,AlignCenter,AlignRight,AlignJustify" example:"AlignLeft"` - VerticalAlign string `json:"vertical_align" enums:"VerticalAlignTop,VerticalAlignMiddle,VerticalAlignBottom" example:"VerticalAlignTop"` - Text *Text `json:"text,omitempty"` - File *File `json:"file,omitempty"` + Id string `json:"id" example:"64394517de52ad5acb89c66c"` // The id of the block + ChildrenIds []string `json:"children_ids" example:"['6797ce8ecda913cde14b02dc']"` // The ids of the block's children + BackgroundColor string `json:"background_color" example:"red"` // The background color of the block + Align string `json:"align" enums:"AlignLeft,AlignCenter,AlignRight,AlignJustify" example:"AlignLeft"` // The alignment of the block + VerticalAlign string `json:"vertical_align" enums:"VerticalAlignTop,VerticalAlignMiddle,VerticalAlignBottom" example:"VerticalAlignTop"` // The vertical alignment of the block + Text *Text `json:"text,omitempty"` // The text of the block, if applicable + File *File `json:"file,omitempty"` // The file of the block, if applicable } type Text struct { - Text string `json:"text" example:"Some text"` - Style string `json:"style" enums:"Paragraph,Header1,Header2,Header3,Header4,Quote,Code,Title,Checkbox,Marked,Numbered,Toggle,Description,Callout" example:"Paragraph"` - Checked bool `json:"checked" example:"true"` - Color string `json:"color" example:"red"` - Icon string `json:"icon" example:"📄"` + Text string `json:"text" example:"Some text..."` // The text + Style string `json:"style" enums:"Paragraph,Header1,Header2,Header3,Header4,Quote,Code,Title,Checkbox,Marked,Numbered,Toggle,Description,Callout" example:"Paragraph"` // The style of the text + Checked bool `json:"checked" example:"true"` // Whether the text is checked + Color string `json:"color" example:"red"` // The color of the text + Icon string `json:"icon" example:"📄"` // The icon of the text } type File struct { - Hash string `json:"hash"` - Name string `json:"name"` - Type string `json:"type"` - Mime string `json:"mime"` - Size int `json:"size"` - AddedAt int `json:"added_at"` - TargetObjectId string `json:"target_object_id"` - State string `json:"state"` - Style string `json:"style"` + Hash string `json:"hash"` // The hash of the file + Name string `json:"name"` // The name of the file + Type string `json:"type"` // The type of the file + Mime string `json:"mime"` // The mime of the file + Size int `json:"size"` // The size of the file + AddedAt int `json:"added_at"` // The added at of the file + TargetObjectId string `json:"target_object_id"` // The target object id of the file + State string `json:"state"` // The state of the file + Style string `json:"style"` // The style of the file } type Detail struct { - Id string `json:"id" enums:"last_modified_date,last_modified_by,created_date,created_by,last_opened_date,tags" example:"last_modified_date"` - Details map[string]interface{} `json:"details"` + Id string `json:"id" enums:"last_modified_date,last_modified_by,created_date,created_by,last_opened_date,tags" example:"last_modified_date"` // The id of the detail + Details map[string]interface{} `json:"details"` // The details } type Tag struct { - Id string `json:"id" example:"bafyreiaixlnaefu3ci22zdenjhsdlyaeeoyjrsid5qhfeejzlccijbj7sq"` - Name string `json:"name" example:"Tag Name"` - Color string `json:"color" example:"yellow"` + Id string `json:"id" example:"bafyreiaixlnaefu3ci22zdenjhsdlyaeeoyjrsid5qhfeejzlccijbj7sq"` // The id of the tag + Name string `json:"name" example:"in-progress"` // The name of the tag + Color string `json:"color" example:"yellow"` // The color of the tag } type TypeResponse struct { - Type Type `json:"type"` + Type Type `json:"type"` // The type } type Type struct { - Type string `json:"type" example:"type"` - Id string `json:"id" example:"bafyreigyb6l5szohs32ts26ku2j42yd65e6hqy2u3gtzgdwqv6hzftsetu"` - UniqueKey string `json:"unique_key" example:"ot-page"` - Name string `json:"name" example:"Page"` - Icon string `json:"icon" example:"📄"` - RecommendedLayout string `json:"recommended_layout" example:"todo"` + Type string `json:"type" example:"type"` // The type of the object + Id string `json:"id" example:"bafyreigyb6l5szohs32ts26ku2j42yd65e6hqy2u3gtzgdwqv6hzftsetu"` // The id of the type + UniqueKey string `json:"unique_key" example:"ot-page"` // The unique key of the type + Name string `json:"name" example:"Page"` // The name of the type + Icon string `json:"icon" example:"📄"` // The icon of the type + RecommendedLayout string `json:"recommended_layout" example:"todo"` // The recommended layout of the type } type TemplateResponse struct { - Template Template `json:"template"` + Template Template `json:"template"` // The template } type Template struct { - Type string `json:"type" example:"template"` - Id string `json:"id" example:"bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge"` - Name string `json:"name" example:"Template Name"` - Icon string `json:"icon" example:"📄"` + Type string `json:"type" example:"template"` // The type of the object + Id string `json:"id" example:"bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge"` // The id of the template + Name string `json:"name" example:"My template"` // The name of the template + Icon string `json:"icon" example:"📄"` // The icon of the template } diff --git a/core/api/internal/object/service.go b/core/api/internal/object/service.go index 91efda7b20..284a588ad3 100644 --- a/core/api/internal/object/service.go +++ b/core/api/internal/object/service.go @@ -447,6 +447,16 @@ func (s *ObjectService) GetDetails(resp *pb.RpcObjectShowResponse) []Detail { } } + memberLastModifiedBy, err := s.spaceService.GetMember(context.Background(), resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeySpaceId.String()].GetStringValue(), lastModifiedById) + if err != nil { + memberLastModifiedBy = space.Member{} + } + + memberCreator, err := s.spaceService.GetMember(context.Background(), resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeySpaceId.String()].GetStringValue(), creatorId) + if err != nil { + memberCreator = space.Member{} + } + return []Detail{ { Id: "last_modified_date", @@ -457,7 +467,7 @@ func (s *ObjectService) GetDetails(resp *pb.RpcObjectShowResponse) []Detail { { Id: "last_modified_by", Details: map[string]interface{}{ - "details": s.spaceService.GetParticipantDetails(s.mw, resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeySpaceId.String()].GetStringValue(), lastModifiedById), + "details": memberLastModifiedBy, }, }, { @@ -469,7 +479,7 @@ func (s *ObjectService) GetDetails(resp *pb.RpcObjectShowResponse) []Detail { { Id: "created_by", Details: map[string]interface{}{ - "details": s.spaceService.GetParticipantDetails(s.mw, resp.ObjectView.Details[0].Details.Fields[bundle.RelationKeySpaceId.String()].GetStringValue(), creatorId), + "details": memberCreator, }, }, { diff --git a/core/api/internal/object/service_test.go b/core/api/internal/object/service_test.go index 10ba7e1c4d..5ae1f6ba25 100644 --- a/core/api/internal/object/service_test.go +++ b/core/api/internal/object/service_test.go @@ -390,7 +390,7 @@ func TestObjectService_CreateObject(t *testing.T) { bundle.RelationKeyOrigin.String(): pbtypes.Int64(int64(model.ObjectOrigin_api)), }, }, - TemplateId: "", + TemplateId: mockedTemplateId, SpaceId: mockedSpaceId, ObjectTypeUniqueKey: mockedObjectTypeUniqueKey, WithChat: false, @@ -483,10 +483,9 @@ func TestObjectService_CreateObject(t *testing.T) { // when object, err := fx.CreateObject(ctx, mockedSpaceId, CreateObjectRequest{ - Name: mockedObjectName, - Icon: mockedObjectIcon, - // TODO: use actual values - TemplateId: "", + Name: mockedObjectName, + Icon: mockedObjectIcon, + TemplateId: mockedTemplateId, ObjectTypeUniqueKey: mockedObjectTypeUniqueKey, }) diff --git a/core/api/internal/search/model.go b/core/api/internal/search/model.go index f52cf59e42..1709338418 100644 --- a/core/api/internal/search/model.go +++ b/core/api/internal/search/model.go @@ -1,12 +1,12 @@ package search type SearchRequest struct { - Query string `json:"query"` - Types []string `json:"types"` - Sort SortOptions `json:"sort"` + Query string `json:"query" example:"test"` // The search term to look for in object names and snippets + Types []string `json:"types" example:"ot-note,ot-page,ot-678043f0cda9133be777049f,bafyreightzrdts2ymxyaeyzspwdfo2juspyam76ewq6qq7ixnw3523gs7q"` // The types of objects to search for, specified by unique key or ID + Sort SortOptions `json:"sort"` // The sorting criteria and direction for the search results } type SortOptions struct { - Direction string `json:"direction" enums:"asc,desc" default:"desc"` - Timestamp string `json:"timestamp" enums:"created_date,last_modified_date,last_opened_date" default:"last_modified_date"` + Direction string `json:"direction" enums:"asc,desc" default:"desc"` // The direction to sort the search results + Timestamp string `json:"timestamp" enums:"created_date,last_modified_date,last_opened_date" default:"last_modified_date"` // The timestamp to sort the search results by } diff --git a/core/api/internal/search/service.go b/core/api/internal/search/service.go index 58c783ef38..ecd2408718 100644 --- a/core/api/internal/search/service.go +++ b/core/api/internal/search/service.go @@ -48,7 +48,7 @@ func (s *SearchService) GlobalSearch(ctx context.Context, request SearchRequest, baseFilters := s.prepareBaseFilters() queryFilters := s.prepareQueryFilter(request.Query) sorts := s.prepareSorts(request.Sort) - dateToSortAfter := sorts.RelationKey + dateToSortAfter := sorts[0].RelationKey allResponses := make([]*pb.RpcObjectSearchResponse, 0, len(spaces)) for _, space := range spaces { @@ -59,7 +59,7 @@ func (s *SearchService) GlobalSearch(ctx context.Context, request SearchRequest, objResp := s.mw.ObjectSearch(ctx, &pb.RpcObjectSearchRequest{ SpaceId: space.Id, Filters: filters, - Sorts: []*model.BlockContentDataviewSort{sorts}, + Sorts: sorts, Keys: []string{bundle.RelationKeyId.String(), bundle.RelationKeySpaceId.String(), dateToSortAfter}, Limit: int32(offset + limit), // nolint: gosec }) @@ -91,7 +91,7 @@ func (s *SearchService) GlobalSearch(ctx context.Context, request SearchRequest, } // sort after posix last_modified_date to achieve descending sort order across all spaces - sort.Slice(combinedRecords, func(i, j int) bool { + sort.SliceStable(combinedRecords, func(i, j int) bool { return combinedRecords[i].DateToSortAfter > combinedRecords[j].DateToSortAfter }) @@ -118,12 +118,12 @@ func (s *SearchService) Search(ctx context.Context, spaceId string, request Sear filters := s.combineFilters(model.BlockContentDataviewFilter_And, baseFilters, queryFilters, typeFilters) sorts := s.prepareSorts(request.Sort) - dateToSortAfter := sorts.RelationKey + dateToSortAfter := sorts[0].RelationKey resp := s.mw.ObjectSearch(ctx, &pb.RpcObjectSearchRequest{ SpaceId: spaceId, Filters: filters, - Sorts: []*model.BlockContentDataviewSort{sorts}, + Sorts: sorts, Keys: []string{bundle.RelationKeyId.String(), bundle.RelationKeySpaceId.String(), dateToSortAfter}, }) @@ -262,14 +262,28 @@ func (s *SearchService) prepareObjectTypeFilters(spaceId string, objectTypes []s } // prepareSorts returns a sort filter based on the given sort parameters -func (s *SearchService) prepareSorts(sort SortOptions) *model.BlockContentDataviewSort { - return &model.BlockContentDataviewSort{ +func (s *SearchService) prepareSorts(sort SortOptions) []*model.BlockContentDataviewSort { + primarySort := &model.BlockContentDataviewSort{ RelationKey: s.getSortRelationKey(sort.Timestamp), Type: s.getSortDirection(sort.Direction), Format: model.RelationFormat_date, IncludeTime: true, EmptyPlacement: model.BlockContentDataviewSort_NotSpecified, } + + // last_opened_date possibly is empty, wherefore we sort by last_modified_date as secondary criterion + if primarySort.RelationKey == bundle.RelationKeyLastOpenedDate.String() { + secondarySort := &model.BlockContentDataviewSort{ + RelationKey: bundle.RelationKeyLastModifiedDate.String(), + Type: s.getSortDirection(sort.Direction), + Format: model.RelationFormat_date, + IncludeTime: true, + EmptyPlacement: model.BlockContentDataviewSort_NotSpecified, + } + return []*model.BlockContentDataviewSort{primarySort, secondarySort} + } + + return []*model.BlockContentDataviewSort{primarySort} } // getSortRelationKey returns the relation key for the given sort timestamp diff --git a/core/api/internal/space/handler.go b/core/api/internal/space/handler.go index 2a6ca043de..5dbbd2cd83 100644 --- a/core/api/internal/space/handler.go +++ b/core/api/internal/space/handler.go @@ -42,6 +42,38 @@ func GetSpacesHandler(s *SpaceService) gin.HandlerFunc { } } +// GetSpaceHandler retrieves a space +// +// @Summary Get space +// @Tags spaces +// @Accept json +// @Produce json +// @Param space_id path string true "Space ID" +// @Success 200 {object} SpaceResponse "Space" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 404 {object} util.NotFoundError "Space not found" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Router /spaces/{space_id} [get] +func GetSpaceHandler(s *SpaceService) gin.HandlerFunc { + return func(c *gin.Context) { + spaceId := c.Param("space_id") + + space, err := s.GetSpace(c.Request.Context(), spaceId) + code := util.MapErrorCode(err, + util.ErrToCode(ErrWorkspaceNotFound, http.StatusNotFound), + util.ErrToCode(ErrFailedOpenWorkspace, http.StatusInternalServerError), + ) + + if code != http.StatusOK { + apiErr := util.CodeToAPIError(code, err.Error()) + c.JSON(code, apiErr) + return + } + + c.JSON(http.StatusOK, SpaceResponse{Space: space}) + } +} + // CreateSpaceHandler creates a new space // // @Summary Create space @@ -49,9 +81,10 @@ func GetSpacesHandler(s *SpaceService) gin.HandlerFunc { // @Accept json // @Produce json // @Param name body CreateSpaceRequest true "Space to create" -// @Success 200 {object} CreateSpaceResponse "Space created successfully" +// @Success 200 {object} SpaceResponse "Space created successfully" // @Failure 400 {object} util.ValidationError "Bad request" // @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 423 {object} util.RateLimitError "Rate limit exceeded" // @Failure 500 {object} util.ServerError "Internal server error" // @Router /spaces [post] func CreateSpaceHandler(s *SpaceService) gin.HandlerFunc { @@ -74,7 +107,7 @@ func CreateSpaceHandler(s *SpaceService) gin.HandlerFunc { return } - c.JSON(http.StatusOK, CreateSpaceResponse{Space: space}) + c.JSON(http.StatusOK, SpaceResponse{Space: space}) } } @@ -111,3 +144,37 @@ func GetMembersHandler(s *SpaceService) gin.HandlerFunc { pagination.RespondWithPagination(c, http.StatusOK, members, total, offset, limit, hasMore) } } + +// GetMemberHandler retrieves a member in a space +// +// @Summary Get member +// @Tags spaces +// @Accept json +// @Produce json +// @Param space_id path string true "Space ID" +// @Param member_id path string true "Member ID" +// @Success 200 {object} MemberResponse "Member" +// @Failure 401 {object} util.UnauthorizedError "Unauthorized" +// @Failure 404 {object} util.NotFoundError "Member not found" +// @Failure 500 {object} util.ServerError "Internal server error" +// @Router /spaces/{space_id}/members/{member_id} [get] +func GetMemberHandler(s *SpaceService) gin.HandlerFunc { + return func(c *gin.Context) { + spaceId := c.Param("space_id") + memberId := c.Param("member_id") + + member, err := s.GetMember(c.Request.Context(), spaceId, memberId) + code := util.MapErrorCode(err, + util.ErrToCode(ErrMemberNotFound, http.StatusNotFound), + util.ErrToCode(ErrFailedGetMember, http.StatusInternalServerError), + ) + + if code != http.StatusOK { + apiErr := util.CodeToAPIError(code, err.Error()) + c.JSON(code, apiErr) + return + } + + c.JSON(http.StatusOK, MemberResponse{Member: member}) + } +} diff --git a/core/api/internal/space/model.go b/core/api/internal/space/model.go index d13951c3f5..6805de3ccb 100644 --- a/core/api/internal/space/model.go +++ b/core/api/internal/space/model.go @@ -1,41 +1,45 @@ package space -type CreateSpaceRequest struct { - Name string `json:"name" example:"New Space"` +type SpaceResponse struct { + Space Space `json:"space"` // The space } -type CreateSpaceResponse struct { - Space Space `json:"space"` +type CreateSpaceRequest struct { + Name string `json:"name" example:"New Space"` // The name of the space } type Space struct { - Type string `json:"type" example:"space"` - Id string `json:"id" example:"bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1"` - Name string `json:"name" example:"Space Name"` - Icon string `json:"icon" example:"http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay"` - HomeObjectId string `json:"home_object_id" example:"bafyreie4qcl3wczb4cw5hrfyycikhjyh6oljdis3ewqrk5boaav3sbwqya"` - ArchiveObjectId string `json:"archive_object_id" example:"bafyreialsgoyflf3etjm3parzurivyaukzivwortf32b4twnlwpwocsrri"` - ProfileObjectId string `json:"profile_object_id" example:"bafyreiaxhwreshjqwndpwtdsu4mtihaqhhmlygqnyqpfyfwlqfq3rm3gw4"` - MarketplaceWorkspaceId string `json:"marketplace_workspace_id" example:"_anytype_marketplace"` - WorkspaceObjectId string `json:"workspace_object_id" example:"bafyreiapey2g6e6za4zfxvlgwdy4hbbfu676gmwrhnqvjbxvrchr7elr3y"` - DeviceId string `json:"device_id" example:"12D3KooWGZMJ4kQVyQVXaj7gJPZr3RZ2nvd9M2Eq2pprEoPih9WF"` - AccountSpaceId string `json:"account_space_id" example:"bafyreihpd2knon5wbljhtfeg3fcqtg3i2pomhhnigui6lrjmzcjzep7gcy.23me69r569oi1"` - WidgetsId string `json:"widgets_id" example:"bafyreialj7pceh53mifm5dixlho47ke4qjmsn2uh4wsjf7xq2pnlo5xfva"` - SpaceViewId string `json:"space_view_id" example:"bafyreigzv3vq7qwlrsin6njoduq727ssnhwd6bgyfj6nm4hv3pxoc2rxhy"` - TechSpaceId string `json:"tech_space_id" example:"bafyreif4xuwncrjl6jajt4zrrfnylpki476nv2w64yf42ovt7gia7oypii.23me69r569oi1"` - GatewayUrl string `json:"gateway_url" example:"http://127.0.0.1:31006"` - LocalStoragePath string `json:"local_storage_path" example:"/Users/johndoe/Library/Application Support/Anytype/data/AAHTtt1wuQEnaYBNZ2Cyfcvs6DqPqxgn8VXDVk4avsUkMuha"` - Timezone string `json:"timezone" example:""` - AnalyticsId string `json:"analytics_id" example:"624aecdd-4797-4611-9d61-a2ae5f53cf1c"` - NetworkId string `json:"network_id" example:"N83gJpVd9MuNRZAuJLZ7LiMntTThhPc6DtzWWVjb1M3PouVU"` + Type string `json:"type" example:"space"` // The type of the object + Id string `json:"id" example:"bafyreigyfkt6rbv24sbv5aq2hko3bhmv5xxlf22b4bypdu6j7hnphm3psq.23me69r569oi1"` // The id of the space + Name string `json:"name" example:"My Space"` // The name of the space + Icon string `json:"icon" example:"http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay"` // The icon of the space + HomeObjectId string `json:"home_object_id" example:"bafyreie4qcl3wczb4cw5hrfyycikhjyh6oljdis3ewqrk5boaav3sbwqya"` // The id of the home object + ArchiveObjectId string `json:"archive_object_id" example:"bafyreialsgoyflf3etjm3parzurivyaukzivwortf32b4twnlwpwocsrri"` // The id of the archive object + ProfileObjectId string `json:"profile_object_id" example:"bafyreiaxhwreshjqwndpwtdsu4mtihaqhhmlygqnyqpfyfwlqfq3rm3gw4"` // The id of the profile object + MarketplaceWorkspaceId string `json:"marketplace_workspace_id" example:"_anytype_marketplace"` // The id of the marketplace workspace + WorkspaceObjectId string `json:"workspace_object_id" example:"bafyreiapey2g6e6za4zfxvlgwdy4hbbfu676gmwrhnqvjbxvrchr7elr3y"` // The id of the workspace object + DeviceId string `json:"device_id" example:"12D3KooWGZMJ4kQVyQVXaj7gJPZr3RZ2nvd9M2Eq2pprEoPih9WF"` // The id of the device + AccountSpaceId string `json:"account_space_id" example:"bafyreihpd2knon5wbljhtfeg3fcqtg3i2pomhhnigui6lrjmzcjzep7gcy.23me69r569oi1"` // The id of the account space + WidgetsId string `json:"widgets_id" example:"bafyreialj7pceh53mifm5dixlho47ke4qjmsn2uh4wsjf7xq2pnlo5xfva"` // The id of the widgets + SpaceViewId string `json:"space_view_id" example:"bafyreigzv3vq7qwlrsin6njoduq727ssnhwd6bgyfj6nm4hv3pxoc2rxhy"` // The id of the space view + TechSpaceId string `json:"tech_space_id" example:"bafyreif4xuwncrjl6jajt4zrrfnylpki476nv2w64yf42ovt7gia7oypii.23me69r569oi1"` // The id of tech space, where objects outside of user's actual spaces are stored, e.g. spaces itself + GatewayUrl string `json:"gateway_url" example:"http://127.0.0.1:31006"` // The gateway url to serve files and media + LocalStoragePath string `json:"local_storage_path" example:"/Users/johndoe/Library/Application Support/Anytype/data/AAHTtt1wuQEnaYBNZ2Cyfcvs6DqPqxgn8VXDVk4avsUkMuha"` // The local storage path of the account + Timezone string `json:"timezone" example:""` // The timezone of the account + AnalyticsId string `json:"analytics_id" example:"624aecdd-4797-4611-9d61-a2ae5f53cf1c"` // The analytics id of the account + NetworkId string `json:"network_id" example:"N83gJpVd9MuNRZAuJLZ7LiMntTThhPc6DtzWWVjb1M3PouVU"` // The network id of the space +} + +type MemberResponse struct { + Member Member `json:"member"` // The member } type Member struct { - Type string `json:"type" example:"member"` - Id string `json:"id" example:"_participant_bafyreigyfkt6rbv24sbv5aq2hko1bhmv5xxlf22b4bypdu6j7hnphm3psq_23me69r569oi1_AAjEaEwPF4nkEh9AWkqEnzcQ8HziBB4ETjiTpvRCQvWnSMDZ"` - Name string `json:"name" example:"John Doe"` - Icon string `json:"icon" example:"http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay?width=100"` - Identity string `json:"identity" example:"AAjEaEwPF4nkEh7AWkqEnzcQ8HziGB4ETjiTpvRCQvWnSMDZ"` - GlobalName string `json:"global_name" example:"john.any"` - Role string `json:"role" enums:"Reader,Writer,Owner,NoPermission" example:"Owner"` + Type string `json:"type" example:"member"` // The type of the object + Id string `json:"id" example:"_participant_bafyreigyfkt6rbv24sbv5aq2hko1bhmv5xxlf22b4bypdu6j7hnphm3psq_23me69r569oi1_AAjEaEwPF4nkEh9AWkqEnzcQ8HziBB4ETjiTpvRCQvWnSMDZ"` // The profile object id of the member + Name string `json:"name" example:"John Doe"` // The name of the member + Icon string `json:"icon" example:"http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay?width=100"` // The icon of the member + Identity string `json:"identity" example:"AAjEaEwPF4nkEh7AWkqEnzcQ8HziGB4ETjiTpvRCQvWnSMDZ"` // The identity of the member in the network + GlobalName string `json:"global_name" example:"john.any"` // The global name of the member in the network + Role string `json:"role" enums:"Reader,Writer,Owner,NoPermission" example:"Owner"` // The role of the member } diff --git a/core/api/internal/space/service.go b/core/api/internal/space/service.go index 9a617b8472..eb83f636c7 100644 --- a/core/api/internal/space/service.go +++ b/core/api/internal/space/service.go @@ -20,15 +20,20 @@ import ( var ( ErrFailedListSpaces = errors.New("failed to retrieve list of spaces") ErrFailedOpenWorkspace = errors.New("failed to open workspace") + ErrWorkspaceNotFound = errors.New("workspace not found") ErrFailedGenerateRandomIcon = errors.New("failed to generate random icon") ErrFailedCreateSpace = errors.New("failed to create space") ErrFailedListMembers = errors.New("failed to retrieve list of members") + ErrFailedGetMember = errors.New("failed to retrieve member") + ErrMemberNotFound = errors.New("member not found") ) type Service interface { ListSpaces(ctx context.Context, offset int, limit int) ([]Space, int, bool, error) + GetSpace(ctx context.Context, spaceId string) (Space, error) CreateSpace(ctx context.Context, name string) (Space, error) ListMembers(ctx context.Context, spaceId string, offset int, limit int) ([]Member, int, bool, error) + GetMember(ctx context.Context, spaceId string, memberId string) (Member, error) } type SpaceService struct { @@ -78,21 +83,55 @@ func (s *SpaceService) ListSpaces(ctx context.Context, offset int, limit int) (s spaces = make([]Space, 0, len(paginatedRecords)) for _, record := range paginatedRecords { - workspace, err := s.getWorkspaceInfo(record.Fields[bundle.RelationKeyTargetSpaceId.String()].GetStringValue()) + name := record.Fields[bundle.RelationKeyName.String()].GetStringValue() + icon := util.GetIconFromEmojiOrImage(s.AccountInfo, record.Fields[bundle.RelationKeyIconEmoji.String()].GetStringValue(), record.Fields[bundle.RelationKeyIconImage.String()].GetStringValue()) + + workspace, err := s.getWorkspaceInfo(record.Fields[bundle.RelationKeyTargetSpaceId.String()].GetStringValue(), name, icon) if err != nil { return nil, 0, false, err } - // TODO: name and icon are only returned here; fix that - workspace.Name = record.Fields[bundle.RelationKeyName.String()].GetStringValue() - workspace.Icon = util.GetIconFromEmojiOrImage(s.AccountInfo, record.Fields[bundle.RelationKeyIconEmoji.String()].GetStringValue(), record.Fields[bundle.RelationKeyIconImage.String()].GetStringValue()) - spaces = append(spaces, workspace) } return spaces, total, hasMore, nil } +// GetSpace returns the space info for the space with the given ID. +func (s *SpaceService) GetSpace(ctx context.Context, spaceId string) (Space, error) { + // Check if the workspace exists and is active + resp := s.mw.ObjectSearch(ctx, &pb.RpcObjectSearchRequest{ + SpaceId: s.AccountInfo.TechSpaceId, + Filters: []*model.BlockContentDataviewFilter{ + { + Operator: model.BlockContentDataviewFilter_No, + RelationKey: bundle.RelationKeyTargetSpaceId.String(), + Condition: model.BlockContentDataviewFilter_Equal, + Value: pbtypes.String(spaceId), + }, + { + Operator: model.BlockContentDataviewFilter_No, + RelationKey: bundle.RelationKeySpaceLocalStatus.String(), + Condition: model.BlockContentDataviewFilter_Equal, + Value: pbtypes.Int64(int64(model.SpaceStatus_Ok)), + }, + }, + Keys: []string{bundle.RelationKeyTargetSpaceId.String(), bundle.RelationKeyName.String(), bundle.RelationKeyIconEmoji.String(), bundle.RelationKeyIconImage.String()}, + }) + + if resp.Error.Code != pb.RpcObjectSearchResponseError_NULL { + return Space{}, ErrFailedOpenWorkspace + } + + if len(resp.Records) == 0 { + return Space{}, ErrWorkspaceNotFound + } + + name := resp.Records[0].Fields[bundle.RelationKeyName.String()].GetStringValue() + icon := util.GetIconFromEmojiOrImage(s.AccountInfo, resp.Records[0].Fields[bundle.RelationKeyIconEmoji.String()].GetStringValue(), resp.Records[0].Fields[bundle.RelationKeyIconImage.String()].GetStringValue()) + return s.getWorkspaceInfo(spaceId, name, icon) +} + // CreateSpace creates a new space with the given name and returns the space info. func (s *SpaceService) CreateSpace(ctx context.Context, name string) (Space, error) { iconOption, err := rand.Int(rand.Reader, big.NewInt(13)) @@ -117,7 +156,7 @@ func (s *SpaceService) CreateSpace(ctx context.Context, name string) (Space, err return Space{}, ErrFailedCreateSpace } - return s.getWorkspaceInfo(resp.SpaceId) + return s.getWorkspaceInfo(resp.SpaceId, name, "") } // ListMembers returns a paginated list of members in the space with the given ID. @@ -174,26 +213,27 @@ func (s *SpaceService) ListMembers(ctx context.Context, spaceId string, offset i return members, total, hasMore, nil } -func (s *SpaceService) GetParticipantDetails(mw service.ClientCommandsServer, spaceId string, participantId string) Member { - resp := mw.ObjectSearch(context.Background(), &pb.RpcObjectSearchRequest{ +// GetMember returns the member with the given ID in the space with the given ID. +func (s *SpaceService) GetMember(ctx context.Context, spaceId string, memberId string) (Member, error) { + resp := s.mw.ObjectSearch(context.Background(), &pb.RpcObjectSearchRequest{ SpaceId: spaceId, Filters: []*model.BlockContentDataviewFilter{ { Operator: model.BlockContentDataviewFilter_No, RelationKey: bundle.RelationKeyId.String(), Condition: model.BlockContentDataviewFilter_Equal, - Value: pbtypes.String(participantId), + Value: pbtypes.String(memberId), }, }, Keys: []string{bundle.RelationKeyId.String(), bundle.RelationKeyName.String(), bundle.RelationKeyIconEmoji.String(), bundle.RelationKeyIconImage.String(), bundle.RelationKeyIdentity.String(), bundle.RelationKeyGlobalName.String(), bundle.RelationKeyParticipantPermissions.String()}, }) if resp.Error.Code != pb.RpcObjectSearchResponseError_NULL { - return Member{} + return Member{}, ErrFailedGetMember } if len(resp.Records) == 0 { - return Member{} + return Member{}, ErrMemberNotFound } icon := util.GetIconFromEmojiOrImage(s.AccountInfo, "", resp.Records[0].Fields[bundle.RelationKeyIconImage.String()].GetStringValue()) @@ -206,11 +246,11 @@ func (s *SpaceService) GetParticipantDetails(mw service.ClientCommandsServer, sp Identity: resp.Records[0].Fields[bundle.RelationKeyIdentity.String()].GetStringValue(), GlobalName: resp.Records[0].Fields[bundle.RelationKeyGlobalName.String()].GetStringValue(), Role: model.ParticipantPermissions_name[int32(resp.Records[0].Fields[bundle.RelationKeyParticipantPermissions.String()].GetNumberValue())], - } + }, nil } // getWorkspaceInfo returns the workspace info for the space with the given ID. -func (s *SpaceService) getWorkspaceInfo(spaceId string) (space Space, err error) { +func (s *SpaceService) getWorkspaceInfo(spaceId string, name string, icon string) (space Space, err error) { workspaceResponse := s.mw.WorkspaceOpen(context.Background(), &pb.RpcWorkspaceOpenRequest{ SpaceId: spaceId, WithChat: true, @@ -223,6 +263,8 @@ func (s *SpaceService) getWorkspaceInfo(spaceId string) (space Space, err error) return Space{ Type: "space", Id: spaceId, + Name: name, + Icon: icon, HomeObjectId: workspaceResponse.Info.HomeObjectId, ArchiveObjectId: workspaceResponse.Info.ArchiveObjectId, ProfileObjectId: workspaceResponse.Info.ProfileObjectId, diff --git a/core/api/pagination/model.go b/core/api/pagination/model.go index 06539ce635..3d679fec48 100644 --- a/core/api/pagination/model.go +++ b/core/api/pagination/model.go @@ -1,13 +1,13 @@ package pagination type PaginationMeta struct { - Total int `json:"total" example:"1024"` // the total number of items available on that endpoint - Offset int `json:"offset" example:"0"` // the current offset - Limit int `json:"limit" example:"100"` // the current limit - HasMore bool `json:"has_more" example:"true"` // whether there are more items available + Total int `json:"total" example:"1024"` // The total number of items available for the endpoint + Offset int `json:"offset" example:"0"` // The number of items skipped before starting to collect the result set + Limit int `json:"limit" example:"100"` // The maximum number of items returned in the result set + HasMore bool `json:"has_more" example:"true"` // Indicates if there are more items available beyond the current result set } type PaginatedResponse[T any] struct { - Data []T `json:"data"` - Pagination PaginationMeta `json:"pagination"` + Data []T `json:"data"` // The list of items in the current result set + Pagination PaginationMeta `json:"pagination"` // The pagination metadata for the response } diff --git a/core/api/server/middleware.go b/core/api/server/middleware.go index da05740b8c..d2a2d23c94 100644 --- a/core/api/server/middleware.go +++ b/core/api/server/middleware.go @@ -11,6 +11,7 @@ import ( "github.com/gin-gonic/gin" "github.com/anyproto/anytype-heart/core/anytype/account" + "github.com/anyproto/anytype-heart/core/api/util" "github.com/anyproto/anytype-heart/pb" "github.com/anyproto/anytype-heart/pb/service" ) @@ -26,7 +27,8 @@ func (s *Server) rateLimit(max float64) gin.HandlerFunc { return func(c *gin.Context) { httpError := tollbooth.LimitByRequest(lmt, c.Writer, c.Request) if httpError != nil { - c.AbortWithStatusJSON(httpError.StatusCode, gin.H{"error": httpError.Message}) + apiErr := util.CodeToAPIError(httpError.StatusCode, httpError.Message) + c.AbortWithStatusJSON(httpError.StatusCode, apiErr) return } c.Next() @@ -38,12 +40,14 @@ func (s *Server) ensureAuthenticated(mw service.ClientCommandsServer) gin.Handle return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") if authHeader == "" { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Missing Authorization header"}) + apiErr := util.CodeToAPIError(http.StatusUnauthorized, "Missing Authorization header") + c.AbortWithStatusJSON(http.StatusUnauthorized, apiErr) return } if !strings.HasPrefix(authHeader, "Bearer ") { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid Authorization header format"}) + apiErr := util.CodeToAPIError(http.StatusUnauthorized, "Invalid Authorization header format") + c.AbortWithStatusJSON(http.StatusUnauthorized, apiErr) return } key := strings.TrimPrefix(authHeader, "Bearer ") @@ -57,7 +61,8 @@ func (s *Server) ensureAuthenticated(mw service.ClientCommandsServer) gin.Handle if !exists { response := mw.WalletCreateSession(context.Background(), &pb.RpcWalletCreateSessionRequest{Auth: &pb.RpcWalletCreateSessionRequestAuthOfAppKey{AppKey: key}}) if response.Error.Code != pb.RpcWalletCreateSessionResponseError_NULL { - c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"}) + apiErr := util.CodeToAPIError(http.StatusUnauthorized, "Invalid token") + c.AbortWithStatusJSON(http.StatusUnauthorized, apiErr) return } token = response.Token @@ -78,7 +83,8 @@ func (s *Server) ensureAccountInfo(accountService account.Service) gin.HandlerFu return func(c *gin.Context) { accInfo, err := accountService.GetInfo(context.Background()) if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get account info: %v", err)}) + apiErr := util.CodeToAPIError(http.StatusInternalServerError, fmt.Sprintf("failed to get account info: %v", err)) + c.AbortWithStatusJSON(http.StatusInternalServerError, apiErr) return } diff --git a/core/api/server/router.go b/core/api/server/router.go index e33206a325..ed1b00a990 100644 --- a/core/api/server/router.go +++ b/core/api/server/router.go @@ -78,7 +78,9 @@ func (s *Server) NewRouter(accountService account.Service, mw service.ClientComm // Space v1.GET("/spaces", space.GetSpacesHandler(s.spaceService)) + v1.GET("/spaces/:space_id", space.GetSpaceHandler(s.spaceService)) v1.GET("/spaces/:space_id/members", space.GetMembersHandler(s.spaceService)) + v1.GET("/spaces/:space_id/members/:member_id", space.GetMemberHandler(s.spaceService)) v1.POST("/spaces", s.rateLimit(maxWriteRequestsPerSecond), space.CreateSpaceHandler(s.spaceService)) // Type diff --git a/core/api/util/error.go b/core/api/util/error.go index b7553f3fd2..77aeff751e 100644 --- a/core/api/util/error.go +++ b/core/api/util/error.go @@ -5,38 +5,45 @@ import ( "net/http" ) -// 400 +// ValidationError is a struct for 400 errors type ValidationError struct { Error struct { - Message string `json:"message"` + Message string `json:"message" example:"Bad request"` } `json:"error"` } -// 401 +// UnauthorizedError is a struct for 401 errors type UnauthorizedError struct { Error struct { - Message string `json:"message"` + Message string `json:"message" example:"Unauthorized"` } `json:"error"` } -// 403 +// ForbiddenError is a struct for 403 errors type ForbiddenError struct { Error struct { - Message string `json:"message"` + Message string `json:"message" example:"Forbidden"` } `json:"error"` } -// 404 +// NotFoundError is a struct for 404 errors type NotFoundError struct { Error struct { - Message string `json:"message"` + Message string `json:"message" example:"Resource not found"` } `json:"error"` } -// 500 +// RateLimitError is a struct for 423 errors +type RateLimitError struct { + Error struct { + Message string `json:"message" example:"Rate limit exceeded"` + } `json:"error"` +} + +// ServerError is a struct for 500 errors type ServerError struct { Error struct { - Message string `json:"message"` + Message string `json:"message" example:"Internal server error"` } `json:"error"` } @@ -72,10 +79,11 @@ func MapErrorCode(err error, mappings ...errCodeMapping) int { // for the given HTTP code, embedding the supplied message. func CodeToAPIError(code int, message string) any { switch code { - case http.StatusNotFound: - return NotFoundError{ + + case http.StatusBadRequest: + return ValidationError{ Error: struct { - Message string `json:"message"` + Message string `json:"message" example:"Bad request"` }{ Message: message, }, @@ -84,16 +92,34 @@ func CodeToAPIError(code int, message string) any { case http.StatusUnauthorized: return UnauthorizedError{ Error: struct { - Message string `json:"message"` + Message string `json:"message" example:"Unauthorized"` }{ Message: message, }, } - case http.StatusBadRequest: - return ValidationError{ + case http.StatusForbidden: + return ForbiddenError{ + Error: struct { + Message string `json:"message" example:"Forbidden"` + }{ + Message: message, + }, + } + + case http.StatusNotFound: + return NotFoundError{ + Error: struct { + Message string `json:"message" example:"Resource not found"` + }{ + Message: message, + }, + } + + case http.StatusTooManyRequests: + return RateLimitError{ Error: struct { - Message string `json:"message"` + Message string `json:"message" example:"Rate limit exceeded"` }{ Message: message, }, @@ -102,7 +128,7 @@ func CodeToAPIError(code int, message string) any { default: return ServerError{ Error: struct { - Message string `json:"message"` + Message string `json:"message" example:"Internal server error"` }{ Message: message, },